mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
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:
parent
57fa364eeb
commit
355fc6f84b
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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]
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user