otTables.py revision e5b245fd074a2efa5fc8902cb7a11a5dcad0f3f5
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""" 7import operator 8from otBase import BaseTable, FormatSwitchingBaseTable 9from types import TupleType 10 11 12class LookupOrder(BaseTable): 13 """Dummy class; this table isn't defined, but is used, and is always NULL.""" 14 15 16class FeatureParams(BaseTable): 17 """This class has been used by Adobe, but but this one implementation was done wrong. 18 No other use has been made, becuase there is no way to know how to interpret 19 the data at the offset.. For now, if we see one, just skip the data on 20 decompiling and dumping to XML. """ 21 # XXX The above is no longer true; the 'size' feature uses FeatureParams now. 22 def __init__(self): 23 BaseTable.__init__(self) 24 self.converters = [] 25 26class Coverage(FormatSwitchingBaseTable): 27 28 # manual implementation to get rid of glyphID dependencies 29 30 def postRead(self, rawTable, font): 31 if self.Format == 1: 32 self.glyphs = rawTable["GlyphArray"] 33 elif self.Format == 2: 34 glyphs = self.glyphs = [] 35 ranges = rawTable["RangeRecord"] 36 getGlyphName = font.getGlyphName 37 for r in ranges: 38 assert r.StartCoverageIndex == len(glyphs), \ 39 (r.StartCoverageIndex, len(glyphs)) 40 start = r.Start 41 end = r.End 42 startID = font.getGlyphID(start) 43 endID = font.getGlyphID(end) 44 glyphs.append(start) 45 rangeList = [getGlyphName(glyphID) for glyphID in range(startID + 1, endID) ] 46 glyphs += rangeList 47 if start != end: 48 glyphs.append(end) 49 else: 50 assert 0, "unknown format: %s" % self.Format 51 52 def preWrite(self, font): 53 glyphs = getattr(self, "glyphs", None) 54 if glyphs is None: 55 glyphs = self.glyphs = [] 56 format = 1 57 rawTable = {"GlyphArray": glyphs} 58 getGlyphID = font.getGlyphID 59 if glyphs: 60 # find out whether Format 2 is more compact or not 61 glyphIDs = [getGlyphID(glyphName) for glyphName in glyphs ] 62 63 last = glyphIDs[0] 64 ranges = [[last]] 65 for glyphID in glyphIDs[1:]: 66 if glyphID != last + 1: 67 ranges[-1].append(last) 68 ranges.append([glyphID]) 69 last = glyphID 70 ranges[-1].append(last) 71 72 if len(ranges) * 3 < len(glyphs): # 3 words vs. 1 word 73 # Format 2 is more compact 74 index = 0 75 for i in range(len(ranges)): 76 start, end = ranges[i] 77 r = RangeRecord() 78 r.Start = font.getGlyphName(start) 79 r.End = font.getGlyphName(end) 80 r.StartCoverageIndex = index 81 ranges[i] = r 82 index = index + end - start + 1 83 format = 2 84 rawTable = {"RangeRecord": ranges} 85 #else: 86 # fallthrough; Format 1 is more compact 87 self.Format = format 88 return rawTable 89 90 def toXML2(self, xmlWriter, font): 91 for glyphName in getattr(self, "glyphs", []): 92 xmlWriter.simpletag("Glyph", value=glyphName) 93 xmlWriter.newline() 94 95 def fromXML(self, (name, attrs, content), font): 96 glyphs = getattr(self, "glyphs", None) 97 if glyphs is None: 98 glyphs = [] 99 self.glyphs = glyphs 100 glyphs.append(attrs["value"]) 101 102 103class LookupList(BaseTable): 104 def preCompile(self): 105 """ This function is used to optimize writing out extension subtables. This is useful 106 when a font has been read in, modified, and we are now writing out a new version. If the 107 the extension subtables have not been touched (proof being that they have not been decompiled) 108 then we can write them out using the original data, and do not have to recompile them. This can save 109 20-30% of the compile time for fonts with large extension tables, such as Japanese Pro fonts.""" 110 111 if hasattr(self, 'LookupCount'): #not defined if loading from xml 112 lookupCount = self.LookupCount 113 else: 114 return # The optimization of not recompiling extension lookup subtables is not possible 115 # when reading from XML. 116 117 liRange = range(lookupCount) 118 extTables = [] 119 for li in liRange: 120 lookup = self.Lookup[li] 121 if hasattr(lookup, 'SubTableCount'): #not defined if loading from xml 122 subtableCount = lookup.SubTableCount 123 else: 124 subtableCount = len(lookup.SubTable) 125 siRange = range(subtableCount) 126 for si in siRange: 127 subtable = lookup.SubTable[si] 128 if hasattr(subtable, 'ExtSubTable'): 129 extTable = subtable.ExtSubTable 130 extTables.append([extTable.start, extTable] ) 131 132 # Since offsets in one subtable can and do point forward into later 133 # subtables, we can afford to simply copy data only for the last subtables 134 # which were not decompiled. So we start figuring out the 135 # data segments starting with the last subtTable, and work our way towards 136 # the first subtable, and then quit as soon as we see a subtable that was decompiled. 137 if extTables: 138 extTables.sort() 139 extTables.reverse() 140 lastTable = extTables[0][1] 141 if lastTable.compileStatus == 1: 142 lastTable.end = len(lastTable.reader.data) 143 lastTable.compileStatus = 3 144 for i in range(1, len(extTables)): 145 extTable = extTables[i][1] 146 if extTable.compileStatus != 1: 147 break 148 extTable.end = lastTable.start 149 extTable.compileStatus = 3 150 lastTable = extTable 151 152def doModulo(value): 153 if value < 0: 154 return value + 65536 155 return value 156 157class SingleSubst(FormatSwitchingBaseTable): 158 159 def postRead(self, rawTable, font): 160 mapping = {} 161 input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 162 lenMapping = len(input) 163 if self.Format == 1: 164 delta = rawTable["DeltaGlyphID"] 165 inputGIDS = [ font.getGlyphID(name) for name in input ] 166 inputGIDS = map(doModulo, inputGIDS) 167 outGIDS = [ glyphID + delta for glyphID in inputGIDS ] 168 outGIDS = map(doModulo, outGIDS) 169 outNames = [ font.getGlyphName(glyphID) for glyphID in outGIDS ] 170 map(operator.setitem, [mapping]*lenMapping, input, outNames) 171 elif self.Format == 2: 172 assert len(input) == rawTable["GlyphCount"], \ 173 "invalid SingleSubstFormat2 table" 174 subst = rawTable["Substitute"] 175 map(operator.setitem, [mapping]*lenMapping, input, subst) 176 else: 177 assert 0, "unknown format: %s" % self.Format 178 self.mapping = mapping 179 180 def preWrite(self, font): 181 mapping = getattr(self, "mapping", None) 182 if mapping is None: 183 mapping = self.mapping = {} 184 items = mapping.items() 185 getGlyphID = font.getGlyphID 186 gidItems = [(getGlyphID(item[0]), getGlyphID(item[1])) for item in items] 187 sortableItems = zip(gidItems, items) 188 sortableItems.sort() 189 190 # figure out format 191 format = 2 192 delta = None 193 for inID, outID in gidItems: 194 if delta is None: 195 delta = outID - inID 196 else: 197 if delta != outID - inID: 198 break 199 else: 200 format = 1 201 202 rawTable = {} 203 self.Format = format 204 cov = Coverage() 205 input = [ item [1][0] for item in sortableItems] 206 subst = [ item [1][1] for item in sortableItems] 207 cov.glyphs = input 208 rawTable["Coverage"] = cov 209 if format == 1: 210 assert delta is not None 211 rawTable["DeltaGlyphID"] = delta 212 else: 213 rawTable["Substitute"] = subst 214 return rawTable 215 216 def toXML2(self, xmlWriter, font): 217 items = self.mapping.items() 218 items.sort() 219 for inGlyph, outGlyph in items: 220 xmlWriter.simpletag("Substitution", 221 [("in", inGlyph), ("out", outGlyph)]) 222 xmlWriter.newline() 223 224 def fromXML(self, (name, attrs, content), font): 225 mapping = getattr(self, "mapping", None) 226 if mapping is None: 227 mapping = {} 228 self.mapping = mapping 229 mapping[attrs["in"]] = attrs["out"] 230 231 232class ClassDef(FormatSwitchingBaseTable): 233 234 def postRead(self, rawTable, font): 235 classDefs = {} 236 getGlyphName = font.getGlyphName 237 238 if self.Format == 1: 239 start = rawTable["StartGlyph"] 240 classList = rawTable["ClassValueArray"] 241 lenList = len(classList) 242 glyphID = font.getGlyphID(start) 243 gidList = range(glyphID, glyphID + len(classList)) 244 keyList = [getGlyphName(glyphID) for glyphID in gidList] 245 246 map(operator.setitem, [classDefs]*lenList, keyList, classList) 247 248 elif self.Format == 2: 249 records = rawTable["ClassRangeRecord"] 250 for rec in records: 251 start = rec.Start 252 end = rec.End 253 cls = rec.Class 254 classDefs[start] = cls 255 glyphIDs = range(font.getGlyphID(start) + 1, font.getGlyphID(end)) 256 lenList = len(glyphIDs) 257 keyList = [getGlyphName(glyphID) for glyphID in glyphIDs] 258 map(operator.setitem, [classDefs]*lenList, keyList, [cls]*lenList) 259 classDefs[end] = cls 260 else: 261 assert 0, "unknown format: %s" % self.Format 262 self.classDefs = classDefs 263 264 def preWrite(self, font): 265 classDefs = getattr(self, "classDefs", None) 266 if classDefs is None: 267 classDefs = self.classDefs = {} 268 items = classDefs.items() 269 getGlyphID = font.getGlyphID 270 for i in range(len(items)): 271 glyphName, cls = items[i] 272 items[i] = getGlyphID(glyphName), glyphName, cls 273 items.sort() 274 if items: 275 last, lastName, lastCls = items[0] 276 rec = ClassRangeRecord() 277 rec.Start = lastName 278 rec.Class = lastCls 279 ranges = [rec] 280 for glyphID, glyphName, cls in items[1:]: 281 if glyphID != last + 1 or cls != lastCls: 282 rec.End = lastName 283 rec = ClassRangeRecord() 284 rec.Start = glyphName 285 rec.Class = cls 286 ranges.append(rec) 287 last = glyphID 288 lastName = glyphName 289 lastCls = cls 290 rec.End = lastName 291 else: 292 ranges = [] 293 self.Format = 2 # currently no support for Format 1 294 return {"ClassRangeRecord": ranges} 295 296 def toXML2(self, xmlWriter, font): 297 items = self.classDefs.items() 298 items.sort() 299 for glyphName, cls in items: 300 xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)]) 301 xmlWriter.newline() 302 303 def fromXML(self, (name, attrs, content), font): 304 classDefs = getattr(self, "classDefs", None) 305 if classDefs is None: 306 classDefs = {} 307 self.classDefs = classDefs 308 classDefs[attrs["glyph"]] = int(attrs["class"]) 309 310 311class AlternateSubst(FormatSwitchingBaseTable): 312 313 def postRead(self, rawTable, font): 314 alternates = {} 315 if self.Format == 1: 316 input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 317 alts = rawTable["AlternateSet"] 318 if len(input) != len(alts): 319 assert len(input) == len(alts) 320 for i in range(len(input)): 321 alternates[input[i]] = alts[i].Alternate 322 else: 323 assert 0, "unknown format: %s" % self.Format 324 self.alternates = alternates 325 326 def preWrite(self, font): 327 self.Format = 1 328 alternates = getattr(self, "alternates", None) 329 if alternates is None: 330 alternates = self.alternates = {} 331 items = alternates.items() 332 for i in range(len(items)): 333 glyphName, set = items[i] 334 items[i] = font.getGlyphID(glyphName), glyphName, set 335 items.sort() 336 cov = Coverage() 337 cov.glyphs = [ item[1] for item in items] 338 alternates = [] 339 setList = [ item[-1] for item in items] 340 for set in setList: 341 alts = AlternateSet() 342 alts.Alternate = set 343 alternates.append(alts) 344 # a special case to deal with the fact that several hundred Adobe Japan1-5 345 # CJK fonts will overflow an offset if the coverage table isn't pushed to the end. 346 # Also useful in that when splitting a sub-table because of an offset overflow 347 # I don't need to calculate the change in the subtable offset due to the change in the coverage table size. 348 # Allows packing more rules in subtable. 349 self.sortCoverageLast = 1 350 return {"Coverage": cov, "AlternateSet": alternates} 351 352 def toXML2(self, xmlWriter, font): 353 items = self.alternates.items() 354 items.sort() 355 for glyphName, alternates in items: 356 xmlWriter.begintag("AlternateSet", glyph=glyphName) 357 xmlWriter.newline() 358 for alt in alternates: 359 xmlWriter.simpletag("Alternate", glyph=alt) 360 xmlWriter.newline() 361 xmlWriter.endtag("AlternateSet") 362 xmlWriter.newline() 363 364 def fromXML(self, (name, attrs, content), font): 365 alternates = getattr(self, "alternates", None) 366 if alternates is None: 367 alternates = {} 368 self.alternates = alternates 369 glyphName = attrs["glyph"] 370 set = [] 371 alternates[glyphName] = set 372 for element in content: 373 if type(element) != TupleType: 374 continue 375 name, attrs, content = element 376 set.append(attrs["glyph"]) 377 378 379class LigatureSubst(FormatSwitchingBaseTable): 380 381 def postRead(self, rawTable, font): 382 ligatures = {} 383 if self.Format == 1: 384 input = rawTable["Coverage"].glyphs 385 ligSets = rawTable["LigatureSet"] 386 assert len(input) == len(ligSets) 387 for i in range(len(input)): 388 ligatures[input[i]] = ligSets[i].Ligature 389 else: 390 assert 0, "unknown format: %s" % self.Format 391 self.ligatures = ligatures 392 393 def preWrite(self, font): 394 ligatures = getattr(self, "ligatures", None) 395 if ligatures is None: 396 ligatures = self.ligatures = {} 397 items = ligatures.items() 398 for i in range(len(items)): 399 glyphName, set = items[i] 400 items[i] = font.getGlyphID(glyphName), glyphName, set 401 items.sort() 402 cov = Coverage() 403 cov.glyphs = [ item[1] for item in items] 404 405 ligSets = [] 406 setList = [ item[-1] for item in items ] 407 for set in setList: 408 ligSet = LigatureSet() 409 ligs = ligSet.Ligature = [] 410 for lig in set: 411 ligs.append(lig) 412 ligSets.append(ligSet) 413 # Useful in that when splitting a sub-table because of an offset overflow 414 # I don't need to calculate the change in subtabl offset due to the coverage table size. 415 # Allows packing more rules in subtable. 416 self.sortCoverageLast = 1 417 return {"Coverage": cov, "LigatureSet": ligSets} 418 419 def toXML2(self, xmlWriter, font): 420 items = self.ligatures.items() 421 items.sort() 422 for glyphName, ligSets in items: 423 xmlWriter.begintag("LigatureSet", glyph=glyphName) 424 xmlWriter.newline() 425 for lig in ligSets: 426 xmlWriter.simpletag("Ligature", glyph=lig.LigGlyph, 427 components=",".join(lig.Component)) 428 xmlWriter.newline() 429 xmlWriter.endtag("LigatureSet") 430 xmlWriter.newline() 431 432 def fromXML(self, (name, attrs, content), font): 433 ligatures = getattr(self, "ligatures", None) 434 if ligatures is None: 435 ligatures = {} 436 self.ligatures = ligatures 437 glyphName = attrs["glyph"] 438 ligs = [] 439 ligatures[glyphName] = ligs 440 for element in content: 441 if type(element) != TupleType: 442 continue 443 name, attrs, content = element 444 lig = Ligature() 445 lig.LigGlyph = attrs["glyph"] 446 lig.Component = attrs["components"].split(",") 447 ligs.append(lig) 448 449 450# 451# For each subtable format there is a class. However, we don't really distinguish 452# between "field name" and "format name": often these are the same. Yet there's 453# a whole bunch of fields with different names. The following dict is a mapping 454# from "format name" to "field name". _buildClasses() uses this to create a 455# subclass for each alternate field name. 456# 457_equivalents = { 458 'MarkArray': ("Mark1Array",), 459 'LangSys': ('DefaultLangSys',), 460 'Coverage': ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage', 461 'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage', 462 'LookAheadCoverage'), 463 'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef', 464 'LookAheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'), 465 'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor', 466 'Mark2Anchor', 'MarkAnchor'), 467 'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice', 468 'XDeviceTable', 'YDeviceTable', 'DeviceTable'), 469 'Axis': ('HorizAxis', 'VertAxis',), 470 'MinMax': ('DefaultMinMax',), 471 'BaseCoord': ('MinCoord', 'MaxCoord',), 472 'JstfLangSys': ('DefJstfLangSys',), 473 'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB', 474 'ExtensionDisableGSUB',), 475 'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS', 476 'ExtensionDisableGPOS',), 477 'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',), 478} 479 480# 481# OverFlow logic, to automatically create ExtensionLookups 482# XXX This should probably move to otBase.py 483# 484 485def fixLookupOverFlows(ttf, overflowRecord): 486 """ Either the offset from the LookupList to a lookup overflowed, or 487 an offset from a lookup to a subtable overflowed. 488 The table layout is: 489 GPSO/GUSB 490 Script List 491 Feature List 492 LookUpList 493 Lookup[0] and contents 494 SubTable offset list 495 SubTable[0] and contents 496 ... 497 SubTable[n] and contents 498 ... 499 Lookup[n] and contents 500 SubTable offset list 501 SubTable[0] and contents 502 ... 503 SubTable[n] and contents 504 If the offset to a lookup overflowed (SubTableIndex == None) 505 we must promote the *previous* lookup to an Extension type. 506 If the offset from a lookup to subtable overflowed, then we must promote it 507 to an Extension Lookup type. 508 """ 509 ok = 0 510 lookupIndex = overflowRecord.LookupListIndex 511 if (overflowRecord.SubTableIndex == None): 512 lookupIndex = lookupIndex - 1 513 if lookupIndex < 0: 514 return ok 515 if overflowRecord.tableType == 'GSUB': 516 extType = 7 517 elif overflowRecord.tableType == 'GPOS': 518 extType = 9 519 520 lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup 521 lookup = lookups[lookupIndex] 522 # If the previous lookup is an extType, look further back. Very unlikely, but possible. 523 while lookup.LookupType == extType: 524 lookupIndex = lookupIndex -1 525 if lookupIndex < 0: 526 return ok 527 lookup = lookups[lookupIndex] 528 529 for si in range(len(lookup.SubTable)): 530 subTable = lookup.SubTable[si] 531 extSubTableClass = lookupTypes[overflowRecord.tableType][extType] 532 extSubTable = extSubTableClass() 533 extSubTable.Format = 1 534 extSubTable.ExtensionLookupType = lookup.LookupType 535 extSubTable.ExtSubTable = subTable 536 lookup.SubTable[si] = extSubTable 537 lookup.LookupType = extType 538 ok = 1 539 return ok 540 541def splitAlternateSubst(oldSubTable, newSubTable, overflowRecord): 542 ok = 1 543 newSubTable.Format = oldSubTable.Format 544 if hasattr(oldSubTable, 'sortCoverageLast'): 545 newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast 546 547 oldAlts = oldSubTable.alternates.items() 548 oldAlts.sort() 549 oldLen = len(oldAlts) 550 551 if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']: 552 # Coverage table is written last. overflow is to or within the 553 # the coverage table. We will just cut the subtable in half. 554 newLen = int(oldLen/2) 555 556 elif overflowRecord.itemName == 'AlternateSet': 557 # We just need to back up by two items 558 # from the overflowed AlternateSet index to make sure the offset 559 # to the Coverage table doesn't overflow. 560 newLen = overflowRecord.itemIndex - 1 561 562 newSubTable.alternates = {} 563 for i in range(newLen, oldLen): 564 item = oldAlts[i] 565 key = item[0] 566 newSubTable.alternates[key] = item[1] 567 del oldSubTable.alternates[key] 568 569 570 return ok 571 572 573def splitLigatureSubst(oldSubTable, newSubTable, overflowRecord): 574 ok = 1 575 newSubTable.Format = oldSubTable.Format 576 oldLigs = oldSubTable.ligatures.items() 577 oldLigs.sort() 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 = int(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 new, 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 not namespace.has_key(name): 687 # the class doesn't exist yet, so the base implementation is used. 688 cls = new.classobj(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] = new.classobj(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 # 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