diff options
Diffstat (limited to 'layout/reftests/fonts/gsubtest/makegsubfonts.py')
-rw-r--r-- | layout/reftests/fonts/gsubtest/makegsubfonts.py | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/layout/reftests/fonts/gsubtest/makegsubfonts.py b/layout/reftests/fonts/gsubtest/makegsubfonts.py new file mode 100644 index 0000000000..8cd6476d97 --- /dev/null +++ b/layout/reftests/fonts/gsubtest/makegsubfonts.py @@ -0,0 +1,515 @@ +import os +import textwrap +from xml.etree import ElementTree +from fontTools.ttLib import TTFont, newTable +from fontTools.misc.psCharStrings import T2CharString +from fontTools.ttLib.tables.otTables import ( + GSUB, + ScriptList, + ScriptRecord, + Script, + DefaultLangSys, + FeatureList, + FeatureRecord, + Feature, + LookupList, + Lookup, + AlternateSubst, + SingleSubst, +) + +# paths +directory = os.path.dirname(__file__) +shellSourcePath = os.path.join(directory, "gsubtest-shell.ttx") +shellTempPath = os.path.join(directory, "gsubtest-shell.otf") +featureList = os.path.join(directory, "gsubtest-features.txt") +javascriptData = os.path.join(directory, "gsubtest-features.js") +outputPath = os.path.join(os.path.dirname(directory), "gsubtest-lookup%d") + +baseCodepoint = 0xE000 + +# ------- +# Features +# ------- + +f = open(featureList, "rb") +text = f.read() +f.close() +mapping = [] +for line in text.splitlines(): + line = line.strip() + if not line: + continue + if line.startswith("#"): + continue + # parse + values = line.split("\t") + tag = values.pop(0) + mapping.append(tag) + +# -------- +# Outlines +# -------- + + +def addGlyphToCFF( + glyphName=None, + program=None, + private=None, + globalSubrs=None, + charStringsIndex=None, + topDict=None, + charStrings=None, +): + charString = T2CharString(program=program, private=private, globalSubrs=globalSubrs) + charStringsIndex.append(charString) + glyphID = len(topDict.charset) + charStrings.charStrings[glyphName] = glyphID + topDict.charset.append(glyphName) + + +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 makeLookup3(): + # make a variation of the shell TTX data + f = open(shellSourcePath) + ttxData = f.read() + f.close() + ttxData = ttxData.replace("__familyName__", "gsubtest-lookup3") + 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 + + # 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 + + # tag.default + glyphName = "%s.default" % 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.alt1,2,3 + for i in range(1, 4): + glyphName = "%s.alt%d" % (tag, i) + 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 + ) + cp += 1 + + # 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 = 3 + lookup.LookupFlag = 0 + lookup.SubTableCount = 1 + lookup.SubTable = [] + lookupList.Lookup.append(lookup) + # subtable + subtable = AlternateSubst() + subtable.Format = 1 + subtable.LookupType = 3 + subtable.alternates = { + "%s.default" % tag: ["%s.fail" % tag, "%s.fail" % tag, "%s.fail" % tag], + "%s.alt1" % tag: ["%s.pass" % tag, "%s.fail" % tag, "%s.fail" % tag], + "%s.alt2" % tag: ["%s.fail" % tag, "%s.pass" % tag, "%s.fail" % tag], + "%s.alt3" % tag: ["%s.fail" % tag, "%s.fail" % tag, "%s.pass" % tag], + } + lookup.SubTable.append(subtable) + + path = outputPath % 3 + ".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 makeJavascriptData(): + features = sorted(mapping) + outStr = [] + + outStr.append("") + outStr.append("/* This file is autogenerated by makegsubfonts.py */") + outStr.append("") + outStr.append("/* ") + outStr.append(" Features defined in gsubtest fonts with associated base") + outStr.append(" codepoints for each feature:") + outStr.append("") + outStr.append(" cp = codepoint for feature featX") + outStr.append("") + outStr.append(" cp default PASS") + outStr.append(" cp featX=1 FAIL") + outStr.append(" cp featX=2 FAIL") + outStr.append("") + outStr.append(" cp+1 default FAIL") + outStr.append(" cp+1 featX=1 PASS") + outStr.append(" cp+1 featX=2 FAIL") + outStr.append("") + outStr.append(" cp+2 default FAIL") + outStr.append(" cp+2 featX=1 FAIL") + outStr.append(" cp+2 featX=2 PASS") + outStr.append("") + outStr.append("*/") + outStr.append("") + outStr.append("var gFeatures = {") + cp = baseCodepoint + + taglist = [] + for tag in features: + taglist.append('"%s": 0x%x' % (tag, cp)) + cp += 4 + + outStr.append( + textwrap.fill(", ".join(taglist), initial_indent=" ", subsequent_indent=" ") + ) + outStr.append("};") + outStr.append("") + + if os.path.exists(javascriptData): + os.remove(javascriptData) + + f = open(javascriptData, "wb") + f.write("\n".join(outStr)) + f.close() + + +# build fonts + +print("Making lookup type 1 font...") +makeLookup1() + +print("Making lookup type 3 font...") +makeLookup3() + +# output javascript data + +print("Making javascript data file...") +makeJavascriptData() |