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