def convertToOTF(ttfPath, dest, report): temp = tempfile.mkstemp(suffix=".otf")[1] font = RFont(ttfPath, document=False, showInterface=False) font.kerning.clear() for attr in font.info.asDict().keys(): if attr not in defaultFontInfoAttributes: setattr(font.info, attr, None) result = font.generate(path=temp, format="otf", decompose=False, checkOutlines=False, autohint=False, releaseMode=True, glyphOrder=font.glyphOrder) if not font.hasInterface(): font.close() report.write(result) sourceFont = TTFont(temp) sourceFontWithTables = TTFont(ttfPath) for table in ["loca", "OS/2", "cmap", "name", "GSUB", "GPOS", "GDEF", "kern"]: if table in sourceFontWithTables: sourceFont[table] = sourceFontWithTables[table] sourceFont.save(dest) result = OTFAutohint(dest) report.writeItems(result) os.remove(temp)
def test_ttcompile_timestamp_calcs(inpath, outpath1, outpath2, tmpdir): inttx = os.path.join("Tests", "ttx", "data", inpath) outttf1 = tmpdir.join(outpath1) outttf2 = tmpdir.join(outpath2) options = ttx.Options([], 1) # build with default options = do not recalculate timestamp ttx.ttCompile(inttx, str(outttf1), options) # confirm that font was built assert outttf1.check(file=True) # confirm that timestamp is same as modified time on ttx file mtime = os.path.getmtime(inttx) epochtime = timestampSinceEpoch(mtime) ttf = TTFont(str(outttf1)) assert ttf["head"].modified == epochtime # reset options to recalculate the timestamp and compile new font options.recalcTimestamp = True ttx.ttCompile(inttx, str(outttf2), options) # confirm that font was built assert outttf2.check(file=True) # confirm that timestamp is more recent than modified time on ttx file mtime = os.path.getmtime(inttx) epochtime = timestampSinceEpoch(mtime) ttf = TTFont(str(outttf2)) assert ttf["head"].modified > epochtime # --no-recalc-timestamp will keep original timestamp options.recalcTimestamp = False ttx.ttCompile(inttx, str(outttf2), options) assert outttf2.check(file=True) inttf = TTFont() inttf.importXML(inttx) assert inttf["head"].modified == TTFont(str(outttf2))["head"].modified
def test_retain_gids_cff2(self): ttx_path = self.getpath("../../varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx") font, fontpath = self.compile_font(ttx_path, ".otf") self.assertEqual(font["hmtx"]["A"], (600, 31)) self.assertEqual(font["hmtx"]["T"], (600, 41)) font["CFF2"].cff[0].decompileAllCharStrings() cs = font["CFF2"].cff[0].CharStrings self.assertGreater(len(cs["A"].program), 0) self.assertGreater(len(cs["T"].program), 0) subsetpath = self.temp_path(".otf") subset.main( [ fontpath, "--retain-gids", "--output-file=%s" % subsetpath, "T", ] ) subsetfont = TTFont(subsetpath) self.assertEqual(len(subsetfont.getGlyphOrder()), len(font.getGlyphOrder()[0:3])) hmtx = subsetfont["hmtx"] self.assertEqual(hmtx["glyph00001"], ( 0, 0)) self.assertEqual(hmtx["T"], (600, 41)) subsetfont["CFF2"].cff[0].decompileAllCharStrings() cs = subsetfont["CFF2"].cff[0].CharStrings self.assertEqual(cs["glyph00001"].program, []) self.assertGreater(len(cs["T"].program), 0)
def post(self): fontdata = self.request.POST.get('font', None) # Need to use isinstance as cgi.FieldStorage always evaluates to False. # See http://bugs.python.org/issue19097 if not isinstance(fontdata, cgi.FieldStorage): self.redirect('/font_conversion.html?' + urllib.urlencode( {'err_msg': 'Please select a font'})) return #TODO(bstell) make this work correctly. font_type = 'woff' name = os.path.splitext(os.path.basename(fontdata.filename))[0] try: font = TTFont(fontdata.file) except: self.redirect('/font_conversion.html?' + urllib.urlencode( {'err_msg': 'failed to parse font'})) return self.response.headers['Content-Type'] = 'application/font-woff' self.response.headers['Content-Disposition'] = \ 'attachment; filename={0}.{1}'.format(name, font_type) font.flavor = font_type output = StringIO.StringIO() font.save(output) self.response.out.write(output.getvalue())
def get_font_xml(self): """ 获取字体 xml :return: """ font = TTFont(self.font_path) font.saveXML(self.xml_path)
def test_retain_gids_ttf(self): _, fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf") font = TTFont(fontpath) self.assertEqual(font["hmtx"]["A"], (500, 132)) self.assertEqual(font["hmtx"]["B"], (400, 132)) self.assertGreater(font["glyf"]["A"].numberOfContours, 0) self.assertGreater(font["glyf"]["B"].numberOfContours, 0) subsetpath = self.temp_path(".ttf") subset.main( [ fontpath, "--retain-gids", "--output-file=%s" % subsetpath, "--glyph-names", "B", ] ) subsetfont = TTFont(subsetpath) self.assertEqual(subsetfont.getGlyphOrder(), font.getGlyphOrder()[0:3]) hmtx = subsetfont["hmtx"] self.assertEqual(hmtx["A"], ( 0, 0)) self.assertEqual(hmtx["B"], (400, 132)) glyf = subsetfont["glyf"] self.assertEqual(glyf["A"].numberOfContours, 0) self.assertGreater(glyf["B"].numberOfContours, 0)
def makeTTFont(): glyphs = """ .notdef space slash fraction semicolon period comma ampersand quotedblleft quotedblright quoteleft quoteright zero one two three four five six seven eight nine zero.oldstyle one.oldstyle two.oldstyle three.oldstyle four.oldstyle five.oldstyle six.oldstyle seven.oldstyle eight.oldstyle nine.oldstyle onequarter onehalf threequarters onesuperior twosuperior threesuperior ordfeminine ordmasculine A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z A.sc B.sc C.sc D.sc E.sc F.sc G.sc H.sc I.sc J.sc K.sc L.sc M.sc N.sc O.sc P.sc Q.sc R.sc S.sc T.sc U.sc V.sc W.sc X.sc Y.sc Z.sc A.alt1 A.alt2 A.alt3 B.alt1 B.alt2 B.alt3 C.alt1 C.alt2 C.alt3 a.alt1 a.alt2 a.alt3 a.end b.alt c.mid d.alt d.mid e.begin e.mid e.end m.begin n.end s.end z.end Eng Eng.alt1 Eng.alt2 Eng.alt3 A.swash B.swash C.swash D.swash E.swash F.swash G.swash H.swash I.swash J.swash K.swash L.swash M.swash N.swash O.swash P.swash Q.swash R.swash S.swash T.swash U.swash V.swash W.swash X.swash Y.swash Z.swash f_l c_h c_k c_s c_t f_f f_f_i f_f_l f_i o_f_f_i s_t f_i.begin a_n_d T_h T_h.swash germandbls ydieresis yacute breve grave acute dieresis macron circumflex cedilla umlaut ogonek caron damma hamza sukun kasratan lam_meem_jeem noon.final noon.initial """.split() font = TTFont() font.setGlyphOrder(glyphs) return font
def handle_font(font_name): font = TTFont(font_name) orig_size = os.path.getsize(font_name) if decompress: from fontTools import subset options = subset.Options() options.desubroutinize = True subsetter = subset.Subsetter(options=options) subsetter.populate(glyphs=font.getGlyphOrder()) subsetter.subset(font) if verbose: print("Compressing font through iterative_encode:") out_name = "%s.compressed%s" % os.path.splitext(font_name) compreffor = Compreffor(font, verbose=verbose, **comp_kwargs) compreffor.compress() # save compressed font font.save(out_name) if generate_cff: # save CFF version font["CFF "].cff.compile(open("%s.cff" % os.path.splitext(out_name)[0], "w"), None) comp_size = os.path.getsize(out_name) print("Compressed to %s -- saved %s" % (os.path.basename(out_name), human_size(orig_size - comp_size))) if check: test_compression_integrity(filename, out_name) test_call_depth(out_name)
def fontToUFO(src, dst, fileType=None): from robofab.ufoLib import UFOWriter from robofab.pens.adapterPens import SegmentToPointPen if fileType is None: fileType = guessFileType(src) if fileType is None: raise ValueError, "Can't determine input file type" ufoWriter = UFOWriter(dst) if fileType == "TTF": from fontTools.ttLib import TTFont font = TTFont(src, 0) elif fileType == "Type 1": from fontTools.t1Lib import T1Font font = T1Font(src) else: assert 0, "unknown file type: %r" % fileType inGlyphSet = font.getGlyphSet() outGlyphSet = ufoWriter.getGlyphSet() for glyphName in inGlyphSet.keys(): print "-", glyphName glyph = inGlyphSet[glyphName] def drawPoints(pen): pen = SegmentToPointPen(pen) glyph.draw(pen) outGlyphSet.writeGlyph(glyphName, glyph, drawPoints) outGlyphSet.writeContents() if fileType == "TTF": info = extractTTFFontInfo(font) elif fileType == "Type 1": info = extractT1FontInfo(font) ufoWriter.writeInfo(info)
def find_and_replace_ttx(ttx_path, find_string, replace_string, tables=['name']): count = 0 # 1. modify 'name' table if 'name' in tables: tree = parse(ttx_path) root = tree.getroot() for child in root.find('name'): if child.text.find(find_string) != -1: new_text = child.text.replace(find_string, replace_string) child.text = new_text count += 1 tree.write(ttx_path) # 2. modify 'CFF ' table if 'CFF ' in tables: CFF_elements = ['version', 'Notice', 'Copyright', 'FullName', 'FamilyName', 'Weight'] tt_font = TTFont() tt_font.importXML(ttx_path) font_dict = tt_font['CFF '].cff.topDictIndex.items[0] for element in CFF_elements: text = getattr(font_dict, element) if text.find(find_string) != -1: new_text = text.replace(find_string, replace_string) setattr(font_dict, element, new_text) count += 1 tt_font.saveXML(ttx_path) # done return count
def __zero_cmaps(self,output): font = TTFont(output) old_cmap_method = change_method(_c_m_a_p.table__c_m_a_p, _decompile_in_table_cmap,'decompile') old_12_method = change_method(_c_m_a_p.cmap_format_12_or_13,_decompile_in_cmap_format_12_13, 'decompile') old_4_method = change_method(_c_m_a_p.cmap_format_4,_decompile_in_cmap_format_4, 'decompile') cmap_offset = font.reader.tables['cmap'].offset cmapTables = font['cmap'] cmap12 = cmapTables.getcmap(3, 10) #format 12 cmap4 = cmapTables.getcmap(3, 1) #format 4 ranges_to_zero = [] if cmap12: ranges_to_zero.append((cmap_offset+cmap12.offset+16,cmap12.length-16)) #if cmap4: # ranges_to_zero.append((cmap_offset+cmap4.offset+14,cmap4.length-14)) change_method(_c_m_a_p.cmap_format_12_or_13,old_12_method,'decompile') change_method(_c_m_a_p.cmap_format_4,old_4_method,'decompile') change_method(_c_m_a_p.table__c_m_a_p,old_cmap_method,'decompile') font.close() #if len(ranges_to_zero)<2: #return if both doesn't exist # return for block in ranges_to_zero: filler = Filler(output) filler.fill(block[0], block[1], '\x00') filler.close()
def __zero_charset_fmt(self,output): font = TTFont(output) cffTableOffset = font.reader.tables['CFF '].offset cffTable = font['CFF '].cff assert len(cffTable.fontNames) == 1 #only one font should be present charsetOffset = cffTable[cffTable.fontNames[0]].rawDict['charset'] numGlyphs = font['maxp'].numGlyphs inner_file = font.reader.file inner_file.seek(cffTableOffset+charsetOffset) format = readCard8(inner_file); if format != 2 and format != 1: return None record_len = 4 if format == 2 else 3 seenGlyphCount = 0 size = 0 while seenGlyphCount < numGlyphs: inner_file.seek(2,io.SEEK_CUR) if format == 2: nLeft = readCard16(inner_file) else: #format 1 nLeft = readCard8(inner_file) seenGlyphCount += nLeft + 1 size += 1 font.close() filler = Filler(output) filler.fill(cffTableOffset+charsetOffset+1, record_len*size, '\x00') filler.close()
def process(infile, outfile, layer): font = TTFont(infile) glyf = font["glyf"] glyphNamesToKeep = [] if layer == "letters": for glyphName in font.getGlyphNames(): if glyphName not in _LAYER2_GLYPHS and glyphName not in _LAYER3_GLYPHS: glyphNamesToKeep.append(glyphName) elif layer == "diacritics": glyphNamesToKeep = _LAYER2_GLYPHS elif layer == "quranic-signs": glyphNamesToKeep = _LAYER3_GLYPHS for glyphName in font.getGlyphNames(): if glyphName not in glyphNamesToKeep: glyph = glyf[glyphName] if glyphName in ("uni0670.medi", "uni06E5.medi", "uni06E6.medi") and layer == "letters": # We want to keep the kashida part of those glyphs. components = [] for component in glyph.components: if component.glyphName != glyphName.split(".")[0]: components.append(component) glyph.components = components else: # This will cause FontTools not to output any outlines for that # glyph. glyph.numberOfContours = 0 font.save(outfile)
def __init__(self, sourcePath, location, DEBUG=False): self.instancePath = tempfile.mkstemp()[1] tempPath = sourcePath.replace('.ttf', '-instance.ttf') cmds = ['python', pathToFontToolsMutator, sourcePath] for k, v in location.items(): cmds.append('%s=%s' %(k, v)) proc = subprocess.Popen(cmds, stdout=subprocess.PIPE) out = proc.communicate()[0] if DEBUG: print '---' print ' '.join(cmds) print '---' print out myUUID = str(uuid.uuid4()).replace('-', '') f = TTFont(tempPath) self.fontName = 'VF'+str(myUUID) f['name'].setName(self.fontName, 6, 1, 0, 0) # Macintosh f['name'].setName(self.fontName, 6, 3, 1, 0x409) # Windows os.remove(tempPath) f.save(self.instancePath)
def load_ttx(ttx): f = UnicodeIO() f.write(ttx) f.seek(0) font = TTFont() font.importXML(f) return font
def test_max_ctx_calc_features_ttx(file_name, max_context): ttx_path = os.path.join(os.path.dirname(__file__), 'data', '{}.ttx'.format(file_name)) font = TTFont() font.importXML(ttx_path) assert maxCtxFont(font) == max_context
def compile_font(self, path, suffix, temp_dir): ttx_filename = os.path.basename(path) savepath = os.path.join(temp_dir, ttx_filename.replace('.ttx', suffix)) font = TTFont(recalcBBoxes=False, recalcTimestamp=False) font.importXML(path) font.save(savepath, reorderTables=None) return font, savepath
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)
class TestClosureTaker(unittest.TestCase): def setUp(self): """Loads font and initializes ClosureTaker """ self.font = TTFont('test_data/NotoSans-Regular_subset.ttf') self.closureTaker = ClosureTaker(self.font) def test_c(self): """Takes closure of character 'c' Expected result is array: [3] """ self.closureTaker.clear() self.closureTaker.add_glyph_names(['c']) gids = self.closureTaker.closure() self.assertTrue(gids == [3], 'Closure of c is [c]') def test_clear(self): """Takes closure of cleared input lists Expected result is array: [] """ self.closureTaker.clear() gids = self.closureTaker.closure() self.assertTrue(gids == [], 'Closure of empty is []') def tearDown(self): """Closes font """ self.font.close()
def main(args): #assume input is .ttx file #TODO:input is .ttf file input = args[0] ttf = TTFont() ttf.importXML(input,quiet=True) bytecode_font = BytecodeFont() constructCVTTable(ttf['cvt '].values) # TODO:for now just analyze the font program file, later # should add the prep and all the glyphs body = Body(bytecode_font, 'fpgm', ttf) constructInstructions(body) constructSuccessor(body) constructPredecessor(body) current_state = Environment() executor = AbstractExecutor(bytecode_font) instruction = body.instructions[0] while instruction in body.successors: executor.execute(instruction, current_state) instruction.prettyPrint() instruction = body.successors[instruction][0] for key,value in bytecode_font.function_table.items(): print('Function #{0}:'.format(key)) for instruction in value: instruction.prettyPrint()
class MyFontTools(object): def __init__(self, fontfile): self.font = TTFont(fontfile) def cmap_format_gbk2utf8(self): cmap = self.font['cmap'] outtables = [] for table in cmap.tables: if table.format in [4, 12, 13, 14]: outtables.append(table) # Convert ot format4 if table.getEncoding() in SUPPORT_CONVERT_FROM_ENCODE: for gbk_code in table.cmap.keys(): uni_code= convert_from_gbk(gbk_code) if gbk_code != uni_code: table.cmap[uni_code] = table.cmap.pop(gbk_code) newtable = CmapSubtable.newSubtable(4) newtable.platformID = self.to_platformID newtable.platEncID = self.to_platEncID newtable.language = table.language newtable.cmap = table.cmap outtables.append(newtable) cmap.tables = outtables def vhea_fixed(self): self.font['vhea'].tableVersion=1.0 def save(self, outfile): import pdb;pdb.set_trace() self.font.save(outfile)
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 import_from_otf(self, otfpath): """Import color data (CPAL/COLR) from a font file. otfpath: Path of the font file""" font = TTFont(otfpath) if not (font.has_key("COLR") and font.has_key("CPAL")): print("ERROR: No COLR and CPAL table present in %s" % otfpath) else: print("Reading palette data ...") cpal = table_C_P_A_L_("CPAL") cpal.decompile(font["CPAL"].data, font) for j in range(len(cpal.palettes)): palette = cpal.palettes[j] _palette = {} for i in range(len(palette)): color = palette[i] _palette[str(i)] = "#%02x%02x%02x%02x" % ( color.red, color.green, color.blue, color.alpha ) self.palettes.append(_palette) colr = table_C_O_L_R_("COLR") colr.decompile(font["COLR"].data, font) print("Reading layer data ...") for glyphname in colr.ColorLayers: layers = colr[glyphname] _glyph = ColorGlyph(self) _glyph.basename = glyphname for layer in layers: _glyph.layers.append(layer.name) _glyph.colors.append(layer.colorID) self[glyphname] = _glyph print("Done.") font.close()
def installFont(self, path): """ Install a font with a given path and the postscript font name will be returned. The postscript font name can be used to set the font as the active font. Fonts are installed only for the current process. Fonts will not be accesible outside the scope of drawBot. All installed fonts will automatically be uninstalled when the script is done. .. showcode:: /../examples/installFont.py """ success, error = self._dummyContext.installFont(path) self._installedFontPaths.add(path) self._addInstruction("installFont", path) from fontTools.ttLib import TTFont, TTLibError try: font = TTFont(path) psName = font["name"].getName(6, 1, 0) if psName is None: psName = font["name"].getName(6, 3, 1) font.close() except IOError: raise DrawBotError("Font '%s' does not exist." % path) except TTLibError: raise DrawBotError("Font '%s' is not a valid font." % path) if psName is not None: psName = psName.toUnicode() if not success: warnings.warn("install font: %s" % error) return psName
def main(args=None): configLogger(logger=log) parser = argparse.ArgumentParser() parser.add_argument("input", nargs='+', metavar="INPUT") parser.add_argument("-o", "--output") parser.add_argument("-e", "--max-error", type=float, default=MAX_ERR) parser.add_argument("--post-format", type=float, default=POST_FORMAT) parser.add_argument( "--keep-direction", dest='reverse_direction', action='store_false') parser.add_argument("--face-index", type=int, default=0) parser.add_argument("--overwrite", action='store_true') options = parser.parse_args(args) if options.output and len(options.input) > 1: if not os.path.isdir(options.output): parser.error("-o/--output option must be a directory when " "processing multiple fonts") for path in options.input: if options.output and not os.path.isdir(options.output): output = options.output else: output = makeOutputFileName(path, outputDir=options.output, extension='.ttf', overWrite=options.overwrite) font = TTFont(path, fontNumber=options.face_index) otf_to_ttf(font, post_format=options.post_format, max_err=options.max_error, reverse_direction=options.reverse_direction) font.save(output)
def main(args=None): if args is None: args = sys.argv[1:] if len(args) < 2: print("usage: merge_woff_metadata.py METADATA.xml " "INPUT.woff [OUTPUT.woff]", file=sys.stderr) return 1 metadata_file = args[0] with open(metadata_file, 'rb') as f: metadata = f.read() infile = args[1] if len(args) > 2: outfile = args[2] else: filename, ext = os.path.splitext(infile) outfile = makeOutputFileName(filename, None, ext) font = TTFont(infile) if font.flavor not in ("woff", "woff2"): print("Input file is not a WOFF or WOFF2 font", file=sys.stderr) return 1 data = font.flavorData # this sets the new WOFF metadata data.metaData = metadata font.save(outfile)
def saveFont(glyphs, outputFileName, asXML, pixelSize, descent, fontName, copyrightYear, creator, version): f = TTFont() vectorizedGlyphs = {glyph : [vectorizeGlyph(glyphs[glyph][0], pixelSize, descent), glyphs[glyph][1]] for glyph in glyphs} unicodes = [code for glyph in glyphs for code in glyphs[glyph][1]] # Populate basic tables (there are a few dependencies so order matters) makeTable_glyf(f, vectorizedGlyphs) makeTable_maxp(f) makeTable_loca(f) makeTable_head(f) makeTable_hmtx(f) makeTable_hhea(f, pixelSize, descent) makeTable_OS2(f, pixelSize, descent, min(unicodes), max(unicodes)) makeTable_cmap(f, glyphs) makeTable_name(f, fontName, "Regular", copyrightYear, creator, version) makeTable_post(f, pixelSize, descent) if asXML: # We have to compile the TTFont manually when saving as TTX # (to auto-calculate stuff here and there) f["glyf"].compile(f) f["maxp"].compile(f) f["loca"].compile(f) f["head"].compile(f) f["hmtx"].compile(f) f["hhea"].compile(f) f["OS/2"].compile(f) f["cmap"].compile(f) f["name"].compile(f) f["post"].compile(f) print "PLEASE NOTE: When exporting directly to XML, the checkSumAdjustment value in the head table will be 0." f.saveXML(outputFileName) else: f.save(outputFileName)
def run(self): font = TTFont(self.in_font) gs = font.getGlyphSet() glyf = font["glyf"] hmtx = font["hmtx"] for gname, (adw, lsb) in hmtx.metrics.items(): # https://github.com/fonttools/fonttools/blob/master/Lib/fontTools/ttLib/tables/_g_l_y_f.py#L189-L192 # __getitem__ internally calls table__g_l_y_f.expand which is essential to obtain xMin g = glyf[gname] # obtain recalculated xMin from glyph's control bounds g.recalcBounds(glyf) hmtx.metrics[gname] = (adw, g.xMin) if self.update_vmtx: from fontTools.pens.boundsPen import BoundsPen vmtx = font["vmtx"] for gname, (adh, tsb) in vmtx.metrics.items(): g = glyf[gname] # obtain yMax g.recalcBounds(glyf) pen = BoundsPen(gs) g.draw(pen, glyf) if pen.bounds is None: continue left, bottom, right, top = pen.bounds vmtx.metrics[gname] = (adh, top + tsb - g.yMax) font.save(self.out_font) return 0
def build(args): font, features = merge(args) build_encoded(font, features) with tempfile.NamedTemporaryFile(mode="r", suffix=args.out_file) as tmp: font.generate(tmp.name, flags=["round", "opentype"]) ttfont = TTFont(tmp.name) try: builder.addOpenTypeFeatures(ttfont, features) except: with tempfile.NamedTemporaryFile(mode="w+", delete=False) as tmp: tmp.write(features.asFea()) print("Failed! Inspect temporary file: %r" % tmp.name) raise # Filter-out useless Macintosh names ttfont["name"].names = [n for n in ttfont["name"].names if n.platformID != 1] # https://github.com/fontforge/fontforge/pull/3235 # fontDirectionHint is deprecated and must be set to 2 ttfont["head"].fontDirectionHint = 2 # unset bits 6..10 ttfont["head"].flags &= ~0x7e0 # Drop useless table with timestamp if "FFTM" in ttfont: del ttfont["FFTM"] ttfont.save(args.out_file)
def _verifyOutput(outPath): f = TTFont(outPath) f.saveXML(outPath + ".ttx") with open(outPath + ".ttx") as f: testData = strip_VariableItems(f.read()) refData = strip_VariableItems(getTestData(os.path.basename(outPath) + ".ttx")) assert refData == testData
#!/usr/bin/env python from itertools import chain import sys from fontTools.ttLib import TTFont from fontTools.unicode import Unicode ttf = TTFont(sys.argv[1]) chars = [] for cmap in ttf['cmap'].tables: if cmap.isUnicode(): for item in cmap.cmap: chars.append(chr(item)) chars_unique = [] for char in chars: if char not in chars_unique: chars_unique.append(char) with open('glyphs.txt', 'w', encoding='utf8') as f: for char in chars_unique: f.write("%s " % char) # Use this to check if font contains the codepoint given as second argument: #char = int(sys.argv[2], 0) #print(Unicode[char]) ttf.close()
#!/usr/bin/env python import sys from fontTools.ttLib import TTFont ttf = TTFont(sys.argv[1]) cmap = ttf.getBestCmap() chars = [format(y[0], 'x') for y in cmap.items()] print(','.join(f'U+{char}' for char in chars)) ttf.close()
# -*- coding: utf-8 -*- from fontTools.ttLib import TTFont # 导包 font = TTFont('base_font.woff') # 打开文件 gly_list = font.getGlyphOrder() # 获取 GlyphOrder 字段的值 for gly in gly_list: # 前两个值不是我们要的,切片去掉 print(gly) font.saveXML('./1.xml')
def save_otfs(self, ufos, ttf=False, is_instance=False, interpolatable=False, use_afdko=False, autohint=None, subset=None, use_production_names=None, subroutinize=False, interpolate_layout_from=None, kern_writer_class=None, mark_writer_class=None): """Build OpenType binaries from UFOs. Args: ufos: Font objects to compile. ttf: If True, build fonts with TrueType outlines and .ttf extension. is_instance: If output fonts are instances, for generating paths. interpolatable: If output is interpolatable, for generating paths. use_afdko: If True, use AFDKO to compile feature source. autohint: Parameters to provide to ttfautohint. If not provided, the autohinting step is skipped. subset: Whether to subset the output according to data in the UFOs. If not provided, also determined by flags in the UFOs. use_production_names: Whether to use production glyph names in the output. If not provided, determined by flags in the UFOs. subroutinize: If True, subroutinize CFF outlines in output. interpolate_layout_from: A designspace path to give varLib for interpolating layout tables to use in output. kern_writer_class: Class overriding ufo2ft's KernFeatureWriter. mark_writer_class: Class overriding ufo2ft's MarkFeatureWriter. """ ext = 'ttf' if ttf else 'otf' fea_compiler = FDKFeatureCompiler if use_afdko else FeatureCompiler if kern_writer_class is None: kern_writer_class = KernFeatureWriter else: logger.info("Using %r", kern_writer_class.__module__) if mark_writer_class is None: mark_writer_class = MarkFeatureWriter else: logger.info("Using %r", mark_writer_class.__module__) if interpolate_layout_from is not None: master_locations, instance_locations = self._designspace_locations( interpolate_layout_from) ufod = self._output_dir('ufo', False, interpolatable) otfd = self._output_dir(ext, False, interpolatable) finder = lambda s: s.replace(ufod, otfd).replace('.ufo', '.' + ext) for ufo in ufos: name = self._font_name(ufo) logger.info('Saving %s for %s' % (ext.upper(), name)) otf_path = self._output_path(ufo, ext, is_instance, interpolatable) if use_production_names is None: use_production_names = not ufo.lib.get( GLYPHS_PREFIX + "Don't use Production Names") compiler_options = dict(featureCompilerClass=fea_compiler, kernWriterClass=kern_writer_class, markWriterClass=mark_writer_class, glyphOrder=ufo.lib.get(PUBLIC_PREFIX + 'glyphOrder'), useProductionNames=use_production_names) if ttf: font = compileTTF(ufo, convertCubics=False, **compiler_options) else: font = compileOTF(ufo, optimizeCFF=subroutinize, **compiler_options) if interpolate_layout_from is not None: loc = instance_locations[ufo.path] gpos_src = interpolate_layout(interpolate_layout_from, loc, finder, mapped=True) font['GPOS'] = gpos_src['GPOS'] gsub_src = TTFont( finder(self._closest_location(master_locations, loc))) font['GDEF'] = gsub_src['GDEF'] font['GSUB'] = gsub_src['GSUB'] font.save(otf_path) if subset is None: export_key = GLYPHS_PREFIX + 'Glyphs.Export' subset = ((GLYPHS_PREFIX + 'Keep Glyphs') in ufo.lib or any( glyph.lib.get(export_key, True) is False for glyph in ufo)) if subset: self.subset_otf_from_ufo(otf_path, ufo) if ttf and autohint is not None: hinted_otf_path = self._output_path(ufo, ext, is_instance, interpolatable, autohinted=True) ttfautohint(otf_path, hinted_otf_path, args=autohint)
def _generate_ttx_dump(font_path, tables=None): font = TTFont(font_path) temp_path = _get_temp_file_path() font.saveXML(temp_path, tables=tables) return temp_path
def Main(): input_file, output_file, demo_file = sys.argv[1:] font = TTFont(input_file, lazy=False) # Rename the font. for name in font["name"].names: new_name = (name.toUnicode().replace("Fira Code", "Fira Emacs").replace( "FiraCode", "FiraEmacs")) font["name"].setName(new_name, name.nameID, name.platformID, name.platEncID, name.langID) # Get the mapping from code points to glyph names. cmap = font.getBestCmap() # Reverse the mapping. inv_cmap = {name: chr(code) for code, name in cmap.items()} # Get the list of all glyph names. glyph_order = font.getGlyphOrder() # Convert the font to XML, because I can't figure out how to make the necessary # updates using the fontTools API. sio = io.StringIO() font.saveXML(sio) root = ET.fromstring(sio.getvalue()) del sio # This list will be populated with tuples of (glyph name, code point, input string) # representing how strings are converted to ligatures and which code point the # ligature glyph is assigned to. output_glyphs = [] # Look at every glyph, picking out the ones that look like ligatures. for glyph in glyph_order: input_string = None m = re.match(r"([^.]+)\.(.*)$", glyph) if m: # We assume the name of a ligature glyph is formed from the names of # the component glyphs separated by underscores. parts = m[1].split("_") if all(p in inv_cmap for p in parts): input_string = "".join(inv_cmap[p] for p in parts) output_glyphs.append( Glyph(name=glyph, input_string=input_string, suffix=m[2])) output_glyphs.sort(key=lambda g: (g.input_string, g.suffix)) # Start with 0xE100 instead of 0xE000 because for some reason the original # font already assigns some glyphs in the private-use area. code_point = 0xE100 for g in output_glyphs: g.code_point = code_point code_point += 1 # Echo the list of strings which can be pasted into fira-code.el to update it. fill_column = 70 prefix = "" with open("fira-code-data.el", "wt") as data_stream: data_stream.write( ";; -*- coding: utf-8 -*-\n(defconst fira-code--data\n '(") for g in output_glyphs: line = prefix + '[{} {} "\\{}"] ; {}'.format( ElispLiteral(g.name), ElispLiteral(g.input_string), hex(g.code_point)[1:], chr(g.code_point), ) prefix = " " data_stream.write(line + "\n") data_stream.write("))\n") # Add extra output_glyphs from code points to glyphs in the relevant cmap variants. for fmt in ["12", "4"]: for table in root.find("cmap").findall("cmap_format_" + fmt): for g in output_glyphs: ET.SubElement(table, "map", dict(code=hex(g.code_point), name=g.name)) # Serialize the XML data and convert it to a font file. bio = io.BytesIO() ET.ElementTree(root).write(bio) font = TTFont() bio.seek(0) font.importXML(bio) font.save(output_file)
from itertools import chain # import numpy as np import sys from fontTools.ttLib import TTFont from fontTools.unicode import Unicode ttf = TTFont('db_hindi_mast_4.ttf') chars = chain.from_iterable([y + (Unicode[y[0]], ) for y in x.cmap.items()] for x in ttf["cmap"].tables) chars1 = (list(chars)) for i in range(len(chars1)): print(chars1[i]) # Use this for just checking if the font contains the codepoint given as # second argument: #char = int(sys.argv[2], 0) #print(Unicode[char]) #print(char in (x[0] for x in chars)) # array = np.array(chars1) # print(array) ttf.close()
def check_ttx_dump(self, font, expected_ttx, tables, suffix): """Ensure the TTX dump is the same after saving and reloading the font.""" path = self.temp_path(suffix=suffix) font.save(path) self.expect_ttx(TTFont(path), expected_ttx, tables)
'//div[@xname="content"]//div[@class="tz-paragraph"]//text()') content_str = ''.join(content_list) print(content_str) # 获取字体的连接文件 fonts_ = re.search(r",url\('(//.*\.ttf)?'\) format", response_html).group(1) # 请求字体文件, 字体文件是动态更新的 fonts_url = 'https:' + fonts_ response = requests.get(fonts_url, headers=headers).content # 讲字体文件保存到本地 with open('fonts.ttf', 'wb') as f: f.write(response) # 解析字体库 font = TTFont('fonts.ttf') # 读取字体的映射关系 uni_list = font['cmap'].tables[0].ttFont.getGlyphOrder() # 转换格式 utf_list = [eval(r"u'\u" + x[3:] + "'") for x in uni_list[1:]] # 被替换的字体的列表 word_list = [ u'一', u'七', u'三', u'上', u'下', u'不', u'九', u'了', u'二', u'五', u'低', u'八', u'六', u'十', u'的', u'着', u'近', u'远', u'长', u'右', u'呢', u'和', u'四', u'地', u'坏', u'多', u'大', u'好', u'小', u'少', u'短', u'矮', u'高', u'左', u'很', u'得', u'是', u'更' ] #遍历需要被替换的字符
def make_dict(): font = TTFont('58.ttf') b = font['cmap'].tables[2].ttFont.getReverseGlyphMap() # 编码对应的数字 c = font['cmap'].tables[2].ttFont.tables['cmap'].tables[ 1].cmap # 页面的十六进制数对应的编码 return b, c
async def fontTest(letter): test = TTFont("resources/Roboto-Medium.ttf") for table in test['cmap'].tables: if ord(letter) in table.cmap.keys(): return True
def var_font(): """VF family consisting of a single font with two axes, wdth, wght""" return TTFont(os.path.join(TEST_DATA, "Inconsolata[wdth,wght].ttf"))
def get_font(): #这是标签为shopNum和address的字体文件 font = TTFont('51fbf957.woff') #获取所有字体的上面对应的编码 font_names = font.getGlyphOrder() #下面这些就是打开FontCreator软件后解析 人均多少 那个字体文件得到 所有文字和数字,共603个: texts = ['','','1','2','3','4','5','6','7','8', '9','0','店','中','美','家','馆','小','车','大', '市','公','酒','行','国','品','发','电','金','心', '业','商','司','超','生','装','园','场','食','有', '新','限','天','面','工','服','海','华','水','房', '饰','城','乐','汽','香','部','利','子','老','艺', '花','专','东','肉','菜','学','福','饭','人','百', '餐','茶','务','通','味','所','山','区','门','药', '银','农','龙','停','尚','安','广','鑫','一','容', '动','南','具','源','兴','鲜','记','时','机','烤', '文','康','信','果','阳','理','锅','宝','达','地', '儿','衣','特','产','西','批','坊','州','牛','佳', '化','五','米','修','爱','北','养','卖','建','材', '三','会','鸡','室','红','站','德','王','光','名', '丽','油','院','堂','烧','江','社','合','星','货', '型','村','自','科','快','便','日','民','营','和', '活','童','明','器','烟','育','宾','精','屋','经', '居','庄','石','顺','林','尔','县','手','厅','销', '用','好','客','火','雅','盛','体','旅','之','鞋', '辣','作','粉','包','楼','校','鱼','平','彩','上', '吧','保','永','万','物','教','吃','设','医','正', '造','丰','健','点','汤','网','庆','技','斯','洗', '料','配','汇','木','缘','加','麻','联','卫','川', '泰','色','世','方','寓','风','幼','羊','烫','来', '高','厂','兰','阿','贝','皮','全','女','拉','成', '云','维','贸','道','术','运','都','口','博','河', '瑞','宏','京','际','路','祥','青','镇','厨','培', '力','惠','连','马','鸿','钢','训','影','甲','助', '窗','布','富','牌','头','四','多','妆','吉','苑', '沙','恒','隆','春','干','饼','氏','里','二','管', '诚','制','售','嘉','长','轩','杂','副','清','计', '黄','讯','太','鸭','号','街','交','与','叉','附', '近','层','旁','对','巷','栋','环','省','桥','湖', '段','乡','厦','府','铺','内','侧','元','购','前', '幢','滨','处','向','座','下','県','凤','港','开', '关','景','泉','塘','放','昌','线','湾','政','步', '宁','解','白','田','町','溪','十','八','古','双', '胜','本','单','同','九','迎','第','台','玉','锦', '底','后','七','斜','期','武','岭','松','角','纪', '朝','峰','六','振','珠','局','岗','洲','横','边', '济','井','办','汉','代','临','弄','团','外','塔', '杨','铁','浦','字','年','岛','陵','原','梅','进', '荣','友','虹','央','桂','沿','事','津','凯','莲', '丁','秀','柳','集','紫','旗','张','谷','的','是', '不','了','很','还','个','也','这','我','就','在', '以','可','到','错','没','去','过','感','次','要', '比','觉','看','得','说','常','真','们','但','最', '喜','哈','么','别','位','能','较','境','非','为', '欢','然','他','挺','着','价','那','意','种','想', '出','员','两','推','做','排','实','分','间','甜', '度','起','满','给','热','完','格','荐','喝','等', '其','再','几','只','现','朋','候','样','直','而', '买','于','般','豆','量','选','奶','打','每','评', '少','算','又','因','情','找','些','份','置','适', '什','蛋','师','气','你','姐','棒','试','总','定', '啊','足','级','整','带','虾','如','态','且','尝', '主','话','强','当','更','板','知','己','无','酸', '让','入','啦','式','笑','赞','片','酱','差','像', '提','队','走','嫩','才','刚','午','接','重','串', '回','晚','微','周','值','费','性','桌','拍','跟', '块','调','糕' ] #然后我们创建一个font_name的字典,用来装字体编码和 --> 所对应的数字、汉字 font_name = {} for index, value in enumerate(texts): #这就是大众点评乱码的结构:由前面的三个字符‘&#x’+后面是小写字母和数字+分号‘;’ n = font_names[index].replace('uni', '&#x').lower() + ';' #下面生成的这个就是乱码,他就对应刚刚字体文件的对应数字,可以运行看看 font_name[n] = value #此刻得到的就是解码的字典 return font_name
class Font: """ Storage of font information while composing the pages. >>> from pagebot.fonttoolbox.objects.font import findFont >>> f = findFont('RobotoDelta-VF') >>> sorted(f.axes.keys()) ['GRAD', 'POPS', 'PWDT', 'PWGT', 'UDLN', 'XOPQ', 'XTRA', 'YOPQ', 'YTAD', 'YTAS', 'YTDD', 'YTDE', 'YTLC', 'YTRA', 'YTUC', 'opsz', 'wdth', 'wght'] >>> f.info.familyName 'RobotoDelta' >>> len(f) 188 >>> f.axes['opsz'] (8.0, 12.0, 144.0) >>> variables = f.variables >>> features = f.features >>> f.groups >>> f.designSpace {} """ GLYPH_CLASS = Glyph FONTANALYZER_CLASS = FontAnalyzer def __init__(self, path=None, ttFont=None, name=None, opticalSize=None, location=None, styleName=None, lazy=True): """Initialize the TTFont, for which Font is a wrapper. self.name is supported, in case the caller wants to use a different >>> f = Font() >>> f <Font Untitled> """ if path is None and ttFont is None: self.ttFont = TTFont() self.path = '%d' % id( ttFont) # In case no path, use unique id instead. elif ttFont is None and path is not None: self.ttFont = TTFont(path, lazy=lazy) self.path = path # File path of the existing font file. elif path is None: self.ttFont = ttFont self.path = '%d' % id( ttFont) # In case no path, use unique id instead. else: # ttFont is not None: There is ttFont data self.ttFont = ttFont self.path = path # Store location, incase this was a created VF instance self.location = location # TTFont is available as lazy style.info.font self.info = FontInfo(self.ttFont) self.info.opticalSize = opticalSize # Optional optical size, to indicate where this Variable Font is rendered for. self.info.location = location # Store origina location of this instance of the font is derived from a Variable Font. # Stores optional custom name, otherwise use original DrawBot name. # Otherwise use from FontInfo.fullName self.name = name or self.info.fullName if styleName is not None: self.info.styleName = styleName # Overwrite default style name in the ttFont or Variable Font location self._kerning = None # Lazy reading. self._groups = None # Lazy reading. self._glyphs = {} # Lazy creation of self[glyphName] self._analyzer = None # Lazy creation. self._variables = None # Lazy creations of delta's dictionary per glyph per axis def __repr__(self): """ >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> path = getTestFontsPath() + '/google/roboto/Roboto-Black.ttf' # We know this exists in the PageBot repository >>> font = getFont(path) >>> str(font) '<Font Roboto-Black>' """ return '<Font %s>' % (path2FontName(self.path) or self.name or 'Untitled') def __getitem__(self, glyphName): """Answer the glyph with glyphName. >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> path = getTestFontsPath() + '/google/roboto/Roboto-Black.ttf' # We know this exists in the PageBot repository >>> font = getFont(path) >>> g = font['A'] >>> g.name, g.width ('A', 1395) """ if not glyphName in self._glyphs: self._glyphs[glyphName] = self.GLYPH_CLASS(self, glyphName) return self._glyphs[glyphName] def __len__(self): """Answer the number of glyphs in the font. >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> path = getTestFontsPath() + '/google/roboto/Roboto-Black.ttf' # We know this exists in the PageBot repository >>> font = getFont(path) >>> len(font) 3387 """ if 'glyf' in self.ttFont: return len(self.ttFont['glyf']) return 0 def nameMatch(self, pattern): """Answer level of matching between pattern and the font file name or font.info.fullName. Pattern can be a single string or a list of string. >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> path = getTestFontsPath() + '/google/roboto/Roboto-Black.ttf' # We know this exists in the PageBot repository >>> font = getFont(path) >>> font.nameMatch('Black') 1.0 >>> font.nameMatch('Blackish') 0 >>> font.nameMatch(('Roboto', 'Black')) 1.0 """ fontName = path2FontName(self.path) if not isinstance(pattern, (list, tuple)): pattern = [pattern] for part in pattern: if not (part in fontName or part in self.info.fullName): return 0 return 1.0 def weightMatch(self, weight): """Answer level of matching for the (abbreviated) weight name or number with font, in a value between 0 and 1. Currently there is only no-match (0) and full-match (1). Future implementations may give a float indicator for the level of matching, so the caller can decide on the level of threshold. >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> path = getTestFontsPath() + '/google/roboto/Roboto-Black.ttf' # We know this exists in the PageBot repository >>> font = getFont(path) >>> font.info.weightClass 900 >>> font.weightMatch(0) # Bad match 0 >>> font.weightMatch(800) # Bad match 0 >>> font.weightMatch(900) # Exact match 0 >>> font.weightMatch(0) # Bad match - 0 >>> font.weightMatch('Black') # Black --> Exact match on 900 1.0 >>> font.weightMatch('Light') # Light --> No match on 900 0 >>> path = getTestFontsPath() + '/google/roboto/Roboto-Regular.ttf' # We know this exists in the PageBot repository >>> font = getFont(path) >>> font.info.weightClass 400 """ """ TODO: Fix these tests >>> font.weightMatch(400) # Match 1.0 >>> font.weightMatch('Regular') # Match 1.0 >>> font.weightMatch('Condensed') # Matching with width name has no match. 0 """ if isinstance(weight, (float, int)): # Comparing by numbers # Compare the weight as number as max difference to what we already have. w = self.info.weightClass if w in FONT_WEIGHT_MATCHES.get(weight, []): return 1.0 # Exact match else: # Comparing by string fileName = path2FontName(self.path) for w in FONT_WEIGHT_MATCHES.get(weight, []): if not isinstance(w, (float, int)) and (w in fileName or w in self.info.styleName): return 1.0 # Exacly match return 0 # No match def widthMatch(self, width): """Answer level of matchting for the (abbreviated) width name or number with font. Currently there is only no-match (0) and full-match (1). Future implementations may give a float indicator for the level of matching, so the caller can decide on the level of threshold. >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> path = getTestFontsPath() + '/google/roboto/Roboto-Black.ttf' # We know this exists in the PageBot repository >>> font = getFont(path) >>> font.info.widthClass 5 >>> font.widthMatch(0) # Bad match 0 >>> font.widthMatch(4) # Close match fails 0 >>> font.widthMatch(5) # Exact match 1.0 >>> font.widthMatch(6) # Close match fails 0 >>> font.widthMatch(10) # Bad match 0 >>> path = getTestFontsPath() + '/google/roboto/Roboto-Bold.ttf' # We know this exists in the PageBot repository >>> font = Font(path) >>> font.info.widthClass 5 >>> font.widthMatch(5) # Wrong exact match --> 1000 due to wrong font.info.widthClass 1.0 >>> font.widthMatch('Wide') # No match on "Wide" 0 >>> #font.widthMatch('Cond') # Exact match on "Cond" 1.0 """ if isinstance(width, (float, int)): # Compare the width as number as max difference to what we already have. w = self.info.widthClass if w <= 100: # Normalize to 1000 w *= 100 if w in FONT_WIDTH_MATCHES.get(width, []): return 1.0 else: # Comparing by string fileName = path2FontName(self.path) for w in FONT_WIDTH_MATCHES.get(width, []): if not isinstance(w, (float, int)) and (w in fileName or w in self.info.styleName): return 1.0 return 0 def isItalic(self): """Answer the boolean flag if this font should be considered to be italic. Currently there is only no-match (0) and full-match (1). Future implementations may give a float indicator for the level of matching, so the caller can decide on the level of threshold. >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> fontPath = getTestFontsPath() >>> path = getTestFontsPath() + '/google/roboto/Roboto-BlackItalic.ttf' # We know this exists in the PageBot repository >>> font = getFont(path) >>> font.isItalic() 1 >>> path = getTestFontsPath() + '/google/roboto/Roboto-Bold.ttf' # We know this exists in the PageBot repository >>> font = Font(path) >>> font.isItalic() 0 """ if self.info.italicAngle: return 1 for altName in FONT_ITALIC_MATCHES.keys(): if altName in path2FontName( self.path) or altName in self.info.styleName: return 1.0 return 0 def match(self, name=None, weight=None, width=None, italic=None): """Answer a value between 0 and 1 to the amount that self matches the defined parameters. Only defined values count in the matching. >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> path = getTestFontsPath() + '/google/roboto/Roboto-Black.ttf' # We know this exists in the PageBot repository >>> font = getFont(path) >>> font.info.widthClass, font.info.weightClass (5, 900) >>> font.match(name='Roboto') 1.0 >>> font.match(name='Robo', weight='Black') 1.0 >>> font.match(name='Robo', weight='Light') # Only match on the name 0.5 >>> font.match(name='Robo', width=5) 1.0 """ """ TODO: Fix these tests >>> font.match(name='Robo', weight=900, width=5) 1.0 >>> font.match(name='Robo', weight=900, width=5, italic=True) 0.75 >>> font.match(name='Robo', weight='Black', width=5, italic=False) 1.0 >>> font.match(name='Robo', weight='Blackish', width=5, italic=False) 0.75 """ matches = [] fontName = path2FontName(self.path) if name is not None: matches.append(self.nameMatch(name)) # Currently the matches only answer 0 or 1. In future implementations this value may vary # as float between 0 and 1. if weight is not None: matches.append(self.weightMatch(weight)) if width is not None: matches.append(self.widthMatch(width)) if italic is not None: matches.append(italic == self.isItalic()) if not matches: return 0 # Avoif division by zero return sum(matches) / len(matches) # Normalize to value between 0..1 def keys(self): """Answer the glyph names of the font. >>> from pagebot.fonttoolbox.objects.font import Font >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> fontPath = getTestFontsPath() >>> path = fontPath + '/fontbureau/Amstelvar-Roman-VF.ttf' >>> f = getFont(path, lazy=False) >>> 'A' in f.keys() True """ if 'glyf' in self.ttFont: return self.ttFont['glyf'].keys() return [] def _get_cmap(self): """Answer the dictionary of sorted {unicode: glyphName, ...} in the font. >>> from pagebot.fonttoolbox.objects.font import Font >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> fontPath = getTestFontsPath() >>> path = fontPath + '/fontbureau/Amstelvar-Roman-VF.ttf' >>> f = getFont(path, lazy=False) >>> f.cmap[65] 'A' """ if 'cmap' in self.ttFont: return self.ttFont['cmap'].getBestCmap() return {} cmap = property(_get_cmap) def __contains__(self, glyphName): """Allow direct testing. >>> from pagebot.toolbox.transformer import * >>> from pagebot.fonttoolbox.objects.font import Font >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> fontPath = getTestFontsPath() >>> path = fontPath + '/fontbureau/Amstelvar-Roman-VF.ttf' >>> f = getFont(path, lazy=False) >>> 'A' in f True """ return glyphName in self.keys() def _get_analyzer(self): """Answer the style/font analyzer if it exists. Otherwise create one. >>> from pagebot.toolbox.transformer import * >>> from pagebot.fonttoolbox.objects.font import Font >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> fontPath = getTestFontsPath() >>> path = fontPath + '/fontbureau/Amstelvar-Roman-VF.ttf' >>> f = getFont(path, lazy=False) >>> #f.analyzer.stems # TODO: Needs bezier path for pixel test. """ if self._analyzer is None: self._analyzer = self.FONTANALYZER_CLASS(self) return self._analyzer analyzer = property(_get_analyzer) def _get_axes(self): """Answer dictionary of axes if self.ttFont is a Variable Font. Otherwise answer an empty dictioary. >>> from pagebot.toolbox.transformer import * >>> from pagebot.fonttoolbox.objects.font import findFont >>> f = findFont('Amstelvar-Roman-VF') >>> f.axes['opsz'] (0.0, 0.0, 1.0) """ try: # TODO: Change value to Axis dictionary instead of list axes = { a.axisTag: (a.minValue, a.defaultValue, a.maxValue) for a in self.ttFont['fvar'].axes } except KeyError: axes = {} # This is not a variable font. return axes axes = property(_get_axes) def getDefaultVarLocation(self): """Answer the location dictionary with the default axes values. >>> from pagebot.toolbox.transformer import * >>> from pagebot.fonttoolbox.objects.font import findFont >>> font = findFont('Amstelvar-Roman-VF') >>> len(font.getDefaultVarLocation().keys()) 1 """ defaultVarLocation = {} for axisName, axis in self.axes.items(): defaultVarLocation[axisName] = axis[1] return defaultVarLocation def _get_rawDeltas(self): """Answer the list of axis dictionaries with deltas for all glyphs and axes. Answer an empty dictionary if the [gvar] table does not exist. >>> from pagebot.fonttoolbox.objects.font import Font >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> fontPath = getTestFontsPath() >>> path = fontPath + '/fontbureau/Amstelvar-Roman-VF.ttf' >>> font = Font(path) >>> len(font.rawDeltas['A']) 0 """ try: return self.ttFont['gvar'].variations except: return {} rawDeltas = property(_get_rawDeltas) def _get_designSpace(self): """Answer the design space in case this is a variable font. >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> fontPath = getTestFontsPath() >>> path = fontPath + '/fontbureau/Amstelvar-Roman-VF.ttf' >>> font = Font(path) >>> font.designSpace # Basically the "cvar" table. {} """ try: designSpace = self.ttFont['cvar'] except KeyError: designSpace = {} return designSpace designSpace = property(_get_designSpace) def _get_variables(self): """Answer the gvar-table (if it exists) translated into plain Python dictionaries of deltas per glyph and per axis if this is a Var-fonts. Otherwise answer an empty dictionary """ """ TODO We need a "stable" var-font to test on. >>> from pagebot.fonttoolbox.objects.font import findFont >>> font = findFont('Amstelvar-Roman-VF') >>> len(font.variables) 592 >>> variables = font.variables['H'] >>> sorted(variables.keys()) [] >>> #['GRAD', 'XOPQ', 'XTRA', 'YOPQ', 'YTRA', 'YTSE', 'YTUC', 'opsz', 'wdth', 'wght'] >>> axis, deltas = variables['GRAD'] >>> axis {'GRAD': (0.0, 1.0, 1.0)} >>> deltas[:6] [(0, 0), None, (52, 0), None, None, (89, 0)] >>> font.variables.get('wrongglyphName') is None True """ if self._variables is None: try: gvar = self.ttFont[ 'gvar'] # Get the raw fonttools gvar table if it exists. self._variables = {} for glyphName, tupleVariations in gvar.variations.items(): self._variables[glyphName] = axisDeltas = {} for tupleVariation in tupleVariations: axisKey = '_'.join( tupleVariation.axes.keys() ) #{'GRAD': (0.0, 1.0, 1.0)} Make unique key, in case multiple axisDeltas[ axisKey] = tupleVariation.axes, tupleVariation.coordinates # ({'GRAD': (0.0, 1.0, 1.0)}, [(0, 0), None, (52, 0), None, None, (89, 0), ...]) except KeyError: pass # No gvar table, just answer the current self._variables as None. return self._variables variables = property(_get_variables) def getInstance(self, location=None, dstPath=None, opticalSize=None, styleName=None, cached=True, lazy=True, kerning=None): """Answer the instance of self at location. If the cache file already exists, then just answer a Font instance to that font file. >>> from pagebot.fonttoolbox.objects.font import findFont >>> f = findFont('RobotoDelta-VF') >>> sorted(f.axes.keys()) ['GRAD', 'POPS', 'PWDT', 'PWGT', 'UDLN', 'XOPQ', 'XTRA', 'YOPQ', 'YTAD', 'YTAS', 'YTDD', 'YTDE', 'YTLC', 'YTRA', 'YTUC', 'opsz', 'wdth', 'wght'] >>> f.name 'RobotoDelta Regular' >>> len(f) 188 >>> f.axes['wght'] (100.0, 400.0, 900.0) >>> g = f['H'] >>> g <PageBot Glyph H Pts:12/Cnt:1/Cmp:0> >>> g.points[6], g.width (APoint(1288,1456,On), 1458) >>> instance = f.getInstance(location=dict(wght=500)) >>> instance <Font RobotoDelta-VF-wght500> >>> ig = instance['H'] >>> ig <PageBot Glyph H Pts:12/Cnt:1/Cmp:0> >>> ig.points[6], ig.width (APoint(1307,1456,On), 1477) """ if location is None: location = self.getDefaultVarLocation() return getInstance(self.path, location=location, dstPath=dstPath, opticalSize=opticalSize, styleName=styleName, cached=cached, lazy=lazy, kerning=kerning) def _get_features(self): # TODO: Use TTFont for this instead. #return context.listOpenTypeFeatures(self.path) return {} features = property(_get_features) def _get_kerning(self): """Answer the (expanded) kerning table of the font. >>> from pagebot.toolbox.transformer import * >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> fontPath = getTestFontsPath() >>> path = fontPath + '/djr/bungee/Bungee-Regular.ttf' >>> f = getFont(path, lazy=False) >>> len(f.kerning) 22827 >>> f.kerning[('V','a')] -10 """ if self._kerning is None: # Lazy read. self._kerning = OTFKernReader(self.path).kerningPairs return self._kerning kerning = property(_get_kerning) def _get_groups(self): """Answer the groups dictionary of the font. >>> from pagebot.toolbox.transformer import * >>> from pagebot.fonttoolbox.objects.font import Font >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> fontPath = getTestFontsPath() >>> path = fontPath + '/djr/bungee/Bungee-Regular.ttf' >>> f = getFont(path, lazy=False) >>> g = f['A'] >>> f.groups is None True """ return self._groups groups = property(_get_groups) def save(self, path=None): """Save the font to optional path or to self.path.""" self.ttFont.save(path or self.path)
def get_font(url): response = requests.get(url) font = TTFont(BytesIO(response.content)) cmap = font.getBestCmap() font.close() return cmap
from fontTools.ttLib import TTFont zt1 = TTFont("zt01.woff") # wods列表中网页上按顺序打出来 words = ['B', '男', '王', '大', '专', 'M', '女', '吴', '硕', '赵', '黄', '李', '1', '8', '经', '2', '下', '本', '届', '5', '应', '科', '7', '中', '生', '6', 'E', '陈', '3', '以', '杨', 'A', '张', '4', '无', '0', '9', '验', '博', '技', '士', '校', '高', '刘', '周'] uni_list = zt1.getGlyphNames()[1:-1] data_map = dict() for index, i in enumerate(uni_list): temp = zt1["glyf"][i].coordinates x1, y1 = temp[0] x2, y2 = temp[1] new = (x2-x1, y2-y1) data_map[new] = words[index] print(data_map)
def main(args=None): if args is None: import sys args = sys.argv[1:] varfilename = args[0] locargs = args[1:] outfile = os.path.splitext(varfilename)[0] + '-instance.ttf' loc = {} for arg in locargs: tag, val = arg.split('=') assert len(tag) <= 4 loc[tag.ljust(4)] = float(val) print("Location:", loc) print("Loading variable font") varfont = TTFont(varfilename) fvar = varfont['fvar'] axes = { a.axisTag: (a.minValue, a.defaultValue, a.maxValue) for a in fvar.axes } # TODO Apply avar # TODO Round to F2Dot14? loc = normalizeLocation(loc, axes) # Location is normalized now print("Normalized location:", loc) gvar = varfont['gvar'] glyf = varfont['glyf'] # get list of glyph names in gvar sorted by component depth glyphnames = sorted( gvar.variations.keys(), key=lambda name: (glyf[name].getCompositeMaxpValues(glyf).maxComponentDepth if glyf[name].isComposite() else 0, name)) for glyphname in glyphnames: variations = gvar.variations[glyphname] coordinates, _ = _GetCoordinates(varfont, glyphname) origCoords, endPts = None, None for var in variations: scalar = supportScalar(loc, var.axes, ot=True) if not scalar: continue delta = var.coordinates if None in delta: if origCoords is None: origCoords, control = _GetCoordinates(varfont, glyphname) endPts = control[1] if control[0] >= 1 else list( range(len(control[1]))) delta = _iup_delta(delta, origCoords, endPts) coordinates += GlyphCoordinates(delta) * scalar _SetCoordinates(varfont, glyphname, coordinates) # Interpolate cvt if 'cvar' in varfont: cvar = varfont['cvar'] cvt = varfont['cvt '] deltas = {} for var in cvar.variations: scalar = supportScalar(loc, var.axes) if not scalar: continue for i, c in enumerate(var.coordinates): if c is not None: deltas[i] = deltas.get(i, 0) + scalar * c for i, delta in deltas.items(): cvt[i] += int(round(delta)) print("Removing variable tables") for tag in ('avar', 'cvar', 'fvar', 'gvar', 'HVAR', 'MVAR', 'VVAR', 'STAT'): if tag in varfont: del varfont[tag] print("Saving instance font", outfile) varfont.save(outfile)
class DouYin(object): ttfond = TTFont(app_path() + "/data/iconfont_9eb9a50.woff") ttfond.saveXML(app_path() + "/data/iconfont_9eb9a50.xml") def __int__(self): pass def get_cmap_dict(self): """ :return: 关系映射表 """ # 从本地读取关系映射表【从网站下载的woff字体文件】 best_cmap = self.ttfond["cmap"].getBestCmap() # 循环关系映射表将数字替换成16进制 best_cmap_dict = {} for key, value in best_cmap.items(): best_cmap_dict[hex(key)] = value return best_cmap_dict # 'num_1', '0xe604': 'num_2', '0xe605': 'num_3' def get_num_cmap(self): """ :return: 返回num和真正的数字映射关系 """ num_map = { "x": "", "num_": 1, "num_1": 0, "num_2": 3, "num_3": 2, "num_4": 4, "num_5": 5, "num_6": 6, "num_7": 9, "num_8": 7, "num_9": 8, } return num_map def map_cmap_num(self, get_cmap_dict, get_num_cmap): new_cmap = {} for key, value in get_cmap_dict().items(): key = re.sub("0", "&#", key, count=1) + ";" # 源代码中的格式  new_cmap[key] = get_num_cmap()[value] # 替换后的格式 # '': 1, '': 0, '': 3, '': 2, return new_cmap # 获取网页源码 def get_html(self, url): ip = ProxyGetter(project_name='douyin').getter_server_proxy() proxy = {'https': 'https://' + ip} headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36' } response = requests.get(url, headers=headers, proxies=proxy).text return response def replace_num_and_cmap(self, result, response): """ 将网页源代码中的替换成数字 :param result: :param response: :return: """ for key, value in result.items(): if key in response: # print(777) response = re.sub(key, str(value), response) return response def manage(self, response): res = etree.HTML(response) douyin_name = res.xpath('//p[@class="nickname"]//text()')[0] douyin_id = 'ID:' + ''.join( res.xpath('//p[@class="shortid"]/i//text()')).replace(' ', '') guanzhu_num = ''.join( res.xpath('//span[@class="focus block"]//text()')).replace( ' ', '') fensi_num = ''.join( res.xpath('//span[@class="follower block"]//text()')).replace( ' ', '') dianzan = ''.join( res.xpath('//span[@class="liked-num block"]//text()')).replace( ' ', '') print(douyin_name, douyin_id, guanzhu_num, fensi_num, dianzan) line = douyin_name + " " + douyin_id + " " + guanzhu_num + ' ' + fensi_num + ' ' + dianzan with open(app_path() + '/data/author.txt', 'a+', encoding='utf-8') as f: f.write(line + '\n') def read_author_id(self): with open(app_path() + '/data/body.txt', 'r') as f: lines = f.readlines() return lines def main(self): lines = self.read_author_id() for line in lines: line = re.sub('\'', '"', line) data = json.loads(line) author_id = data['author_id'] url = 'https://www.iesdouyin.com/share/user/' + str(author_id) response = self.get_html(url) new_cmap = self.map_cmap_num(self.get_cmap_dict, self.get_num_cmap) res = self.replace_num_and_cmap(new_cmap, response) self.manage(res)
def openOpenTypeFile(path, outFilePath): # If input font is CFF or PS, build a dummy ttFont in memory.. # return ttFont, and flag if is a real OTF font Return flag is 0 if OTF, 1 if CFF, and 2 if PS/ fontType = 0 # OTF tempPathCFF = path + kTempCFFSuffix try: ff = file(path, "rb") data = ff.read(10) ff.close() except (IOError, OSError): logMsg("Failed to open and read font file %s." % path) if data[:4] == "OTTO": # it is an OTF font, can process file directly try: ttFont = TTFont(path) except (IOError, OSError): raise focusFontError( "Error opening or reading from font file <%s>." % path) except TTLibError: raise focusFontError("Error parsing font file <%s>." % path) try: cffTable = ttFont["CFF "] except KeyError: raise focusFontError("Error: font is not a CFF font <%s>." % fontFileName) else: # It is not an OTF file. if (data[0] == '\1') and (data[1] == '\0'): # CFF file fontType = 1 tempPathCFF = path elif not "%" in data: #not a PS file either logMsg("Font file must be a PS, CFF or OTF fontfile: %s." % path) raise focusFontError("Font file must be PS, CFF or OTF file: %s." % path) else: # It is a PS file. Convert to CFF. fontType = 2 print "Converting Type1 font to temp CFF font file..." command = "tx -cff +b -std \"%s\" \"%s\" 2>&1" % (path, tempPathCFF) report = FDKUtils.runShellCmd(command) if "fatal" in report: logMsg( "Attempted to convert font %s from PS to a temporary CFF data file." % path) logMsg(report) raise focusFontError( "Failed to convert PS font %s to a temp CFF font." % path) # now package the CFF font as an OTF font. ff = file(tempPathCFF, "rb") data = ff.read() ff.close() try: ttFont = TTFont() cffModule = getTableModule('CFF ') cffTable = cffModule.table_C_F_F_('CFF ') ttFont['CFF '] = cffTable cffTable.decompile(data, ttFont) except: logMsg("\t%s" % (traceback.format_exception_only( sys.exc_type, sys.exc_value)[-1])) logMsg("Attempted to read font %s as CFF." % path) raise focusFontError("Error parsing font file <%s>." % path) fontData = CFFFontData(ttFont, path, outFilePath, fontType, logMsg) return fontData
def parse(self, response): # print(response.url) # ---------------------------------先下载字体文件用于破解数字---------------------------------------------- font_url = re.findall(r'((https|http|ftp|rtsp|mms)?://[^\s]+ttf)', response.css('.update style::text').extract_first())[0][0] font_name = response.css('.all-img-list li .update span::attr(class)').extract_first() + '.ttf' font_path = os.path.join(FONT_STORE, font_name) if os.path.exists('font_path') is False: download = requests.get(font_url) print('开始下载字体文件:' + font_name) with open(font_path , "wb") as file: file.write(download.content) # --------------------------------------------------------------------------------------------------------- # ---------------------------------将字库转换为XML后获取对应数字------------------------------------------- xml_path = font_path.replace('.ttf','.xml') font = TTFont(font_path) # 打开文件 font.saveXML(xml_path) # 转换成 xml 文件并保存 root = et.parse(xml_path).getroot() # 找到map那一堆标签(PyQuery) map_ele = root.find('cmap').find('cmap_format_12').findall('map') map_dict = {} # 把map那一堆数据存到字典中 for map in map_ele: # print(help(m)) code = map.attrib['code'].replace('0x', '') map_dict[code] = self.number_dict[map.attrib['name']] # code是键, name是值 print(map_dict) # --------------------------------------------------------------------------------------------------------- for fiction in response.css('.all-img-list li'): # 创建对象 item = FictionItem() words_temp = '' # 获取小说名称 item['title'] = fiction.css('a[data-eid=qd_B58]::text').extract_first() # 获取作者 item['author'] = fiction.css('a[data-eid=qd_B59]::text').extract_first() # 获取小说类型 item['type'] = fiction.css('a[data-eid=qd_B60]::text').extract_first() # 获取小说状态 item['status'] = fiction.css('.author span::text').extract_first() # 通过查表的方式获取小说中字数(表来源于字体中提取) for num in str(fiction.css('.update span::text').extract_first().encode("unicode-escape")).strip( '\'').strip('b\'').split(r'\\'): #确认有内容才取值 if num is not "": words_temp += map_dict.get(num[4:]) item['words'] = words_temp + '万字' #获得小说评分(需要通过ajax获取) fiction_id = fiction.css('a::attr(data-bid)').extract_first() url = 'https://book.qidian.com/ajax/comment/index?_csrfToken=1&bookId=%s&pageSize=15' % fiction_id request = urllib.request.Request(url=url) response = urllib.request.urlopen(request) #将字符串转换为dict类型 dict_data = eval(str(response.read(), encoding='utf-8')) # print(dict_data['data']['rate']) item['score'] = dict_data['data']['rate'] # 获取小说链接 item['fiction_urls'] = 'http:' + fiction.css('.book-mid-info a::attr(href)').extract_first() # 获取图片链接 item['image_urls'] = 'http:' + fiction.css('a[data-eid=qd_B57] img::attr(src)').extract_first() yield item
def mada_ttFonts(): return [TTFont(path) for path in mada_fonts]
async def fontTest(letter): test = TTFont("./temp/Roboto-Medium.ttf") for table in test["cmap"].tables: if ord(letter) in table.cmap.keys(): return True
parser.add_argument('--output', metavar="PNG", help='where to write the PNG file') args = parser.parse_args() # Constants WIDTH, HEIGHT, MARGIN, FRAMES = 2048, 2048, 128, 1 FONT_PATH = "sources/variable_ttf/Mekorot-VF.ttf" AUXILIARY_FONT = "Helvetica Neue" AUXILIARY_FONT_SIZE = 48 BIG_TEXT = FormattedString() BIG_TEXT.append("Aa", font=FONT_PATH, fontSize=MARGIN*8, fill=(1)) BIG_TEXT_WIDTH = BIG_TEXT.size().width BIG_TEXT_HEIGHT = BIG_TEXT.size().height BIG_TEXT_X_POS = (WIDTH - BIG_TEXT_WIDTH) / 2 # Adjust this number to change x-axis of main text BIG_TEXT_Y_POS = (HEIGHT - BIG_TEXT_HEIGHT - (MARGIN * 2)) ttFont = TTFont(FONT_PATH) # Constants we will work out dynamically MY_URL = subprocess.check_output("git remote get-url origin", shell=True).decode() MY_HASH = subprocess.check_output("git rev-parse --short HEAD", shell=True).decode() MY_FONT_NAME = ttFont["name"].getDebugName(4) FONT_VERSION = "v%s" % floatToFixedToStr(ttFont["head"].fontRevision, 16) # Draws a grid def grid(): stroke(1,0.1) strokeWidth(2) STEP_X, STEP_Y = 0, 0 INCREMENT_X, INCREMENT_Y = MARGIN / 2, MARGIN / 2 rect(MARGIN, MARGIN, WIDTH - (MARGIN * 2), HEIGHT - (MARGIN * 2)) for x in range(29):
def TTFontsXML(filenames): # 转换成XMl 到临时目录 filenametemp = "static/font/toolstemp.xml" font = TTFont(filenames) font.saveXML(filenametemp) return filenametemp
class WOFF2Reader(SFNTReader): flavor = "woff2" def __init__(self, file, checkChecksums=1, fontNumber=-1): if not haveBrotli: log.error( 'The WOFF2 decoder requires the Brotli Python extension, available at: ' 'https://github.com/google/brotli') raise ImportError("No module named brotli") self.file = file signature = Tag(self.file.read(4)) if signature != b"wOF2": raise TTLibError("Not a WOFF2 font (bad signature)") self.file.seek(0) self.DirectoryEntry = WOFF2DirectoryEntry data = self.file.read(woff2DirectorySize) if len(data) != woff2DirectorySize: raise TTLibError('Not a WOFF2 font (not enough data)') sstruct.unpack(woff2DirectoryFormat, data, self) self.tables = OrderedDict() offset = 0 for i in range(self.numTables): entry = self.DirectoryEntry() entry.fromFile(self.file) tag = Tag(entry.tag) self.tables[tag] = entry entry.offset = offset offset += entry.length totalUncompressedSize = offset compressedData = self.file.read(self.totalCompressedSize) decompressedData = brotli.decompress(compressedData) if len(decompressedData) != totalUncompressedSize: raise TTLibError( 'unexpected size for decompressed font data: expected %d, found %d' % (totalUncompressedSize, len(decompressedData))) self.transformBuffer = BytesIO(decompressedData) self.file.seek(0, 2) if self.length != self.file.tell(): raise TTLibError("reported 'length' doesn't match the actual file size") self.flavorData = WOFF2FlavorData(self) # make empty TTFont to store data while reconstructing tables self.ttFont = TTFont(recalcBBoxes=False, recalcTimestamp=False) def __getitem__(self, tag): """Fetch the raw table data. Reconstruct transformed tables.""" entry = self.tables[Tag(tag)] if not hasattr(entry, 'data'): if entry.transformed: entry.data = self.reconstructTable(tag) else: entry.data = entry.loadData(self.transformBuffer) return entry.data def reconstructTable(self, tag): """Reconstruct table named 'tag' from transformed data.""" entry = self.tables[Tag(tag)] rawData = entry.loadData(self.transformBuffer) if tag == 'glyf': # no need to pad glyph data when reconstructing padding = self.padding if hasattr(self, 'padding') else None data = self._reconstructGlyf(rawData, padding) elif tag == 'loca': data = self._reconstructLoca() elif tag == 'hmtx': data = self._reconstructHmtx(rawData) else: raise TTLibError("transform for table '%s' is unknown" % tag) return data def _reconstructGlyf(self, data, padding=None): """ Return recostructed glyf table data, and set the corresponding loca's locations. Optionally pad glyph offsets to the specified number of bytes. """ self.ttFont['loca'] = WOFF2LocaTable() glyfTable = self.ttFont['glyf'] = WOFF2GlyfTable() glyfTable.reconstruct(data, self.ttFont) if padding: glyfTable.padding = padding data = glyfTable.compile(self.ttFont) return data def _reconstructLoca(self): """ Return reconstructed loca table data. """ if 'loca' not in self.ttFont: # make sure glyf is reconstructed first self.tables['glyf'].data = self.reconstructTable('glyf') locaTable = self.ttFont['loca'] data = locaTable.compile(self.ttFont) if len(data) != self.tables['loca'].origLength: raise TTLibError( "reconstructed 'loca' table doesn't match original size: " "expected %d, found %d" % (self.tables['loca'].origLength, len(data))) return data def _reconstructHmtx(self, data): """ Return reconstructed hmtx table data. """ # Before reconstructing 'hmtx' table we need to parse other tables: # 'glyf' is required for reconstructing the sidebearings from the glyphs' # bounding box; 'hhea' is needed for the numberOfHMetrics field. if "glyf" in self.flavorData.transformedTables: # transformed 'glyf' table is self-contained, thus 'loca' not needed tableDependencies = ("maxp", "hhea", "glyf") else: # decompiling untransformed 'glyf' requires 'loca', which requires 'head' tableDependencies = ("maxp", "head", "hhea", "loca", "glyf") for tag in tableDependencies: self._decompileTable(tag) hmtxTable = self.ttFont["hmtx"] = WOFF2HmtxTable() hmtxTable.reconstruct(data, self.ttFont) data = hmtxTable.compile(self.ttFont) return data def _decompileTable(self, tag): """Decompile table data and store it inside self.ttFont.""" data = self[tag] if self.ttFont.isLoaded(tag): return self.ttFont[tag] tableClass = getTableClass(tag) table = tableClass(tag) self.ttFont.tables[tag] = table table.decompile(data, self.ttFont)
def _decode_antifont(self, font_url): response = requests.get(font_url) font = TTFont(BytesIO(response.content)) cmap = font.getBestCmap() # 返回unicode cmap字典可用的字体 font.close() return cmap
from fontTools.ttLib import TTFont font = TTFont('movie.woff') # 打开当前目录的movie.woff文件 font.saveXML('movie.xml') # 另存为movie.xml
import sys from fontTools.ttLib import TTFont import argparse parser = argparse.ArgumentParser(description="Finds deeply nested components") parser.add_argument("--depth", metavar="depth", type=int, default=2, help="minimum depth to report") parser.add_argument("font", metavar="FONT", help="the font file to test") args = parser.parse_args() font = TTFont(args.font) if "glyf" not in font: print("find-nested-components can only be used on TrueType fonts") nesting = {} def get_nesting(glyph): if glyph in nesting: return nesting[glyph] gg = font["glyf"].glyphs[glyph] components = gg.getComponentNames(font["glyf"]) if not components: nesting[glyph] = {"depth": 0, "chain": []} return nesting[glyph] depth = 0 chain = []
def _CheckFont(self, font_path, glyph): font = TTFont(font_path) for table in font['cmap'].tables: if ord(glyph) in table.cmap.keys(): return True return False
class WOFF2Writer(SFNTWriter): flavor = "woff2" def __init__(self, file, numTables, sfntVersion="\000\001\000\000", flavor=None, flavorData=None): if not haveBrotli: log.error( 'The WOFF2 encoder requires the Brotli Python extension, available at: ' 'https://github.com/google/brotli') raise ImportError("No module named brotli") self.file = file self.numTables = numTables self.sfntVersion = Tag(sfntVersion) self.flavorData = WOFF2FlavorData(data=flavorData) self.directoryFormat = woff2DirectoryFormat self.directorySize = woff2DirectorySize self.DirectoryEntry = WOFF2DirectoryEntry self.signature = Tag("wOF2") self.nextTableOffset = 0 self.transformBuffer = BytesIO() self.tables = OrderedDict() # make empty TTFont to store data while normalising and transforming tables self.ttFont = TTFont(recalcBBoxes=False, recalcTimestamp=False) def __setitem__(self, tag, data): """Associate new entry named 'tag' with raw table data.""" if tag in self.tables: raise TTLibError("cannot rewrite '%s' table" % tag) if tag == 'DSIG': # always drop DSIG table, since the encoding process can invalidate it self.numTables -= 1 return entry = self.DirectoryEntry() entry.tag = Tag(tag) entry.flags = getKnownTagIndex(entry.tag) # WOFF2 table data are written to disk only on close(), after all tags # have been specified entry.data = data self.tables[tag] = entry def close(self): """ All tags must have been specified. Now write the table data and directory. """ if len(self.tables) != self.numTables: raise TTLibError("wrong number of tables; expected %d, found %d" % (self.numTables, len(self.tables))) if self.sfntVersion in ("\x00\x01\x00\x00", "true"): isTrueType = True elif self.sfntVersion == "OTTO": isTrueType = False else: raise TTLibError("Not a TrueType or OpenType font (bad sfntVersion)") # The WOFF2 spec no longer requires the glyph offsets to be 4-byte aligned. # However, the reference WOFF2 implementation still fails to reconstruct # 'unpadded' glyf tables, therefore we need to 'normalise' them. # See: # https://github.com/khaledhosny/ots/issues/60 # https://github.com/google/woff2/issues/15 if isTrueType and "glyf" in self.flavorData.transformedTables: self._normaliseGlyfAndLoca(padding=4) self._setHeadTransformFlag() # To pass the legacy OpenType Sanitiser currently included in browsers, # we must sort the table directory and data alphabetically by tag. # See: # https://github.com/google/woff2/pull/3 # https://lists.w3.org/Archives/Public/public-webfonts-wg/2015Mar/0000.html # TODO(user): remove to match spec once browsers are on newer OTS self.tables = OrderedDict(sorted(self.tables.items())) self.totalSfntSize = self._calcSFNTChecksumsLengthsAndOffsets() fontData = self._transformTables() compressedFont = brotli.compress(fontData, mode=brotli.MODE_FONT) self.totalCompressedSize = len(compressedFont) self.length = self._calcTotalSize() self.majorVersion, self.minorVersion = self._getVersion() self.reserved = 0 directory = self._packTableDirectory() self.file.seek(0) self.file.write(pad(directory + compressedFont, size=4)) self._writeFlavorData() def _normaliseGlyfAndLoca(self, padding=4): """ Recompile glyf and loca tables, aligning glyph offsets to multiples of 'padding' size. Update the head table's 'indexToLocFormat' accordingly while compiling loca. """ if self.sfntVersion == "OTTO": return for tag in ('maxp', 'head', 'loca', 'glyf'): self._decompileTable(tag) self.ttFont['glyf'].padding = padding for tag in ('glyf', 'loca'): self._compileTable(tag) def _setHeadTransformFlag(self): """ Set bit 11 of 'head' table flags to indicate that the font has undergone a lossless modifying transform. Re-compile head table data.""" self._decompileTable('head') self.ttFont['head'].flags |= (1 << 11) self._compileTable('head') def _decompileTable(self, tag): """ Fetch table data, decompile it, and store it inside self.ttFont. """ tag = Tag(tag) if tag not in self.tables: raise TTLibError("missing required table: %s" % tag) if self.ttFont.isLoaded(tag): return data = self.tables[tag].data if tag == 'loca': tableClass = WOFF2LocaTable elif tag == 'glyf': tableClass = WOFF2GlyfTable elif tag == 'hmtx': tableClass = WOFF2HmtxTable else: tableClass = getTableClass(tag) table = tableClass(tag) self.ttFont.tables[tag] = table table.decompile(data, self.ttFont) def _compileTable(self, tag): """ Compile table and store it in its 'data' attribute. """ self.tables[tag].data = self.ttFont[tag].compile(self.ttFont) def _calcSFNTChecksumsLengthsAndOffsets(self): """ Compute the 'original' SFNT checksums, lengths and offsets for checksum adjustment calculation. Return the total size of the uncompressed font. """ offset = sfntDirectorySize + sfntDirectoryEntrySize * len(self.tables) for tag, entry in self.tables.items(): data = entry.data entry.origOffset = offset entry.origLength = len(data) if tag == 'head': entry.checkSum = calcChecksum(data[:8] + b'\0\0\0\0' + data[12:]) else: entry.checkSum = calcChecksum(data) offset += (entry.origLength + 3) & ~3 return offset def _transformTables(self): """Return transformed font data.""" transformedTables = self.flavorData.transformedTables for tag, entry in self.tables.items(): data = None if tag in transformedTables: data = self.transformTable(tag) if data is not None: entry.transformed = True if data is None: # pass-through the table data without transformation data = entry.data entry.transformed = False entry.offset = self.nextTableOffset entry.saveData(self.transformBuffer, data) self.nextTableOffset += entry.length self.writeMasterChecksum() fontData = self.transformBuffer.getvalue() return fontData def transformTable(self, tag): """Return transformed table data, or None if some pre-conditions aren't met -- in which case, the non-transformed table data will be used. """ if tag == "loca": data = b"" elif tag == "glyf": for tag in ('maxp', 'head', 'loca', 'glyf'): self._decompileTable(tag) glyfTable = self.ttFont['glyf'] data = glyfTable.transform(self.ttFont) elif tag == "hmtx": if "glyf" not in self.tables: return for tag in ("maxp", "head", "hhea", "loca", "glyf", "hmtx"): self._decompileTable(tag) hmtxTable = self.ttFont["hmtx"] data = hmtxTable.transform(self.ttFont) # can be None else: raise TTLibError("Transform for table '%s' is unknown" % tag) return data def _calcMasterChecksum(self): """Calculate checkSumAdjustment.""" tags = list(self.tables.keys()) checksums = [] for i in range(len(tags)): checksums.append(self.tables[tags[i]].checkSum) # Create a SFNT directory for checksum calculation purposes self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(self.numTables, 16) directory = sstruct.pack(sfntDirectoryFormat, self) tables = sorted(self.tables.items()) for tag, entry in tables: sfntEntry = SFNTDirectoryEntry() sfntEntry.tag = entry.tag sfntEntry.checkSum = entry.checkSum sfntEntry.offset = entry.origOffset sfntEntry.length = entry.origLength directory = directory + sfntEntry.toString() directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize assert directory_end == len(directory) checksums.append(calcChecksum(directory)) checksum = sum(checksums) & 0xffffffff # BiboAfba! checksumadjustment = (0xB1B0AFBA - checksum) & 0xffffffff return checksumadjustment def writeMasterChecksum(self): """Write checkSumAdjustment to the transformBuffer.""" checksumadjustment = self._calcMasterChecksum() self.transformBuffer.seek(self.tables['head'].offset + 8) self.transformBuffer.write(struct.pack(">L", checksumadjustment)) def _calcTotalSize(self): """Calculate total size of WOFF2 font, including any meta- and/or private data.""" offset = self.directorySize for entry in self.tables.values(): offset += len(entry.toString()) offset += self.totalCompressedSize offset = (offset + 3) & ~3 offset = self._calcFlavorDataOffsetsAndSize(offset) return offset def _calcFlavorDataOffsetsAndSize(self, start): """Calculate offsets and lengths for any meta- and/or private data.""" offset = start data = self.flavorData if data.metaData: self.metaOrigLength = len(data.metaData) self.metaOffset = offset self.compressedMetaData = brotli.compress( data.metaData, mode=brotli.MODE_TEXT) self.metaLength = len(self.compressedMetaData) offset += self.metaLength else: self.metaOffset = self.metaLength = self.metaOrigLength = 0 self.compressedMetaData = b"" if data.privData: # make sure private data is padded to 4-byte boundary offset = (offset + 3) & ~3 self.privOffset = offset self.privLength = len(data.privData) offset += self.privLength else: self.privOffset = self.privLength = 0 return offset def _getVersion(self): """Return the WOFF2 font's (majorVersion, minorVersion) tuple.""" data = self.flavorData if data.majorVersion is not None and data.minorVersion is not None: return data.majorVersion, data.minorVersion else: # if None, return 'fontRevision' from 'head' table if 'head' in self.tables: return struct.unpack(">HH", self.tables['head'].data[4:8]) else: return 0, 0 def _packTableDirectory(self): """Return WOFF2 table directory data.""" directory = sstruct.pack(self.directoryFormat, self) for entry in self.tables.values(): directory = directory + entry.toString() return directory def _writeFlavorData(self): """Write metadata and/or private data using appropiate padding.""" compressedMetaData = self.compressedMetaData privData = self.flavorData.privData if compressedMetaData and privData: compressedMetaData = pad(compressedMetaData, size=4) if compressedMetaData: self.file.seek(self.metaOffset) assert self.file.tell() == self.metaOffset self.file.write(compressedMetaData) if privData: self.file.seek(self.privOffset) assert self.file.tell() == self.privOffset self.file.write(privData) def reordersTables(self): return True