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