Exemplo n.º 1
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
Exemplo n.º 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(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
Exemplo n.º 3
0
def normalize_edge(name, cssvalue):
    style = {}
    if isinstance(cssvalue, PropertyValue):
        primitives = [css_text(v) for v in cssvalue]
    else:
        primitives = [css_text(cssvalue)]
    if len(primitives) == 1:
        value, = primitives
        values = (value, value, value, value)
    elif len(primitives) == 2:
        vert, horiz = primitives
        values = (vert, horiz, vert, horiz)
    elif len(primitives) == 3:
        top, horiz, bottom = primitives
        values = (top, horiz, bottom, horiz)
    else:
        values = primitives[:4]
    if '-' in name:
        l, _, r = name.partition('-')
        for edge, value in zip(EDGES, values):
            style['%s-%s-%s' % (l, edge, r)] = value
    else:
        for edge, value in zip(EDGES, values):
            style['%s-%s' % (name, edge)] = value
    return style
Exemplo n.º 4
0
def normalize_edge(name, cssvalue):
    style = {}
    if isinstance(cssvalue, PropertyValue):
        primitives = [css_text(v) for v in cssvalue]
    else:
        primitives = [css_text(cssvalue)]
    if len(primitives) == 1:
        value, = primitives
        values = (value, value, value, value)
    elif len(primitives) == 2:
        vert, horiz = primitives
        values = (vert, horiz, vert, horiz)
    elif len(primitives) == 3:
        top, horiz, bottom = primitives
        values = (top, horiz, bottom, horiz)
    else:
        values = primitives[:4]
    if '-' in name:
        l, _, r = name.partition('-')
        for edge, value in zip(EDGES, values):
            style['%s-%s-%s' % (l, edge, r)] = value
    else:
        for edge, value in zip(EDGES, values):
            style['%s-%s' % (name, edge)] = value
    return style
Exemplo n.º 5
0
def remove_links_to(container, predicate):
    ''' predicate must be a function that takes the arguments (name, href,
    fragment=None) and returns True iff the link should be removed '''
    from calibre.ebooks.oeb.base import iterlinks, OEB_DOCS, OEB_STYLES, XPath, XHTML
    stylepath = XPath('//h:style')
    styleattrpath = XPath('//*[@style]')
    changed = set()
    for name, mt in iteritems(container.mime_map):
        removed = False
        if mt in OEB_DOCS:
            root = container.parsed(name)
            for el, attr, href, pos in iterlinks(root,
                                                 find_links_in_css=False):
                hname = container.href_to_name(href, name)
                frag = href.partition('#')[-1]
                if predicate(hname, href, frag):
                    if attr is None:
                        el.text = None
                    else:
                        if el.tag == XHTML('link') or el.tag == XHTML('img'):
                            extract(el)
                        else:
                            del el.attrib[attr]
                    removed = True
            for tag in stylepath(root):
                if tag.text and (tag.get('type')
                                 or 'text/css').lower() == 'text/css':
                    sheet = container.parse_css(tag.text)
                    if remove_links_in_sheet(
                            partial(container.href_to_name, base=name), sheet,
                            predicate):
                        tag.text = css_text(sheet)
                        removed = True
            for tag in styleattrpath(root):
                style = tag.get('style')
                if style:
                    style = container.parse_css(style, is_declaration=True)
                    if remove_links_in_declaration(
                            partial(container.href_to_name, base=name), style,
                            predicate):
                        removed = True
                        tag.set('style', css_text(style))
        elif mt in OEB_STYLES:
            removed = remove_links_in_sheet(
                partial(container.href_to_name, base=name),
                container.parsed(name), predicate)
        if removed:
            changed.add(name)
    for i in changed:
        container.dirty(i)
    return changed
Exemplo n.º 6
0
def remove_property_value(prop, predicate):
    ''' Remove the Values that match the predicate from this property. If all
    values of the property would be removed, the property is removed from its
    parent instead. Note that this means the property must have a parent (a
    CSSStyleDeclaration). '''
    removed_vals = list(filter(predicate, prop.propertyValue))
    if len(removed_vals) == len(prop.propertyValue):
        prop.parent.removeProperty(prop.name)
    else:
        x = css_text(prop.propertyValue)
        for v in removed_vals:
            x = x.replace(css_text(v), '').strip()
        prop.propertyValue.cssText = x
    return bool(removed_vals)
Exemplo n.º 7
0
def remove_property_value(prop, predicate):
    ''' Remove the Values that match the predicate from this property. If all
    values of the property would be removed, the property is removed from its
    parent instead. Note that this means the property must have a parent (a
    CSSStyleDeclaration). '''
    removed_vals = list(filter(predicate, prop.propertyValue))
    if len(removed_vals) == len(prop.propertyValue):
        prop.parent.removeProperty(prop.name)
    else:
        x = css_text(prop.propertyValue)
        for v in removed_vals:
            x = x.replace(css_text(v), '').strip()
        prop.propertyValue.cssText = x
    return bool(removed_vals)
Exemplo n.º 8
0
 def test_border_condensation(self):
     vals = 'red solid 5px'
     css = '; '.join('border-%s-%s: %s' % (edge, p, v) for edge in EDGES
                     for p, v in zip(BORDER_PROPS, vals.split()))
     style = parseStyle(css)
     condense_rule(style)
     for e, p in product(EDGES, BORDER_PROPS):
         self.assertFalse(style.getProperty('border-%s-%s' % (e, p)))
         self.assertFalse(style.getProperty('border-%s' % e))
         self.assertFalse(style.getProperty('border-%s' % p))
     self.assertEqual(style.getProperty('border').value, vals)
     css = '; '.join('border-%s-%s: %s' % (edge, p, v)
                     for edge in ('top', )
                     for p, v in zip(BORDER_PROPS, vals.split()))
     style = parseStyle(css)
     condense_rule(style)
     self.assertEqual(css_text(style), 'border-top: %s' % vals)
     css += ';' + '; '.join(
         'border-%s-%s: %s' % (edge, p, v)
         for edge in ('right', 'left', 'bottom')
         for p, v in zip(BORDER_PROPS,
                         vals.replace('red', 'green').split()))
     style = parseStyle(css)
     condense_rule(style)
     self.assertEqual(len(style.getProperties()), 4)
     self.assertEqual(style.getProperty('border-top').value, vals)
     self.assertEqual(
         style.getProperty('border-left').value,
         vals.replace('red', 'green'))
Exemplo n.º 9
0
def change_font(container, old_name, new_name=None):
    '''
    Change a font family from old_name to new_name. Changes all occurrences of
    the font family in stylesheets, style tags and style attributes.
    If the old_name refers to an embedded font, it is removed. You can set
    new_name to None to remove the font family instead of changing it.
    '''
    changed = False
    for name, mt in tuple(iteritems(container.mime_map)):
        if mt in OEB_STYLES:
            sheet = container.parsed(name)
            if change_font_in_sheet(container, sheet, old_name, new_name, name):
                container.dirty(name)
                changed = True
        elif mt in OEB_DOCS:
            root = container.parsed(name)
            for style in root.xpath('//*[local-name() = "style"]'):
                if style.text and style.get('type', 'text/css').lower() == 'text/css':
                    sheet = container.parse_css(style.text)
                    if change_font_in_sheet(container, sheet, old_name, new_name, name):
                        container.dirty(name)
                        changed = True
            for elem in root.xpath('//*[@style]'):
                style = elem.get('style', '')
                if style:
                    style = container.parse_css(style, is_declaration=True)
                    if change_font_in_declaration(style, old_name, new_name):
                        style = css_text(style).strip().rstrip(';').strip()
                        if style:
                            elem.set('style', style)
                        else:
                            del elem.attrib['style']
                        container.dirty(name)
                        changed = True
    return changed
Exemplo n.º 10
0
def change_font(container, old_name, new_name=None):
    '''
    Change a font family from old_name to new_name. Changes all occurrences of
    the font family in stylesheets, style tags and style attributes.
    If the old_name refers to an embedded font, it is removed. You can set
    new_name to None to remove the font family instead of changing it.
    '''
    changed = False
    for name, mt in tuple(iteritems(container.mime_map)):
        if mt in OEB_STYLES:
            sheet = container.parsed(name)
            if change_font_in_sheet(container, sheet, old_name, new_name, name):
                container.dirty(name)
                changed = True
        elif mt in OEB_DOCS:
            root = container.parsed(name)
            for style in root.xpath('//*[local-name() = "style"]'):
                if style.text and style.get('type', 'text/css').lower() == 'text/css':
                    sheet = container.parse_css(style.text)
                    if change_font_in_sheet(container, sheet, old_name, new_name, name):
                        container.dirty(name)
                        changed = True
            for elem in root.xpath('//*[@style]'):
                style = elem.get('style', '')
                if style:
                    style = container.parse_css(style, is_declaration=True)
                    if change_font_in_declaration(style, old_name, new_name):
                        style = css_text(style).strip().rstrip(';').strip()
                        if style:
                            elem.set('style', style)
                        else:
                            del elem.attrib['style']
                        container.dirty(name)
                        changed = True
    return changed
Exemplo n.º 11
0
def normalize_simple_composition(name, cssvalue, composition, check_inherit=True):
    if check_inherit and css_text(cssvalue) == 'inherit':
        style = {k:'inherit' for k in composition}
    else:
        style = {k:DEFAULTS[k] for k in composition}
        try:
            primitives = [css_text(v) for v in cssvalue]
        except TypeError:
            primitives = [css_text(cssvalue)]
        while primitives:
            value = primitives.pop()
            for key in composition:
                if cssprofiles.validate(key, value):
                    style[key] = value
                    break
    return style
Exemplo n.º 12
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
Exemplo n.º 13
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
Exemplo n.º 14
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
Exemplo n.º 15
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
Exemplo n.º 16
0
def remove_links_to(container, predicate):
    ''' predicate must be a function that takes the arguments (name, href,
    fragment=None) and returns True iff the link should be removed '''
    from calibre.ebooks.oeb.base import iterlinks, OEB_DOCS, OEB_STYLES, XPath, XHTML
    stylepath = XPath('//h:style')
    styleattrpath = XPath('//*[@style]')
    changed = set()
    for name, mt in iteritems(container.mime_map):
        removed = False
        if mt in OEB_DOCS:
            root = container.parsed(name)
            for el, attr, href, pos in iterlinks(root, find_links_in_css=False):
                hname = container.href_to_name(href, name)
                frag = href.partition('#')[-1]
                if predicate(hname, href, frag):
                    if attr is None:
                        el.text = None
                    else:
                        if el.tag == XHTML('link') or el.tag == XHTML('img'):
                            extract(el)
                        else:
                            del el.attrib[attr]
                    removed = True
            for tag in stylepath(root):
                if tag.text and (tag.get('type') or 'text/css').lower() == 'text/css':
                    sheet = container.parse_css(tag.text)
                    if remove_links_in_sheet(partial(container.href_to_name, base=name), sheet, predicate):
                        tag.text = css_text(sheet)
                        removed = True
            for tag in styleattrpath(root):
                style = tag.get('style')
                if style:
                    style = container.parse_css(style, is_declaration=True)
                    if remove_links_in_declaration(partial(container.href_to_name, base=name), style, predicate):
                        removed = True
                        tag.set('style', css_text(style))
        elif mt in OEB_STYLES:
            removed = remove_links_in_sheet(partial(container.href_to_name, base=name), container.parsed(name), predicate)
        if removed:
            changed.add(name)
    tuple(map(container.dirty, changed))
    return changed
Exemplo n.º 17
0
 def __call__(self, oeb):
     if not self.body_font_family:
         return None
     if not self.href:
         iid, href = oeb.manifest.generate(u'page_styles', u'page_styles.css')
         rules = [css_text(x) for x in self.rules]
         rules = u'\n\n'.join(rules)
         sheet = css_parser.parseString(rules, validate=False)
         self.href = oeb.manifest.add(iid, href, guess_type(href)[0],
                 data=sheet).href
     return self.href
Exemplo n.º 18
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(
                             css_text(prop.propertyValue))
                     ]
                 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'
                             % css_text(prop.propertyValue))
             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)
