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