michael@0: michael@0: import os michael@0: import textwrap michael@0: from xml.etree import ElementTree michael@0: from fontTools.ttLib import TTFont, newTable michael@0: from fontTools.misc.psCharStrings import T2CharString michael@0: from fontTools.ttLib.tables.otTables import GSUB,\ michael@0: ScriptList, ScriptRecord, Script, DefaultLangSys,\ michael@0: FeatureList, FeatureRecord, Feature,\ michael@0: LookupList, Lookup, AlternateSubst, SingleSubst michael@0: michael@0: # paths michael@0: directory = os.path.dirname(__file__) michael@0: shellSourcePath = os.path.join(directory, "gsubtest-shell.ttx") michael@0: shellTempPath = os.path.join(directory, "gsubtest-shell.otf") michael@0: featureList = os.path.join(directory, "gsubtest-features.txt") michael@0: javascriptData = os.path.join(directory, "gsubtest-features.js") michael@0: outputPath = os.path.join(os.path.dirname(directory), "gsubtest-lookup%d") michael@0: michael@0: baseCodepoint = 0xe000 michael@0: michael@0: # ------- michael@0: # Features michael@0: # ------- michael@0: michael@0: f = open(featureList, "rb") michael@0: text = f.read() michael@0: f.close() michael@0: mapping = [] michael@0: for line in text.splitlines(): michael@0: line = line.strip() michael@0: if not line: michael@0: continue michael@0: if line.startswith("#"): michael@0: continue michael@0: # parse michael@0: values = line.split("\t") michael@0: tag = values.pop(0) michael@0: mapping.append(tag); michael@0: michael@0: # -------- michael@0: # Outlines michael@0: # -------- michael@0: michael@0: def addGlyphToCFF(glyphName=None, program=None, private=None, globalSubrs=None, charStringsIndex=None, topDict=None, charStrings=None): michael@0: charString = T2CharString(program=program, private=private, globalSubrs=globalSubrs) michael@0: charStringsIndex.append(charString) michael@0: glyphID = len(topDict.charset) michael@0: charStrings.charStrings[glyphName] = glyphID michael@0: topDict.charset.append(glyphName) michael@0: michael@0: def makeLookup1(): michael@0: # make a variation of the shell TTX data michael@0: f = open(shellSourcePath) michael@0: ttxData = f.read() michael@0: f.close() michael@0: ttxData = ttxData.replace("__familyName__", "gsubtest-lookup1") michael@0: tempShellSourcePath = shellSourcePath + ".temp" michael@0: f = open(tempShellSourcePath, "wb") michael@0: f.write(ttxData) michael@0: f.close() michael@0: michael@0: # compile the shell michael@0: shell = TTFont(sfntVersion="OTTO") michael@0: shell.importXML(tempShellSourcePath) michael@0: shell.save(shellTempPath) michael@0: os.remove(tempShellSourcePath) michael@0: michael@0: # load the shell michael@0: shell = TTFont(shellTempPath) michael@0: michael@0: # grab the PASS and FAIL data michael@0: hmtx = shell["hmtx"] michael@0: glyphSet = shell.getGlyphSet() michael@0: michael@0: failGlyph = glyphSet["F"] michael@0: failGlyph.decompile() michael@0: failGlyphProgram = list(failGlyph.program) michael@0: failGlyphMetrics = hmtx["F"] michael@0: michael@0: passGlyph = glyphSet["P"] michael@0: passGlyph.decompile() michael@0: passGlyphProgram = list(passGlyph.program) michael@0: passGlyphMetrics = hmtx["P"] michael@0: michael@0: # grab some tables michael@0: hmtx = shell["hmtx"] michael@0: cmap = shell["cmap"] michael@0: michael@0: # start the glyph order michael@0: existingGlyphs = [".notdef", "space", "F", "P"] michael@0: glyphOrder = list(existingGlyphs) michael@0: michael@0: # start the CFF michael@0: cff = shell["CFF "].cff michael@0: globalSubrs = cff.GlobalSubrs michael@0: topDict = cff.topDictIndex[0] michael@0: topDict.charset = existingGlyphs michael@0: private = topDict.Private michael@0: charStrings = topDict.CharStrings michael@0: charStringsIndex = charStrings.charStringsIndex michael@0: michael@0: features = sorted(mapping) michael@0: michael@0: # build the outline, hmtx and cmap data michael@0: cp = baseCodepoint michael@0: for index, tag in enumerate(features): michael@0: michael@0: # tag.pass michael@0: glyphName = "%s.pass" % tag michael@0: glyphOrder.append(glyphName) michael@0: addGlyphToCFF( michael@0: glyphName=glyphName, michael@0: program=passGlyphProgram, michael@0: private=private, michael@0: globalSubrs=globalSubrs, michael@0: charStringsIndex=charStringsIndex, michael@0: topDict=topDict, michael@0: charStrings=charStrings michael@0: ) michael@0: hmtx[glyphName] = passGlyphMetrics michael@0: michael@0: for table in cmap.tables: michael@0: if table.format == 4: michael@0: table.cmap[cp] = glyphName michael@0: else: michael@0: raise NotImplementedError, "Unsupported cmap table format: %d" % table.format michael@0: cp += 1 michael@0: michael@0: # tag.fail michael@0: glyphName = "%s.fail" % tag michael@0: glyphOrder.append(glyphName) michael@0: addGlyphToCFF( michael@0: glyphName=glyphName, michael@0: program=failGlyphProgram, michael@0: private=private, michael@0: globalSubrs=globalSubrs, michael@0: charStringsIndex=charStringsIndex, michael@0: topDict=topDict, michael@0: charStrings=charStrings michael@0: ) michael@0: hmtx[glyphName] = failGlyphMetrics michael@0: michael@0: for table in cmap.tables: michael@0: if table.format == 4: michael@0: table.cmap[cp] = glyphName michael@0: else: michael@0: raise NotImplementedError, "Unsupported cmap table format: %d" % table.format michael@0: michael@0: # bump this up so that the sequence is the same as the lookup 3 font michael@0: cp += 3 michael@0: michael@0: # set the glyph order michael@0: shell.setGlyphOrder(glyphOrder) michael@0: michael@0: # start the GSUB michael@0: shell["GSUB"] = newTable("GSUB") michael@0: gsub = shell["GSUB"].table = GSUB() michael@0: gsub.Version = 1.0 michael@0: michael@0: # make a list of all the features we will make michael@0: featureCount = len(features) michael@0: michael@0: # set up the script list michael@0: scriptList = gsub.ScriptList = ScriptList() michael@0: scriptList.ScriptCount = 1 michael@0: scriptList.ScriptRecord = [] michael@0: scriptRecord = ScriptRecord() michael@0: scriptList.ScriptRecord.append(scriptRecord) michael@0: scriptRecord.ScriptTag = "DFLT" michael@0: script = scriptRecord.Script = Script() michael@0: defaultLangSys = script.DefaultLangSys = DefaultLangSys() michael@0: defaultLangSys.FeatureCount = featureCount michael@0: defaultLangSys.FeatureIndex = range(defaultLangSys.FeatureCount) michael@0: defaultLangSys.ReqFeatureIndex = 65535 michael@0: defaultLangSys.LookupOrder = None michael@0: script.LangSysCount = 0 michael@0: script.LangSysRecord = [] michael@0: michael@0: # set up the feature list michael@0: featureList = gsub.FeatureList = FeatureList() michael@0: featureList.FeatureCount = featureCount michael@0: featureList.FeatureRecord = [] michael@0: for index, tag in enumerate(features): michael@0: # feature record michael@0: featureRecord = FeatureRecord() michael@0: featureRecord.FeatureTag = tag michael@0: feature = featureRecord.Feature = Feature() michael@0: featureList.FeatureRecord.append(featureRecord) michael@0: # feature michael@0: feature.FeatureParams = None michael@0: feature.LookupCount = 1 michael@0: feature.LookupListIndex = [index] michael@0: michael@0: # write the lookups michael@0: lookupList = gsub.LookupList = LookupList() michael@0: lookupList.LookupCount = featureCount michael@0: lookupList.Lookup = [] michael@0: for tag in features: michael@0: # lookup michael@0: lookup = Lookup() michael@0: lookup.LookupType = 1 michael@0: lookup.LookupFlag = 0 michael@0: lookup.SubTableCount = 1 michael@0: lookup.SubTable = [] michael@0: lookupList.Lookup.append(lookup) michael@0: # subtable michael@0: subtable = SingleSubst() michael@0: subtable.Format = 2 michael@0: subtable.LookupType = 1 michael@0: subtable.mapping = { michael@0: "%s.pass" % tag : "%s.fail" % tag, michael@0: "%s.fail" % tag : "%s.pass" % tag, michael@0: } michael@0: lookup.SubTable.append(subtable) michael@0: michael@0: path = outputPath % 1 + ".otf" michael@0: if os.path.exists(path): michael@0: os.remove(path) michael@0: shell.save(path) michael@0: michael@0: # get rid of the shell michael@0: if os.path.exists(shellTempPath): michael@0: os.remove(shellTempPath) michael@0: michael@0: def makeLookup3(): michael@0: # make a variation of the shell TTX data michael@0: f = open(shellSourcePath) michael@0: ttxData = f.read() michael@0: f.close() michael@0: ttxData = ttxData.replace("__familyName__", "gsubtest-lookup3") michael@0: tempShellSourcePath = shellSourcePath + ".temp" michael@0: f = open(tempShellSourcePath, "wb") michael@0: f.write(ttxData) michael@0: f.close() michael@0: michael@0: # compile the shell michael@0: shell = TTFont(sfntVersion="OTTO") michael@0: shell.importXML(tempShellSourcePath) michael@0: shell.save(shellTempPath) michael@0: os.remove(tempShellSourcePath) michael@0: michael@0: # load the shell michael@0: shell = TTFont(shellTempPath) michael@0: michael@0: # grab the PASS and FAIL data michael@0: hmtx = shell["hmtx"] michael@0: glyphSet = shell.getGlyphSet() michael@0: michael@0: failGlyph = glyphSet["F"] michael@0: failGlyph.decompile() michael@0: failGlyphProgram = list(failGlyph.program) michael@0: failGlyphMetrics = hmtx["F"] michael@0: michael@0: passGlyph = glyphSet["P"] michael@0: passGlyph.decompile() michael@0: passGlyphProgram = list(passGlyph.program) michael@0: passGlyphMetrics = hmtx["P"] michael@0: michael@0: # grab some tables michael@0: hmtx = shell["hmtx"] michael@0: cmap = shell["cmap"] michael@0: michael@0: # start the glyph order michael@0: existingGlyphs = [".notdef", "space", "F", "P"] michael@0: glyphOrder = list(existingGlyphs) michael@0: michael@0: # start the CFF michael@0: cff = shell["CFF "].cff michael@0: globalSubrs = cff.GlobalSubrs michael@0: topDict = cff.topDictIndex[0] michael@0: topDict.charset = existingGlyphs michael@0: private = topDict.Private michael@0: charStrings = topDict.CharStrings michael@0: charStringsIndex = charStrings.charStringsIndex michael@0: michael@0: features = sorted(mapping) michael@0: michael@0: # build the outline, hmtx and cmap data michael@0: cp = baseCodepoint michael@0: for index, tag in enumerate(features): michael@0: michael@0: # tag.pass michael@0: glyphName = "%s.pass" % tag michael@0: glyphOrder.append(glyphName) michael@0: addGlyphToCFF( michael@0: glyphName=glyphName, michael@0: program=passGlyphProgram, michael@0: private=private, michael@0: globalSubrs=globalSubrs, michael@0: charStringsIndex=charStringsIndex, michael@0: topDict=topDict, michael@0: charStrings=charStrings michael@0: ) michael@0: hmtx[glyphName] = passGlyphMetrics michael@0: michael@0: # tag.fail michael@0: glyphName = "%s.fail" % tag michael@0: glyphOrder.append(glyphName) michael@0: addGlyphToCFF( michael@0: glyphName=glyphName, michael@0: program=failGlyphProgram, michael@0: private=private, michael@0: globalSubrs=globalSubrs, michael@0: charStringsIndex=charStringsIndex, michael@0: topDict=topDict, michael@0: charStrings=charStrings michael@0: ) michael@0: hmtx[glyphName] = failGlyphMetrics michael@0: michael@0: # tag.default michael@0: glyphName = "%s.default" % tag michael@0: glyphOrder.append(glyphName) michael@0: addGlyphToCFF( michael@0: glyphName=glyphName, michael@0: program=passGlyphProgram, michael@0: private=private, michael@0: globalSubrs=globalSubrs, michael@0: charStringsIndex=charStringsIndex, michael@0: topDict=topDict, michael@0: charStrings=charStrings michael@0: ) michael@0: hmtx[glyphName] = passGlyphMetrics michael@0: michael@0: for table in cmap.tables: michael@0: if table.format == 4: michael@0: table.cmap[cp] = glyphName michael@0: else: michael@0: raise NotImplementedError, "Unsupported cmap table format: %d" % table.format michael@0: cp += 1 michael@0: michael@0: # tag.alt1,2,3 michael@0: for i in range(1,4): michael@0: glyphName = "%s.alt%d" % (tag, i) michael@0: glyphOrder.append(glyphName) michael@0: addGlyphToCFF( michael@0: glyphName=glyphName, michael@0: program=failGlyphProgram, michael@0: private=private, michael@0: globalSubrs=globalSubrs, michael@0: charStringsIndex=charStringsIndex, michael@0: topDict=topDict, michael@0: charStrings=charStrings michael@0: ) michael@0: hmtx[glyphName] = failGlyphMetrics michael@0: for table in cmap.tables: michael@0: if table.format == 4: michael@0: table.cmap[cp] = glyphName michael@0: else: michael@0: raise NotImplementedError, "Unsupported cmap table format: %d" % table.format michael@0: cp += 1 michael@0: michael@0: # set the glyph order michael@0: shell.setGlyphOrder(glyphOrder) michael@0: michael@0: # start the GSUB michael@0: shell["GSUB"] = newTable("GSUB") michael@0: gsub = shell["GSUB"].table = GSUB() michael@0: gsub.Version = 1.0 michael@0: michael@0: # make a list of all the features we will make michael@0: featureCount = len(features) michael@0: michael@0: # set up the script list michael@0: scriptList = gsub.ScriptList = ScriptList() michael@0: scriptList.ScriptCount = 1 michael@0: scriptList.ScriptRecord = [] michael@0: scriptRecord = ScriptRecord() michael@0: scriptList.ScriptRecord.append(scriptRecord) michael@0: scriptRecord.ScriptTag = "DFLT" michael@0: script = scriptRecord.Script = Script() michael@0: defaultLangSys = script.DefaultLangSys = DefaultLangSys() michael@0: defaultLangSys.FeatureCount = featureCount michael@0: defaultLangSys.FeatureIndex = range(defaultLangSys.FeatureCount) michael@0: defaultLangSys.ReqFeatureIndex = 65535 michael@0: defaultLangSys.LookupOrder = None michael@0: script.LangSysCount = 0 michael@0: script.LangSysRecord = [] michael@0: michael@0: # set up the feature list michael@0: featureList = gsub.FeatureList = FeatureList() michael@0: featureList.FeatureCount = featureCount michael@0: featureList.FeatureRecord = [] michael@0: for index, tag in enumerate(features): michael@0: # feature record michael@0: featureRecord = FeatureRecord() michael@0: featureRecord.FeatureTag = tag michael@0: feature = featureRecord.Feature = Feature() michael@0: featureList.FeatureRecord.append(featureRecord) michael@0: # feature michael@0: feature.FeatureParams = None michael@0: feature.LookupCount = 1 michael@0: feature.LookupListIndex = [index] michael@0: michael@0: # write the lookups michael@0: lookupList = gsub.LookupList = LookupList() michael@0: lookupList.LookupCount = featureCount michael@0: lookupList.Lookup = [] michael@0: for tag in features: michael@0: # lookup michael@0: lookup = Lookup() michael@0: lookup.LookupType = 3 michael@0: lookup.LookupFlag = 0 michael@0: lookup.SubTableCount = 1 michael@0: lookup.SubTable = [] michael@0: lookupList.Lookup.append(lookup) michael@0: # subtable michael@0: subtable = AlternateSubst() michael@0: subtable.Format = 1 michael@0: subtable.LookupType = 3 michael@0: subtable.alternates = { michael@0: "%s.default" % tag : ["%s.fail" % tag, "%s.fail" % tag, "%s.fail" % tag], michael@0: "%s.alt1" % tag : ["%s.pass" % tag, "%s.fail" % tag, "%s.fail" % tag], michael@0: "%s.alt2" % tag : ["%s.fail" % tag, "%s.pass" % tag, "%s.fail" % tag], michael@0: "%s.alt3" % tag : ["%s.fail" % tag, "%s.fail" % tag, "%s.pass" % tag] michael@0: } michael@0: lookup.SubTable.append(subtable) michael@0: michael@0: path = outputPath % 3 + ".otf" michael@0: if os.path.exists(path): michael@0: os.remove(path) michael@0: shell.save(path) michael@0: michael@0: # get rid of the shell michael@0: if os.path.exists(shellTempPath): michael@0: os.remove(shellTempPath) michael@0: michael@0: def makeJavascriptData(): michael@0: features = sorted(mapping) michael@0: outStr = [] michael@0: michael@0: outStr.append("") michael@0: outStr.append("/* This file is autogenerated by makegsubfonts.py */") michael@0: outStr.append("") michael@0: outStr.append("/* ") michael@0: outStr.append(" Features defined in gsubtest fonts with associated base") michael@0: outStr.append(" codepoints for each feature:") michael@0: outStr.append("") michael@0: outStr.append(" cp = codepoint for feature featX") michael@0: outStr.append("") michael@0: outStr.append(" cp default PASS") michael@0: outStr.append(" cp featX=1 FAIL") michael@0: outStr.append(" cp featX=2 FAIL") michael@0: outStr.append("") michael@0: outStr.append(" cp+1 default FAIL") michael@0: outStr.append(" cp+1 featX=1 PASS") michael@0: outStr.append(" cp+1 featX=2 FAIL") michael@0: outStr.append("") michael@0: outStr.append(" cp+2 default FAIL") michael@0: outStr.append(" cp+2 featX=1 FAIL") michael@0: outStr.append(" cp+2 featX=2 PASS") michael@0: outStr.append("") michael@0: outStr.append("*/") michael@0: outStr.append("") michael@0: outStr.append("var gFeatures = {"); michael@0: cp = baseCodepoint michael@0: michael@0: taglist = [] michael@0: for tag in features: michael@0: taglist.append("\"%s\": 0x%x" % (tag, cp)) michael@0: cp += 4 michael@0: michael@0: outStr.append(textwrap.fill(", ".join(taglist), initial_indent=" ", subsequent_indent=" ")) michael@0: outStr.append("};"); michael@0: outStr.append(""); michael@0: michael@0: if os.path.exists(javascriptData): michael@0: os.remove(javascriptData) michael@0: michael@0: f = open(javascriptData, "wb") michael@0: f.write("\n".join(outStr)) michael@0: f.close() michael@0: michael@0: michael@0: # build fonts michael@0: michael@0: print "Making lookup type 1 font..." michael@0: makeLookup1() michael@0: michael@0: print "Making lookup type 3 font..." michael@0: makeLookup3() michael@0: michael@0: # output javascript data michael@0: michael@0: print "Making javascript data file..." michael@0: makeJavascriptData()