fontchain_lint.py revision 0e969e2c0ba9ad863c7fcfc3973a16b1b599e50a
1#!/usr/bin/env python 2 3import collections 4import glob 5from os import path 6import sys 7from xml.etree import ElementTree 8 9from fontTools import ttLib 10 11LANG_TO_SCRIPT = { 12 'de': 'Latn', 13 'en': 'Latn', 14 'es': 'Latn', 15 'eu': 'Latn', 16 'ja': 'Jpan', 17 'ko': 'Kore', 18 'hu': 'Latn', 19 'hy': 'Armn', 20 'nb': 'Latn', 21 'nn': 'Latn', 22 'pt': 'Latn', 23} 24 25def lang_to_script(lang_code): 26 lang = lang_code.lower() 27 while lang not in LANG_TO_SCRIPT: 28 hyphen_idx = lang.rfind('-') 29 assert hyphen_idx != -1, ( 30 'We do not know what script the "%s" language is written in.' 31 % lang_code) 32 assumed_script = lang[hyphen_idx+1:] 33 if len(assumed_script) == 4 and assumed_script.isalpha(): 34 # This is actually the script 35 return assumed_script.title() 36 lang = lang[:hyphen_idx] 37 return LANG_TO_SCRIPT[lang] 38 39 40def get_best_cmap(font): 41 font_file, index = font 42 font_path = path.join(_fonts_dir, font_file) 43 if index is not None: 44 ttfont = ttLib.TTFont(font_path, fontNumber=index) 45 else: 46 ttfont = ttLib.TTFont(font_path) 47 all_unicode_cmap = None 48 bmp_cmap = None 49 for cmap in ttfont['cmap'].tables: 50 specifier = (cmap.format, cmap.platformID, cmap.platEncID) 51 if specifier == (4, 3, 1): 52 assert bmp_cmap is None, 'More than one BMP cmap in %s' % (font, ) 53 bmp_cmap = cmap 54 elif specifier == (12, 3, 10): 55 assert all_unicode_cmap is None, ( 56 'More than one UCS-4 cmap in %s' % (font, )) 57 all_unicode_cmap = cmap 58 59 return all_unicode_cmap.cmap if all_unicode_cmap else bmp_cmap.cmap 60 61 62def assert_font_supports_any_of_chars(font, chars): 63 best_cmap = get_best_cmap(font) 64 for char in chars: 65 if char in best_cmap: 66 return 67 sys.exit('None of characters in %s were found in %s' % (chars, font)) 68 69 70def check_hyphens(hyphens_dir): 71 # Find all the scripts that need automatic hyphenation 72 scripts = set() 73 for hyb_file in glob.iglob(path.join(hyphens_dir, '*.hyb')): 74 hyb_file = path.basename(hyb_file) 75 assert hyb_file.startswith('hyph-'), ( 76 'Unknown hyphenation file %s' % hyb_file) 77 lang_code = hyb_file[hyb_file.index('-')+1:hyb_file.index('.')] 78 scripts.add(lang_to_script(lang_code)) 79 80 HYPHENS = {0x002D, 0x2010} 81 for script in scripts: 82 fonts = _script_to_font_map[script] 83 assert fonts, 'No fonts found for the "%s" script' % script 84 for font in fonts: 85 assert_font_supports_any_of_chars(font, HYPHENS) 86 87 88def parse_fonts_xml(fonts_xml_path): 89 global _script_to_font_map, _fallback_chain 90 _script_to_font_map = collections.defaultdict(set) 91 _fallback_chain = [] 92 tree = ElementTree.parse(fonts_xml_path) 93 for family in tree.findall('family'): 94 name = family.get('name') 95 variant = family.get('variant') 96 langs = family.get('lang') 97 if name: 98 assert variant is None, ( 99 'No variant expected for LGC font %s.' % name) 100 assert langs is None, ( 101 'No language expected for LGC fonts %s.' % name) 102 else: 103 assert variant in {None, 'elegant', 'compact'}, ( 104 'Unexpected value for variant: %s' % variant) 105 106 if langs: 107 langs = langs.split() 108 scripts = {lang_to_script(lang) for lang in langs} 109 else: 110 scripts = set() 111 112 for child in family: 113 assert child.tag == 'font', ( 114 'Unknown tag <%s>' % child.tag) 115 font_file = child.text 116 weight = int(child.get('weight')) 117 assert weight % 100 == 0, ( 118 'Font weight "%d" is not a multiple of 100.' % weight) 119 120 style = child.get('style') 121 assert style in {'normal', 'italic'}, ( 122 'Unknown style "%s"' % style) 123 124 index = child.get('index') 125 if index: 126 index = int(index) 127 128 _fallback_chain.append(( 129 name, 130 frozenset(scripts), 131 variant, 132 weight, 133 style, 134 (font_file, index))) 135 136 if name: # non-empty names are used for default LGC fonts 137 map_scripts = {'Latn', 'Grek', 'Cyrl'} 138 else: 139 map_scripts = scripts 140 for script in map_scripts: 141 _script_to_font_map[script].add((font_file, index)) 142 143 144def main(): 145 target_out = sys.argv[1] 146 global _fonts_dir 147 _fonts_dir = path.join(target_out, 'fonts') 148 149 fonts_xml_path = path.join(target_out, 'etc', 'fonts.xml') 150 parse_fonts_xml(fonts_xml_path) 151 152 hyphens_dir = path.join(target_out, 'usr', 'hyphen-data') 153 check_hyphens(hyphens_dir) 154 155 156if __name__ == '__main__': 157 main() 158