Пример #1
0
 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)
Пример #2
0
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
Пример #3
0
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
Пример #4
0
 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)
Пример #5
0
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
Пример #6
0
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
Пример #7
0
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
Пример #8
0
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
Пример #9
0
 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))
Пример #10
0
 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))
Пример #11
0
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
Пример #12
0
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)
Пример #13
0
    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'])
Пример #14
0
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
Пример #15
0
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
Пример #16
0
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
Пример #17
0
    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'])
Пример #18
0
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