diff --git a/src/calibre/ebooks/docx/names.py b/src/calibre/ebooks/docx/names.py index f13b963e10..526b7b7d88 100644 --- a/src/calibre/ebooks/docx/names.py +++ b/src/calibre/ebooks/docx/names.py @@ -76,11 +76,11 @@ def barename(x): def XML(x): return '{%s}%s' % (namespaces['xml'], x) -def expand(name): - ns, tag = name.partition(':')[0::2] - if ns: +def expand(name, sep=':'): + ns, tag = name.partition(sep)[::2] + if ns and tag: tag = '{%s}%s' % (namespaces[ns], tag) - return tag + return tag or ns def get(x, attr, default=None): return x.attrib.get(expand(attr), default) @@ -104,3 +104,9 @@ def children(elem, *args): def descendants(elem, *args): return XPath('|'.join('descendant::%s' % a for a in args))(elem) + +def makeelement(root, tag, append=True, **attrs): + ans = root.makeelement(expand(tag), **{expand(k, sep='_'):v for k, v in attrs.iteritems()}) + if append: + root.append(ans) + return ans diff --git a/src/calibre/ebooks/docx/writer/from_html.py b/src/calibre/ebooks/docx/writer/from_html.py index 3430ebf0ff..9110e7b53e 100644 --- a/src/calibre/ebooks/docx/writer/from_html.py +++ b/src/calibre/ebooks/docx/writer/from_html.py @@ -66,6 +66,9 @@ class TextRun(object): def add_break(self, clear='none'): self.texts.append((None, clear)) + def add_image(self, drawing): + self.texts.append((drawing, None)) + def serialize(self, p): r = p.makeelement(w('r')) p.append(r) @@ -75,6 +78,8 @@ class TextRun(object): for text, preserve_whitespace in self.texts: if text is None: r.append(r.makeelement(w('br'), **{w('clear'):preserve_whitespace})) + elif hasattr(text, 'xpath'): + r.append(text) else: t = r.makeelement(w('t')) r.append(t) @@ -125,6 +130,14 @@ class Block(object): self.runs.append(run) run.add_break(clear=clear) + def add_image(self, drawing): + if self.runs: + run = self.runs[-1] + else: + run = TextRun(self.styles_manager.create_text_style(self.html_style), self.html_block) + self.runs.append(run) + run.add_image(drawing) + def serialize(self, body): p = body.makeelement(w('p')) body.append(p) @@ -241,7 +254,7 @@ class Convert(object): self.blocks[-1].add_text(html_child.tail, stylizer.style(html_child.getparent()), html_parent=html_child.getparent(), is_parent_style=True) def write(self): - dn = {k:v for k, v in namespaces.iteritems() if k in {'w', 'r', 'm', 've', 'o', 'wp', 'w10', 'wne'}} + dn = {k:v for k, v in namespaces.iteritems() if k in {'w', 'r', 'm', 've', 'o', 'wp', 'w10', 'wne', 'a', 'pic'}} E = ElementMaker(namespace=dn['w'], nsmap=dn) self.docx.document = doc = E.document() body = E.body() diff --git a/src/calibre/ebooks/docx/writer/images.py b/src/calibre/ebooks/docx/writer/images.py index 5ad5ddada1..f77d483d41 100644 --- a/src/calibre/ebooks/docx/writer/images.py +++ b/src/calibre/ebooks/docx/writer/images.py @@ -10,8 +10,13 @@ import os import shutil, posixpath from collections import namedtuple from functools import partial +from future_builtins import map + +from lxml import etree from calibre.ebooks.oeb.base import urlunquote +from calibre.ebooks.docx.names import makeelement, namespaces +from calibre.ebooks.docx.images import pt_to_emu from calibre.ptempfile import PersistentTemporaryDirectory from calibre.utils.filenames import ascii_filename from calibre.utils.magick.draw import identify_data @@ -26,6 +31,7 @@ class ImagesManager(object): self.seen_filenames = set() self.document_relationships = document_relationships self._tdir = None + self.count = 0 @property def tdir(self): @@ -52,8 +58,40 @@ 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() + drawing = self.create_image_markup(img, stylizer, href) + block.add_image(drawing) 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) + 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)) + + 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') + 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:cNvPicPr') + bf = makeelement(pic, 'pic:blipFill') + 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))) fname = posixpath.splitext(fname)[0] diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py index d50062d7b6..247a70986a 100644 --- a/src/calibre/ebooks/oeb/stylizer.py +++ b/src/calibre/ebooks/oeb/stylizer.py @@ -500,6 +500,40 @@ class Style(object): self._fontSize = result return self._fontSize + def img_dimension(self, attr, img_size): + ans = None + parent = self._get_parent() + if parent is not None: + base = getattr(parent, attr) + else: + base = getattr(self._profile, attr + '_pts') + x = self._style.get(attr) + if x is not None: + if x == 'auto': + ans = base + else: + x = self._unit_convert(x, base=base) + if isinstance(x, (float, int, long)): + ans = x + if ans is None: + x = self._element.get(attr) + if x is not None: + x = self._unit_convert(x + 'px', base=base) + if isinstance(x, (float, int, long)): + ans = x + if ans is None: + ans = self._unit_convert(str(img_size) + 'px', base=base) + maa = self._style.get('max-' + attr) + if maa is not None: + x = self._unit_convert(maa, base=base) + if isinstance(x, (int, float, long)) and (ans is None or x < ans): + ans = x + return ans + + def img_size(self, width, height): + ' Return the final size of an given that it points to an imafe of size widthxheight ' + return self.img_dimension('width', width), self.img_dimension('height', height) + @property def width(self): if self._width is None: