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