DOCX Output: Add support for images

Only basic support, image properties such as float, margins/borders, etc
are still to be implemented
This commit is contained in:
Kovid Goyal 2015-03-25 13:55:10 +05:30
parent 57fa364eeb
commit 355fc6f84b
4 changed files with 96 additions and 5 deletions

View File

@ -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

View File

@ -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()

View File

@ -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]

View File

@ -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 <img> 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: