otTables.py revision 58acba2d0284ec63bf27c457fa261adf7774e996
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 16 17class FeatureParams(BaseTable): 18 """This class has been used by Adobe, but but this one implementation was done wrong. 19 No other use has been made, becuase there is no way to know how to interpret 20 the data at the offset.. For now, if we see one, just skip the data on 21 decompiling and dumping to XML. """ 22 # XXX The above is no longer true; the 'size' feature uses FeatureParams now. 23 def __init__(self): 24 BaseTable.__init__(self) 25 self.converters = [] 26 27class Coverage(FormatSwitchingBaseTable): 28 29 # manual implementation to get rid of glyphID dependencies 30 31 def postRead(self, rawTable, font): 32 if self.Format == 1: 33 self.glyphs = rawTable["GlyphArray"] 34 elif self.Format == 2: 35 glyphs = self.glyphs = [] 36 ranges = rawTable["RangeRecord"] 37 glyphOrder = font.getGlyphOrder() 38 # Some SIL fonts have coverage entries that don't have sorted 39 # StartCoverageIndex. If it is so, fixup and warn. We undo 40 # this when writing font out. 41 sorted_ranges = sorted(ranges, cmp=lambda a,b: cmp(a.StartCoverageIndex,b.StartCoverageIndex)) 42 if ranges != sorted_ranges: 43 warnings.warn("GSUB/GPOS Coverage is not sorted by glyph ids.") 44 ranges = sorted_ranges 45 del sorted_ranges 46 for r in ranges: 47 assert r.StartCoverageIndex == len(glyphs), \ 48 (r.StartCoverageIndex, len(glyphs)) 49 start = r.Start 50 end = r.End 51 try: 52 startID = font.getGlyphID(start, requireReal=1) 53 except KeyError: 54 warnings.warn("Coverage table has start glyph ID out of range: %s." % start) 55 continue 56 try: 57 endID = font.getGlyphID(end, requireReal=1) 58 except KeyError: 59 warnings.warn("Coverage table has end glyph ID out of range: %s." % end) 60 endID = len(glyphOrder) 61 glyphs.append(start) 62 rangeList = [glyphOrder[glyphID] for glyphID in range(startID + 1, endID) ] 63 glyphs += rangeList 64 if start != end and endID < len(glyphOrder): 65 glyphs.append(end) 66 else: 67 assert 0, "unknown format: %s" % self.Format 68 69 def preWrite(self, font): 70 glyphs = getattr(self, "glyphs", None) 71 if glyphs is None: 72 glyphs = self.glyphs = [] 73 format = 1 74 rawTable = {"GlyphArray": glyphs} 75 getGlyphID = font.getGlyphID 76 if glyphs: 77 # find out whether Format 2 is more compact or not 78 glyphIDs = [getGlyphID(glyphName) for glyphName in glyphs ] 79 brokenOrder = sorted(glyphIDs) != glyphIDs 80 81 last = glyphIDs[0] 82 ranges = [[last]] 83 for glyphID in glyphIDs[1:]: 84 if glyphID != last + 1: 85 ranges[-1].append(last) 86 ranges.append([glyphID]) 87 last = glyphID 88 ranges[-1].append(last) 89 90 if brokenOrder or len(ranges) * 3 < len(glyphs): # 3 words vs. 1 word 91 # Format 2 is more compact 92 index = 0 93 for i in range(len(ranges)): 94 start, end = ranges[i] 95 r = RangeRecord() 96 r.StartID = start 97 r.Start = font.getGlyphName(start) 98 r.End = font.getGlyphName(end) 99 r.StartCoverageIndex = index 100 ranges[i] = r 101 index = index + end - start + 1 102 if brokenOrder: 103 warnings.warn("GSUB/GPOS Coverage is not sorted by glyph ids.") 104 ranges.sort(cmp=lambda a,b: cmp(a.StartID,b.StartID)) 105 for r in ranges: 106 del r.StartID 107 format = 2 108 rawTable = {"RangeRecord": ranges} 109 #else: 110 # fallthrough; Format 1 is more compact 111 self.Format = format 112 return rawTable 113 114 def toXML2(self, xmlWriter, font): 115 for glyphName in getattr(self, "glyphs", []): 116 xmlWriter.simpletag("Glyph", value=glyphName) 117 xmlWriter.newline() 118 119 def fromXML(self, (name, attrs, content), font): 120 glyphs = getattr(self, "glyphs", None) 121 if glyphs is None: 122 glyphs = [] 123 self.glyphs = glyphs 124 glyphs.append(attrs["value"]) 125 126 127def doModulo(value): 128 if value < 0: 129 return value + 65536 130 return value 131 132class SingleSubst(FormatSwitchingBaseTable): 133 134 def postRead(self, rawTable, font): 135 mapping = {} 136 input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 137 lenMapping = len(input) 138 if self.Format == 1: 139 delta = rawTable["DeltaGlyphID"] 140 inputGIDS = [ font.getGlyphID(name) for name in input ] 141 inputGIDS = map(doModulo, inputGIDS) 142 outGIDS = [ glyphID + delta for glyphID in inputGIDS ] 143 outGIDS = map(doModulo, outGIDS) 144 outNames = [ font.getGlyphName(glyphID) for glyphID in outGIDS ] 145 map(operator.setitem, [mapping]*lenMapping, input, outNames) 146 elif self.Format == 2: 147 assert len(input) == rawTable["GlyphCount"], \ 148 "invalid SingleSubstFormat2 table" 149 subst = rawTable["Substitute"] 150 map(operator.setitem, [mapping]*lenMapping, input, subst) 151 else: 152 assert 0, "unknown format: %s" % self.Format 153 self.mapping = mapping 154 155 def preWrite(self, font): 156 mapping = getattr(self, "mapping", None) 157 if mapping is None: 158 mapping = self.mapping = {} 159 items = mapping.items() 160 getGlyphID = font.getGlyphID 161 gidItems = [(getGlyphID(item[0]), getGlyphID(item[1])) for item in items] 162 sortableItems = zip(gidItems, items) 163 sortableItems.sort() 164 165 # figure out format 166 format = 2 167 delta = None 168 for inID, outID in gidItems: 169 if delta is None: 170 delta = outID - inID 171 else: 172 if delta != outID - inID: 173 break 174 else: 175 format = 1 176 177 rawTable = {} 178 self.Format = format 179 cov = Coverage() 180 input = [ item [1][0] for item in sortableItems] 181 subst = [ item [1][1] for item in sortableItems] 182 cov.glyphs = input 183 rawTable["Coverage"] = cov 184 if format == 1: 185 assert delta is not None 186 rawTable["DeltaGlyphID"] = delta 187 else: 188 rawTable["Substitute"] = subst 189 return rawTable 190 191 def toXML2(self, xmlWriter, font): 192 items = self.mapping.items() 193 items.sort() 194 for inGlyph, outGlyph in items: 195 xmlWriter.simpletag("Substitution", 196 [("in", inGlyph), ("out", outGlyph)]) 197 xmlWriter.newline() 198 199 def fromXML(self, (name, attrs, content), font): 200 mapping = getattr(self, "mapping", None) 201 if mapping is None: 202 mapping = {} 203 self.mapping = mapping 204 mapping[attrs["in"]] = attrs["out"] 205 206 207class ClassDef(FormatSwitchingBaseTable): 208 209 def postRead(self, rawTable, font): 210 classDefs = {} 211 getGlyphName = font.getGlyphName 212 213 if self.Format == 1: 214 start = rawTable["StartGlyph"] 215 classList = rawTable["ClassValueArray"] 216 lenList = len(classList) 217 glyphID = font.getGlyphID(start) 218 gidList = range(glyphID, glyphID + len(classList)) 219 keyList = [getGlyphName(glyphID) for glyphID in gidList] 220 221 map(operator.setitem, [classDefs]*lenList, keyList, classList) 222 223 elif self.Format == 2: 224 records = rawTable["ClassRangeRecord"] 225 for rec in records: 226 start = rec.Start 227 end = rec.End 228 cls = rec.Class 229 classDefs[start] = cls 230 glyphIDs = range(font.getGlyphID(start) + 1, font.getGlyphID(end)) 231 lenList = len(glyphIDs) 232 keyList = [getGlyphName(glyphID) for glyphID in glyphIDs] 233 map(operator.setitem, [classDefs]*lenList, keyList, [cls]*lenList) 234 classDefs[end] = cls 235 else: 236 assert 0, "unknown format: %s" % self.Format 237 self.classDefs = classDefs 238 239 def preWrite(self, font): 240 classDefs = getattr(self, "classDefs", None) 241 if classDefs is None: 242 classDefs = self.classDefs = {} 243 items = classDefs.items() 244 getGlyphID = font.getGlyphID 245 for i in range(len(items)): 246 glyphName, cls = items[i] 247 items[i] = getGlyphID(glyphName), glyphName, cls 248 items.sort() 249 if items: 250 last, lastName, lastCls = items[0] 251 rec = ClassRangeRecord() 252 rec.Start = lastName 253 rec.Class = lastCls 254 ranges = [rec] 255 for glyphID, glyphName, cls in items[1:]: 256 if glyphID != last + 1 or cls != lastCls: 257 rec.End = lastName 258 rec = ClassRangeRecord() 259 rec.Start = glyphName 260 rec.Class = cls 261 ranges.append(rec) 262 last = glyphID 263 lastName = glyphName 264 lastCls = cls 265 rec.End = lastName 266 else: 267 ranges = [] 268 self.Format = 2 # currently no support for Format 1 269 return {"ClassRangeRecord": ranges} 270 271 def toXML2(self, xmlWriter, font): 272 items = self.classDefs.items() 273 items.sort() 274 for glyphName, cls in items: 275 xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)]) 276 xmlWriter.newline() 277 278 def fromXML(self, (name, attrs, content), font): 279 classDefs = getattr(self, "classDefs", None) 280 if classDefs is None: 281 classDefs = {} 282 self.classDefs = classDefs 283 classDefs[attrs["glyph"]] = int(attrs["class"]) 284 285 286class AlternateSubst(FormatSwitchingBaseTable): 287 288 def postRead(self, rawTable, font): 289 alternates = {} 290 if self.Format == 1: 291 input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 292 alts = rawTable["AlternateSet"] 293 if len(input) != len(alts): 294 assert len(input) == len(alts) 295 for i in range(len(input)): 296 alternates[input[i]] = alts[i].Alternate 297 else: 298 assert 0, "unknown format: %s" % self.Format 299 self.alternates = alternates 300 301 def preWrite(self, font): 302 self.Format = 1 303 alternates = getattr(self, "alternates", None) 304 if alternates is None: 305 alternates = self.alternates = {} 306 items = alternates.items() 307 for i in range(len(items)): 308 glyphName, set = items[i] 309 items[i] = font.getGlyphID(glyphName), glyphName, set 310 items.sort() 311 cov = Coverage() 312 cov.glyphs = [ item[1] for item in items] 313 alternates = [] 314 setList = [ item[-1] for item in items] 315 for set in setList: 316 alts = AlternateSet() 317 alts.Alternate = set 318 alternates.append(alts) 319 # a special case to deal with the fact that several hundred Adobe Japan1-5 320 # CJK fonts will overflow an offset if the coverage table isn't pushed to the end. 321 # Also useful in that when splitting a sub-table because of an offset overflow 322 # I don't need to calculate the change in the subtable offset due to the change in the coverage table size. 323 # Allows packing more rules in subtable. 324 self.sortCoverageLast = 1 325 return {"Coverage": cov, "AlternateSet": alternates} 326 327 def toXML2(self, xmlWriter, font): 328 items = self.alternates.items() 329 items.sort() 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 type(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 = self.ligatures.items() 396 items.sort() 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 type(element) != TupleType: 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 = oldSubTable.alternates.items() 523 oldAlts.sort() 524 oldLen = len(oldAlts) 525 526 if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']: 527 # Coverage table is written last. overflow is to or within the 528 # the coverage table. We will just cut the subtable in half. 529 newLen = int(oldLen/2) 530 531 elif overflowRecord.itemName == 'AlternateSet': 532 # We just need to back up by two items 533 # from the overflowed AlternateSet index to make sure the offset 534 # to the Coverage table doesn't overflow. 535 newLen = overflowRecord.itemIndex - 1 536 537 newSubTable.alternates = {} 538 for i in range(newLen, oldLen): 539 item = oldAlts[i] 540 key = item[0] 541 newSubTable.alternates[key] = item[1] 542 del oldSubTable.alternates[key] 543 544 545 return ok 546 547 548def splitLigatureSubst(oldSubTable, newSubTable, overflowRecord): 549 ok = 1 550 newSubTable.Format = oldSubTable.Format 551 oldLigs = oldSubTable.ligatures.items() 552 oldLigs.sort() 553 oldLen = len(oldLigs) 554 555 if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']: 556 # Coverage table is written last. overflow is to or within the 557 # the coverage table. We will just cut the subtable in half. 558 newLen = int(oldLen/2) 559 560 elif overflowRecord.itemName == 'LigatureSet': 561 # We just need to back up by two items 562 # from the overflowed AlternateSet index to make sure the offset 563 # to the Coverage table doesn't overflow. 564 newLen = overflowRecord.itemIndex - 1 565 566 newSubTable.ligatures = {} 567 for i in range(newLen, oldLen): 568 item = oldLigs[i] 569 key = item[0] 570 newSubTable.ligatures[key] = item[1] 571 del oldSubTable.ligatures[key] 572 573 return ok 574 575 576splitTable = { 'GSUB': { 577# 1: splitSingleSubst, 578# 2: splitMultipleSubst, 579 3: splitAlternateSubst, 580 4: splitLigatureSubst, 581# 5: splitContextSubst, 582# 6: splitChainContextSubst, 583# 7: splitExtensionSubst, 584# 8: splitReverseChainSingleSubst, 585 }, 586 'GPOS': { 587# 1: splitSinglePos, 588# 2: splitPairPos, 589# 3: splitCursivePos, 590# 4: splitMarkBasePos, 591# 5: splitMarkLigPos, 592# 6: splitMarkMarkPos, 593# 7: splitContextPos, 594# 8: splitChainContextPos, 595# 9: splitExtensionPos, 596 } 597 598 } 599 600def fixSubTableOverFlows(ttf, overflowRecord): 601 """ 602 An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts. 603 """ 604 ok = 0 605 table = ttf[overflowRecord.tableType].table 606 lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex] 607 subIndex = overflowRecord.SubTableIndex 608 subtable = lookup.SubTable[subIndex] 609 610 if hasattr(subtable, 'ExtSubTable'): 611 # We split the subtable of the Extension table, and add a new Extension table 612 # to contain the new subtable. 613 614 subTableType = subtable.ExtensionLookupType 615 extSubTable = subtable 616 subtable = extSubTable.ExtSubTable 617 newExtSubTableClass = lookupTypes[overflowRecord.tableType][lookup.LookupType] 618 newExtSubTable = newExtSubTableClass() 619 newExtSubTable.Format = extSubTable.Format 620 newExtSubTable.ExtensionLookupType = extSubTable.ExtensionLookupType 621 lookup.SubTable.insert(subIndex + 1, newExtSubTable) 622 623 newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] 624 newSubTable = newSubTableClass() 625 newExtSubTable.ExtSubTable = newSubTable 626 else: 627 subTableType = lookup.LookupType 628 newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] 629 newSubTable = newSubTableClass() 630 lookup.SubTable.insert(subIndex + 1, newSubTable) 631 632 if hasattr(lookup, 'SubTableCount'): # may not be defined yet. 633 lookup.SubTableCount = lookup.SubTableCount + 1 634 635 try: 636 splitFunc = splitTable[overflowRecord.tableType][subTableType] 637 except KeyError: 638 return ok 639 640 ok = splitFunc(subtable, newSubTable, overflowRecord) 641 return ok 642 643# End of OverFlow logic 644 645 646def _buildClasses(): 647 import new, re 648 from otData import otData 649 650 formatPat = re.compile("([A-Za-z0-9]+)Format(\d+)$") 651 namespace = globals() 652 653 # populate module with classes 654 for name, table in otData: 655 baseClass = BaseTable 656 m = formatPat.match(name) 657 if m: 658 # XxxFormatN subtable, we only add the "base" table 659 name = m.group(1) 660 baseClass = FormatSwitchingBaseTable 661 if not namespace.has_key(name): 662 # the class doesn't exist yet, so the base implementation is used. 663 cls = new.classobj(name, (baseClass,), {}) 664 namespace[name] = cls 665 666 for base, alts in _equivalents.items(): 667 base = namespace[base] 668 for alt in alts: 669 namespace[alt] = new.classobj(alt, (base,), {}) 670 671 global lookupTypes 672 lookupTypes = { 673 'GSUB': { 674 1: SingleSubst, 675 2: MultipleSubst, 676 3: AlternateSubst, 677 4: LigatureSubst, 678 5: ContextSubst, 679 6: ChainContextSubst, 680 7: ExtensionSubst, 681 8: ReverseChainSingleSubst, 682 }, 683 'GPOS': { 684 1: SinglePos, 685 2: PairPos, 686 3: CursivePos, 687 4: MarkBasePos, 688 5: MarkLigPos, 689 6: MarkMarkPos, 690 7: ContextPos, 691 8: ChainContextPos, 692 9: ExtensionPos, 693 }, 694 } 695 lookupTypes['JSTF'] = lookupTypes['GPOS'] # JSTF contains GPOS 696 for lookupEnum in lookupTypes.values(): 697 for enum, cls in lookupEnum.items(): 698 cls.LookupType = enum 699 700 # add converters to classes 701 from otConverters import buildConverters 702 for name, table in otData: 703 m = formatPat.match(name) 704 if m: 705 # XxxFormatN subtable, add converter to "base" table 706 name, format = m.groups() 707 format = int(format) 708 cls = namespace[name] 709 if not hasattr(cls, "converters"): 710 cls.converters = {} 711 cls.convertersByName = {} 712 converters, convertersByName = buildConverters(table[1:], namespace) 713 cls.converters[format] = converters 714 cls.convertersByName[format] = convertersByName 715 else: 716 cls = namespace[name] 717 cls.converters, cls.convertersByName = buildConverters(table, namespace) 718 719 720_buildClasses() 721 722 723def _getGlyphsFromCoverageTable(coverage): 724 if coverage is None: 725 # empty coverage table 726 return [] 727 else: 728 return coverage.glyphs 729