def test_varlib_main_ttf(self): """Mostly for testing varLib.main() """ suffix = '.ttf' ds_path = self.get_test_input('Build.designspace') ufo_dir = self.get_test_input('master_ufo') ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf') self.temp_dir() ttf_dir = os.path.join(self.tempdir, 'master_ttf_interpolatable') os.makedirs(ttf_dir) ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily-') for path in ttx_paths: self.compile_font(path, suffix, ttf_dir) ds_copy = os.path.join(self.tempdir, 'BuildMain.designspace') shutil.copy2(ds_path, ds_copy) varLib_main([ds_copy]) varfont_path = os.path.splitext(ds_copy)[0] + '-VF' + suffix varfont = TTFont(varfont_path) tables = [ table_tag for table_tag in varfont.keys() if table_tag != 'head' ] expected_ttx_path = self.get_test_output('BuildMain.ttx') self.expect_ttx(varfont, expected_ttx_path, tables)
def test_varlib_mutator_ttf(self): suffix = '.ttf' ds_path = self.get_test_input('Build.designspace') ufo_dir = self.get_test_input('master_ufo') ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf') self.temp_dir() ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily-') for path in ttx_paths: self.compile_font(path, suffix, self.tempdir) finder = lambda s: s.replace(ufo_dir, self.tempdir).replace( '.ufo', suffix) varfont, _, _ = build(ds_path, finder) varfont_name = 'Mutator' varfont_path = os.path.join(self.tempdir, varfont_name + suffix) varfont.save(varfont_path) args = [varfont_path, 'wght=500', 'cntr=50'] mutator(args) instfont_path = os.path.splitext( varfont_path)[0] + '-instance' + suffix instfont = TTFont(instfont_path) tables = [ table_tag for table_tag in instfont.keys() if table_tag != 'head' ] expected_ttx_path = self.get_test_output(varfont_name + '.ttx') self.expect_ttx(instfont, expected_ttx_path, tables)
def test_varlib_interpolate_layout_main_ttf(self): """Mostly for testing varLib.interpolate_layout.main() """ suffix = '.ttf' ds_path = self.get_test_input('Build.designspace') ufo_dir = self.get_test_input('master_ufo') ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf') self.temp_dir() ttf_dir = os.path.join(self.tempdir, 'master_ttf_interpolatable') os.makedirs(ttf_dir) ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily-') for path in ttx_paths: self.compile_font(path, suffix, ttf_dir) finder = lambda s: s.replace(ufo_dir, ttf_dir).replace('.ufo', suffix) varfont, _, _ = build(ds_path, finder) varfont_name = 'InterpolateLayoutMain' varfont_path = os.path.join(self.tempdir, varfont_name + suffix) varfont.save(varfont_path) ds_copy = os.path.splitext(varfont_path)[0] + '.designspace' shutil.copy2(ds_path, ds_copy) args = [ds_copy, 'weight=500', 'contrast=50'] interpolate_layout_main(args) instfont_path = os.path.splitext(varfont_path)[0] + '-instance' + suffix instfont = TTFont(instfont_path) tables = [table_tag for table_tag in instfont.keys() if table_tag != 'head'] expected_ttx_path = self.get_test_output(varfont_name + '.ttx') self.expect_ttx(instfont, expected_ttx_path, tables)
def generateFont(font, outfile): flags = ("opentype", "dummy-dsig", "round", "omit-instructions") font.selection.all() font.correctReferences() font.selection.none() # fix some common font issues validateGlyphs(font) tmpfile = mkstemp(suffix=os.path.basename(outfile))[1] font.generate(tmpfile, flags=flags) font.close() # now open in fontTools from fontTools.ttLib import TTFont ftfont = TTFont(tmpfile) # force compiling tables by fontTools, saves few tens of KBs for tag in ftfont.keys(): if hasattr(ftfont[tag], "compile"): ftfont[tag].compile(ftfont) ftfont.save(outfile) ftfont.close() os.remove(tmpfile)
def test_varlib_mutator_ttf(self): suffix = '.ttf' ds_path = self.get_test_input('Build.designspace') ufo_dir = self.get_test_input('master_ufo') ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf') self.temp_dir() ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily-') for path in ttx_paths: self.compile_font(path, suffix, self.tempdir) finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix) varfont, _, _ = build(ds_path, finder) varfont_name = 'Mutator' varfont_path = os.path.join(self.tempdir, varfont_name + suffix) varfont.save(varfont_path) args = [varfont_path, 'wght=500', 'cntr=50'] mutator(args) instfont_path = os.path.splitext(varfont_path)[0] + '-instance' + suffix instfont = TTFont(instfont_path) tables = [table_tag for table_tag in instfont.keys() if table_tag != 'head'] expected_ttx_path = self.get_test_output(varfont_name + '.ttx') self.expect_ttx(instfont, expected_ttx_path, tables)
def test_varlib_interpolate_layout_main_ttf(self): """Mostly for testing varLib.interpolate_layout.main() """ suffix = '.ttf' ds_path = self.get_test_input('Build.designspace') ufo_dir = self.get_test_input('master_ufo') ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf') self.temp_dir() ttf_dir = os.path.join(self.tempdir, 'master_ttf_interpolatable') os.makedirs(ttf_dir) ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily-') for path in ttx_paths: self.compile_font(path, suffix, ttf_dir) finder = lambda s: s.replace(ufo_dir, ttf_dir).replace('.ufo', suffix) varfont, _, _ = build(ds_path, finder) varfont_name = 'InterpolateLayoutMain' varfont_path = os.path.join(self.tempdir, varfont_name + suffix) varfont.save(varfont_path) ds_copy = os.path.splitext(varfont_path)[0] + '.designspace' shutil.copy2(ds_path, ds_copy) args = [ds_copy, 'weight=500', 'contrast=50'] interpolate_layout_main(args) instfont_path = os.path.splitext( varfont_path)[0] + '-instance' + suffix instfont = TTFont(instfont_path) tables = [ table_tag for table_tag in instfont.keys() if table_tag != 'head' ] expected_ttx_path = self.get_test_output(varfont_name + '.ttx') self.expect_ttx(instfont, expected_ttx_path, tables)
def getAxes(): """ returns a dict of all ttfs {tag, min, def, max} """ fonts = {} for ttf in glob.glob("fonts/**.ttf"): ftf = TTFont(ttf) fontname = os.path.basename(ttf)[:-4] axes = [] # print(fontname) if 'fvar' in ftf.keys(): axes = [] for axis in ftf['fvar'].axes: a = {} a['tag'] = axis.axisTag a['min'] = axis.minValue a['def'] = axis.defaultValue a['max'] = axis.maxValue a['name'] = ftf['name'].getDebugName(axis.axisNameID) axes.append(a) if axes: fonts[fontname] = axes # d = open("axes", "w+") # d.write(str(fonts)) # d.close() return fonts
def test_varlib_mutator_iup_ttf(self): suffix = '.ttf' ufo_dir = self.get_test_input('master_ufo') ttx_dir = self.get_test_input('master_ttx_varfont_ttf') self.temp_dir() ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'Mutator_IUP') for path in ttx_paths: self.compile_font(path, suffix, self.tempdir) varfont_name = 'Mutator_IUP' varfont_path = os.path.join(self.tempdir, varfont_name + suffix) args = [varfont_path, 'wdth=80', 'ASCN=628'] mutator(args) instfont_path = os.path.splitext( varfont_path)[0] + '-instance' + suffix instfont = TTFont(instfont_path) tables = [ table_tag for table_tag in instfont.keys() if table_tag != 'head' ] expected_ttx_path = self.get_test_output(varfont_name + '-instance.ttx') self.expect_ttx(instfont, expected_ttx_path, tables)
class FontSize(object): def __init__(self, filepath): self.ttfont = TTFont(filepath) self.filepath = filepath self.total_glyphs = 0 self.encoded_glyphs = 0 self.total_bytes = 0 self.per_enc_glyph_bytes = 0.0 self.per_total_glyph_bytes = 0.0 self.tables = {} self._calculate_sizes() def _calculate_sizes(self): self.total_bytes = os.path.getsize(self.filepath) self._calc_per_encoded_glyph_size() self._calc_per_total_glyph_size() self._calc_table_sizes() def _calc_per_encoded_glyph_size(self): self.encoded_glyphs = len(self.ttfont.getBestCmap()) self.per_enc_glyph_bytes = self.total_bytes / self.encoded_glyphs def _calc_per_total_glyph_size(self): self.total_glyphs = len(self.ttfont.getGlyphSet()) self.per_total_glyph_bytes = self.total_bytes / self.total_glyphs def _calc_table_sizes(self): tables = self.get_table_tags() for table in tables: self.tables[table] = self.ttfont.reader.tables[table].length def get_table_tags(self): return [tag for tag in self.ttfont.keys() if tag != "GlyphOrder"]
def getSFNTData(pathOrFile, unsortGlyfLoca=False, glyphBBox="", alt255UInt16=False): font = TTFont(pathOrFile) tableChecksums = {} tableData = {} tableOrder = [i for i in sorted(font.keys()) if len(i) == 4] if unsortGlyfLoca: assert "loca" in tableOrder loca = tableOrder.index("loca") glyf = tableOrder.index("glyf") tableOrder.insert(glyf, tableOrder.pop(loca)) for tag in tableOrder: tableChecksums[tag] = font.reader.tables[tag].checkSum tableData[tag] = transformTable(font, tag, glyphBBox=glyphBBox, alt255UInt16=alt255UInt16) totalData = "".join([tableData[tag][1] for tag in tableOrder]) compData = brotli.compress(totalData, brotli.MODE_FONT) if len(compData) >= len(totalData): compData = totalData font.close() del font return tableData, compData, tableOrder, tableChecksums
def cleanTTF(ttfFile, outfile): # now open in fontTools ftfont = TTFont(ttfFile) # the ttf contains NAME table IDs with platformID="1", these should be removed name = ftfont['name'] names = [] for record in name.names: if record.platformID == 1: continue names.append(record) name.names = names # remove non-standard 'FFTM' the FontForge time stamp table del ftfont['FFTM'] # issue #40 let Adobe InDesign show the font in the Arabic section of the font menu. # This essentially says the only codepage that "is considered functional" is Arabic. # https://www.microsoft.com/typography/otspec/os2.htm#cpr ftfont['OS/2'].ulCodePageRange1 = 64 ftfont['OS/2'].ulCodePageRange2 = 0 # force compiling tables by fontTools, saves few tens of KBs for tag in ftfont.keys(): if hasattr(ftfont[tag], "compile"): ftfont[tag].compile(ftfont) ftfont.save(outfile) ftfont.close()
def _sniff_cff_table_format(otf: ttLib.TTFont) -> CFFTableTag: cff_tag = next( (CFFTableTag(tag) for tag in otf.keys() if tag in CFFTableTag.__members__.values()), None, ) if not cff_tag: raise Error("Invalid OTF: no 'CFF ' or 'CFF2' tables found") return cff_tag
def getSFNTData(pathOrFile): font = TTFont(pathOrFile) # checksums tableChecksums = {} for tag, entry in font.reader.tables.items(): tableChecksums[tag] = entry.checkSum # data tableData = {} for tag in font.keys(): if len(tag) != 4: continue origData = font.getTableData(tag) compData = zlib.compress(origData) if len(compData) >= len(origData) or tag == "head": compData = origData tableData[tag] = (origData, compData) # order tableOrder = [i for i in font.keys() if len(i) == 4] font.close() del font return tableData, tableOrder, tableChecksums
def get_cmap(font_text): # with open('ChcCQ1sUz0yANyKDAABj8LPX4oE56..ttf', 'wb') as f: # f.write(font_text) font = TTFont('ChcCQ1sUz0yANyKDAABj8LPX4oE56..ttf') font.saveXML('ChcCQ1sUz0yANyKDAABj8LPX4oE56..xml') bestcmap = TTFont("ChcCQ1sUz0yANyKDAABj8LPX4oE56..ttf")['cmap'].getBestCmap() newmap = dict() for key in bestcmap.keys(): value = bestcmap[key] if bestcmap[key] not in list(base_mapping.keys()) else base_mapping[bestcmap[key]] # 类型判断,手机号改为int # if isinstance(value, str): newmap[hex(key)] = value return newmap
def test_varlib_main_ttf(self): """Mostly for testing varLib.main() """ suffix = '.ttf' ds_path = self.get_test_input('Build.designspace') ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf') self.temp_dir() ttf_dir = os.path.join(self.tempdir, 'master_ttf_interpolatable') os.makedirs(ttf_dir) ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily-') for path in ttx_paths: self.compile_font(path, suffix, ttf_dir) ds_copy = os.path.join(self.tempdir, 'BuildMain.designspace') shutil.copy2(ds_path, ds_copy) # by default, varLib.main finds master TTFs inside a # 'master_ttf_interpolatable' subfolder in current working dir cwd = os.getcwd() os.chdir(self.tempdir) try: varLib_main([ds_copy]) finally: os.chdir(cwd) varfont_path = os.path.splitext(ds_copy)[0] + '-VF' + suffix self.assertTrue(os.path.exists(varfont_path)) # try again passing an explicit --master-finder os.remove(varfont_path) finder = "%s/master_ttf_interpolatable/{stem}.ttf" % self.tempdir varLib_main([ds_copy, "--master-finder", finder]) self.assertTrue(os.path.exists(varfont_path)) # and also with explicit -o output option os.remove(varfont_path) varfont_path = os.path.splitext(varfont_path)[0] + "-o" + suffix varLib_main([ds_copy, "-o", varfont_path, "--master-finder", finder]) self.assertTrue(os.path.exists(varfont_path)) varfont = TTFont(varfont_path) tables = [ table_tag for table_tag in varfont.keys() if table_tag != 'head' ] expected_ttx_path = self.get_test_output('BuildMain.ttx') self.expect_ttx(varfont, expected_ttx_path, tables)
def makeWeb(infile, outfile): """If we are building a web version then try to minimise file size""" # "short-post" generates a post table without glyph names to save some KBs # since glyph names are only needed for PDF's as readers use them to # "guess" characters when copying text, which is of little use in web fonts. flags = ("opentype", "short-post", "omit-instructions") fontforge.setPrefs("PreserveTables", "COLR,CPAL") font = fontforge.open(infile) font.encoding = "UnicodeBmp" # avoid a crash if compact was set # removed compatibility glyphs that of little use on the web compat_ranges = ( (0xfb50, 0xfbb1), (0xfbd3, 0xfd3d), (0xfd50, 0xfdf9), (0xfdfc, 0xfdfc), (0xfe70, 0xfefc), ) for glyph in font.glyphs(): for i in compat_ranges: start = i[0] end = i[1] if start <= glyph.unicode <= end: font.removeGlyph(glyph) break tmpfile = mkstemp(suffix=os.path.basename(outfile))[1] font.generate(tmpfile, flags=flags) font.close() # now open in fontTools from fontTools.ttLib import TTFont ftfont = TTFont(tmpfile) # force compiling tables by fontTools, saves few tens of KBs for tag in ftfont.keys(): if hasattr(ftfont[tag], "compile"): ftfont[tag].compile(ftfont) ftfont.save(outfile) ftfont.close() os.remove(tmpfile)
def test_varlib_main_ttf(self): """Mostly for testing varLib.main() """ suffix = '.ttf' ds_path = self.get_test_input('Build.designspace') ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf') self.temp_dir() ttf_dir = os.path.join(self.tempdir, 'master_ttf_interpolatable') os.makedirs(ttf_dir) ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily-') for path in ttx_paths: self.compile_font(path, suffix, ttf_dir) ds_copy = os.path.join(self.tempdir, 'BuildMain.designspace') shutil.copy2(ds_path, ds_copy) # by default, varLib.main finds master TTFs inside a # 'master_ttf_interpolatable' subfolder in current working dir cwd = os.getcwd() os.chdir(self.tempdir) try: varLib_main([ds_copy]) finally: os.chdir(cwd) varfont_path = os.path.splitext(ds_copy)[0] + '-VF' + suffix self.assertTrue(os.path.exists(varfont_path)) # try again passing an explicit --master-finder os.remove(varfont_path) finder = "%s/master_ttf_interpolatable/{stem}.ttf" % self.tempdir varLib_main([ds_copy, "--master-finder", finder]) self.assertTrue(os.path.exists(varfont_path)) # and also with explicit -o output option os.remove(varfont_path) varfont_path = os.path.splitext(varfont_path)[0] + "-o" + suffix varLib_main([ds_copy, "-o", varfont_path, "--master-finder", finder]) self.assertTrue(os.path.exists(varfont_path)) varfont = TTFont(varfont_path) tables = [table_tag for table_tag in varfont.keys() if table_tag != 'head'] expected_ttx_path = self.get_test_output('BuildMain.ttx') self.expect_ttx(varfont, expected_ttx_path, tables)
def test_varlib_mutator_getvar_ttf(self): suffix = '.ttf' ttx_dir = self.get_test_input('master_ttx_getvar_ttf') self.temp_dir() ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'Mutator_Getvar') for path in ttx_paths: self.compile_font(path, suffix, self.tempdir) varfont_name = 'Mutator_Getvar' varfont_path = os.path.join(self.tempdir, varfont_name + suffix) args = [varfont_path, 'wdth=80', 'ASCN=628'] mutator(args) instfont_path = os.path.splitext(varfont_path)[0] + '-instance' + suffix instfont = TTFont(instfont_path) tables = [table_tag for table_tag in instfont.keys() if table_tag != 'head'] expected_ttx_path = self.get_test_output(varfont_name + '-instance.ttx') self.expect_ttx(instfont, expected_ttx_path, tables)
def generateFont(font, outfile): flags = ("opentype", "dummy-dsig", "round", "omit-instructions") font.selection.all() font.correctReferences() font.selection.none() # fix some common font issues validateGlyphs(font) tmpfile = mkstemp(suffix=os.path.basename(outfile))[1] font.generate(tmpfile, flags=flags) font.close() # now open in fontTools from fontTools.ttLib import TTFont ftfont = TTFont(tmpfile) # the ttf contains NAME table IDs with platformID="1", these should be removed name = ftfont['name'] names = [] for record in name.names: if record.platformID == 1: continue; names.append(record) name.names = names # remove non-standard 'FFTM' the FontForge time stamp table del ftfont['FFTM']; # force compiling tables by fontTools, saves few tens of KBs for tag in ftfont.keys(): if hasattr(ftfont[tag], "compile"): ftfont[tag].compile(ftfont) ftfont.save(outfile) ftfont.close() os.remove(tmpfile)
def analysis_font(font_url: str, mode=None) -> dict: ''' analysis font ''' if (not os.path.exists(base_font) or not os.path.exists(base_pkl)) and not mode: print('base file not exist!!!') return suffix = font_url.split('.')[-1] font = requests.get(font_url, headers=header, timeout=30) font_name = '%sfont.%s' % (data_dir, suffix) with codecs.open(font_name, 'wb') as f: f.write(font.content) font_map = TTFont(font_name).getBestCmap() ''' prepare base ''' if not mode is None: char_list = [ hex(ii).upper().replace('0X', '&#x') + ';' for ii in font_map.keys() ] base_unicode = [ int(mode[ii]) if ii in mode else '.' for ii in char_list ] pickle.dump(base_unicode, codecs.open(base_pkl, 'wb')) with codecs.open(base_font, 'wb') as f: f.write(font.content) return {} base_unicode = pickle.load(open(base_pkl, 'rb')) base_map = TTFont(base_font).getBestCmap() font_dict = { jj: base_unicode[ii] for ii, jj in enumerate(base_map.values()) } num_dict = { hex(ii).upper().replace('0X', '&#x') + ';': str(font_dict[jj]) for ii, jj in font_map.items() } return num_dict
def generateFont(font, outfile): flags = ("opentype", "dummy-dsig", "round", "omit-instructions") font.selection.all() font.correctReferences() font.selection.none() # fix some common font issues validateGlyphs(font) tmpfile = mkstemp(suffix=os.path.basename(outfile))[1] font.generate(tmpfile, flags=flags) font.close() # now open in fontTools from fontTools.ttLib import TTFont ftfont = TTFont(tmpfile) # the ttf contains NAME table IDs with platformID="1", these should be removed name = ftfont['name'] names = [] for record in name.names: if record.platformID == 1: continue names.append(record) name.names = names # remove non-standard 'FFTM' the FontForge time stamp table del ftfont['FFTM'] # force compiling tables by fontTools, saves few tens of KBs for tag in ftfont.keys(): if hasattr(ftfont[tag], "compile"): ftfont[tag].compile(ftfont) ftfont.save(outfile) ftfont.close() os.remove(tmpfile)
def cleanTTF(ttfFile, outfile): # now open in fontTools ftfont = TTFont(ttfFile) # the ttf contains NAME table IDs with platformID="1", these should be removed name = ftfont['name'] names = [] for record in name.names: if record.platformID == 1: continue names.append(record) name.names = names # remove non-standard 'FFTM' the FontForge time stamp table del ftfont['FFTM'] # force compiling tables by fontTools, saves few tens of KBs for tag in ftfont.keys(): if hasattr(ftfont[tag], "compile"): ftfont[tag].compile(ftfont) ftfont.save(outfile) ftfont.close()
def test_varlib_main_ttf(self): """Mostly for testing varLib.main() """ suffix = '.ttf' ds_path = self.get_test_input('Build.designspace') ufo_dir = self.get_test_input('master_ufo') ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf') self.temp_dir() ttf_dir = os.path.join(self.tempdir, 'master_ttf_interpolatable') os.makedirs(ttf_dir) ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily-') for path in ttx_paths: self.compile_font(path, suffix, ttf_dir) ds_copy = os.path.join(self.tempdir, 'BuildMain.designspace') shutil.copy2(ds_path, ds_copy) varLib_main([ds_copy]) varfont_path = os.path.splitext(ds_copy)[0] + '-VF' + suffix varfont = TTFont(varfont_path) tables = [table_tag for table_tag in varfont.keys() if table_tag != 'head'] expected_ttx_path = self.get_test_output('BuildMain.ttx') self.expect_ttx(varfont, expected_ttx_path, tables)
def makeWeb(infile, outfile): """If we are building a web version then try to minimise file size""" # "short-post" generates a post table without glyph names to save some KBs # since glyph names are only needed for PDF's as readers use them to # "guess" characters when copying text, which is of little use in web fonts. flags = ("opentype", "short-post", "omit-instructions") font = fontforge.open(infile) font.encoding = "UnicodeBmp" # avoid a crash if compact was set # removed compatibility glyphs that of little use on the web compat_ranges = ( (0xfb50, 0xfbb1), (0xfbd3, 0xfd3d), (0xfd50, 0xfdf9), (0xfdfc, 0xfdfc), (0xfe70, 0xfefc), ) for glyph in font.glyphs(): for i in compat_ranges: start = i[0] end = i[1] if start <= glyph.unicode <= end: font.removeGlyph(glyph) break tmpfile = mkstemp(suffix=os.path.basename(outfile))[1] font.generate(tmpfile, flags=flags) font.close() # now open in fontTools from fontTools.ttLib import TTFont ftfont = TTFont(tmpfile) # our 'name' table is a bit bulky, and of almost no use in for web fonts, # so we strip all unnecessary entries. name = ftfont['name'] names = [] for record in name.names: platID = record.platformID langID = record.langID nameID = record.nameID # we keep only en_US entries in Windows and Mac platform id, every # thing else is dropped if (platID == 1 and langID == 0) or (platID == 3 and langID == 1033): if nameID == 13: # the full OFL text is too much, replace it with a simple # string if platID == 3: # MS strings are UTF-16 encoded text = 'OFL v1.1'.encode('utf_16_be') else: text = 'OFL v1.1' record.string = text names.append(record) # keep every thing else except Descriptor, Sample Text elif nameID not in (10, 19): names.append(record) name.names = names # force compiling tables by fontTools, saves few tens of KBs for tag in ftfont.keys(): if hasattr(ftfont[tag], "compile"): ftfont[tag].compile(ftfont) ftfont.save(outfile) ftfont.close() os.remove(tmpfile)
def getWOFFCollectionData(pathOrFiles, MismatchGlyfLoca=False): from defaultData import defaultTestData tableChecksums = [] tableData = [] tableOrder = [] collectionDirectory = [] locaIndices = [] for i, pathOrFile in enumerate(pathOrFiles): font = TTFont(pathOrFile) # Make the name table unique name = font["name"] for namerecord in name.names: nameID = namerecord.nameID string = namerecord.toUnicode() if nameID == 1: namerecord.string = "%s %d" % (string, i) elif nameID == 4: namerecord.string = string.replace("Regular", "%d Regular" % i) elif nameID == 6: namerecord.string = string.replace("-", "%d-" % i) tags = [i for i in sorted(font.keys()) if len(i) == 4] if "glyf" in tags: glyf = tags.index("glyf") loca = tags.index("loca") tags.insert(glyf + 1, tags.pop(loca)) tableIndices = OrderedDict() for tag in tags: data = transformTable(font, tag) if MismatchGlyfLoca and tag in ("glyf", "loca"): tableData.append([tag, data]) tableChecksums.append([tag, font.reader.tables[tag].checkSum]) tableOrder.append(tag) tableIndex = len(tableData) - 1 tableIndices[tag] = tableIndex if tag == "loca": locaIndices.append(tableIndex) else: if [tag, data] not in tableData: tableData.append([tag, data]) tableChecksums.append( [tag, font.reader.tables[tag].checkSum]) tableOrder.append(tag) tableIndices[tag] = tableData.index([tag, data]) collectionDirectory.append( dict(numTables=len(tableIndices), flavor=font.sfntVersion, index=tableIndices)) font.close() del font if MismatchGlyfLoca: locaIndices.reverse() for i, entry in enumerate(collectionDirectory): entry["index"]["loca"] = locaIndices[i] totalData = "".join([data[1][1] for data in tableData]) compData = brotli.compress(totalData, brotli.MODE_FONT) if len(compData) >= len(totalData): compData = totalData directory = [ dict(tag=tag, origLength=0, transformLength=0) for tag in tableOrder ] header, directory, collectionHeader, collectionDirectory, tableData = defaultTestData( directory=directory, tableData=tableData, compressedData=compData, collectionDirectory=collectionDirectory) data = packTestHeader(header) data += packTestDirectory(directory, isCollection=True) data += packTestCollectionHeader(collectionHeader) data += packTestCollectionDirectory(collectionDirectory) data += tableData data = padData(data) return data
def main(): args = parser.parse_args() projectVersion = getVersion() for font_path in args.fonts: # open font path as a font object, for manipulation ttfont = TTFont(font_path) # check for gvar table to see whether it's a variable font if 'gvar' not in ttfont.keys(): fontIsStatic = True print("\n-------------------------------------------\nFont is static.") else: fontIsStatic = False print("\n-------------------------------------------\nFont is variable.") # GET NAME ID 17, typographic style name, to use in name ID 6 styleName = getFontNameID(ttfont, 17) print(styleName) styleNames = str(styleName).split(' ') # UPDATE NAME ID 16, typographic family name famName = getFontNameID(ttfont, 16) newFamName = f"{famName} {projectVersion}st" newFamName = abbreviateName(newFamName, styleNames) setFontNameID(ttfont, 16, newFamName) # UPDATE NAME ID 6 # replace last part of postScript font name, e.g. "LinearA" from "RecursiveMono-LinearA" if fontIsStatic: psName = str(getFontNameID(ttfont, 6)) # psStyle = psName.split("-")[-1] psFam = psName.split("-")[0] newPsName = psName.replace(psFam, f"{psFam}{projectVersion.replace(' ','').replace('1.','')}st") if 'Beta' in newPsName: newPsName = newPsName.replace('Beta', NAME_ABBR['Beta']) newPsName = abbreviateName(newPsName, styleNames) else: print("Variable font") psName = str(getFontNameID(ttfont, 6)) psFam = psName.split("-")[0] newPsName = psName.replace(psFam, psFam + projectVersion.replace(' ','').replace('1.','')) # set new ps name setFontNameID(ttfont, 6, newPsName) # VERSION, ID 5 (e.g. "Version 1.005") if "Beta" in projectVersion: newVersion = f"Version {projectVersion.replace('Beta ','')}" else: newVersion = f"Version {projectVersion}" setFontNameID(ttfont, 5, newVersion) # FULL FONT NAME, ID 4 if fontIsStatic: newFamName = f"{famName} {projectVersion}st" newFamName = newFamName + ' ' + styleName styleName = styleName.replace('Linear ','').replace('Casual ','') # if 'Linear' in styleName: # newFamName = newFamName + ' ' + styleName # styleName = styleName.replace('Linear ','') # if 'Casual' in styleName: # newFamName = newFamName + ' ' + styleName # styleName = styleName.replace('Casual ','') newFamName = newFamName.replace(' Italic','').replace('Italic','') else: newFamName = f"{famName} {projectVersion}" newFamName = abbreviateName(newFamName, styleNames) if fontIsStatic: completeName = newFamName if 'Italic' in styleName: completeName = abbreviateName(newFamName + 'Italic', styleNames) setFontNameID(ttfont, 4, completeName) else: newFamName = abbreviateName(newFamName, styleNames) setFontNameID(ttfont, 4, newFamName) # UNIQUE FONT NAME, ID 3 (e.g. 1.005;ARRW;RecursiveSans-LinearA) oldUniqueID = str(getFontNameID(ttfont, 3)) oldUniqueIDParts = oldUniqueID.split(";") newUniqueID = f"{projectVersion.replace('Beta ','')};{oldUniqueIDParts[1]};{newPsName}" setFontNameID(ttfont, 3, newUniqueID) # UPDATE BASIC FONT NAME, id 1 legalStyleNames = ['Regular', 'Italic', 'Bold', 'Bold Italic'] # TODO: if styleName not in legalStyleNames and 'Italic' in styleName: styleName = 'Italic' if styleName not in legalStyleNames and 'Italic' not in styleName: styleName = 'Regular' setFontNameID(ttfont, 2, styleName) setFontNameID(ttfont, 1, newFamName) # SAVE FONT if args.inplace: ttfont.save(font_path) else: ttfont.save(font_path + '.fix')
class TTF: ##################################################################################### tt = None cmap = None width = 0 linegap = 0 ##### INITIATION #####====================================================================== def __init__(self, fname): # Loads TrueType Font from fname. Returns success self.tt = TTFont(fname) # print self.tt.getGlyphNames() self.getCMAP() print self.tt.keys() # print self.tt['head'] self.width = .8*self.tt['hhea'].advanceWidthMax self.linegap = self.tt['hhea'].lineGap print self.width, self.linegap # print self.tt[ 'hdmx' ].hdmx # print self.tt[ 'kern' ] # # print self.tt.tables['kern'] def __getitem__(self, i): if type(i) is str: i = ord(i) #self.tt.getGlyphNames()[i] # print self.getCMAP().cmap[i] return self.shapeFromGlyph( self.tt[ 'glyf' ][ self.getCMAP().cmap[i] ] ) def getCMAP(self): if self.cmap == None: for table in self.tt['cmap'].tables: print table if isinstance(table, fontTools.ttLib.tables._c_m_a_p.cmap_format_0) or isinstance(table, fontTools.ttLib.tables._c_m_a_p.cmap_format_4): # print table # print table.cmap # print "\n" # print sorted(table.cmap.items()) self.cmap = table break return self.cmap def shapeFromGlyph(self, glyph): # Save loaded glyphs for speed toReturn = Shape([],[]) if glyph.isComposite(): print "Warning: This case is broken!, it does not transform..." # print "COMPOSITE" for component in glyph.components: print component print component.getComponentInfo() [name, transform] = component.getComponentInfo() print transform print type(transform) m = Matrix(transform) print m print self.tt['glyf'][name] toReturn.add(self.shapeFromGlyph(self.tt['glyf'][name])) # for item in self.getCMAP().cmap.items(): # if item[1] == name: # toReturn.add(self.shapeFromGlyph(self.tt[ 'glyf' ][item[0]])) # break else: # print "NOT COMPOSITE" last = 0 for i in range(glyph.numberOfContours): toAdd = Polyline([], True) prevV = None prevOn = None prevprevV = None prevprevOn = None firstV = None firstOn = None secondV = None secondOn = None for j in range(last, glyph.endPtsOfContours[i] + 1): v = Vector(glyph.coordinates[j][0], glyph.coordinates[j][1])/float(self.width) # This is a temporary fix! on = glyph.flags[j] & 0x01 if firstOn == None: firstOn = on firstV = v elif secondOn == None: secondOn = on secondV = v # print v # print prevprevOn, prevOn, on # print prevprevV, prevV, v if prevOn and on: if prevprevOn == None: toAdd.add(prevV) toAdd.add(v) elif prevOn != None and prevprevOn != None: if prevprevOn and not prevOn: if on: # 101 toAdd.add(qBezier(prevprevV, prevV, v)) elif not on: # 100 # print (prevV + v) # print (prevV + v)/2 toAdd.add(qBezier(prevprevV, prevV, (prevV + v)/2)) elif not prevprevOn and not prevOn: if on: # 001 toAdd.add(qBezier((prevprevV + prevV)/2, prevV, v)) elif not on: # 000 toAdd.add(qBezier((prevprevV + prevV)/2, prevV, (prevV + v)/2)) prevprevOn = prevOn prevprevV = prevV prevOn = on prevV = v # print "toAdd: " # print toAdd # print "First Here" # print prevOn, on, firstOn # print prevprevV, v, firstV # toAdd.add(Vector(0,0)) if not on: if firstOn != None and not firstOn: if prevprevOn: toAdd.add(qBezier(prevprevV, v, (v + firstV)/2)) else: toAdd.add(qBezier((prevprevV + v)/2, v, (v + firstV)/2)) if firstOn != None and firstOn: if prevprevOn: toAdd.add(qBezier(prevprevV, v, firstV)) else: toAdd.add(qBezier((prevprevV + v)/2, v, firstV)) # if firstOn != None and secondOn != None: # print "First Second Here" # print on, firstOn, secondOn ## if on and firstOn and secondOn: # 111 # if on and not firstOn and secondOn: # 101 # toAdd.add(qBezier(v, firstV, secondV)) # if on and not firstOn and not secondOn: # 100 # toAdd.add(qBezier(v, firstV, (firstV + secondV)/2)) # if not on and not firstOn and secondOn: # 001 # toAdd.add(qBezier((v + firstV)/2, firstV, secondV)) # if not on and not firstOn and not secondOn: # 000 # toAdd.add(qBezier((v + firstV)/2, firstV, (firstV + secondV)/2)) # toAdd.add(v) last = glyph.endPtsOfContours[i] + 1 # print toAdd.area() # toAdd.add(Vector(0,0)) toReturn.add(toAdd) # print "toReturn: " # print toReturn if len(toReturn.polylines) > 1: k = 0 for i in range(0, toReturn.size): # print toReturn.polylines[i].area() if abs(toReturn.polylines[i].area()) > abs(toReturn.polylines[k].area()): # Save area for speed k = i finList = [] for i in range(0, toReturn.size): if toReturn.polylines[i].area() < 0: # print k, i # print toReturn.polylines[i] # print toReturn.polylines[k] # toReturn.polylines[i].points.reverse() m = 0 #toReturn.polylines[i].size-1 j = 0 norm = (toReturn.polylines[k].points[0] - toReturn.polylines[i].points[m]).norm2() # Possible error if empty toReturn.polylines[k].sizeCalc() for l in range(0, toReturn.polylines[k].size): # print norm, (toReturn.polylines[k].points[l] - toReturn.polylines[i].points[m]).norm2() if (toReturn.polylines[k].points[l] - toReturn.polylines[i].points[m]).norm2() < norm: j = l norm = (toReturn.polylines[k].points[l] - toReturn.polylines[i].points[m]).norm2() # print j # toReturn.polylines[k].points[j], toReturn.polylines[k].points = toReturn.polylines[k].points[0:j+1] + toReturn.polylines[i].points + [toReturn.polylines[i].points[0]] + toReturn.polylines[k].points[j:] # toReturn.polylines[k].points = toReturn.polylines[k].points[0:j+1] + [toReturn.polylines[i].points[toReturn.polylines[i].size-1]] + toReturn.polylines[i].points + toReturn.polylines[k].points[j:] toReturn.polylines[k].sizeCalc() else: finList += [ toReturn.polylines[i] ] toReturn = Shape(finList) # toReturn.polylines.remove(i) # finList = [toReturn.polylines[k]] # # for polyline in toReturn.polylines: # print polyline # if polyline != toReturn.polylines[k]: # print polyline.area() # if polyline.area() < 0: # # highest = polyline.points[0] # print "HIGHEST: ", highest # j = 0 # # for i in range(0, polyline.size): # if polyline.points[i].y > highest.y: # highest = polyline.points[i] # j = i # # polyline.points.insert(i, highest) # polyline.points.insert(i, highest + Vector(0,2)) # polyline.points.insert(i, highest) # # else: # print "extended" # finList.extend(polyline) # # toReturn.polylines[k].union(polyline) # # return Shape(finList,[]) return toReturn def shapeFromString(self, string): toReturn = Shape([],[]) # dv = Vector(1. + self.linegap/self.width,0) v = Vector(0,0) w = Vector(0,0) for char in string: # plt.plot([v.x, v.x, v.x + wid, v.x + wid, v.x], [v.y, v.y + hgt, v.y + hgt, v.y, v.y]) # toReturn.polylines.extend( [(letter + v - Vector(shift, 0))] ) if char != '\n': letter = self[char] bb = letter.getBoundingBox() if bb != None: wid = bb[1].x - bb[0].x hgt = bb[1].y - bb[0].y shift = bb[0].x else: wid = 1 hgt = 1 shift = 0 for polyline in letter.polylines: toReturn.polylines.extend( [(polyline + v - Vector(shift, 0))] ) # polyline = self[char] # (polyline + v).plot() v += Vector(wid + .2, 0) else: v = Vector(0,v.y-hgt - .6) # # polyline = font2[char] # (polyline + w).plot() # w += dv toReturn.sizeCalc() return toReturn def shapeFromStringBoundedCentered(self, string, w=0, h=0): shape = self.shapeFromString(string) [bbll, bbur] = shape.getBoundingBox() c = (bbll + bbur)/2 if w < 0 and h < 0: return shape - c elif w < 0: w = h elif h < 0: h = w numlines = 1; for char in string: if char == '\n': numlines += 1; h *= (numlines*1.6 - .6); sw = (bbur-bbll).x sh = (bbur-bbll).y if sw < 0 or sh < 0: print "something went horribly wrong..." ratioShape = sw/sh ratioBound = w/h if ratioBound > ratioShape: return (shape - c)*(w/sw) elif ratioBound <= ratioShape: return (shape - c)*(h/sh)
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. from __future__ import print_function from fontTools.ttLib import TTFont, sfnt from os.path import splitext import sys if __name__ == '__main__': if len(sys.argv) < 2: print("usage: python %s filename" % sys.argv[0], file=sys.stderr) sys.exit(1) filename = sys.argv[1] basename = splitext(filename)[0] sfnt.USE_ZOPFLI = True for flavor in ["woff", "woff2"]: outfilename = "%s.%s" % (basename, flavor) print("Processing %s => %s" % (filename, outfilename)) font = TTFont(filename, recalcBBoxes=False, recalcTimestamp=False) for t in font.keys(): if hasattr(font[t], "compile"): font[t].compile(font) font.flavor = flavor font.save(outfilename, reorderTables=False)
class DFont(TTFont): """Container font for ttfont, freetype and hb fonts""" def __init__(self, path=None, lazy=False, size=1500): self.path = path self.ttfont = TTFont(self.path) self._src_ttfont = TTFont(self.path) self.glyphset = None self.recalc_glyphset() self.axis_order = None self.instance_coordinates = self._get_dflt_instance_coordinates() self.instances_coordinates = self._get_instances_coordinates() self.glyphs = self.marks = self.mkmks = self.kerns = \ self.glyph_metrics = self.names = self.attribs = None self.ftfont = freetype.Face(self.path) self.ftslot = self.ftfont.glyph self.size = size self.ftfont.set_char_size(self.size) with open(self.path, 'rb') as fontfile: self._fontdata = fontfile.read() self.hbface = hb.Face.create(self._fontdata) self.hbfont = hb.Font.create(self.hbface) self.hbfont.scale = (self.size, self.size) if not lazy: self.recalc_tables() def _get_instances_coordinates(self): if self.is_variable: return [i.coordinates for i in self._src_ttfont["fvar"].instances] return None def _get_dflt_instance_coordinates(self): if self.is_variable: return {i.axisTag: i.defaultValue for i in self._src_ttfont['fvar'].axes} return None def glyph(self, name): return self.glyphset[name] def recalc_glyphset(self): if not 'cmap' in self.ttfont.keys(): self.glyphset = [] inputs = InputGenerator(self).all_inputs() self.glyphset = {g.name: g for g in inputs} @property def is_variable(self): if 'fvar' in self._src_ttfont: return True return False def set_variations(self, axes): """Instantiate a ttfont VF with axes vals""" if self.is_variable: font = instantiateVariableFont(self._src_ttfont, axes, inplace=False) self.ttfont = copy(font) self.axis_order = [a.axisTag for a in self._src_ttfont['fvar'].axes] self.instance_coordinates = {a.axisTag: a.defaultValue for a in self._src_ttfont['fvar'].axes} for axis in axes: if axis in self.instance_coordinates: self.instance_coordinates[axis] = axes[axis] else: logger.info("font has no axis called {}".format(axis)) self.recalc_tables() coords = [] for name in self.axis_order: coord = FT_Fixed(int(self.instance_coordinates[name]) << 16) coords.append(coord) ft_coords = (FT_Fixed * len(coords))(*coords) FT_Set_Var_Design_Coordinates(self.ftfont._FT_Face, len(ft_coords), ft_coords) self.hbface = hb.Face.create(self._fontdata) self.hbfont = hb.Font.create(self.hbface) self.hbfont.set_variations(self.instance_coordinates) self.hbfont.scale = (self.size, self.size) else: logger.info("Not vf") def set_variations_from_static(self, dfont): """Set the variations of a variable font using the vals from a static font""" variations = {} if self.is_variable: variations["wght"] = dfont.ttfont["OS/2"].usWeightClass # TODO (M Foley) add wdth, slnt axes self.set_variations(variations) def recalc_tables(self): """Recalculate DFont tables""" self.recalc_glyphset() anchors = DumpAnchors(self) self.glyphs = dump_glyphs(self) self.marks = anchors.marks_table self.mkmks = anchors.mkmks_table self.glyph_metrics = dump_glyph_metrics(self) self.attribs = dump_attribs(self) self.names = dump_nametable(self) self.kerns = dump_kerning(self) self.metrics = dump_glyph_metrics(self)
import base64 from fontTools.ttLib import TTFont font_face = "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" b = base64.b64decode(font_face) f = open('58.ttf', 'wb') f.write(b) f.close() fo = TTFont("58.ttf") print(fo.keys()) fo.saveXML('test.xml')
def test_ensureDecompiled(lazy): # test that no matter the lazy value, ensureDecompiled decompiles all tables font = TTFont() font.importXML(os.path.join(DATA_DIR, "TestTTF-Regular.ttx")) # test font has no OTL so we add some, as an example of otData-driven tables addOpenTypeFeaturesFromString( font, """ feature calt { sub period' period' period' space by ellipsis; } calt; feature dist { pos period period -30; } dist; """ ) # also add an additional cmap subtable that will be lazily-loaded cm = CmapSubtable.newSubtable(14) cm.platformID = 0 cm.platEncID = 5 cm.language = 0 cm.cmap = {} cm.uvsDict = {0xFE00: [(0x002e, None)]} font["cmap"].tables.append(cm) # save and reload, potentially lazily buf = io.BytesIO() font.save(buf) buf.seek(0) font = TTFont(buf, lazy=lazy) # check no table is loaded until/unless requested, no matter the laziness for tag in font.keys(): assert not font.isLoaded(tag) if lazy is not False: # additional cmap doesn't get decompiled automatically unless lazy=False; # can't use hasattr or else cmap's maginc __getattr__ kicks in... cm = next(st for st in font["cmap"].tables if st.__dict__["format"] == 14) assert cm.data is not None assert "uvsDict" not in cm.__dict__ # glyf glyphs are not expanded unless lazy=False assert font["glyf"].glyphs["period"].data is not None assert not hasattr(font["glyf"].glyphs["period"], "coordinates") if lazy is True: # OTL tables hold a 'reader' to lazily load when lazy=True assert "reader" in font["GSUB"].table.LookupList.__dict__ assert "reader" in font["GPOS"].table.LookupList.__dict__ font.ensureDecompiled() # all tables are decompiled now for tag in font.keys(): assert font.isLoaded(tag) # including the additional cmap cm = next(st for st in font["cmap"].tables if st.__dict__["format"] == 14) assert cm.data is None assert "uvsDict" in cm.__dict__ # expanded glyf glyphs lost the 'data' attribute assert not hasattr(font["glyf"].glyphs["period"], "data") assert hasattr(font["glyf"].glyphs["period"], "coordinates") # and OTL tables have read their 'reader' assert "reader" not in font["GSUB"].table.LookupList.__dict__ assert "Lookup" in font["GSUB"].table.LookupList.__dict__ assert "reader" not in font["GPOS"].table.LookupList.__dict__ assert "Lookup" in font["GPOS"].table.LookupList.__dict__
class DFont(TTFont): """Container font for ttfont, freetype and hb fonts""" def __init__(self, path=None, lazy=False, size=1500, ft_load_glyph_flags=FTHintMode.UNHINTED): self.path = path self.ttfont = TTFont(self.path) has_outlines = self.ttfont.has_key("glyf") or self.ttfont.has_key( "CFF ") if not has_outlines: # Create faux empty glyf table with empty glyphs to make # it a valid font, e.g. for old-style CBDT/CBLC fonts logger.warning( "No outlines present, treating {} as bitmap font".format( self.path)) self.ttfont["glyf"] = newTable("glyf") self.ttfont["glyf"].glyphs = {} pen = TTGlyphPen({}) for name in self.ttfont.getGlyphOrder(): self.ttfont["glyf"].glyphs[name] = pen.glyph() self._src_ttfont = TTFont(self.path) self.glyphset = None self.recalc_glyphset() self.axis_order = None self.instance_coordinates = self._get_dflt_instance_coordinates() self.instances_coordinates = self._get_instances_coordinates() self.glyphs = self.marks = self.mkmks = self.kerns = \ self.glyph_metrics = self.names = self.attribs = None self.ftfont = freetype.Face(self.path) self.ftslot = self.ftfont.glyph self.ft_load_glyph_flags = ft_load_glyph_flags self.size = size if self.ftfont.is_scalable: self.ftfont.set_char_size(self.size) with open(self.path, 'rb') as fontfile: self._fontdata = fontfile.read() self.hbface = hb.Face.create(self._fontdata) self.hbfont = hb.Font.create(self.hbface) self.hbfont.scale = (self.size, self.size) if not lazy: self.recalc_tables() def _get_instances_coordinates(self): results = {} if self.is_variable: for inst in self._src_ttfont['fvar'].instances: inst_name = self._src_ttfont['name'].getName( inst.subfamilyNameID, 3, 1, 1033).toUnicode() results[inst_name] = inst.coordinates return results def _get_dflt_instance_coordinates(self): if self.is_variable: return { i.axisTag: i.defaultValue for i in self._src_ttfont['fvar'].axes } return None def glyph(self, name): return self.glyphset[name] def recalc_glyphset(self): if not 'cmap' in self.ttfont.keys(): self.glyphset = [] inputs = InputGenerator(self).all_inputs() self.glyphset = {g.name: g for g in inputs} @property def is_variable(self): if 'fvar' in self._src_ttfont: return True return False def set_variations(self, axes): """Instantiate a ttfont VF with axes vals""" logger.debug("Setting variations to {}".format(axes)) if self.is_variable: font = instantiateVariableFont(self._src_ttfont, axes, inplace=False) self.ttfont = copy(font) self.axis_order = [ a.axisTag for a in self._src_ttfont['fvar'].axes ] self.instance_coordinates = { a.axisTag: a.defaultValue for a in self._src_ttfont['fvar'].axes } for axis in axes: if axis in self.instance_coordinates: self.instance_coordinates[axis] = axes[axis] else: logger.info("font has no axis called {}".format(axis)) self.recalc_tables() coords = [] for name in self.axis_order: coord = FT_Fixed(int(self.instance_coordinates[name]) << 16) coords.append(coord) ft_coords = (FT_Fixed * len(coords))(*coords) FT_Set_Var_Design_Coordinates(self.ftfont._FT_Face, len(ft_coords), ft_coords) self.hbface = hb.Face.create(self._fontdata) self.hbfont = hb.Font.create(self.hbface) self.hbfont.set_variations(self.instance_coordinates) self.hbfont.scale = (self.size, self.size) else: logger.info("Not vf") def set_variations_from_static(self, static_font): """Set VF font variations so they match a static font.""" if not self.is_variable: raise Exception("Not a variable font") # Use an fvar instance if its name matches the static font's # typographic subfamily name or subfamily name subfamilyname = static_font.ttfont['name'].getName(2, 3, 1, 1033) typosubfamilyname = static_font.ttfont['name'].getName(17, 3, 1, 1033) anysubfamilyname = typosubfamilyname or subfamilyname subfamilyname = anysubfamilyname.toUnicode( ) if anysubfamilyname else "" # The Google Fonts v1 api can only handle the wght axis. For families # which have widths, we have to release them as a seperate family, # https://fonts.google.com/?query=condensed # To distinguish the width family from the normal family, we append # the width to the family name e.g Roboto Condensed filename = os.path.basename(static_font.path) family_name = filename.split("-")[0] family_name_width = find_token(family_name, list(WIDTH_NAME_TO_FVAR.keys())) if family_name_width: subfamilyname = f"{family_name_width} {subfamilyname}" if subfamilyname in self.instances_coordinates: logger.debug( f"Instance name '{subfamilyname}' matches static font " "subfamily names. Setting variations using this instance.") variations = self.instances_coordinates[subfamilyname] self.set_variations(variations) return # if the font doesn't contain an instance name which matches the # static font, infer the correct values logger.debug( f"Font does not contain an instance name which matches " f"static font subfamily names '{subfamilyname}'. Inferring " "values instead.") variations = {} # wght parsed_weight = find_token(os.path.basename(static_font.path), list(WEIGHT_NAME_TO_FVAR.keys())) if parsed_weight: variations["wght"] = WEIGHT_NAME_TO_FVAR[parsed_weight] else: logger.debug( f"Couldn't parse weight value from {static_font.path}") weight_class = static_font.ttfont["OS/2"].usWeightClass # Google Fonts used to set the usWeightClass of Thin static # fonts to 250 and the ExtraLight to 275. Override these # values with 100 and 200. if weight_class == 250: weight_class = 100 if weight_class == 275: weight_class = 200 variations["wght"] = weight_class # wdth # We cannot simply use OS/2.usWidthClass since Google Fonts # releases Condensed styles as new families. These new families # have a usWidthClass of 5 (Normal). parsed_width = find_token(os.path.basename(static_font.path), list(WIDTH_NAME_TO_FVAR.keys())) if parsed_width: variations["wdth"] = WIDTH_NAME_TO_FVAR[parsed_width] else: logger.debug( f"Couldn't parse weight value from {static_font.path}") width_class = static_font.ttfont["OS/2"].usWidthClass variations["wdth"] = WIDTH_CLASS_TO_FVAR[width_class] # TODO (M Foley) add slnt axes self.set_variations(variations) def recalc_tables(self): """Recalculate DFont tables""" self.recalc_glyphset() anchors = DumpAnchors(self) self.glyphs = dump_glyphs(self) self.marks = anchors.marks_table self.mkmks = anchors.mkmks_table self.attribs = dump_attribs(self) self.names = dump_nametable(self) self.kerns = dump_kerning(self) self.metrics = dump_glyph_metrics(self) self.gdef_base, self.gdef_mark = dump_gdef(self)