def main(): options = parser.parse_args() fonts = options.ttf_font if options.ascents or \ options.descents or \ options.linegaps or \ options.linegaps == 0 or \ options.ascents_hhea or \ options.ascents_typo or \ options.ascents_win or \ options.descents_hhea or \ options.descents_typo or \ options.descents_win or \ options.linegaps_hhea or \ options.linegaps_hhea == 0 or \ options.linegaps_typo or \ options.linegaps_typo == 0: for f in fonts: try: ttfont = ttLib.TTFont(f) except TTLibError as ex: print('Error: {0}: {1}'.format(f, ex)) continue if options.ascents: ttfont['hhea'].ascent = options.ascents ttfont['OS/2'].sTypoAscender = options.ascents ttfont['OS/2'].usWinAscent = options.ascents if options.descents: ttfont['hhea'].descent = options.descents ttfont['OS/2'].sTypoDescender = options.descents ttfont['OS/2'].usWinDescent = abs(options.descents) if options.linegaps or options.linegaps == 0: ttfont['hhea'].lineGap = options.linegaps ttfont['OS/2'].sTypoLineGap = options.linegaps if options.ascents_hhea: ttfont['hhea'].ascent = options.ascents_hhea if options.ascents_typo: ttfont['OS/2'].sTypoAscender = options.ascents_typo if options.ascents_win: ttfont['OS/2'].usWinAscent = options.ascents_win if options.descents_hhea: ttfont['hhea'].descent = options.descents_hhea if options.descents_typo: ttfont['OS/2'].sTypoDescender = options.descents_typo if options.descents_win: ttfont['OS/2'].usWinDescent = abs(options.descents_win) if options.linegaps_hhea or options.linegaps_hhea == 0: ttfont['hhea'].lineGap = options.linegaps_hhea if options.linegaps_typo or options.linegaps_typo == 0: ttfont['OS/2'].sTypoLineGap = options.linegaps_typo ttfont.save(f[:-4] + '.fix.ttf') elif options.autofix: ttFonts = [] for f in fonts: try: ttFonts.append(ttLib.TTFont(f)) except TTLibError as ex: print('Error: {0}: {1}'.format(f, ex)) continue v_metrics = vmetrics(ttFonts) for ttfont in ttFonts: ttfont['hhea'].ascent = v_metrics["ymax"] ttfont['OS/2'].sTypoAscender = v_metrics["ymax"] ttfont['OS/2'].usWinAscent = v_metrics["ymax"] ttfont['hhea'].descent = v_metrics["ymin"] ttfont['OS/2'].sTypoDescender = v_metrics["ymin"] ttfont['OS/2'].usWinDescent = abs(v_metrics["ymin"]) ttfont.save(ttfont.reader.file.name[:-4] + '.fix.ttf') else: entries = [('hhea', 'ascent'), ('OS/2', 'sTypoAscender'), ('OS/2', 'usWinAscent'), ('hhea', 'descent'), ('OS/2', 'sTypoDescender'), ('OS/2', 'usWinDescent'), ('hhea', 'lineGap'), ('OS/2', 'sTypoLineGap')] for f in fonts: ttfont = ttLib.TTFont(f) print("## {}".format(f)) for table, field in entries: print("{} {}: {}".format(table, field, getattr(ttfont[table], field))) print()
def setUp(self): self.font = font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False) font['head'] = ttLib.newTable('head') font['loca'] = WOFF2LocaTable() font['glyf'] = WOFF2GlyfTable()
def __init__(self, path): self.font = ttLib.TTFont(path) self.path = path self.saveit = False
def __init__(self, font_file): MAPPING_FILE = './character' self.font = ttLib.TTFont(font_file) self.table = pickle.load(open(MAPPING_FILE, 'rb'))
def test_required_tables(self): font = ttLib.TTFont(flavor="woff2") with self.assertRaisesRegex(ttLib.TTLibError, "missing required table"): font.save(BytesIO())
def upm(self, ftpath): ft = ttLib.TTFont(ftpath) upm = ft["head"].unitsPerEm return ft, upm
def fromfontcmap(fontname): font = ttLib.TTFont(fontname) return CodeList.fromset(font_data.get_cmap(font))
def main(): global maxInstructions, quietcount # First read the command-line arguments. At minimum we need the inputfile. argparser = argparse.ArgumentParser( prog='xgridfit', description= 'Compile XML into TrueType instructions and add them to a font.') argparser.add_argument('-v', '--version', action='version', version='Xgridfit ' + __version__) argparser.add_argument( '-e', '--expand', action="store_true", help="Convert file to expanded syntax, save, and exit") argparser.add_argument( '-c', '--compact', action="store_true", help="Convert file to compact syntax, save, and exit") argparser.add_argument('-n', '--novalidation', action="store_true", help="Skip validation of the Xgridfit program") argparser.add_argument('--nocompilation', action="store_true", help="Skip compilation of the Xgridfit program") argparser.add_argument( '--nocompact', action="store_true", help="Do not compact glyph programs (can help with debugging)") argparser.add_argument('-m', '--merge', action="store_true", help="Merge Xgridfit with existing instructions") argparser.add_argument( '-r', '--replaceprep', action="store_true", help= "Whether to replace the existing prep table or append the new one (use with --merge)" ) argparser.add_argument( '--initgraphics', choices=['yes', 'no'], help= "Whether to initialize graphics-tracking variables at the beginning of glyph program" ) argparser.add_argument( '-a', '--assume_y', choices=['yes', 'no'], help="Whether compiler should assume that your hints are all vertical") argparser.add_argument( '-q', '--quiet', action="count", default=0, help="No progress messages (-qq to suppress warnings too)") argparser.add_argument('-g', '--glyphlist', help="List of glyphs to compile") argparser.add_argument('-i', '--inputfont', action='store', type=str, help="The font file to add instructions to") argparser.add_argument('-o', '--outputfont', action='store', type=str, help="The font file to write") argparser.add_argument('-s', '--saveprograms', action="store_true", help="Save generated instructions to text files") argparser.add_argument( '-f', '--coordinatefuzz', type=int, default=1, help= "Error tolerance for points identified by coordinates (default is 1)") argparser.add_argument("inputfile", help='Xgridfit (XML) file to process.') argparser.add_argument( "outputfile", nargs='?', help="Filename for options (e.g. --expand) that produce text output") args = argparser.parse_args() inputfile = args.inputfile outputfile = args.outputfile inputfont = args.inputfont outputfont = args.outputfont skipval = args.novalidation skipcomp = args.nocompilation expandonly = args.expand compactonly = args.compact mergemode = args.merge quietcount = args.quiet initgraphics = args.initgraphics assume_y = args.assume_y glyphlist = args.glyphlist replaceprep = args.replaceprep saveprograms = args.saveprograms nocompact = args.nocompact cfuzz = args.coordinatefuzz if quietcount < 1: print("Opening the Xgridfit file ...") if cfuzz > 1: coordinateFuzz = cfuzz xgffile = etree.parse(inputfile) # We'll need namespaces ns = { "xgf": "http://xgridfit.sourceforge.net/Xgridfit2", "xi": "http://www.w3.org/2001/XInclude", "xsl": "http://www.w3.org/1999/XSL/Transform" } # Do xinclude if this is a multipart file if len(xgffile.xpath("/xgf:xgridfit/xi:include", namespaces=ns)): xgffile.xinclude() # Next determine whether we are using long tagnames or short. Best way # is to find out which tag is used for the required <pre-program> (<prep>) # element. If we don't find it, print an error message and exit. Here's # where we validate too; and if we're only expanding or compacting a file, # do that and exit before we go to the trouble of opening the font. if quietcount < 1: print("Validating ...") if len(xgffile.xpath("/xgf:xgridfit/xgf:prep", namespaces=ns)): # first validate validate(xgffile, "compact", skipval) # as we can't use the compact syntax, always expand if quietcount < 1: print("Expanding compact to normal syntax ...") xslfile = get_file_path("XSL/expand.xsl") etransform = etree.XSLT(etree.parse(xslfile)) xgffile = etransform(xgffile) if expandonly: tstr = str(xgffile) tstr = tstr.replace('xgf:', '') tstr = tstr.replace( 'xmlns:xgf="http://xgridfit.sourceforge.net/Xgridfit2"', '') if outputfile: of = open(outputfile, "w") of.write(tstr) of.close() else: print(tstr) sys.exit(0) elif len(xgffile.xpath("/xgf:xgridfit/xgf:pre-program", namespaces=ns)): validate(xgffile, "normal", skipval) if compactonly: xslfile = get_file_path("XSL/compact.xsl") etransform = etree.XSLT(etree.parse(xslfile)) xgffile = etransform(xgffile) tstr = str(xgffile) tstr = tstr.replace('xgf:', '') tstr = tstr.replace( 'xmlns:xgf="http://xgridfit.sourceforge.net/Xgridfit2"', '') tstr = tstr.replace(' >', '>') if outputfile: of = open(outputfile, "w") of.write(tstr) of.close() else: print(tstr) sys.exit(0) else: print( "The xgridfit program must contain a pre-program (prep) element,") print("even if it's empty.") sys.exit(1) if skipcomp and quietcount < 1: print("Skipping compilation") sys.exit(0) # Now open the font. If we're in merge-mode, we need to know some things # about the current state of it; otherwise we just wipe it. if quietcount < 1: print("Opening and evaluating the font ...") if not inputfont: inputfont = xgffile.xpath("/xgf:xgridfit/xgf:inputfont/text()", namespaces=ns)[0] if not inputfont: print("Need the filename of a font to read. Use the --inputfont") print("command-line argument or the <inputfont> element in your") print("Xgridfit file.") sys.exit(1) thisFont = ttLib.TTFont(inputfont) functionBase = 0 # Offset to account for functions in existing font cvtBase = 0 # Offset to account for CVs in existing font storageBase = 0 # Offset to account for storage in existing font maxStack = 256 # Our (generous) default stack size twilightBase = 0 # Offset to account for twilight space in existing font if mergemode: maxInstructions = max(maxInstructions, thisFont['maxp'].maxSizeOfInstructions) storageBase = thisFont['maxp'].maxStorage maxStack = max(maxStack, thisFont['maxp'].maxStackElements) functionBase = thisFont['maxp'].maxFunctionDefs twilightBase = thisFont['maxp'].maxTwilightPoints try: cvtBase = len(getattr(thisFont['cvt '], 'values')) except: cvtBase = 0 else: wipe_font(thisFont) # Get the xsl file, change some defaults (only relevant in merge-mode), # and get a transform object xslfile = etree.parse(get_file_path("XSL/xgridfit-ft.xsl")) if mergemode: xslfile.xpath("/xsl:stylesheet/xsl:param[@name='function-base']", namespaces=ns)[0].attrib['select'] = str(functionBase) xslfile.xpath("/xsl:stylesheet/xsl:param[@name='cvt-base']", namespaces=ns)[0].attrib['select'] = str(cvtBase) xslfile.xpath("/xsl:stylesheet/xsl:param[@name='storage-base']", namespaces=ns)[0].attrib['select'] = str(storageBase) etransform = etree.XSLT(xslfile) # Get a list of the glyphs to compile if quietcount < 1: print("Getting glyph list ...") if glyphlist: # a list passed in as a command-line argument glyph_list = glyphlist else: # all the glyph programs in the file glyph_list = str(etransform(xgffile, **{"get-glyph-list": "'yes'"})) glyph_list = list(glyph_list.split(" ")) no_compact_list = str( etransform(xgffile, **{"get-no-compact-list": "'yes'"})) if no_compact_list == None: no_compact_list = [] else: no_compact_list = list(no_compact_list.split(" ")) # Now that we have a glyph list, we can make the coordinate index # and substitute point numbers for coordinates. coordinateIndex = make_coordinate_index(glyph_list, thisFont) coordinates_to_points(glyph_list, xgffile, coordinateIndex, ns) # Back to the xgf file. We're also going to need a list of stack-safe # functions in this font. if quietcount < 1: print("Getting list of safe function calls ...") safe_calls = etransform(xgffile, **{"stack-safe-list": "'yes'"}) safe_calls = literal_eval(str(safe_calls)) # Get cvt if quietcount < 1: print("Building control-value table ...") cvt_list = str(etransform(xgffile, **{"get-cvt-list": "'yes'"})) cvt_list = literal_eval("[" + cvt_list + "]") install_cvt(thisFont, cvt_list, cvtBase) # Test whether we have a cvar element (i.e. this is a variable font) cvar_count = len(xgffile.xpath("/xgf:xgridfit/xgf:cvar", namespaces=ns)) if cvar_count > 0: if quietcount < 1: print("Building cvar table ...") tuple_store = literal_eval( str(etransform(xgffile, **{"get-cvar": "'yes'"}))) install_cvar(thisFont, tuple_store, mergemode, cvtBase) if quietcount < 1: print("Building fpgm table ...") predef_functions = int( xslfile.xpath( "/xsl:stylesheet/xsl:variable[@name='predefined-functions']", namespaces=ns)[0].attrib['select']) maxFunction = etransform(xgffile, **{"function-count": "'yes'"}) maxFunction = int(maxFunction) + predef_functions + functionBase fpgm_code = etransform(xgffile, **{"fpgm-only": "'yes'"}) install_functions(thisFont, fpgm_code, functionBase) if saveprograms: instf = open("fpgm.instructions", "w") instf.write(str(fpgm_code)) instf.close if quietcount < 1: print("Building prep table ...") prep_code = etransform(xgffile, **{"prep-only": "'yes'"}) install_prep(thisFont, prep_code, mergemode, replaceprep) if saveprograms: instf = open("prep.instructions", "w") instf.write(str(prep_code)) instf.close # Now loop through the glyphs for which there is code. cycler = 0 for g in glyph_list: if quietcount < 1: print("Processing glyph " + g) elif quietcount < 2 and cycler == 4: print(".", end=" ", flush=True) cycler += 1 if cycler == 5: cycler = 0 try: gt = "'" + g + "'" glyph_args = {'singleGlyphId': gt} if initgraphics: glyph_args['init_graphics'] = "'" + initgraphics + "'" if assume_y: glyph_args['assume-always-y'] = "'" + assume_y + "'" g_inst = etransform(xgffile, **glyph_args) except Exception as e: print(e) for entry in etransform.error_log: print('message from line %s, col %s: %s' % (entry.line, entry.column, entry.message)) sys.exit(1) if nocompact or g in no_compact_list: g_inst_final = str(g_inst) else: g_inst_final = compact_instructions(str(g_inst), safe_calls) install_glyph_program(g, thisFont, g_inst_final) if saveprograms: gfn = userNameToFileName(g) + ".instructions" gfnfile = open(gfn, "w") if nocompact or g in no_compact_list: gfnfile.write(g_inst_final) else: gfnfile.write("Uncompacted:\n\n") gfnfile.write(str(g_inst)) gfnfile.write("\n\nCompacted:\n\n") gfnfile.write(g_inst_final) gfnfile.close print("") if quietcount < 1: print("Hinted " + str(len(glyph_list)) + " glyphs.") print("Cleaning up and writing the new font") thisFont['maxp'].maxSizeOfInstructions = maxInstructions + 50 thisFont['maxp'].maxTwilightPoints = twilightBase + 25 thisFont['maxp'].maxStorage = storageBase + 64 thisFont['maxp'].maxStackElements = maxStack thisFont['maxp'].maxFunctionDefs = maxFunction thisFont['head'].flags |= 0b0000000000001000 if skipcomp: if quietcount < 1: print( "As --nocompilation flag is set, exiting without writing font file." ) sys.exit(0) if not outputfont: outputfont = str( xgffile.xpath("/xgf:xgridfit/xgf:outputfont/text()", namespaces=ns)[0]) if not outputfont: print("Need the filename of a font to write. Use the --outputfont") print("command-line argument or the <outputfont> element in your") print("Xgridfit file.") sys.exit(1) thisFont.save(outputfont, 1)
def copy_ttx_files(project, build, log): from .app import app config = project.config param = { 'login': project.login, 'id': project.id, 'revision': build.revision, 'build': build.id } _in = os.path.join(app.config['DATA_ROOT'], '%(login)s/%(id)s.in/' % param) _out = os.path.join(app.config['DATA_ROOT'], '%(login)s/%(id)s.out/%(build)s.%(revision)s/' % param) _out_src = os.path.join( app.config['DATA_ROOT'], '%(login)s/%(id)s.out/%(build)s.%(revision)s/sources/' % param) ttx_files = [] for x in config['state'].get('process_files', []): if x.endswith('.ttx'): ttx_files.append(x) for ttx_file in ttx_files: _ttx_path = os.path.join(_in, ttx_file) if not os.path.exists(_ttx_path): run("echo file '{}' not found".format(_ttx_path), cwd=_out, log=log) continue font = ttLib.TTFont(None, lazy=False, recalcBBoxes=True, verbose=False, allowVID=False) font.importXML(_ttx_path, quiet=True) _ttx_name = os.path.splitext(os.path.basename(_ttx_path))[0] def nameTableRead(font, NameID, fallbackNameID=False): for record in font['name'].names: if record.nameID == NameID: if b'\000' in record.string: return record.string.decode('utf-16-be').encode( 'utf-8') else: return record.string if fallbackNameID: return nameTableRead(font, fallbackNameID) styleName = nameTableRead(font, 17, 2) # Always have a regular style if styleName == 'Normal': styleName = 'Regular' # NameID=1 is required familyName = nameTableRead(font, 16, 1) # Remove whitespace from names styleNameNoWhitespace = re.sub(r'\s', '', styleName) familyNameNoWhitespace = re.sub(r'\s', '', familyName) _out_ttx_name = "{familyname}-{stylename}".format( familyname=familyNameNoWhitespace, stylename=styleNameNoWhitespace) if font.sfntVersion == '\x00\x01\x00\x00': # TTF _out_name = '{}.ttf.ttx'.format(_out_ttx_name) elif font.sfntVersion == 'OTTO': # OTF _out_name = '{}.otf.ttx'.format(_out_ttx_name) run("cp '{}' '{}'".format(_ttx_path, _out_src), cwd=_out, log=log) run("mv '{ttx_name}.ttx' '{out_name}'".format(ttx_name=_ttx_name, out_name=_out_name), cwd=_out_src, log=log) run("ttx -i -q {}".format(_out_name), cwd=_out_src, log=log) run("mv {0}.ttf.ttf {0}.ttf".format(_out_ttx_name), cwd=_out_src, log=log) if font.sfntVersion == 'OTTO': # OTF scripts_folder = os.path.join(app.config['ROOT'], 'scripts') cmd = "python autoconvert.py '{out_src}{ttx_name}.otf' '{out}{ttx_name}.ttf'".format( out_src=_out_src, ttx_name=_out_ttx_name, out=_out) run(cmd, cwd=scripts_folder, log=log) else: run("mv '{0}.ttf' '../{0}.ttf'".format(_out_ttx_name), _out_src, log=log)
def _remove_from_cmap(infile, outfile, exclude=[]): font = ttLib.TTFont(infile) font_data.delete_from_cmap(font, exclude) font.save(outfile)
def splitFont( fontPath, outputDirectory="fonts/rec_mono-for-code", newName="Rec Mono", ttc=False, zip=False, ): # access font as TTFont object varfont = ttLib.TTFont(fontPath) fontFileName = os.path.basename(fontPath) for package in instanceValues: outputSubDir = f"{outputDirectory}/{package}" for instance in instanceValues[package]: print(instance) instanceFont = instancer.instantiateVariableFont( varfont, { "wght": instanceValues[package][instance]["wght"], "CASL": instanceValues[package][instance]["CASL"], "MONO": instanceValues[package][instance]["MONO"], "slnt": instanceValues[package][instance]["slnt"], "CRSV": instanceValues[package][instance]["CRSV"], }, ) # UPDATE NAME ID 6, postscript name currentPsName = getFontNameID(instanceFont, 6) newPsName = (currentPsName.replace("Sans", "").replace( oldName, newName.replace(" ", "")).replace("LinearLight", instance.replace(" ", ""))) setFontNameID(instanceFont, 6, newPsName) # UPDATE NAME ID 4, full font name currentFullName = getFontNameID(instanceFont, 4) newFullName = (currentFullName.replace("Sans", "").replace( oldName, newName).replace(" Linear Light", instance)) setFontNameID(instanceFont, 4, newFullName) # UPDATE NAME ID 3, unique font ID currentUniqueName = getFontNameID(instanceFont, 3) newUniqueName = (currentUniqueName.replace("Sans", "").replace( oldName, newName.replace(" ", "")).replace("LinearLight", instance.replace(" ", ""))) setFontNameID(instanceFont, 3, newUniqueName) # ADD name 2, style linking name newStyleLinkingName = instanceValues[package][instance]["style"] setFontNameID(instanceFont, 2, newStyleLinkingName) setFontNameID(instanceFont, 17, newStyleLinkingName) # UPDATE NAME ID 1, unique font ID currentFamName = getFontNameID(instanceFont, 1) newFamName = (currentFamName.replace(" Sans", "").replace( oldName, newName).replace( "Linear Light", instance.replace( " " + instanceValues[package][instance]["style"], ""), )) setFontNameID(instanceFont, 1, newFamName) setFontNameID(instanceFont, 16, newFamName) newFileName = fontFileName.replace(oldName, newName.replace( " ", "")).replace("_VF_", "-" + instance.replace(" ", "") + "-") # make dir for new fonts pathlib.Path(outputSubDir).mkdir(parents=True, exist_ok=True) # drop STAT table to allow RIBBI style naming & linking on Windows del instanceFont["STAT"] outputPath = f"{outputSubDir}/{newFileName}" # save font instanceFont.save(outputPath) # freeze in rvrn features with pyftfeatfreeze pyftfeatfreeze.main(["--features=rvrn", outputPath, outputPath]) # swap dlig2calt to make code ligatures work in old code editor apps dlig2calt(outputPath, inplace=True) # ----------------------------------------------------------- # make TTC (truetype collection) of fonts – doesn't current work on Mac very well :( if ttc: # make list of fonts in subdir fontPaths = [ os.path.abspath(outputSubDir + "/" + x) for x in os.listdir(outputSubDir) ] # form command command = f"otf2otc {' '.join(fontPaths)} -o {outputDirectory}/RecMono-{package}.ttc" print("▶", command, "\n") # run command in shell subprocess.run(command.split(), check=True, text=True) # remove dir with individual fontpaths shutil.rmtree(os.path.abspath(outputSubDir)) # Make zip of output, then put inside output directory if zip: shutil.make_archive(f"{outputDirectory}", "zip", outputDirectory) shutil.move( f"{outputDirectory}.zip", f"{outputDirectory}/{outputDirectory.split('/')[-1]}.zip", )
def processFont(fontPath, outputFolderPath, options): font = ttLib.TTFont(fontPath) glyphOrder = font.getGlyphOrder() svgTag = 'SVG ' if svgTag not in font: print("ERROR: The font does not have the {} table.".format( svgTag.strip()), file=sys.stderr) sys.exit(1) svgTable = font[svgTag] if not len(svgTable.docList): print("ERROR: The {} table has no data that can be output.".format( svgTag.strip()), file=sys.stderr) sys.exit(1) # Define the list of glyph names to convert to SVG if options.gnames_to_generate: glyphNamesList = sorted(options.gnames_to_generate) else: glyphNamesList = sorted(glyphOrder) # Confirm that there's something to process if not glyphNamesList: print("The fonts and options provided can't produce any SVG files.", file=sys.stdout) return # Define the list of glyph names to skip glyphNamesToSkipList = [".notdef"] if options.gnames_to_exclude: glyphNamesToSkipList.extend(options.gnames_to_exclude) # Determine which glyph names need to be saved in a nested folder glyphNamesToSaveInNestedFolder = get_gnames_to_save_in_nested_folder( glyphNamesList) nestedFolderPath = None filesSaved = 0 unnamedNum = 1 # Write the SVG files by iterating over the entries in the SVG table. # The process assumes that each SVG document contains only one 'id' value, # i.e. the artwork for two or more glyphs is not included in a single SVG. for svgItemsList in svgTable.docList: svgItemData, startGID, endGID = svgItemsList if options.reset_viewbox: svgItemData = resetViewBox(svgItemData) while (startGID != endGID + 1): try: gName = glyphOrder[startGID] except IndexError: gName = "_unnamed{}".format(unnamedNum) glyphNamesList.append(gName) unnamedNum += 1 print("WARNING: The SVG table references a glyph ID (#{}) " "which the font does not contain.\n" " The artwork will be saved as '{}.svg'.".format( startGID, gName), file=sys.stderr) if gName not in glyphNamesList or gName in glyphNamesToSkipList: startGID += 1 continue # Create the output folder. # This may be necessary if the folder was not provided. # The folder is made this late in the process because # only now it's clear that's needed. create_folder(outputFolderPath) # Create the nested folder, if there are conflicting glyph names. if gName in glyphNamesToSaveInNestedFolder: folderPath = create_nested_folder(nestedFolderPath, outputFolderPath) else: folderPath = outputFolderPath svgFilePath = os.path.join(folderPath, gName + '.svg') write_file(svgFilePath, svgItemData) filesSaved += 1 startGID += 1 font.close() final_message(filesSaved)
def _ttFont(font_file): return ttLib.TTFont(font_file)
def font(): font = ttLib.TTFont() font.setGlyphOrder(["glyph%05d" % i for i in range(30)]) return font
def opener(path): font = ttLib.TTFont() font.importXML(path) return font
def __init__(self, font_file): """传入woff文件实例化, 调用 convert 方法转换加密字体""" MAPPING_FILE = './character' self.font = ttLib.TTFont(font_file) self.table = pickle.load(open(MAPPING_FILE, 'rb'))
def glyphOrders(self, fontpathlist): ftpath2glyphorder = dict() for fpath in fontpathlist: ftpath2glyphorder[fpath] = ttLib.TTFont(fpath).getGlyphOrder() return ftpath2glyphorder
def main(): TTF_FILES = [] parser = argparse.ArgumentParser() parser.add_argument('-l', '--license', help='show license terms', action='store_true') parser.add_argument('-f', '--ttf_folder', default='C:\\Windows\\Fonts\\', help='Folder where ttf files are stored') parser.add_argument( '-o', '--output_folder', default='..\\bmh_fonts', help= 'Folder where bitmapheader output files will be stored. A subfolder for each Font will be created under the directory.' ) parser.add_argument('-c', '--character_filename', default='..//characters_digits.txt', help='filename for characters to be processed') parser.add_argument( '--font', default='', help= 'Define Font Name to be processed. Name should include modifier like Bold or Italic. If none is given, all fonts in folder will be processed.' ) parser.add_argument('-s', '--fontsize', default='32', choices=['24', '32', '40', '48', '56', '64', 'all'], help='Fontsize (Fontheight) in pixels. Default: 32') parser.add_argument('--variable_width', default=False, action='store_true', help='Variable width of characters.') parser.add_argument( '--progmem', dest='progmem', default=False, action='store_true', help= 'C Variable declaration adds PROGMEM to character arrays. Useful to store the characters in porgram memory for AVR Microcontrollers with limited Flash or EEprom' ) parser.add_argument( '-p', '--print_ascii', dest='print_ascii', default=False, action='store_true', help='Print each character as ASCII Art on commandline, for debugging') args = parser.parse_args() print_program_header() if len(sys.argv) == 1: parser.print_help() return (1) elif (args.license): print_license() return (0) else: progmem = args.progmem print_ascii = args.print_ascii # Folder to iterate on ttf_searchfolder = args.ttf_folder output_folder = args.output_folder if not (os.path.exists(output_folder)): os.mkdir(output_folder) if not (os.path.exists(ttf_searchfolder)): print('TTF Folder does not exist') return (-1) variable_width = args.variable_width Target_Font = args.font if not (Target_Font == ''): ttf_filename = get_ttf_filename(Target_Font, ttf_searchfolder) if (ttf_filename == -1): print('No font with name: ' + Target_Font + ' found') return (-1) else: ttf_file = {'dir': ttf_searchfolder, 'filename': ttf_filename} TTF_FILES.append(ttf_file) else: TTF_FILES = search_ttf_folder(ttf_searchfolder) # Definition of Font Heights and offsets font_heights = [24, 32, 40, 48, 56, 64] font_yoffsets = [3, 5, 7, 8, 9, 10] if (args.fontsize == 'all'): height_indices = range(len(font_heights)) else: height_indices = [font_heights.index(int(args.fontsize))] # Read characters from file [character_line, chars] = read_character_file(args.character_filename) # Start logging logfile = logfile_open(output_folder) # Main Loop for ttf_file in TTF_FILES: # Generate and check file paths and ttf_filename = ttf_file['filename'] ttf_filepath = os.path.abspath(ttf_file['dir']) ttf_absolute_filename = ttf_filepath + '\\' + ttf_filename tt = ttLib.TTFont(ttf_absolute_filename) fm = tt['name'].names[4].string Font = fm.decode('utf-8') Font = re.sub('\x00', '', Font) output_bmh_folder = output_folder + '\\' + Font if not (os.path.exists(output_bmh_folder)): os.mkdir(output_bmh_folder) for height_idx in height_indices: width_array = [] # initialize PIL Image height = font_heights[height_idx] width = int(height * 0.75) yoffset = font_yoffsets[height_idx] # Filename Definitions filename = Font + '_' + str(height) # General Filename h_filename = output_bmh_folder + '\\' + filename + '.h' # Outputfile for font png_filename = output_bmh_folder + '\\' + filename + '.png' # Outputfile for font # define PILfont size = [width, height] PILfont = ImageFont.truetype(ttf_absolute_filename, int(height * 1.25)) # Open BMH file and start writing outfile = write_bmh_head(h_filename, Font, height) for char in chars: # Create pixel image with PIL image = Image.new('1', size, color=255) draw = ImageDraw.Draw(image) draw.text((0, -yoffset), char, font=PILfont) # Calculate byte arrays and write to file if (variable_width): [zero_col_cnt_left, zero_col_cnt_right ] = calculate_char_width(image, width, height) char_width = width - zero_col_cnt_right - zero_col_cnt_left x_offset = zero_col_cnt_left else: char_width = width x_offset = 0 width_array.append(str(char_width)) dot_array = get_pixel_byte(image, height, char_width, x_offset) write_bmh_char(outfile, char, dot_array, progmem) if (print_ascii): print_char(image, height, char_width, x_offset) # write tail and close bmh file write_bmh_tail(outfile, width_array, character_line) # write Image picture with all characters write_pic_file(character_line, PILfont, width, height, png_filename) if (len(TTF_FILES) < 20): print(filename + '.h written') logfile_append(logfile, filename) print( '-------------------------------------------------------------------------' ) print("TTF2BMH Finished") logfile_close(logfile)
def swaper(self): ftpathList, swaped, unicodesInt = [], [], [] propLF = [ "zero.lf", "one.lf", "two.lf", "three.lf", "four.lf", "five.lf", "six.lf", "seven.lf", "eight.lf", "nine.lf", ] propOSF = [ "zero.osf", "one.osf", "two.osf", "three.osf", "four.osf", "five.osf", "six.osf", "seven.osf", "eight.osf", "nine.osf", ] tabOSF = [ "zero.tosf", "one.tosf", "two.tosf", "three.tosf", "four.tosf", "five.tosf", "six.tosf", "seven.tosf", "eight.tosf", "nine.tosf", ] salt = [ 'I.salt', 'IJ.salt', 'Iacute.salt', 'Ibreve.salt', 'uni01CF.salt', 'Icircumflex.salt', 'uni0208.salt', 'Idieresis.salt', 'uni1E2E.salt', 'Idotaccent.salt', 'uni1ECA.salt', 'Igrave.salt', 'uni1EC8.salt', 'uni020A.salt', 'Imacron.salt', 'Iogonek.salt', 'Itilde.salt', 'uni1E2C.salt', 'J.salt', 'Jcircumflex.salt', 'uni01C7.salt', 'uni01CA.salt', 'uniA7F7.salt', 'uni0406.salt', 'uni0407.salt', 'uni0408.salt', 'uni04C0.salt', 'uni04CF.salt', 'uni037F.salt', 'Iota.salt', 'Iotatonos.salt', 'Iotadieresis.salt', 'uni1F38.salt', 'uni1F39.salt', 'uni1F3A.salt', 'uni1F3B.salt', 'uni1F3C.salt', 'uni1F3D.salt', 'uni1F3E.salt', 'uni1F3F.salt', 'uni1FDA.salt', 'uni1FDB.salt', 'uni1FD8.salt', 'uni1FD9.salt', 'uni1D35.salt', 'uni1D36.salt' ] unicodeIJ = [ 73, 306, 205, 300, 463, 206, 520, 207, 7726, 304, 7882, 204, 7880, 522, 298, 302, 296, 7724, 74, 308, 455, 458, 42999, 1030, 1031, 1032, 1216, 1231, 895, 921, 906, 938, 7992, 7993, 7994, 7995, 7996, 7997, 7998, 7999, 8154, 8155, 8152, 8153, 7477, 7478 ] unicodesFig = [i for i in range(48, 58)] for path in self.fonts2merge_list: if os.path.basename(path).split("-")[0] in self.lgcfonts: ftpathList.append(path) tosfOsfLinningFonts = [ "NotoSans", "NotoSans-Italic", "NotoSerif", "NotoSerif-Italic", "NotoSansDisplay-Italic", "NotoSansDisplay", "NotoSerifDisplay", "NotoSerifDisplay-Italic" ] altIJFonts = [ "NotoSans", "NotoSans-Italic", "NotoSansDisplay", "NotoSansDisplay-Italic" ] if self.repoNames[0] in tosfOsfLinningFonts: if "tosf" in self.swapedstyles: swaped = tabOSF elif "osf" in self.swapedstyles: swaped = propOSF elif "plf" in self.swapedstyles: swaped = propLF if len(swaped) > 0: unicodesInt = unicodesFig if "Sans" in self.contrast: lgc = set(altIJFonts) & set(self.repoNames) if "altIJ" in self.swapedstyles and len(lgc) > 0: swaped = swaped + salt unicodesInt = unicodesInt + unicodeIJ for path in ftpathList: ft = ttLib.TTFont(path) cmap = ft['cmap'] go = ft.getGlyphOrder() outtables = [] for table in cmap.tables: modif = table.cmap for uni in unicodesInt: if uni in modif: if swaped[unicodesInt.index(uni)] in go: modif[uni] = swaped[unicodesInt.index(uni)] newtable = CmapSubtable.newSubtable(table.format) newtable.platformID = table.platformID newtable.platEncID = table.platEncID newtable.language = table.language newtable.cmap = modif outtables.append(newtable) cmap.tables = [] cmap.tables = outtables newpath = path.replace(".ttf", "_edited.ttf") ft.save(newpath) self.fonts2merge_list = self.listReplacer( path, newpath, self.fonts2merge_list)
def openFont(filename): font = ttLib.TTFont(filename) if font.sfntVersion == 'OTTO': sys.exit("Error: Need TTF font, got CFF") return font
def _get_font(fontname): font = _FONT_CACHE.get(fontname) if not font: font = ttLib.TTFont(fontname) _FONT_CACHE[fontname] = font return font
def generate(in_file, format='.ttf', out_file=None): font = ttLib.TTFont(in_file) if out_file is None: out_file = in_file + format font.save(out_file)
#!/usr/bin/env python import sys from fontTools import ttLib font = ttLib.TTFont(sys.argv[1]) MS_Platform_ID = 3 MS_Enc_ID = 1 MS_Lang_US_ID = 0x409 FullName_ID = 4 name = font["name"] cff = font["CFF "] psname = cff.cff.fontNames[0] # set MS full name to psname # per name table spec fullname = name.getName(FullName_ID, MS_Platform_ID, MS_Enc_ID, MS_Lang_US_ID) fullname.string = psname.encode("utf_16_be") font.save(sys.argv[1] + ".post")
def dir_font_info( fonts_dir: str = "", info_file_dir: str = "font_info", info_filename: str = "font_info.json", ): available_font_extension_set: set = [ ".ttf", ".ttc", ".otf", ".woff", ".woff2", ] if not fonts_dir: if os.name != "nt": raise OSError("only available in Windows") windows_fonts_dir: str = os.path.join(os.environ["SystemRoot"], "Fonts") fonts_dir = windows_fonts_dir FONTTOOLS_NAME_ID_FAMILY = 1 FONTTOOLS_NAME_ID_NAME = 4 FONTTOOLS_PLATFORM_ID_WINDOWS = 3 LCID_EN = 1033 update_font_info_bool: bool = True info_file_dir = os.path.abspath(info_file_dir) if not os.path.isdir(info_file_dir): os.makedirs(info_file_dir) info_filepath: str = os.path.join(info_file_dir, info_filename) font_info_dict: dict = dict(font_info_list=[]) if os.path.isfile(info_filepath): font_info_dict = load_config(info_filepath) all_font_filename_list: list = [ filename for filename in os.listdir(fonts_dir) if any(filename.lower().endswith(ext) for ext in available_font_extension_set) ] all_font_filepath_list: list = [ os.path.join(fonts_dir, filename) for filename in all_font_filename_list ] info_existed_font_filepath_set: set = set( single_font_info_dict["filepath"] for single_font_info_dict in font_info_dict["font_info_list"]) if set(all_font_filepath_list) == info_existed_font_filepath_set: update_font_info_bool = False if update_font_info_bool: font_info_dict = dict(font_info_list=[]) for font_path in all_font_filepath_list: font_num: int = 1 with open(font_path, "rb") as file: font_sfnt_version: str = "" file.seek(0) font_sfnt_version = file.read(4) file.seek(0) if font_sfnt_version == b"ttcf": header = readTTCHeader(file) font_num = header.numFonts file.seek(header.offsetTable[0]) data = file.read(sfntDirectorySize) if len(data) != sfntDirectorySize: font_error_warning_str: str = ( f"{font_path} is Not a Font Collection " "(not enough data), skip.") g_logger.log(logging.WARNING, font_error_warning_str) warnings.warn(font_error_warning_str, RuntimeWarning) continue data_dict: dict = sstruct.unpack(sfntDirectoryFormat, data) font_sfnt_version = data_dict["sfntVersion"] elif font_sfnt_version == b"wOFF": font_num = 1 data = file.read(woffDirectorySize) if len(data) != woffDirectorySize: font_error_warning_str: str = ( f"{font_path} is Not a WOFF font " "(not enough data), skip.") g_logger.log(logging.WARNING, font_error_warning_str) warnings.warn(font_error_warning_str, RuntimeWarning) continue data_dict: dict = sstruct.unpack(woffDirectoryFormat, data) font_sfnt_version = data_dict["sfntVersion"] else: font_num = 1 data = file.read(sfntDirectorySize) if len(data) != sfntDirectorySize: font_error_warning_str: str = ( f"{font_path} is Not a TrueType or OpenType font " "(not enough data), skip.") g_logger.log(logging.WARNING, font_error_warning_str) warnings.warn(font_error_warning_str, RuntimeWarning) continue data_dict: dict = sstruct.unpack(sfntDirectoryFormat, data) font_sfnt_version = data_dict["sfntVersion"] font_sfnt_version_tag = Tag(font_sfnt_version) if font_sfnt_version_tag not in ( "\x00\x01\x00\x00", "OTTO", "true", ): print(font_sfnt_version) print(font_sfnt_version_tag) font_error_warning_str: str = ( f"{font_path} is Not a TrueType or OpenType font " "(bad sfntVersion), skip.") g_logger.log(logging.WARNING, font_error_warning_str) warnings.warn(font_error_warning_str, RuntimeWarning) continue for font_num_index in range(font_num): tt_font = ttLib.TTFont(font_path, fontNumber=font_num_index) family_list: list = [] for name_record in tt_font["name"].names: if (name_record.nameID == FONTTOOLS_NAME_ID_FAMILY and name_record.platformID == FONTTOOLS_PLATFORM_ID_WINDOWS): record_str: str = "" try: record_str = name_record.toStr() except UnicodeDecodeError: encoding: str = chardet.detect( name_record.string)["encoding"] if encoding: record_str = name_record.string.decode( encoding) else: continue family_list.append(record_str) family_list = list(set(family_list)) single_font_info_dict: dict = dict( filepath=font_path, family_list=family_list, index=font_num_index, file_font_num=font_num, ) font_info_dict["font_info_list"].append(single_font_info_dict) save_config(info_filepath, font_info_dict) return font_info_dict["font_info_list"]
def test_tables_sorted_alphabetically(self): expected = sorted([t for t in self.tags if t != 'DSIG']) woff2font = ttLib.TTFont(self.file) self.assertEqual(expected, list(woff2font.reader.keys()))
def check_spreadsheet(src_file): filenames = set() prev_script_name = None fontdata = {} filedata = {} with open(src_file) as csvfile: reader = csv.DictReader(csvfile) for index, row in enumerate(reader): font = row['Fonts'].replace('\xc2\xa0', ' ').strip() hinting = row['Hinting'].strip() status = row['Status'].strip() accepted_version = row['Accepted Version'].strip() note = row['Note'].strip() # family script style (variant UI) weight, mostly m = re.match( r'Noto (Kufi|Naskh|Color Emoji|Emoji|Sans|Serif|Nastaliq)' r'(?: (.*?))?' r'(?: (UI))?' r' (Thin|Light|DemiLight|Regular|Medium|Bold Italic' r'|Bold|Black|Italic)(?: \(merged\))?$', font) if not m: m = re.match(r'Noto (Sans) (Myanmar) (UI)(.*)', font) if not m: print('could not parse Myanmar exception: "%s"' % font) continue style, script, ui, weight = m.groups() weight = weight or 'Regular' weight = weight.replace(' ', '') ui = ui or '' script = script or '' script = re.sub('-| ', '', script) style = style.replace(' ', '') ext = 'ttf' if script == 'CJK': ext = 'ttc' elif script.startswith('TTC'): ext = 'ttc' script = '' elif script == '(LGC)': script = '' elif script == 'UI': ui = 'UI' script = '' elif script == 'Phagspa': script = 'PhagsPa' elif script == 'SumeroAkkadianCuneiform': script = 'Cuneiform' fontname = ''.join( ['Noto', style, script, ui, '-', weight, '.', ext]) # print('%s:\n--> %s\n--> %s' % ( # font, str((style, script, ui, weight)), fontname)) if not hinting in ['hinted', 'hinted (CFF)', 'unhinted']: print('unrecognized hinting value \'%s\' on line %d (%s)' % (hinting, index, fontname)) continue hinted = 'hinted' if hinting in ['hinted', 'hinted (CFF)' ] else 'unhinted' if not status in [ 'In finishing', 'Released w. lint errors', 'Approved & Released', 'Approved & Not Released', 'In design', 'Design approved', 'Design re-approved', 'Released' ]: print('unrecognized status value \'%s\' on line %d (%s)' % (status, index, fontname)) continue expect_font = status in [ 'Released w. lint errors', 'Approved & Released', 'Approved & Not Released', 'Released' ] data = (fontname, (index, font, style, script, ui, weight), hinted, status, accepted_version, note, expect_font) filedata[hinted + '/' + fontname] = data # ok, now let's see if we can find these files all_noto = noto_fonts.get_noto_fonts() notodata = {('hinted' if f.is_hinted else 'unhinted') + '/' + path.basename(f.filepath): f for f in all_noto} noto_filenames = frozenset(notodata.keys()) spreadsheet_filenames = frozenset(k for k in filedata if filedata[k][6]) spreadsheet_extra = spreadsheet_filenames - noto_filenames spreadsheet_missing = noto_filenames - spreadsheet_filenames if spreadsheet_extra: print('spreadsheet extra:\n ' + '\n '.join(sorted(spreadsheet_extra))) if spreadsheet_missing: print('spreadsheet missing:\n ' + '\n '.join(sorted(spreadsheet_missing))) spreadsheet_match = spreadsheet_filenames & noto_filenames for filename in sorted(spreadsheet_match): data = filedata[filename] filepath = notodata[filename].filepath ttfont = ttLib.TTFont(filepath, fontNumber=0) font_version = font_data.printable_font_revision(ttfont) approved_version = data[4] if approved_version: warn = '!!!' if approved_version != font_version else '' print('%s%s version: %s approved: %s' % (warn, filename, font_version, approved_version)) else: print('%s version: %s' % (filename, font_version))
def setUpClass(cls): cls.file = BytesIO(CFF_WOFF2.getvalue()) cls.font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False) cls.font.importXML(OTX)
else: family = record.string if name and family: break return name, family if __name__ == '__main__': if (len(sys.argv[1:]) < 1): print "Usage: ", sys.argv[0], "Target(Font File)" sys.exit() else: fileFont = sys.argv[1] try: tt = ttLib.TTFont(fileFont) except: print "Font File Corrupted" else: fontName = shortName(tt)[1] lf = win32gui.LOGFONT() htr = windll.gdi32.AddFontResourceExA(fileFont, FR_PRIVATE, None) w = mainWindow() hwnd = w.CreateWindow() hdc = windll.user32.GetDC(hwnd) for fontsize in range(0, 1000, 100): lf.lfHeight = fontsize lf.lfFaceName = fontName #lf.lfFaceName=fontPath
import tabulate from fontTools import ttLib args = argparse.ArgumentParser( description='Print out italicAngle of the fonts') args.add_argument('font', nargs="+") args.add_argument('--csv', default=False, action='store_true') if __name__ == '__main__': arg = args.parse_args() headers = ['filename', 'italicAngle'] rows = [] for font in arg.font: ttfont = ttLib.TTFont(font) rows.append([os.path.basename(font), ttfont['post'].italicAngle]) def as_csv(rows): import csv import sys writer = csv.writer(sys.stdout) writer.writerows([headers]) writer.writerows(rows) sys.exit(0) if arg.csv: as_csv(rows) print(tabulate.tabulate(rows, headers, tablefmt="pipe"))
def getFuzzerInstance(folder, queue): return NativeFuzzer(os.path.join('fonts_extracted', folder), queue) if __name__ == '__main__': if (len(sys.argv[1:]) < 1): print 'Usage: {} font_directory'.format(sys.argv[0]) elif (len(sys.argv[1:]) == 2 and sys.argv[1] == 'd'): try: abspath = os.path.abspath(sys.argv[2]) print 'Testing font {}'.format(abspath) deploy(abspath, ttLib.TTFont(abspath)) except Exception as e: print '[E] Issues parsing font {}: {}'.format(sys.argv[2], e) # fuzz else: for i in range(0, 1): #break # 2] generate test cases numberOfTestCases = generateTestCases(sys.argv[1]) print '[*] Generated {} test cases'.format(numberOfTestCases) for i in os.listdir('testcases'):