1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/reftests/fonts/gsubtest/makegsubfonts.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,486 @@ 1.4 + 1.5 +import os 1.6 +import textwrap 1.7 +from xml.etree import ElementTree 1.8 +from fontTools.ttLib import TTFont, newTable 1.9 +from fontTools.misc.psCharStrings import T2CharString 1.10 +from fontTools.ttLib.tables.otTables import GSUB,\ 1.11 + ScriptList, ScriptRecord, Script, DefaultLangSys,\ 1.12 + FeatureList, FeatureRecord, Feature,\ 1.13 + LookupList, Lookup, AlternateSubst, SingleSubst 1.14 + 1.15 +# paths 1.16 +directory = os.path.dirname(__file__) 1.17 +shellSourcePath = os.path.join(directory, "gsubtest-shell.ttx") 1.18 +shellTempPath = os.path.join(directory, "gsubtest-shell.otf") 1.19 +featureList = os.path.join(directory, "gsubtest-features.txt") 1.20 +javascriptData = os.path.join(directory, "gsubtest-features.js") 1.21 +outputPath = os.path.join(os.path.dirname(directory), "gsubtest-lookup%d") 1.22 + 1.23 +baseCodepoint = 0xe000 1.24 + 1.25 +# ------- 1.26 +# Features 1.27 +# ------- 1.28 + 1.29 +f = open(featureList, "rb") 1.30 +text = f.read() 1.31 +f.close() 1.32 +mapping = [] 1.33 +for line in text.splitlines(): 1.34 + line = line.strip() 1.35 + if not line: 1.36 + continue 1.37 + if line.startswith("#"): 1.38 + continue 1.39 + # parse 1.40 + values = line.split("\t") 1.41 + tag = values.pop(0) 1.42 + mapping.append(tag); 1.43 + 1.44 +# -------- 1.45 +# Outlines 1.46 +# -------- 1.47 + 1.48 +def addGlyphToCFF(glyphName=None, program=None, private=None, globalSubrs=None, charStringsIndex=None, topDict=None, charStrings=None): 1.49 + charString = T2CharString(program=program, private=private, globalSubrs=globalSubrs) 1.50 + charStringsIndex.append(charString) 1.51 + glyphID = len(topDict.charset) 1.52 + charStrings.charStrings[glyphName] = glyphID 1.53 + topDict.charset.append(glyphName) 1.54 + 1.55 +def makeLookup1(): 1.56 + # make a variation of the shell TTX data 1.57 + f = open(shellSourcePath) 1.58 + ttxData = f.read() 1.59 + f.close() 1.60 + ttxData = ttxData.replace("__familyName__", "gsubtest-lookup1") 1.61 + tempShellSourcePath = shellSourcePath + ".temp" 1.62 + f = open(tempShellSourcePath, "wb") 1.63 + f.write(ttxData) 1.64 + f.close() 1.65 + 1.66 + # compile the shell 1.67 + shell = TTFont(sfntVersion="OTTO") 1.68 + shell.importXML(tempShellSourcePath) 1.69 + shell.save(shellTempPath) 1.70 + os.remove(tempShellSourcePath) 1.71 + 1.72 + # load the shell 1.73 + shell = TTFont(shellTempPath) 1.74 + 1.75 + # grab the PASS and FAIL data 1.76 + hmtx = shell["hmtx"] 1.77 + glyphSet = shell.getGlyphSet() 1.78 + 1.79 + failGlyph = glyphSet["F"] 1.80 + failGlyph.decompile() 1.81 + failGlyphProgram = list(failGlyph.program) 1.82 + failGlyphMetrics = hmtx["F"] 1.83 + 1.84 + passGlyph = glyphSet["P"] 1.85 + passGlyph.decompile() 1.86 + passGlyphProgram = list(passGlyph.program) 1.87 + passGlyphMetrics = hmtx["P"] 1.88 + 1.89 + # grab some tables 1.90 + hmtx = shell["hmtx"] 1.91 + cmap = shell["cmap"] 1.92 + 1.93 + # start the glyph order 1.94 + existingGlyphs = [".notdef", "space", "F", "P"] 1.95 + glyphOrder = list(existingGlyphs) 1.96 + 1.97 + # start the CFF 1.98 + cff = shell["CFF "].cff 1.99 + globalSubrs = cff.GlobalSubrs 1.100 + topDict = cff.topDictIndex[0] 1.101 + topDict.charset = existingGlyphs 1.102 + private = topDict.Private 1.103 + charStrings = topDict.CharStrings 1.104 + charStringsIndex = charStrings.charStringsIndex 1.105 + 1.106 + features = sorted(mapping) 1.107 + 1.108 + # build the outline, hmtx and cmap data 1.109 + cp = baseCodepoint 1.110 + for index, tag in enumerate(features): 1.111 + 1.112 + # tag.pass 1.113 + glyphName = "%s.pass" % tag 1.114 + glyphOrder.append(glyphName) 1.115 + addGlyphToCFF( 1.116 + glyphName=glyphName, 1.117 + program=passGlyphProgram, 1.118 + private=private, 1.119 + globalSubrs=globalSubrs, 1.120 + charStringsIndex=charStringsIndex, 1.121 + topDict=topDict, 1.122 + charStrings=charStrings 1.123 + ) 1.124 + hmtx[glyphName] = passGlyphMetrics 1.125 + 1.126 + for table in cmap.tables: 1.127 + if table.format == 4: 1.128 + table.cmap[cp] = glyphName 1.129 + else: 1.130 + raise NotImplementedError, "Unsupported cmap table format: %d" % table.format 1.131 + cp += 1 1.132 + 1.133 + # tag.fail 1.134 + glyphName = "%s.fail" % tag 1.135 + glyphOrder.append(glyphName) 1.136 + addGlyphToCFF( 1.137 + glyphName=glyphName, 1.138 + program=failGlyphProgram, 1.139 + private=private, 1.140 + globalSubrs=globalSubrs, 1.141 + charStringsIndex=charStringsIndex, 1.142 + topDict=topDict, 1.143 + charStrings=charStrings 1.144 + ) 1.145 + hmtx[glyphName] = failGlyphMetrics 1.146 + 1.147 + for table in cmap.tables: 1.148 + if table.format == 4: 1.149 + table.cmap[cp] = glyphName 1.150 + else: 1.151 + raise NotImplementedError, "Unsupported cmap table format: %d" % table.format 1.152 + 1.153 + # bump this up so that the sequence is the same as the lookup 3 font 1.154 + cp += 3 1.155 + 1.156 + # set the glyph order 1.157 + shell.setGlyphOrder(glyphOrder) 1.158 + 1.159 + # start the GSUB 1.160 + shell["GSUB"] = newTable("GSUB") 1.161 + gsub = shell["GSUB"].table = GSUB() 1.162 + gsub.Version = 1.0 1.163 + 1.164 + # make a list of all the features we will make 1.165 + featureCount = len(features) 1.166 + 1.167 + # set up the script list 1.168 + scriptList = gsub.ScriptList = ScriptList() 1.169 + scriptList.ScriptCount = 1 1.170 + scriptList.ScriptRecord = [] 1.171 + scriptRecord = ScriptRecord() 1.172 + scriptList.ScriptRecord.append(scriptRecord) 1.173 + scriptRecord.ScriptTag = "DFLT" 1.174 + script = scriptRecord.Script = Script() 1.175 + defaultLangSys = script.DefaultLangSys = DefaultLangSys() 1.176 + defaultLangSys.FeatureCount = featureCount 1.177 + defaultLangSys.FeatureIndex = range(defaultLangSys.FeatureCount) 1.178 + defaultLangSys.ReqFeatureIndex = 65535 1.179 + defaultLangSys.LookupOrder = None 1.180 + script.LangSysCount = 0 1.181 + script.LangSysRecord = [] 1.182 + 1.183 + # set up the feature list 1.184 + featureList = gsub.FeatureList = FeatureList() 1.185 + featureList.FeatureCount = featureCount 1.186 + featureList.FeatureRecord = [] 1.187 + for index, tag in enumerate(features): 1.188 + # feature record 1.189 + featureRecord = FeatureRecord() 1.190 + featureRecord.FeatureTag = tag 1.191 + feature = featureRecord.Feature = Feature() 1.192 + featureList.FeatureRecord.append(featureRecord) 1.193 + # feature 1.194 + feature.FeatureParams = None 1.195 + feature.LookupCount = 1 1.196 + feature.LookupListIndex = [index] 1.197 + 1.198 + # write the lookups 1.199 + lookupList = gsub.LookupList = LookupList() 1.200 + lookupList.LookupCount = featureCount 1.201 + lookupList.Lookup = [] 1.202 + for tag in features: 1.203 + # lookup 1.204 + lookup = Lookup() 1.205 + lookup.LookupType = 1 1.206 + lookup.LookupFlag = 0 1.207 + lookup.SubTableCount = 1 1.208 + lookup.SubTable = [] 1.209 + lookupList.Lookup.append(lookup) 1.210 + # subtable 1.211 + subtable = SingleSubst() 1.212 + subtable.Format = 2 1.213 + subtable.LookupType = 1 1.214 + subtable.mapping = { 1.215 + "%s.pass" % tag : "%s.fail" % tag, 1.216 + "%s.fail" % tag : "%s.pass" % tag, 1.217 + } 1.218 + lookup.SubTable.append(subtable) 1.219 + 1.220 + path = outputPath % 1 + ".otf" 1.221 + if os.path.exists(path): 1.222 + os.remove(path) 1.223 + shell.save(path) 1.224 + 1.225 + # get rid of the shell 1.226 + if os.path.exists(shellTempPath): 1.227 + os.remove(shellTempPath) 1.228 + 1.229 +def makeLookup3(): 1.230 + # make a variation of the shell TTX data 1.231 + f = open(shellSourcePath) 1.232 + ttxData = f.read() 1.233 + f.close() 1.234 + ttxData = ttxData.replace("__familyName__", "gsubtest-lookup3") 1.235 + tempShellSourcePath = shellSourcePath + ".temp" 1.236 + f = open(tempShellSourcePath, "wb") 1.237 + f.write(ttxData) 1.238 + f.close() 1.239 + 1.240 + # compile the shell 1.241 + shell = TTFont(sfntVersion="OTTO") 1.242 + shell.importXML(tempShellSourcePath) 1.243 + shell.save(shellTempPath) 1.244 + os.remove(tempShellSourcePath) 1.245 + 1.246 + # load the shell 1.247 + shell = TTFont(shellTempPath) 1.248 + 1.249 + # grab the PASS and FAIL data 1.250 + hmtx = shell["hmtx"] 1.251 + glyphSet = shell.getGlyphSet() 1.252 + 1.253 + failGlyph = glyphSet["F"] 1.254 + failGlyph.decompile() 1.255 + failGlyphProgram = list(failGlyph.program) 1.256 + failGlyphMetrics = hmtx["F"] 1.257 + 1.258 + passGlyph = glyphSet["P"] 1.259 + passGlyph.decompile() 1.260 + passGlyphProgram = list(passGlyph.program) 1.261 + passGlyphMetrics = hmtx["P"] 1.262 + 1.263 + # grab some tables 1.264 + hmtx = shell["hmtx"] 1.265 + cmap = shell["cmap"] 1.266 + 1.267 + # start the glyph order 1.268 + existingGlyphs = [".notdef", "space", "F", "P"] 1.269 + glyphOrder = list(existingGlyphs) 1.270 + 1.271 + # start the CFF 1.272 + cff = shell["CFF "].cff 1.273 + globalSubrs = cff.GlobalSubrs 1.274 + topDict = cff.topDictIndex[0] 1.275 + topDict.charset = existingGlyphs 1.276 + private = topDict.Private 1.277 + charStrings = topDict.CharStrings 1.278 + charStringsIndex = charStrings.charStringsIndex 1.279 + 1.280 + features = sorted(mapping) 1.281 + 1.282 + # build the outline, hmtx and cmap data 1.283 + cp = baseCodepoint 1.284 + for index, tag in enumerate(features): 1.285 + 1.286 + # tag.pass 1.287 + glyphName = "%s.pass" % tag 1.288 + glyphOrder.append(glyphName) 1.289 + addGlyphToCFF( 1.290 + glyphName=glyphName, 1.291 + program=passGlyphProgram, 1.292 + private=private, 1.293 + globalSubrs=globalSubrs, 1.294 + charStringsIndex=charStringsIndex, 1.295 + topDict=topDict, 1.296 + charStrings=charStrings 1.297 + ) 1.298 + hmtx[glyphName] = passGlyphMetrics 1.299 + 1.300 + # tag.fail 1.301 + glyphName = "%s.fail" % tag 1.302 + glyphOrder.append(glyphName) 1.303 + addGlyphToCFF( 1.304 + glyphName=glyphName, 1.305 + program=failGlyphProgram, 1.306 + private=private, 1.307 + globalSubrs=globalSubrs, 1.308 + charStringsIndex=charStringsIndex, 1.309 + topDict=topDict, 1.310 + charStrings=charStrings 1.311 + ) 1.312 + hmtx[glyphName] = failGlyphMetrics 1.313 + 1.314 + # tag.default 1.315 + glyphName = "%s.default" % tag 1.316 + glyphOrder.append(glyphName) 1.317 + addGlyphToCFF( 1.318 + glyphName=glyphName, 1.319 + program=passGlyphProgram, 1.320 + private=private, 1.321 + globalSubrs=globalSubrs, 1.322 + charStringsIndex=charStringsIndex, 1.323 + topDict=topDict, 1.324 + charStrings=charStrings 1.325 + ) 1.326 + hmtx[glyphName] = passGlyphMetrics 1.327 + 1.328 + for table in cmap.tables: 1.329 + if table.format == 4: 1.330 + table.cmap[cp] = glyphName 1.331 + else: 1.332 + raise NotImplementedError, "Unsupported cmap table format: %d" % table.format 1.333 + cp += 1 1.334 + 1.335 + # tag.alt1,2,3 1.336 + for i in range(1,4): 1.337 + glyphName = "%s.alt%d" % (tag, i) 1.338 + glyphOrder.append(glyphName) 1.339 + addGlyphToCFF( 1.340 + glyphName=glyphName, 1.341 + program=failGlyphProgram, 1.342 + private=private, 1.343 + globalSubrs=globalSubrs, 1.344 + charStringsIndex=charStringsIndex, 1.345 + topDict=topDict, 1.346 + charStrings=charStrings 1.347 + ) 1.348 + hmtx[glyphName] = failGlyphMetrics 1.349 + for table in cmap.tables: 1.350 + if table.format == 4: 1.351 + table.cmap[cp] = glyphName 1.352 + else: 1.353 + raise NotImplementedError, "Unsupported cmap table format: %d" % table.format 1.354 + cp += 1 1.355 + 1.356 + # set the glyph order 1.357 + shell.setGlyphOrder(glyphOrder) 1.358 + 1.359 + # start the GSUB 1.360 + shell["GSUB"] = newTable("GSUB") 1.361 + gsub = shell["GSUB"].table = GSUB() 1.362 + gsub.Version = 1.0 1.363 + 1.364 + # make a list of all the features we will make 1.365 + featureCount = len(features) 1.366 + 1.367 + # set up the script list 1.368 + scriptList = gsub.ScriptList = ScriptList() 1.369 + scriptList.ScriptCount = 1 1.370 + scriptList.ScriptRecord = [] 1.371 + scriptRecord = ScriptRecord() 1.372 + scriptList.ScriptRecord.append(scriptRecord) 1.373 + scriptRecord.ScriptTag = "DFLT" 1.374 + script = scriptRecord.Script = Script() 1.375 + defaultLangSys = script.DefaultLangSys = DefaultLangSys() 1.376 + defaultLangSys.FeatureCount = featureCount 1.377 + defaultLangSys.FeatureIndex = range(defaultLangSys.FeatureCount) 1.378 + defaultLangSys.ReqFeatureIndex = 65535 1.379 + defaultLangSys.LookupOrder = None 1.380 + script.LangSysCount = 0 1.381 + script.LangSysRecord = [] 1.382 + 1.383 + # set up the feature list 1.384 + featureList = gsub.FeatureList = FeatureList() 1.385 + featureList.FeatureCount = featureCount 1.386 + featureList.FeatureRecord = [] 1.387 + for index, tag in enumerate(features): 1.388 + # feature record 1.389 + featureRecord = FeatureRecord() 1.390 + featureRecord.FeatureTag = tag 1.391 + feature = featureRecord.Feature = Feature() 1.392 + featureList.FeatureRecord.append(featureRecord) 1.393 + # feature 1.394 + feature.FeatureParams = None 1.395 + feature.LookupCount = 1 1.396 + feature.LookupListIndex = [index] 1.397 + 1.398 + # write the lookups 1.399 + lookupList = gsub.LookupList = LookupList() 1.400 + lookupList.LookupCount = featureCount 1.401 + lookupList.Lookup = [] 1.402 + for tag in features: 1.403 + # lookup 1.404 + lookup = Lookup() 1.405 + lookup.LookupType = 3 1.406 + lookup.LookupFlag = 0 1.407 + lookup.SubTableCount = 1 1.408 + lookup.SubTable = [] 1.409 + lookupList.Lookup.append(lookup) 1.410 + # subtable 1.411 + subtable = AlternateSubst() 1.412 + subtable.Format = 1 1.413 + subtable.LookupType = 3 1.414 + subtable.alternates = { 1.415 + "%s.default" % tag : ["%s.fail" % tag, "%s.fail" % tag, "%s.fail" % tag], 1.416 + "%s.alt1" % tag : ["%s.pass" % tag, "%s.fail" % tag, "%s.fail" % tag], 1.417 + "%s.alt2" % tag : ["%s.fail" % tag, "%s.pass" % tag, "%s.fail" % tag], 1.418 + "%s.alt3" % tag : ["%s.fail" % tag, "%s.fail" % tag, "%s.pass" % tag] 1.419 + } 1.420 + lookup.SubTable.append(subtable) 1.421 + 1.422 + path = outputPath % 3 + ".otf" 1.423 + if os.path.exists(path): 1.424 + os.remove(path) 1.425 + shell.save(path) 1.426 + 1.427 + # get rid of the shell 1.428 + if os.path.exists(shellTempPath): 1.429 + os.remove(shellTempPath) 1.430 + 1.431 +def makeJavascriptData(): 1.432 + features = sorted(mapping) 1.433 + outStr = [] 1.434 + 1.435 + outStr.append("") 1.436 + outStr.append("/* This file is autogenerated by makegsubfonts.py */") 1.437 + outStr.append("") 1.438 + outStr.append("/* ") 1.439 + outStr.append(" Features defined in gsubtest fonts with associated base") 1.440 + outStr.append(" codepoints for each feature:") 1.441 + outStr.append("") 1.442 + outStr.append(" cp = codepoint for feature featX") 1.443 + outStr.append("") 1.444 + outStr.append(" cp default PASS") 1.445 + outStr.append(" cp featX=1 FAIL") 1.446 + outStr.append(" cp featX=2 FAIL") 1.447 + outStr.append("") 1.448 + outStr.append(" cp+1 default FAIL") 1.449 + outStr.append(" cp+1 featX=1 PASS") 1.450 + outStr.append(" cp+1 featX=2 FAIL") 1.451 + outStr.append("") 1.452 + outStr.append(" cp+2 default FAIL") 1.453 + outStr.append(" cp+2 featX=1 FAIL") 1.454 + outStr.append(" cp+2 featX=2 PASS") 1.455 + outStr.append("") 1.456 + outStr.append("*/") 1.457 + outStr.append("") 1.458 + outStr.append("var gFeatures = {"); 1.459 + cp = baseCodepoint 1.460 + 1.461 + taglist = [] 1.462 + for tag in features: 1.463 + taglist.append("\"%s\": 0x%x" % (tag, cp)) 1.464 + cp += 4 1.465 + 1.466 + outStr.append(textwrap.fill(", ".join(taglist), initial_indent=" ", subsequent_indent=" ")) 1.467 + outStr.append("};"); 1.468 + outStr.append(""); 1.469 + 1.470 + if os.path.exists(javascriptData): 1.471 + os.remove(javascriptData) 1.472 + 1.473 + f = open(javascriptData, "wb") 1.474 + f.write("\n".join(outStr)) 1.475 + f.close() 1.476 + 1.477 + 1.478 +# build fonts 1.479 + 1.480 +print "Making lookup type 1 font..." 1.481 +makeLookup1() 1.482 + 1.483 +print "Making lookup type 3 font..." 1.484 +makeLookup3() 1.485 + 1.486 +# output javascript data 1.487 + 1.488 +print "Making javascript data file..." 1.489 +makeJavascriptData() 1.490 \ No newline at end of file