diff --git a/src/calibre/ebooks/conversion/plugins/docx_output.py b/src/calibre/ebooks/conversion/plugins/docx_output.py index 3254767878..486416253c 100644 --- a/src/calibre/ebooks/conversion/plugins/docx_output.py +++ b/src/calibre/ebooks/conversion/plugins/docx_output.py @@ -28,6 +28,10 @@ class DOCXOutput(OutputFormatPlugin): 'EG. `123x321` to specify the width and height (in pts). ' 'This overrides any specified page-size.')), + OptionRecommendation(name='docx_no_cover', recommended_value=False, + help=_('Do not insert the book cover as an image at the start of the document.' + ' If you use this option, the book cover will be discarded.')), + OptionRecommendation(name='extract_to', help=_('Extract the contents of the generated %s file to the ' 'specified directory. The contents of the directory are first ' @@ -55,7 +59,7 @@ class DOCXOutput(OutputFormatPlugin): from calibre.ebooks.docx.writer.from_html import Convert docx = DOCX(opts, log) self.convert_metadata(oeb) - Convert(oeb, docx, self.mi)() + Convert(oeb, docx, self.mi, not opts.docx_no_cover)() docx.write(output_path, self.mi) if opts.extract_to: from calibre.ebooks.docx.dump import do_dump diff --git a/src/calibre/ebooks/docx/writer/container.py b/src/calibre/ebooks/docx/writer/container.py index 079c039197..9c6b460464 100644 --- a/src/calibre/ebooks/docx/writer/container.py +++ b/src/calibre/ebooks/docx/writer/container.py @@ -27,6 +27,12 @@ def xml2str(root, pretty_print=False, with_tail=False): pretty_print=pretty_print, with_tail=with_tail) return ans +def page_size(opts): + width, height = PAPER_SIZES[opts.docx_page_size] + if opts.docx_custom_page_size is not None: + width, height = map(float, opts.docx_custom_page_size.partition('x')[0::2]) + return width, height + def create_skeleton(opts, namespaces=None): namespaces = namespaces or DOCXNamespace().namespaces def w(x): @@ -36,9 +42,7 @@ def create_skeleton(opts, namespaces=None): doc = E.document() body = E.body() doc.append(body) - width, height = PAPER_SIZES[opts.docx_page_size] - if opts.docx_custom_page_size is not None: - width, height = map(float, opts.docx_custom_page_size.partition('x')[0::2]) + width, height = page_size(opts) width, height = int(20 * width), int(20 * height) def margin(which): return w(which), str(int(getattr(opts, 'margin_'+which) * 20)) diff --git a/src/calibre/ebooks/docx/writer/from_html.py b/src/calibre/ebooks/docx/writer/from_html.py index 14588f0a19..ff7d80267b 100644 --- a/src/calibre/ebooks/docx/writer/from_html.py +++ b/src/calibre/ebooks/docx/writer/from_html.py @@ -9,7 +9,7 @@ __copyright__ = '2013, Kovid Goyal ' import re from collections import Counter -from calibre.ebooks.docx.writer.container import create_skeleton +from calibre.ebooks.docx.writer.container import create_skeleton, page_size from calibre.ebooks.docx.writer.styles import StylesManager, FloatSpec from calibre.ebooks.docx.writer.links import LinksManager from calibre.ebooks.docx.writer.images import ImagesManager @@ -390,10 +390,11 @@ class Convert(object): a[href] { text-decoration: underline; color: blue } ''' - def __init__(self, oeb, docx, mi): - self.oeb, self.docx = oeb, docx + def __init__(self, oeb, docx, mi, add_cover): + self.oeb, self.docx, self.add_cover = oeb, docx, add_cover self.log, self.opts = docx.log, docx.opts self.mi = mi + self.cover_img = None def __call__(self): from calibre.ebooks.oeb.transforms.rasterize import SVGRasterizer @@ -411,6 +412,11 @@ class Convert(object): for item in self.oeb.spine: self.process_item(item) + if self.add_cover and self.oeb.metadata.cover and unicode(self.oeb.metadata.cover[0]) in self.oeb.manifest.ids: + cover_id = unicode(self.oeb.metadata.cover[0]) + item = self.oeb.manifest.ids[cover_id] + self.cover_img = self.images_manager.read_image(item.href) + all_blocks = self.blocks.all_blocks remove_blocks = [] for i, block in enumerate(all_blocks): @@ -427,6 +433,8 @@ class Convert(object): self.blocks.apply_page_break_after() self.blocks.resolve_language() + if self.cover_img is not None: + self.cover_img = self.images_manager.create_cover_markup(self.cover_img, *page_size(self.opts)) self.lists_manager.finalize(all_blocks) self.styles_manager.finalize(all_blocks) self.write() @@ -549,6 +557,8 @@ class Convert(object): self.docx.document, self.docx.styles, body = create_skeleton(self.opts) self.blocks.serialize(body) body.append(body[0]) # Move to the end + if self.cover_img is not None: + self.images_manager.write_cover_block(body, self.cover_img) self.styles_manager.serialize(self.docx.styles) self.images_manager.serialize(self.docx.images) self.fonts_manager.serialize(self.styles_manager.text_styles, self.docx.font_table, self.docx.embedded_fonts, self.docx.fonts) diff --git a/src/calibre/ebooks/docx/writer/images.py b/src/calibre/ebooks/docx/writer/images.py index 48a1364c0d..02208e4910 100644 --- a/src/calibre/ebooks/docx/writer/images.py +++ b/src/calibre/ebooks/docx/writer/images.py @@ -44,11 +44,7 @@ class ImagesManager(object): self.document_relationships = document_relationships self.count = 0 - def add_image(self, img, block, stylizer, bookmark=None, as_block=False): - src = img.get('src') - if not src: - return - href = self.abshref(src) + def read_image(self, href): if href not in self.images: item = self.oeb.manifest.hrefs.get(href) if item is None or not isinstance(item.data, bytes): @@ -58,9 +54,17 @@ class ImagesManager(object): image_rid = self.document_relationships.add_image(image_fname) self.images[href] = Image(image_rid, image_fname, width, height, fmt, item) item.unload_data_from_memory() + return self.images[href] + + def add_image(self, img, block, stylizer, bookmark=None, as_block=False): + src = img.get('src') + if not src: + return + href = self.abshref(src) + rid = self.read_image(href).rid drawing = self.create_image_markup(img, stylizer, href, as_block=as_block) block.add_image(drawing, bookmark=bookmark) - return self.images[href].rid + return rid def create_image_markup(self, html_img, stylizer, href, as_block=False): # TODO: img inside a link (clickable image) @@ -95,7 +99,7 @@ class ImagesManager(object): makeelement(parent, 'wp:simplePos', x='0', y='0') makeelement(makeelement(parent, 'wp:positionH', relativeFrom='margin'), 'wp:align').text = floating makeelement(makeelement(parent, 'wp:positionV', relativeFrom='line'), 'wp:align').text = 'top' - makeelement(parent, 'wp:extent', cx=str(width), cy=str(width)) + makeelement(parent, 'wp:extent', cx=str(width), cy=str(height)) if fake_margins: # DOCX does not support setting margins for inline images, so we # fake it by using effect extents to simulate margins @@ -108,22 +112,26 @@ class ImagesManager(object): makeelement(parent, 'wp:wrapTopAndBottom') else: makeelement(parent, 'wp:wrapSquare', wrapText='bothSides') - makeelement(parent, 'wp:docPr', id=str(self.count), name=name, descr=html_img.get('alt') or name) + self.create_docx_image_markup(parent, name, html_img.get('alt') or name, img.rid, width, height) + return ans + + def create_docx_image_markup(self, parent, name, alt, img_rid, width, height): + makeelement, namespaces = self.document_relationships.namespace.makeelement, self.document_relationships.namespace.namespaces + makeelement(parent, 'wp:docPr', id=str(self.count), name=name, descr=alt) makeelement(makeelement(parent, 'wp:cNvGraphicFramePr'), 'a:graphicFrameLocks', noChangeAspect="1") g = makeelement(parent, 'a:graphic') gd = makeelement(g, 'a:graphicData', uri=namespaces['pic']) pic = makeelement(gd, 'pic:pic') nvPicPr = makeelement(pic, 'pic:nvPicPr') - makeelement(nvPicPr, 'pic:cNvPr', id='0', name=name, descr=html_img.get('alt') or name) + makeelement(nvPicPr, 'pic:cNvPr', id='0', name=name, descr=alt) makeelement(nvPicPr, 'pic:cNvPicPr') bf = makeelement(pic, 'pic:blipFill') - makeelement(bf, 'a:blip', r_embed=img.rid) + makeelement(bf, 'a:blip', r_embed=img_rid) makeelement(makeelement(bf, 'a:stretch'), 'a:fillRect') spPr = makeelement(pic, 'pic:spPr') xfrm = makeelement(spPr, 'a:xfrm') makeelement(xfrm, 'a:off', x='0', y='0'), makeelement(xfrm, 'a:ext', cx=str(width), cy=str(height)) makeelement(makeelement(spPr, 'a:prstGeom', prst='rect'), 'a:avLst') - return ans def create_filename(self, href, fmt): fname = ascii_filename(urlunquote(posixpath.basename(href))) @@ -147,3 +155,31 @@ class ImagesManager(object): return item.data finally: item.unload_data_from_memory(False) + + def create_cover_markup(self, img, width, height): + self.count += 1 + makeelement, namespaces = self.document_relationships.namespace.makeelement, self.document_relationships.namespace.namespaces + + root = etree.Element('root', nsmap=namespaces) + ans = makeelement(root, 'w:drawing', append=False) + parent = makeelement(ans, 'wp:anchor', **{'dist'+edge:'0' for edge in 'LRTB'}) + parent.set('simplePos', '0'), parent.set('relativeHeight', '1'), parent.set('behindDoc',"0"), parent.set('locked', "0") + parent.set('layoutInCell', "1"), parent.set('allowOverlap', '1') + makeelement(parent, 'wp:simplePos', x='0', y='0') + makeelement(makeelement(parent, 'wp:positionH', relativeFrom='page'), 'wp:align').text = 'center' + makeelement(makeelement(parent, 'wp:positionV', relativeFrom='page'), 'wp:align').text = 'center' + width, height = map(pt_to_emu, (width, height)) + makeelement(parent, 'wp:extent', cx=str(width), cy=str(height)) + makeelement(parent, 'wp:effectExtent', l='0', r='0', t='0', b='0') + makeelement(parent, 'wp:wrapTopAndBottom') + self.create_docx_image_markup(parent, 'cover.jpg', _('Cover'), img.rid, width, height) + return ans + + def write_cover_block(self, body, cover_image): + makeelement, namespaces = self.document_relationships.namespace.makeelement, self.document_relationships.namespace.namespaces + pbb = body[0].xpath('//*[local-name()="pageBreakBefore"]')[0] + pbb.set('{%s}val' % namespaces['w'], 'on') + p = makeelement(body, 'w:p', append=False) + body.insert(0, p) + r = makeelement(p, 'w:r') + r.append(cover_image) diff --git a/src/calibre/gui2/convert/docx_output.py b/src/calibre/gui2/convert/docx_output.py index d07ccd3728..9329e9c40e 100644 --- a/src/calibre/gui2/convert/docx_output.py +++ b/src/calibre/gui2/convert/docx_output.py @@ -19,7 +19,7 @@ class PluginWidget(Widget, Ui_Form): def __init__(self, parent, get_option, get_help, db=None, book_id=None): Widget.__init__(self, parent, [ - 'docx_page_size', 'docx_custom_page_size', + 'docx_page_size', 'docx_custom_page_size', 'docx_no_cover', ]) for x in get_option('docx_page_size').option.choices: self.opt_docx_page_size.addItem(x) diff --git a/src/calibre/gui2/convert/docx_output.ui b/src/calibre/gui2/convert/docx_output.ui index 53f05c7790..68da6ff387 100644 --- a/src/calibre/gui2/convert/docx_output.ui +++ b/src/calibre/gui2/convert/docx_output.ui @@ -47,6 +47,13 @@ + + + + Do not insert &cover as image at start of document + + +