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