otTables.py revision 63b257e677993295ee015e02b92ff438a5e91135
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, key=lambda a: a.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=True) 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=True) 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 glyphs.extend(glyphOrder[glyphID] for glyphID in range(startID + 1, endID)) 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(key=lambda a: a.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 = list(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 glyphOrder = font.getGlyphOrder() 214 215 if self.Format == 1: 216 start = rawTable["StartGlyph"] 217 classList = rawTable["ClassValueArray"] 218 try: 219 startID = font.getGlyphID(start, requireReal=True) 220 except KeyError: 221 warnings.warn("ClassDef table has start glyph ID out of range: %s." % start) 222 startID = len(glyphOrder) 223 endID = startID + len(classList) 224 if endID > len(glyphOrder): 225 warnings.warn("ClassDef table has entries for out of range glyph IDs: %s,%s." % (start, len(classList))) 226 endID = len(glyphOrder) 227 228 for glyphID, cls in zip(range(startID, endID), classList): 229 classDefs[glyphOrder[glyphID]] = cls 230 231 elif self.Format == 2: 232 records = rawTable["ClassRangeRecord"] 233 for rec in records: 234 start = rec.Start 235 end = rec.End 236 cls = rec.Class 237 try: 238 startID = font.getGlyphID(start, requireReal=True) 239 except KeyError: 240 warnings.warn("ClassDef table has start glyph ID out of range: %s." % start) 241 continue 242 try: 243 endID = font.getGlyphID(end, requireReal=True) 244 except KeyError: 245 warnings.warn("ClassDef table has end glyph ID out of range: %s." % end) 246 endID = len(glyphOrder) 247 for glyphID in range(startID, endID): 248 classDefs[glyphOrder[glyphID]] = cls 249 else: 250 assert 0, "unknown format: %s" % self.Format 251 self.classDefs = classDefs 252 253 def preWrite(self, font): 254 classDefs = getattr(self, "classDefs", None) 255 if classDefs is None: 256 classDefs = self.classDefs = {} 257 items = list(classDefs.items()) 258 format = 2 259 rawTable = {"ClassRangeRecord": []} 260 getGlyphID = font.getGlyphID 261 for i in range(len(items)): 262 glyphName, cls = items[i] 263 items[i] = getGlyphID(glyphName), glyphName, cls 264 items.sort() 265 if items: 266 last, lastName, lastCls = items[0] 267 ranges = [[lastCls, last, lastName]] 268 for glyphID, glyphName, cls in items[1:]: 269 if glyphID != last + 1 or cls != lastCls: 270 ranges[-1].extend([last, lastName]) 271 ranges.append([cls, glyphID, glyphName]) 272 last = glyphID 273 lastName = glyphName 274 lastCls = cls 275 ranges[-1].extend([last, lastName]) 276 277 startGlyph = ranges[0][1] 278 endGlyph = ranges[-1][3] 279 glyphCount = endGlyph - startGlyph + 1 280 if len(ranges) * 3 < glyphCount + 1: 281 # Format 2 is more compact 282 for i in range(len(ranges)): 283 cls, start, startName, end, endName = ranges[i] 284 rec = ClassRangeRecord() 285 rec.Start = startName 286 rec.End = endName 287 rec.Class = cls 288 ranges[i] = rec 289 format = 2 290 rawTable = {"ClassRangeRecord": ranges} 291 else: 292 # Format 1 is more compact 293 startGlyphName = ranges[0][2] 294 classes = [0] * glyphCount 295 for cls, start, startName, end, endName in ranges: 296 for g in range(start - startGlyph, end - startGlyph + 1): 297 classes[g] = cls 298 format = 1 299 rawTable = {"StartGlyph": startGlyphName, "ClassValueArray": classes} 300 self.Format = format 301 return rawTable 302 303 def toXML2(self, xmlWriter, font): 304 items = sorted(self.classDefs.items()) 305 for glyphName, cls in items: 306 xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)]) 307 xmlWriter.newline() 308 309 def fromXML(self, name, attrs, content, font): 310 classDefs = getattr(self, "classDefs", None) 311 if classDefs is None: 312 classDefs = {} 313 self.classDefs = classDefs 314 classDefs[attrs["glyph"]] = int(attrs["class"]) 315 316 317class AlternateSubst(FormatSwitchingBaseTable): 318 319 def postRead(self, rawTable, font): 320 alternates = {} 321 if self.Format == 1: 322 input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 323 alts = rawTable["AlternateSet"] 324 if len(input) != len(alts): 325 assert len(input) == len(alts) 326 for i in range(len(input)): 327 alternates[input[i]] = alts[i].Alternate 328 else: 329 assert 0, "unknown format: %s" % self.Format 330 self.alternates = alternates 331 332 def preWrite(self, font): 333 self.Format = 1 334 alternates = getattr(self, "alternates", None) 335 if alternates is None: 336 alternates = self.alternates = {} 337 items = list(alternates.items()) 338 for i in range(len(items)): 339 glyphName, set = items[i] 340 items[i] = font.getGlyphID(glyphName), glyphName, set 341 items.sort() 342 cov = Coverage() 343 cov.glyphs = [ item[1] for item in items] 344 alternates = [] 345 setList = [ item[-1] for item in items] 346 for set in setList: 347 alts = AlternateSet() 348 alts.Alternate = set 349 alternates.append(alts) 350 # a special case to deal with the fact that several hundred Adobe Japan1-5 351 # CJK fonts will overflow an offset if the coverage table isn't pushed to the end. 352 # Also useful in that when splitting a sub-table because of an offset overflow 353 # I don't need to calculate the change in the subtable offset due to the change in the coverage table size. 354 # Allows packing more rules in subtable. 355 self.sortCoverageLast = 1 356 return {"Coverage": cov, "AlternateSet": alternates} 357 358 def toXML2(self, xmlWriter, font): 359 items = sorted(self.alternates.items()) 360 for glyphName, alternates in items: 361 xmlWriter.begintag("AlternateSet", glyph=glyphName) 362 xmlWriter.newline() 363 for alt in alternates: 364 xmlWriter.simpletag("Alternate", glyph=alt) 365 xmlWriter.newline() 366 xmlWriter.endtag("AlternateSet") 367 xmlWriter.newline() 368 369 def fromXML(self, name, attrs, content, font): 370 alternates = getattr(self, "alternates", None) 371 if alternates is None: 372 alternates = {} 373 self.alternates = alternates 374 glyphName = attrs["glyph"] 375 set = [] 376 alternates[glyphName] = set 377 for element in content: 378 if not isinstance(element, tuple): 379 continue 380 name, attrs, content = element 381 set.append(attrs["glyph"]) 382 383 384class LigatureSubst(FormatSwitchingBaseTable): 385 386 def postRead(self, rawTable, font): 387 ligatures = {} 388 if self.Format == 1: 389 input = rawTable["Coverage"].glyphs 390 ligSets = rawTable["LigatureSet"] 391 assert len(input) == len(ligSets) 392 for i in range(len(input)): 393 ligatures[input[i]] = ligSets[i].Ligature 394 else: 395 assert 0, "unknown format: %s" % self.Format 396 self.ligatures = ligatures 397 398 def preWrite(self, font): 399 ligatures = getattr(self, "ligatures", None) 400 if ligatures is None: 401 ligatures = self.ligatures = {} 402 items = list(ligatures.items()) 403 for i in range(len(items)): 404 glyphName, set = items[i] 405 items[i] = font.getGlyphID(glyphName), glyphName, set 406 items.sort() 407 cov = Coverage() 408 cov.glyphs = [ item[1] for item in items] 409 410 ligSets = [] 411 setList = [ item[-1] for item in items ] 412 for set in setList: 413 ligSet = LigatureSet() 414 ligs = ligSet.Ligature = [] 415 for lig in set: 416 ligs.append(lig) 417 ligSets.append(ligSet) 418 # Useful in that when splitting a sub-table because of an offset overflow 419 # I don't need to calculate the change in subtabl offset due to the coverage table size. 420 # Allows packing more rules in subtable. 421 self.sortCoverageLast = 1 422 return {"Coverage": cov, "LigatureSet": ligSets} 423 424 def toXML2(self, xmlWriter, font): 425 items = sorted(self.ligatures.items()) 426 for glyphName, ligSets in items: 427 xmlWriter.begintag("LigatureSet", glyph=glyphName) 428 xmlWriter.newline() 429 for lig in ligSets: 430 xmlWriter.simpletag("Ligature", glyph=lig.LigGlyph, 431 components=",".join(lig.Component)) 432 xmlWriter.newline() 433 xmlWriter.endtag("LigatureSet") 434 xmlWriter.newline() 435 436 def fromXML(self, name, attrs, content, font): 437 ligatures = getattr(self, "ligatures", None) 438 if ligatures is None: 439 ligatures = {} 440 self.ligatures = ligatures 441 glyphName = attrs["glyph"] 442 ligs = [] 443 ligatures[glyphName] = ligs 444 for element in content: 445 if not isinstance(element, tuple): 446 continue 447 name, attrs, content = element 448 lig = Ligature() 449 lig.LigGlyph = attrs["glyph"] 450 lig.Component = attrs["components"].split(",") 451 ligs.append(lig) 452 453 454# 455# For each subtable format there is a class. However, we don't really distinguish 456# between "field name" and "format name": often these are the same. Yet there's 457# a whole bunch of fields with different names. The following dict is a mapping 458# from "format name" to "field name". _buildClasses() uses this to create a 459# subclass for each alternate field name. 460# 461_equivalents = { 462 'MarkArray': ("Mark1Array",), 463 'LangSys': ('DefaultLangSys',), 464 'Coverage': ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage', 465 'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage', 466 'LookAheadCoverage'), 467 'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef', 468 'LookAheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'), 469 'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor', 470 'Mark2Anchor', 'MarkAnchor'), 471 'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice', 472 'XDeviceTable', 'YDeviceTable', 'DeviceTable'), 473 'Axis': ('HorizAxis', 'VertAxis',), 474 'MinMax': ('DefaultMinMax',), 475 'BaseCoord': ('MinCoord', 'MaxCoord',), 476 'JstfLangSys': ('DefJstfLangSys',), 477 'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB', 478 'ExtensionDisableGSUB',), 479 'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS', 480 'ExtensionDisableGPOS',), 481 'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',), 482} 483 484# 485# OverFlow logic, to automatically create ExtensionLookups 486# XXX This should probably move to otBase.py 487# 488 489def fixLookupOverFlows(ttf, overflowRecord): 490 """ Either the offset from the LookupList to a lookup overflowed, or 491 an offset from a lookup to a subtable overflowed. 492 The table layout is: 493 GPSO/GUSB 494 Script List 495 Feature List 496 LookUpList 497 Lookup[0] and contents 498 SubTable offset list 499 SubTable[0] and contents 500 ... 501 SubTable[n] and contents 502 ... 503 Lookup[n] and contents 504 SubTable offset list 505 SubTable[0] and contents 506 ... 507 SubTable[n] and contents 508 If the offset to a lookup overflowed (SubTableIndex is None) 509 we must promote the *previous* lookup to an Extension type. 510 If the offset from a lookup to subtable overflowed, then we must promote it 511 to an Extension Lookup type. 512 """ 513 ok = 0 514 lookupIndex = overflowRecord.LookupListIndex 515 if (overflowRecord.SubTableIndex is None): 516 lookupIndex = lookupIndex - 1 517 if lookupIndex < 0: 518 return ok 519 if overflowRecord.tableType == 'GSUB': 520 extType = 7 521 elif overflowRecord.tableType == 'GPOS': 522 extType = 9 523 524 lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup 525 lookup = lookups[lookupIndex] 526 # If the previous lookup is an extType, look further back. Very unlikely, but possible. 527 while lookup.LookupType == extType: 528 lookupIndex = lookupIndex -1 529 if lookupIndex < 0: 530 return ok 531 lookup = lookups[lookupIndex] 532 533 for si in range(len(lookup.SubTable)): 534 subTable = lookup.SubTable[si] 535 extSubTableClass = lookupTypes[overflowRecord.tableType][extType] 536 extSubTable = extSubTableClass() 537 extSubTable.Format = 1 538 extSubTable.ExtensionLookupType = lookup.LookupType 539 extSubTable.ExtSubTable = subTable 540 lookup.SubTable[si] = extSubTable 541 lookup.LookupType = extType 542 ok = 1 543 return ok 544 545def splitAlternateSubst(oldSubTable, newSubTable, overflowRecord): 546 ok = 1 547 newSubTable.Format = oldSubTable.Format 548 if hasattr(oldSubTable, 'sortCoverageLast'): 549 newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast 550 551 oldAlts = sorted(oldSubTable.alternates.items()) 552 oldLen = len(oldAlts) 553 554 if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']: 555 # Coverage table is written last. overflow is to or within the 556 # the coverage table. We will just cut the subtable in half. 557 newLen = oldLen//2 558 559 elif overflowRecord.itemName == 'AlternateSet': 560 # We just need to back up by two items 561 # from the overflowed AlternateSet index to make sure the offset 562 # to the Coverage table doesn't overflow. 563 newLen = overflowRecord.itemIndex - 1 564 565 newSubTable.alternates = {} 566 for i in range(newLen, oldLen): 567 item = oldAlts[i] 568 key = item[0] 569 newSubTable.alternates[key] = item[1] 570 del oldSubTable.alternates[key] 571 572 573 return ok 574 575 576def splitLigatureSubst(oldSubTable, newSubTable, overflowRecord): 577 ok = 1 578 newSubTable.Format = oldSubTable.Format 579 oldLigs = sorted(oldSubTable.ligatures.items()) 580 oldLen = len(oldLigs) 581 582 if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']: 583 # Coverage table is written last. overflow is to or within the 584 # the coverage table. We will just cut the subtable in half. 585 newLen = oldLen//2 586 587 elif overflowRecord.itemName == 'LigatureSet': 588 # We just need to back up by two items 589 # from the overflowed AlternateSet index to make sure the offset 590 # to the Coverage table doesn't overflow. 591 newLen = overflowRecord.itemIndex - 1 592 593 newSubTable.ligatures = {} 594 for i in range(newLen, oldLen): 595 item = oldLigs[i] 596 key = item[0] 597 newSubTable.ligatures[key] = item[1] 598 del oldSubTable.ligatures[key] 599 600 return ok 601 602 603splitTable = { 'GSUB': { 604# 1: splitSingleSubst, 605# 2: splitMultipleSubst, 606 3: splitAlternateSubst, 607 4: splitLigatureSubst, 608# 5: splitContextSubst, 609# 6: splitChainContextSubst, 610# 7: splitExtensionSubst, 611# 8: splitReverseChainSingleSubst, 612 }, 613 'GPOS': { 614# 1: splitSinglePos, 615# 2: splitPairPos, 616# 3: splitCursivePos, 617# 4: splitMarkBasePos, 618# 5: splitMarkLigPos, 619# 6: splitMarkMarkPos, 620# 7: splitContextPos, 621# 8: splitChainContextPos, 622# 9: splitExtensionPos, 623 } 624 625 } 626 627def fixSubTableOverFlows(ttf, overflowRecord): 628 """ 629 An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts. 630 """ 631 ok = 0 632 table = ttf[overflowRecord.tableType].table 633 lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex] 634 subIndex = overflowRecord.SubTableIndex 635 subtable = lookup.SubTable[subIndex] 636 637 if hasattr(subtable, 'ExtSubTable'): 638 # We split the subtable of the Extension table, and add a new Extension table 639 # to contain the new subtable. 640 641 subTableType = subtable.ExtensionLookupType 642 extSubTable = subtable 643 subtable = extSubTable.ExtSubTable 644 newExtSubTableClass = lookupTypes[overflowRecord.tableType][lookup.LookupType] 645 newExtSubTable = newExtSubTableClass() 646 newExtSubTable.Format = extSubTable.Format 647 newExtSubTable.ExtensionLookupType = extSubTable.ExtensionLookupType 648 lookup.SubTable.insert(subIndex + 1, newExtSubTable) 649 650 newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] 651 newSubTable = newSubTableClass() 652 newExtSubTable.ExtSubTable = newSubTable 653 else: 654 subTableType = lookup.LookupType 655 newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] 656 newSubTable = newSubTableClass() 657 lookup.SubTable.insert(subIndex + 1, newSubTable) 658 659 if hasattr(lookup, 'SubTableCount'): # may not be defined yet. 660 lookup.SubTableCount = lookup.SubTableCount + 1 661 662 try: 663 splitFunc = splitTable[overflowRecord.tableType][subTableType] 664 except KeyError: 665 return ok 666 667 ok = splitFunc(subtable, newSubTable, overflowRecord) 668 return ok 669 670# End of OverFlow logic 671 672 673def _buildClasses(): 674 import re 675 from .otData import otData 676 677 formatPat = re.compile("([A-Za-z0-9]+)Format(\d+)$") 678 namespace = globals() 679 680 # populate module with classes 681 for name, table in otData: 682 baseClass = BaseTable 683 m = formatPat.match(name) 684 if m: 685 # XxxFormatN subtable, we only add the "base" table 686 name = m.group(1) 687 baseClass = FormatSwitchingBaseTable 688 if name not in namespace: 689 # the class doesn't exist yet, so the base implementation is used. 690 cls = type(name, (baseClass,), {}) 691 namespace[name] = cls 692 693 for base, alts in _equivalents.items(): 694 base = namespace[base] 695 for alt in alts: 696 namespace[alt] = type(alt, (base,), {}) 697 698 global lookupTypes 699 lookupTypes = { 700 'GSUB': { 701 1: SingleSubst, 702 2: MultipleSubst, 703 3: AlternateSubst, 704 4: LigatureSubst, 705 5: ContextSubst, 706 6: ChainContextSubst, 707 7: ExtensionSubst, 708 8: ReverseChainSingleSubst, 709 }, 710 'GPOS': { 711 1: SinglePos, 712 2: PairPos, 713 3: CursivePos, 714 4: MarkBasePos, 715 5: MarkLigPos, 716 6: MarkMarkPos, 717 7: ContextPos, 718 8: ChainContextPos, 719 9: ExtensionPos, 720 }, 721 } 722 lookupTypes['JSTF'] = lookupTypes['GPOS'] # JSTF contains GPOS 723 for lookupEnum in lookupTypes.values(): 724 for enum, cls in lookupEnum.items(): 725 cls.LookupType = enum 726 727 global featureParamTypes 728 featureParamTypes = { 729 'size': FeatureParamsSize, 730 } 731 for i in range(1, 20+1): 732 featureParamTypes['ss%02d' % i] = FeatureParamsStylisticSet 733 for i in range(1, 99+1): 734 featureParamTypes['cv%02d' % i] = FeatureParamsCharacterVariants 735 736 # add converters to classes 737 from .otConverters import buildConverters 738 for name, table in otData: 739 m = formatPat.match(name) 740 if m: 741 # XxxFormatN subtable, add converter to "base" table 742 name, format = m.groups() 743 format = int(format) 744 cls = namespace[name] 745 if not hasattr(cls, "converters"): 746 cls.converters = {} 747 cls.convertersByName = {} 748 converters, convertersByName = buildConverters(table[1:], namespace) 749 cls.converters[format] = converters 750 cls.convertersByName[format] = convertersByName 751 else: 752 cls = namespace[name] 753 cls.converters, cls.convertersByName = buildConverters(table, namespace) 754 755 756_buildClasses() 757 758 759def _getGlyphsFromCoverageTable(coverage): 760 if coverage is None: 761 # empty coverage table 762 return [] 763 else: 764 return coverage.glyphs 765