mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54: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):
|
def XML(x):
|
||||||
return '{%s}%s' % (namespaces['xml'], x)
|
return '{%s}%s' % (namespaces['xml'], x)
|
||||||
|
|
||||||
def expand(name):
|
def expand(name, sep=':'):
|
||||||
ns, tag = name.partition(':')[0::2]
|
ns, tag = name.partition(sep)[::2]
|
||||||
if ns:
|
if ns and tag:
|
||||||
tag = '{%s}%s' % (namespaces[ns], tag)
|
tag = '{%s}%s' % (namespaces[ns], tag)
|
||||||
return tag
|
return tag or ns
|
||||||
|
|
||||||
def get(x, attr, default=None):
|
def get(x, attr, default=None):
|
||||||
return x.attrib.get(expand(attr), default)
|
return x.attrib.get(expand(attr), default)
|
||||||
@ -104,3 +104,9 @@ def children(elem, *args):
|
|||||||
|
|
||||||
def descendants(elem, *args):
|
def descendants(elem, *args):
|
||||||
return XPath('|'.join('descendant::%s' % a for a in args))(elem)
|
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'):
|
def add_break(self, clear='none'):
|
||||||
self.texts.append((None, clear))
|
self.texts.append((None, clear))
|
||||||
|
|
||||||
|
def add_image(self, drawing):
|
||||||
|
self.texts.append((drawing, None))
|
||||||
|
|
||||||
def serialize(self, p):
|
def serialize(self, p):
|
||||||
r = p.makeelement(w('r'))
|
r = p.makeelement(w('r'))
|
||||||
p.append(r)
|
p.append(r)
|
||||||
@ -75,6 +78,8 @@ class TextRun(object):
|
|||||||
for text, preserve_whitespace in self.texts:
|
for text, preserve_whitespace in self.texts:
|
||||||
if text is None:
|
if text is None:
|
||||||
r.append(r.makeelement(w('br'), **{w('clear'):preserve_whitespace}))
|
r.append(r.makeelement(w('br'), **{w('clear'):preserve_whitespace}))
|
||||||
|
elif hasattr(text, 'xpath'):
|
||||||
|
r.append(text)
|
||||||
else:
|
else:
|
||||||
t = r.makeelement(w('t'))
|
t = r.makeelement(w('t'))
|
||||||
r.append(t)
|
r.append(t)
|
||||||
@ -125,6 +130,14 @@ class Block(object):
|
|||||||
self.runs.append(run)
|
self.runs.append(run)
|
||||||
run.add_break(clear=clear)
|
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):
|
def serialize(self, body):
|
||||||
p = body.makeelement(w('p'))
|
p = body.makeelement(w('p'))
|
||||||
body.append(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)
|
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):
|
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)
|
E = ElementMaker(namespace=dn['w'], nsmap=dn)
|
||||||
self.docx.document = doc = E.document()
|
self.docx.document = doc = E.document()
|
||||||
body = E.body()
|
body = E.body()
|
||||||
|
@ -10,8 +10,13 @@ import os
|
|||||||
import shutil, posixpath
|
import shutil, posixpath
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from future_builtins import map
|
||||||
|
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
from calibre.ebooks.oeb.base import urlunquote
|
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.ptempfile import PersistentTemporaryDirectory
|
||||||
from calibre.utils.filenames import ascii_filename
|
from calibre.utils.filenames import ascii_filename
|
||||||
from calibre.utils.magick.draw import identify_data
|
from calibre.utils.magick.draw import identify_data
|
||||||
@ -26,6 +31,7 @@ class ImagesManager(object):
|
|||||||
self.seen_filenames = set()
|
self.seen_filenames = set()
|
||||||
self.document_relationships = document_relationships
|
self.document_relationships = document_relationships
|
||||||
self._tdir = None
|
self._tdir = None
|
||||||
|
self.count = 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tdir(self):
|
def tdir(self):
|
||||||
@ -52,8 +58,40 @@ class ImagesManager(object):
|
|||||||
image_rid = self.document_relationships.add_image(image_fname)
|
image_rid = self.document_relationships.add_image(image_fname)
|
||||||
self.images[href] = Image(image_rid, image_fname, width, height, fmt, item)
|
self.images[href] = Image(image_rid, image_fname, width, height, fmt, item)
|
||||||
item.unload_data_from_memory()
|
item.unload_data_from_memory()
|
||||||
|
drawing = self.create_image_markup(img, stylizer, href)
|
||||||
|
block.add_image(drawing)
|
||||||
return self.images[href].rid
|
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):
|
def create_filename(self, href, fmt):
|
||||||
fname = ascii_filename(urlunquote(posixpath.basename(href)))
|
fname = ascii_filename(urlunquote(posixpath.basename(href)))
|
||||||
fname = posixpath.splitext(fname)[0]
|
fname = posixpath.splitext(fname)[0]
|
||||||
|
@ -500,6 +500,40 @@ class Style(object):
|
|||||||
self._fontSize = result
|
self._fontSize = result
|
||||||
return self._fontSize
|
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
|
@property
|
||||||
def width(self):
|
def width(self):
|
||||||
if self._width is None:
|
if self._width is None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user