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