diff --git a/src/calibre/ebooks/oeb/polish/embed.py b/src/calibre/ebooks/oeb/polish/embed.py index be88ce39c3..ad0050bdba 100644 --- a/src/calibre/ebooks/oeb/polish/embed.py +++ b/src/calibre/ebooks/oeb/polish/embed.py @@ -37,6 +37,22 @@ def matching_rule(font, rules): return rule +def format_failed_match_report(fonts, font_family, font, report): + msg = _('Failed to find a font in the "%s" family matching the CSS font specification:') % font_family + msg += '\n\n* font-weight: %s' % font['font-weight'] + msg += '\n* font-style: %s' % font['font-style'] + msg += '\n* font-stretch: %s' % font['font-stretch'] + msg += '\n\n' + _('Available fonts in the family are:') + '\n' + for f in fonts: + msg += '\n' + f['path'] + msg += '\n\n* font-weight: %s' % f.get('font-weight', '400').strip() + msg += '\n* font-style: %s' % f.get('font-style', 'normal').strip() + msg += '\n* font-stretch: %s' % f.get('font-stretch', 'normal').strip() + msg += '\n\n' + report(msg) + report('') + + def embed_font(container, font, all_font_rules, report, warned): rule = matching_rule(font, all_font_rules) ff = font['font-family'] @@ -49,9 +65,12 @@ def embed_font(container, font, all_font_rules, report, warned): try: fonts = font_scanner.fonts_for_family(ff) except NoFonts: - report(_('Failed to find fonts for family: %s, not embedding') % ff) - warned.add(ff) - return + try: + fonts = font_scanner.alt_fonts_for_family(ff) + except NoFonts: + report(_('Failed to find fonts for family: %s, not embedding') % ff) + warned.add(ff) + return wt = int(font.get('font-weight', '400')) for f in fonts: if f['weight'] == wt and f['font-style'] == font.get('font-style', 'normal') and f['font-stretch'] == font.get('font-stretch', 'normal'): @@ -69,11 +88,10 @@ def embed_font(container, font, all_font_rules, report, warned): rule['src'] = 'url(%s)' % href rule['name'] = name return rule - msg = _('Failed to find font matching: family: %(family)s; weight: %(weight)s; style: %(style)s; stretch: %(stretch)s') % dict( - family=ff, weight=font['font-weight'], style=font['font-style'], stretch=font['font-stretch']) - if msg not in warned: - warned.add(msg) - report(msg) + wkey = ('spec-mismatch-', ff, wt, font['font-style'], font['font-stretch']) + if wkey not in warned: + warned.add(wkey) + format_failed_match_report(fonts, ff, font, report) else: name = rule['src'] href = container.name_to_href(name) @@ -159,4 +177,3 @@ if __name__ == '__main__': prints(msg) print() prints('Output written to:', outbook) - diff --git a/src/calibre/utils/fonts/scanner.py b/src/calibre/utils/fonts/scanner.py index ddde3e0699..39a2930a18 100644 --- a/src/calibre/utils/fonts/scanner.py +++ b/src/calibre/utils/fonts/scanner.py @@ -21,6 +21,8 @@ from calibre.utils.icu import sort_key class NoFonts(ValueError): pass +# Font dirs {{{ + def default_font_dirs(): return [ @@ -115,6 +117,77 @@ def font_dirs(): os.path.expanduser('~/Library/Fonts'), ] return fc_list() +# }}} + +# Build font family maps {{{ + + +def font_priority(font): + ''' + Try to ensure that the "Regular" face is the first font for a given + family. + ''' + style_normal = font['font-style'] == 'normal' + width_normal = font['font-stretch'] == 'normal' + weight_normal = font['font-weight'] == 'normal' + num_normal = sum(filter(None, (style_normal, width_normal, + weight_normal))) + subfamily_name = (font['wws_subfamily_name'] or + font['preferred_subfamily_name'] or font['subfamily_name']) + if num_normal == 3 and subfamily_name == 'Regular': + return 0 + if num_normal == 3: + return 1 + if subfamily_name == 'Regular': + return 2 + return 3 + (3 - num_normal) + + +def path_significance(path, folders): + path = os.path.normcase(os.path.abspath(path)) + for i, q in enumerate(folders): + if path.startswith(q): + return i + return -1 + + +def build_families(cached_fonts, folders, family_attr='font-family'): + families = defaultdict(list) + for f in cached_fonts.itervalues(): + if not f: + continue + lf = icu_lower(f.get(family_attr) or '') + if lf: + families[lf].append(f) + + for fonts in families.itervalues(): + # Look for duplicate font files and choose the copy that is from a + # more significant font directory (prefer user directories over + # system directories). + fmap = {} + remove = [] + for f in fonts: + fingerprint = (icu_lower(f['font-family']), f['font-weight'], + f['font-stretch'], f['font-style']) + if fingerprint in fmap: + opath = fmap[fingerprint]['path'] + npath = f['path'] + if path_significance(npath, folders) >= path_significance(opath, folders): + remove.append(fmap[fingerprint]) + fmap[fingerprint] = f + else: + remove.append(f) + else: + fmap[fingerprint] = f + for font in remove: + fonts.remove(font) + fonts.sort(key=font_priority) + + font_family_map = dict.copy(families) + font_families = tuple(sorted((f[0]['font-family'] for f in + font_family_map.itervalues()), key=sort_key)) + return font_family_map, font_families +# }}} class FontScanner(Thread): @@ -149,6 +222,17 @@ class FontScanner(Thread): except KeyError: raise NoFonts('No fonts found for the family: %r'%family) + def alt_fonts_for_family(self, family): + ''' Same as fonts_for_family() except that it uses the family name key + instead of the preferred_family_name key. This is needed because some + software, like Word uses the family name to refer to fonts instead of + the preferred family name. ''' + self.join() + try: + return self.alt_font_family_map[icu_lower(family)] + except KeyError: + raise NoFonts('No fonts found for the family: %r'%family) + def legacy_fonts_for_family(self, family): ''' Return a simple set of regular, bold, italic and bold-italic faces for @@ -285,68 +369,9 @@ class FontScanner(Thread): self.build_families() - def font_priority(self, font): - ''' - Try to ensure that the "Regular" face is the first font for a given - family. - ''' - style_normal = font['font-style'] == 'normal' - width_normal = font['font-stretch'] == 'normal' - weight_normal = font['font-weight'] == 'normal' - num_normal = sum(filter(None, (style_normal, width_normal, - weight_normal))) - subfamily_name = (font['wws_subfamily_name'] or - font['preferred_subfamily_name'] or font['subfamily_name']) - if num_normal == 3 and subfamily_name == 'Regular': - return 0 - if num_normal == 3: - return 1 - if subfamily_name == 'Regular': - return 2 - return 3 + (3 - num_normal) - def build_families(self): - families = defaultdict(list) - for f in self.cached_fonts.itervalues(): - if not f: - continue - lf = icu_lower(f['font-family'] or '') - if lf: - families[lf].append(f) - - for fonts in families.itervalues(): - # Look for duplicate font files and choose the copy that is from a - # more significant font directory (prefer user directories over - # system directories). - fmap = {} - remove = [] - for f in fonts: - fingerprint = (icu_lower(f['font-family']), f['font-weight'], - f['font-stretch'], f['font-style']) - if fingerprint in fmap: - opath = fmap[fingerprint]['path'] - npath = f['path'] - if self.path_significance(npath) >= self.path_significance(opath): - remove.append(fmap[fingerprint]) - fmap[fingerprint] = f - else: - remove.append(f) - else: - fmap[fingerprint] = f - for font in remove: - fonts.remove(font) - fonts.sort(key=self.font_priority) - - self.font_family_map = dict.copy(families) - self.font_families = tuple(sorted((f[0]['font-family'] for f in - self.font_family_map.itervalues()), key=sort_key)) - - def path_significance(self, path): - path = os.path.normcase(os.path.abspath(path)) - for i, q in enumerate(self.folders): - if path.startswith(q): - return i - return -1 + self.font_family_map, self.font_families = build_families(self.cached_fonts, self.folders) + self.alt_font_family_map = build_families(self.cached_fonts, self.folders, 'family_name')[0] def write_cache(self): with self.cache: @@ -395,5 +420,3 @@ def force_rescan(): if __name__ == '__main__': font_scanner.dump_fonts() - -