otTables.py revision 3a9fd301808f5a8991ca9ac44028d1ecb22d307f
1d4d151390d1288f8d2df30f6dfa26a309c7334dajvr"""fontTools.ttLib.tables.otTables -- A collection of classes representing the various
2d4d151390d1288f8d2df30f6dfa26a309c7334dajvrOpenType subtables.
3d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
464b5c80e80444a124da335e8d4d208bffcf2737bjvrMost are constructed upon import from data in otData.py, all are populated with
564b5c80e80444a124da335e8d4d208bffcf2737bjvrconverter objects from otConverters.py.
6d4d151390d1288f8d2df30f6dfa26a309c7334dajvr"""
7823f8cd15f16bb9dc3991c2672f16dd90579711bjvrimport operator
82b06aaa2a6bcd363c25fb0c43f6bb906906594bdBehdad Esfahbodfrom .otBase import BaseTable, FormatSwitchingBaseTable
9b2486125e9438443a1419706c50958ab0676eb5ajvrfrom types import TupleType
106e7066765929929d8315a719aeff1c5e5a5d9e9aBehdad Esfahbodimport warnings
11d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
12d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
13d4d151390d1288f8d2df30f6dfa26a309c7334dajvrclass LookupOrder(BaseTable):
14d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	"""Dummy class; this table isn't defined, but is used, and is always NULL."""
15d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
16d4d151390d1288f8d2df30f6dfa26a309c7334dajvrclass FeatureParams(BaseTable):
179e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod
189e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod	def compile(self, writer, font):
199e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod		assert featureParamTypes.get(writer['FeatureTag'], None) == self.__class__, "Wrong FeatureParams type for feature '%s': %s" % (writer['FeatureTag'], self.__class__.__name__)
209e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod		BaseTable.compile(self, writer, font)
219e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod
229e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbodclass FeatureParamsSize(FeatureParams):
239e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod	pass
249e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod
259e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbodclass FeatureParamsStylisticSet(FeatureParams):
269e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod	pass
279e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod
289e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbodclass FeatureParamsCharacterVariants(FeatureParams):
299e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod	pass
30d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
31d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvrclass Coverage(FormatSwitchingBaseTable):
32d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
33d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr	# manual implementation to get rid of glyphID dependencies
34d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
351a53beb7b4bb252dc002dcdc2de70517d4727494jvr	def postRead(self, rawTable, font):
36d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		if self.Format == 1:
371a53beb7b4bb252dc002dcdc2de70517d4727494jvr			self.glyphs = rawTable["GlyphArray"]
38d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		elif self.Format == 2:
39d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			glyphs = self.glyphs = []
401a53beb7b4bb252dc002dcdc2de70517d4727494jvr			ranges = rawTable["RangeRecord"]
41183afe6408fade5d45dd45d062293c416a4adef1Behdad Esfahbod			glyphOrder = font.getGlyphOrder()
4217700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod			# Some SIL fonts have coverage entries that don't have sorted
4317700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod			# StartCoverageIndex.  If it is so, fixup and warn.  We undo
4417700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod			# this when writing font out.
4517700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod			sorted_ranges = sorted(ranges, cmp=lambda a,b: cmp(a.StartCoverageIndex,b.StartCoverageIndex))
4617700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod			if ranges != sorted_ranges:
4717700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod				warnings.warn("GSUB/GPOS Coverage is not sorted by glyph ids.")
4817700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod				ranges = sorted_ranges
4917700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod			del sorted_ranges
50d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			for r in ranges:
51d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				assert r.StartCoverageIndex == len(glyphs), \
52d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					(r.StartCoverageIndex, len(glyphs))
53d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				start = r.Start
54d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				end = r.End
556e7066765929929d8315a719aeff1c5e5a5d9e9aBehdad Esfahbod				try:
566e7066765929929d8315a719aeff1c5e5a5d9e9aBehdad Esfahbod					startID = font.getGlyphID(start, requireReal=1)
576e7066765929929d8315a719aeff1c5e5a5d9e9aBehdad Esfahbod				except KeyError:
586e7066765929929d8315a719aeff1c5e5a5d9e9aBehdad Esfahbod					warnings.warn("Coverage table has start glyph ID out of range: %s." % start)
596e7066765929929d8315a719aeff1c5e5a5d9e9aBehdad Esfahbod					continue
606e7066765929929d8315a719aeff1c5e5a5d9e9aBehdad Esfahbod				try:
616e7066765929929d8315a719aeff1c5e5a5d9e9aBehdad Esfahbod					endID = font.getGlyphID(end, requireReal=1)
626e7066765929929d8315a719aeff1c5e5a5d9e9aBehdad Esfahbod				except KeyError:
636e7066765929929d8315a719aeff1c5e5a5d9e9aBehdad Esfahbod					warnings.warn("Coverage table has end glyph ID out of range: %s." % end)
646e7066765929929d8315a719aeff1c5e5a5d9e9aBehdad Esfahbod					endID = len(glyphOrder)
65d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				glyphs.append(start)
66183afe6408fade5d45dd45d062293c416a4adef1Behdad Esfahbod				rangeList = [glyphOrder[glyphID] for glyphID in range(startID + 1, endID) ]
67823f8cd15f16bb9dc3991c2672f16dd90579711bjvr				glyphs += rangeList
686e7066765929929d8315a719aeff1c5e5a5d9e9aBehdad Esfahbod				if start != end and endID < len(glyphOrder):
69d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					glyphs.append(end)
70d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		else:
711a53beb7b4bb252dc002dcdc2de70517d4727494jvr			assert 0, "unknown format: %s" % self.Format
72d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
73d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr	def preWrite(self, font):
7431ae380735dbf75a460df002a7fc2bcd81e28153jvr		glyphs = getattr(self, "glyphs", None)
75257fba71729b4552a73f296b8fbd5b6224cdcabbjvr		if glyphs is None:
76257fba71729b4552a73f296b8fbd5b6224cdcabbjvr			glyphs = self.glyphs = []
77d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		format = 1
78a138467da399c071dbd78682938642b8e8d8a4cejvr		rawTable = {"GlyphArray": glyphs}
79823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		getGlyphID = font.getGlyphID
80a138467da399c071dbd78682938642b8e8d8a4cejvr		if glyphs:
811a53beb7b4bb252dc002dcdc2de70517d4727494jvr			# find out whether Format 2 is more compact or not
82823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			glyphIDs = [getGlyphID(glyphName) for glyphName in glyphs ]
8317700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod			brokenOrder = sorted(glyphIDs) != glyphIDs
84d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
85d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			last = glyphIDs[0]
86d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			ranges = [[last]]
87d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			for glyphID in glyphIDs[1:]:
88a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				if glyphID != last + 1:
89d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					ranges[-1].append(last)
90d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					ranges.append([glyphID])
91d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				last = glyphID
92d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			ranges[-1].append(last)
93d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
9417700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod			if brokenOrder or len(ranges) * 3 < len(glyphs):  # 3 words vs. 1 word
95d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				# Format 2 is more compact
96d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				index = 0
97d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				for i in range(len(ranges)):
98d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					start, end = ranges[i]
99d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					r = RangeRecord()
10017700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod					r.StartID = start
101d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					r.Start = font.getGlyphName(start)
102d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					r.End = font.getGlyphName(end)
103d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					r.StartCoverageIndex = index
104d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					ranges[i] = r
105d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr					index = index + end - start + 1
10617700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod				if brokenOrder:
10717700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod					warnings.warn("GSUB/GPOS Coverage is not sorted by glyph ids.")
10817700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod					ranges.sort(cmp=lambda a,b: cmp(a.StartID,b.StartID))
10917700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod				for r in ranges:
11017700bf926645d7def1d4013b2e119d2be4ff904Behdad Esfahbod					del r.StartID
111d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr				format = 2
1121a53beb7b4bb252dc002dcdc2de70517d4727494jvr				rawTable = {"RangeRecord": ranges}
113d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			#else:
114d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			#	fallthrough; Format 1 is more compact
115d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		self.Format = format
1161a53beb7b4bb252dc002dcdc2de70517d4727494jvr		return rawTable
117d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
118d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr	def toXML2(self, xmlWriter, font):
119a138467da399c071dbd78682938642b8e8d8a4cejvr		for glyphName in getattr(self, "glyphs", []):
120d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			xmlWriter.simpletag("Glyph", value=glyphName)
121d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			xmlWriter.newline()
122d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr
1233a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod	def fromXML(self, name, attrs, content, font):
124d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		glyphs = getattr(self, "glyphs", None)
125d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		if glyphs is None:
126d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			glyphs = []
127d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr			self.glyphs = glyphs
128d41386e7f7b950d2429fbf6d1964ba129d2bc2a6jvr		glyphs.append(attrs["value"])
129d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
130d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
131823f8cd15f16bb9dc3991c2672f16dd90579711bjvrdef doModulo(value):
132823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	if value < 0:
133823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		return value + 65536
134823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	return value
135823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
1361d6360af4c553a7b26447b0e1e96f7b701c9325ejvrclass SingleSubst(FormatSwitchingBaseTable):
1371d6360af4c553a7b26447b0e1e96f7b701c9325ejvr
1381d6360af4c553a7b26447b0e1e96f7b701c9325ejvr	def postRead(self, rawTable, font):
1391d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		mapping = {}
1401fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr		input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
141823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		lenMapping = len(input)
1421d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		if self.Format == 1:
1431d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			delta = rawTable["DeltaGlyphID"]
144823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			inputGIDS =  [ font.getGlyphID(name) for name in input ]
145823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			inputGIDS = map(doModulo, inputGIDS)
146823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			outGIDS = [ glyphID + delta for glyphID in inputGIDS ]
147823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			outGIDS = map(doModulo, outGIDS)
148823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			outNames = [ font.getGlyphName(glyphID) for glyphID in outGIDS ]
149823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			map(operator.setitem, [mapping]*lenMapping, input, outNames)
1501d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		elif self.Format == 2:
1511d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			assert len(input) == rawTable["GlyphCount"], \
1521d6360af4c553a7b26447b0e1e96f7b701c9325ejvr					"invalid SingleSubstFormat2 table"
1531d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			subst = rawTable["Substitute"]
154823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			map(operator.setitem, [mapping]*lenMapping, input, subst)
1551d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		else:
1561d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			assert 0, "unknown format: %s" % self.Format
1571d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		self.mapping = mapping
1581d6360af4c553a7b26447b0e1e96f7b701c9325ejvr
1591d6360af4c553a7b26447b0e1e96f7b701c9325ejvr	def preWrite(self, font):
16031ae380735dbf75a460df002a7fc2bcd81e28153jvr		mapping = getattr(self, "mapping", None)
16131ae380735dbf75a460df002a7fc2bcd81e28153jvr		if mapping is None:
16231ae380735dbf75a460df002a7fc2bcd81e28153jvr			mapping = self.mapping = {}
16331ae380735dbf75a460df002a7fc2bcd81e28153jvr		items = mapping.items()
164823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		getGlyphID = font.getGlyphID
165823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		gidItems = [(getGlyphID(item[0]), getGlyphID(item[1])) for item in items]
166823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		sortableItems = zip(gidItems, items)
167823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		sortableItems.sort()
168823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
169823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# figure out format
1701d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		format = 2
1711d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		delta = None
172823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		for inID, outID in gidItems:
1731d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			if delta is None:
1741d6360af4c553a7b26447b0e1e96f7b701c9325ejvr				delta = outID - inID
1751d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			else:
1761d6360af4c553a7b26447b0e1e96f7b701c9325ejvr				if delta != outID - inID:
1771d6360af4c553a7b26447b0e1e96f7b701c9325ejvr					break
1781d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		else:
1791d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			format = 1
180823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
1811d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		rawTable = {}
1821d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		self.Format = format
1831d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		cov = Coverage()
184823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		input =  [ item [1][0] for item in sortableItems]
185823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		subst =  [ item [1][1] for item in sortableItems]
186823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		cov.glyphs = input
1871d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		rawTable["Coverage"] = cov
1881d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		if format == 1:
1891d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			assert delta is not None
1901d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			rawTable["DeltaGlyphID"] = delta
1911d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		else:
1921d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			rawTable["Substitute"] = subst
1931d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		return rawTable
1941d6360af4c553a7b26447b0e1e96f7b701c9325ejvr
1951d6360af4c553a7b26447b0e1e96f7b701c9325ejvr	def toXML2(self, xmlWriter, font):
1961d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		items = self.mapping.items()
1971d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		items.sort()
1981d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		for inGlyph, outGlyph in items:
1991d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			xmlWriter.simpletag("Substitution",
2001d6360af4c553a7b26447b0e1e96f7b701c9325ejvr					[("in", inGlyph), ("out", outGlyph)])
2011d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			xmlWriter.newline()
2021d6360af4c553a7b26447b0e1e96f7b701c9325ejvr
2033a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod	def fromXML(self, name, attrs, content, font):
2041d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		mapping = getattr(self, "mapping", None)
2051d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		if mapping is None:
2061d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			mapping = {}
2071d6360af4c553a7b26447b0e1e96f7b701c9325ejvr			self.mapping = mapping
2081d6360af4c553a7b26447b0e1e96f7b701c9325ejvr		mapping[attrs["in"]] = attrs["out"]
2091d6360af4c553a7b26447b0e1e96f7b701c9325ejvr
2101d6360af4c553a7b26447b0e1e96f7b701c9325ejvr
211a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvrclass ClassDef(FormatSwitchingBaseTable):
212a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr
213a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr	def postRead(self, rawTable, font):
214a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		classDefs = {}
215823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		getGlyphName = font.getGlyphName
216823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
217a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		if self.Format == 1:
218a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			start = rawTable["StartGlyph"]
219823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			classList = rawTable["ClassValueArray"]
220823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			lenList = len(classList)
221a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			glyphID = font.getGlyphID(start)
222823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			gidList = range(glyphID, glyphID + len(classList))
223823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			keyList = [getGlyphName(glyphID) for glyphID in gidList]
224823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
225823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			map(operator.setitem, [classDefs]*lenList, keyList, classList)
226823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
227a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		elif self.Format == 2:
228a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			records = rawTable["ClassRangeRecord"]
229a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			for rec in records:
230a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				start = rec.Start
231a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				end = rec.End
232a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				cls = rec.Class
233a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				classDefs[start] = cls
234823f8cd15f16bb9dc3991c2672f16dd90579711bjvr				glyphIDs = range(font.getGlyphID(start) + 1, font.getGlyphID(end))
235823f8cd15f16bb9dc3991c2672f16dd90579711bjvr				lenList = len(glyphIDs)
236823f8cd15f16bb9dc3991c2672f16dd90579711bjvr				keyList = [getGlyphName(glyphID) for glyphID in glyphIDs]
237823f8cd15f16bb9dc3991c2672f16dd90579711bjvr				map(operator.setitem,  [classDefs]*lenList, keyList, [cls]*lenList)
238a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr				classDefs[end] = cls
239a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		else:
240a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			assert 0, "unknown format: %s" % self.Format
241a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		self.classDefs = classDefs
242a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr
243a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr	def preWrite(self, font):
24431ae380735dbf75a460df002a7fc2bcd81e28153jvr		classDefs = getattr(self, "classDefs", None)
24531ae380735dbf75a460df002a7fc2bcd81e28153jvr		if classDefs is None:
24631ae380735dbf75a460df002a7fc2bcd81e28153jvr			classDefs = self.classDefs = {}
24731ae380735dbf75a460df002a7fc2bcd81e28153jvr		items = classDefs.items()
248823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		getGlyphID = font.getGlyphID
249a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		for i in range(len(items)):
250a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			glyphName, cls = items[i]
251823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			items[i] = getGlyphID(glyphName), glyphName, cls
252a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		items.sort()
25331ae380735dbf75a460df002a7fc2bcd81e28153jvr		if items:
25431ae380735dbf75a460df002a7fc2bcd81e28153jvr			last, lastName, lastCls = items[0]
25531ae380735dbf75a460df002a7fc2bcd81e28153jvr			rec = ClassRangeRecord()
25631ae380735dbf75a460df002a7fc2bcd81e28153jvr			rec.Start = lastName
25731ae380735dbf75a460df002a7fc2bcd81e28153jvr			rec.Class = lastCls
25831ae380735dbf75a460df002a7fc2bcd81e28153jvr			ranges = [rec]
25931ae380735dbf75a460df002a7fc2bcd81e28153jvr			for glyphID, glyphName, cls in items[1:]:
26031ae380735dbf75a460df002a7fc2bcd81e28153jvr				if glyphID != last + 1 or cls != lastCls:
26131ae380735dbf75a460df002a7fc2bcd81e28153jvr					rec.End = lastName
26231ae380735dbf75a460df002a7fc2bcd81e28153jvr					rec = ClassRangeRecord()
26331ae380735dbf75a460df002a7fc2bcd81e28153jvr					rec.Start = glyphName
26431ae380735dbf75a460df002a7fc2bcd81e28153jvr					rec.Class = cls
26531ae380735dbf75a460df002a7fc2bcd81e28153jvr					ranges.append(rec)
26631ae380735dbf75a460df002a7fc2bcd81e28153jvr				last = glyphID
26731ae380735dbf75a460df002a7fc2bcd81e28153jvr				lastName = glyphName
26831ae380735dbf75a460df002a7fc2bcd81e28153jvr				lastCls = cls
26931ae380735dbf75a460df002a7fc2bcd81e28153jvr			rec.End = lastName
27031ae380735dbf75a460df002a7fc2bcd81e28153jvr		else:
27131ae380735dbf75a460df002a7fc2bcd81e28153jvr			ranges = []
272a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		self.Format = 2  # currently no support for Format 1
273a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		return {"ClassRangeRecord": ranges}
274a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr
275a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr	def toXML2(self, xmlWriter, font):
276a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		items = self.classDefs.items()
277a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		items.sort()
278a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		for glyphName, cls in items:
279a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)])
280a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			xmlWriter.newline()
281a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr
2823a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod	def fromXML(self, name, attrs, content, font):
283a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		classDefs = getattr(self, "classDefs", None)
284a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		if classDefs is None:
285a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			classDefs = {}
286a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr			self.classDefs = classDefs
287a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr		classDefs[attrs["glyph"]] = int(attrs["class"])
288a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr
289a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr
290b2486125e9438443a1419706c50958ab0676eb5ajvrclass AlternateSubst(FormatSwitchingBaseTable):
291b2486125e9438443a1419706c50958ab0676eb5ajvr
292b2486125e9438443a1419706c50958ab0676eb5ajvr	def postRead(self, rawTable, font):
293b2486125e9438443a1419706c50958ab0676eb5ajvr		alternates = {}
294b2486125e9438443a1419706c50958ab0676eb5ajvr		if self.Format == 1:
2951fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr			input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
296b2486125e9438443a1419706c50958ab0676eb5ajvr			alts = rawTable["AlternateSet"]
297823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			if len(input) != len(alts):
298823f8cd15f16bb9dc3991c2672f16dd90579711bjvr				assert len(input) == len(alts)
299b2486125e9438443a1419706c50958ab0676eb5ajvr			for i in range(len(input)):
300b2486125e9438443a1419706c50958ab0676eb5ajvr				alternates[input[i]] = alts[i].Alternate
301b2486125e9438443a1419706c50958ab0676eb5ajvr		else:
302b2486125e9438443a1419706c50958ab0676eb5ajvr			assert 0, "unknown format: %s" % self.Format
303b2486125e9438443a1419706c50958ab0676eb5ajvr		self.alternates = alternates
304b2486125e9438443a1419706c50958ab0676eb5ajvr
305b2486125e9438443a1419706c50958ab0676eb5ajvr	def preWrite(self, font):
306b2486125e9438443a1419706c50958ab0676eb5ajvr		self.Format = 1
30731ae380735dbf75a460df002a7fc2bcd81e28153jvr		alternates = getattr(self, "alternates", None)
30831ae380735dbf75a460df002a7fc2bcd81e28153jvr		if alternates is None:
30931ae380735dbf75a460df002a7fc2bcd81e28153jvr			alternates = self.alternates = {}
31031ae380735dbf75a460df002a7fc2bcd81e28153jvr		items = alternates.items()
311b2486125e9438443a1419706c50958ab0676eb5ajvr		for i in range(len(items)):
312b2486125e9438443a1419706c50958ab0676eb5ajvr			glyphName, set = items[i]
313b2486125e9438443a1419706c50958ab0676eb5ajvr			items[i] = font.getGlyphID(glyphName), glyphName, set
314b2486125e9438443a1419706c50958ab0676eb5ajvr		items.sort()
315b2486125e9438443a1419706c50958ab0676eb5ajvr		cov = Coverage()
316823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		cov.glyphs = [ item[1] for item in items]
317b2486125e9438443a1419706c50958ab0676eb5ajvr		alternates = []
318823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		setList = [ item[-1] for item in items]
319823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		for  set in setList:
320b2486125e9438443a1419706c50958ab0676eb5ajvr			alts = AlternateSet()
321b2486125e9438443a1419706c50958ab0676eb5ajvr			alts.Alternate = set
322b2486125e9438443a1419706c50958ab0676eb5ajvr			alternates.append(alts)
323823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# a special case to deal with the fact that several hundred Adobe Japan1-5
324823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# CJK fonts will overflow an offset if the coverage table isn't pushed to the end.
325823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# Also useful in that when splitting a sub-table because of an offset overflow
326823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# I don't need to calculate the change in the subtable offset due to the change in the coverage table size.
327823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# Allows packing more rules in subtable.
328823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		self.sortCoverageLast = 1
329b2486125e9438443a1419706c50958ab0676eb5ajvr		return {"Coverage": cov, "AlternateSet": alternates}
330b2486125e9438443a1419706c50958ab0676eb5ajvr
331b2486125e9438443a1419706c50958ab0676eb5ajvr	def toXML2(self, xmlWriter, font):
332b2486125e9438443a1419706c50958ab0676eb5ajvr		items = self.alternates.items()
333b2486125e9438443a1419706c50958ab0676eb5ajvr		items.sort()
334b2486125e9438443a1419706c50958ab0676eb5ajvr		for glyphName, alternates in items:
335b2486125e9438443a1419706c50958ab0676eb5ajvr			xmlWriter.begintag("AlternateSet", glyph=glyphName)
336b2486125e9438443a1419706c50958ab0676eb5ajvr			xmlWriter.newline()
337b2486125e9438443a1419706c50958ab0676eb5ajvr			for alt in alternates:
338b2486125e9438443a1419706c50958ab0676eb5ajvr				xmlWriter.simpletag("Alternate", glyph=alt)
339b2486125e9438443a1419706c50958ab0676eb5ajvr				xmlWriter.newline()
340b2486125e9438443a1419706c50958ab0676eb5ajvr			xmlWriter.endtag("AlternateSet")
341b2486125e9438443a1419706c50958ab0676eb5ajvr			xmlWriter.newline()
342b2486125e9438443a1419706c50958ab0676eb5ajvr
3433a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod	def fromXML(self, name, attrs, content, font):
344b2486125e9438443a1419706c50958ab0676eb5ajvr		alternates = getattr(self, "alternates", None)
345b2486125e9438443a1419706c50958ab0676eb5ajvr		if alternates is None:
346b2486125e9438443a1419706c50958ab0676eb5ajvr			alternates = {}
347b2486125e9438443a1419706c50958ab0676eb5ajvr			self.alternates = alternates
348b2486125e9438443a1419706c50958ab0676eb5ajvr		glyphName = attrs["glyph"]
349b2486125e9438443a1419706c50958ab0676eb5ajvr		set = []
350b2486125e9438443a1419706c50958ab0676eb5ajvr		alternates[glyphName] = set
351b2486125e9438443a1419706c50958ab0676eb5ajvr		for element in content:
352b2486125e9438443a1419706c50958ab0676eb5ajvr			if type(element) != TupleType:
353b2486125e9438443a1419706c50958ab0676eb5ajvr				continue
354b2486125e9438443a1419706c50958ab0676eb5ajvr			name, attrs, content = element
355b2486125e9438443a1419706c50958ab0676eb5ajvr			set.append(attrs["glyph"])
356b2486125e9438443a1419706c50958ab0676eb5ajvr
357b2486125e9438443a1419706c50958ab0676eb5ajvr
358f2164abef39af7cfef989352fadfca628ed077b2jvrclass LigatureSubst(FormatSwitchingBaseTable):
359f2164abef39af7cfef989352fadfca628ed077b2jvr
360f2164abef39af7cfef989352fadfca628ed077b2jvr	def postRead(self, rawTable, font):
361f2164abef39af7cfef989352fadfca628ed077b2jvr		ligatures = {}
362f2164abef39af7cfef989352fadfca628ed077b2jvr		if self.Format == 1:
363823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			input = rawTable["Coverage"].glyphs
364f2164abef39af7cfef989352fadfca628ed077b2jvr			ligSets = rawTable["LigatureSet"]
365f2164abef39af7cfef989352fadfca628ed077b2jvr			assert len(input) == len(ligSets)
366f2164abef39af7cfef989352fadfca628ed077b2jvr			for i in range(len(input)):
367f2164abef39af7cfef989352fadfca628ed077b2jvr				ligatures[input[i]] = ligSets[i].Ligature
368f2164abef39af7cfef989352fadfca628ed077b2jvr		else:
369f2164abef39af7cfef989352fadfca628ed077b2jvr			assert 0, "unknown format: %s" % self.Format
370f2164abef39af7cfef989352fadfca628ed077b2jvr		self.ligatures = ligatures
371f2164abef39af7cfef989352fadfca628ed077b2jvr
372f2164abef39af7cfef989352fadfca628ed077b2jvr	def preWrite(self, font):
37331ae380735dbf75a460df002a7fc2bcd81e28153jvr		ligatures = getattr(self, "ligatures", None)
37431ae380735dbf75a460df002a7fc2bcd81e28153jvr		if ligatures is None:
37531ae380735dbf75a460df002a7fc2bcd81e28153jvr			ligatures = self.ligatures = {}
37631ae380735dbf75a460df002a7fc2bcd81e28153jvr		items = ligatures.items()
377f2164abef39af7cfef989352fadfca628ed077b2jvr		for i in range(len(items)):
378f2164abef39af7cfef989352fadfca628ed077b2jvr			glyphName, set = items[i]
379f2164abef39af7cfef989352fadfca628ed077b2jvr			items[i] = font.getGlyphID(glyphName), glyphName, set
380f2164abef39af7cfef989352fadfca628ed077b2jvr		items.sort()
381f2164abef39af7cfef989352fadfca628ed077b2jvr		cov = Coverage()
382823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		cov.glyphs = [ item[1] for item in items]
383823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
384f2164abef39af7cfef989352fadfca628ed077b2jvr		ligSets = []
385823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		setList = [ item[-1] for item in items ]
386823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		for set in setList:
387f2164abef39af7cfef989352fadfca628ed077b2jvr			ligSet = LigatureSet()
388f2164abef39af7cfef989352fadfca628ed077b2jvr			ligs = ligSet.Ligature = []
389f2164abef39af7cfef989352fadfca628ed077b2jvr			for lig in set:
390f2164abef39af7cfef989352fadfca628ed077b2jvr				ligs.append(lig)
391f2164abef39af7cfef989352fadfca628ed077b2jvr			ligSets.append(ligSet)
392823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# Useful in that when splitting a sub-table because of an offset overflow
393823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# I don't need to calculate the change in subtabl offset due to the coverage table size.
394823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# Allows packing more rules in subtable.
395823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		self.sortCoverageLast = 1
396f2164abef39af7cfef989352fadfca628ed077b2jvr		return {"Coverage": cov, "LigatureSet": ligSets}
397f2164abef39af7cfef989352fadfca628ed077b2jvr
398f2164abef39af7cfef989352fadfca628ed077b2jvr	def toXML2(self, xmlWriter, font):
399f2164abef39af7cfef989352fadfca628ed077b2jvr		items = self.ligatures.items()
400f2164abef39af7cfef989352fadfca628ed077b2jvr		items.sort()
401f2164abef39af7cfef989352fadfca628ed077b2jvr		for glyphName, ligSets in items:
402f2164abef39af7cfef989352fadfca628ed077b2jvr			xmlWriter.begintag("LigatureSet", glyph=glyphName)
403f2164abef39af7cfef989352fadfca628ed077b2jvr			xmlWriter.newline()
404f2164abef39af7cfef989352fadfca628ed077b2jvr			for lig in ligSets:
405f2164abef39af7cfef989352fadfca628ed077b2jvr				xmlWriter.simpletag("Ligature", glyph=lig.LigGlyph,
406f2164abef39af7cfef989352fadfca628ed077b2jvr					components=",".join(lig.Component))
407f2164abef39af7cfef989352fadfca628ed077b2jvr				xmlWriter.newline()
408f2164abef39af7cfef989352fadfca628ed077b2jvr			xmlWriter.endtag("LigatureSet")
409f2164abef39af7cfef989352fadfca628ed077b2jvr			xmlWriter.newline()
410f2164abef39af7cfef989352fadfca628ed077b2jvr
4113a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod	def fromXML(self, name, attrs, content, font):
412f2164abef39af7cfef989352fadfca628ed077b2jvr		ligatures = getattr(self, "ligatures", None)
413f2164abef39af7cfef989352fadfca628ed077b2jvr		if ligatures is None:
414f2164abef39af7cfef989352fadfca628ed077b2jvr			ligatures = {}
415f2164abef39af7cfef989352fadfca628ed077b2jvr			self.ligatures = ligatures
416f2164abef39af7cfef989352fadfca628ed077b2jvr		glyphName = attrs["glyph"]
417f2164abef39af7cfef989352fadfca628ed077b2jvr		ligs = []
418f2164abef39af7cfef989352fadfca628ed077b2jvr		ligatures[glyphName] = ligs
419f2164abef39af7cfef989352fadfca628ed077b2jvr		for element in content:
420f2164abef39af7cfef989352fadfca628ed077b2jvr			if type(element) != TupleType:
421f2164abef39af7cfef989352fadfca628ed077b2jvr				continue
422f2164abef39af7cfef989352fadfca628ed077b2jvr			name, attrs, content = element
423f2164abef39af7cfef989352fadfca628ed077b2jvr			lig = Ligature()
424f2164abef39af7cfef989352fadfca628ed077b2jvr			lig.LigGlyph = attrs["glyph"]
425f2164abef39af7cfef989352fadfca628ed077b2jvr			lig.Component = attrs["components"].split(",")
426f2164abef39af7cfef989352fadfca628ed077b2jvr			ligs.append(lig)
427f2164abef39af7cfef989352fadfca628ed077b2jvr
428f2164abef39af7cfef989352fadfca628ed077b2jvr
42964b5c80e80444a124da335e8d4d208bffcf2737bjvr#
43064b5c80e80444a124da335e8d4d208bffcf2737bjvr# For each subtable format there is a class. However, we don't really distinguish
43164b5c80e80444a124da335e8d4d208bffcf2737bjvr# between "field name" and "format name": often these are the same. Yet there's
43264b5c80e80444a124da335e8d4d208bffcf2737bjvr# a whole bunch of fields with different names. The following dict is a mapping
43364b5c80e80444a124da335e8d4d208bffcf2737bjvr# from "format name" to "field name". _buildClasses() uses this to create a
43464b5c80e80444a124da335e8d4d208bffcf2737bjvr# subclass for each alternate field name.
43564b5c80e80444a124da335e8d4d208bffcf2737bjvr#
43664b5c80e80444a124da335e8d4d208bffcf2737bjvr_equivalents = {
43764b5c80e80444a124da335e8d4d208bffcf2737bjvr	'MarkArray': ("Mark1Array",),
43864b5c80e80444a124da335e8d4d208bffcf2737bjvr	'LangSys': ('DefaultLangSys',),
43964b5c80e80444a124da335e8d4d208bffcf2737bjvr	'Coverage': ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage',
440d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage',
44127f108844ef1aeeb5c694a8304bf6c4944783d42jvr			'LookAheadCoverage'),
44264b5c80e80444a124da335e8d4d208bffcf2737bjvr	'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef',
44327f108844ef1aeeb5c694a8304bf6c4944783d42jvr			'LookAheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'),
44464b5c80e80444a124da335e8d4d208bffcf2737bjvr	'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor',
44564b5c80e80444a124da335e8d4d208bffcf2737bjvr			'Mark2Anchor', 'MarkAnchor'),
44664b5c80e80444a124da335e8d4d208bffcf2737bjvr	'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice',
44764b5c80e80444a124da335e8d4d208bffcf2737bjvr			'XDeviceTable', 'YDeviceTable', 'DeviceTable'),
44864b5c80e80444a124da335e8d4d208bffcf2737bjvr	'Axis': ('HorizAxis', 'VertAxis',),
44964b5c80e80444a124da335e8d4d208bffcf2737bjvr	'MinMax': ('DefaultMinMax',),
45064b5c80e80444a124da335e8d4d208bffcf2737bjvr	'BaseCoord': ('MinCoord', 'MaxCoord',),
45164b5c80e80444a124da335e8d4d208bffcf2737bjvr	'JstfLangSys': ('DefJstfLangSys',),
45264b5c80e80444a124da335e8d4d208bffcf2737bjvr	'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB',
45364b5c80e80444a124da335e8d4d208bffcf2737bjvr			'ExtensionDisableGSUB',),
45464b5c80e80444a124da335e8d4d208bffcf2737bjvr	'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS',
45564b5c80e80444a124da335e8d4d208bffcf2737bjvr			'ExtensionDisableGPOS',),
45664b5c80e80444a124da335e8d4d208bffcf2737bjvr	'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',),
45764b5c80e80444a124da335e8d4d208bffcf2737bjvr}
458d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
459e5b245fd074a2efa5fc8902cb7a11a5dcad0f3f5jvr#
460e5b245fd074a2efa5fc8902cb7a11a5dcad0f3f5jvr# OverFlow logic, to automatically create ExtensionLookups
461e5b245fd074a2efa5fc8902cb7a11a5dcad0f3f5jvr# XXX This should probably move to otBase.py
462e5b245fd074a2efa5fc8902cb7a11a5dcad0f3f5jvr#
463d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
464823f8cd15f16bb9dc3991c2672f16dd90579711bjvrdef fixLookupOverFlows(ttf, overflowRecord):
465823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	""" Either the offset from the LookupList to a lookup overflowed, or
466823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	an offset from a lookup to a subtable overflowed.
467823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	The table layout is:
468823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	GPSO/GUSB
469823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		Script List
470823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		Feature List
471823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		LookUpList
472823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			Lookup[0] and contents
473823f8cd15f16bb9dc3991c2672f16dd90579711bjvr				SubTable offset list
474823f8cd15f16bb9dc3991c2672f16dd90579711bjvr					SubTable[0] and contents
475823f8cd15f16bb9dc3991c2672f16dd90579711bjvr					...
476823f8cd15f16bb9dc3991c2672f16dd90579711bjvr					SubTable[n] and contents
477823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			...
478823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			Lookup[n] and contents
479823f8cd15f16bb9dc3991c2672f16dd90579711bjvr				SubTable offset list
480823f8cd15f16bb9dc3991c2672f16dd90579711bjvr					SubTable[0] and contents
481823f8cd15f16bb9dc3991c2672f16dd90579711bjvr					...
482823f8cd15f16bb9dc3991c2672f16dd90579711bjvr					SubTable[n] and contents
483823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	If the offset to a lookup overflowed (SubTableIndex == None)
484823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		we must promote the *previous*	lookup to an Extension type.
485823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	If the offset from a lookup to subtable overflowed, then we must promote it
486823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		to an Extension Lookup type.
487823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	"""
488823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	ok = 0
489823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	lookupIndex = overflowRecord.LookupListIndex
490823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	if (overflowRecord.SubTableIndex == None):
491823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		lookupIndex = lookupIndex - 1
492823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	if lookupIndex < 0:
493823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		return ok
494823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	if overflowRecord.tableType == 'GSUB':
495823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		extType = 7
496823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	elif overflowRecord.tableType == 'GPOS':
497823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		extType = 9
498823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
499823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup
500823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	lookup = lookups[lookupIndex]
501823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	# If the previous lookup is an extType, look further back. Very unlikely, but possible.
502823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	while lookup.LookupType == extType:
503823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		lookupIndex = lookupIndex -1
504823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		if lookupIndex < 0:
505823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			return ok
506823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		lookup = lookups[lookupIndex]
507823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
508823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	for si in range(len(lookup.SubTable)):
509823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		subTable = lookup.SubTable[si]
510823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		extSubTableClass = lookupTypes[overflowRecord.tableType][extType]
511823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		extSubTable = extSubTableClass()
512823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		extSubTable.Format = 1
513823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		extSubTable.ExtensionLookupType = lookup.LookupType
514823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		extSubTable.ExtSubTable = subTable
515823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		lookup.SubTable[si] = extSubTable
516823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	lookup.LookupType = extType
517823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	ok = 1
518823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	return ok
519823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
520823f8cd15f16bb9dc3991c2672f16dd90579711bjvrdef splitAlternateSubst(oldSubTable, newSubTable, overflowRecord):
521823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	ok = 1
522823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	newSubTable.Format = oldSubTable.Format
523823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	if hasattr(oldSubTable, 'sortCoverageLast'):
524823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast
525823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
526823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	oldAlts = oldSubTable.alternates.items()
527823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	oldAlts.sort()
528823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	oldLen = len(oldAlts)
529823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
530823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
531823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# Coverage table is written last. overflow is to or within the
532823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# the coverage table. We will just cut the subtable in half.
533823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newLen = int(oldLen/2)
534823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
535823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	elif overflowRecord.itemName == 'AlternateSet':
536823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# We just need to back up by two items
537823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# from the overflowed AlternateSet index to make sure the offset
538823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# to the Coverage table doesn't overflow.
539823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newLen  = overflowRecord.itemIndex - 1
540823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
541823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	newSubTable.alternates = {}
542823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	for i in range(newLen, oldLen):
543823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		item = oldAlts[i]
544823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		key = item[0]
545823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newSubTable.alternates[key] = item[1]
546823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		del oldSubTable.alternates[key]
547823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
548823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
549823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	return ok
550823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
551823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
552823f8cd15f16bb9dc3991c2672f16dd90579711bjvrdef splitLigatureSubst(oldSubTable, newSubTable, overflowRecord):
553823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	ok = 1
554823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	newSubTable.Format = oldSubTable.Format
555823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	oldLigs = oldSubTable.ligatures.items()
556823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	oldLigs.sort()
557823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	oldLen = len(oldLigs)
558823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
559823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
560823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# Coverage table is written last. overflow is to or within the
561823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# the coverage table. We will just cut the subtable in half.
562823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newLen = int(oldLen/2)
563823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
564823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	elif overflowRecord.itemName == 'LigatureSet':
565823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# We just need to back up by two items
566823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# from the overflowed AlternateSet index to make sure the offset
567823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# to the Coverage table doesn't overflow.
568823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newLen  = overflowRecord.itemIndex - 1
569823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
570823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	newSubTable.ligatures = {}
571823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	for i in range(newLen, oldLen):
572823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		item = oldLigs[i]
573823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		key = item[0]
574823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newSubTable.ligatures[key] = item[1]
575823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		del oldSubTable.ligatures[key]
576823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
577823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	return ok
578823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
579823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
580823f8cd15f16bb9dc3991c2672f16dd90579711bjvrsplitTable = {	'GSUB': {
581823f8cd15f16bb9dc3991c2672f16dd90579711bjvr#					1: splitSingleSubst,
582823f8cd15f16bb9dc3991c2672f16dd90579711bjvr#					2: splitMultipleSubst,
583823f8cd15f16bb9dc3991c2672f16dd90579711bjvr					3: splitAlternateSubst,
584823f8cd15f16bb9dc3991c2672f16dd90579711bjvr					4: splitLigatureSubst,
585823f8cd15f16bb9dc3991c2672f16dd90579711bjvr#					5: splitContextSubst,
586823f8cd15f16bb9dc3991c2672f16dd90579711bjvr#					6: splitChainContextSubst,
587823f8cd15f16bb9dc3991c2672f16dd90579711bjvr#					7: splitExtensionSubst,
588823f8cd15f16bb9dc3991c2672f16dd90579711bjvr#					8: splitReverseChainSingleSubst,
589823f8cd15f16bb9dc3991c2672f16dd90579711bjvr					},
590823f8cd15f16bb9dc3991c2672f16dd90579711bjvr				'GPOS': {
591823f8cd15f16bb9dc3991c2672f16dd90579711bjvr#					1: splitSinglePos,
592823f8cd15f16bb9dc3991c2672f16dd90579711bjvr#					2: splitPairPos,
593823f8cd15f16bb9dc3991c2672f16dd90579711bjvr#					3: splitCursivePos,
594823f8cd15f16bb9dc3991c2672f16dd90579711bjvr#					4: splitMarkBasePos,
595823f8cd15f16bb9dc3991c2672f16dd90579711bjvr#					5: splitMarkLigPos,
596823f8cd15f16bb9dc3991c2672f16dd90579711bjvr#					6: splitMarkMarkPos,
597823f8cd15f16bb9dc3991c2672f16dd90579711bjvr#					7: splitContextPos,
598823f8cd15f16bb9dc3991c2672f16dd90579711bjvr#					8: splitChainContextPos,
599823f8cd15f16bb9dc3991c2672f16dd90579711bjvr#					9: splitExtensionPos,
600823f8cd15f16bb9dc3991c2672f16dd90579711bjvr					}
601823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
602823f8cd15f16bb9dc3991c2672f16dd90579711bjvr			}
603823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
604823f8cd15f16bb9dc3991c2672f16dd90579711bjvrdef fixSubTableOverFlows(ttf, overflowRecord):
605823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	"""
606823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts.
607823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	"""
608823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	ok = 0
609823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	table = ttf[overflowRecord.tableType].table
610823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex]
611823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	subIndex = overflowRecord.SubTableIndex
612823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	subtable = lookup.SubTable[subIndex]
613823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
614823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	if hasattr(subtable, 'ExtSubTable'):
615823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# We split the subtable of the Extension table, and add a new Extension table
616823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		# to contain the new subtable.
617823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
618823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		subTableType = subtable.ExtensionLookupType
619823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		extSubTable = subtable
620823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		subtable = extSubTable.ExtSubTable
621823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newExtSubTableClass = lookupTypes[overflowRecord.tableType][lookup.LookupType]
622823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newExtSubTable = newExtSubTableClass()
623823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newExtSubTable.Format = extSubTable.Format
624823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newExtSubTable.ExtensionLookupType = extSubTable.ExtensionLookupType
625823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		lookup.SubTable.insert(subIndex + 1, newExtSubTable)
626823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
627823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
628823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newSubTable = newSubTableClass()
629823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newExtSubTable.ExtSubTable = newSubTable
630823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	else:
631823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		subTableType = lookup.LookupType
632823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
633823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		newSubTable = newSubTableClass()
634823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		lookup.SubTable.insert(subIndex + 1, newSubTable)
635823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
636823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	if hasattr(lookup, 'SubTableCount'): # may not be defined yet.
637823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		lookup.SubTableCount = lookup.SubTableCount + 1
638823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
639823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	try:
640823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		splitFunc = splitTable[overflowRecord.tableType][subTableType]
641823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	except KeyError:
642823f8cd15f16bb9dc3991c2672f16dd90579711bjvr		return ok
643823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
644823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	ok = splitFunc(subtable, newSubTable, overflowRecord)
645823f8cd15f16bb9dc3991c2672f16dd90579711bjvr	return ok
646823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
647e5b245fd074a2efa5fc8902cb7a11a5dcad0f3f5jvr# End of OverFlow logic
648823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
649823f8cd15f16bb9dc3991c2672f16dd90579711bjvr
650d4d151390d1288f8d2df30f6dfa26a309c7334dajvrdef _buildClasses():
651d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	import new, re
6522b06aaa2a6bcd363c25fb0c43f6bb906906594bdBehdad Esfahbod	from .otData import otData
653d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
654d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	formatPat = re.compile("([A-Za-z0-9]+)Format(\d+)$")
655d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	namespace = globals()
656d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
657d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	# populate module with classes
658d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	for name, table in otData:
659d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		baseClass = BaseTable
660d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		m = formatPat.match(name)
661d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		if m:
662d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			# XxxFormatN subtable, we only add the "base" table
663d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			name = m.group(1)
664d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			baseClass = FormatSwitchingBaseTable
665bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod		if name not in namespace:
66664b5c80e80444a124da335e8d4d208bffcf2737bjvr			# the class doesn't exist yet, so the base implementation is used.
667d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			cls = new.classobj(name, (baseClass,), {})
668d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			namespace[name] = cls
669d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
67064b5c80e80444a124da335e8d4d208bffcf2737bjvr	for base, alts in _equivalents.items():
671d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		base = namespace[base]
672d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		for alt in alts:
673d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			namespace[alt] = new.classobj(alt, (base,), {})
674d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
675d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	global lookupTypes
676d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	lookupTypes = {
677d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		'GSUB': {
678d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			1: SingleSubst,
679d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			2: MultipleSubst,
680d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			3: AlternateSubst,
681d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			4: LigatureSubst,
682d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			5: ContextSubst,
683d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			6: ChainContextSubst,
684d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			7: ExtensionSubst,
6858e1d75b1a3fb4874bb78d9c34a11ba30de81a5ccjvr			8: ReverseChainSingleSubst,
686d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		},
687d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		'GPOS': {
688d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			1: SinglePos,
689d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			2: PairPos,
690d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			3: CursivePos,
691d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			4: MarkBasePos,
692d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			5: MarkLigPos,
693d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			6: MarkMarkPos,
694d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			7: ContextPos,
695d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			8: ChainContextPos,
696d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			9: ExtensionPos,
697d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		},
698d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	}
699d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	lookupTypes['JSTF'] = lookupTypes['GPOS']  # JSTF contains GPOS
70064b5c80e80444a124da335e8d4d208bffcf2737bjvr	for lookupEnum in lookupTypes.values():
70164b5c80e80444a124da335e8d4d208bffcf2737bjvr		for enum, cls in lookupEnum.items():
70264b5c80e80444a124da335e8d4d208bffcf2737bjvr			cls.LookupType = enum
7039e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod
7049e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod	global featureParamTypes
7059e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod	featureParamTypes = {
7069e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod		'size': FeatureParamsSize,
7079e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod	}
7089e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod	for i in range(1, 20+1):
7099e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod		featureParamTypes['ss%02d' % i] = FeatureParamsStylisticSet
7109e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod	for i in range(1, 99+1):
7119e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod		featureParamTypes['cv%02d' % i] = FeatureParamsCharacterVariants
712d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
713d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	# add converters to classes
7142b06aaa2a6bcd363c25fb0c43f6bb906906594bdBehdad Esfahbod	from .otConverters import buildConverters
715d4d151390d1288f8d2df30f6dfa26a309c7334dajvr	for name, table in otData:
716d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		m = formatPat.match(name)
717d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		if m:
718d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			# XxxFormatN subtable, add converter to "base" table
719d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			name, format = m.groups()
720d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			format = int(format)
721d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			cls = namespace[name]
722d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			if not hasattr(cls, "converters"):
723d4d151390d1288f8d2df30f6dfa26a309c7334dajvr				cls.converters = {}
724d4d151390d1288f8d2df30f6dfa26a309c7334dajvr				cls.convertersByName = {}
72564b5c80e80444a124da335e8d4d208bffcf2737bjvr			converters, convertersByName = buildConverters(table[1:], namespace)
726d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			cls.converters[format] = converters
727d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			cls.convertersByName[format] = convertersByName
728d4d151390d1288f8d2df30f6dfa26a309c7334dajvr		else:
729d4d151390d1288f8d2df30f6dfa26a309c7334dajvr			cls = namespace[name]
73064b5c80e80444a124da335e8d4d208bffcf2737bjvr			cls.converters, cls.convertersByName = buildConverters(table, namespace)
731d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
732d4d151390d1288f8d2df30f6dfa26a309c7334dajvr
733d4d151390d1288f8d2df30f6dfa26a309c7334dajvr_buildClasses()
7341fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr
7351fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr
7361fae9b48d3a275c6e1dc0649c40ae121dfc4098djvrdef _getGlyphsFromCoverageTable(coverage):
7371fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr	if coverage is None:
7381fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr		# empty coverage table
7391fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr		return []
7401fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr	else:
7411fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr		return coverage.glyphs
742