From 055b23109d1787e89984e82aa9350d7846ad82f6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 25 Mar 2015 20:20:51 +0530 Subject: [PATCH] Implement float and margin for images --- src/calibre/ebooks/docx/writer/from_html.py | 15 ++++--- src/calibre/ebooks/docx/writer/images.py | 47 +++++++++++++++++---- src/calibre/ebooks/oeb/stylizer.py | 35 +++++++++++++-- 3 files changed, 78 insertions(+), 19 deletions(-) diff --git a/src/calibre/ebooks/docx/writer/from_html.py b/src/calibre/ebooks/docx/writer/from_html.py index 6b699f7485..c7858bb25f 100644 --- a/src/calibre/ebooks/docx/writer/from_html.py +++ b/src/calibre/ebooks/docx/writer/from_html.py @@ -200,9 +200,7 @@ class Convert(object): if block_style.is_hidden: return if html_block.tag.endswith('}img'): - b = Block(self.styles_manager, html_block, None) - self.blocks.append(b) - self.images_manager.add_image(html_block, b, stylizer) + self.images_manager.add_image(html_block, docx_block, stylizer) else: if html_block.text: docx_block.add_text(html_block.text, block_style, ignore_leading_whitespace=True, is_parent_style=True) @@ -212,9 +210,14 @@ class Convert(object): style = stylizer.style(child) display = style._get('display') if display == 'block' and tag != 'br': - b = Block(self.styles_manager, child, style) - self.blocks.append(b) - self.process_block(child, b, stylizer) + if tag == 'img' and style['float'] in {'left', 'right'}: + # Image is floating so dont start a new paragraph for + # it + self.process_inline(child, self.blocks[-1], stylizer) + else: + b = Block(self.styles_manager, child, style) + self.blocks.append(b) + self.process_block(child, b, stylizer) else: self.process_inline(child, self.blocks[-1], stylizer) diff --git a/src/calibre/ebooks/docx/writer/images.py b/src/calibre/ebooks/docx/writer/images.py index f77d483d41..8331713efe 100644 --- a/src/calibre/ebooks/docx/writer/images.py +++ b/src/calibre/ebooks/docx/writer/images.py @@ -23,6 +23,13 @@ from calibre.utils.magick.draw import identify_data Image = namedtuple('Image', 'rid fname width height fmt item') +def get_image_margins(style): + ans = {} + for edge in 'Left Right Top Bottom'.split(): + val = getattr(style, 'padding' + edge) + getattr(style, 'margin' + edge) + ans['dist' + edge[0]] = str(pt_to_emu(val)) + return ans + class ImagesManager(object): def __init__(self, oeb, document_relationships): @@ -63,21 +70,43 @@ class ImagesManager(object): return self.images[href].rid def create_image_markup(self, html_img, stylizer, href): - # TODO: Handle floating images, margin/padding/border on image, img - # inside a link (clickable image) + # TODO: inline img with non baseline vertical-align, img inside a link (clickable image) + style = stylizer.style(html_img) + floating = style['float'] + if floating not in {'left', 'right'}: + floating = None + fake_margins = floating is None self.count += 1 img = self.images[href] name = urlunquote(posixpath.basename(href)) - width, height = map(pt_to_emu, stylizer.style(html_img).img_size(img.width, img.height)) + width, height = map(pt_to_emu, style.img_size(img.width, img.height)) root = etree.Element('root', nsmap=namespaces) ans = makeelement(root, 'w:drawing', append=False) - inline = makeelement(ans, 'wp:inline', distT='0', distB='0', distR='0', distL='0') - makeelement(inline, 'wp:extent', cx=str(width), cy=str(width)) - makeelement(inline, 'wp:effectExtent', l='0', r='0', t='0', b='0') - makeelement(inline, 'wp:docPr', id=str(self.count), name=name, descr=html_img.get('alt') or name) - makeelement(makeelement(inline, 'wp:cNvGraphicFramePr'), 'a:graphicFrameLocks', noChangeAspect="1") - g = makeelement(inline, 'a:graphic') + if floating is None: + parent = makeelement(ans, 'wp:inline') + else: + parent = makeelement(ans, 'wp:anchor', **get_image_margins(style)) + # The next three lines are boilerplate that Word requires, even + # though the DOCX specs define defaults for all of them + 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='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)) + if fake_margins: + # DOCX does not support setting margins for inline images, so we + # fake it by using effect extents to simulate margins + makeelement(parent, 'wp:effectExtent', **{k[-1].lower():v for k, v in get_image_margins(style).iteritems()}) + else: + makeelement(parent, 'wp:effectExtent', l='0', r='0', t='0', b='0') + if floating is not None: + # The idiotic Word requires this to be after the extent settings + makeelement(parent, 'wp:wrapSquare', wrapText='bothSides') + makeelement(parent, 'wp:docPr', id=str(self.count), name=name, descr=html_img.get('alt') or name) + 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') diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py index 247a70986a..4bcb2c6d58 100644 --- a/src/calibre/ebooks/oeb/stylizer.py +++ b/src/calibre/ebooks/oeb/stylizer.py @@ -564,6 +564,13 @@ class Style(object): return self._width + @property + def parent_width(self): + parent = self._get_parent() + if parent is None: + return self.width + return parent.width + @property def height(self): if self._height is None: @@ -638,22 +645,42 @@ class Style(object): @property def marginTop(self): return self._unit_convert( - self._get('margin-top'), base=self.height) + self._get('margin-top'), base=self.parent_width) @property def marginBottom(self): return self._unit_convert( - self._get('margin-bottom'), base=self.height) + self._get('margin-bottom'), base=self.parent_width) + + @property + def marginLeft(self): + return self._unit_convert( + self._get('margin-left'), base=self.parent_width) + + @property + def marginRight(self): + return self._unit_convert( + self._get('margin-right'), base=self.parent_width) @property def paddingTop(self): return self._unit_convert( - self._get('padding-top'), base=self.height) + self._get('padding-top'), base=self.parent_width) @property def paddingBottom(self): return self._unit_convert( - self._get('padding-bottom'), base=self.height) + self._get('padding-bottom'), base=self.parent_width) + + @property + def paddingLeft(self): + return self._unit_convert( + self._get('padding-left'), base=self.parent_width) + + @property + def paddingRight(self): + return self._unit_convert( + self._get('padding-right'), base=self.parent_width) def __str__(self): items = sorted(self._style.iteritems())