Fixed various bugs; added various improvements.

This commit is contained in:
Marshall T. Vandegrift 2009-01-06 09:44:24 -05:00
parent 28512fac5a
commit 4346c7f66c
7 changed files with 183 additions and 43 deletions

View File

@ -104,7 +104,7 @@ class MobiMLizer(object):
return ptsize
fbase = self.profile.fbase
if ptsize < fbase:
return "%dpt" % int(round(ptsize * 2))
return "%dpt" % int(round(ptsize))
return "%dem" % int(round(ptsize / fbase))
def preize_text(self, text):
@ -284,9 +284,9 @@ class MobiMLizer(object):
else:
istate.family = 'serif'
valign = style['vertical-align']
if valign in ('super', 'sup') and asfloat(valign) > 0:
if valign in ('super', 'sup') or asfloat(valign) > 0:
istate.valign = 'super'
elif valign == 'sub' and asfloat(valign) < 0:
elif valign == 'sub' or asfloat(valign) < 0:
istate.valign = 'sub'
else:
istate.valign = 'baseline'
@ -300,6 +300,15 @@ class MobiMLizer(object):
if tag == 'img' and 'src' in elem.attrib:
istate.attrib['src'] = elem.attrib['src']
istate.attrib['align'] = 'baseline'
for prop in ('width', 'height'):
if style[prop] != 'auto':
value = style[prop]
if value == getattr(self.profile, prop):
result = '100%'
else:
ems = int(round(value / self.profile.fbase))
result = "%dem" % ems
istate.attrib[prop] = result
elif tag == 'hr' and asfloat(style['width']) > 0:
prop = style['width'] / self.profile.width
istate.attrib['width'] = "%d%%" % int(round(prop * 100))

View File

@ -225,11 +225,22 @@ class MobiWriter(object):
self._oeb = oeb
self._stream = stream
self._records = [None]
self._remove_html_cover()
self._generate_content()
self._generate_record0()
self._write_header()
self._write_content()
def _remove_html_cover(self):
oeb = self._oeb
if not oeb.metadata.cover \
or 'cover' not in oeb.guide:
return
href = oeb.guide['cover'].href
del oeb.guide['cover']
item = oeb.manifest.hrefs[href]
oeb.manifest.remove(item)
def _generate_content(self):
self._map_image_names()
self._generate_text()
@ -391,7 +402,7 @@ class MobiWriter(object):
nrecs += 1
if oeb.metadata.cover:
id = str(oeb.metadata.cover[0])
item = oeb.manifest[id]
item = oeb.manifest.ids[id]
href = item.href
index = self._images[href] - 1
exth.write(pack('>III', 0xc9, 0x0c, index))

View File

