tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

makegsubfonts.py (14127B)


      1 import os
      2 import textwrap
      3 from xml.etree import ElementTree
      4 from fontTools.ttLib import TTFont, newTable
      5 from fontTools.misc.psCharStrings import T2CharString
      6 from fontTools.ttLib.tables.otTables import GSUB,\
      7    ScriptList, ScriptRecord, Script, DefaultLangSys,\
      8    FeatureList, FeatureRecord, Feature,\
      9    LookupList, Lookup, AlternateSubst, SingleSubst
     10 
     11 # paths
     12 directory = os.path.dirname(__file__)
     13 shellSourcePath = os.path.join(directory, "gsubtest-shell.ttx")
     14 shellTempPath = os.path.join(directory, "gsubtest-shell.otf")
     15 featureList = os.path.join(directory, "gsubtest-features.txt")
     16 javascriptData = os.path.join(directory, "gsubtest-features.js")
     17 outputPath = os.path.join(os.path.dirname(directory), "gsubtest-lookup%d")
     18 
     19 baseCodepoint = 0xe000
     20 
     21 # -------
     22 # Features
     23 # -------
     24 
     25 f = open(featureList, "rb")
     26 text = f.read()
     27 f.close()
     28 mapping = []
     29 for line in text.splitlines():
     30    line = line.strip()
     31    if not line:
     32        continue
     33    if line.startswith("#"):
     34        continue
     35    # parse
     36    values = line.split("\t")
     37    tag = values.pop(0)
     38    mapping.append(tag);
     39 
     40 # --------
     41 # Outlines
     42 # --------
     43 
     44 def addGlyphToCFF(glyphName=None, program=None, private=None, globalSubrs=None, charStringsIndex=None, topDict=None, charStrings=None):
     45    charString = T2CharString(program=program, private=private, globalSubrs=globalSubrs)
     46    charStringsIndex.append(charString)
     47    glyphID = len(topDict.charset)
     48    charStrings.charStrings[glyphName] = glyphID
     49    topDict.charset.append(glyphName)
     50 
     51 def makeLookup1():
     52    # make a variation of the shell TTX data
     53    f = open(shellSourcePath)
     54    ttxData = f.read()
     55    f.close()
     56    ttxData = ttxData.replace("__familyName__", "gsubtest-lookup1")
     57    tempShellSourcePath = shellSourcePath + ".temp"
     58    f = open(tempShellSourcePath, "wb")
     59    f.write(ttxData)
     60    f.close()
     61 
     62    # compile the shell
     63    shell = TTFont(sfntVersion="OTTO")
     64    shell.importXML(tempShellSourcePath)
     65    shell.save(shellTempPath)
     66    os.remove(tempShellSourcePath)
     67 
     68    # load the shell
     69    shell = TTFont(shellTempPath)
     70 
     71    # grab the PASS and FAIL data
     72    hmtx = shell["hmtx"]
     73    glyphSet = shell.getGlyphSet()
     74 
     75    failGlyph = glyphSet["F"]
     76    failGlyph.decompile()
     77    failGlyphProgram = list(failGlyph.program)
     78    failGlyphMetrics = hmtx["F"]
     79 
     80    passGlyph = glyphSet["P"]
     81    passGlyph.decompile()
     82    passGlyphProgram = list(passGlyph.program)
     83    passGlyphMetrics = hmtx["P"]
     84 
     85    # grab some tables
     86    hmtx = shell["hmtx"]
     87    cmap = shell["cmap"]
     88 
     89    # start the glyph order
     90    existingGlyphs = [".notdef", "space", "F", "P"]
     91    glyphOrder = list(existingGlyphs)
     92 
     93    # start the CFF
     94    cff = shell["CFF "].cff
     95    globalSubrs = cff.GlobalSubrs
     96    topDict = cff.topDictIndex[0]
     97    topDict.charset = existingGlyphs
     98    private = topDict.Private
     99    charStrings = topDict.CharStrings
    100    charStringsIndex = charStrings.charStringsIndex
    101 
    102    features = sorted(mapping)
    103 
    104    # build the outline, hmtx and cmap data
    105    cp = baseCodepoint
    106    for index, tag in enumerate(features):
    107 
    108    	# tag.pass
    109    	glyphName = "%s.pass" % tag
    110    	glyphOrder.append(glyphName)
    111    	addGlyphToCFF(
    112    		glyphName=glyphName,
    113    		program=passGlyphProgram,
    114    		private=private,
    115    		globalSubrs=globalSubrs,
    116    		charStringsIndex=charStringsIndex,
    117    		topDict=topDict,
    118            charStrings=charStrings
    119    	)
    120    	hmtx[glyphName] = passGlyphMetrics
    121 
    122    	for table in cmap.tables:
    123    		if table.format == 4:
    124    			table.cmap[cp] = glyphName
    125    		else:
    126    			raise NotImplementedError("Unsupported cmap table format: %d" % table.format)
    127    	cp += 1
    128 
    129    	# tag.fail
    130    	glyphName = "%s.fail" % tag
    131    	glyphOrder.append(glyphName)
    132    	addGlyphToCFF(
    133    		glyphName=glyphName,
    134    		program=failGlyphProgram,
    135    		private=private,
    136    		globalSubrs=globalSubrs,
    137    		charStringsIndex=charStringsIndex,
    138    		topDict=topDict,
    139            charStrings=charStrings
    140    	)
    141    	hmtx[glyphName] = failGlyphMetrics
    142 
    143    	for table in cmap.tables:
    144    		if table.format == 4:
    145    			table.cmap[cp] = glyphName
    146    		else:
    147    			raise NotImplementedError("Unsupported cmap table format: %d" % table.format)
    148 
    149        # bump this up so that the sequence is the same as the lookup 3 font
    150    	cp += 3
    151 
    152    # set the glyph order
    153    shell.setGlyphOrder(glyphOrder)
    154 
    155    # start the GSUB
    156    shell["GSUB"] = newTable("GSUB")
    157    gsub = shell["GSUB"].table = GSUB()
    158    gsub.Version = 1.0
    159 
    160    # make a list of all the features we will make
    161    featureCount = len(features)
    162 
    163    # set up the script list
    164    scriptList = gsub.ScriptList = ScriptList()
    165    scriptList.ScriptCount = 1
    166    scriptList.ScriptRecord = []
    167    scriptRecord = ScriptRecord()
    168    scriptList.ScriptRecord.append(scriptRecord)
    169    scriptRecord.ScriptTag = "DFLT"
    170    script = scriptRecord.Script = Script()
    171    defaultLangSys = script.DefaultLangSys = DefaultLangSys()
    172    defaultLangSys.FeatureCount = featureCount
    173    defaultLangSys.FeatureIndex = range(defaultLangSys.FeatureCount)
    174    defaultLangSys.ReqFeatureIndex = 65535
    175    defaultLangSys.LookupOrder = None
    176    script.LangSysCount = 0
    177    script.LangSysRecord = []
    178 
    179    # set up the feature list
    180    featureList = gsub.FeatureList = FeatureList()
    181    featureList.FeatureCount = featureCount
    182    featureList.FeatureRecord = []
    183    for index, tag in enumerate(features):
    184        # feature record
    185        featureRecord = FeatureRecord()
    186        featureRecord.FeatureTag = tag
    187        feature = featureRecord.Feature = Feature()
    188        featureList.FeatureRecord.append(featureRecord)
    189        # feature
    190        feature.FeatureParams = None
    191        feature.LookupCount = 1
    192        feature.LookupListIndex = [index]
    193 
    194    # write the lookups
    195    lookupList = gsub.LookupList = LookupList()
    196    lookupList.LookupCount = featureCount
    197    lookupList.Lookup = []
    198    for tag in features:
    199        # lookup
    200        lookup = Lookup()
    201        lookup.LookupType = 1
    202        lookup.LookupFlag = 0
    203        lookup.SubTableCount = 1
    204        lookup.SubTable = []
    205        lookupList.Lookup.append(lookup)
    206        # subtable
    207        subtable = SingleSubst()
    208        subtable.Format = 2
    209        subtable.LookupType = 1
    210        subtable.mapping = {
    211            "%s.pass" % tag : "%s.fail" % tag,
    212            "%s.fail" % tag : "%s.pass" % tag,
    213        }
    214        lookup.SubTable.append(subtable)
    215 
    216    path = outputPath % 1 + ".otf"
    217    if os.path.exists(path):
    218    	os.remove(path)
    219    shell.save(path)
    220 
    221    # get rid of the shell
    222    if os.path.exists(shellTempPath):
    223        os.remove(shellTempPath)
    224 
    225 def makeLookup3():
    226    # make a variation of the shell TTX data
    227    f = open(shellSourcePath)
    228    ttxData = f.read()
    229    f.close()
    230    ttxData = ttxData.replace("__familyName__", "gsubtest-lookup3")
    231    tempShellSourcePath = shellSourcePath + ".temp"
    232    f = open(tempShellSourcePath, "wb")
    233    f.write(ttxData)
    234    f.close()
    235 
    236    # compile the shell
    237    shell = TTFont(sfntVersion="OTTO")
    238    shell.importXML(tempShellSourcePath)
    239    shell.save(shellTempPath)
    240    os.remove(tempShellSourcePath)
    241 
    242    # load the shell
    243    shell = TTFont(shellTempPath)
    244 
    245    # grab the PASS and FAIL data
    246    hmtx = shell["hmtx"]
    247    glyphSet = shell.getGlyphSet()
    248 
    249    failGlyph = glyphSet["F"]
    250    failGlyph.decompile()
    251    failGlyphProgram = list(failGlyph.program)
    252    failGlyphMetrics = hmtx["F"]
    253 
    254    passGlyph = glyphSet["P"]
    255    passGlyph.decompile()
    256    passGlyphProgram = list(passGlyph.program)
    257    passGlyphMetrics = hmtx["P"]
    258 
    259    # grab some tables
    260    hmtx = shell["hmtx"]
    261    cmap = shell["cmap"]
    262 
    263    # start the glyph order
    264    existingGlyphs = [".notdef", "space", "F", "P"]
    265    glyphOrder = list(existingGlyphs)
    266 
    267    # start the CFF
    268    cff = shell["CFF "].cff
    269    globalSubrs = cff.GlobalSubrs
    270    topDict = cff.topDictIndex[0]
    271    topDict.charset = existingGlyphs
    272    private = topDict.Private
    273    charStrings = topDict.CharStrings
    274    charStringsIndex = charStrings.charStringsIndex
    275 
    276    features = sorted(mapping)
    277 
    278    # build the outline, hmtx and cmap data
    279    cp = baseCodepoint
    280    for index, tag in enumerate(features):
    281 
    282    	# tag.pass
    283    	glyphName = "%s.pass" % tag
    284    	glyphOrder.append(glyphName)
    285    	addGlyphToCFF(
    286    		glyphName=glyphName,
    287    		program=passGlyphProgram,
    288    		private=private,
    289    		globalSubrs=globalSubrs,
    290    		charStringsIndex=charStringsIndex,
    291    		topDict=topDict,
    292            charStrings=charStrings
    293    	)
    294    	hmtx[glyphName] = passGlyphMetrics
    295 
    296    	# tag.fail
    297    	glyphName = "%s.fail" % tag
    298    	glyphOrder.append(glyphName)
    299    	addGlyphToCFF(
    300    		glyphName=glyphName,
    301    		program=failGlyphProgram,
    302    		private=private,
    303    		globalSubrs=globalSubrs,
    304    		charStringsIndex=charStringsIndex,
    305    		topDict=topDict,
    306            charStrings=charStrings
    307    	)
    308    	hmtx[glyphName] = failGlyphMetrics
    309 
    310    	# tag.default
    311    	glyphName = "%s.default" % tag
    312    	glyphOrder.append(glyphName)
    313    	addGlyphToCFF(
    314    		glyphName=glyphName,
    315    		program=passGlyphProgram,
    316    		private=private,
    317    		globalSubrs=globalSubrs,
    318    		charStringsIndex=charStringsIndex,
    319    		topDict=topDict,
    320            charStrings=charStrings
    321    	)
    322    	hmtx[glyphName] = passGlyphMetrics
    323 
    324    	for table in cmap.tables:
    325    		if table.format == 4:
    326    			table.cmap[cp] = glyphName
    327    		else:
    328    			raise NotImplementedError("Unsupported cmap table format: %d" % table.format)
    329    	cp += 1
    330 
    331    	# tag.alt1,2,3
    332    	for i in range(1,4):
    333    		glyphName = "%s.alt%d" % (tag, i)
    334    		glyphOrder.append(glyphName)
    335    		addGlyphToCFF(
    336    			glyphName=glyphName,
    337    			program=failGlyphProgram,
    338    			private=private,
    339    			globalSubrs=globalSubrs,
    340    			charStringsIndex=charStringsIndex,
    341    			topDict=topDict,
    342                charStrings=charStrings
    343    		)
    344    		hmtx[glyphName] = failGlyphMetrics
    345    		for table in cmap.tables:
    346    			if table.format == 4:
    347    				table.cmap[cp] = glyphName
    348    			else:
    349    				raise NotImplementedError("Unsupported cmap table format: %d" % table.format)
    350    		cp += 1
    351 
    352    # set the glyph order
    353    shell.setGlyphOrder(glyphOrder)
    354 
    355    # start the GSUB
    356    shell["GSUB"] = newTable("GSUB")
    357    gsub = shell["GSUB"].table = GSUB()
    358    gsub.Version = 1.0
    359 
    360    # make a list of all the features we will make
    361    featureCount = len(features)
    362 
    363    # set up the script list
    364    scriptList = gsub.ScriptList = ScriptList()
    365    scriptList.ScriptCount = 1
    366    scriptList.ScriptRecord = []
    367    scriptRecord = ScriptRecord()
    368    scriptList.ScriptRecord.append(scriptRecord)
    369    scriptRecord.ScriptTag = "DFLT"
    370    script = scriptRecord.Script = Script()
    371    defaultLangSys = script.DefaultLangSys = DefaultLangSys()
    372    defaultLangSys.FeatureCount = featureCount
    373    defaultLangSys.FeatureIndex = range(defaultLangSys.FeatureCount)
    374    defaultLangSys.ReqFeatureIndex = 65535
    375    defaultLangSys.LookupOrder = None
    376    script.LangSysCount = 0
    377    script.LangSysRecord = []
    378 
    379    # set up the feature list
    380    featureList = gsub.FeatureList = FeatureList()
    381    featureList.FeatureCount = featureCount
    382    featureList.FeatureRecord = []
    383    for index, tag in enumerate(features):
    384    	# feature record
    385    	featureRecord = FeatureRecord()
    386    	featureRecord.FeatureTag = tag
    387    	feature = featureRecord.Feature = Feature()
    388    	featureList.FeatureRecord.append(featureRecord)
    389    	# feature
    390    	feature.FeatureParams = None
    391    	feature.LookupCount = 1
    392    	feature.LookupListIndex = [index]
    393 
    394    # write the lookups
    395    lookupList = gsub.LookupList = LookupList()
    396    lookupList.LookupCount = featureCount
    397    lookupList.Lookup = []
    398    for tag in features:
    399    	# lookup
    400    	lookup = Lookup()
    401    	lookup.LookupType = 3
    402    	lookup.LookupFlag = 0
    403    	lookup.SubTableCount = 1
    404    	lookup.SubTable = []
    405    	lookupList.Lookup.append(lookup)
    406    	# subtable
    407    	subtable = AlternateSubst()
    408    	subtable.Format = 1
    409    	subtable.LookupType = 3
    410    	subtable.alternates = {
    411    		"%s.default" % tag : ["%s.fail" % tag, "%s.fail" % tag, "%s.fail" % tag],
    412    		"%s.alt1" % tag    : ["%s.pass" % tag, "%s.fail" % tag, "%s.fail" % tag],
    413    		"%s.alt2" % tag    : ["%s.fail" % tag, "%s.pass" % tag, "%s.fail" % tag],
    414    		"%s.alt3" % tag    : ["%s.fail" % tag, "%s.fail" % tag, "%s.pass" % tag]
    415    	}
    416    	lookup.SubTable.append(subtable)
    417 
    418    path = outputPath % 3 + ".otf"
    419    if os.path.exists(path):
    420    	os.remove(path)
    421    shell.save(path)
    422 
    423    # get rid of the shell
    424    if os.path.exists(shellTempPath):
    425        os.remove(shellTempPath)
    426 
    427 def makeJavascriptData():
    428    features = sorted(mapping)
    429    outStr = []
    430 
    431    outStr.append("")
    432    outStr.append("/* This file is autogenerated by makegsubfonts.py */")
    433    outStr.append("")
    434    outStr.append("/* ")
    435    outStr.append("  Features defined in gsubtest fonts with associated base")
    436    outStr.append("  codepoints for each feature:")
    437    outStr.append("")
    438    outStr.append("    cp = codepoint for feature featX")
    439    outStr.append("")
    440    outStr.append("    cp   default   PASS")
    441    outStr.append("    cp   featX=1   FAIL")
    442    outStr.append("    cp   featX=2   FAIL")
    443    outStr.append("")
    444    outStr.append("    cp+1 default   FAIL")
    445    outStr.append("    cp+1 featX=1   PASS")
    446    outStr.append("    cp+1 featX=2   FAIL")
    447    outStr.append("")
    448    outStr.append("    cp+2 default   FAIL")
    449    outStr.append("    cp+2 featX=1   FAIL")
    450    outStr.append("    cp+2 featX=2   PASS")
    451    outStr.append("")
    452    outStr.append("*/")
    453    outStr.append("")
    454    outStr.append("var gFeatures = {");
    455    cp = baseCodepoint
    456 
    457    taglist = []
    458    for tag in features:
    459        taglist.append("\"%s\": 0x%x" % (tag, cp))
    460        cp += 4
    461 
    462    outStr.append(textwrap.fill(", ".join(taglist), initial_indent="  ", subsequent_indent="  "))
    463    outStr.append("};");
    464    outStr.append("");
    465 
    466    if os.path.exists(javascriptData):
    467    	os.remove(javascriptData)
    468 
    469    f = open(javascriptData, "wb")
    470    f.write("\n".join(outStr))
    471    f.close()
    472 
    473 
    474 # build fonts
    475 
    476 print("Making lookup type 1 font...")
    477 makeLookup1()
    478 
    479 print("Making lookup type 3 font...")
    480 makeLookup3()
    481 
    482 # output javascript data
    483 
    484 print("Making javascript data file...")
    485 makeJavascriptData()