mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Mobipocket support:
- Add 'vertical-align: text-top' as a <sup/>-generating case - Add titles to title-less known <guide/> <reference/>s and cleanly ignoren any others - Add transform to generate results of CSS 'text-transform' and 'font-variant'
This commit is contained in:
parent
ed6f3b6702
commit
2397ee84bf
@ -303,7 +303,7 @@ class MobiMLizer(object):
|
|||||||
else:
|
else:
|
||||||
istate.family = 'serif'
|
istate.family = 'serif'
|
||||||
valign = style['vertical-align']
|
valign = style['vertical-align']
|
||||||
if valign in ('super', 'sup') or asfloat(valign) > 0:
|
if valign in ('super', 'text-top') or asfloat(valign) > 0:
|
||||||
istate.valign = 'super'
|
istate.valign = 'super'
|
||||||
elif valign == 'sub' or asfloat(valign) < 0:
|
elif valign == 'sub' or asfloat(valign) < 0:
|
||||||
istate.valign = 'sub'
|
istate.valign = 'sub'
|
||||||
|
@ -28,6 +28,7 @@ from calibre.ebooks.oeb.transforms.flatcss import CSSFlattener
|
|||||||
from calibre.ebooks.oeb.transforms.rasterize import SVGRasterizer
|
from calibre.ebooks.oeb.transforms.rasterize import SVGRasterizer
|
||||||
from calibre.ebooks.oeb.transforms.trimmanifest import ManifestTrimmer
|
from calibre.ebooks.oeb.transforms.trimmanifest import ManifestTrimmer
|
||||||
from calibre.ebooks.oeb.transforms.htmltoc import HTMLTOCAdder
|
from calibre.ebooks.oeb.transforms.htmltoc import HTMLTOCAdder
|
||||||
|
from calibre.ebooks.oeb.transforms.manglecase import CaseMangler
|
||||||
from calibre.ebooks.mobi.palmdoc import compress_doc
|
from calibre.ebooks.mobi.palmdoc import compress_doc
|
||||||
from calibre.ebooks.mobi.langcodes import iana2mobi
|
from calibre.ebooks.mobi.langcodes import iana2mobi
|
||||||
from calibre.ebooks.mobi.mobiml import MBP_NS, MBP, MobiMLizer
|
from calibre.ebooks.mobi.mobiml import MBP_NS, MBP, MobiMLizer
|
||||||
@ -114,7 +115,8 @@ class Serializer(object):
|
|||||||
buffer.write('<guide>')
|
buffer.write('<guide>')
|
||||||
for ref in self.oeb.guide.values():
|
for ref in self.oeb.guide.values():
|
||||||
path, frag = urldefrag(ref.href)
|
path, frag = urldefrag(ref.href)
|
||||||
if hrefs[path].media_type not in OEB_DOCS:
|
if hrefs[path].media_type not in OEB_DOCS or
|
||||||
|
not ref.title:
|
||||||
continue
|
continue
|
||||||
buffer.write('<reference title="%s" type="%s" '
|
buffer.write('<reference title="%s" type="%s" '
|
||||||
% (ref.title, ref.type))
|
% (ref.title, ref.type))
|
||||||
@ -485,12 +487,14 @@ def main(argv=sys.argv):
|
|||||||
fbase = context.dest.fbase
|
fbase = context.dest.fbase
|
||||||
fkey = context.dest.fnums.values()
|
fkey = context.dest.fnums.values()
|
||||||
tocadder = HTMLTOCAdder()
|
tocadder = HTMLTOCAdder()
|
||||||
|
mangler = CaseMangler()
|
||||||
flattener = CSSFlattener(
|
flattener = CSSFlattener(
|
||||||
fbase=fbase, fkey=fkey, unfloat=True, untable=True)
|
fbase=fbase, fkey=fkey, unfloat=True, untable=True)
|
||||||
rasterizer = SVGRasterizer()
|
rasterizer = SVGRasterizer()
|
||||||
trimmer = ManifestTrimmer()
|
trimmer = ManifestTrimmer()
|
||||||
mobimlizer = MobiMLizer()
|
mobimlizer = MobiMLizer()
|
||||||
tocadder.transform(oeb, context)
|
tocadder.transform(oeb, context)
|
||||||
|
mangler.transform(oeb, context)
|
||||||
flattener.transform(oeb, context)
|
flattener.transform(oeb, context)
|
||||||
rasterizer.transform(oeb, context)
|
rasterizer.transform(oeb, context)
|
||||||
mobimlizer.transform(oeb, context)
|
mobimlizer.transform(oeb, context)
|
||||||
|
@ -66,6 +66,25 @@ OEB_RASTER_IMAGES = set([GIF_MIME, JPEG_MIME, PNG_MIME])
|
|||||||
OEB_IMAGES = set([GIF_MIME, JPEG_MIME, PNG_MIME, SVG_MIME])
|
OEB_IMAGES = set([GIF_MIME, JPEG_MIME, PNG_MIME, SVG_MIME])
|
||||||
|
|
||||||
MS_COVER_TYPE = 'other.ms-coverimage-standard'
|
MS_COVER_TYPE = 'other.ms-coverimage-standard'
|
||||||
|
GUIDE_TITLES = {
|
||||||
|
'cover': 'Cover',
|
||||||
|
'title-page': 'Title Page',
|
||||||
|
'toc': 'Table of Contents',
|
||||||
|
'index': 'Index',
|
||||||
|
'glossary': 'Glossary',
|
||||||
|
'acknowledgements': 'Acknowledgements',
|
||||||
|
'bibliography': 'Bibliography',
|
||||||
|
'colophon': 'Colophon',
|
||||||
|
'copyright-page': 'Copyright',
|
||||||
|
'dedication': 'Dedication',
|
||||||
|
'epigraph': 'Epigraph',
|
||||||
|
'foreword': 'Foreword',
|
||||||
|
'loi': 'List of Illustrations',
|
||||||
|
'lot': 'List of Tables',
|
||||||
|
'notes': 'Notes',
|
||||||
|
'preface': 'Preface',
|
||||||
|
'text': 'Begin Reading'
|
||||||
|
}
|
||||||
|
|
||||||
recode = lambda s: s.decode('iso-8859-1').encode('ascii', 'xmlcharrefreplace')
|
recode = lambda s: s.decode('iso-8859-1').encode('ascii', 'xmlcharrefreplace')
|
||||||
ENTITYDEFS = dict((k, recode(v)) for k, v in htmlentitydefs.entitydefs.items())
|
ENTITYDEFS = dict((k, recode(v)) for k, v in htmlentitydefs.entitydefs.items())
|
||||||
@ -537,10 +556,12 @@ class Spine(object):
|
|||||||
class Guide(object):
|
class Guide(object):
|
||||||
class Reference(object):
|
class Reference(object):
|
||||||
def __init__(self, type, title, href):
|
def __init__(self, type, title, href):
|
||||||
|
if not title and type in GUIDE_TITLES:
|
||||||
|
title = GUIDE_TITLES[type]
|
||||||
self.type = type
|
self.type = type
|
||||||
self.title = title
|
self.title = title
|
||||||
self.href = urlnormalize(href)
|
self.href = urlnormalize(href)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Reference(type=%r, title=%r, href=%r)' \
|
return 'Reference(type=%r, title=%r, href=%r)' \
|
||||||
% (self.type, self.title, self.href)
|
% (self.type, self.title, self.href)
|
||||||
|
111
src/calibre/ebooks/oeb/transforms/manglecase.py
Normal file
111
src/calibre/ebooks/oeb/transforms/manglecase.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
'''
|
||||||
|
CSS case-mangling transform.
|
||||||
|
'''
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import operator
|
||||||
|
import math
|
||||||
|
from itertools import chain
|
||||||
|
from collections import defaultdict
|
||||||
|
from lxml import etree
|
||||||
|
from calibre.ebooks.oeb.base import XHTML, XHTML_NS
|
||||||
|
from calibre.ebooks.oeb.base import CSS_MIME
|
||||||
|
from calibre.ebooks.oeb.base import namespace
|
||||||
|
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||||
|
|
||||||
|
CASE_MANGLER_CSS = """
|
||||||
|
.calibre_lowercase {
|
||||||
|
font-variant: normal;
|
||||||
|
font-size: 0.65em;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
TEXT_TRANSFORMS = set(['capitalize', 'uppercase', 'lowercase'])
|
||||||
|
|
||||||
|
class CaseMangler(object):
|
||||||
|
def transform(self, oeb, context):
|
||||||
|
oeb.logger.info('Applying case-transforming CSS...')
|
||||||
|
self.oeb = oeb
|
||||||
|
self.profile = context.source
|
||||||
|
self.mangle_spine()
|
||||||
|
|
||||||
|
def mangle_spine(self):
|
||||||
|
id, href = self.oeb.manifest.generate('manglecase', 'manglecase.css')
|
||||||
|
self.oeb.manifest.add(id, href, CSS_MIME, data=CASE_MANGLER_CSS)
|
||||||
|
for item in self.oeb.spine:
|
||||||
|
html = item.data
|
||||||
|
etree.SubElement(html.find(XHTML('head')), XHTML('link'),
|
||||||
|
rel='stylesheet', href=href, type=CSS_MIME)
|
||||||
|
stylizer = Stylizer(html, item.href, self.oeb, self.profile)
|
||||||
|
self.mangle_elem(html.find(XHTML('body')), stylizer)
|
||||||
|
|
||||||
|
def text_transform(self, transform, text):
|
||||||
|
if transform == 'capitalize':
|
||||||
|
return text.title()
|
||||||
|
elif transform == 'uppercase':
|
||||||
|
return text.upper()
|
||||||
|
elif transform == 'lowercase':
|
||||||
|
return text.lower()
|
||||||
|
return text
|
||||||
|
|
||||||
|
def split_text(self, text):
|
||||||
|
results = ['']
|
||||||
|
isupper = text[0].isupper()
|
||||||
|
for char in text:
|
||||||
|
if char.isupper() == isupper:
|
||||||
|
results[-1] += char
|
||||||
|
else:
|
||||||
|
isupper = not isupper
|
||||||
|
results.append(char)
|
||||||
|
return results
|
||||||
|
|
||||||
|
def smallcaps_elem(self, elem, attr):
|
||||||
|
texts = self.split_text(getattr(elem, attr))
|
||||||
|
setattr(elem, attr, None)
|
||||||
|
last = elem if attr == 'tail' else None
|
||||||
|
attrib = {'class': 'calibre_lowercase'}
|
||||||
|
for text in texts:
|
||||||
|
if text.isupper():
|
||||||
|
if last is None:
|
||||||
|
elem.text = text
|
||||||
|
else:
|
||||||
|
last.tail = text
|
||||||
|
else:
|
||||||
|
child = etree.Element(XHTML('span'), attrib=attrib)
|
||||||
|
child.text = text.upper()
|
||||||
|
if last is None:
|
||||||
|
elem.insert(0, child)
|
||||||
|
else:
|
||||||
|
# addnext() moves the tail for some reason
|
||||||
|
tail = last.tail
|
||||||
|
last.addnext(child)
|
||||||
|
last.tail = tail
|
||||||
|
child.tail = None
|
||||||
|
last = child
|
||||||
|
|
||||||
|
def mangle_elem(self, elem, stylizer):
|
||||||
|
if not isinstance(elem.tag, basestring) or \
|
||||||
|
namespace(elem.tag) != XHTML_NS:
|
||||||
|
return
|
||||||
|
children = list(elem)
|
||||||
|
style = stylizer.style(elem)
|
||||||
|
transform = style['text-transform']
|
||||||
|
variant = style['font-variant']
|
||||||
|
if elem.text:
|
||||||
|
if transform in TEXT_TRANSFORMS:
|
||||||
|
elem.text = self.text_transform(transform, elem.text)
|
||||||
|
if variant == 'small-caps':
|
||||||
|
self.smallcaps_elem(elem, 'text')
|
||||||
|
for child in children:
|
||||||
|
self.mangle_elem(child, stylizer)
|
||||||
|
if child.tail:
|
||||||
|
if transform in TEXT_TRANSFORMS:
|
||||||
|
child.tail = self.text_transform(transform, child.tail)
|
||||||
|
if variant == 'small-caps':
|
||||||
|
self.smallcaps_elem(child, 'tail')
|
Loading…
x
Reference in New Issue
Block a user