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