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