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