diff --git a/src/calibre/ebooks/oeb/normalize_css.py b/src/calibre/ebooks/oeb/normalize_css.py index 311bd7ca9b..44cc3c51b1 100644 --- a/src/calibre/ebooks/oeb/normalize_css.py +++ b/src/calibre/ebooks/oeb/normalize_css.py @@ -113,7 +113,7 @@ def normalize_simple_composition(name, cssvalue, composition, check_inherit=True font_composition = ('font-style', 'font-variant', 'font-weight', 'font-size', 'line-height', 'font-family') -def normalize_font(name, cssvalue): +def normalize_font(cssvalue, font_family_as_list=False): # See https://developer.mozilla.org/en-US/docs/Web/CSS/font composition = font_composition val = cssvalue.cssText @@ -121,28 +121,44 @@ def normalize_font(name, cssvalue): return {k:'inherit' for k in composition} if val in {'caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar'}: return {k:DEFAULTS[k] for k in composition} - try: - primitives = list(v.cssText for v in cssvalue) - except TypeError: - primitives = [cssvalue.cssText] - if len(primitives) < 2: + if getattr(cssvalue, 'length', 1) < 2: return {} # Mandatory to define both font size and font family style = {k:DEFAULTS[k] for k in composition} - style['font-family'] = primitives.pop() - if len(primitives) > 1 and cssprofiles.validate('line-height', primitives[-1]) and cssprofiles.validate('font-size', primitives[-2]): - style['line-height'], style['font-size'] = primitives.pop(), primitives.pop() - else: - val = primitives.pop() - if not cssprofiles.validate('font-size', val): - return {} - style['font-size'] = val - composition = composition[:3] - while primitives: - value = primitives.pop() - for key in composition: - if cssprofiles.validate(key, value): - style[key] = value + families = [] + vals = [x.cssText for x in cssvalue] + found_font_size = False + while vals: + text = vals.pop() + if not families and text == 'inherit': + families.append(text) + continue + if cssprofiles.validate('font-size', text): + style['font-size'] = text + found_font_size = True + break + if cssprofiles.validate('line-height', text): + style['line-height'] = text + if not vals or not cssprofiles.validate('font-size', vals[-1]): + return {} # must have font-size here + style['font-size'] = vals.pop() + found_font_size = True + break + if families == ['inherit']: + return {} # Cannot have multiple font-families if the last one if inherit + families.insert(0, text) + if not families or not found_font_size: + return {} # font-family required + style['font-family'] = families if font_family_as_list else ', '.join(families) + props = ['font-style', 'font-variant', 'font-weight'] + while vals: + for i, prop in enumerate(tuple(props)): + if cssprofiles.validate(prop, vals[0]): + props.pop(i) + style[prop] = vals.pop(0) break + else: + return {} # unrecognized value + return style def normalize_border(name, cssvalue): @@ -154,7 +170,7 @@ def normalize_border(name, cssvalue): normalizers = { 'list-style': simple_normalizer('list-style', ('type', 'position', 'image')), - 'font': normalize_font, + 'font': lambda prop, v: normalize_font(v), 'border': normalize_border, } @@ -268,7 +284,7 @@ def test_normalization(): # {{{ def test_font_normalization(self): def font_dict(expected): - ans = {k:DEFAULTS[k] for k in font_composition} + ans = {k:DEFAULTS[k] for k in font_composition} if expected else {} ans.update(expected) return ans @@ -276,6 +292,7 @@ def test_normalization(): # {{{ 'some_font': {}, 'none': {}, 'inherit':{k:'inherit' for k in font_composition}, '1.2pt/1.4 A_Font': {'font-family':'A_Font', 'font-size':'1.2pt', 'line-height':'1.4'}, 'bad font': {}, '10% serif': {'font-family':'serif', 'font-size':'10%'}, + '12px "My Font", serif': {'font-family':'"My Font", serif', 'font-size': '12px'}, 'bold italic large serif': {'font-family':'serif', 'font-weight':'bold', 'font-style':'italic', 'font-size':'large'}, 'bold italic small-caps larger/normal serif': {'font-family':'serif', 'font-weight':'bold', 'font-style':'italic', 'font-size':'larger', @@ -283,10 +300,7 @@ def test_normalization(): # {{{ }.iteritems(): val = tuple(parseStyle('font: %s' % raw, validate=False))[0].cssValue style = normalizers['font']('font', val) - if expected: - self.assertDictEqual(font_dict(expected), style) - else: - self.assertDictEqual(expected, style) + self.assertDictEqual(font_dict(expected), style, raw) def test_border_normalization(self): def border_edge_dict(expected, edge='right'):