def flattenGlyphs(input, fontNumber, output): font = TTFont(input, fontNumber=fontNumber) font.recalcBBoxes = False if "glyf" in font: for glyphName in font.getGlyphOrder(): glyph = font["glyf"][glyphName] coordinates, endPtsOfContours, flags = glyph.getCoordinates( font["glyf"]) glyph.numberOfContours = len(endPtsOfContours) glyph.coordinates = coordinates glyph.endPtsOfContours = endPtsOfContours glyph.flags = flags glyph.program = ttProgram.Program() font["glyf"][glyphName] = glyph elif "CFF " in font: cff = font["CFF "] fontName = cff.cff.fontNames[0] topDict = cff.cff[fontName] for glyphID in range(len(font.getGlyphOrder())): charString = topDict.CharStrings.charStringsIndex[glyphID] charString.decompile() localSubrs = getattr(charString.private, "Subrs", []) globalSubrs = charString.globalSubrs inlinedProgram = inlineProgram(localSubrs, globalSubrs, charString.program) charString.program = inlinedProgram if "Private" in topDict.rawDict and "Subrs" in topDict.Private.rawDict: topDict.Private.Subrs del topDict.Private.rawDict["Subrs"] del topDict.Private.Subrs topDict.GlobalSubrs.items = [] else: raise FlattenError("Could not flatten glyphs.") font.save(output)
def glyph(self, componentFlags: int = 0x4) -> Glyph: """ Returns a :py:class:`~._g_l_y_f.Glyph` object representing the glyph. """ if not self._isClosed(): raise PenError("Didn't close last contour.") components = self._buildComponents(componentFlags) glyph = Glyph() glyph.coordinates = GlyphCoordinates(self.points) glyph.coordinates.toInt() glyph.endPtsOfContours = self.endPts glyph.flags = array("B", self.types) self.init() if components: # If both components and contours were present, they have by now # been decomposed by _buildComponents. glyph.components = components glyph.numberOfContours = -1 else: glyph.numberOfContours = len(glyph.endPtsOfContours) glyph.program = ttProgram.Program() glyph.program.fromBytecode(b"") return glyph
def test_trim_remove_hinting_composite_glyph(self): glyphSet = {"dummy": TTGlyphPen(None).glyph()} pen = TTGlyphPen(glyphSet) pen.addComponent("dummy", (1, 0, 0, 1, 0, 0)) composite = pen.glyph() p = ttProgram.Program() p.fromAssembly(['SVTCA[0]']) composite.program = p glyphSet["composite"] = composite glyfTable = newTable("glyf") glyfTable.glyphs = glyphSet glyfTable.glyphOrder = sorted(glyphSet) composite.compact(glyfTable) self.assertTrue(hasattr(composite, "data")) # remove hinting from the compacted composite glyph, without expanding it composite.trim(remove_hinting=True) # check that, after expanding the glyph, we have no instructions composite.expand(glyfTable) self.assertFalse(hasattr(composite, "program")) # now remove hinting from expanded composite glyph composite.program = p composite.trim(remove_hinting=True) # check we have no instructions self.assertFalse(hasattr(composite, "program")) composite.compact(glyfTable)
def glyph(self, componentFlags=0x4): assert self._isClosed(), "Didn't close last contour." components = [] for glyphName, transformation in self.components: if self.points: # can't have both, so decompose the glyph tpen = TransformPen(self, transformation) self.glyphSet[glyphName].draw(tpen) continue component = GlyphComponent() component.glyphName = glyphName if transformation[:4] != (1, 0, 0, 1): component.transform = (transformation[:2], transformation[2:4]) component.x, component.y = transformation[4:] component.flags = componentFlags components.append(component) glyph = Glyph() glyph.coordinates = GlyphCoordinates(self.points) glyph.endPtsOfContours = self.endPts glyph.flags = array("B", self.types) self.init() if components: glyph.components = components glyph.numberOfContours = -1 else: glyph.numberOfContours = len(glyph.endPtsOfContours) glyph.program = ttProgram.Program() glyph.program.fromBytecode(b"") return glyph
def main(): args = parser.parse_args() # Open the font file supplied as the first argument on the command line fontfile_in = os.path.abspath(args.fontfile_in[0]) font = ttLib.TTFont(fontfile_in) # Save a backup backupfont = '{}-backup-fonttools-prep-gasp{}'.format( fontfile_in[0:-4], fontfile_in[-4:]) # print "Saving to ", backupfont font.save(backupfont) print(backupfont, " saved.") # Print the Gasp table if "gasp" in font: print("GASP was: ", font["gasp"].gaspRange) else: print("GASP wasn't there") # Print the PREP table if "prep" in font: old_program = ttProgram.Program.getAssembly(font["prep"].program) print("PREP was:\n\t" + "\n\t".join(old_program)) else: print("PREP wasn't there") # Create a new GASP table gasp = ttLib.newTable("gasp") # Set GASP to the magic number gasp.gaspRange = {0xFFFF: 15} # Create a new hinting program program = ttProgram.Program() assembly = ['PUSHW[]', '511', 'SCANCTRL[]', 'PUSHB[]', '4', 'SCANTYPE[]'] program.fromAssembly(assembly) # Create a new PREP table prep = ttLib.newTable("prep") # Insert the magic program into it prep.program = program # Add the tables to the font, replacing existing ones font["gasp"] = gasp font["prep"] = prep # Print the Gasp table print("GASP now: ", font["gasp"].gaspRange) # Print the PREP table current_program = ttProgram.Program.getAssembly(font["prep"].program) print("PREP now:\n\t" + "\n\t".join(current_program)) # Save the new file with the name of the input file fontfile_out = os.path.abspath(args.fontfile_out[0]) font.save(fontfile_out) print(fontfile_out, " saved.")
def _decodeInstructions(self, glyph): glyphStream = self.glyphStream instructionStream = self.instructionStream instructionLength, glyphStream = unpack255UShort(glyphStream) glyph.program = ttProgram.Program() glyph.program.fromBytecode(instructionStream[:instructionLength]) self.glyphStream = glyphStream self.instructionStream = instructionStream[instructionLength:]
def glyph(self, componentFlags=0x4): assert self._isClosed(), "Didn't close last contour." if self.handleOverflowingTransforms: # we can't encode transform values > 2 or < -2 in F2Dot14, # so we must decompose the glyph if any transform exceeds these overflowing = any(s > 2 or s < -2 for (glyphName, transformation) in self.components for s in transformation[:4]) components = [] for glyphName, transformation in self.components: if (self.points or (self.handleOverflowingTransforms and overflowing)): # can't have both coordinates and components, so decompose try: baseGlyph = self.glyphSet[glyphName] except KeyError: self.log.debug( "can't decompose non-existing component '%s'; skipped", glyphName) continue else: tpen = TransformPen(self, transformation) baseGlyph.draw(tpen) continue component = GlyphComponent() component.glyphName = glyphName component.x, component.y = transformation[4:] transformation = transformation[:4] if transformation != (1, 0, 0, 1): if (self.handleOverflowingTransforms and any(MAX_F2DOT14 < s <= 2 for s in transformation)): # clamp values ~= +2.0 so we can keep the component transformation = tuple( MAX_F2DOT14 if MAX_F2DOT14 < s <= 2 else s for s in transformation) component.transform = (transformation[:2], transformation[2:]) component.flags = componentFlags components.append(component) glyph = Glyph() glyph.coordinates = GlyphCoordinates(self.points) glyph.endPtsOfContours = self.endPts glyph.flags = array("B", self.types) self.init() if components: glyph.components = components glyph.numberOfContours = -1 else: glyph.numberOfContours = len(glyph.endPtsOfContours) glyph.program = ttProgram.Program() glyph.program.fromBytecode(b"") return glyph
def generateFont(options, font): generateFeatures(font, options) from datetime import datetime info = font.info major, minor = options.version.split(".") info.versionMajor, info.versionMinor = int(major), int(minor) year = datetime.now().year info.copyright = f"Copyright 2010-{year} The Amiri Project Authors (https://github.com/alif-type/amiri)." info.openTypeNameLicense = "This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: https://scripts.sil.org/OFL" info.openTypeNameLicenseURL = "https://scripts.sil.org/OFL" if options.output.endswith(".ttf"): from fontTools.ttLib import newTable from fontTools.ttLib.tables import ttProgram otf = compileTTF(font, inplace=True, removeOverlaps=True, overlapsBackend="pathops", featureWriters=[]) otf["DSIG"] = DSIG = newTable("DSIG") DSIG.ulVersion = 1 DSIG.usFlag = 0 DSIG.usNumSigs = 0 DSIG.signatureRecords = [] otf["prep"] = prep = newTable("prep") prep.program = ttProgram.Program() prep.program.fromAssembly( ['PUSHW[]', '511', 'SCANCTRL[]', 'PUSHB[]', '4', 'SCANTYPE[]']) else: import cffsubr otf = compileOTF(font, inplace=True, optimizeCFF=0, removeOverlaps=True, overlapsBackend="pathops", featureWriters=[]) cffsubr.subroutinize(otf) if info.styleMapStyleName and "italic" in info.styleMapStyleName: otf['name'].names = [n for n in otf['name'].names if n.nameID != 17] for name in otf['name'].names: if name.nameID == 2: name.string = info.styleName glyf = otf.get("glyf") if glyf: from fontTools.ttLib.tables._g_l_y_f import UNSCALED_COMPONENT_OFFSET for name, glyph in glyf.glyphs.items(): glyph.expand(glyf) if glyph.isComposite(): for component in glyph.components: component.flags |= UNSCALED_COMPONENT_OFFSET return otf
def main(): font = TTFont(sys.argv[1]) # Drop VOLT table if "TSIV" in font: del font["TSIV"] # Add STAT table os2 = font["OS/2"] italic = bool(os2.fsSelection & (1 << 0)) fvar = font["fvar"] axes = [dict(tag=a.axisTag, name=a.axisNameID) for a in fvar.axes] if italic: value = dict(value=italic, name="Italic") else: value = dict(value=italic, name="Roman", flags=0x0002, linkedValue=1) axes.append(dict(tag="ital", name="Italic", values=[value])) buildStatTable(font, axes) # Prune name table names = [n for n in font["name"].names if n.platformID == 3] # Drop Regular from Roman font names if not italic: for name in names: if name.nameID in (3, 6): name.string = str(name).replace("-Regular", "") if name.nameID == 4: name.string = str(name).replace(" Regular", "") font["name"].names = names font["OS/2"].usMaxContext = maxCtxFont(font) font["DSIG"] = DSIG = newTable("DSIG") DSIG.ulVersion = 1 DSIG.usFlag = 0 DSIG.usNumSigs = 0 DSIG.signatureRecords = [] if "glyf" in font and "prep" not in font: # Google Fonts “smart dropout control” font["prep"] = prep = newTable("prep") prep.program = ttProgram.Program() prep.program.fromAssembly( ["PUSHW[]", "511", "SCANCTRL[]", "PUSHB[]", "4", "SCANTYPE[]"] ) if "MVAR" in font: del font["MVAR"] font.save(sys.argv[1])
def test__bool__(): fpgm = table__f_p_g_m() assert not bool(fpgm) p = ttProgram.Program() fpgm.program = p assert not bool(fpgm) bc = bytearray([0]) p.fromBytecode(bc) assert bool(fpgm) p.bytecode.pop() assert not bool(fpgm)
def glyph(self): glyph = Glyph() glyph.coordinates = GlyphCoordinates(self.points) glyph.endPtsOfContours = self.endPts glyph.flags = array("B", self.types) glyph.components = self.components if glyph.components: assert not glyph.endPtsOfContours, ( "TrueType glyph can't have both contours and components.") glyph.numberOfContours = -1 else: glyph.numberOfContours = len(glyph.endPtsOfContours) glyph.program = ttProgram.Program() glyph.program.fromBytecode("") return glyph
def glyph(self, componentFlags=0x4): assert self._isClosed(), "Didn't close last contour." components = self._buildComponents(componentFlags) glyph = Glyph() glyph.coordinates = GlyphCoordinates(self.points) glyph.endPtsOfContours = self.endPts glyph.flags = array("B", self.types) self.init() if components: glyph.components = components glyph.numberOfContours = -1 else: glyph.numberOfContours = len(glyph.endPtsOfContours) glyph.program = ttProgram.Program() glyph.program.fromBytecode(b"") return glyph
def generateFont(options, font): generateFeatures(font, options) from datetime import datetime info = font.info major, minor = options.version.split(".") info.versionMajor, info.versionMinor = int(major), int(minor) year = datetime.now().year info.copyright = f"Copyright 2010-{year} The Amiri Project Authors (https://github.com/alif-type/amiri)." info.openTypeNameLicense = "This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: https://scripts.sil.org/OFL" info.openTypeNameLicenseURL = "https://scripts.sil.org/OFL" if options.output.endswith(".ttf"): from fontTools.ttLib import newTable from fontTools.ttLib.tables import ttProgram otf = compileTTF(font, inplace=True, removeOverlaps=True, overlapsBackend="pathops", featureWriters=[]) otf["prep"] = prep = newTable("prep") prep.program = ttProgram.Program() prep.program.fromAssembly( ['PUSHW[]', '511', 'SCANCTRL[]', 'PUSHB[]', '4', 'SCANTYPE[]']) else: otf = compileOTF(font, inplace=True, optimizeCFF=2, subroutinizer="cffsubr", removeOverlaps=True, overlapsBackend="pathops", featureWriters=[]) if info.styleMapStyleName and "italic" in info.styleMapStyleName: otf['name'].names = [n for n in otf['name'].names if n.nameID != 17] for name in otf['name'].names: if name.nameID == 2: name.string = info.styleName return otf
def glyph(self, componentFlags=0x4): """Returns a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.""" assert self._isClosed(), "Didn't close last contour." components = self._buildComponents(componentFlags) glyph = Glyph() glyph.coordinates = GlyphCoordinates(self.points) glyph.coordinates.toInt() glyph.endPtsOfContours = self.endPts glyph.flags = array("B", self.types) self.init() if components: glyph.components = components glyph.numberOfContours = -1 else: glyph.numberOfContours = len(glyph.endPtsOfContours) glyph.program = ttProgram.Program() glyph.program.fromBytecode(b"") return glyph
def fix_unhinted_font(ttFont): """Improve the appearance of an unhinted font on Win platforms by: - Add a new GASP table with a newtable that has a single range which is set to smooth. - Add a new prep table which is optimized for unhinted fonts. Args: ttFont: a TTFont instance """ gasp = newTable("gasp") # Set GASP so all sizes are smooth gasp.gaspRange = {0xFFFF: 15} program = ttProgram.Program() assembly = ["PUSHW[]", "511", "SCANCTRL[]", "PUSHB[]", "4", "SCANTYPE[]"] program.fromAssembly(assembly) prep = newTable("prep") prep.program = program ttFont["gasp"] = gasp ttFont["prep"] = prep
def instantiateVariableFont(varfont, location, inplace=False): """ Generate a static instance from a variable TTFont and a dictionary defining the desired location along the variable font's axes. The location values must be specified as user-space coordinates, e.g.: {'wght': 400, 'wdth': 100} By default, a new TTFont object is returned. If ``inplace`` is True, the input varfont is modified and reduced to a static font. """ if not inplace: # make a copy to leave input varfont unmodified stream = BytesIO() varfont.save(stream) stream.seek(0) varfont = TTFont(stream) fvar = varfont['fvar'] axes = {a.axisTag:(a.minValue,a.defaultValue,a.maxValue) for a in fvar.axes} loc = normalizeLocation(location, axes) if 'avar' in varfont: maps = varfont['avar'].segments loc = {k: piecewiseLinearMap(v, maps[k]) for k,v in loc.items()} # Quantize to F2Dot14, to avoid surprise interpolations. loc = {k:floatToFixedToFloat(v, 14) for k,v in loc.items()} # Location is normalized now log.info("Normalized location: %s", loc) if 'gvar' in varfont: log.info("Mutating glyf/gvar tables") 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) 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) else: glyf = None if 'cvar' in varfont: log.info("Mutating cvt/cvar tables") 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] += otRound(delta) if 'CFF2' in varfont: log.info("Mutating CFF2 table") glyphOrder = varfont.getGlyphOrder() CFF2 = varfont['CFF2'] topDict = CFF2.cff.topDictIndex[0] vsInstancer = VarStoreInstancer(topDict.VarStore.otVarStore, fvar.axes, loc) interpolateFromDeltas = vsInstancer.interpolateFromDeltas interpolate_cff2_PrivateDict(topDict, interpolateFromDeltas) CFF2.desubroutinize() interpolate_cff2_charstrings(topDict, interpolateFromDeltas, glyphOrder) interpolate_cff2_metrics(varfont, topDict, glyphOrder, loc) del topDict.rawDict['VarStore'] del topDict.VarStore if 'MVAR' in varfont: log.info("Mutating MVAR table") mvar = varfont['MVAR'].table varStoreInstancer = VarStoreInstancer(mvar.VarStore, fvar.axes, loc) records = mvar.ValueRecord for rec in records: mvarTag = rec.ValueTag if mvarTag not in MVAR_ENTRIES: continue tableTag, itemName = MVAR_ENTRIES[mvarTag] delta = otRound(varStoreInstancer[rec.VarIdx]) if not delta: continue setattr(varfont[tableTag], itemName, getattr(varfont[tableTag], itemName) + delta) log.info("Mutating FeatureVariations") for tableTag in 'GSUB','GPOS': if not tableTag in varfont: continue table = varfont[tableTag].table if not hasattr(table, 'FeatureVariations'): continue variations = table.FeatureVariations for record in variations.FeatureVariationRecord: applies = True for condition in record.ConditionSet.ConditionTable: if condition.Format == 1: axisIdx = condition.AxisIndex axisTag = fvar.axes[axisIdx].axisTag Min = condition.FilterRangeMinValue Max = condition.FilterRangeMaxValue v = loc[axisTag] if not (Min <= v <= Max): applies = False else: applies = False if not applies: break if applies: assert record.FeatureTableSubstitution.Version == 0x00010000 for rec in record.FeatureTableSubstitution.SubstitutionRecord: table.FeatureList.FeatureRecord[rec.FeatureIndex].Feature = rec.Feature break del table.FeatureVariations if 'GDEF' in varfont and varfont['GDEF'].table.Version >= 0x00010003: log.info("Mutating GDEF/GPOS/GSUB tables") gdef = varfont['GDEF'].table instancer = VarStoreInstancer(gdef.VarStore, fvar.axes, loc) merger = MutatorMerger(varfont, loc) merger.mergeTables(varfont, [varfont], ['GDEF', 'GPOS']) # Downgrade GDEF. del gdef.VarStore gdef.Version = 0x00010002 if gdef.MarkGlyphSetsDef is None: del gdef.MarkGlyphSetsDef gdef.Version = 0x00010000 if not (gdef.LigCaretList or gdef.MarkAttachClassDef or gdef.GlyphClassDef or gdef.AttachList or (gdef.Version >= 0x00010002 and gdef.MarkGlyphSetsDef)): del varfont['GDEF'] addidef = False if glyf: for glyph in glyf.glyphs.values(): if hasattr(glyph, "program"): instructions = glyph.program.getAssembly() # If GETVARIATION opcode is used in bytecode of any glyph add IDEF addidef = any(op.startswith("GETVARIATION") for op in instructions) if addidef: break if addidef: log.info("Adding IDEF to fpgm table for GETVARIATION opcode") asm = [] if 'fpgm' in varfont: fpgm = varfont['fpgm'] asm = fpgm.program.getAssembly() else: fpgm = newTable('fpgm') fpgm.program = ttProgram.Program() varfont['fpgm'] = fpgm asm.append("PUSHB[000] 145") asm.append("IDEF[ ]") args = [str(len(loc))] for a in fvar.axes: args.append(str(floatToFixed(loc[a.axisTag], 14))) asm.append("NPUSHW[ ] " + ' '.join(args)) asm.append("ENDF[ ]") fpgm.program.fromAssembly(asm) # Change maxp attributes as IDEF is added if 'maxp' in varfont: maxp = varfont['maxp'] if hasattr(maxp, "maxInstructionDefs"): maxp.maxInstructionDefs += 1 else: setattr(maxp, "maxInstructionDefs", 1) if hasattr(maxp, "maxStackElements"): maxp.maxStackElements = max(len(loc), maxp.maxStackElements) else: setattr(maxp, "maxInstructionDefs", len(loc)) if 'name' in varfont: log.info("Pruning name table") exclude = {a.axisNameID for a in fvar.axes} for i in fvar.instances: exclude.add(i.subfamilyNameID) exclude.add(i.postscriptNameID) if 'ltag' in varfont: # Drop the whole 'ltag' table if all its language tags are referenced by # name records to be pruned. # TODO: prune unused ltag tags and re-enumerate langIDs accordingly excludedUnicodeLangIDs = [ n.langID for n in varfont['name'].names if n.nameID in exclude and n.platformID == 0 and n.langID != 0xFFFF ] if set(excludedUnicodeLangIDs) == set(range(len((varfont['ltag'].tags)))): del varfont['ltag'] varfont['name'].names[:] = [ n for n in varfont['name'].names if n.nameID not in exclude ] if "wght" in location and "OS/2" in varfont: varfont["OS/2"].usWeightClass = otRound( max(1, min(location["wght"], 1000)) ) if "wdth" in location: wdth = location["wdth"] for percent, widthClass in sorted(OS2_WIDTH_CLASS_VALUES.items()): if wdth < percent: varfont["OS/2"].usWidthClass = widthClass break else: varfont["OS/2"].usWidthClass = 9 if "slnt" in location and "post" in varfont: varfont["post"].italicAngle = max(-90, min(location["slnt"], 90)) log.info("Removing variable tables") for tag in ('avar','cvar','fvar','gvar','HVAR','MVAR','VVAR','STAT'): if tag in varfont: del varfont[tag] return varfont
def main(): parser = argparse.ArgumentParser(description="Merge Aref Ruqaa fonts.") parser.add_argument("file1", metavar="FILE", help="input font to process") parser.add_argument("file2", metavar="FILE", help="input font to process") parser.add_argument("--color", action="store_true", help="merge color tables") parser.add_argument("--family", metavar="STR", help="base family name") parser.add_argument("--suffix", metavar="STR", help="suffix to add to family name") parser.add_argument( "--out-file", metavar="FILE", help="output font to write", required=True ) args = parser.parse_args() configLogger(level=logging.ERROR) merger = merge.Merger() font = merger.merge([args.file1, args.file2]) if args.color: orig = TTFont(args.file1) font["CPAL"] = copy.deepcopy(orig["CPAL"]) font["COLR"] = copy.deepcopy(orig["COLR"]) name = font["name"] family = args.family psname = args.family.replace(" ", "") for rec in name.names: if rec.nameID in (1, 3, 4, 6): rec.string = ( str(rec) .replace(family, family + " " + args.suffix) .replace(psname, psname + args.suffix) ) unicodes = font.getBestCmap().keys() options = subset.Options() options.set( layout_features="*", layout_scripts="*", name_IDs="*", name_languages="*", notdef_outline=True, glyph_names=False, recalc_average_width=True, ) subsetter = subset.Subsetter(options=options) subsetter.populate(unicodes=unicodes) subsetter.subset(font) if "glyf" in font: from fontTools.ttLib import newTable from fontTools.ttLib.tables import ttProgram font["prep"] = prep = newTable("prep") prep.program = ttProgram.Program() prep.program.fromAssembly( ["PUSHW[]", "511", "SCANCTRL[]", "PUSHB[]", "4", "SCANTYPE[]"] ) font.save(args.out_file)
print("GASP wasn't there") # Print the PREP table if "prep" in font: print("PREP was: ", ttProgram.Program.getAssembly(font["prep"].program)) else: print("PREP wasn't there") # Create a new GASP table gasp = ttLib.newTable("gasp") # Set GASP to the magic number gasp.gaspRange = {65535: 15} # Create a new hinting program program = ttProgram.Program() assembly = ['PUSHW[]', '511', 'SCANCTRL[]', 'PUSHB[]', '4', 'SCANTYPE[]'] program.fromAssembly(assembly) # Create a new PREP table prep = ttLib.newTable("prep") # Insert the magic program into it prep.program = program # Add the tables to the font, replacing existing ones font["gasp"] = gasp font["prep"] = prep # Print the Gasp table
def build(instance, isTTF, version): font = instance.parent source = font.masters[0] fea, marks = makeFeatures(instance, source) source.blueValues = [] source.otherBlues = [] for zone in sorted(source.alignmentZones): pos = zone.position size = zone.size vals = sorted((pos, pos + size)) if pos == 0 or size >= 0: source.blueValues.extend(vals) else: source.otherBlues.extend(vals) fontinfo = f""" FontName {instance.fontName} OrigEmSqUnits {font.upm} DominantV {source.verticalStems} DominantH {source.horizontalStems} BaselineOvershoot {source.blueValues[0]} BaselineYCoord {source.blueValues[1]} LcHeight {source.blueValues[2]} LcOvershoot {source.blueValues[3] - source.blueValues[2]} CapHeight {source.blueValues[4]} CapOvershoot {source.blueValues[5] - source.blueValues[4]} AscenderHeight {source.blueValues[6]} AscenderOvershoot {source.blueValues[7] - source.blueValues[6]} Baseline5 {source.otherBlues[1]} Baseline5Overshoot {source.otherBlues[0] - source.otherBlues[1]} FlexOK true BlueFuzz 1 """ characterMap = {} glyphs = {} metrics = {} layerSet = {g.name: g.layers[source.id] for g in font.glyphs} if isTTF: from fontTools.pens.cu2quPen import Cu2QuPen from fontTools.pens.recordingPen import RecordingPen for glyph in font.glyphs: layer = glyph.layers[source.id] pen = RecordingPen() layer.draw(pen) layer.paths = [] layer.components = [] pen.replay(Cu2QuPen(layer.getPen(), 1.0, reverse_direction=True)) for glyph in font.glyphs: if not glyph.export and not isTTF: continue name = glyph.name for code in glyph.unicodes: characterMap[int(code, 16)] = name layer = glyph.layers[source.id] width = 0 if name in marks else layer.width pen = BoundsPen(layerSet) layer.draw(pen) metrics[name] = (width, pen.bounds[0] if pen.bounds else 0) if isTTF: from fontTools.pens.ttGlyphPen import TTGlyphPen pen = TTGlyphPen(layerSet) if layer.paths: # Decompose and remove overlaps. path = Path() layer.draw(DecomposePathPen(path, layerSet)) path.simplify(fix_winding=True, keep_starting_points=True) path.draw(pen) else: # Composite-only glyph, no need to decompose. layer.draw(FlattenComponentsPen(pen, layerSet)) glyphs[name] = pen.glyph() else: from fontTools.pens.t2CharStringPen import T2CharStringPen # Draw glyph and remove overlaps. path = Path() layer.draw(DecomposePathPen(path, layerSet)) path.simplify(fix_winding=True, keep_starting_points=True) # Build CharString. pen = T2CharStringPen(width, None) path.draw(pen) glyphs[name] = pen.getCharString() vendor = font.customParameters["vendorID"] names = { "copyright": font.copyright, "familyName": instance.familyName, "styleName": instance.name, "uniqueFontIdentifier": f"{version};{vendor};{instance.fontName}", "fullName": instance.fullName, "version": f"Version {version}", "psName": instance.fontName, "manufacturer": font.manufacturer, "designer": font.designer, "description": font.customParameters["description"], "vendorURL": font.manufacturerURL, "designerURL": font.designerURL, "licenseDescription": font.customParameters["license"], "licenseInfoURL": font.customParameters["licenseURL"], "sampleText": font.customParameters["sampleText"], } date = int(font.date.timestamp()) - epoch_diff fb = FontBuilder(font.upm, isTTF=isTTF) fb.updateHead(fontRevision=version, created=date, modified=date) fb.setupGlyphOrder(font.glyphOrder) fb.setupCharacterMap(characterMap) fb.setupNameTable(names, mac=False) fb.setupHorizontalHeader( ascent=source.ascender, descent=source.descender, lineGap=source.customParameters["typoLineGap"], ) if isTTF: fb.setupGlyf(glyphs) else: privateDict = { "BlueValues": source.blueValues, "OtherBlues": source.otherBlues, "StemSnapH": source.horizontalStems, "StemSnapV": source.verticalStems, "StdHW": source.horizontalStems[0], "StdVW": source.verticalStems[0], } fontInfo = { "FullName": names["fullName"], "Notice": names["copyright"].replace("©", "\(c\)"), "version": f"{version}", "Weight": instance.name, } fb.setupCFF(names["psName"], fontInfo, glyphs, privateDict) fb.setupHorizontalMetrics(metrics) codePages = [CODEPAGE_RANGES[v] for v in font.customParameters["codePageRanges"]] fb.setupOS2( version=4, sTypoAscender=source.ascender, sTypoDescender=source.descender, sTypoLineGap=source.customParameters["typoLineGap"], usWinAscent=source.ascender, usWinDescent=-source.descender, sxHeight=source.xHeight, sCapHeight=source.capHeight, achVendID=vendor, fsType=calcBits(font.customParameters["fsType"], 0, 16), fsSelection=calcFsSelection(instance), ulUnicodeRange1=calcBits(font.customParameters["unicodeRanges"], 0, 32), ulCodePageRange1=calcBits(codePages, 0, 32), ) underlineThickness = int(source.customParameters["underlineThickness"]) underlinePosition = int(source.customParameters["underlinePosition"]) fb.setupPost( keepGlyphNames=False, underlineThickness=underlineThickness, underlinePosition=underlinePosition + underlineThickness // 2, ) fb.font["meta"] = meta = newTable("meta") meta.data = {"dlng": "Arab", "slng": "Arab"} fb.addOpenTypeFeatures(fea) if isTTF: from fontTools.ttLib.tables import ttProgram fb.setupDummyDSIG() fb.font["gasp"] = gasp = newTable("gasp") gasp.gaspRange = {0xFFFF: 15} fb.font["prep"] = prep = newTable("prep") prep.program = ttProgram.Program() assembly = ["PUSHW[]", "511", "SCANCTRL[]", "PUSHB[]", "4", "SCANTYPE[]"] prep.program.fromAssembly(assembly) else: from cffsubr import subroutinize subroutinize(fb.font) return fb.font