From 3b1921fca452d734110e10bbbed75eb8a1034221 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 4 May 2012 15:37:10 +0530 Subject: [PATCH] EPUB Output: Consolidate inline CSS generated by calibre into external stylesheets for ease of editing the EPUB --- .../ebooks/conversion/plugins/epub_output.py | 8 +-- .../ebooks/conversion/plugins/pdf_output.py | 8 +-- src/calibre/ebooks/oeb/base.py | 13 ++++ src/calibre/ebooks/oeb/transforms/flatcss.py | 65 +++++++++++++------ .../ebooks/oeb/transforms/page_margin.py | 7 +- 5 files changed, 63 insertions(+), 38 deletions(-) diff --git a/src/calibre/ebooks/conversion/plugins/epub_output.py b/src/calibre/ebooks/conversion/plugins/epub_output.py index 45df8ba9d1..0da2868969 100644 --- a/src/calibre/ebooks/conversion/plugins/epub_output.py +++ b/src/calibre/ebooks/conversion/plugins/epub_output.py @@ -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. diff --git a/src/calibre/ebooks/conversion/plugins/pdf_output.py b/src/calibre/ebooks/conversion/plugins/pdf_output.py index 4422265976..808d7edff3 100644 --- a/src/calibre/ebooks/conversion/plugins/pdf_output.py +++ b/src/calibre/ebooks/conversion/plugins/pdf_output.py @@ -99,12 +99,8 @@ class PDFOutput(OutputFormatPlugin): # Remove page-break-before on 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']) diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index dc71041caa..a18e528a51 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -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 diff --git a/src/calibre/ebooks/oeb/transforms/flatcss.py b/src/calibre/ebooks/oeb/transforms/flatcss.py index bd8db01f15..890c7db9ad 100644 --- a/src/calibre/ebooks/oeb/transforms/flatcss.py +++ b/src/calibre/ebooks/oeb/transforms/flatcss.py @@ -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,34 +390,56 @@ 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) - 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 - 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 + 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 - 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) + 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 = ';\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) + css += '\n\n' + raw + global_css[css].append(item) + + gc_map = {} + manifest = self.oeb.manifest + 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) styles = {} @@ -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]) diff --git a/src/calibre/ebooks/oeb/transforms/page_margin.py b/src/calibre/ebooks/oeb/transforms/page_margin.py index d7c99d24c6..9181c8fd4e 100644 --- a/src/calibre/ebooks/oeb/transforms/page_margin.py +++ b/src/calibre/ebooks/oeb/transforms/page_margin.py @@ -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