From 6bb8a2eb7ebecbbd9ec113c4d660f91000371431 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 30 Jun 2016 15:13:25 +0530 Subject: [PATCH] Use the new font family parsing code in the rest of the container infrastucture --- src/calibre/ebooks/oeb/polish/cascade.py | 4 ++ src/calibre/ebooks/oeb/polish/check/fonts.py | 39 ++--------- src/calibre/ebooks/oeb/polish/fonts.py | 68 ++++++++------------ src/calibre/ebooks/oeb/polish/stats.py | 3 +- 4 files changed, 37 insertions(+), 77 deletions(-) diff --git a/src/calibre/ebooks/oeb/polish/cascade.py b/src/calibre/ebooks/oeb/polish/cascade.py index a2f2447417..1e0fb45e79 100644 --- a/src/calibre/ebooks/oeb/polish/cascade.py +++ b/src/calibre/ebooks/oeb/polish/cascade.py @@ -18,6 +18,7 @@ from calibre.ebooks.css_transform_rules import all_properties from calibre.ebooks.oeb.base import OEB_STYLES, XHTML from calibre.ebooks.oeb.normalize_css import normalizers, DEFAULTS from calibre.ebooks.oeb.stylizer import media_ok, INHERITED +from tinycss.fonts3 import serialize_font_family, parse_font_family _html_css_stylesheet = None @@ -116,6 +117,9 @@ class Values(tuple): def normalize_style_declaration(decl, sheet_name): ans = {} for prop in iterdeclaration(decl): + if prop.name == 'font-family': + # Needed because of https://bitbucket.org/cthedot/cssutils/issues/66/incorrect-handling-of-spaces-in-font + prop.propertyValue.cssText = serialize_font_family(parse_font_family(prop.propertyValue.cssText)) ans[prop.name] = Values(prop.propertyValue, sheet_name, prop.priority) return ans diff --git a/src/calibre/ebooks/oeb/polish/check/fonts.py b/src/calibre/ebooks/oeb/polish/check/fonts.py index 63e765c096..7d9fdd29ed 100644 --- a/src/calibre/ebooks/oeb/polish/check/fonts.py +++ b/src/calibre/ebooks/oeb/polish/check/fonts.py @@ -12,52 +12,21 @@ from calibre import force_unicode from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES from calibre.ebooks.oeb.polish.check.base import BaseError, WARN from calibre.ebooks.oeb.polish.container import OEB_FONTS -from calibre.ebooks.oeb.polish.fonts import change_font_family_value from calibre.ebooks.oeb.polish.pretty import pretty_script_or_style +from calibre.ebooks.oeb.polish.fonts import change_font_in_declaration from calibre.utils.fonts.utils import get_all_font_names -from tinycss.fonts3 import parse_font_family, parse_font, serialize_font_family, serialize_font +from tinycss.fonts3 import parse_font_family class InvalidFont(BaseError): HELP = _('This font could not be processed. It most likely will' ' not work in an ebook reader, either') -def fix_property(prop, css_name, font_name): - changed = False - ff = prop.propertyValue - for i in xrange(ff.length): - val = ff.item(i) - if hasattr(val.value, 'lower') and val.value.lower() == css_name.lower(): - change_font_family_value(val, font_name) - changed = True - return changed - -def fix_declaration(style, css_name, font_name): - changed = False - ff = style.getProperty('font-family') - if ff is not None: - fams = parse_font_family(ff.propertyValue.cssText) - nfams = [font_name if x == css_name else x for x in fams] - if fams != nfams: - ff.propertyValue.cssText = serialize_font_family(nfams) - changed = True - ff = style.getProperty('font') - if ff is not None: - props = parse_font(ff.propertyValue.cssText) - fams = props.get('font-family') or [] - nfams = [font_name if x == css_name else x for x in fams] - if fams != nfams: - props['font-family'] = nfams - ff.propertyValue.cssText = serialize_font(props) - changed = True - return changed - def fix_sheet(sheet, css_name, font_name): changed = False for rule in sheet.cssRules: if rule.type in (CSSRule.FONT_FACE_RULE, CSSRule.STYLE_RULE): - if fix_declaration(rule.style, css_name, font_name): - changed = True + changed = change_font_in_declaration(rule.style, css_name, font_name) or changed return changed class FontAliasing(BaseError): @@ -92,7 +61,7 @@ class FontAliasing(BaseError): changed = True for elem in container.parsed(name).xpath('//*[@style and contains(@style, "font-family")]'): style = container.parse_css(elem.get('style'), is_declaration=True) - if fix_declaration(style, self.css_name, self.font_name): + if change_font_in_declaration(style, self.css_name, self.font_name): elem.set('style', force_unicode(style.cssText, 'utf-8').replace('\n', ' ')) container.dirty(name) changed = True diff --git a/src/calibre/ebooks/oeb/polish/fonts.py b/src/calibre/ebooks/oeb/polish/fonts.py index c63abfcc34..248d0ca541 100644 --- a/src/calibre/ebooks/oeb/polish/fonts.py +++ b/src/calibre/ebooks/oeb/polish/fonts.py @@ -6,10 +6,9 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' -import re - from calibre.ebooks.oeb.polish.container import OEB_STYLES, OEB_DOCS from calibre.ebooks.oeb.normalize_css import normalize_font +from tinycss.fonts3 import parse_font_family, parse_font, serialize_font_family, serialize_font def unquote(x): if x and len(x) > 1 and x[0] == x[-1] and x[0] in ('"', "'"): @@ -25,7 +24,7 @@ def font_family_data_from_declaration(style, families): font_families = [unquote(x) for x in f] f = style.getProperty('font-family') if f is not None: - font_families = [x.value for x in f.propertyValue] + font_families = parse_font_family(f.propertyValue.cssText) for f in font_families: families[f] = families.get(f, False) @@ -37,8 +36,8 @@ def font_family_data_from_sheet(sheet, families): elif rule.type == rule.FONT_FACE_RULE: ff = rule.style.getProperty('font-family') if ff is not None: - for f in ff.propertyValue: - families[f.value] = True + for f in parse_font_family(ff.propertyValue.cssText): + families[f] = True def font_family_data(container): families = {} @@ -58,43 +57,30 @@ def font_family_data(container): font_family_data_from_declaration(style, families) return families -def change_font_family_value(cssvalue, new_name): - # If cssvalue.type == 'IDENT' cssutils will not serialize the font - # name properly (it will not enclose it in quotes). So we - # use the following hack (setting an internal property of the - # Value class) - cssvalue.value = new_name - cssvalue._type = 'STRING' - -def change_font_family_in_property(style, prop, old_name, new_name=None): - changed = False - families = {x.value for x in prop.propertyValue} - _dummy_family = 'd7d81cf1-1c8c-4993-b788-e1ab596c0f1f' - if new_name and new_name in families: - new_name = None # new name already exists in this property, so simply remove old_name - for val in prop.propertyValue: - if val.value == old_name: - change_font_family_value(val, new_name or _dummy_family) - changed = True - if changed and not new_name: - # Remove dummy family, cssutils provides no clean way to do this, so we - # roundtrip via cssText - pat = re.compile(r'''['"]{0,1}%s['"]{0,1}\s*,{0,1}''' % _dummy_family) - repl = pat.sub('', prop.propertyValue.cssText).strip().rstrip(',').strip() - if repl: - prop.propertyValue.cssText = repl - if prop.name == 'font' and not prop.validate(): - style.removeProperty(prop.name) # no families left in font: - else: - style.removeProperty(prop.name) - return changed - def change_font_in_declaration(style, old_name, new_name=None): changed = False - for x in ('font', 'font-family'): - prop = style.getProperty(x) - if prop is not None: - changed |= change_font_family_in_property(style, prop, old_name, new_name) + ff = style.getProperty('font-family') + if ff is not None: + fams = parse_font_family(ff.propertyValue.cssText) + nfams = filter(None, [new_name if x == old_name else x for x in fams]) + if fams != nfams: + if nfams: + ff.propertyValue.cssText = serialize_font_family(nfams) + else: + style.removeProperty(ff.name) + changed = True + ff = style.getProperty('font') + if ff is not None: + props = parse_font(ff.propertyValue.cssText) + fams = props.get('font-family') or [] + nfams = filter(None, [new_name if x == old_name else x for x in fams]) + if fams != nfams: + props['font-family'] = nfams + if nfams: + ff.propertyValue.cssText = serialize_font(props) + else: + style.removeProperty(ff.name) + changed = True return changed def remove_embedded_font(container, sheet, rule, sheet_name): @@ -118,7 +104,7 @@ def change_font_in_sheet(container, sheet, old_name, new_name, sheet_name): elif rule.type == rule.FONT_FACE_RULE: ff = rule.style.getProperty('font-family') if ff is not None: - families = {x.value for x in ff.propertyValue} + families = {x for x in parse_font_family(ff.propertyValue.cssText)} if old_name in families: changed = True removals.append(rule) diff --git a/src/calibre/ebooks/oeb/polish/stats.py b/src/calibre/ebooks/oeb/polish/stats.py index 0772410ad6..bfb44eb828 100644 --- a/src/calibre/ebooks/oeb/polish/stats.py +++ b/src/calibre/ebooks/oeb/polish/stats.py @@ -16,6 +16,7 @@ import regex from calibre.ebooks.oeb.base import XHTML from calibre.ebooks.oeb.polish.cascade import iterrules, resolve_styles, iterdeclaration from calibre.utils.icu import ord_string, safe_chr +from tinycss.fonts3 import parse_font_family def normalize_font_properties(font): w = font.get('font-weight', None) @@ -191,7 +192,7 @@ class StatsCollector(object): cssdict = {} for prop in iterdeclaration(rule.style): if prop.name == 'font-family': - cssdict['font-family'] = [icu_lower(x.value) for x in prop.propertyValue] + cssdict['font-family'] = [icu_lower(x) for x in parse_font_family(prop.propertyValue.cssText)] elif prop.name.startswith('font-'): cssdict[prop.name] = prop.propertyValue[0].value elif prop.name == 'src':