@ -17,6 +17,7 @@ import logging
import re
import htmlentitydefs
import uuid
import copy
from lxml import etree
from calibre import LoggingInterface
@ -32,10 +33,11 @@ XSI_NS = 'http://www.w3.org/2001/XMLSchema-instance'
DCTERMS_NS = 'http://purl.org/dc/terms/'
NCX_NS = 'http://www.daisy.org/z3986/2005/ncx/'
SVG_NS = 'http://www.w3.org/2000/svg'
XLINK_NS = 'http://www.w3.org/1999/xlink'
XPNSMAP = {'h': XHTML_NS, 'o1': OPF1_NS, 'o2': OPF2_NS,
'd09': DC09_NS, 'd10': DC10_NS, 'd11': DC11_NS,
'xsi': XSI_NS, 'dt': DCTERMS_NS, 'ncx': NCX_NS,
'svg': SVG_NS}
'svg': SVG_NS, 'xl': XLINK_NS}
def XML(name): return '{%s}%s' % (XML_NS, name)
def XHTML(name): return '{%s}%s' % (XHTML_NS, name)
@ -43,6 +45,7 @@ def OPF(name): return '{%s}%s' % (OPF2_NS, name)
def DC(name): return '{%s}%s' % (DC11_NS, name)
def NCX(name): return '{%s}%s' % (NCX_NS, name)
def SVG(name): return '{%s}%s' % (SVG_NS, name)
def XLINK(name): return '{%s}%s' % (XLINK_NS, name)
EPUB_MIME = 'application/epub+zip'
XHTML_MIME = 'application/xhtml+xml'
@ -246,10 +249,10 @@ class Metadata(object):
self.oeb = oeb
self.items = defaultdict(list)
def add(self, term, value, attrib={}, **kwargs):
def add(self, term, value, attrib={}, index=-1, **kwargs):
item = self.Item(term, value, attrib, **kwargs)
items = self.items[barename(item.term)]
items.append(item)
items.insert(index, item)
return item
def iterkeys(self):
@ -323,8 +326,7 @@ class Manifest(object):
data = self._loader(self.href)
if self.media_type in OEB_DOCS:
data = self._force_xhtml(data)
elif self.media_type[-4:] in ('+xml', '/xml') \
and self.media_type != SVG_MIME:
elif self.media_type[-4:] in ('+xml', '/xml'):
data = etree.fromstring(data, parser=XML_PARSER)
self._data = data
return data
@ -341,6 +343,9 @@ class Manifest(object):
return xml2str(data)
return str(data)
def __eq__(self, other):
return id(self) == id(other)
def __cmp__(self, other):
result = cmp(self.spine_position, other.spine_position)
if result != 0:
@ -558,8 +563,11 @@ class Guide(object):
for type, ref in self.refs.items():
yield type, ref
def __getitem__(self, index):
return self.refs[index]
def __getitem__(self, key):
return self.refs[key]
def __delitem__(self, key):
del self.refs[key]
def __contains__(self, key):
return key in self.refs
@ -891,20 +899,27 @@ class OEBBook(object):
def _ensure_cover_image(self):
cover = None
spine0 = self.spine[0]
html = spine0.data
if self.metadata.cover:
id = str(self.metadata.cover[0])
cover = self.manifest[id]
cover = self.manifest.ids[id]
elif MS_COVER_TYPE in self.guide:
href = self.guide[MS_COVER_TYPE].href
cover = self.manifest.hrefs[href]
elif 'cover' in self.guide:
href = self.guide['cover'].href
elif xpath(html, '//h:img[position()=1]'):
img = xpath(html, '//h:img[position()=1]')[0]
href = img.get('src')
cover = self.manifest.hrefs[href]
else:
html = self.spine[0].data
imgs = xpath(html, '//h:img[position()=1]')
href = imgs[0].get('src') if imgs else None
cover = self.manifest.hrefs[href] if href else None
elif xpath(html, '//h:object[position()=1]'):
object = xpath(html, '//h:object[position()=1]')[0]
href = object.get('data')
cover = self.manifest.hrefs[href]
elif xpath(html, '//svg:svg[position()=1]'):
svg = copy.deepcopy(xpath(html, '//svg:svg[position()=1]')[0])
href = os.path.splitext(spine0.href)[0] + '.svg'
id, href = self.manifest.generate(spine0.id, href)
cover = self.manifest.add(id, href, SVG_MIME, data=svg)
if cover and not self.metadata.cover:
self.metadata.add('cover', cover.id)

View File

