123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- 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()
|