Exemplo n.º 19
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(css_text(f.propertyValue))

    for f in font_families:
        families[f] = families.get(f, False)
Exemplo n.º 20
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(css_text(f.propertyValue))

    for f in font_families:
        families[f] = families.get(f, False)
Exemplo n.º 21
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, css_text
        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(
                            css_text(f.propertyValue))
                        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'])
Exemplo n.º 22
0
 def sort_css(self):
     from calibre.gui2.dialogs.confirm_delete import confirm
     if confirm(_('Sorting CSS rules can in rare cases change the effective styles applied to the book.'
                  ' Are you sure you want to proceed?'), 'edit-book-confirm-sort-css', parent=self, config_set=tprefs):
         c = self.textCursor()
         c.beginEditBlock()
         c.movePosition(c.Start), c.movePosition(c.End, c.KeepAnchor)
         text = unicode_type(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n').rstrip('\0')
         from calibre.ebooks.oeb.polish.css import sort_sheet
         text = css_text(sort_sheet(current_container(), text))
         c.insertText(text)
         c.movePosition(c.Start)
         c.endEditBlock()
         self.setTextCursor(c)
Exemplo n.º 23
0
 def sort_css(self):
     from calibre.gui2.dialogs.confirm_delete import confirm
     if confirm(_('Sorting CSS rules can in rare cases change the effective styles applied to the book.'
                  ' Are you sure you want to proceed?'), 'edit-book-confirm-sort-css', parent=self, config_set=tprefs):
         c = self.textCursor()
         c.beginEditBlock()
         c.movePosition(c.Start), c.movePosition(c.End, c.KeepAnchor)
         text = unicode_type(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n').rstrip('\0')
         from calibre.ebooks.oeb.polish.css import sort_sheet
         text = css_text(sort_sheet(current_container(), text))
         c.insertText(text)
         c.movePosition(c.Start)
         c.endEditBlock()
         self.setTextCursor(c)
Exemplo n.º 24
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(css_text(ff.propertyValue))}
                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
Exemplo n.º 25
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(css_text(ff.propertyValue))}
                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
