Exemplo n.º 1
0
    def fonts_for_family(self, family, normalize=True):
        family = type("")(family)
        ans = {}
        for weight, is_italic in product((self.w.FW_NORMAL, self.w.FW_BOLD), (False, True)):
            try:
                data = self.w.font_data(family, is_italic, weight)
            except Exception as e:
                prints(
                    "Failed to get font data for font: %s [%s] with error: %s"
                    % (family, self.get_normalized_name(is_italic, weight), e)
                )
                continue

            ok, sig = is_truetype_font(data)
            if not ok:
                prints("Not a supported font, sfnt_version: %r" % sig)
                continue
            ext = "otf" if sig == b"OTTO" else "ttf"

            try:
                weight, is_italic, is_bold, is_regular = get_font_characteristics(data)[:4]
            except Exception as e:
                prints(
                    "Failed to get font characteristic for font: %s [%s]"
                    " with error: %s" % (family, self.get_normalized_name(is_italic, weight), e)
                )
                continue

            try:
                family_name, sub_family_name, full_name = get_font_names(data)
            except:
                pass

            if normalize:
                ft = {(True, True): "bi", (True, False): "italic", (False, True): "bold", (False, False): "normal"}[
                    (is_italic, is_bold)
                ]
            else:
                ft = (1 if is_italic else 0, weight // 10)

            if not (family_name or full_name):
                # prints('Font %s [%s] has no names'%(family,
                #     self.get_normalized_name(is_italic, weight)))
                family_name = family
            name = full_name or family + " " + (sub_family_name or "")

            try:
                name.encode("ascii")
            except ValueError:
                try:
                    sub_family_name.encode("ascii")
                    subf = sub_family_name
                except:
                    subf = ""

                name = family + ((" " + subf) if subf else "")

            ans[ft] = (ext, name, data)

        return ans
Exemplo n.º 2
0
def subset_all_fonts(container, font_stats, report):
    remove = set()
    total_old = total_new = 0
    for name, mt in container.mime_map.iteritems():
        if (mt in OEB_FONTS or name.rpartition('.')[-1].lower() in {'otf', 'ttf'}) and mt != guess_type('a.woff'):
            chars = font_stats.get(name, set())
            path = container.name_path_map[name]
            total_old += os.path.getsize(path)
            if not chars:
                remove.add(name)
                report('Removed unused font: %s'%name)
                continue
            with open(path, 'r+b') as f:
                raw = f.read()
                font_name = get_font_names(raw)[-1]
                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(old_sizes.itervalues())
                nlen = sum(new_sizes.itervalues())
                total_new += len(nraw)
                if nlen == olen:
                    report('The font %s was already subset'%font_name)
                else:
                    report('Decreased the font %s to %.1f%% of its original size'%
                       (font_name, nlen/olen * 100))
                f.seek(0), f.truncate(), f.write(nraw)

    for name in remove:
        container.remove_item(name)

    if remove:
        for name, mt in container.mime_map.iteritems():
            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 = sheet.cssText
                            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')
Exemplo n.º 3
0
 def get_font_family_name(self, name):
     try:
         with current_container().open(name) as f:
             f.seek(0, os.SEEK_END)
             sz = f.tell()
     except Exception:
         sz = 0
     key = name, sz
     if key not in self.font_name_cache:
         raw = current_container().raw_data(name, decode=False)
         try:
             ans = get_font_names(raw)[-1]
         except Exception:
             ans = None
         self.font_name_cache[key] = ans
     return self.font_name_cache[key]
Exemplo n.º 4
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(old_sizes.itervalues())
            nlen = sum(new_sizes.itervalues())
            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 container.mime_map.iteritems():
            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 = sheet.cssText
                            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.º 5
0
    def fonts_for_family(self, family, normalize=True):
        family = unicode_type(family)
        ans = {}
        for weight, is_italic in product((self.w.FW_NORMAL, self.w.FW_BOLD), (False, True)):
            if family in self.app_font_families:
                m = self.app_font_families[family]
                path = m.get((weight, is_italic), None)
                if path is None:
                    continue
                data = P(path, data=True)
            else:
                try:
                    data = self.w.font_data(family, is_italic, weight)
                except Exception as e:
                    prints('Failed to get font data for font: %s [%s] with error: %s'%
                            (family, self.get_normalized_name(is_italic, weight), e))
                    continue

            ok, sig = is_truetype_font(data)
            if not ok:
                prints('Not a supported font, sfnt_version: %r'%sig)
                continue
            ext = 'otf' if sig == b'OTTO' else 'ttf'

            try:
                weight, is_italic, is_bold, is_regular = get_font_characteristics(data)[:4]
            except Exception as e:
                prints('Failed to get font characteristic for font: %s [%s]'
                        ' with error: %s'%(family,
                            self.get_normalized_name(is_italic, weight), e))
                continue

            try:
                family_name, sub_family_name, full_name = get_font_names(data)
            except:
                pass

            if normalize:
                ft = {(True, True):'bi', (True, False):'italic', (False,
                    True):'bold', (False, False):'normal'}[(is_italic,
                        is_bold)]
            else:
                ft = (1 if is_italic else 0, weight//10)

            if not (family_name or full_name):
                # prints('Font %s [%s] has no names'%(family,
                #     self.get_normalized_name(is_italic, weight)))
                family_name = family
            name = full_name or family + ' ' + (sub_family_name or '')

            try:
                name.encode('ascii')
            except ValueError:
                try:
                    sub_family_name.encode('ascii')
                    subf = sub_family_name
                except:
                    subf = ''

                name = family + ((' ' + subf) if subf else '')

            ans[ft] = (ext, name, data)

        return ans
Exemplo n.º 6
0
    def fonts_for_family(self, family, normalize=True):
        family = type(u'')(family)
        ans = {}
        for weight, is_italic in product( (self.w.FW_NORMAL, self.w.FW_BOLD), (False, True) ):
            if family in self.app_font_families:
                m = self.app_font_families[family]
                path = m.get((weight, is_italic), None)
                if path is None: continue
                data = P(path, data=True)
            else:
                try:
                    data = self.w.font_data(family, is_italic, weight)
                except Exception as e:
                    prints('Failed to get font data for font: %s [%s] with error: %s'%
                            (family, self.get_normalized_name(is_italic, weight), e))
                    continue

            ok, sig = is_truetype_font(data)
            if not ok:
                prints('Not a supported font, sfnt_version: %r'%sig)
                continue
            ext = 'otf' if sig == b'OTTO' else 'ttf'

            try:
                weight, is_italic, is_bold, is_regular = get_font_characteristics(data)[:4]
            except Exception as e:
                prints('Failed to get font characteristic for font: %s [%s]'
                        ' with error: %s'%(family,
                            self.get_normalized_name(is_italic, weight), e))
                continue

            try:
                family_name, sub_family_name, full_name = get_font_names(data)
            except:
                pass

            if normalize:
                ft = {(True, True):'bi', (True, False):'italic', (False,
                    True):'bold', (False, False):'normal'}[(is_italic,
                        is_bold)]
            else:
                ft = (1 if is_italic else 0, weight//10)

            if not (family_name or full_name):
                # prints('Font %s [%s] has no names'%(family,
                #     self.get_normalized_name(is_italic, weight)))
                family_name = family
            name = full_name or family + ' ' + (sub_family_name or '')

            try:
                name.encode('ascii')
            except ValueError:
                try:
                    sub_family_name.encode('ascii')
                    subf = sub_family_name
                except:
                    subf = ''

                name = family + ((' ' + subf) if subf else '')

            ans[ft] = (ext, name, data)

        return ans
Exemplo n.º 7
0
    def handle_embedded_fonts(self):
        '''
        Because of QtWebKit's inability to handle embedded fonts correctly, we
        remove the embedded fonts and make them available system wide instead.
        If you ever move to Qt WebKit 2.3+ then this will be unnecessary.
        '''
        from calibre.ebooks.oeb.base import urlnormalize
        from calibre.gui2 import must_use_qt
        from calibre.utils.fonts.utils import get_font_names, remove_embed_restriction
        from PyQt4.Qt import QFontDatabase, QByteArray

        # First find all @font-face rules and remove them, adding the embedded
        # fonts to Qt
        family_map = {}
        for item in list(self.oeb.manifest):
            if not hasattr(item.data, 'cssRules'): continue
            remove = set()
            for i, rule in enumerate(item.data.cssRules):
                if rule.type == rule.FONT_FACE_RULE:
                    remove.add(i)
                    try:
                        s = rule.style
                        src = s.getProperty('src').propertyValue[0].uri
                        font_family = s.getProperty('font-family').propertyValue[0].value
                    except:
                        continue
                    path = item.abshref(src)
                    ff = self.oeb.manifest.hrefs.get(urlnormalize(path), None)
                    if ff is None:
                        continue

                    raw = ff.data
                    self.oeb.manifest.remove(ff)
                    try:
                        raw = remove_embed_restriction(raw)
                    except:
                        continue
                    must_use_qt()
                    QFontDatabase.addApplicationFontFromData(QByteArray(raw))
                    try:
                        family_name = get_font_names(raw)[0]
                    except:
                        family_name = None
                    if family_name:
                        family_map[icu_lower(font_family)] = family_name

            for i in sorted(remove, reverse=True):
                item.data.cssRules.pop(i)

        # Now map the font family name specified in the css to the actual
        # family name of the embedded font (they may be different in general).
        for item in self.oeb.manifest:
            if not hasattr(item.data, 'cssRules'): continue
            for i, rule in enumerate(item.data.cssRules):
                if rule.type != rule.STYLE_RULE: continue
                ff = rule.style.getProperty('font-family')
                if ff is None: continue
                val = ff.propertyValue
                for i in xrange(val.length):
                    k = icu_lower(val[i].value)
                    if k in family_map:
                        val[i].value = family_map[k]
Exemplo n.º 8
0
def subset_all_fonts(container, font_stats, report):
    remove = set()
    total_old = total_new = 0
    for name, mt in container.mime_map.iteritems():
        if mt in OEB_FONTS or name.rpartition('.')[-1].lower() in {
                'otf', 'ttf'
        }:
            chars = font_stats.get(name, set())
            path = container.name_path_map[name]
            total_old += os.path.getsize(path)
            if not chars:
                remove.add(name)
                report('Removed unused font: %s' % name)
                continue
            with open(path, 'r+b') as f:
                raw = f.read()
                font_name = get_font_names(raw)[-1]
                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(old_sizes.itervalues())
                nlen = sum(new_sizes.itervalues())
                total_new += len(nraw)
                if nlen == olen:
                    report('The font %s was already subset' % font_name)
                else:
                    report(
                        'Decreased the font %s to %.1f%% of its original size'
                        % (font_name, nlen / olen * 100))
                f.seek(0), f.truncate(), f.write(nraw)

    for name in remove:
        container.remove_item(name)

    if remove:
        for name, mt in container.mime_map.iteritems():
            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 = sheet.cssText
                            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')