EPUB Output: Consolidate inline CSS generated by calibre into external stylesheets for ease of editing the EPUB

This commit is contained in:
Kovid Goyal 2012-05-04 15:37:10 +05:30
parent 6a88470186
commit 3b1921fca4
5 changed files with 63 additions and 38 deletions

View File

@ -312,13 +312,9 @@ class EPUBOutput(OutputFormatPlugin):
Perform various markup transforms to get the output to render correctly Perform various markup transforms to get the output to render correctly
in the quirky ADE. in the quirky ADE.
''' '''
from calibre.ebooks.oeb.base import XPath, XHTML, OEB_STYLES, barename, urlunquote from calibre.ebooks.oeb.base import XPath, XHTML, barename, urlunquote
stylesheet = None stylesheet = self.oeb.manifest.main_stylesheet
for item in self.oeb.manifest:
if item.media_type.lower() in OEB_STYLES:
stylesheet = item
break
# ADE cries big wet tears when it encounters an invalid fragment # ADE cries big wet tears when it encounters an invalid fragment
# identifier in the NCX toc. # identifier in the NCX toc.

View File

@ -99,12 +99,8 @@ class PDFOutput(OutputFormatPlugin):
# Remove page-break-before on <body> element as it causes # Remove page-break-before on <body> element as it causes
# blank pages in PDF Output # blank pages in PDF Output
from calibre.ebooks.oeb.base import OEB_STYLES, XPath from calibre.ebooks.oeb.base import XPath
stylesheet = None stylesheet = self.oeb.manifest.main_stylesheet
for item in self.oeb.manifest:
if item.media_type.lower() in OEB_STYLES:
stylesheet = item
break
if stylesheet is not None: if stylesheet is not None:
from cssutils.css import CSSRule from cssutils.css import CSSRule
classes = set(['.calibre']) classes = set(['.calibre'])

View File

@ -1142,6 +1142,19 @@ class Manifest(object):
element(elem, OPF('item'), attrib=attrib) element(elem, OPF('item'), attrib=attrib)
return elem return elem
@dynamic_property
def main_stylesheet(self):
def fget(self):
ans = getattr(self, '_main_stylesheet', None)
if ans is None:
for item in self:
if item.media_type.lower() in OEB_STYLES:
ans = item
break
return ans
def fset(self, item):
self._main_stylesheet = item
return property(fget=fget, fset=fset)
class Spine(object): class Spine(object):
"""Collection of manifest items composing an OEB data model book's main """Collection of manifest items composing an OEB data model book's main

View File

@ -378,7 +378,7 @@ class CSSFlattener(object):
for child in node: for child in node:
self.flatten_node(child, stylizer, names, styles, psize, item_id, left) self.flatten_node(child, stylizer, names, styles, psize, item_id, left)
def flatten_head(self, item, stylizer, href): def flatten_head(self, item, href, global_href):
html = item.data html = item.data
head = html.find(XHTML('head')) head = html.find(XHTML('head'))
for node in head: for node in head:
@ -390,33 +390,55 @@ class CSSFlattener(object):
and node.get('type', CSS_MIME) in OEB_STYLES: and node.get('type', CSS_MIME) in OEB_STYLES:
head.remove(node) head.remove(node)
href = item.relhref(href) href = item.relhref(href)
etree.SubElement(head, XHTML('link'), l = etree.SubElement(head, XHTML('link'),
rel='stylesheet', type=CSS_MIME, href=href) rel='stylesheet', type=CSS_MIME, href=href)
l.tail='\n'
href = item.relhref(global_href)
l = etree.SubElement(head, XHTML('link'),
rel='stylesheet', type=CSS_MIME, href=href)
l.tail = '\n'
def replace_css(self, css):
manifest = self.oeb.manifest
for item in manifest.values():
if item.media_type in OEB_STYLES:
manifest.remove(item)
id, href = manifest.generate('css', 'stylesheet.css')
item = manifest.add(id, href, CSS_MIME, data=cssutils.parseString(css,
validate=False))
self.oeb.manifest.main_stylesheet = item
return href
def collect_global_css(self):
global_css = defaultdict(list)
for item in self.oeb.spine:
stylizer = self.stylizers[item]
stylizer.page_rule['margin-top'] = '%gpt'%\ stylizer.page_rule['margin-top'] = '%gpt'%\
float(self.context.margin_top) float(self.context.margin_top)
stylizer.page_rule['margin-bottom'] = '%gpt'%\ stylizer.page_rule['margin-bottom'] = '%gpt'%\
float(self.context.margin_bottom) float(self.context.margin_bottom)
items = stylizer.page_rule.items() items = stylizer.page_rule.items()
items.sort() items.sort()
css = '; '.join("%s: %s" % (key, val) for key, val in items) css = ';\n'.join("%s: %s" % (key, val) for key, val in items)
style = etree.SubElement(head, XHTML('style'), type=CSS_MIME) css = '@page {\n%s\n}\n'%css
style.text = "\n\t\t@page { %s; }" % css
rules = [r.cssText for r in stylizer.font_face_rules] rules = [r.cssText for r in stylizer.font_face_rules]
raw = '\n\n'.join(rules) raw = '\n\n'.join(rules)
# Make URLs referring to fonts relative to this item css += '\n\n' + raw
sheet = cssutils.parseString(raw, validate=False) global_css[css].append(item)
cssutils.replaceUrls(sheet, item.relhref, ignoreImportRules=True)
style.text += '\n' + sheet.cssText
def replace_css(self, css): gc_map = {}
manifest = self.oeb.manifest manifest = self.oeb.manifest
id, href = manifest.generate('css', 'stylesheet.css') for css in global_css:
for item in manifest.values(): id_, href = manifest.generate('page_css', 'page_styles.css')
if item.media_type in OEB_STYLES: manifest.add(id_, href, CSS_MIME, data=cssutils.parseString(css,
manifest.remove(item) validate=False))
item = manifest.add(id, href, CSS_MIME, data=css) gc_map[css] = href
return href
ans = {}
for css, items in global_css.iteritems():
for item in items:
ans[item] = gc_map[css]
return ans
def flatten_spine(self): def flatten_spine(self):
names = defaultdict(int) names = defaultdict(int)
@ -433,7 +455,8 @@ class CSSFlattener(object):
items.sort() items.sort()
css = ''.join(".%s {\n%s;\n}\n\n" % (key, val) for key, val in items) css = ''.join(".%s {\n%s;\n}\n\n" % (key, val) for key, val in items)
href = self.replace_css(css) href = self.replace_css(css)
global_css = self.collect_global_css()
for item in self.oeb.spine: for item in self.oeb.spine:
stylizer = self.stylizers[item] stylizer = self.stylizers[item]
self.flatten_head(item, stylizer, href) self.flatten_head(item, href, global_css[item])

View File

@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
from collections import Counter from collections import Counter
from calibre.ebooks.oeb.base import OEB_STYLES, barename, XPath from calibre.ebooks.oeb.base import barename, XPath
class RemoveAdobeMargins(object): class RemoveAdobeMargins(object):
''' '''
@ -51,10 +51,7 @@ class RemoveFakeMargins(object):
self.stats = {} self.stats = {}
self.selector_map = {} self.selector_map = {}
for item in self.oeb.manifest: stylesheet = self.oeb.manifest.main_stylesheet
if item.media_type.lower() in OEB_STYLES:
stylesheet = item
break
if stylesheet is None: if stylesheet is None:
return return