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