Exemplo n.º 26
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
Exemplo n.º 27
0
def normalize_font(cssvalue, font_family_as_list=False):
    # See https://developer.mozilla.org/en-US/docs/Web/CSS/font
    composition = font_composition
    val = css_text(cssvalue)
    if val == 'inherit':
        ans = {k:'inherit' for k in composition}
    elif val in {'caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar'}:
        ans = {k:DEFAULTS[k] for k in composition}
    else:
        ans = {k:DEFAULTS[k] for k in composition}
        ans.update(parse_font(val))
    if font_family_as_list:
        if isinstance(ans['font-family'], string_or_bytes):
            ans['font-family'] = [x.strip() for x in ans['font-family'].split(',')]
    else:
        if not isinstance(ans['font-family'], string_or_bytes):
            ans['font-family'] = serialize_font_family(ans['font-family'])
    return ans
Exemplo n.º 28
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, css_text
        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(css_text(f.propertyValue))
                        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'])
Exemplo n.º 29
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
Exemplo n.º 30
0
 def test_border_condensation(self):
     vals = 'red solid 5px'
     css = '; '.join('border-%s-%s: %s' % (edge, p, v) for edge in EDGES for p, v in zip(BORDER_PROPS, vals.split()))
     style = parseStyle(css)
     condense_rule(style)
     for e, p in product(EDGES, BORDER_PROPS):
         self.assertFalse(style.getProperty('border-%s-%s' % (e, p)))
         self.assertFalse(style.getProperty('border-%s' % e))
         self.assertFalse(style.getProperty('border-%s' % p))
     self.assertEqual(style.getProperty('border').value, vals)
     css = '; '.join('border-%s-%s: %s' % (edge, p, v) for edge in ('top',) for p, v in zip(BORDER_PROPS, vals.split()))
     style = parseStyle(css)
     condense_rule(style)
     self.assertEqual(css_text(style), 'border-top: %s' % vals)
     css += ';' + '; '.join('border-%s-%s: %s' % (edge, p, v) for edge in ('right', 'left', 'bottom') for p, v in
                      zip(BORDER_PROPS, vals.replace('red', 'green').split()))
     style = parseStyle(css)
     condense_rule(style)
     self.assertEqual(len(style.getProperties()), 4)
     self.assertEqual(style.getProperty('border-top').value, vals)
     self.assertEqual(style.getProperty('border-left').value, vals.replace('red', 'green'))
