otTables.py revision ac1b4359467ca3deab03186a15eae1d55eb35567
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] 166ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod sortableItems = sorted(zip(gidItems, items)) 167823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 168823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # figure out format 1691d6360af4c553a7b26447b0e1e96f7b701c9325ejvr format = 2 1701d6360af4c553a7b26447b0e1e96f7b701c9325ejvr delta = None 171823f8cd15f16bb9dc3991c2672f16dd90579711bjvr for inID, outID in gidItems: 1721d6360af4c553a7b26447b0e1e96f7b701c9325ejvr if delta is None: 1731d6360af4c553a7b26447b0e1e96f7b701c9325ejvr delta = outID - inID 1741d6360af4c553a7b26447b0e1e96f7b701c9325ejvr else: 1751d6360af4c553a7b26447b0e1e96f7b701c9325ejvr if delta != outID - inID: 1761d6360af4c553a7b26447b0e1e96f7b701c9325ejvr break 1771d6360af4c553a7b26447b0e1e96f7b701c9325ejvr else: 1781d6360af4c553a7b26447b0e1e96f7b701c9325ejvr format = 1 179823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 1801d6360af4c553a7b26447b0e1e96f7b701c9325ejvr rawTable = {} 1811d6360af4c553a7b26447b0e1e96f7b701c9325ejvr self.Format = format 1821d6360af4c553a7b26447b0e1e96f7b701c9325ejvr cov = Coverage() 183823f8cd15f16bb9dc3991c2672f16dd90579711bjvr input = [ item [1][0] for item in sortableItems] 184823f8cd15f16bb9dc3991c2672f16dd90579711bjvr subst = [ item [1][1] for item in sortableItems] 185823f8cd15f16bb9dc3991c2672f16dd90579711bjvr cov.glyphs = input 1861d6360af4c553a7b26447b0e1e96f7b701c9325ejvr rawTable["Coverage"] = cov 1871d6360af4c553a7b26447b0e1e96f7b701c9325ejvr if format == 1: 1881d6360af4c553a7b26447b0e1e96f7b701c9325ejvr assert delta is not None 1891d6360af4c553a7b26447b0e1e96f7b701c9325ejvr rawTable["DeltaGlyphID"] = delta 1901d6360af4c553a7b26447b0e1e96f7b701c9325ejvr else: 1911d6360af4c553a7b26447b0e1e96f7b701c9325ejvr rawTable["Substitute"] = subst 1921d6360af4c553a7b26447b0e1e96f7b701c9325ejvr return rawTable 1931d6360af4c553a7b26447b0e1e96f7b701c9325ejvr 1941d6360af4c553a7b26447b0e1e96f7b701c9325ejvr def toXML2(self, xmlWriter, font): 195ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod items = sorted(self.mapping.items()) 1961d6360af4c553a7b26447b0e1e96f7b701c9325ejvr for inGlyph, outGlyph in items: 1971d6360af4c553a7b26447b0e1e96f7b701c9325ejvr xmlWriter.simpletag("Substitution", 1981d6360af4c553a7b26447b0e1e96f7b701c9325ejvr [("in", inGlyph), ("out", outGlyph)]) 1991d6360af4c553a7b26447b0e1e96f7b701c9325ejvr xmlWriter.newline() 2001d6360af4c553a7b26447b0e1e96f7b701c9325ejvr 2013a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, font): 2021d6360af4c553a7b26447b0e1e96f7b701c9325ejvr mapping = getattr(self, "mapping", None) 2031d6360af4c553a7b26447b0e1e96f7b701c9325ejvr if mapping is None: 2041d6360af4c553a7b26447b0e1e96f7b701c9325ejvr mapping = {} 2051d6360af4c553a7b26447b0e1e96f7b701c9325ejvr self.mapping = mapping 2061d6360af4c553a7b26447b0e1e96f7b701c9325ejvr mapping[attrs["in"]] = attrs["out"] 2071d6360af4c553a7b26447b0e1e96f7b701c9325ejvr 2081d6360af4c553a7b26447b0e1e96f7b701c9325ejvr 209a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvrclass ClassDef(FormatSwitchingBaseTable): 210a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr 211a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr def postRead(self, rawTable, font): 212a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr classDefs = {} 213823f8cd15f16bb9dc3991c2672f16dd90579711bjvr getGlyphName = font.getGlyphName 214823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 215a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr if self.Format == 1: 216a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr start = rawTable["StartGlyph"] 217823f8cd15f16bb9dc3991c2672f16dd90579711bjvr classList = rawTable["ClassValueArray"] 218823f8cd15f16bb9dc3991c2672f16dd90579711bjvr lenList = len(classList) 219a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr glyphID = font.getGlyphID(start) 22097dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod gidList = list(range(glyphID, glyphID + len(classList))) 221823f8cd15f16bb9dc3991c2672f16dd90579711bjvr keyList = [getGlyphName(glyphID) for glyphID in gidList] 222823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 223823f8cd15f16bb9dc3991c2672f16dd90579711bjvr map(operator.setitem, [classDefs]*lenList, keyList, classList) 224823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 225a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr elif self.Format == 2: 226a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr records = rawTable["ClassRangeRecord"] 227a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr for rec in records: 228a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr start = rec.Start 229a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr end = rec.End 230a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr cls = rec.Class 231a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr classDefs[start] = cls 23297dea0a5d02ba1655d27a06fe91540e3495b8ef9Behdad Esfahbod glyphIDs = list(range(font.getGlyphID(start) + 1, font.getGlyphID(end))) 233823f8cd15f16bb9dc3991c2672f16dd90579711bjvr lenList = len(glyphIDs) 234823f8cd15f16bb9dc3991c2672f16dd90579711bjvr keyList = [getGlyphName(glyphID) for glyphID in glyphIDs] 235823f8cd15f16bb9dc3991c2672f16dd90579711bjvr map(operator.setitem, [classDefs]*lenList, keyList, [cls]*lenList) 236a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr classDefs[end] = cls 237a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr else: 238a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr assert 0, "unknown format: %s" % self.Format 239a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr self.classDefs = classDefs 240a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr 241a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr def preWrite(self, font): 24231ae380735dbf75a460df002a7fc2bcd81e28153jvr classDefs = getattr(self, "classDefs", None) 24331ae380735dbf75a460df002a7fc2bcd81e28153jvr if classDefs is None: 24431ae380735dbf75a460df002a7fc2bcd81e28153jvr classDefs = self.classDefs = {} 24531ae380735dbf75a460df002a7fc2bcd81e28153jvr items = classDefs.items() 246823f8cd15f16bb9dc3991c2672f16dd90579711bjvr getGlyphID = font.getGlyphID 247a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr for i in range(len(items)): 248a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr glyphName, cls = items[i] 249823f8cd15f16bb9dc3991c2672f16dd90579711bjvr items[i] = getGlyphID(glyphName), glyphName, cls 250a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr items.sort() 25131ae380735dbf75a460df002a7fc2bcd81e28153jvr if items: 25231ae380735dbf75a460df002a7fc2bcd81e28153jvr last, lastName, lastCls = items[0] 25331ae380735dbf75a460df002a7fc2bcd81e28153jvr rec = ClassRangeRecord() 25431ae380735dbf75a460df002a7fc2bcd81e28153jvr rec.Start = lastName 25531ae380735dbf75a460df002a7fc2bcd81e28153jvr rec.Class = lastCls 25631ae380735dbf75a460df002a7fc2bcd81e28153jvr ranges = [rec] 25731ae380735dbf75a460df002a7fc2bcd81e28153jvr for glyphID, glyphName, cls in items[1:]: 25831ae380735dbf75a460df002a7fc2bcd81e28153jvr if glyphID != last + 1 or cls != lastCls: 25931ae380735dbf75a460df002a7fc2bcd81e28153jvr rec.End = lastName 26031ae380735dbf75a460df002a7fc2bcd81e28153jvr rec = ClassRangeRecord() 26131ae380735dbf75a460df002a7fc2bcd81e28153jvr rec.Start = glyphName 26231ae380735dbf75a460df002a7fc2bcd81e28153jvr rec.Class = cls 26331ae380735dbf75a460df002a7fc2bcd81e28153jvr ranges.append(rec) 26431ae380735dbf75a460df002a7fc2bcd81e28153jvr last = glyphID 26531ae380735dbf75a460df002a7fc2bcd81e28153jvr lastName = glyphName 26631ae380735dbf75a460df002a7fc2bcd81e28153jvr lastCls = cls 26731ae380735dbf75a460df002a7fc2bcd81e28153jvr rec.End = lastName 26831ae380735dbf75a460df002a7fc2bcd81e28153jvr else: 26931ae380735dbf75a460df002a7fc2bcd81e28153jvr ranges = [] 270a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr self.Format = 2 # currently no support for Format 1 271a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr return {"ClassRangeRecord": ranges} 272a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr 273a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr def toXML2(self, xmlWriter, font): 274ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod items = sorted(self.classDefs.items()) 275a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr for glyphName, cls in items: 276a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)]) 277a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr xmlWriter.newline() 278a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr 2793a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, font): 280a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr classDefs = getattr(self, "classDefs", None) 281a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr if classDefs is None: 282a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr classDefs = {} 283a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr self.classDefs = classDefs 284a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr classDefs[attrs["glyph"]] = int(attrs["class"]) 285a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr 286a1dfa2b77a0ac7cf2e7cdca25e024e31de436b1ajvr 287b2486125e9438443a1419706c50958ab0676eb5ajvrclass AlternateSubst(FormatSwitchingBaseTable): 288b2486125e9438443a1419706c50958ab0676eb5ajvr 289b2486125e9438443a1419706c50958ab0676eb5ajvr def postRead(self, rawTable, font): 290b2486125e9438443a1419706c50958ab0676eb5ajvr alternates = {} 291b2486125e9438443a1419706c50958ab0676eb5ajvr if self.Format == 1: 2921fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 293b2486125e9438443a1419706c50958ab0676eb5ajvr alts = rawTable["AlternateSet"] 294823f8cd15f16bb9dc3991c2672f16dd90579711bjvr if len(input) != len(alts): 295823f8cd15f16bb9dc3991c2672f16dd90579711bjvr assert len(input) == len(alts) 296b2486125e9438443a1419706c50958ab0676eb5ajvr for i in range(len(input)): 297b2486125e9438443a1419706c50958ab0676eb5ajvr alternates[input[i]] = alts[i].Alternate 298b2486125e9438443a1419706c50958ab0676eb5ajvr else: 299b2486125e9438443a1419706c50958ab0676eb5ajvr assert 0, "unknown format: %s" % self.Format 300b2486125e9438443a1419706c50958ab0676eb5ajvr self.alternates = alternates 301b2486125e9438443a1419706c50958ab0676eb5ajvr 302b2486125e9438443a1419706c50958ab0676eb5ajvr def preWrite(self, font): 303b2486125e9438443a1419706c50958ab0676eb5ajvr self.Format = 1 30431ae380735dbf75a460df002a7fc2bcd81e28153jvr alternates = getattr(self, "alternates", None) 30531ae380735dbf75a460df002a7fc2bcd81e28153jvr if alternates is None: 30631ae380735dbf75a460df002a7fc2bcd81e28153jvr alternates = self.alternates = {} 30731ae380735dbf75a460df002a7fc2bcd81e28153jvr items = alternates.items() 308b2486125e9438443a1419706c50958ab0676eb5ajvr for i in range(len(items)): 309b2486125e9438443a1419706c50958ab0676eb5ajvr glyphName, set = items[i] 310b2486125e9438443a1419706c50958ab0676eb5ajvr items[i] = font.getGlyphID(glyphName), glyphName, set 311b2486125e9438443a1419706c50958ab0676eb5ajvr items.sort() 312b2486125e9438443a1419706c50958ab0676eb5ajvr cov = Coverage() 313823f8cd15f16bb9dc3991c2672f16dd90579711bjvr cov.glyphs = [ item[1] for item in items] 314b2486125e9438443a1419706c50958ab0676eb5ajvr alternates = [] 315823f8cd15f16bb9dc3991c2672f16dd90579711bjvr setList = [ item[-1] for item in items] 316823f8cd15f16bb9dc3991c2672f16dd90579711bjvr for set in setList: 317b2486125e9438443a1419706c50958ab0676eb5ajvr alts = AlternateSet() 318b2486125e9438443a1419706c50958ab0676eb5ajvr alts.Alternate = set 319b2486125e9438443a1419706c50958ab0676eb5ajvr alternates.append(alts) 320823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # a special case to deal with the fact that several hundred Adobe Japan1-5 321823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # CJK fonts will overflow an offset if the coverage table isn't pushed to the end. 322823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # Also useful in that when splitting a sub-table because of an offset overflow 323823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # I don't need to calculate the change in the subtable offset due to the change in the coverage table size. 324823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # Allows packing more rules in subtable. 325823f8cd15f16bb9dc3991c2672f16dd90579711bjvr self.sortCoverageLast = 1 326b2486125e9438443a1419706c50958ab0676eb5ajvr return {"Coverage": cov, "AlternateSet": alternates} 327b2486125e9438443a1419706c50958ab0676eb5ajvr 328b2486125e9438443a1419706c50958ab0676eb5ajvr def toXML2(self, xmlWriter, font): 329ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod items = sorted(self.alternates.items()) 330b2486125e9438443a1419706c50958ab0676eb5ajvr for glyphName, alternates in items: 331b2486125e9438443a1419706c50958ab0676eb5ajvr xmlWriter.begintag("AlternateSet", glyph=glyphName) 332b2486125e9438443a1419706c50958ab0676eb5ajvr xmlWriter.newline() 333b2486125e9438443a1419706c50958ab0676eb5ajvr for alt in alternates: 334b2486125e9438443a1419706c50958ab0676eb5ajvr xmlWriter.simpletag("Alternate", glyph=alt) 335b2486125e9438443a1419706c50958ab0676eb5ajvr xmlWriter.newline() 336b2486125e9438443a1419706c50958ab0676eb5ajvr xmlWriter.endtag("AlternateSet") 337b2486125e9438443a1419706c50958ab0676eb5ajvr xmlWriter.newline() 338b2486125e9438443a1419706c50958ab0676eb5ajvr 3393a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, font): 340b2486125e9438443a1419706c50958ab0676eb5ajvr alternates = getattr(self, "alternates", None) 341b2486125e9438443a1419706c50958ab0676eb5ajvr if alternates is None: 342b2486125e9438443a1419706c50958ab0676eb5ajvr alternates = {} 343b2486125e9438443a1419706c50958ab0676eb5ajvr self.alternates = alternates 344b2486125e9438443a1419706c50958ab0676eb5ajvr glyphName = attrs["glyph"] 345b2486125e9438443a1419706c50958ab0676eb5ajvr set = [] 346b2486125e9438443a1419706c50958ab0676eb5ajvr alternates[glyphName] = set 347b2486125e9438443a1419706c50958ab0676eb5ajvr for element in content: 348ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod if not isinstance(element, TupleType): 349b2486125e9438443a1419706c50958ab0676eb5ajvr continue 350b2486125e9438443a1419706c50958ab0676eb5ajvr name, attrs, content = element 351b2486125e9438443a1419706c50958ab0676eb5ajvr set.append(attrs["glyph"]) 352b2486125e9438443a1419706c50958ab0676eb5ajvr 353b2486125e9438443a1419706c50958ab0676eb5ajvr 354f2164abef39af7cfef989352fadfca628ed077b2jvrclass LigatureSubst(FormatSwitchingBaseTable): 355f2164abef39af7cfef989352fadfca628ed077b2jvr 356f2164abef39af7cfef989352fadfca628ed077b2jvr def postRead(self, rawTable, font): 357f2164abef39af7cfef989352fadfca628ed077b2jvr ligatures = {} 358f2164abef39af7cfef989352fadfca628ed077b2jvr if self.Format == 1: 359823f8cd15f16bb9dc3991c2672f16dd90579711bjvr input = rawTable["Coverage"].glyphs 360f2164abef39af7cfef989352fadfca628ed077b2jvr ligSets = rawTable["LigatureSet"] 361f2164abef39af7cfef989352fadfca628ed077b2jvr assert len(input) == len(ligSets) 362f2164abef39af7cfef989352fadfca628ed077b2jvr for i in range(len(input)): 363f2164abef39af7cfef989352fadfca628ed077b2jvr ligatures[input[i]] = ligSets[i].Ligature 364f2164abef39af7cfef989352fadfca628ed077b2jvr else: 365f2164abef39af7cfef989352fadfca628ed077b2jvr assert 0, "unknown format: %s" % self.Format 366f2164abef39af7cfef989352fadfca628ed077b2jvr self.ligatures = ligatures 367f2164abef39af7cfef989352fadfca628ed077b2jvr 368f2164abef39af7cfef989352fadfca628ed077b2jvr def preWrite(self, font): 36931ae380735dbf75a460df002a7fc2bcd81e28153jvr ligatures = getattr(self, "ligatures", None) 37031ae380735dbf75a460df002a7fc2bcd81e28153jvr if ligatures is None: 37131ae380735dbf75a460df002a7fc2bcd81e28153jvr ligatures = self.ligatures = {} 37231ae380735dbf75a460df002a7fc2bcd81e28153jvr items = ligatures.items() 373f2164abef39af7cfef989352fadfca628ed077b2jvr for i in range(len(items)): 374f2164abef39af7cfef989352fadfca628ed077b2jvr glyphName, set = items[i] 375f2164abef39af7cfef989352fadfca628ed077b2jvr items[i] = font.getGlyphID(glyphName), glyphName, set 376f2164abef39af7cfef989352fadfca628ed077b2jvr items.sort() 377f2164abef39af7cfef989352fadfca628ed077b2jvr cov = Coverage() 378823f8cd15f16bb9dc3991c2672f16dd90579711bjvr cov.glyphs = [ item[1] for item in items] 379823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 380f2164abef39af7cfef989352fadfca628ed077b2jvr ligSets = [] 381823f8cd15f16bb9dc3991c2672f16dd90579711bjvr setList = [ item[-1] for item in items ] 382823f8cd15f16bb9dc3991c2672f16dd90579711bjvr for set in setList: 383f2164abef39af7cfef989352fadfca628ed077b2jvr ligSet = LigatureSet() 384f2164abef39af7cfef989352fadfca628ed077b2jvr ligs = ligSet.Ligature = [] 385f2164abef39af7cfef989352fadfca628ed077b2jvr for lig in set: 386f2164abef39af7cfef989352fadfca628ed077b2jvr ligs.append(lig) 387f2164abef39af7cfef989352fadfca628ed077b2jvr ligSets.append(ligSet) 388823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # Useful in that when splitting a sub-table because of an offset overflow 389823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # I don't need to calculate the change in subtabl offset due to the coverage table size. 390823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # Allows packing more rules in subtable. 391823f8cd15f16bb9dc3991c2672f16dd90579711bjvr self.sortCoverageLast = 1 392f2164abef39af7cfef989352fadfca628ed077b2jvr return {"Coverage": cov, "LigatureSet": ligSets} 393f2164abef39af7cfef989352fadfca628ed077b2jvr 394f2164abef39af7cfef989352fadfca628ed077b2jvr def toXML2(self, xmlWriter, font): 395ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod items = sorted(self.ligatures.items()) 396f2164abef39af7cfef989352fadfca628ed077b2jvr for glyphName, ligSets in items: 397f2164abef39af7cfef989352fadfca628ed077b2jvr xmlWriter.begintag("LigatureSet", glyph=glyphName) 398f2164abef39af7cfef989352fadfca628ed077b2jvr xmlWriter.newline() 399f2164abef39af7cfef989352fadfca628ed077b2jvr for lig in ligSets: 400f2164abef39af7cfef989352fadfca628ed077b2jvr xmlWriter.simpletag("Ligature", glyph=lig.LigGlyph, 401f2164abef39af7cfef989352fadfca628ed077b2jvr components=",".join(lig.Component)) 402f2164abef39af7cfef989352fadfca628ed077b2jvr xmlWriter.newline() 403f2164abef39af7cfef989352fadfca628ed077b2jvr xmlWriter.endtag("LigatureSet") 404f2164abef39af7cfef989352fadfca628ed077b2jvr xmlWriter.newline() 405f2164abef39af7cfef989352fadfca628ed077b2jvr 4063a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, font): 407f2164abef39af7cfef989352fadfca628ed077b2jvr ligatures = getattr(self, "ligatures", None) 408f2164abef39af7cfef989352fadfca628ed077b2jvr if ligatures is None: 409f2164abef39af7cfef989352fadfca628ed077b2jvr ligatures = {} 410f2164abef39af7cfef989352fadfca628ed077b2jvr self.ligatures = ligatures 411f2164abef39af7cfef989352fadfca628ed077b2jvr glyphName = attrs["glyph"] 412f2164abef39af7cfef989352fadfca628ed077b2jvr ligs = [] 413f2164abef39af7cfef989352fadfca628ed077b2jvr ligatures[glyphName] = ligs 414f2164abef39af7cfef989352fadfca628ed077b2jvr for element in content: 415ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod if not isinstance(element, TupleType): 416f2164abef39af7cfef989352fadfca628ed077b2jvr continue 417f2164abef39af7cfef989352fadfca628ed077b2jvr name, attrs, content = element 418f2164abef39af7cfef989352fadfca628ed077b2jvr lig = Ligature() 419f2164abef39af7cfef989352fadfca628ed077b2jvr lig.LigGlyph = attrs["glyph"] 420f2164abef39af7cfef989352fadfca628ed077b2jvr lig.Component = attrs["components"].split(",") 421f2164abef39af7cfef989352fadfca628ed077b2jvr ligs.append(lig) 422f2164abef39af7cfef989352fadfca628ed077b2jvr 423f2164abef39af7cfef989352fadfca628ed077b2jvr 42464b5c80e80444a124da335e8d4d208bffcf2737bjvr# 42564b5c80e80444a124da335e8d4d208bffcf2737bjvr# For each subtable format there is a class. However, we don't really distinguish 42664b5c80e80444a124da335e8d4d208bffcf2737bjvr# between "field name" and "format name": often these are the same. Yet there's 42764b5c80e80444a124da335e8d4d208bffcf2737bjvr# a whole bunch of fields with different names. The following dict is a mapping 42864b5c80e80444a124da335e8d4d208bffcf2737bjvr# from "format name" to "field name". _buildClasses() uses this to create a 42964b5c80e80444a124da335e8d4d208bffcf2737bjvr# subclass for each alternate field name. 43064b5c80e80444a124da335e8d4d208bffcf2737bjvr# 43164b5c80e80444a124da335e8d4d208bffcf2737bjvr_equivalents = { 43264b5c80e80444a124da335e8d4d208bffcf2737bjvr 'MarkArray': ("Mark1Array",), 43364b5c80e80444a124da335e8d4d208bffcf2737bjvr 'LangSys': ('DefaultLangSys',), 43464b5c80e80444a124da335e8d4d208bffcf2737bjvr 'Coverage': ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage', 435d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage', 43627f108844ef1aeeb5c694a8304bf6c4944783d42jvr 'LookAheadCoverage'), 43764b5c80e80444a124da335e8d4d208bffcf2737bjvr 'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef', 43827f108844ef1aeeb5c694a8304bf6c4944783d42jvr 'LookAheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'), 43964b5c80e80444a124da335e8d4d208bffcf2737bjvr 'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor', 44064b5c80e80444a124da335e8d4d208bffcf2737bjvr 'Mark2Anchor', 'MarkAnchor'), 44164b5c80e80444a124da335e8d4d208bffcf2737bjvr 'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice', 44264b5c80e80444a124da335e8d4d208bffcf2737bjvr 'XDeviceTable', 'YDeviceTable', 'DeviceTable'), 44364b5c80e80444a124da335e8d4d208bffcf2737bjvr 'Axis': ('HorizAxis', 'VertAxis',), 44464b5c80e80444a124da335e8d4d208bffcf2737bjvr 'MinMax': ('DefaultMinMax',), 44564b5c80e80444a124da335e8d4d208bffcf2737bjvr 'BaseCoord': ('MinCoord', 'MaxCoord',), 44664b5c80e80444a124da335e8d4d208bffcf2737bjvr 'JstfLangSys': ('DefJstfLangSys',), 44764b5c80e80444a124da335e8d4d208bffcf2737bjvr 'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB', 44864b5c80e80444a124da335e8d4d208bffcf2737bjvr 'ExtensionDisableGSUB',), 44964b5c80e80444a124da335e8d4d208bffcf2737bjvr 'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS', 45064b5c80e80444a124da335e8d4d208bffcf2737bjvr 'ExtensionDisableGPOS',), 45164b5c80e80444a124da335e8d4d208bffcf2737bjvr 'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',), 45264b5c80e80444a124da335e8d4d208bffcf2737bjvr} 453d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 454e5b245fd074a2efa5fc8902cb7a11a5dcad0f3f5jvr# 455e5b245fd074a2efa5fc8902cb7a11a5dcad0f3f5jvr# OverFlow logic, to automatically create ExtensionLookups 456e5b245fd074a2efa5fc8902cb7a11a5dcad0f3f5jvr# XXX This should probably move to otBase.py 457e5b245fd074a2efa5fc8902cb7a11a5dcad0f3f5jvr# 458d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 459823f8cd15f16bb9dc3991c2672f16dd90579711bjvrdef fixLookupOverFlows(ttf, overflowRecord): 460823f8cd15f16bb9dc3991c2672f16dd90579711bjvr """ Either the offset from the LookupList to a lookup overflowed, or 461823f8cd15f16bb9dc3991c2672f16dd90579711bjvr an offset from a lookup to a subtable overflowed. 462823f8cd15f16bb9dc3991c2672f16dd90579711bjvr The table layout is: 463823f8cd15f16bb9dc3991c2672f16dd90579711bjvr GPSO/GUSB 464823f8cd15f16bb9dc3991c2672f16dd90579711bjvr Script List 465823f8cd15f16bb9dc3991c2672f16dd90579711bjvr Feature List 466823f8cd15f16bb9dc3991c2672f16dd90579711bjvr LookUpList 467823f8cd15f16bb9dc3991c2672f16dd90579711bjvr Lookup[0] and contents 468823f8cd15f16bb9dc3991c2672f16dd90579711bjvr SubTable offset list 469823f8cd15f16bb9dc3991c2672f16dd90579711bjvr SubTable[0] and contents 470823f8cd15f16bb9dc3991c2672f16dd90579711bjvr ... 471823f8cd15f16bb9dc3991c2672f16dd90579711bjvr SubTable[n] and contents 472823f8cd15f16bb9dc3991c2672f16dd90579711bjvr ... 473823f8cd15f16bb9dc3991c2672f16dd90579711bjvr Lookup[n] and contents 474823f8cd15f16bb9dc3991c2672f16dd90579711bjvr SubTable offset list 475823f8cd15f16bb9dc3991c2672f16dd90579711bjvr SubTable[0] and contents 476823f8cd15f16bb9dc3991c2672f16dd90579711bjvr ... 477823f8cd15f16bb9dc3991c2672f16dd90579711bjvr SubTable[n] and contents 478823f8cd15f16bb9dc3991c2672f16dd90579711bjvr If the offset to a lookup overflowed (SubTableIndex == None) 479823f8cd15f16bb9dc3991c2672f16dd90579711bjvr we must promote the *previous* lookup to an Extension type. 480823f8cd15f16bb9dc3991c2672f16dd90579711bjvr If the offset from a lookup to subtable overflowed, then we must promote it 481823f8cd15f16bb9dc3991c2672f16dd90579711bjvr to an Extension Lookup type. 482823f8cd15f16bb9dc3991c2672f16dd90579711bjvr """ 483823f8cd15f16bb9dc3991c2672f16dd90579711bjvr ok = 0 484823f8cd15f16bb9dc3991c2672f16dd90579711bjvr lookupIndex = overflowRecord.LookupListIndex 485823f8cd15f16bb9dc3991c2672f16dd90579711bjvr if (overflowRecord.SubTableIndex == None): 486823f8cd15f16bb9dc3991c2672f16dd90579711bjvr lookupIndex = lookupIndex - 1 487823f8cd15f16bb9dc3991c2672f16dd90579711bjvr if lookupIndex < 0: 488823f8cd15f16bb9dc3991c2672f16dd90579711bjvr return ok 489823f8cd15f16bb9dc3991c2672f16dd90579711bjvr if overflowRecord.tableType == 'GSUB': 490823f8cd15f16bb9dc3991c2672f16dd90579711bjvr extType = 7 491823f8cd15f16bb9dc3991c2672f16dd90579711bjvr elif overflowRecord.tableType == 'GPOS': 492823f8cd15f16bb9dc3991c2672f16dd90579711bjvr extType = 9 493823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 494823f8cd15f16bb9dc3991c2672f16dd90579711bjvr lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup 495823f8cd15f16bb9dc3991c2672f16dd90579711bjvr lookup = lookups[lookupIndex] 496823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # If the previous lookup is an extType, look further back. Very unlikely, but possible. 497823f8cd15f16bb9dc3991c2672f16dd90579711bjvr while lookup.LookupType == extType: 498823f8cd15f16bb9dc3991c2672f16dd90579711bjvr lookupIndex = lookupIndex -1 499823f8cd15f16bb9dc3991c2672f16dd90579711bjvr if lookupIndex < 0: 500823f8cd15f16bb9dc3991c2672f16dd90579711bjvr return ok 501823f8cd15f16bb9dc3991c2672f16dd90579711bjvr lookup = lookups[lookupIndex] 502823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 503823f8cd15f16bb9dc3991c2672f16dd90579711bjvr for si in range(len(lookup.SubTable)): 504823f8cd15f16bb9dc3991c2672f16dd90579711bjvr subTable = lookup.SubTable[si] 505823f8cd15f16bb9dc3991c2672f16dd90579711bjvr extSubTableClass = lookupTypes[overflowRecord.tableType][extType] 506823f8cd15f16bb9dc3991c2672f16dd90579711bjvr extSubTable = extSubTableClass() 507823f8cd15f16bb9dc3991c2672f16dd90579711bjvr extSubTable.Format = 1 508823f8cd15f16bb9dc3991c2672f16dd90579711bjvr extSubTable.ExtensionLookupType = lookup.LookupType 509823f8cd15f16bb9dc3991c2672f16dd90579711bjvr extSubTable.ExtSubTable = subTable 510823f8cd15f16bb9dc3991c2672f16dd90579711bjvr lookup.SubTable[si] = extSubTable 511823f8cd15f16bb9dc3991c2672f16dd90579711bjvr lookup.LookupType = extType 512823f8cd15f16bb9dc3991c2672f16dd90579711bjvr ok = 1 513823f8cd15f16bb9dc3991c2672f16dd90579711bjvr return ok 514823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 515823f8cd15f16bb9dc3991c2672f16dd90579711bjvrdef splitAlternateSubst(oldSubTable, newSubTable, overflowRecord): 516823f8cd15f16bb9dc3991c2672f16dd90579711bjvr ok = 1 517823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newSubTable.Format = oldSubTable.Format 518823f8cd15f16bb9dc3991c2672f16dd90579711bjvr if hasattr(oldSubTable, 'sortCoverageLast'): 519823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast 520823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 521ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod oldAlts = sorted(oldSubTable.alternates.items()) 522823f8cd15f16bb9dc3991c2672f16dd90579711bjvr oldLen = len(oldAlts) 523823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 524823f8cd15f16bb9dc3991c2672f16dd90579711bjvr if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']: 525823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # Coverage table is written last. overflow is to or within the 526823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # the coverage table. We will just cut the subtable in half. 527823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newLen = int(oldLen/2) 528823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 529823f8cd15f16bb9dc3991c2672f16dd90579711bjvr elif overflowRecord.itemName == 'AlternateSet': 530823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # We just need to back up by two items 531823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # from the overflowed AlternateSet index to make sure the offset 532823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # to the Coverage table doesn't overflow. 533823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newLen = overflowRecord.itemIndex - 1 534823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 535823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newSubTable.alternates = {} 536823f8cd15f16bb9dc3991c2672f16dd90579711bjvr for i in range(newLen, oldLen): 537823f8cd15f16bb9dc3991c2672f16dd90579711bjvr item = oldAlts[i] 538823f8cd15f16bb9dc3991c2672f16dd90579711bjvr key = item[0] 539823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newSubTable.alternates[key] = item[1] 540823f8cd15f16bb9dc3991c2672f16dd90579711bjvr del oldSubTable.alternates[key] 541823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 542823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 543823f8cd15f16bb9dc3991c2672f16dd90579711bjvr return ok 544823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 545823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 546823f8cd15f16bb9dc3991c2672f16dd90579711bjvrdef splitLigatureSubst(oldSubTable, newSubTable, overflowRecord): 547823f8cd15f16bb9dc3991c2672f16dd90579711bjvr ok = 1 548823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newSubTable.Format = oldSubTable.Format 549ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod oldLigs = sorted(oldSubTable.ligatures.items()) 550823f8cd15f16bb9dc3991c2672f16dd90579711bjvr oldLen = len(oldLigs) 551823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 552823f8cd15f16bb9dc3991c2672f16dd90579711bjvr if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']: 553823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # Coverage table is written last. overflow is to or within the 554823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # the coverage table. We will just cut the subtable in half. 555823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newLen = int(oldLen/2) 556823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 557823f8cd15f16bb9dc3991c2672f16dd90579711bjvr elif overflowRecord.itemName == 'LigatureSet': 558823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # We just need to back up by two items 559823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # from the overflowed AlternateSet index to make sure the offset 560823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # to the Coverage table doesn't overflow. 561823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newLen = overflowRecord.itemIndex - 1 562823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 563823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newSubTable.ligatures = {} 564823f8cd15f16bb9dc3991c2672f16dd90579711bjvr for i in range(newLen, oldLen): 565823f8cd15f16bb9dc3991c2672f16dd90579711bjvr item = oldLigs[i] 566823f8cd15f16bb9dc3991c2672f16dd90579711bjvr key = item[0] 567823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newSubTable.ligatures[key] = item[1] 568823f8cd15f16bb9dc3991c2672f16dd90579711bjvr del oldSubTable.ligatures[key] 569823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 570823f8cd15f16bb9dc3991c2672f16dd90579711bjvr return ok 571823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 572823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 573823f8cd15f16bb9dc3991c2672f16dd90579711bjvrsplitTable = { 'GSUB': { 574823f8cd15f16bb9dc3991c2672f16dd90579711bjvr# 1: splitSingleSubst, 575823f8cd15f16bb9dc3991c2672f16dd90579711bjvr# 2: splitMultipleSubst, 576823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 3: splitAlternateSubst, 577823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 4: splitLigatureSubst, 578823f8cd15f16bb9dc3991c2672f16dd90579711bjvr# 5: splitContextSubst, 579823f8cd15f16bb9dc3991c2672f16dd90579711bjvr# 6: splitChainContextSubst, 580823f8cd15f16bb9dc3991c2672f16dd90579711bjvr# 7: splitExtensionSubst, 581823f8cd15f16bb9dc3991c2672f16dd90579711bjvr# 8: splitReverseChainSingleSubst, 582823f8cd15f16bb9dc3991c2672f16dd90579711bjvr }, 583823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 'GPOS': { 584823f8cd15f16bb9dc3991c2672f16dd90579711bjvr# 1: splitSinglePos, 585823f8cd15f16bb9dc3991c2672f16dd90579711bjvr# 2: splitPairPos, 586823f8cd15f16bb9dc3991c2672f16dd90579711bjvr# 3: splitCursivePos, 587823f8cd15f16bb9dc3991c2672f16dd90579711bjvr# 4: splitMarkBasePos, 588823f8cd15f16bb9dc3991c2672f16dd90579711bjvr# 5: splitMarkLigPos, 589823f8cd15f16bb9dc3991c2672f16dd90579711bjvr# 6: splitMarkMarkPos, 590823f8cd15f16bb9dc3991c2672f16dd90579711bjvr# 7: splitContextPos, 591823f8cd15f16bb9dc3991c2672f16dd90579711bjvr# 8: splitChainContextPos, 592823f8cd15f16bb9dc3991c2672f16dd90579711bjvr# 9: splitExtensionPos, 593823f8cd15f16bb9dc3991c2672f16dd90579711bjvr } 594823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 595823f8cd15f16bb9dc3991c2672f16dd90579711bjvr } 596823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 597823f8cd15f16bb9dc3991c2672f16dd90579711bjvrdef fixSubTableOverFlows(ttf, overflowRecord): 598823f8cd15f16bb9dc3991c2672f16dd90579711bjvr """ 599823f8cd15f16bb9dc3991c2672f16dd90579711bjvr An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts. 600823f8cd15f16bb9dc3991c2672f16dd90579711bjvr """ 601823f8cd15f16bb9dc3991c2672f16dd90579711bjvr ok = 0 602823f8cd15f16bb9dc3991c2672f16dd90579711bjvr table = ttf[overflowRecord.tableType].table 603823f8cd15f16bb9dc3991c2672f16dd90579711bjvr lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex] 604823f8cd15f16bb9dc3991c2672f16dd90579711bjvr subIndex = overflowRecord.SubTableIndex 605823f8cd15f16bb9dc3991c2672f16dd90579711bjvr subtable = lookup.SubTable[subIndex] 606823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 607823f8cd15f16bb9dc3991c2672f16dd90579711bjvr if hasattr(subtable, 'ExtSubTable'): 608823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # We split the subtable of the Extension table, and add a new Extension table 609823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # to contain the new subtable. 610823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 611823f8cd15f16bb9dc3991c2672f16dd90579711bjvr subTableType = subtable.ExtensionLookupType 612823f8cd15f16bb9dc3991c2672f16dd90579711bjvr extSubTable = subtable 613823f8cd15f16bb9dc3991c2672f16dd90579711bjvr subtable = extSubTable.ExtSubTable 614823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newExtSubTableClass = lookupTypes[overflowRecord.tableType][lookup.LookupType] 615823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newExtSubTable = newExtSubTableClass() 616823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newExtSubTable.Format = extSubTable.Format 617823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newExtSubTable.ExtensionLookupType = extSubTable.ExtensionLookupType 618823f8cd15f16bb9dc3991c2672f16dd90579711bjvr lookup.SubTable.insert(subIndex + 1, newExtSubTable) 619823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 620823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] 621823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newSubTable = newSubTableClass() 622823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newExtSubTable.ExtSubTable = newSubTable 623823f8cd15f16bb9dc3991c2672f16dd90579711bjvr else: 624823f8cd15f16bb9dc3991c2672f16dd90579711bjvr subTableType = lookup.LookupType 625823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] 626823f8cd15f16bb9dc3991c2672f16dd90579711bjvr newSubTable = newSubTableClass() 627823f8cd15f16bb9dc3991c2672f16dd90579711bjvr lookup.SubTable.insert(subIndex + 1, newSubTable) 628823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 629823f8cd15f16bb9dc3991c2672f16dd90579711bjvr if hasattr(lookup, 'SubTableCount'): # may not be defined yet. 630823f8cd15f16bb9dc3991c2672f16dd90579711bjvr lookup.SubTableCount = lookup.SubTableCount + 1 631823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 632823f8cd15f16bb9dc3991c2672f16dd90579711bjvr try: 633823f8cd15f16bb9dc3991c2672f16dd90579711bjvr splitFunc = splitTable[overflowRecord.tableType][subTableType] 634823f8cd15f16bb9dc3991c2672f16dd90579711bjvr except KeyError: 635823f8cd15f16bb9dc3991c2672f16dd90579711bjvr return ok 636823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 637823f8cd15f16bb9dc3991c2672f16dd90579711bjvr ok = splitFunc(subtable, newSubTable, overflowRecord) 638823f8cd15f16bb9dc3991c2672f16dd90579711bjvr return ok 639823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 640e5b245fd074a2efa5fc8902cb7a11a5dcad0f3f5jvr# End of OverFlow logic 641823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 642823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 643d4d151390d1288f8d2df30f6dfa26a309c7334dajvrdef _buildClasses(): 644d4d151390d1288f8d2df30f6dfa26a309c7334dajvr import new, re 6452b06aaa2a6bcd363c25fb0c43f6bb906906594bdBehdad Esfahbod from .otData import otData 646d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 647d4d151390d1288f8d2df30f6dfa26a309c7334dajvr formatPat = re.compile("([A-Za-z0-9]+)Format(\d+)$") 648d4d151390d1288f8d2df30f6dfa26a309c7334dajvr namespace = globals() 649d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 650d4d151390d1288f8d2df30f6dfa26a309c7334dajvr # populate module with classes 651d4d151390d1288f8d2df30f6dfa26a309c7334dajvr for name, table in otData: 652d4d151390d1288f8d2df30f6dfa26a309c7334dajvr baseClass = BaseTable 653d4d151390d1288f8d2df30f6dfa26a309c7334dajvr m = formatPat.match(name) 654d4d151390d1288f8d2df30f6dfa26a309c7334dajvr if m: 655d4d151390d1288f8d2df30f6dfa26a309c7334dajvr # XxxFormatN subtable, we only add the "base" table 656d4d151390d1288f8d2df30f6dfa26a309c7334dajvr name = m.group(1) 657d4d151390d1288f8d2df30f6dfa26a309c7334dajvr baseClass = FormatSwitchingBaseTable 658bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if name not in namespace: 65964b5c80e80444a124da335e8d4d208bffcf2737bjvr # the class doesn't exist yet, so the base implementation is used. 660d4d151390d1288f8d2df30f6dfa26a309c7334dajvr cls = new.classobj(name, (baseClass,), {}) 661d4d151390d1288f8d2df30f6dfa26a309c7334dajvr namespace[name] = cls 662d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 66364b5c80e80444a124da335e8d4d208bffcf2737bjvr for base, alts in _equivalents.items(): 664d4d151390d1288f8d2df30f6dfa26a309c7334dajvr base = namespace[base] 665d4d151390d1288f8d2df30f6dfa26a309c7334dajvr for alt in alts: 666d4d151390d1288f8d2df30f6dfa26a309c7334dajvr namespace[alt] = new.classobj(alt, (base,), {}) 667d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 668d4d151390d1288f8d2df30f6dfa26a309c7334dajvr global lookupTypes 669d4d151390d1288f8d2df30f6dfa26a309c7334dajvr lookupTypes = { 670d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 'GSUB': { 671d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 1: SingleSubst, 672d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 2: MultipleSubst, 673d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 3: AlternateSubst, 674d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 4: LigatureSubst, 675d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 5: ContextSubst, 676d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 6: ChainContextSubst, 677d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 7: ExtensionSubst, 6788e1d75b1a3fb4874bb78d9c34a11ba30de81a5ccjvr 8: ReverseChainSingleSubst, 679d4d151390d1288f8d2df30f6dfa26a309c7334dajvr }, 680d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 'GPOS': { 681d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 1: SinglePos, 682d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 2: PairPos, 683d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 3: CursivePos, 684d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 4: MarkBasePos, 685d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 5: MarkLigPos, 686d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 6: MarkMarkPos, 687d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 7: ContextPos, 688d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 8: ChainContextPos, 689d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 9: ExtensionPos, 690d4d151390d1288f8d2df30f6dfa26a309c7334dajvr }, 691d4d151390d1288f8d2df30f6dfa26a309c7334dajvr } 692d4d151390d1288f8d2df30f6dfa26a309c7334dajvr lookupTypes['JSTF'] = lookupTypes['GPOS'] # JSTF contains GPOS 69364b5c80e80444a124da335e8d4d208bffcf2737bjvr for lookupEnum in lookupTypes.values(): 69464b5c80e80444a124da335e8d4d208bffcf2737bjvr for enum, cls in lookupEnum.items(): 69564b5c80e80444a124da335e8d4d208bffcf2737bjvr cls.LookupType = enum 6969e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod 6979e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod global featureParamTypes 6989e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod featureParamTypes = { 6999e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod 'size': FeatureParamsSize, 7009e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod } 7019e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod for i in range(1, 20+1): 7029e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod featureParamTypes['ss%02d' % i] = FeatureParamsStylisticSet 7039e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod for i in range(1, 99+1): 7049e1bd2d0b4416acc7fce623a3bae37064d61d15bBehdad Esfahbod featureParamTypes['cv%02d' % i] = FeatureParamsCharacterVariants 705d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 706d4d151390d1288f8d2df30f6dfa26a309c7334dajvr # add converters to classes 7072b06aaa2a6bcd363c25fb0c43f6bb906906594bdBehdad Esfahbod from .otConverters import buildConverters 708d4d151390d1288f8d2df30f6dfa26a309c7334dajvr for name, table in otData: 709d4d151390d1288f8d2df30f6dfa26a309c7334dajvr m = formatPat.match(name) 710d4d151390d1288f8d2df30f6dfa26a309c7334dajvr if m: 711d4d151390d1288f8d2df30f6dfa26a309c7334dajvr # XxxFormatN subtable, add converter to "base" table 712d4d151390d1288f8d2df30f6dfa26a309c7334dajvr name, format = m.groups() 713d4d151390d1288f8d2df30f6dfa26a309c7334dajvr format = int(format) 714d4d151390d1288f8d2df30f6dfa26a309c7334dajvr cls = namespace[name] 715d4d151390d1288f8d2df30f6dfa26a309c7334dajvr if not hasattr(cls, "converters"): 716d4d151390d1288f8d2df30f6dfa26a309c7334dajvr cls.converters = {} 717d4d151390d1288f8d2df30f6dfa26a309c7334dajvr cls.convertersByName = {} 71864b5c80e80444a124da335e8d4d208bffcf2737bjvr converters, convertersByName = buildConverters(table[1:], namespace) 719d4d151390d1288f8d2df30f6dfa26a309c7334dajvr cls.converters[format] = converters 720d4d151390d1288f8d2df30f6dfa26a309c7334dajvr cls.convertersByName[format] = convertersByName 721d4d151390d1288f8d2df30f6dfa26a309c7334dajvr else: 722d4d151390d1288f8d2df30f6dfa26a309c7334dajvr cls = namespace[name] 72364b5c80e80444a124da335e8d4d208bffcf2737bjvr cls.converters, cls.convertersByName = buildConverters(table, namespace) 724d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 725d4d151390d1288f8d2df30f6dfa26a309c7334dajvr 726d4d151390d1288f8d2df30f6dfa26a309c7334dajvr_buildClasses() 7271fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr 7281fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr 7291fae9b48d3a275c6e1dc0649c40ae121dfc4098djvrdef _getGlyphsFromCoverageTable(coverage): 7301fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr if coverage is None: 7311fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr # empty coverage table 7321fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr return [] 7331fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr else: 7341fae9b48d3a275c6e1dc0649c40ae121dfc4098djvr return coverage.glyphs 735