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
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')
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]
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
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
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
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]
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')