Exemplo n.º 31
0
    def collect_global_css(self):
        global_css = defaultdict(list)
        for item in self.items:
            stylizer = self.stylizers[item]
            if float(self.context.margin_top) >= 0:
                stylizer.page_rule['margin-top'] = '%gpt'%\
                        float(self.context.margin_top)
            if float(self.context.margin_bottom) >= 0:
                stylizer.page_rule['margin-bottom'] = '%gpt'%\
                        float(self.context.margin_bottom)
            items = sorted(stylizer.page_rule.items())
            css = ';\n'.join("%s: %s" % (key, val) for key, val in items)
            css = ('@page {\n%s\n}\n' % css) if items else ''
            rules = [
                css_text(r)
                for r in stylizer.font_face_rules + self.embed_font_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:
            href = None
            if css.strip():
                id_, href = manifest.generate('page_css', 'page_styles.css')
                sheet = css_parser.parseString(css, validate=False)
                if self.transform_css_rules:
                    from calibre.ebooks.css_transform_rules import transform_sheet
                    transform_sheet(self.transform_css_rules, sheet)
                manifest.add(id_, href, CSS_MIME, data=sheet)
            gc_map[css] = href

        ans = {}
        for css, items in iteritems(global_css):
            for item in items:
                ans[item] = gc_map[css]
        return ans
Exemplo n.º 32
0
    def collect_global_css(self):
        global_css = defaultdict(list)
        for item in self.items:
            stylizer = self.stylizers[item]
            if float(self.context.margin_top) >= 0:
                stylizer.page_rule['margin-top'] = '%gpt'%\
                        float(self.context.margin_top)
            if float(self.context.margin_bottom) >= 0:
                stylizer.page_rule['margin-bottom'] = '%gpt'%\
                        float(self.context.margin_bottom)
            items = sorted(stylizer.page_rule.items())
            css = ';\n'.join("%s: %s" % (key, val) for key, val in items)
            css = ('@page {\n%s\n}\n'%css) if items else ''
            rules = [css_text(r) for r in stylizer.font_face_rules + self.embed_font_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:
            href = None
            if css.strip():
                id_, href = manifest.generate('page_css', 'page_styles.css')
                sheet = css_parser.parseString(css, validate=False)
                if self.transform_css_rules:
                    from calibre.ebooks.css_transform_rules import transform_sheet
                    transform_sheet(self.transform_css_rules, sheet)
                manifest.add(id_, href, CSS_MIME, data=sheet)
            gc_map[css] = href

        ans = {}
        for css, items in iteritems(global_css):
            for item in items:
                ans[item] = gc_map[css]
        return ans
Exemplo n.º 33
0
    def __init__(self, oeb, opts, replace_previous_inline_toc=True, ignore_existing_toc=False):
        self.oeb, self.opts, self.log = oeb, opts, oeb.log
        self.title = opts.toc_title or DEFAULT_TITLE
        self.at_start = opts.mobi_toc_at_start
        self.generated_item = None
        self.added_toc_guide_entry = False
        self.has_toc = oeb.toc and oeb.toc.count() > 1

        self.tocitem = tocitem = None
        if replace_previous_inline_toc:
            tocitem = self.tocitem = find_previous_calibre_inline_toc(oeb)
        if ignore_existing_toc and 'toc' in oeb.guide:
            oeb.guide.remove('toc')

        if 'toc' in oeb.guide:
            # Remove spurious toc entry from guide if it is not in spine or it
            # does not have any hyperlinks
            href = urlnormalize(oeb.guide['toc'].href.partition('#')[0])
            if href in oeb.manifest.hrefs:
                item = oeb.manifest.hrefs[href]
                if (hasattr(item.data, 'xpath') and
                    XPath('//h:a[@href]')(item.data)):
                    if oeb.spine.index(item) < 0:
                        oeb.spine.add(item, linear=False)
                    return
                elif self.has_toc:
                    oeb.guide.remove('toc')
            else:
                oeb.guide.remove('toc')

        if (not self.has_toc or 'toc' in oeb.guide or opts.no_inline_toc or
            getattr(opts, 'mobi_passthrough', False)):
            return

        self.log('\tGenerating in-line ToC')

        embed_css = ''
        s = getattr(oeb, 'store_embed_font_rules', None)
        if getattr(s, 'body_font_family', None):
            css = [css_text(x) for x in s.rules] + [
                    'body { font-family: %s }'%s.body_font_family]
            embed_css = '\n\n'.join(css)

        root = safe_xml_fromstring(TEMPLATE.format(xhtmlns=XHTML_NS,
            title=self.title, embed_css=embed_css,
            extra_css=(opts.extra_css or '')))
        parent = XPath('//h:ul')(root)[0]
        parent.text = '\n\t'
        for child in self.oeb.toc:
            self.process_toc_node(child, parent)

        if tocitem is not None:
            href = tocitem.href
            if oeb.spine.index(tocitem) > -1:
                oeb.spine.remove(tocitem)
            tocitem.data = root
        else:
            id, href = oeb.manifest.generate('contents', 'contents.xhtml')
            tocitem = self.generated_item = oeb.manifest.add(id, href, XHTML_MIME,
                    data=root)
        if self.at_start:
            oeb.spine.insert(0, tocitem, linear=True)
        else:
            oeb.spine.add(tocitem, linear=False)

        oeb.guide.add('toc', 'Table of Contents', href)
Exemplo n.º 34
0
    def __init__(self, oeb, opts, replace_previous_inline_toc=True, ignore_existing_toc=False):
        self.oeb, self.opts, self.log = oeb, opts, oeb.log
        self.title = opts.toc_title or DEFAULT_TITLE
        self.at_start = opts.mobi_toc_at_start
        self.generated_item = None
        self.added_toc_guide_entry = False
        self.has_toc = oeb.toc and oeb.toc.count() > 1

        self.tocitem = tocitem = None
        if replace_previous_inline_toc:
            tocitem = self.tocitem = find_previous_calibre_inline_toc(oeb)
        if ignore_existing_toc and 'toc' in oeb.guide:
            oeb.guide.remove('toc')

        if 'toc' in oeb.guide:
            # Remove spurious toc entry from guide if it is not in spine or it
            # does not have any hyperlinks
            href = urlnormalize(oeb.guide['toc'].href.partition('#')[0])
            if href in oeb.manifest.hrefs:
                item = oeb.manifest.hrefs[href]
                if (hasattr(item.data, 'xpath') and
                    XPath('//h:a[@href]')(item.data)):
                    if oeb.spine.index(item) < 0:
                        oeb.spine.add(item, linear=False)
                    return
                elif self.has_toc:
                    oeb.guide.remove('toc')
            else:
                oeb.guide.remove('toc')

        if (not self.has_toc or 'toc' in oeb.guide or opts.no_inline_toc or
            getattr(opts, 'mobi_passthrough', False)):
            return

        self.log('\tGenerating in-line ToC')

        embed_css = ''
        s = getattr(oeb, 'store_embed_font_rules', None)
        if getattr(s, 'body_font_family', None):
            css = [css_text(x) for x in s.rules] + [
                    'body { font-family: %s }'%s.body_font_family]
            embed_css = '\n\n'.join(css)

        root = etree.fromstring(TEMPLATE.format(xhtmlns=XHTML_NS,
            title=self.title, embed_css=embed_css,
            extra_css=(opts.extra_css or '')))
        parent = XPath('//h:ul')(root)[0]
        parent.text = '\n\t'
        for child in self.oeb.toc:
            self.process_toc_node(child, parent)

        if tocitem is not None:
            href = tocitem.href
            if oeb.spine.index(tocitem) > -1:
                oeb.spine.remove(tocitem)
            tocitem.data = root
        else:
            id, href = oeb.manifest.generate('contents', 'contents.xhtml')
            tocitem = self.generated_item = oeb.manifest.add(id, href, XHTML_MIME,
                    data=root)
        if self.at_start:
            oeb.spine.insert(0, tocitem, linear=True)
        else:
            oeb.spine.add(tocitem, linear=False)

        oeb.guide.add('toc', 'Table of Contents', href)
Exemplo n.º 35
0
def subset_all_fonts(container, font_stats, report):
    remove = set()
    total_old = total_new = 0
    changed = False
    for name, mt in iter_subsettable_fonts(container):
        chars = font_stats.get(name, set())
        with container.open(name, 'rb') as f:
            f.seek(0, os.SEEK_END)
            total_old += f.tell()
        if not chars:
            remove.add(name)
            report(_('Removed unused font: %s') % name)
            continue
        with container.open(name, 'r+b') as f:
            raw = f.read()
            try:
                font_name = get_font_names(raw)[-1]
            except Exception as e:
                container.log.warning(
                    'Corrupted font: %s, ignoring.  Error: %s' %
                    (name, as_unicode(e)))
                continue
            warnings = []
            container.log('Subsetting font: %s' % (font_name or name))
            try:
                nraw, old_sizes, new_sizes = subset(raw,
                                                    chars,
                                                    warnings=warnings)
            except UnsupportedFont as e:
                container.log.warning(
                    'Unsupported font: %s, ignoring.  Error: %s' %
                    (name, as_unicode(e)))
                continue

            for w in warnings:
                container.log.warn(w)
            olen = sum(itervalues(old_sizes))
            nlen = sum(itervalues(new_sizes))
            total_new += len(nraw)
            if nlen == olen:
                report(_('The font %s was already subset') % font_name)
            else:
                report(
                    _('Decreased the font {0} to {1} of its original size').
                    format(font_name, ('%.1f%%' % (nlen / olen * 100))))
                changed = True
            f.seek(0), f.truncate(), f.write(nraw)

    for name in remove:
        container.remove_item(name)
        changed = True

    if remove:
        for name, mt in iteritems(container.mime_map):
            if mt in OEB_STYLES:
                sheet = container.parsed(name)
                if remove_font_face_rules(container, sheet, remove, name):
                    container.dirty(name)
            elif mt in OEB_DOCS:
                for style in XPath('//h:style')(container.parsed(name)):
                    if style.get('type',
                                 'text/css') == 'text/css' and style.text:
                        sheet = container.parse_css(style.text, name)
                        if remove_font_face_rules(container, sheet, remove,
                                                  name):
                            style.text = css_text(sheet)
                            container.dirty(name)
    if total_old > 0:
        report(
            _('Reduced total font size to %.1f%% of original') %
            (total_new / total_old * 100))
    else:
        report(_('No embedded fonts found'))
    return changed
Exemplo n.º 36
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(css_text(prop.propertyValue))]
                 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' % css_text(prop.propertyValue))
             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)
