def collect_font_face_rules(self, container, processed, spine_name, sheet, sheet_name): if sheet_name in processed: sheet_rules = processed[sheet_name] else: sheet_rules = [] if sheet_name != spine_name: processed[sheet_name] = sheet_rules for rule, base_name, rule_index in iterrules(container, sheet_name, rules=sheet, rule_type='FONT_FACE_RULE'): cssdict = {} for prop in iterdeclaration(rule.style): if prop.name == 'font-family': 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': for val in prop.propertyValue: x = val.value fname = container.href_to_name(x, sheet_name) if container.has_name(fname): cssdict['src'] = fname break else: container.log.warn('The @font-face rule refers to a font file that does not exist in the book: %s' % prop.propertyValue.cssText) if 'src' not in cssdict: continue ff = cssdict.get('font-family') if not ff or ff[0] in bad_fonts: continue normalize_font_properties(cssdict) prepare_font_rule(cssdict) sheet_rules.append(cssdict) self.font_rule_map[spine_name].extend(sheet_rules)
def change_font_in_declaration(style, old_name, new_name=None): changed = False 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 change_font_in_declaration(style, old_name, new_name=None): changed = False ff = style.getProperty('font-family') if ff is not None: fams = parse_font_family(css_text(ff.propertyValue)) nfams = list( 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(css_text(ff.propertyValue)) fams = props.get('font-family') or [] nfams = list( 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 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(css_text(prop.propertyValue))) ans[prop.name] = Values(prop.propertyValue, sheet_name, prop.priority) return ans
def font_family_data_from_sheet(sheet, families): for rule in sheet.cssRules: if rule.type == rule.STYLE_RULE: font_family_data_from_declaration(rule.style, families) elif rule.type == rule.FONT_FACE_RULE: ff = rule.style.getProperty('font-family') if ff is not None: for f in parse_font_family(css_text(ff.propertyValue)): families[f] = True
def font_family_data_from_sheet(sheet, families): for rule in sheet.cssRules: if rule.type == rule.STYLE_RULE: font_family_data_from_declaration(rule.style, families) elif rule.type == rule.FONT_FACE_RULE: ff = rule.style.getProperty('font-family') if ff is not None: for f in parse_font_family(ff.propertyValue.cssText): families[f] = True
def test_parse_font_family(self): ' Test parsing of font-family values ' for raw, q in iteritems({ '"1as"': ['1as'], 'A B C, serif': ['A B C', 'serif'], r'Red\/Black': ['Red/Black'], 'A B': ['A B'], r'Ahem\!': ['Ahem!'], r'"Ahem!"': ['Ahem!'], '€42': ['€42'], r'Hawaii\ 5-0': ['Hawaii 5-0'], r'"X \"Y"': ['X "Y'], 'A B, C D, "E", serif': ['A B', 'C D', 'E', 'serif'], '': [], '"", a': ['a'], }): self.ae(q, parse_font_family(raw)) for single in ('serif', 'sans-serif', 'A B C'): self.ae([single], parse_font_family(single))
def check_fonts(container): font_map = {} errors = [] for name, mt in container.mime_map.iteritems(): if mt in OEB_FONTS: raw = container.raw_data(name) try: name_map = get_all_font_names(raw) except Exception as e: errors.append(InvalidFont(_('Not a valid font: %s') % e, name)) continue font_map[name] = name_map.get('family_name', None) or name_map.get( 'preferred_family_name', None) or name_map.get( 'wws_family_name', None) try: embeddable, fs_type = is_font_embeddable(raw) except UnsupportedFont: embeddable = True if not embeddable: errors.append(NotEmbeddable(name, fs_type)) sheets = [] for name, mt in container.mime_map.iteritems(): if mt in OEB_STYLES: try: sheets.append((name, container.parsed(name), None)) except Exception: pass # Could not parse, ignore elif mt in OEB_DOCS: for style in container.parsed(name).xpath( '//*[local-name()="style"]'): if style.get('type', 'text/css') == 'text/css' and style.text: sheets.append((name, container.parse_css(style.text), style.sourceline)) for name, sheet, line_offset in sheets: for rule in sheet.cssRules.rulesOfType(CSSRule.FONT_FACE_RULE): src = rule.style.getPropertyCSSValue('src') if src is not None and src.length > 0: href = getattr(src.item(0), 'uri', None) if href is not None: fname = container.href_to_name(href, name) font_name = font_map.get(fname, None) if font_name is None: continue families = parse_font_family( rule.style.getPropertyValue('font-family')) if families: if families[0] != font_name: errors.append( FontAliasing(font_name, families[0], name, line_offset)) return errors
def font_family_data_from_declaration(style, families): font_families = [] f = style.getProperty('font') if f is not None: f = normalize_font(f.propertyValue, font_family_as_list=True).get('font-family', None) if f is not None: font_families = [unquote(x) for x in f] f = style.getProperty('font-family') if f is not None: font_families = parse_font_family(f.propertyValue.cssText) for f in font_families: families[f] = families.get(f, False)
def process_fonts(self): ''' Make sure all fonts are embeddable. Also remove some fonts that cause problems. ''' from calibre.ebooks.oeb.base import urlnormalize from calibre.utils.fonts.utils import remove_embed_restriction processed = set() for item in list(self.oeb.manifest): if not hasattr(item.data, 'cssRules'): continue for i, rule in enumerate(item.data.cssRules): if rule.type == rule.FONT_FACE_RULE: try: s = rule.style src = s.getProperty('src').propertyValue[0].uri except: continue path = item.abshref(src) ff = self.oeb.manifest.hrefs.get(urlnormalize(path), None) if ff is None: continue raw = nraw = ff.data if path not in processed: processed.add(path) try: nraw = remove_embed_restriction(raw) except: continue if nraw != raw: ff.data = nraw self.oeb.container.write(path, nraw) elif iswindows and rule.type == rule.STYLE_RULE: from tinycss.fonts3 import parse_font_family, serialize_font_family s = rule.style f = s.getProperty(u'font-family') if f is not None: font_families = parse_font_family( f.propertyValue.cssText) ff = [ x for x in font_families if x.lower() != u'courier' ] if len(ff) != len(font_families): if 'courier' not in self.filtered_font_warnings: # See https://bugs.launchpad.net/bugs/1665835 self.filtered_font_warnings.add(u'courier') self.log.warn( u'Removing courier font family as it does not render on windows' ) f.propertyValue.cssText = serialize_font_family( ff or [u'monospace'])
def change_font_in_sheet(container, sheet, old_name, new_name, sheet_name): changed = False removals = [] for rule in sheet.cssRules: if rule.type == rule.STYLE_RULE: changed |= change_font_in_declaration(rule.style, old_name, new_name) elif rule.type == rule.FONT_FACE_RULE: ff = rule.style.getProperty('font-family') if ff is not None: families = {x for x in parse_font_family(ff.propertyValue.cssText)} if old_name in families: changed = True removals.append(rule) for rule in reversed(removals): remove_embedded_font(container, sheet, rule, sheet_name) return changed
def check_fonts(container): font_map = {} errors = [] for name, mt in iteritems(container.mime_map): if mt in OEB_FONTS: raw = container.raw_data(name) try: name_map = get_all_font_names(raw) except Exception as e: errors.append(InvalidFont(_('Not a valid font: %s') % e, name)) continue font_map[name] = name_map.get('family_name', None) or name_map.get('preferred_family_name', None) or name_map.get('wws_family_name', None) try: embeddable, fs_type = is_font_embeddable(raw) except UnsupportedFont: embeddable = True if not embeddable: errors.append(NotEmbeddable(name, fs_type)) sheets = [] for name, mt in iteritems(container.mime_map): if mt in OEB_STYLES: try: sheets.append((name, container.parsed(name), None)) except Exception: pass # Could not parse, ignore elif mt in OEB_DOCS: for style in container.parsed(name).xpath('//*[local-name()="style"]'): if style.get('type', 'text/css') == 'text/css' and style.text: sheets.append((name, container.parse_css(style.text), style.sourceline)) for name, sheet, line_offset in sheets: for rule in sheet.cssRules.rulesOfType(CSSRule.FONT_FACE_RULE): src = rule.style.getPropertyCSSValue('src') if src is not None and src.length > 0: href = getattr(src.item(0), 'uri', None) if href is not None: fname = container.href_to_name(href, name) font_name = font_map.get(fname, None) if font_name is None: continue families = parse_font_family(rule.style.getPropertyValue('font-family')) if families: if families[0] != font_name: errors.append(FontAliasing(font_name, families[0], name, line_offset)) return errors
def get_font_properties(rule, default=None): ''' Given a CSS rule, extract normalized font properties from it. Note that shorthand font property should already have been expanded by the CSS flattening code. ''' props = {} s = rule.style for q in ('font-family', 'src', 'font-weight', 'font-stretch', 'font-style'): g = 'uri' if q == 'src' else 'value' try: val = s.getProperty(q).propertyValue[0] val = getattr(val, g) if q == 'font-family': val = parse_font_family( css_text(s.getProperty(q).propertyValue)) if val and val[0] == 'inherit': val = None except (IndexError, KeyError, AttributeError, TypeError, ValueError): val = None if q in {'src', 'font-family'} else default if q in {'font-weight', 'font-stretch', 'font-style'}: val = unicode_type(val).lower() if (val or val == 0) else val if val == 'inherit': val = default if q == 'font-weight': val = {'normal': '400', 'bold': '700'}.get(val, val) if val not in { '100', '200', '300', '400', '500', '600', '700', '800', '900', 'bolder', 'lighter' }: val = default if val == 'normal': val = '400' elif q == 'font-style': if val not in {'normal', 'italic', 'oblique'}: val = default elif q == 'font-stretch': if val not in { 'normal', 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded' }: val = default props[q] = val return props
def process_fonts(self): ''' Make sure all fonts are embeddable. Also remove some fonts that causes problems. ''' from calibre.ebooks.oeb.base import urlnormalize from calibre.utils.fonts.utils import remove_embed_restriction processed = set() for item in list(self.oeb.manifest): if not hasattr(item.data, 'cssRules'): continue for i, rule in enumerate(item.data.cssRules): if rule.type == rule.FONT_FACE_RULE: try: s = rule.style src = s.getProperty('src').propertyValue[0].uri except: continue path = item.abshref(src) ff = self.oeb.manifest.hrefs.get(urlnormalize(path), None) if ff is None: continue raw = nraw = ff.data if path not in processed: processed.add(path) try: nraw = remove_embed_restriction(raw) except: continue if nraw != raw: ff.data = nraw self.oeb.container.write(path, nraw) elif iswindows and rule.type == rule.STYLE_RULE: from tinycss.fonts3 import parse_font_family, serialize_font_family s = rule.style f = s.getProperty(u'font-family') if f is not None: font_families = parse_font_family(f.propertyValue.cssText) ff = [x for x in font_families if x.lower() != u'courier'] if len(ff) != len(font_families): if 'courier' not in self.filtered_font_warnings: # See https://bugs.launchpad.net/bugs/1665835 self.filtered_font_warnings.add(u'courier') self.log.warn(u'Removing courier font family as it does not render on windows') f.propertyValue.cssText = serialize_font_family(ff or [u'monospace'])
def get_font_properties(rule, default=None): ''' Given a CSS rule, extract normalized font properties from it. Note that shorthand font property should already have been expanded by the CSS flattening code. ''' props = {} s = rule.style for q in ('font-family', 'src', 'font-weight', 'font-stretch', 'font-style'): g = 'uri' if q == 'src' else 'value' try: val = s.getProperty(q).propertyValue[0] val = getattr(val, g) if q == 'font-family': val = parse_font_family(s.getProperty(q).propertyValue.cssText) if val and val[0] == 'inherit': val = None except (IndexError, KeyError, AttributeError, TypeError, ValueError): val = None if q in {'src', 'font-family'} else default if q in {'font-weight', 'font-stretch', 'font-style'}: val = unicode(val).lower() if (val or val == 0) else val if val == 'inherit': val = default if q == 'font-weight': val = {'normal':'400', 'bold':'700'}.get(val, val) if val not in {'100', '200', '300', '400', '500', '600', '700', '800', '900', 'bolder', 'lighter'}: val = default if val == 'normal': val = '400' elif q == 'font-style': if val not in {'normal', 'italic', 'oblique'}: val = default elif q == 'font-stretch': if val not in {'normal', 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded'}: val = default props[q] = val return props