зеркало из https://github.com/mozilla/gecko-dev.git
486 строки
14 KiB
Python
486 строки
14 KiB
Python
|
|
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() |