@ -35,7 +35,8 @@
*
* ***** END LICENSE BLOCK ***** */
@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */
@namespace url(http://www.w3.org/1999/xhtml);
@namespace svg url(http://www.w3.org/2000/svg);
/* blocks */
@ -399,8 +400,8 @@ br {
display: block;
}
/* Images and embedded object size defaults */
img, object {
/* Images, embedded object, and SVG size defaults */
img, object, svg|svg {
width: auto;
height: auto;
}

View File

@ -41,8 +41,8 @@ PROFILES = {
# Not really, but let's pretend
'MobiDesktop':
Profile(width=280, height=300, dpi=96, fbase=12,
fsizes=[9, 10, 11, 12, 14, 17, 20, 24]),
Profile(width=280, height=300, dpi=96, fbase=18,
fsizes=[14, 14, 16, 18, 20, 22, 22, 24]),
# No clue on usable screen size and DPI
'CybookG3':

View File

@ -268,6 +268,7 @@ class Style(object):
self._style = {}
self._fontSize = None
self._width = None
self._height = None
stylizer._styles[element] = self
def _update_cssdict(self, cssdict):
@ -390,17 +391,38 @@ class Style(object):
base = styles[self._element.getparent()].width
else:
base = self._profile.width
if 'width' in self._style:
if 'width' is self._element.attrib:
width = self._element.attrib['width']
elif 'width' in self._style:
width = self._style['width']
if width == 'auto':
result = base
else:
result = base
if not result:
result = self._unit_convert(width, base=base)
else:
result = base
self._width = result
return self._width
@property
def height(self):
if self._height is None:
result = None
base = None
if self._has_parent():
styles = self._stylizer._styles
base = styles[self._element.getparent()].height
else:
base = self._profile.height
if 'height' is self._element.attrib:
height = self._element.attrib['height']
elif 'height' in self._style:
height = self._style['height']
else:
result = base
if not result:
result = self._unit_convert(height, base=base)
self._height = result
return self._height
def __str__(self):
items = self._style.items()
items.sort()

View File

@ -8,17 +8,21 @@ __copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
import sys
import os
from urlparse import urldefrag
import base64
from lxml import etree
from PyQt4.QtCore import Qt
from PyQt4.QtCore import QByteArray
from PyQt4.QtCore import QBuffer
from PyQt4.QtCore import QIODevice
from PyQt4.QtGui import QColor
from PyQt4.QtGui import QImage
from PyQt4.QtGui import QPainter
from PyQt4.QtSvg import QSvgRenderer
from PyQt4.QtGui import QApplication
from calibre.ebooks.oeb.base import XHTML, SVG, SVG_NS, SVG_MIME
from calibre.ebooks.oeb.base import namespace, barename
from calibre.ebooks.oeb.base import XHTML_NS, XHTML, SVG_NS, SVG, XLINK
from calibre.ebooks.oeb.base import SVG_MIME, PNG_MIME
from calibre.ebooks.oeb.base import xml2str, xpath, namespace, barename
from calibre.ebooks.oeb.stylizer import Stylizer
IMAGE_TAGS = set([XHTML('img'), XHTML('object')])
@ -32,7 +36,54 @@ class SVGRasterizer(object):
self.oeb = oeb
self.profile = context.dest
self.images = {}
self.dataize_manifest()
self.rasterize_spine()
self.rasterize_cover()
def rasterize_svg(self, elem, width=0, height=0):
data = QByteArray(xml2str(elem))
svg = QSvgRenderer(data)
size = svg.defaultSize()
if size.width() == 100 and size.height() == 100 \
and 'viewBox' in elem.attrib:
box = [float(x) for x in elem.attrib['viewBox'].split()]
size.setWidth(box[2] - box[0])
size.setHeight(box[3] - box[1])
if width or height:
size.scale(width, height, Qt.KeepAspectRatio)
image = QImage(size, QImage.Format_ARGB32_Premultiplied)
image.fill(QColor("white").rgb())
painter = QPainter(image)
svg.render(painter)
painter.end()
array = QByteArray()
buffer = QBuffer(array)
buffer.open(QIODevice.WriteOnly)
image.save(buffer, 'PNG')
return str(array)
def dataize_manifest(self):
for item in self.oeb.manifest.values():
if item.media_type == SVG_MIME:
self.dataize_svg(item)
def dataize_svg(self, item, svg=None):
if svg is None:
svg = item.data
hrefs = self.oeb.manifest.hrefs
for elem in xpath(svg, '//svg:*[@xl:href]'):
href = elem.attrib[XLINK('href')]
path, frag = urldefrag(href)
if not path:
continue
abshref = item.abshref(path)
if abshref not in hrefs:
continue
linkee = hrefs[abshref]
data = base64.encodestring(str(linkee))
data = "data:%s;base64,%s" % (linkee.media_type, data)
elem.attrib[XLINK('href')] = data
return svg
def rasterize_spine(self):
for item in self.oeb.spine:
@ -44,7 +95,7 @@ class SVGRasterizer(object):
if not isinstance(elem.tag, basestring): return
style = stylizer.style(elem)
if namespace(elem.tag) == SVG_NS:
return self.rasterize_inline(elem, style)
return self.rasterize_inline(elem, style, item)
if elem.tag in IMAGE_TAGS:
manifest = self.oeb.manifest
src = elem.get('src', None) or elem.get('data', None)
@ -54,27 +105,46 @@ class SVGRasterizer(object):
for child in elem:
self.rasterize_elem(child, item, stylizer)
def rasterize_inline(self, elem, style):
pass
def rasterize_external(self, elem, style, item, svgitem):
data = QByteArray(svgitem.data)
svg = QSvgRenderer(data)
size = svg.defaultSize()
height = style['height']
if height == 'auto':
height = self.profile.height
def rasterize_inline(self, elem, style, item):
width = style['width']
if width == 'auto':
width = self.profile.width
height = style['height']
if height == 'auto':
height = self.profile.height
width = (width / 72) * self.profile.dpi
height = (height / 72) * self.profile.dpi
elem = self.dataize_svg(item, elem)
data = self.rasterize_svg(elem, width, height)
manifest = self.oeb.manifest
href = os.path.splitext(item.href)[0] + '.png'
id, href = manifest.generate(item.id, href)
manifest.add(id, href, PNG_MIME, data=data)
img = etree.Element(XHTML('img'), src=item.relhref(href))
elem.getparent().replace(elem, img)
for prop in ('width', 'height'):
if prop in elem.attrib:
img.attrib[prop] = elem.attrib[prop]
def rasterize_external(self, elem, style, item, svgitem):
width = style['width']
if width == 'auto':
width = self.profile.width
height = style['height']
if height == 'auto':
height = self.profile.height
width = (width / 72) * self.profile.dpi
height = (height / 72) * self.profile.dpi
data = QByteArray(str(svgitem))
svg = QSvgRenderer(data)
size = svg.defaultSize()
size.scale(width, height, Qt.KeepAspectRatio)
key = (svgitem.href, size.width(), size.height())
if key in self.images:
href = self.images[key]
else:
image = QImage(size, QImage.Format_ARGB32_Premultiplied)
image.fill(QColor("white").rgb())
painter = QPainter(image)
svg.render(painter)
painter.end()
@ -86,7 +156,7 @@ class SVGRasterizer(object):
manifest = self.oeb.manifest
href = os.path.splitext(svgitem.href)[0] + '.png'
id, href = manifest.generate(svgitem.id, href)
manifest.add(id, href, 'image/png', data=data)
manifest.add(id, href, PNG_MIME, data=data)
self.images[key] = href
elem.tag = XHTML('img')
elem.attrib['src'] = item.relhref(href)
@ -94,3 +164,15 @@ class SVGRasterizer(object):
for child in elem:
elem.remove(child)
def rasterize_cover(self):
covers = self.oeb.metadata.cover
if not covers:
return
cover = self.oeb.manifest.ids[str(covers[0])]
if not cover.media_type == SVG_MIME:
return
data = self.rasterize_svg(cover.data, 600, 800)
href = os.path.splitext(cover.href)[0] + '.png'
id, href = self.oeb.manifest.generate(cover.id, href)
self.oeb.manifest.add(id, href, PNG_MIME, data=data)
covers[0].value = id