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