def _merge_OTL(font, model, master_fonts, axisTags): log.info("Merging OpenType Layout tables") merger = VariationMerger(model, axisTags, font) merger.mergeTables(font, master_fonts, ['GPOS']) # TODO Merge GSUB # TODO Merge GDEF itself! store = merger.store_builder.finish() if not store.VarData: return try: GDEF = font['GDEF'].table assert GDEF.Version <= 0x00010002 except KeyError: font['GDEF']= newTable('GDEF') GDEFTable = font["GDEF"] = newTable('GDEF') GDEF = GDEFTable.table = ot.GDEF() GDEF.Version = 0x00010003 GDEF.VarStore = store # Optimize varidx_map = store.optimize() GDEF.remap_device_varidxes(varidx_map) if 'GPOS' in font: font['GPOS'].table.remap_device_varidxes(varidx_map)
def otf_to_ttf(ttFont, post_format=POST_FORMAT, **kwargs): assert ttFont.sfntVersion == "OTTO" assert "CFF " in ttFont glyphOrder = ttFont.getGlyphOrder() ttFont["loca"] = newTable("loca") ttFont["glyf"] = glyf = newTable("glyf") glyf.glyphOrder = glyphOrder glyf.glyphs = glyphs_to_quadratic(ttFont.getGlyphSet(), **kwargs) del ttFont["CFF "] ttFont["maxp"] = maxp = newTable("maxp") maxp.tableVersion = 0x00010000 maxp.maxZones = 1 maxp.maxTwilightPoints = 0 maxp.maxStorage = 0 maxp.maxFunctionDefs = 0 maxp.maxInstructionDefs = 0 maxp.maxStackElements = 0 maxp.maxSizeOfInstructions = 0 maxp.maxComponentElements = max( len(g.components if hasattr(g, 'components') else []) for g in glyf.glyphs.values()) post = ttFont["post"] post.formatType = post_format post.extraNames = [] post.mapping = {} post.glyphOrder = glyphOrder ttFont.sfntVersion = "\000\001\000\000"
def setupTable_glyf(self): """Make the glyf table.""" allGlyphs = self.allGlyphs if self.convertCubics: from cu2qu.pens import Cu2QuPen allGlyphs = {} for name, glyph in self.allGlyphs.items(): if isinstance(glyph, StubGlyph): allGlyphs[name] = glyph continue newGlyph = glyph.__class__() glyph.draw(Cu2QuPen( newGlyph.getPen(), self.cubicConversionError, reverse_direction=True)) allGlyphs[name] = newGlyph self.otf["loca"] = newTable("loca") self.otf["glyf"] = glyf = newTable("glyf") glyf.glyphs = {} glyf.glyphOrder = self.glyphOrder for name in self.glyphOrder: pen = TTGlyphPen(allGlyphs) allGlyphs[name].draw(pen) glyf[name] = pen.glyph()
def makeFont(self): cvt, cvar, fvar = newTable("cvt "), newTable("cvar"), newTable("fvar") font = {"cvt ": cvt, "cvar": cvar, "fvar": fvar} cvt.values = [0, 1000, -2000] Axis = getTableModule("fvar").Axis fvar.axes = [Axis(), Axis()] fvar.axes[0].axisTag, fvar.axes[1].axisTag = "wght", "wdth" return font, cvar
def colorize(font): COLR = newTable("COLR") CPAL = newTable("CPAL") glyf = font["glyf"] hmtx = font["hmtx"] CPAL.version = 0 COLR.version = 0 palette = list(GROUPS.values()) CPAL.palettes = [palette] CPAL.numPaletteEntries = len(palette) COLR.ColorLayers = {} glyphOrder = list(font.getGlyphOrder()) for name in glyphOrder: glyph = glyf[name] layers = [] if glyph.isComposite() and len(glyph.components) > 1: componentColors = [getGlyphColor(c.getComponentInfo()[0]) for c in glyph.components] if any(componentColors): for component in glyph.components: componentName, trans = component.getComponentInfo() componentColor = getGlyphColor(componentName) if componentColor is None: componentColor = 0xFFFF else: componentColor = palette.index(componentColor) if trans == (1, 0, 0, 1, 0, 0): layers.append(newLayer(componentName, componentColor)) else: newName = "%s.%s" % (componentName, hash(trans)) if newName not in font.glyphOrder: font.glyphOrder.append(newName) newGlyph = getTableModule("glyf").Glyph() newGlyph.numberOfContours = -1 newGlyph.components = [component] glyf.glyphs[newName] = newGlyph assert len(glyf.glyphs) == len(font.glyphOrder), (name, newName) width = hmtx[name][0] lsb = hmtx[componentName][1] + trans[4] hmtx.metrics[newName] = [width, lsb] layers.append(newLayer(newName, componentColor)) if not layers: color = getGlyphColor(name) if color is not None: layers = [newLayer(name, palette.index(color))] if layers: COLR[name] = layers font["COLR"] = COLR font["CPAL"] = CPAL
def setUp(self): self.font = font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False) font.setGlyphOrder(self.glyphOrder) font['head'] = ttLib.newTable('head') font['maxp'] = ttLib.newTable('maxp') font['loca'] = WOFF2LocaTable() font['glyf'] = WOFF2GlyfTable() for tag in self.transformedTags: font[tag].decompile(self.tables[tag], font)
def setUp(self): self.font = font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False) font.setGlyphOrder(self.glyphOrder) font["head"] = ttLib.newTable("head") font["maxp"] = ttLib.newTable("maxp") font["loca"] = WOFF2LocaTable() font["glyf"] = WOFF2GlyfTable() for tag in self.transformedTags: font[tag].decompile(self.tables[tag], font)
def makeFont(self, numGlyphs, numberOfMetrics): font = TTFont() maxp = font['maxp'] = newTable('maxp') maxp.numGlyphs = numGlyphs # from A to ... font.glyphOrder = [chr(i) for i in range(65, 65+numGlyphs)] headerTag = self.tableClass.headerTag font[headerTag] = newTable(headerTag) numberOfMetricsName = self.tableClass.numberOfMetricsName setattr(font[headerTag], numberOfMetricsName, numberOfMetrics) return font
def setupTable_glyf(self): """Make the glyf table.""" self.otf["loca"] = newTable("loca") self.otf["glyf"] = glyf = newTable("glyf") glyf.glyphs = {} glyf.glyphOrder = self.glyphOrder for name in self.glyphOrder: pen = TTGlyphPen(self.allGlyphs) self.allGlyphs[name].draw(pen) glyf[name] = pen.glyph()
def setupTable_glyf(self): """Make the glyf table.""" self.otf["loca"] = newTable("loca") self.otf["glyf"] = glyf = newTable("glyf") glyf.glyphs = {} glyf.glyphOrder = self.glyphOrder for glyph in self.ufo: pen = TTGlyphPen(self.ufo) glyph.draw(pen) glyf[glyph.name] = pen.glyph()
def makeFont(self, variations): glyphs = [".notdef", "space", "I"] Axis = getTableModule("fvar").Axis Glyph = getTableModule("glyf").Glyph glyf, fvar, gvar = newTable("glyf"), newTable("fvar"), newTable("gvar") font = FakeFont(glyphs) font.tables = {"glyf": glyf, "gvar": gvar, "fvar": fvar} glyf.glyphs = {glyph: Glyph() for glyph in glyphs} glyf.glyphs["I"].coordinates = [(10, 10), (10, 20), (20, 20), (20, 10)] fvar.axes = [Axis(), Axis()] fvar.axes[0].axisTag, fvar.axes[1].axisTag = "wght", "wdth" gvar.variations = variations return font, gvar
def test_toXML(self): font = TTFont(sfntVersion="\x00\x01\x00\x00") glyfTable = font['glyf'] = newTable('glyf') font['head'] = newTable('head') font['loca'] = newTable('loca') font['maxp'] = newTable('maxp') font['maxp'].decompile(self.maxpData, font) font['head'].decompile(self.headData, font) font['loca'].decompile(self.locaData, font) glyfTable.decompile(self.glyfData, font) out = UnicodeIO() font.saveXML(out) glyfXML = strip_ttLibVersion(out.getvalue()).splitlines() self.assertEqual(glyfXML, self.glyfXML)
def buildGSUB(): """Build a GSUB table from scratch.""" fontTable = newTable("GSUB") gsub = fontTable.table = ot.GSUB() gsub.Version = 0x00010001 # allow gsub.FeatureVariations gsub.ScriptList = ot.ScriptList() gsub.ScriptList.ScriptRecord = [] gsub.FeatureList = ot.FeatureList() gsub.FeatureList.FeatureRecord = [] gsub.LookupList = ot.LookupList() gsub.LookupList.Lookup = [] srec = ot.ScriptRecord() srec.ScriptTag = 'DFLT' srec.Script = ot.Script() srec.Script.DefaultLangSys = None srec.Script.LangSysRecord = [] langrec = ot.LangSysRecord() langrec.LangSys = ot.LangSys() langrec.LangSys.ReqFeatureIndex = 0xFFFF langrec.LangSys.FeatureIndex = [0] srec.Script.DefaultLangSys = langrec.LangSys gsub.ScriptList.ScriptRecord.append(srec) gsub.FeatureVariations = None return fontTable
def fix(self): if 'DSIG' in self.font: return False try: from fontTools.ttLib.tables.D_S_I_G_ import SignatureRecord except ImportError: error_message = ("The '{}' font does not have an existing" " digital signature proving its authenticity," " so Fontbakery needs to add one. To do this" " requires version 2.3 or later of Fonttools" " to be installed. Please upgrade at" " https://pypi.python.org/pypi/FontTools/2.4") logger.error(error_message.format(os.path.basename(self.fontpath))) return False newDSIG = ttLib.newTable("DSIG") newDSIG.ulVersion = 1 newDSIG.usFlag = 1 newDSIG.usNumSigs = 1 sig = SignatureRecord() sig.ulLength = 20 sig.cbSignature = 12 sig.usReserved2 = 0 sig.usReserved1 = 0 sig.pkcs7 = '\xd3M4\xd3M5\xd3M4\xd3M4' sig.ulFormat = 1 sig.ulOffset = 20 newDSIG.signatureRecords = [sig] self.font.tables["DSIG"] = newDSIG return True
def normalise_table(font, tag, padding=4): """ Return normalised table data. Keep 'font' instance unmodified. """ assert tag in ("glyf", "loca", "head") assert tag in font if tag == "head": origHeadFlags = font["head"].flags font["head"].flags |= 1 << 11 tableData = font["head"].compile(font) if font.sfntVersion in ("\x00\x01\x00\x00", "true"): assert {"glyf", "loca", "head"}.issubset(font.keys()) origIndexFormat = font["head"].indexToLocFormat if hasattr(font["loca"], "locations"): origLocations = font["loca"].locations[:] else: origLocations = [] glyfTable = ttLib.newTable("glyf") glyfTable.decompile(font.getTableData("glyf"), font) glyfTable.padding = padding if tag == "glyf": tableData = glyfTable.compile(font) elif tag == "loca": glyfTable.compile(font) tableData = font["loca"].compile(font) if tag == "head": glyfTable.compile(font) font["loca"].compile(font) tableData = font["head"].compile(font) font["head"].indexToLocFormat = origIndexFormat font["loca"].set(origLocations) if tag == "head": font["head"].flags = origHeadFlags return tableData
def _add_stat(font, axes): # for now we just get the axis tags and nameIDs from the fvar, # so we can reuse the same nameIDs which were defined in there. # TODO make use of 'axes' once it adds style attributes info: # https://github.com/LettError/designSpaceDocument/issues/8 if "STAT" in font: return fvarTable = font['fvar'] STAT = font["STAT"] = newTable('STAT') stat = STAT.table = ot.STAT() stat.Version = 0x00010002 axisRecords = [] for i, a in enumerate(fvarTable.axes): axis = ot.AxisRecord() axis.AxisTag = Tag(a.axisTag) axis.AxisNameID = a.axisNameID axis.AxisOrdering = i axisRecords.append(axis) axisRecordArray = ot.AxisRecordArray() axisRecordArray.Axis = axisRecords # XXX these should not be hard-coded but computed automatically stat.DesignAxisRecordSize = 8 stat.DesignAxisCount = len(axisRecords) stat.DesignAxisRecord = axisRecordArray # for the elided fallback name, we default to the base style name. # TODO make this user-configurable via designspace document stat.ElidedFallbackNameID = 2
def main(): # open the source font f = ttLib.TTFont("NyanTemplate.ttf") # mapping of image size to directory name sets = { 15: "pngs" } sbix = ttLib.newTable("sbix") go = f.getGlyphOrder() for s, d in sets.iteritems(): # make an empty bitmap set for current image size mySet = BitmapSet(size=s) for root, dirs, files in walk(d, topdown=False): for myFile in files: if myFile[-4:] == ".png": # use file name without suffix as glyph name # FIXME: filename clashes with case-sensitive glyph names glyphname = myFile[:-4] if glyphname in go: # only use files that have a matching glyph in the source font print glyphname img = open(join(root, myFile), "rb") imgData = img.read() img.close() # make a bitmap record for the current image myBitmap = Bitmap(glyphName=glyphname, imageFormatTag="png ", imageData=imgData) # add bitmap to current bitmap set mySet.bitmaps[glyphname] = myBitmap sbix.bitmapSets[s] = mySet # add sbix table to the source font f["sbix"] = sbix # save font under new name f.save("Nyan.ttf")
def normalise_table(font, tag, padding=4): """ Return normalised table data. Keep 'font' instance unmodified. """ assert tag in ('glyf', 'loca', 'head') assert tag in font if tag == 'head': origHeadFlags = font['head'].flags font['head'].flags |= (1 << 11) tableData = font['head'].compile(font) if font.sfntVersion in ("\x00\x01\x00\x00", "true"): assert {'glyf', 'loca', 'head'}.issubset(font.keys()) origIndexFormat = font['head'].indexToLocFormat if hasattr(font['loca'], 'locations'): origLocations = font['loca'].locations[:] else: origLocations = [] glyfTable = ttLib.newTable('glyf') glyfTable.decompile(font.getTableData('glyf'), font) glyfTable.padding = padding if tag == 'glyf': tableData = glyfTable.compile(font) elif tag == 'loca': glyfTable.compile(font) tableData = font['loca'].compile(font) if tag == 'head': glyfTable.compile(font) font['loca'].compile(font) tableData = font['head'].compile(font) font['head'].indexToLocFormat = origIndexFormat font['loca'].set(origLocations) if tag == 'head': font['head'].flags = origHeadFlags return tableData
def makeTable_cmap(ttf, glyphs): unicodeCMAP = {index: glyph for glyph in glyphs if glyph in ttf["glyf"].glyphs for index in glyphs[glyph][1]} macRoman = dict(CMAP_MACROMAN) macRomanCMAP = {index: macRoman[index] if index in macRoman and macRoman[index] in ttf["glyf"].glyphs else '.notdef' for index in range(256)} # Unicode cmap4_0_3 = cmap_format_4(4) cmap4_0_3.platformID = 0 cmap4_0_3.platEncID = 3 cmap4_0_3.language = 0 cmap4_0_3.cmap = unicodeCMAP # Mac Roman cmap0_1_0 = cmap_format_0(0) cmap0_1_0.platformID = 1 cmap0_1_0.platEncID = 0 cmap0_1_0.language = 0 cmap0_1_0.cmap = macRomanCMAP # Windows cmap4_3_1 = cmap_format_4(4) cmap4_3_1.platformID = 3 cmap4_3_1.platEncID = 1 cmap4_3_1.language = 0 cmap4_3_1.cmap = unicodeCMAP cmap = newTable("cmap") cmap.tableVersion = 0 cmap.tables = [cmap4_0_3, cmap0_1_0, cmap4_3_1] ttf["cmap"] = cmap
def test_decompile_not_enough_data(self): font = self.makeFont(numGlyphs=1, numberOfMetrics=1) mtxTable = newTable(self.tag) msg = "not enough '%s' table data" % self.tag with self.assertRaisesRegex(TTLibError, msg): mtxTable.decompile(b"\0\0\0", font)
def setupTable_vmtx(self): """ Make the vmtx table. **This should not be called externally.** Subclasses may override or supplement this method to handle the table creation in a different way if desired. """ self.otf["vmtx"] = vmtx = newTable("vmtx") vmtx.metrics = {} for glyphName, glyph in self.allGlyphs.items(): height = glyph.height verticalOrigin = _getVerticalOrigin(glyph) top = 0 if len(glyph) or len(glyph.components): # tsb should be consistent with glyf yMax, which is just # maximum y for coordinate data pen = ControlBoundsPen(self.ufo, ignoreSinglePoints=True) glyph.draw(pen) if pen.bounds is not None: top = pen.bounds[3] # take ceil of tsb/yMax, as fontTools does with max bounds vmtx[glyphName] = (_roundInt(height), int(math.ceil(verticalOrigin) - math.ceil(top)))
def _merge_OTL(font, model, master_fonts, axisTags, base_idx): log.info("Merging OpenType Layout tables") merger = VariationMerger(model, axisTags, font) merge_tables(font, merger, master_fonts, axisTags, base_idx, ['GPOS']) store = merger.store_builder.finish() try: GDEF = font['GDEF'].table assert GDEF.Version <= 0x00010002 except KeyError: font['GDEF']= newTable('GDEF') GDEFTable = font["GDEF"] = newTable('GDEF') GDEF = GDEFTable.table = ot.GDEF() GDEF.Version = 0x00010003 GDEF.VarStore = store
def test_toXML_v1(self): name = FakeNameTable({258: "Spring theme", 259: "Winter theme", 513: "darks", 515: "lights"}) cpal = newTable('CPAL') ttFont = {"name": name, "CPAL": cpal} cpal.decompile(CPAL_DATA_V1, ttFont) self.assertEqual(getXML(cpal.toXML, ttFont), '<version value="1"/>' '<numPaletteEntries value="3"/>' '<palette index="0" label="258" type="1">' ' <!-- Spring theme -->' ' <color index="0" value="#CAFECAFE"/>' ' <color index="1" value="#22110033"/>' ' <color index="2" value="#66554477"/>' '</palette>' '<palette index="1" label="259" type="2">' ' <!-- Winter theme -->' ' <color index="0" value="#59413127"/>' ' <color index="1" value="#42424242"/>' ' <color index="2" value="#13330037"/>' '</palette>' '<paletteEntryLabels>' ' <label index="0" value="513"/><!-- darks -->' ' <label index="1" value="514"/>' ' <label index="2" value="515"/><!-- lights -->' '</paletteEntryLabels>')
def test_delitem(self): mtxTable = newTable(self.tag) mtxTable.metrics = {'A': (0, 0)} del mtxTable['A'] self.assertTrue('A' not in mtxTable.metrics)
def test_compile_struct_out_of_range(self): font = self.makeFont(numGlyphs=1, numberOfMetrics=1) mtxTable = font[self.tag] = newTable(self.tag) mtxTable.metrics = {'A': (0xFFFF+1, -0x8001)} with self.assertRaises(struct.error): mtxTable.compile(font)
def test_addMultilingualName(self): # Microsoft Windows has language codes for “English” (en) # and for “Standard German as used in Switzerland” (de-CH). # In this case, we expect that the implementation just # encodes the name for the Windows platform; Apple platforms # have been able to decode Windows names since the early days # of OSX (~2001). However, Windows has no language code for # “Swiss German as used in Liechtenstein” (gsw-LI), so we # expect that the implementation populates the 'ltag' table # to represent that particular, rather exotic BCP47 code. font = FakeFont(glyphs=[".notdef", "A"]) nameTable = font.tables['name'] = newTable("name") with CapturingLogHandler(log, "WARNING") as captor: widthID = nameTable.addMultilingualName({ "en": "Width", "de-CH": "Breite", "gsw-LI": "Bräiti", }, ttFont=font) self.assertEqual(widthID, 256) xHeightID = nameTable.addMultilingualName({ "en": "X-Height", "gsw-LI": "X-Hööchi" }, ttFont=font) self.assertEqual(xHeightID, 257) captor.assertRegex("cannot add Windows name in language gsw-LI") self.assertEqual(names(nameTable), [ (256, 0, 4, 0, "Bräiti"), (256, 3, 1, 0x0409, "Width"), (256, 3, 1, 0x0807, "Breite"), (257, 0, 4, 0, "X-Hööchi"), (257, 3, 1, 0x0409, "X-Height"), ]) self.assertEqual(set(font.tables.keys()), {"ltag", "name"}) self.assertEqual(font["ltag"].tags, ["gsw-LI"])
def _add_gvar(font, model, master_ttfs): log.info("Generating gvar") assert "gvar" not in font gvar = font["gvar"] = newTable('gvar') gvar.version = 1 gvar.reserved = 0 gvar.variations = {} for glyph in font.getGlyphOrder(): allData = [_GetCoordinates(m, glyph) for m in master_ttfs] allCoords = [d[0] for d in allData] allControls = [d[1] for d in allData] control = allControls[0] if (any(c != control for c in allControls)): warnings.warn("glyph %s has incompatible masters; skipping" % glyph) continue del allControls # Update gvar gvar.variations[glyph] = [] deltas = model.getDeltas(allCoords) supports = model.supports assert len(deltas) == len(supports) for i,(delta,support) in enumerate(zip(deltas[1:], supports[1:])): var = TupleVariation(support, delta) gvar.variations[glyph].append(var)
def makeDSIG(tt_font): ''' Add a dummy DSIG table to an OpenType-TTF font, so positioning features work in Office applications on Windows. thanks to Ben Kiel on TypeDrawers: http://typedrawers.com/discussion/192/making-ot-ttf-layout-features-work-in-ms-word-2010 ''' from fontTools import ttLib from fontTools.ttLib.tables.D_S_I_G_ import SignatureRecord newDSIG = ttLib.newTable("DSIG") newDSIG.ulVersion = 1 newDSIG.usFlag = 1 newDSIG.usNumSigs = 1 sig = SignatureRecord() sig.ulLength = 20 sig.cbSignature = 12 sig.usReserved2 = 0 sig.usReserved1 = 0 sig.pkcs7 = '\xd3M4\xd3M5\xd3M4\xd3M4' sig.ulFormat = 1 sig.ulOffset = 20 newDSIG.signatureRecords = [sig] tt_font["DSIG"] = newDSIG # ugly but necessary -> so all tables are added to ttfont # tt_font.lazy = False for key in tt_font.keys(): print tt_font[key]
def fourStyleFamily(self, position, suffix=None): """ Replaces the name table and certain OS/2 values with those that will make a four-style family. """ f = self.f source = TTFont(fourStyleFamilySources[position]) tf = tempfile.mkstemp() pathToXML = tf[1] source.saveXML(pathToXML, tables=['name']) os.close(tf[0]) with open(pathToXML, "r") as temp: xml = temp.read() # make the changes if suffix: xml = xml.replace("Input", "Input" + suffix) # save the table with open(pathToXML, 'w') as temp: temp.write(xml) temp.write('\r') f['OS/2'].usWeightClass = source['OS/2'].usWeightClass f['OS/2'].fsType = source['OS/2'].fsType # write the table f['name'] = newTable('name') importXML(f, pathToXML)
def setupTable_post(self): """ Make the post table. **This should not be called externally.** Subclasses may override or supplement this method to handle the table creation in a different way if desired. """ self.otf["post"] = post = newTable("post") font = self.ufo post.formatType = 3.0 # italic angle italicAngle = getAttrWithFallback(font.info, "italicAngle") post.italicAngle = italicAngle # underline underlinePosition = getAttrWithFallback(font.info, "postscriptUnderlinePosition") if underlinePosition is None: underlinePosition = 0 post.underlinePosition = _roundInt(underlinePosition) underlineThickness = getAttrWithFallback(font.info, "postscriptUnderlineThickness") if underlineThickness is None: underlineThickness = 0 post.underlineThickness = _roundInt(underlineThickness) # determine if the font has a fixed width widths = set([glyph.width for glyph in self.allGlyphs.values()]) post.isFixedPitch = getAttrWithFallback(font.info, "postscriptIsFixedPitch") # misc post.minMemType42 = 0 post.maxMemType42 = 0 post.minMemType1 = 0 post.maxMemType1 = 0
def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True): assert tolerance >= 0 log.info("Generating gvar") assert "gvar" not in font gvar = font["gvar"] = newTable('gvar') gvar.version = 1 gvar.reserved = 0 gvar.variations = {} glyf = font['glyf'] # use hhea.ascent of base master as default vertical origin when vmtx is missing baseAscent = font['hhea'].ascent for glyph in font.getGlyphOrder(): isComposite = glyf[glyph].isComposite() allData = [ m["glyf"].getCoordinatesAndControls( glyph, m, defaultVerticalOrigin=baseAscent) for m in master_ttfs ] model, allData = masterModel.getSubModel(allData) allCoords = [d[0] for d in allData] allControls = [d[1] for d in allData] control = allControls[0] if not models.allEqual(allControls): log.warning("glyph %s has incompatible masters; skipping" % glyph) continue del allControls # Update gvar gvar.variations[glyph] = [] deltas = model.getDeltas(allCoords) supports = model.supports assert len(deltas) == len(supports) # Prepare for IUP optimization origCoords = deltas[0] endPts = control.endPts for i, (delta, support) in enumerate(zip(deltas[1:], supports[1:])): if all(abs(v) <= tolerance for v in delta.array) and not isComposite: continue var = TupleVariation(support, delta) if optimize: delta_opt = iup_delta_optimize(delta, origCoords, endPts, tolerance=tolerance) if None in delta_opt: """In composite glyphs, there should be one 0 entry to make sure the gvar entry is written to the font. This is to work around an issue with macOS 10.14 and can be removed once the behaviour of macOS is changed. https://github.com/fonttools/fonttools/issues/1381 """ if all(d is None for d in delta_opt): delta_opt = [(0, 0)] + [None] * (len(delta_opt) - 1) # Use "optimized" version only if smaller... var_opt = TupleVariation(support, delta_opt) axis_tags = sorted(support.keys( )) # Shouldn't matter that this is different from fvar...? tupleData, auxData, _ = var.compile(axis_tags, [], None) unoptimized_len = len(tupleData) + len(auxData) tupleData, auxData, _ = var_opt.compile( axis_tags, [], None) optimized_len = len(tupleData) + len(auxData) if optimized_len < unoptimized_len: var = var_opt gvar.variations[glyph].append(var)
def _add_avar(font, axes): """ Add 'avar' table to font. axes is an ordered dictionary of AxisDescriptor objects. """ assert axes assert isinstance(axes, OrderedDict) log.info("Generating avar") avar = newTable('avar') interesting = False for axis in axes.values(): # Currently, some rasterizers require that the default value maps # (-1 to -1, 0 to 0, and 1 to 1) be present for all the segment # maps, even when the default normalization mapping for the axis # was not modified. # https://github.com/googlei18n/fontmake/issues/295 # https://github.com/fonttools/fonttools/issues/1011 # TODO(anthrotype) revert this (and 19c4b37) when issue is fixed curve = avar.segments[axis.tag] = {-1.0: -1.0, 0.0: 0.0, 1.0: 1.0} if not axis.map: continue items = sorted(axis.map) keys = [item[0] for item in items] vals = [item[1] for item in items] # Current avar requirements. We don't have to enforce # these on the designer and can deduce some ourselves, # but for now just enforce them. assert axis.minimum == min(keys) assert axis.maximum == max(keys) assert axis.default in keys # No duplicates assert len(set(keys)) == len(keys) assert len(set(vals)) == len(vals) # Ascending values assert sorted(vals) == vals keys_triple = (axis.minimum, axis.default, axis.maximum) vals_triple = tuple(axis.map_forward(v) for v in keys_triple) keys = [models.normalizeValue(v, keys_triple) for v in keys] vals = [models.normalizeValue(v, vals_triple) for v in vals] if all(k == v for k, v in zip(keys, vals)): continue interesting = True curve.update(zip(keys, vals)) assert 0.0 in curve and curve[0.0] == 0.0 assert -1.0 not in curve or curve[-1.0] == -1.0 assert +1.0 not in curve or curve[+1.0] == +1.0 # curve.update({-1.0: -1.0, 0.0: 0.0, 1.0: 1.0}) assert "avar" not in font if not interesting: log.info("No need for avar") avar = None else: font['avar'] = avar return avar
def _merge_TTHinting(font, masterModel, master_ttfs): log.info("Merging TT hinting") assert "cvar" not in font # Check that the existing hinting is compatible # fpgm and prep table for tag in ("fpgm", "prep"): all_pgms = [m[tag].program for m in master_ttfs if tag in m] if not all_pgms: continue font_pgm = getattr(font.get(tag), 'program', None) if any(pgm != font_pgm for pgm in all_pgms): log.warning( "Masters have incompatible %s tables, hinting is discarded." % tag) _remove_TTHinting(font) return # glyf table font_glyf = font['glyf'] master_glyfs = [m['glyf'] for m in master_ttfs] for name, glyph in font_glyf.glyphs.items(): all_pgms = [ getattr(glyf.get(name), 'program', None) for glyf in master_glyfs ] if not any(all_pgms): continue glyph.expand(font_glyf) font_pgm = getattr(glyph, 'program', None) if any(pgm != font_pgm for pgm in all_pgms if pgm): log.warning( "Masters have incompatible glyph programs in glyph '%s', hinting is discarded." % name) # TODO Only drop hinting from this glyph. _remove_TTHinting(font) return # cvt table all_cvs = [ Vector(m["cvt "].values) if 'cvt ' in m else None for m in master_ttfs ] nonNone_cvs = models.nonNone(all_cvs) if not nonNone_cvs: # There is no cvt table to make a cvar table from, we're done here. return if not models.allEqual(len(c) for c in nonNone_cvs): log.warning( "Masters have incompatible cvt tables, hinting is discarded.") _remove_TTHinting(font) return variations = [] deltas, supports = masterModel.getDeltasAndSupports( all_cvs, round=round ) # builtin round calls into Vector.__round__, which uses builtin round as we like for i, (delta, support) in enumerate(zip(deltas[1:], supports[1:])): if all(v == 0 for v in delta): continue var = TupleVariation(support, delta) variations.append(var) # We can build the cvar table now. if variations: cvar = font["cvar"] = newTable('cvar') cvar.version = 1 cvar.variations = variations
def build_CFF(self): ctx = self.ctx data = self.metadataProvider self.otf["CFF "] = cff = ttLib.newTable("CFF ") cff = cff.cff cff.major = 1 cff.minor = 0 cff.hdrSize = 4 cff.offSize = 4 cff.fontNames = [] strings = IndexedStrings() cff.strings = strings private = PrivateDict(strings=strings) private.rawDict.update(private.defaults) globalSubrs = GlobalSubrsIndex(private=private) topDict = TopDict(GlobalSubrs=globalSubrs, strings=strings) topDict.Private = private charStrings = topDict.CharStrings = CharStrings( file=None, charset=None, globalSubrs=globalSubrs, private=private, fdSelect=None, fdArray=None, ) charStrings.charStringsAreIndexed = True topDict.charset = [] charStringsIndex = charStrings.charStringsIndex = SubrsIndex( private=private, globalSubrs=globalSubrs) cff.topDictIndex = topDictIndex = TopDictIndex() topDictIndex.append(topDict) topDictIndex.strings = strings cff.GlobalSubrs = globalSubrs cff.fontNames.append(data.name_postscriptFontName(ctx)) topDict = cff.topDictIndex[0] topDict.version = data.version(ctx) topDict.Notice = conversion.to_postscript_string( data.name_trademark(ctx), lambda result: ctx.log.append( semlog.warning_attr_truncated(attr="trademark", result=result)) ) topDict.Copyright = conversion.to_postscript_string( data.copyright(ctx), lambda result: ctx.log.append( semlog.warning_attr_truncated(attr="copyright", result=result)) ) topDict.FullName = data.name_postscriptFullName(ctx) topDict.FamilyName = data.CFF_postscriptFamilyName(ctx) topDict.Weight = data.CFF_postscriptWeightName(ctx) topDict.isFixedPitch = data.post_isFixedPitch(ctx) topDict.ItalicAngle = data.italicAngle(ctx) topDict.UnderlinePosition = otRound(data.post_underlinePosition(ctx)) topDict.UnderlineThickness = otRound(data.post_underlineThickness(ctx)) scale = 1.0 / otRound(data.unitsPerEm(ctx)) topDict.FontMatrix = [scale, 0, 0, scale, 0, 0] defaultWidthX, nominalWidthX = FontProc.postscript_width_stats( ctx, self.otf) if defaultWidthX: private.rawDict["defaultWidthX"] = defaultWidthX if nominalWidthX: private.rawDict["nominalWidthX"] = nominalWidthX blueFuzz = otRound(data.CFF_postscriptBlueFuzz(ctx)) blueShift = otRound(data.CFF_postscriptBlueShift(ctx)) blueScale = data.CFF_postscriptBlueScale(ctx) forceBold = data.CFF_postscriptForceBold(ctx) blueValues = otRoundSequence(data.CFF_postscriptBlueValues(ctx)) otherBlues = otRoundSequence(data.CFF_postscriptOtherBlues(ctx)) familyBlues = otRoundSequence(data.CFF_postscriptFamilyBlues(ctx)) familyOtherBlues = otRoundSequence( data.CFF_postscriptFamilyOtherBlues(ctx)) stemSnapH = otRoundSequence(data.CFF_postscriptStemSnapH(ctx)) stemSnapV = otRoundSequence(data.CFF_postscriptStemSnapV(ctx)) # only write the blues data if some blues are defined. if any((blueValues, otherBlues, familyBlues, familyOtherBlues)): private.rawDict["BlueFuzz"] = blueFuzz private.rawDict["BlueShift"] = blueShift private.rawDict["BlueScale"] = blueScale private.rawDict["ForceBold"] = forceBold if blueValues: private.rawDict["BlueValues"] = blueValues if otherBlues: private.rawDict["OtherBlues"] = otherBlues if familyBlues: private.rawDict["FamilyBlues"] = familyBlues if familyOtherBlues: private.rawDict["FamilyOtherBlues"] = familyOtherBlues # only write the stems if both are defined. if stemSnapH and stemSnapV: private.rawDict["StemSnapH"] = stemSnapH private.rawDict["StdHW"] = stemSnapH[0] private.rawDict["StemSnapV"] = stemSnapV private.rawDict["StdVW"] = stemSnapV[0] # populate glyphs for glyphName in self.glyphOrder: glyph = self.glyphMap[glyphName] charString = self.draw_charstring(glyph, private, globalSubrs) # add to the font charStringsIndex.append(charString) glyphID = len(topDict.charset) charStrings.charStrings[glyphName] = glyphID topDict.charset.append(glyphName) bounds = self.fontBounds topDict.FontBBox = (bounds.left, bounds.bottom, bounds.right, bounds.top)
def setUp(self): vhea = newTable('vhea') self.font = TTFont(sfntVersion='OTTO') self.font['vhea'] = vhea
def nametable_from_filename(filepath): """Generate a new nametable based on a ttf and the GF Spec""" font = TTFont(filepath) old_table = font['name'] new_table = newTable('name') filename = ntpath.basename(filepath)[:-4] family_name, style_name = filename.split('-') family_name = _split_camelcase(family_name) font_version = font['name'].getName(5, 3, 1, 1033) font_version = str(font_version).decode('utf_16_be') vendor_id = font['OS/2'].achVendID # SET MAC NAME FIELDS # ------------------- # Copyright old_cp = old_table.getName(0, 3, 1, 1033).string.decode('utf_16_be') new_table.setName(old_cp.encode('mac_roman'), 0, 1, 0, 0) # Font Family Name new_table.setName(family_name.encode('mac_roman'), 1, 1, 0, 0) # Subfamily name mac_subfamily_name = _mac_subfamily_name(style_name).encode('mac_roman') new_table.setName(mac_subfamily_name, 2, 1, 0, 0) # Unique ID unique_id = _unique_id(_version(font_version), vendor_id, filename) mac_unique_id = unique_id.encode('mac_roman') new_table.setName(mac_unique_id, 3, 1, 0, 0) # Full name fullname = _full_name(family_name, style_name) mac_fullname = fullname.encode('mac_roman') new_table.setName(mac_fullname, 4, 1, 0, 0) # Version string old_v = old_table.getName(5, 3, 1, 1033).string.decode('utf_16_be') mac_old_v = old_v.encode('mac_roman') new_table.setName(mac_old_v, 5, 1, 0, 0) # Postscript name mac_ps_name = filename.encode('mac_roman') new_table.setName(mac_ps_name, 6, 1, 0, 0) # SET WIN NAME FIELDS # ------------------- # Copyright new_table.setName(old_cp, 0, 3, 1, 1033) # Font Family Name win_family_name = _win_family_name(family_name, style_name) win_family_name = win_family_name.encode('utf_16_be') new_table.setName(win_family_name, 1, 3, 1, 1033) # Subfamily Name win_subfamily_name = _win_subfamily_name(style_name).encode('utf_16_be') new_table.setName(win_subfamily_name, 2, 3, 1, 1033) # Unique ID win_unique_id = unique_id.encode('utf_16_be') new_table.setName(win_unique_id, 3, 3, 1, 1033) # Full name win_fullname = fullname.encode('utf_16_be') new_table.setName(win_fullname, 4, 3, 1, 1033) # Version string win_old_v = old_v.encode('utf_16_be') new_table.setName(win_old_v, 5, 3, 1, 1033) # Postscript name win_ps_name = filename.encode('utf_16_be') new_table.setName(win_ps_name, 6, 3, 1, 1033) if style_name not in WIN_SAFE_STYLES: # Preferred Family Name new_table.setName(family_name.encode('utf_16_be'), 16, 3, 1, 1033) # Preferred SubfamilyName win_pref_subfam_name = _mac_subfamily_name(style_name).encode( 'utf_16_be') new_table.setName(win_pref_subfam_name, 17, 3, 1, 1033) # PAD missing fields # ------------------ for field in REQUIRED_FIELDS: text = None if new_table.getName(*field): pass # Name has already been updated elif old_table.getName(*field): text = old_table.getName(*field).string elif old_table.getName(field[0], 3, 1, 1033): text = old_table.getName(field[0], 3, 1, 1033).string.decode('utf_16_be') elif old_table.getName(field[0], 1, 0, 0): # check if field exists for mac text = old_table.getName(field[0], 3, 1, 1033).string.decode('mac_roman') if text: enc = 'utf_16_be' if field[0] == 3 else 'mac_roman' new_table.setName(text.encode(enc), *field) return new_table
def test_compile_v1(self): cpal = newTable('CPAL') cpal.decompile(CPAL_DATA_V1, ttFont=None) self.assertEqual(cpal.compile(ttFont=None), CPAL_DATA_V1)
def test_decompile_toXML_version_1_1(self): table = newTable('STAT') table.decompile(STAT_DATA_VERSION_1_1, font=FakeFont(['.notdef'])) self.assertEqual(getXML(table.toXML), STAT_XML_VERSION_1_1)
def test_decompile_toXML_format3(self): table = newTable('STAT') table.decompile(STAT_DATA_AXIS_VALUE_FORMAT3, font=FakeFont(['.notdef'])) self.assertEqual(getXML(table.toXML), STAT_XML_AXIS_VALUE_FORMAT3)
def test_decompile_toXML(self): table = newTable('STAT') table.decompile(STAT_DATA, font=FakeFont(['.notdef'])) self.assertEqual(getXML(table.toXML), STAT_XML)
def _add_fvar(font, axes, instances): """ Add 'fvar' table to font. axes is an ordered dictionary of DesignspaceAxis objects. instances is list of dictionary objects with 'location', 'stylename', and possibly 'postscriptfontname' entries. """ assert axes assert isinstance(axes, OrderedDict) log.info("Generating fvar") fvar = newTable('fvar') nameTable = font['name'] for a in axes.values(): axis = Axis() axis.axisTag = Tag(a.tag) # TODO Skip axes that have no variation. axis.minValue, axis.defaultValue, axis.maxValue = a.minimum, a.default, a.maximum axis.axisNameID = nameTable.addMultilingualName(a.labelNames, font, minNameID=256) axis.flags = int(a.hidden) fvar.axes.append(axis) for instance in instances: coordinates = instance.location if "en" not in instance.localisedStyleName: if not instance.styleName: raise VarLibValidationError( f"Instance at location '{coordinates}' must have a default English " "style name ('stylename' attribute on the instance element or a " "stylename element with an 'xml:lang=\"en\"' attribute).") localisedStyleName = dict(instance.localisedStyleName) localisedStyleName["en"] = tostr(instance.styleName) else: localisedStyleName = instance.localisedStyleName psname = instance.postScriptFontName inst = NamedInstance() inst.subfamilyNameID = nameTable.addMultilingualName( localisedStyleName) if psname is not None: psname = tostr(psname) inst.postscriptNameID = nameTable.addName(psname) inst.coordinates = { axes[k].tag: axes[k].map_backward(v) for k, v in coordinates.items() } #inst.coordinates = {axes[k].tag:v for k,v in coordinates.items()} fvar.instances.append(inst) assert "fvar" not in font font['fvar'] = fvar return fvar
def _merge_TTHinting(font, masterModel, master_ttfs, tolerance=0.5): log.info("Merging TT hinting") assert "cvar" not in font # Check that the existing hinting is compatible # fpgm and prep table for tag in ("fpgm", "prep"): all_pgms = [m[tag].program for m in master_ttfs if tag in m] if len(all_pgms) == 0: continue if tag in font: font_pgm = font[tag].program else: font_pgm = Program() if any(pgm != font_pgm for pgm in all_pgms): log.warning( "Masters have incompatible %s tables, hinting is discarded." % tag) _remove_TTHinting(font) return # glyf table for name, glyph in font["glyf"].glyphs.items(): all_pgms = [ m["glyf"][name].program for m in master_ttfs if name in m['glyf'] and hasattr(m["glyf"][name], "program") ] if not any(all_pgms): continue glyph.expand(font["glyf"]) if hasattr(glyph, "program"): font_pgm = glyph.program else: font_pgm = Program() if any(pgm != font_pgm for pgm in all_pgms if pgm): log.warning( "Masters have incompatible glyph programs in glyph '%s', hinting is discarded." % name) # TODO Only drop hinting from this glyph. _remove_TTHinting(font) return # cvt table all_cvs = [ Vector(m["cvt "].values) if 'cvt ' in m else None for m in master_ttfs ] nonNone_cvs = models.nonNone(all_cvs) if not nonNone_cvs: # There is no cvt table to make a cvar table from, we're done here. return if not models.allEqual(len(c) for c in nonNone_cvs): log.warning( "Masters have incompatible cvt tables, hinting is discarded.") _remove_TTHinting(font) return # We can build the cvar table now. cvar = font["cvar"] = newTable('cvar') cvar.version = 1 cvar.variations = [] deltas, supports = masterModel.getDeltasAndSupports(all_cvs) for i, (delta, support) in enumerate(zip(deltas[1:], supports[1:])): delta = [otRound(d) for d in delta] if all(abs(v) <= tolerance for v in delta): continue var = TupleVariation(support, delta) cvar.variations.append(var)
def _add_MVAR(font, masterModel, master_ttfs, axisTags): log.info("Generating MVAR") store_builder = varStore.OnlineVarStoreBuilder(axisTags) records = [] lastTableTag = None fontTable = None tables = None # HACK: we need to special-case post.underlineThickness and .underlinePosition # and unilaterally/arbitrarily define a sentinel value to distinguish the case # when a post table is present in a given master simply because that's where # the glyph names in TrueType must be stored, but the underline values are not # meant to be used for building MVAR's deltas. The value of -0x8000 (-36768) # the minimum FWord (int16) value, was chosen for its unlikelyhood to appear # in real-world underline position/thickness values. specialTags = {"unds": -0x8000, "undo": -0x8000} for tag, (tableTag, itemName) in sorted(MVAR_ENTRIES.items(), key=lambda kv: kv[1]): # For each tag, fetch the associated table from all fonts (or not when we are # still looking at a tag from the same tables) and set up the variation model # for them. if tableTag != lastTableTag: tables = fontTable = None if tableTag in font: fontTable = font[tableTag] tables = [] for master in master_ttfs: if tableTag not in master or ( tag in specialTags and getattr(master[tableTag], itemName) == specialTags[tag]): tables.append(None) else: tables.append(master[tableTag]) model, tables = masterModel.getSubModel(tables) store_builder.setModel(model) lastTableTag = tableTag if tables is None: # Tag not applicable to the master font. continue # TODO support gasp entries master_values = [getattr(table, itemName) for table in tables] if models.allEqual(master_values): base, varIdx = master_values[0], None else: base, varIdx = store_builder.storeMasters(master_values) setattr(fontTable, itemName, base) if varIdx is None: continue log.info(' %s: %s.%s %s', tag, tableTag, itemName, master_values) rec = ot.MetricsValueRecord() rec.ValueTag = tag rec.VarIdx = varIdx records.append(rec) assert "MVAR" not in font if records: store = store_builder.finish() # Optimize mapping = store.optimize() for rec in records: rec.VarIdx = mapping[rec.VarIdx] MVAR = font["MVAR"] = newTable('MVAR') mvar = MVAR.table = ot.MVAR() mvar.Version = 0x00010000 mvar.Reserved = 0 mvar.VarStore = store # XXX these should not be hard-coded but computed automatically mvar.ValueRecordSize = 8 mvar.ValueRecordCount = len(records) mvar.ValueRecord = sorted(records, key=lambda r: r.ValueTag)
def test_compile_fromXML_version_1_1(self): table = newTable('STAT') font = FakeFont(['.notdef']) for name, attrs, content in parseXML(STAT_XML_VERSION_1_1): table.fromXML(name, attrs, content, font=font) self.assertEqual(table.compile(font), STAT_DATA_VERSION_1_1)
def colorize(font): COLR = newTable("COLR") CPAL = newTable("CPAL") glyf = font["glyf"] hmtx = font["hmtx"] CPAL.version = 0 COLR.version = 0 palette = list(GROUPS.values()) palette.append(BLACK) CPAL.palettes = [palette] CPAL.numPaletteEntries = len(palette) COLR.ColorLayers = {} glyphOrder = list(font.getGlyphOrder()) for name in glyphOrder: glyph = glyf[name] layers = [] if glyph.isComposite() and len(glyph.components) > 1: componentColors = [ getGlyphColor(c.getComponentInfo()[0]) for c in glyph.components ] if any(componentColors): for component in glyph.components: componentName, trans = component.getComponentInfo() componentColor = getGlyphColor(componentName) if trans == (1, 0, 0, 1, 0, 0): if componentColor is None: # broken in current versions of Firefox, # see https://bugzilla.mozilla.org/show_bug.cgi?id=1283932 #layers.append(newLayer(componentName, 0xFFFF)) # broken if FF47 layers.append( newLayer(componentName, palette.index(BLACK))) else: layers.append( newLayer(componentName, palette.index(componentColor))) else: newName = "%s.%s" % (componentName, hash(trans)) if newName not in font.glyphOrder: font.glyphOrder.append(newName) newGlyph = getTableModule("glyf").Glyph() newGlyph.numberOfContours = -1 newGlyph.components = [component] glyf.glyphs[newName] = newGlyph assert (len(glyf.glyphs) == len( font.glyphOrder)), (name, newName) width = hmtx[name][0] lsb = hmtx[componentName][1] + trans[4] hmtx.metrics[newName] = [width, lsb] if componentColor is None: # broken in current versions of Firefox, # see https://bugzilla.mozilla.org/show_bug.cgi?id=1283932 #layers.append(newLayer(componentName, 0xFFFF)) # broken if FF47 layers.append( newLayer(newName, palette.index(BLACK))) else: layers.append( newLayer(newName, palette.index(componentColor))) if not layers: color = getGlyphColor(name) if color is not None: layers = [newLayer(name, palette.index(color))] if layers: COLR[name] = layers font["COLR"] = COLR font["CPAL"] = CPAL
def changeUPM(self, targetUPM): # Calculate scaling factor between old and new UPM upmOld = self.font["head"].unitsPerEm upmNew = int(targetUPM) isCubic = self.font.has_key("CFF ") if upmOld == upmNew: return elif upmNew < 16 or upmNew > 16384: print("WARNING: Invalid UPM value. --UPM is now ignored.", file=sys.stderr) return elif isCubic: print( "WARNING: CFF-based font detected. Unfortunately it is currently not supported.", file=sys.stderr) return elif upmNew > 5000: print( "WARNING: UPM > 5000 will cause problems in Adobe InDesign and Illustrator.", file=sys.stderr) else: pass scaleFactor = upmNew / upmOld # Get float because __future__.division has been imported # Conversion: re-scale all glyphs scaledGlyphs = {} glyphOrder = self.font.getGlyphOrder() glyphSet = self.font.getGlyphSet() for glyphName in glyphSet.keys(): glyph = glyphSet[glyphName] if isCubic: # TODO: `CFF ` # basePen = OTGlyphPen(glyphSet) pass else: # `glyf` basePen = TTGlyphPen(glyphSet) scalePen = TransformPen(basePen, Scale(scaleFactor, scaleFactor)) # Deal with quad composites (all cubics are not affected) if not isCubic and glyph._glyph.isComposite( ): # Scale each component's xy offset glyph.draw(basePen) for i in range(len(basePen.components)): componentName, oldTrans = basePen.components[i] newTrans = (oldTrans[0], oldTrans[1], oldTrans[2], oldTrans[3], oldTrans[4] * scaleFactor, oldTrans[5] * scaleFactor) basePen.components[i] = (componentName, newTrans) else: # Scale all cubics or base quads so that their composites will not be scaled multiple times glyph.draw(scalePen) # Glyph-specific hinting will be removed upon TTGlyphPen.glyph() call. scaledGlyphs[glyphName] = basePen.glyph() # Apply `glyf` table with scaled glyphs glyf = newTable("glyf") glyf.glyphOrder = glyphOrder glyf.glyphs = scaledGlyphs self.font["glyf"] = glyf # Update tables to apply the new UPM self.__applyNewUPM(upmOld, upmNew) # Recalculate `head`, `glyf`, `maxp` upon compile self.font.recalcBBoxes = True return
def build_OS2(self): ctx = self.ctx data = self.metadataProvider self.otf["OS/2"] = os2 = ttLib.newTable("OS/2") os2.version = 0x0004 os2.xAvgCharWidth = FontProc.average_char_width(ctx, self.otf) os2.usWeightClass = data.OS2_weightClass(ctx) os2.usWidthClass = data.OS2_widthClass(ctx) os2.fsType = data.OS2_fsType(ctx) os2.ySubscriptXSize = otRound(data.OS2_subscriptXSize(ctx)) os2.ySubscriptYSize = otRound(data.OS2_subscriptYSize(ctx)) os2.ySubscriptXOffset = otRound(data.OS2_subscriptXOffset(ctx)) os2.ySubscriptYOffset = otRound(data.OS2_subscriptYOffset(ctx)) os2.ySuperscriptXSize = otRound(data.OS2_superscriptXSize(ctx)) os2.ySuperscriptYSize = otRound(data.OS2_superscriptYSize(ctx)) os2.ySuperscriptXOffset = otRound(data.OS2_superscriptXOffset(ctx)) os2.ySuperscriptYOffset = otRound(data.OS2_superscriptYOffset(ctx)) os2.yStrikeoutSize = otRound(data.OS2_strikeoutSize(ctx)) os2.yStrikeoutPosition = otRound(data.OS2_strikeoutPosition(ctx)) os2.sFamilyClass = data.OS2_familyClass(ctx) panose = Panose() panose.bFamilyType, \ panose.bSerifStyle, \ panose.bWeight, \ panose.bProportion, \ panose.bContrast, \ panose.bStrokeVariation, \ panose.bArmStyle, \ panose.bLetterForm, \ panose.bMidline, \ panose.bXHeight = data.OS2_panose(ctx) os2.panose = panose unicodeRanges = data.OS2_unicodeRanges(ctx) if unicodeRanges is not None: os2.ulUnicodeRange1 = conversion.to_bitflags(unicodeRanges, 0, 32) os2.ulUnicodeRange2 = conversion.to_bitflags(unicodeRanges, 32, 32) os2.ulUnicodeRange3 = conversion.to_bitflags(unicodeRanges, 64, 32) os2.ulUnicodeRange4 = conversion.to_bitflags(unicodeRanges, 96, 32) else: os2.recalcUnicodeRanges(self.otf) codepageRanges = data.OS2_codepageRanges(ctx) if codepageRanges is None: codepageRanges = FontProc.codepage_ranges( ctx, self.unicodeToGlyphNameMap.keys()) os2.ulCodePageRange1 = conversion.to_bitflags(codepageRanges, 0, 32) os2.ulCodePageRange2 = conversion.to_bitflags(codepageRanges, 32, 32) os2.achVendID = data.OS2_vendorID(ctx) os2.sxHeight = otRound(data.xHeight(ctx)) os2.sCapHeight = otRound(data.capHeight(ctx)) os2.sTypoAscender, \ os2.sTypoDescender, \ os2.sTypoLineGap = otRoundSequence(data.OS2_typoMetrics(ctx)) os2.usWinAscent, \ os2.usWinDescent = otRoundSequence(data.OS2_winMetrics(ctx, self.fontBounds)) os2.fsSelection = data.OS2_fsSelection(ctx) os2.fsFirstCharIndex, \ os2.fsLastCharIndex = FontProc.minmax_cids(ctx, self.unicodeToGlyphNameMap.keys()) os2.usBreakChar = 32 os2.usDefaultChar = 0 # maximum contextual lookup length os2.usMaxContext = 0
import os import sys from fontTools.ttLib import TTFont, newTable print 'inserting STAT table from external file...' arguments = sys.argv[1:] path = arguments[0] f = TTFont(path) base = os.path.split(os.path.split(__file__)[0])[0] pathToXML = os.path.join(base, 'sources/2-build/STAT.ttx') f['STAT'] = newTable('STAT') f.importXML(pathToXML) os.remove(path) f.save(path)
def build_maxp(self): self.otf["maxp"] = maxp = ttLib.newTable("maxp") maxp.tableVersion = 0x00005000 maxp.numGlyphs = len(self.glyphOrder)
def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True): if tolerance < 0: raise ValueError("`tolerance` must be a positive number.") log.info("Generating gvar") assert "gvar" not in font gvar = font["gvar"] = newTable('gvar') glyf = font['glyf'] defaultMasterIndex = masterModel.reverseMapping[0] master_datas = [ _MasterData(m['glyf'], m['hmtx'].metrics, getattr(m.get('vmtx'), 'metrics', None)) for m in master_ttfs ] for glyph in font.getGlyphOrder(): isComposite = glyf[glyph].isComposite() allData = [ m.glyf._getCoordinatesAndControls(glyph, m.hMetrics, m.vMetrics) for m in master_datas ] if allData[defaultMasterIndex][1].numberOfContours != 0: # If the default master is not empty, interpret empty non-default masters # as missing glyphs from a sparse master allData = [ d if d is not None and d[1].numberOfContours != 0 else None for d in allData ] model, allData = masterModel.getSubModel(allData) allCoords = [d[0] for d in allData] allControls = [d[1] for d in allData] control = allControls[0] if not models.allEqual(allControls): log.warning("glyph %s has incompatible masters; skipping" % glyph) continue del allControls # Update gvar gvar.variations[glyph] = [] deltas = model.getDeltas(allCoords, round=partial(GlyphCoordinates.__round__, round=round)) supports = model.supports assert len(deltas) == len(supports) # Prepare for IUP optimization origCoords = deltas[0] endPts = control.endPts for i, (delta, support) in enumerate(zip(deltas[1:], supports[1:])): if all(v == 0 for v in delta.array) and not isComposite: continue var = TupleVariation(support, delta) if optimize: delta_opt = iup_delta_optimize(delta, origCoords, endPts, tolerance=tolerance) if None in delta_opt: """In composite glyphs, there should be one 0 entry to make sure the gvar entry is written to the font. This is to work around an issue with macOS 10.14 and can be removed once the behaviour of macOS is changed. https://github.com/fonttools/fonttools/issues/1381 """ if all(d is None for d in delta_opt): delta_opt = [(0, 0)] + [None] * (len(delta_opt) - 1) # Use "optimized" version only if smaller... var_opt = TupleVariation(support, delta_opt) axis_tags = sorted(support.keys( )) # Shouldn't matter that this is different from fvar...? tupleData, auxData = var.compile(axis_tags) unoptimized_len = len(tupleData) + len(auxData) tupleData, auxData = var_opt.compile(axis_tags) optimized_len = len(tupleData) + len(auxData) if optimized_len < unoptimized_len: var = var_opt gvar.variations[glyph].append(var)
def test_decompile_toXML_format1(self): table = newTable('opbd') table.decompile(OPBD_FORMAT_1_DATA, self.font) self.assertEqual(getXML(table.toXML), OPBD_FORMAT_1_XML)
def makeLookup1(): # make a variation of the shell TTX data f = open(shellSourcePath) ttxData = f.read() f.close() ttxData = ttxData.replace("__familyName__", "gsubtest-lookup1") tempShellSourcePath = shellSourcePath + ".temp" f = open(tempShellSourcePath, "wb") f.write(ttxData) f.close() # compile the shell shell = TTFont(sfntVersion="OTTO") shell.importXML(tempShellSourcePath) shell.save(shellTempPath) os.remove(tempShellSourcePath) # load the shell shell = TTFont(shellTempPath) # grab the PASS and FAIL data hmtx = shell["hmtx"] glyphSet = shell.getGlyphSet() failGlyph = glyphSet["F"] failGlyph.decompile() failGlyphProgram = list(failGlyph.program) failGlyphMetrics = hmtx["F"] passGlyph = glyphSet["P"] passGlyph.decompile() passGlyphProgram = list(passGlyph.program) passGlyphMetrics = hmtx["P"] # grab some tables hmtx = shell["hmtx"] cmap = shell["cmap"] # start the glyph order existingGlyphs = [".notdef", "space", "F", "P"] glyphOrder = list(existingGlyphs) # start the CFF cff = shell["CFF "].cff globalSubrs = cff.GlobalSubrs topDict = cff.topDictIndex[0] topDict.charset = existingGlyphs private = topDict.Private charStrings = topDict.CharStrings charStringsIndex = charStrings.charStringsIndex features = sorted(mapping) # build the outline, hmtx and cmap data cp = baseCodepoint for index, tag in enumerate(features): # tag.pass glyphName = "%s.pass" % tag glyphOrder.append(glyphName) addGlyphToCFF(glyphName=glyphName, program=passGlyphProgram, private=private, globalSubrs=globalSubrs, charStringsIndex=charStringsIndex, topDict=topDict, charStrings=charStrings) hmtx[glyphName] = passGlyphMetrics for table in cmap.tables: if table.format == 4: table.cmap[cp] = glyphName else: raise NotImplementedError, "Unsupported cmap table format: %d" % table.format cp += 1 # tag.fail glyphName = "%s.fail" % tag glyphOrder.append(glyphName) addGlyphToCFF(glyphName=glyphName, program=failGlyphProgram, private=private, globalSubrs=globalSubrs, charStringsIndex=charStringsIndex, topDict=topDict, charStrings=charStrings) hmtx[glyphName] = failGlyphMetrics for table in cmap.tables: if table.format == 4: table.cmap[cp] = glyphName else: raise NotImplementedError, "Unsupported cmap table format: %d" % table.format # bump this up so that the sequence is the same as the lookup 3 font cp += 3 # set the glyph order shell.setGlyphOrder(glyphOrder) # start the GSUB shell["GSUB"] = newTable("GSUB") gsub = shell["GSUB"].table = GSUB() gsub.Version = 1.0 # make a list of all the features we will make featureCount = len(features) # set up the script list scriptList = gsub.ScriptList = ScriptList() scriptList.ScriptCount = 1 scriptList.ScriptRecord = [] scriptRecord = ScriptRecord() scriptList.ScriptRecord.append(scriptRecord) scriptRecord.ScriptTag = "DFLT" script = scriptRecord.Script = Script() defaultLangSys = script.DefaultLangSys = DefaultLangSys() defaultLangSys.FeatureCount = featureCount defaultLangSys.FeatureIndex = range(defaultLangSys.FeatureCount) defaultLangSys.ReqFeatureIndex = 65535 defaultLangSys.LookupOrder = None script.LangSysCount = 0 script.LangSysRecord = [] # set up the feature list featureList = gsub.FeatureList = FeatureList() featureList.FeatureCount = featureCount featureList.FeatureRecord = [] for index, tag in enumerate(features): # feature record featureRecord = FeatureRecord() featureRecord.FeatureTag = tag feature = featureRecord.Feature = Feature() featureList.FeatureRecord.append(featureRecord) # feature feature.FeatureParams = None feature.LookupCount = 1 feature.LookupListIndex = [index] # write the lookups lookupList = gsub.LookupList = LookupList() lookupList.LookupCount = featureCount lookupList.Lookup = [] for tag in features: # lookup lookup = Lookup() lookup.LookupType = 1 lookup.LookupFlag = 0 lookup.SubTableCount = 1 lookup.SubTable = [] lookupList.Lookup.append(lookup) # subtable subtable = SingleSubst() subtable.Format = 2 subtable.LookupType = 1 subtable.mapping = { "%s.pass" % tag: "%s.fail" % tag, "%s.fail" % tag: "%s.pass" % tag, } lookup.SubTable.append(subtable) path = outputPath % 1 + ".otf" if os.path.exists(path): os.remove(path) shell.save(path) # get rid of the shell if os.path.exists(shellTempPath): os.remove(shellTempPath)
def test_compile_fromXML(self): table = newTable('morx') for name, attrs, content in parseXML(MORX_NONCONTEXTUAL_XML): table.fromXML(name, attrs, content, font=self.font) self.assertEqual(hexStr(table.compile(self.font)), hexStr(MORX_NONCONTEXTUAL_DATA))
def test_compile_fromXML_format1(self): table = newTable('opbd') for name, attrs, content in parseXML(OPBD_FORMAT_1_XML): table.fromXML(name, attrs, content, font=self.font) self.assertEqual(hexStr(table.compile(self.font)), hexStr(OPBD_FORMAT_1_DATA))
def test_decompile_AppleChancery(self): # Make sure we do not crash when decompiling the 'opbd' table of # AppleChancery.ttf. https://github.com/fonttools/fonttools/issues/1031 table = newTable('opbd') table.decompile(OPBD_APPLE_CHANCERY_DATA, self.font) self.assertIn('<OpticalBounds Format="0">', getXML(table.toXML))
def processFont(path, d, flatKernData): font = TTFont(path) oldD, f = os.path.split(path) new = os.path.join(d, f) name = font['name'].getName(6, 1, 0, 0).string # Make a flat kerning table if flatKernData is not None: newKern = ttLib.newTable("kern") newKern.version = 0 newKern.kernTables = [KernTable_format_0()] flat = flatKernData[name] sortedKeys = flat.keys() sortedKeys.sort() left = '' i = 0 for key in sortedKeys: if left == '': left = key[0] kern_table = {key: flat[key]} count = _flat_kern_count(left, sortedKeys) i += 1 elif i + 1 < len(sortedKeys) and left != sortedKeys[i + 1][0]: if count + _flat_kern_count(sortedKeys[i + 1][0], sortedKeys) > 10920: kern_table[key] = flat[key] t = len(newKern.kernTables) - 1 table = newKern.kernTables[t] table.kernTable = kern_table table.version = 0 table.coverage = 1 table.apple = False newKern.kernTables.append(KernTable_format_0()) left = '' else: kern_table[key] = flat[key] left = sortedKeys[i + 1][0] count = _flat_kern_count(sortedKeys[i + 1][0], sortedKeys) + count i += 1 elif len(sortedKeys) == i + 1: kern_table[key] = flat[key] t = len(newKern.kernTables) - 1 table = newKern.kernTables[t] table.kernTable = kern_table table.version = 0 table.coverage = 1 table.apple = False i += 1 else: kern_table[key] = flat[key] i += 1 # Add this table back to the font font.tables["kern"] = newKern # Fix the ugly id string versionClean(font) nameTableTweak(font) makeDSIG(font) font.save(new)
def test_compile_fromXML_withAxisJunk(self): table = newTable('STAT') font = FakeFont(['.notdef']) for name, attrs, content in parseXML(STAT_XML_WITH_AXIS_JUNK): table.fromXML(name, attrs, content, font=font) self.assertEqual(table.compile(font), STAT_DATA_WITH_AXIS_JUNK)
def test_compile_v1_noLabelsNoTypes(self): cpal = newTable('CPAL') cpal.decompile(CPAL_DATA_V1_NOLABELS_NOTYPES, ttFont=None) self.assertEqual(cpal.compile(ttFont=None), CPAL_DATA_V1_NOLABELS_NOTYPES)
def test_decompile_toXML_withAxisJunk(self): table = newTable('STAT') table.decompile(STAT_DATA_WITH_AXIS_JUNK, font=FakeFont(['.notdef'])) self.assertEqual(getXML(table.toXML), STAT_XML_WITH_AXIS_JUNK)
def test_compile_fromXML_format3(self): table = newTable('STAT') font = FakeFont(['.notdef']) for name, attrs, content in parseXML(STAT_XML_AXIS_VALUE_FORMAT3): table.fromXML(name, attrs, content, font=font) self.assertEqual(table.compile(font), STAT_DATA_AXIS_VALUE_FORMAT3)