Exemplo n.º 37
0
 def css(d):
     return css_text(d).replace('\n', ' ')
Exemplo n.º 38
0
def subset_all_fonts(container, font_stats, report):
    remove = set()
    total_old = total_new = 0
    changed = False
    for name, mt in iter_subsettable_fonts(container):
        chars = font_stats.get(name, set())
        with container.open(name, 'rb') as f:
            f.seek(0, os.SEEK_END)
            total_old += f.tell()
        if not chars:
            remove.add(name)
            report(_('Removed unused font: %s')%name)
            continue
        with container.open(name, 'r+b') as f:
            raw = f.read()
            try:
                font_name = get_font_names(raw)[-1]
            except Exception as e:
                container.log.warning(
                    'Corrupted font: %s, ignoring.  Error: %s'%(
                        name, as_unicode(e)))
                continue
            warnings = []
            container.log('Subsetting font: %s'%(font_name or name))
            try:
                nraw, old_sizes, new_sizes = subset(raw, chars,
                                                warnings=warnings)
            except UnsupportedFont as e:
                container.log.warning(
                    'Unsupported font: %s, ignoring.  Error: %s'%(
                        name, as_unicode(e)))
                continue

            for w in warnings:
                container.log.warn(w)
            olen = sum(itervalues(old_sizes))
            nlen = sum(itervalues(new_sizes))
            total_new += len(nraw)
            if nlen == olen:
                report(_('The font %s was already subset')%font_name)
            else:
                report(_('Decreased the font {0} to {1} of its original size').format(
                    font_name, ('%.1f%%' % (nlen/olen * 100))))
                changed = True
            f.seek(0), f.truncate(), f.write(nraw)

    for name in remove:
        container.remove_item(name)
        changed = True

    if remove:
        for name, mt in iteritems(container.mime_map):
            if mt in OEB_STYLES:
                sheet = container.parsed(name)
                if remove_font_face_rules(container, sheet, remove, name):
                    container.dirty(name)
            elif mt in OEB_DOCS:
                for style in XPath('//h:style')(container.parsed(name)):
                    if style.get('type', 'text/css') == 'text/css' and style.text:
                        sheet = container.parse_css(style.text, name)
                        if remove_font_face_rules(container, sheet, remove, name):
                            style.text = css_text(sheet)
                            container.dirty(name)
    if total_old > 0:
        report(_('Reduced total font size to %.1f%% of original')%(
            total_new/total_old*100))
    else:
        report(_('No embedded fonts found'))
    return changed
Exemplo n.º 39
0
 def css(d):
     return css_text(d).replace('\n', ' ')
Exemplo n.º 40
0
 def cssText(self):
     ' This will return either a string or a tuple of strings '
     if len(self) == 1:
         return css_text(self[0])
     return tuple(css_text(x) for x in self)
Exemplo n.º 41
0
 def cssText(self):
     ' This will return either a string or a tuple of strings '
     if len(self) == 1:
         return css_text(self[0])
     return tuple(css_text(x) for x in self)