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
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
for item in self.oeb.manifest:
if item.media_type.lower() in OEB_STYLES:
stylesheet = item
break
stylesheet = self.oeb.manifest.main_stylesheet
# ADE cries big wet tears when it encounters an invalid fragment
# identifier in the NCX toc.

View File

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

View File

@ -1142,6 +1142,19 @@ class Manifest(object):
element(elem, OPF('item'), attrib=attrib)
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):
"""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:
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
head = html.find(XHTML('head'))
for node in head:
@ -390,33 +390,55 @@ class CSSFlattener(object):
and node.get('type', CSS_MIME) in OEB_STYLES:
head.remove(node)
href = item.relhref(href)
etree.SubElement(head, XHTML('link'),
l = etree.SubElement(head, XHTML('link'),
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'%\
float(self.context.margin_top)
stylizer.page_rule['margin-bottom'] = '%gpt'%\
float(self.context.margin_bottom)
items = stylizer.page_rule.items()
items.sort()
css = '; '.join("%s: %s" % (key, val) for key, val in items)
style = etree.SubElement(head, XHTML('style'), type=CSS_MIME)
style.text = "\n\t\t@page { %s; }" % css
css = ';\n'.join("%s: %s" % (key, val) for key, val in items)
css = '@page {\n%s\n}\n'%css
rules = [r.cssText for r in stylizer.font_face_rules]
raw = '\n\n'.join(rules)
# Make URLs referring to fonts relative to this item
sheet = cssutils.parseString(raw, validate=False)
cssutils.replaceUrls(sheet, item.relhref, ignoreImportRules=True)
style.text += '\n' + sheet.cssText
css += '\n\n' + raw
global_css[css].append(item)
def replace_css(self, css):
gc_map = {}
manifest = self.oeb.manifest
id, href = manifest.generate('css', 'stylesheet.css')
for item in manifest.values():
if item.media_type in OEB_STYLES:
manifest.remove(item)
item = manifest.add(id, href, CSS_MIME, data=css)
return href
for css in global_css:
id_, href = manifest.generate('page_css', 'page_styles.css')
manifest.add(id_, href, CSS_MIME, data=cssutils.parseString(css,
validate=False))
gc_map[css] = href
ans = {}
for css, items in global_css.iteritems():
for item in items:
ans[item] = gc_map[css]
return ans
def flatten_spine(self):
names = defaultdict(int)
@ -433,7 +455,8 @@ class CSSFlattener(object):
items.sort()
css = ''.join(".%s {\n%s;\n}\n\n" % (key, val) for key, val in items)
href = self.replace_css(css)
global_css = self.collect_global_css()
for item in self.oeb.spine:
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 calibre.ebooks.oeb.base import OEB_STYLES, barename, XPath
from calibre.ebooks.oeb.base import barename, XPath
class RemoveAdobeMargins(object):
'''
@ -51,10 +51,7 @@ class RemoveFakeMargins(object):
self.stats = {}
self.selector_map = {}
for item in self.oeb.manifest:
if item.media_type.lower() in OEB_STYLES:
stylesheet = item
break
stylesheet = self.oeb.manifest.main_stylesheet
if stylesheet is None:
return