otTables.py revision 97dea0a5d02ba1655d27a06fe91540e3495b8ef9
1"""fontTools.ttLib.tables.otTables -- A collection of classes representing the various
2OpenType subtables.
3
4Most are constructed upon import from data in otData.py, all are populated with
5converter objects from otConverters.py.
6"""
7import operator
8from .otBase import BaseTable, FormatSwitchingBaseTable
9from types import TupleType
10import warnings
11
12
13class LookupOrder(BaseTable):
14	"""Dummy class; this table isn't defined, but is used, and is always NULL."""
15
16class FeatureParams(BaseTable):
17
18	def compile(self, writer, font):
19		assert featureParamTypes.get(writer['FeatureTag'], None) == self.__class__, "Wrong FeatureParams type for feature '%s': %s" % (writer['FeatureTag'], self.__class__.__name__)
20		BaseTable.compile(self, writer, font)
21
22class FeatureParamsSize(FeatureParams):
23	pass
24
25class FeatureParamsStylisticSet(FeatureParams):
26	pass
27
28class FeatureParamsCharacterVariants(FeatureParams):
29	pass
30
31class Coverage(FormatSwitchingBaseTable):
32
33	# manual implementation to get rid of glyphID dependencies
34
35	def postRead(self, rawTable, font):
36		if self.Format == 1:
37			self.glyphs = rawTable["GlyphArray"]
38		elif self.Format == 2:
39			glyphs = self.glyphs = []
40			ranges = rawTable["RangeRecord"]
41			glyphOrder = font.getGlyphOrder()
42			# Some SIL fonts have coverage entries that don't have sorted
43			# StartCoverageIndex.  If it is so, fixup and warn.  We undo
44			# this when writing font out.
45			sorted_ranges = sorted(ranges, cmp=lambda a,b: cmp(a.StartCoverageIndex,b.StartCoverageIndex))
46			if ranges != sorted_ranges:
47				warnings.warn("GSUB/GPOS Coverage is not sorted by glyph ids.")
48				ranges = sorted_ranges
49			del sorted_ranges
50			for r in ranges:
51				assert r.StartCoverageIndex == len(glyphs), \
52					(r.StartCoverageIndex, len(glyphs))
53				start = r.Start
54				end = r.End
55				try:
56					startID = font.getGlyphID(start, requireReal=1)
57				except KeyError:
58					warnings.warn("Coverage table has start glyph ID out of range: %s." % start)
59					continue
60				try:
61					endID = font.getGlyphID(end, requireReal=1)
62				except KeyError:
63					warnings.warn("Coverage table has end glyph ID out of range: %s." % end)
64					endID = len(glyphOrder)
65				glyphs.append(start)
66				rangeList = [glyphOrder[glyphID] for glyphID in range(startID + 1, endID) ]
67				glyphs += rangeList
68				if start != end and endID < len(glyphOrder):
69					glyphs.append(end)
70		else:
71			assert 0, "unknown format: %s" % self.Format
72
73	def preWrite(self, font):
74		glyphs = getattr(self, "glyphs", None)
75		if glyphs is None:
76			glyphs = self.glyphs = []
77		format = 1
78		rawTable = {"GlyphArray": glyphs}
79		getGlyphID = font.getGlyphID
80		if glyphs:
81			# find out whether Format 2 is more compact or not
82			glyphIDs = [getGlyphID(glyphName) for glyphName in glyphs ]
83			brokenOrder = sorted(glyphIDs) != glyphIDs
84
85			last = glyphIDs[0]
86			ranges = [[last]]
87			for glyphID in glyphIDs[1:]:
88				if glyphID != last + 1:
89					ranges[-1].append(last)
90					ranges.append([glyphID])
91				last = glyphID
92			ranges[-1].append(last)
93
94			if brokenOrder or len(ranges) * 3 < len(glyphs):  # 3 words vs. 1 word
95				# Format 2 is more compact
96				index = 0
97				for i in range(len(ranges)):
98					start, end = ranges[i]
99					r = RangeRecord()
100					r.StartID = start
101					r.Start = font.getGlyphName(start)
102					r.End = font.getGlyphName(end)
103					r.StartCoverageIndex = index
104					ranges[i] = r
105					index = index + end - start + 1
106				if brokenOrder:
107					warnings.warn("GSUB/GPOS Coverage is not sorted by glyph ids.")
108					ranges.sort(cmp=lambda a,b: cmp(a.StartID,b.StartID))
109				for r in ranges:
110					del r.StartID
111				format = 2
112				rawTable = {"RangeRecord": ranges}
113			#else:
114			#	fallthrough; Format 1 is more compact
115		self.Format = format
116		return rawTable
117
118	def toXML2(self, xmlWriter, font):
119		for glyphName in getattr(self, "glyphs", []):
120			xmlWriter.simpletag("Glyph", value=glyphName)
121			xmlWriter.newline()
122
123	def fromXML(self, name, attrs, content, font):
124		glyphs = getattr(self, "glyphs", None)
125		if glyphs is None:
126			glyphs = []
127			self.glyphs = glyphs
128		glyphs.append(attrs["value"])
129
130
131def doModulo(value):
132	if value < 0:
133		return value + 65536
134	return value
135
136class SingleSubst(FormatSwitchingBaseTable):
137
138	def postRead(self, rawTable, font):
139		mapping = {}
140		input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
141		lenMapping = len(input)
142		if self.Format == 1:
143			delta = rawTable["DeltaGlyphID"]
144			inputGIDS =  [ font.getGlyphID(name) for name in input ]
145			inputGIDS = map(doModulo, inputGIDS)
146			outGIDS = [ glyphID + delta for glyphID in inputGIDS ]
147			outGIDS = map(doModulo, outGIDS)
148			outNames = [ font.getGlyphName(glyphID) for glyphID in outGIDS ]
149			map(operator.setitem, [mapping]*lenMapping, input, outNames)
150		elif self.Format == 2:
151			assert len(input) == rawTable["GlyphCount"], \
152					"invalid SingleSubstFormat2 table"
153			subst = rawTable["Substitute"]
154			map(operator.setitem, [mapping]*lenMapping, input, subst)
155		else:
156			assert 0, "unknown format: %s" % self.Format
157		self.mapping = mapping
158
159	def preWrite(self, font):
160		mapping = getattr(self, "mapping", None)
161		if mapping is None:
162			mapping = self.mapping = {}
163		items = mapping.items()
164		getGlyphID = font.getGlyphID
165		gidItems = [(getGlyphID(item[0]), getGlyphID(item[1])) for item in items]
166		sortableItems = zip(gidItems, items)
167		sortableItems.sort()
168
169		# figure out format
170		format = 2
171		delta = None
172		for inID, outID in gidItems:
173			if delta is None:
174				delta = outID - inID
175			else:
176				if delta != outID - inID:
177					break
178		else:
179			format = 1
180
181		rawTable = {}
182		self.Format = format
183		cov = Coverage()
184		input =  [ item [1][0] for item in sortableItems]
185		subst =  [ item [1][1] for item in sortableItems]
186		cov.glyphs = input
187		rawTable["Coverage"] = cov
188		if format == 1:
189			assert delta is not None
190			rawTable["DeltaGlyphID"] = delta
191		else:
192			rawTable["Substitute"] = subst
193		return rawTable
194
195	def toXML2(self, xmlWriter, font):
196		items = self.mapping.items()
197		items.sort()
198		for inGlyph, outGlyph in items:
199			xmlWriter.simpletag("Substitution",
200					[("in", inGlyph), ("out", outGlyph)])
201			xmlWriter.newline()
202
203	def fromXML(self, name, attrs, content, font):
204		mapping = getattr(self, "mapping", None)
205		if mapping is None:
206			mapping = {}
207			self.mapping = mapping
208		mapping[attrs["in"]] = attrs["out"]
209
210
211class ClassDef(FormatSwitchingBaseTable):
212
213	def postRead(self, rawTable, font):
214		classDefs = {}
215		getGlyphName = font.getGlyphName
216
217		if self.Format == 1:
218			start = rawTable["StartGlyph"]
219			classList = rawTable["ClassValueArray"]
220			lenList = len(classList)
221			glyphID = font.getGlyphID(start)
222			gidList = list(range(glyphID, glyphID + len(classList)))
223			keyList = [getGlyphName(glyphID) for glyphID in gidList]
224
225			map(operator.setitem, [classDefs]*lenList, keyList, classList)
226
227		elif self.Format == 2:
228			records = rawTable["ClassRangeRecord"]
229			for rec in records:
230				start = rec.Start
231				end = rec.End
232				cls = rec.Class
233				classDefs[start] = cls
234				glyphIDs = list(range(font.getGlyphID(start) + 1, font.getGlyphID(end)))
235				lenList = len(glyphIDs)
236				keyList = [getGlyphName(glyphID) for glyphID in glyphIDs]
237				map(operator.setitem,  [classDefs]*lenList, keyList, [cls]*lenList)
238				classDefs[end] = cls
239		else:
240			assert 0, "unknown format: %s" % self.Format
241		self.classDefs = classDefs
242
243	def preWrite(self, font):
244		classDefs = getattr(self, "classDefs", None)
245		if classDefs is None:
246			classDefs = self.classDefs = {}
247		items = classDefs.items()
248		getGlyphID = font.getGlyphID
249		for i in range(len(items)):
250			glyphName, cls = items[i]
251			items[i] = getGlyphID(glyphName), glyphName, cls
252		items.sort()
253		if items:
254			last, lastName, lastCls = items[0]
255			rec = ClassRangeRecord()
256			rec.Start = lastName
257			rec.Class = lastCls
258			ranges = [rec]
259			for glyphID, glyphName, cls in items[1:]:
260				if glyphID != last + 1 or cls != lastCls:
261					rec.End = lastName
262					rec = ClassRangeRecord()
263					rec.Start = glyphName
264					rec.Class = cls
265					ranges.append(rec)
266				last = glyphID
267				lastName = glyphName
268				lastCls = cls
269			rec.End = lastName
270		else:
271			ranges = []
272		self.Format = 2  # currently no support for Format 1
273		return {"ClassRangeRecord": ranges}
274
275	def toXML2(self, xmlWriter, font):
276		items = self.classDefs.items()
277		items.sort()
278		for glyphName, cls in items:
279			xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)])
280			xmlWriter.newline()
281
282	def fromXML(self, name, attrs, content, font):
283		classDefs = getattr(self, "classDefs", None)
284		if classDefs is None:
285			classDefs = {}
286			self.classDefs = classDefs
287		classDefs[attrs["glyph"]] = int(attrs["class"])
288
289
290class AlternateSubst(FormatSwitchingBaseTable):
291
292	def postRead(self, rawTable, font):
293		alternates = {}
294		if self.Format == 1:
295			input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
296			alts = rawTable["AlternateSet"]
297			if len(input) != len(alts):
298				assert len(input) == len(alts)
299			for i in range(len(input)):
300				alternates[input[i]] = alts[i].Alternate
301		else:
302			assert 0, "unknown format: %s" % self.Format
303		self.alternates = alternates
304
305	def preWrite(self, font):
306		self.Format = 1
307		alternates = getattr(self, "alternates", None)
308		if alternates is None:
309			alternates = self.alternates = {}
310		items = alternates.items()
311		for i in range(len(items)):
312			glyphName, set = items[i]
313			items[i] = font.getGlyphID(glyphName), glyphName, set
314		items.sort()
315		cov = Coverage()
316		cov.glyphs = [ item[1] for item in items]
317		alternates = []
318		setList = [ item[-1] for item in items]
319		for  set in setList:
320			alts = AlternateSet()
321			alts.Alternate = set
322			alternates.append(alts)
323		# a special case to deal with the fact that several hundred Adobe Japan1-5
324		# CJK fonts will overflow an offset if the coverage table isn't pushed to the end.
325		# Also useful in that when splitting a sub-table because of an offset overflow
326		# I don't need to calculate the change in the subtable offset due to the change in the coverage table size.
327		# Allows packing more rules in subtable.
328		self.sortCoverageLast = 1
329		return {"Coverage": cov, "AlternateSet": alternates}
330
331	def toXML2(self, xmlWriter, font):
332		items = self.alternates.items()
333		items.sort()
334		for glyphName, alternates in items:
335			xmlWriter.begintag("AlternateSet", glyph=glyphName)
336			xmlWriter.newline()
337			for alt in alternates:
338				xmlWriter.simpletag("Alternate", glyph=alt)
339				xmlWriter.newline()
340			xmlWriter.endtag("AlternateSet")
341			xmlWriter.newline()
342
343	def fromXML(self, name, attrs, content, font):
344		alternates = getattr(self, "alternates", None)
345		if alternates is None:
346			alternates = {}
347			self.alternates = alternates
348		glyphName = attrs["glyph"]
349		set = []
350		alternates[glyphName] = set
351		for element in content:
352			if type(element) != TupleType:
353				continue
354			name, attrs, content = element
355			set.append(attrs["glyph"])
356
357
358class LigatureSubst(FormatSwitchingBaseTable):
359
360	def postRead(self, rawTable, font):
361		ligatures = {}
362		if self.Format == 1:
363			input = rawTable["Coverage"].glyphs
364			ligSets = rawTable["LigatureSet"]
365			assert len(input) == len(ligSets)
366			for i in range(len(input)):
367				ligatures[input[i]] = ligSets[i].Ligature
368		else:
369			assert 0, "unknown format: %s" % self.Format
370		self.ligatures = ligatures
371
372	def preWrite(self, font):
373		ligatures = getattr(self, "ligatures", None)
374		if ligatures is None:
375			ligatures = self.ligatures = {}
376		items = ligatures.items()
377		for i in range(len(items)):
378			glyphName, set = items[i]
379			items[i] = font.getGlyphID(glyphName), glyphName, set
380		items.sort()
381		cov = Coverage()
382		cov.glyphs = [ item[1] for item in items]
383
384		ligSets = []
385		setList = [ item[-1] for item in items ]
386		for set in setList:
387			ligSet = LigatureSet()
388			ligs = ligSet.Ligature = []
389			for lig in set:
390				ligs.append(lig)
391			ligSets.append(ligSet)
392		# Useful in that when splitting a sub-table because of an offset overflow
393		# I don't need to calculate the change in subtabl offset due to the coverage table size.
394		# Allows packing more rules in subtable.
395		self.sortCoverageLast = 1
396		return {"Coverage": cov, "LigatureSet": ligSets}
397
398	def toXML2(self, xmlWriter, font):
399		items = self.ligatures.items()
400		items.sort()
401		for glyphName, ligSets in items:
402			xmlWriter.begintag("LigatureSet", glyph=glyphName)
403			xmlWriter.newline()
404			for lig in ligSets:
405				xmlWriter.simpletag("Ligature", glyph=lig.LigGlyph,
406					components=",".join(lig.Component))
407				xmlWriter.newline()
408			xmlWriter.endtag("LigatureSet")
409			xmlWriter.newline()
410
411	def fromXML(self, name, attrs, content, font):
412		ligatures = getattr(self, "ligatures", None)
413		if ligatures is None:
414			ligatures = {}
415			self.ligatures = ligatures
416		glyphName = attrs["glyph"]
417		ligs = []
418		ligatures[glyphName] = ligs
419		for element in content:
420			if type(element) != TupleType:
421				continue
422			name, attrs, content = element
423			lig = Ligature()
424			lig.LigGlyph = attrs["glyph"]
425			lig.Component = attrs["components"].split(",")
426			ligs.append(lig)
427
428
429#
430# For each subtable format there is a class. However, we don't really distinguish
431# between "field name" and "format name": often these are the same. Yet there's
432# a whole bunch of fields with different names. The following dict is a mapping
433# from "format name" to "field name". _buildClasses() uses this to create a
434# subclass for each alternate field name.
435#
436_equivalents = {
437	'MarkArray': ("Mark1Array",),
438	'LangSys': ('DefaultLangSys',),
439	'Coverage': ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage',
440			'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage',
441			'LookAheadCoverage'),
442	'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef',
443			'LookAheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'),
444	'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor',
445			'Mark2Anchor', 'MarkAnchor'),
446	'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice',
447			'XDeviceTable', 'YDeviceTable', 'DeviceTable'),
448	'Axis': ('HorizAxis', 'VertAxis',),
449	'MinMax': ('DefaultMinMax',),
450	'BaseCoord': ('MinCoord', 'MaxCoord',),
451	'JstfLangSys': ('DefJstfLangSys',),
452	'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB',
453			'ExtensionDisableGSUB',),
454	'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS',
455			'ExtensionDisableGPOS',),
456	'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',),
457}
458
459#
460# OverFlow logic, to automatically create ExtensionLookups
461# XXX This should probably move to otBase.py
462#
463
464def fixLookupOverFlows(ttf, overflowRecord):
465	""" Either the offset from the LookupList to a lookup overflowed, or
466	an offset from a lookup to a subtable overflowed.
467	The table layout is:
468	GPSO/GUSB
469		Script List
470		Feature List
471		LookUpList
472			Lookup[0] and contents
473				SubTable offset list
474					SubTable[0] and contents
475					...
476					SubTable[n] and contents
477			...
478			Lookup[n] and contents
479				SubTable offset list
480					SubTable[0] and contents
481					...
482					SubTable[n] and contents
483	If the offset to a lookup overflowed (SubTableIndex == None)
484		we must promote the *previous*	lookup to an Extension type.
485	If the offset from a lookup to subtable overflowed, then we must promote it
486		to an Extension Lookup type.
487	"""
488	ok = 0
489	lookupIndex = overflowRecord.LookupListIndex
490	if (overflowRecord.SubTableIndex == None):
491		lookupIndex = lookupIndex - 1
492	if lookupIndex < 0:
493		return ok
494	if overflowRecord.tableType == 'GSUB':
495		extType = 7
496	elif overflowRecord.tableType == 'GPOS':
497		extType = 9
498
499	lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup
500	lookup = lookups[lookupIndex]
501	# If the previous lookup is an extType, look further back. Very unlikely, but possible.
502	while lookup.LookupType == extType:
503		lookupIndex = lookupIndex -1
504		if lookupIndex < 0:
505			return ok
506		lookup = lookups[lookupIndex]
507
508	for si in range(len(lookup.SubTable)):
509		subTable = lookup.SubTable[si]
510		extSubTableClass = lookupTypes[overflowRecord.tableType][extType]
511		extSubTable = extSubTableClass()
512		extSubTable.Format = 1
513		extSubTable.ExtensionLookupType = lookup.LookupType
514		extSubTable.ExtSubTable = subTable
515		lookup.SubTable[si] = extSubTable
516	lookup.LookupType = extType
517	ok = 1
518	return ok
519
520def splitAlternateSubst(oldSubTable, newSubTable, overflowRecord):
521	ok = 1
522	newSubTable.Format = oldSubTable.Format
523	if hasattr(oldSubTable, 'sortCoverageLast'):
524		newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast
525
526	oldAlts = oldSubTable.alternates.items()
527	oldAlts.sort()
528	oldLen = len(oldAlts)
529
530	if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
531		# Coverage table is written last. overflow is to or within the
532		# the coverage table. We will just cut the subtable in half.
533		newLen = int(oldLen/2)
534
535	elif overflowRecord.itemName == 'AlternateSet':
536		# We just need to back up by two items
537		# from the overflowed AlternateSet index to make sure the offset
538		# to the Coverage table doesn't overflow.
539		newLen  = overflowRecord.itemIndex - 1
540
541	newSubTable.alternates = {}
542	for i in range(newLen, oldLen):
543		item = oldAlts[i]
544		key = item[0]
545		newSubTable.alternates[key] = item[1]
546		del oldSubTable.alternates[key]
547
548
549	return ok
550
551
552def splitLigatureSubst(oldSubTable, newSubTable, overflowRecord):
553	ok = 1
554	newSubTable.Format = oldSubTable.Format
555	oldLigs = oldSubTable.ligatures.items()
556	oldLigs.sort()
557	oldLen = len(oldLigs)
558
559	if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
560		# Coverage table is written last. overflow is to or within the
561		# the coverage table. We will just cut the subtable in half.
562		newLen = int(oldLen/2)
563
564	elif overflowRecord.itemName == 'LigatureSet':
565		# We just need to back up by two items
566		# from the overflowed AlternateSet index to make sure the offset
567		# to the Coverage table doesn't overflow.
568		newLen  = overflowRecord.itemIndex - 1
569
570	newSubTable.ligatures = {}
571	for i in range(newLen, oldLen):
572		item = oldLigs[i]
573		key = item[0]
574		newSubTable.ligatures[key] = item[1]
575		del oldSubTable.ligatures[key]
576
577	return ok
578
579
580splitTable = {	'GSUB': {
581#					1: splitSingleSubst,
582#					2: splitMultipleSubst,
583					3: splitAlternateSubst,
584					4: splitLigatureSubst,
585#					5: splitContextSubst,
586#					6: splitChainContextSubst,
587#					7: splitExtensionSubst,
588#					8: splitReverseChainSingleSubst,
589					},
590				'GPOS': {
591#					1: splitSinglePos,
592#					2: splitPairPos,
593#					3: splitCursivePos,
594#					4: splitMarkBasePos,
595#					5: splitMarkLigPos,
596#					6: splitMarkMarkPos,
597#					7: splitContextPos,
598#					8: splitChainContextPos,
599#					9: splitExtensionPos,
600					}
601
602			}
603
604def fixSubTableOverFlows(ttf, overflowRecord):
605	"""
606	An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts.
607	"""
608	ok = 0
609	table = ttf[overflowRecord.tableType].table
610	lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex]
611	subIndex = overflowRecord.SubTableIndex
612	subtable = lookup.SubTable[subIndex]
613
614	if hasattr(subtable, 'ExtSubTable'):
615		# We split the subtable of the Extension table, and add a new Extension table
616		# to contain the new subtable.
617
618		subTableType = subtable.ExtensionLookupType
619		extSubTable = subtable
620		subtable = extSubTable.ExtSubTable
621		newExtSubTableClass = lookupTypes[overflowRecord.tableType][lookup.LookupType]
622		newExtSubTable = newExtSubTableClass()
623		newExtSubTable.Format = extSubTable.Format
624		newExtSubTable.ExtensionLookupType = extSubTable.ExtensionLookupType
625		lookup.SubTable.insert(subIndex + 1, newExtSubTable)
626
627		newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
628		newSubTable = newSubTableClass()
629		newExtSubTable.ExtSubTable = newSubTable
630	else:
631		subTableType = lookup.LookupType
632		newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
633		newSubTable = newSubTableClass()
634		lookup.SubTable.insert(subIndex + 1, newSubTable)
635
636	if hasattr(lookup, 'SubTableCount'): # may not be defined yet.
637		lookup.SubTableCount = lookup.SubTableCount + 1
638
639	try:
640		splitFunc = splitTable[overflowRecord.tableType][subTableType]
641	except KeyError:
642		return ok
643
644	ok = splitFunc(subtable, newSubTable, overflowRecord)
645	return ok
646
647# End of OverFlow logic
648
649
650def _buildClasses():
651	import new, re
652	from .otData import otData
653
654	formatPat = re.compile("([A-Za-z0-9]+)Format(\d+)$")
655	namespace = globals()
656
657	# populate module with classes
658	for name, table in otData:
659		baseClass = BaseTable
660		m = formatPat.match(name)
661		if m:
662			# XxxFormatN subtable, we only add the "base" table
663			name = m.group(1)
664			baseClass = FormatSwitchingBaseTable
665		if name not in namespace:
666			# the class doesn't exist yet, so the base implementation is used.
667			cls = new.classobj(name, (baseClass,), {})
668			namespace[name] = cls
669
670	for base, alts in _equivalents.items():
671		base = namespace[base]
672		for alt in alts:
673			namespace[alt] = new.classobj(alt, (base,), {})
674
675	global lookupTypes
676	lookupTypes = {
677		'GSUB': {
678			1: SingleSubst,
679			2: MultipleSubst,
680			3: AlternateSubst,
681			4: LigatureSubst,
682			5: ContextSubst,
683			6: ChainContextSubst,
684			7: ExtensionSubst,
685			8: ReverseChainSingleSubst,
686		},
687		'GPOS': {
688			1: SinglePos,
689			2: PairPos,
690			3: CursivePos,
691			4: MarkBasePos,
692			5: MarkLigPos,
693			6: MarkMarkPos,
694			7: ContextPos,
695			8: ChainContextPos,
696			9: ExtensionPos,
697		},
698	}
699	lookupTypes['JSTF'] = lookupTypes['GPOS']  # JSTF contains GPOS
700	for lookupEnum in lookupTypes.values():
701		for enum, cls in lookupEnum.items():
702			cls.LookupType = enum
703
704	global featureParamTypes
705	featureParamTypes = {
706		'size': FeatureParamsSize,
707	}
708	for i in range(1, 20+1):
709		featureParamTypes['ss%02d' % i] = FeatureParamsStylisticSet
710	for i in range(1, 99+1):
711		featureParamTypes['cv%02d' % i] = FeatureParamsCharacterVariants
712
713	# add converters to classes
714	from .otConverters import buildConverters
715	for name, table in otData:
716		m = formatPat.match(name)
717		if m:
718			# XxxFormatN subtable, add converter to "base" table
719			name, format = m.groups()
720			format = int(format)
721			cls = namespace[name]
722			if not hasattr(cls, "converters"):
723				cls.converters = {}
724				cls.convertersByName = {}
725			converters, convertersByName = buildConverters(table[1:], namespace)
726			cls.converters[format] = converters
727			cls.convertersByName[format] = convertersByName
728		else:
729			cls = namespace[name]
730			cls.converters, cls.convertersByName = buildConverters(table, namespace)
731
732
733_buildClasses()
734
735
736def _getGlyphsFromCoverageTable(coverage):
737	if coverage is None:
738		# empty coverage table
739		return []
740	else:
741		return coverage.glyphs
742