otTables.py revision 17700bf926645d7def1d4013b2e119d2be4ff904
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 10import warnings 11 12 13class LookupOrder(BaseTable): 14 """Dummy class; this table isn't defined, but is used, and is always NULL.""" 15 16 17class FeatureParams(BaseTable): 18 """This class has been used by Adobe, but but this one implementation was done wrong. 19 No other use has been made, becuase there is no way to know how to interpret 20 the data at the offset.. For now, if we see one, just skip the data on 21 decompiling and dumping to XML. """ 22 # XXX The above is no longer true; the 'size' feature uses FeatureParams now. 23 def __init__(self): 24 BaseTable.__init__(self) 25 self.converters = [] 26 27class Coverage(FormatSwitchingBaseTable): 28 29 # manual implementation to get rid of glyphID dependencies 30 31 def postRead(self, rawTable, font): 32 if self.Format == 1: 33 self.glyphs = rawTable["GlyphArray"] 34 elif self.Format == 2: 35 glyphs = self.glyphs = [] 36 ranges = rawTable["RangeRecord"] 37 glyphOrder = font.getGlyphOrder() 38 # Some SIL fonts have coverage entries that don't have sorted 39 # StartCoverageIndex. If it is so, fixup and warn. We undo 40 # this when writing font out. 41 sorted_ranges = sorted(ranges, cmp=lambda a,b: cmp(a.StartCoverageIndex,b.StartCoverageIndex)) 42 if ranges != sorted_ranges: 43 warnings.warn("GSUB/GPOS Coverage is not sorted by glyph ids.") 44 ranges = sorted_ranges 45 del sorted_ranges 46 for r in ranges: 47 assert r.StartCoverageIndex == len(glyphs), \ 48 (r.StartCoverageIndex, len(glyphs)) 49 start = r.Start 50 end = r.End 51 try: 52 startID = font.getGlyphID(start, requireReal=1) 53 except KeyError: 54 warnings.warn("Coverage table has start glyph ID out of range: %s." % start) 55 continue 56 try: 57 endID = font.getGlyphID(end, requireReal=1) 58 except KeyError: 59 warnings.warn("Coverage table has end glyph ID out of range: %s." % end) 60 endID = len(glyphOrder) 61 glyphs.append(start) 62 rangeList = [glyphOrder[glyphID] for glyphID in range(startID + 1, endID) ] 63 glyphs += rangeList 64 if start != end and endID < len(glyphOrder): 65 glyphs.append(end) 66 else: 67 assert 0, "unknown format: %s" % self.Format 68 69 def preWrite(self, font): 70 glyphs = getattr(self, "glyphs", None) 71 if glyphs is None: 72 glyphs = self.glyphs = [] 73 format = 1 74 rawTable = {"GlyphArray": glyphs} 75 getGlyphID = font.getGlyphID 76 if glyphs: 77 # find out whether Format 2 is more compact or not 78 glyphIDs = [getGlyphID(glyphName) for glyphName in glyphs ] 79 brokenOrder = sorted(glyphIDs) != glyphIDs 80 81 last = glyphIDs[0] 82 ranges = [[last]] 83 for glyphID in glyphIDs[1:]: 84 if glyphID != last + 1: 85 ranges[-1].append(last) 86 ranges.append([glyphID]) 87 last = glyphID 88 ranges[-1].append(last) 89 90 if brokenOrder or len(ranges) * 3 < len(glyphs): # 3 words vs. 1 word 91 # Format 2 is more compact 92 index = 0 93 for i in range(len(ranges)): 94 start, end = ranges[i] 95 r = RangeRecord() 96 r.StartID = start 97 r.Start = font.getGlyphName(start) 98 r.End = font.getGlyphName(end) 99 r.StartCoverageIndex = index 100 ranges[i] = r 101 index = index + end - start + 1 102 if brokenOrder: 103 warnings.warn("GSUB/GPOS Coverage is not sorted by glyph ids.") 104 ranges.sort(cmp=lambda a,b: cmp(a.StartID,b.StartID)) 105 for r in ranges: 106 del r.StartID 107 format = 2 108 rawTable = {"RangeRecord": ranges} 109 #else: 110 # fallthrough; Format 1 is more compact 111 self.Format = format 112 return rawTable 113 114 def toXML2(self, xmlWriter, font): 115 for glyphName in getattr(self, "glyphs", []): 116 xmlWriter.simpletag("Glyph", value=glyphName) 117 xmlWriter.newline() 118 119 def fromXML(self, (name, attrs, content), font): 120 glyphs = getattr(self, "glyphs", None) 121 if glyphs is None: 122 glyphs = [] 123 self.glyphs = glyphs 124 glyphs.append(attrs["value"]) 125 126 127class LookupList(BaseTable): 128 def preCompile(self): 129 """ This function is used to optimize writing out extension subtables. This is useful 130 when a font has been read in, modified, and we are now writing out a new version. If the 131 the extension subtables have not been touched (proof being that they have not been decompiled) 132 then we can write them out using the original data, and do not have to recompile them. This can save 133 20-30% of the compile time for fonts with large extension tables, such as Japanese Pro fonts.""" 134 135 if hasattr(self, 'LookupCount'): #not defined if loading from xml 136 lookupCount = self.LookupCount 137 else: 138 return # The optimization of not recompiling extension lookup subtables is not possible 139 # when reading from XML. 140 141 liRange = range(lookupCount) 142 extTables = [] 143 for li in liRange: 144 lookup = self.Lookup[li] 145 if hasattr(lookup, 'SubTableCount'): #not defined if loading from xml 146 subtableCount = lookup.SubTableCount 147 else: 148 subtableCount = len(lookup.SubTable) 149 siRange = range(subtableCount) 150 for si in siRange: 151 subtable = lookup.SubTable[si] 152 if hasattr(subtable, 'ExtSubTable'): 153 extTable = subtable.ExtSubTable 154 extTables.append([extTable.start, extTable] ) 155 156 # Since offsets in one subtable can and do point forward into later 157 # subtables, we can afford to simply copy data only for the last subtables 158 # which were not decompiled. So we start figuring out the 159 # data segments starting with the last subtTable, and work our way towards 160 # the first subtable, and then quit as soon as we see a subtable that was decompiled. 161 if extTables: 162 extTables.sort() 163 extTables.reverse() 164 lastTable = extTables[0][1] 165 if lastTable.compileStatus == 1: 166 lastTable.end = len(lastTable.reader.data) 167 lastTable.compileStatus = 3 168 for i in range(1, len(extTables)): 169 extTable = extTables[i][1] 170 if extTable.compileStatus != 1: 171 break 172 extTable.end = lastTable.start 173 extTable.compileStatus = 3 174 lastTable = extTable 175 176def doModulo(value): 177 if value < 0: 178 return value + 65536 179 return value 180 181class SingleSubst(FormatSwitchingBaseTable): 182 183 def postRead(self, rawTable, font): 184 mapping = {} 185 input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 186 lenMapping = len(input) 187 if self.Format == 1: 188 delta = rawTable["DeltaGlyphID"] 189 inputGIDS = [ font.getGlyphID(name) for name in input ] 190 inputGIDS = map(doModulo, inputGIDS) 191 outGIDS = [ glyphID + delta for glyphID in inputGIDS ] 192 outGIDS = map(doModulo, outGIDS) 193 outNames = [ font.getGlyphName(glyphID) for glyphID in outGIDS ] 194 map(operator.setitem, [mapping]*lenMapping, input, outNames) 195 elif self.Format == 2: 196 assert len(input) == rawTable["GlyphCount"], \ 197 "invalid SingleSubstFormat2 table" 198 subst = rawTable["Substitute"] 199 map(operator.setitem, [mapping]*lenMapping, input, subst) 200 else: 201 assert 0, "unknown format: %s" % self.Format 202 self.mapping = mapping 203 204 def preWrite(self, font): 205 mapping = getattr(self, "mapping", None) 206 if mapping is None: 207 mapping = self.mapping = {} 208 items = mapping.items() 209 getGlyphID = font.getGlyphID 210 gidItems = [(getGlyphID(item[0]), getGlyphID(item[1])) for item in items] 211 sortableItems = zip(gidItems, items) 212 sortableItems.sort() 213 214 # figure out format 215 format = 2 216 delta = None 217 for inID, outID in gidItems: 218 if delta is None: 219 delta = outID - inID 220 else: 221 if delta != outID - inID: 222 break 223 else: 224 format = 1 225 226 rawTable = {} 227 self.Format = format 228 cov = Coverage() 229 input = [ item [1][0] for item in sortableItems] 230 subst = [ item [1][1] for item in sortableItems] 231 cov.glyphs = input 232 rawTable["Coverage"] = cov 233 if format == 1: 234 assert delta is not None 235 rawTable["DeltaGlyphID"] = delta 236 else: 237 rawTable["Substitute"] = subst 238 return rawTable 239 240 def toXML2(self, xmlWriter, font): 241 items = self.mapping.items() 242 items.sort() 243 for inGlyph, outGlyph in items: 244 xmlWriter.simpletag("Substitution", 245 [("in", inGlyph), ("out", outGlyph)]) 246 xmlWriter.newline() 247 248 def fromXML(self, (name, attrs, content), font): 249 mapping = getattr(self, "mapping", None) 250 if mapping is None: 251 mapping = {} 252 self.mapping = mapping 253 mapping[attrs["in"]] = attrs["out"] 254 255 256class ClassDef(FormatSwitchingBaseTable): 257 258 def postRead(self, rawTable, font): 259 classDefs = {} 260 getGlyphName = font.getGlyphName 261 262 if self.Format == 1: 263 start = rawTable["StartGlyph"] 264 classList = rawTable["ClassValueArray"] 265 lenList = len(classList) 266 glyphID = font.getGlyphID(start) 267 gidList = range(glyphID, glyphID + len(classList)) 268 keyList = [getGlyphName(glyphID) for glyphID in gidList] 269 270 map(operator.setitem, [classDefs]*lenList, keyList, classList) 271 272 elif self.Format == 2: 273 records = rawTable["ClassRangeRecord"] 274 for rec in records: 275 start = rec.Start 276 end = rec.End 277 cls = rec.Class 278 classDefs[start] = cls 279 glyphIDs = range(font.getGlyphID(start) + 1, font.getGlyphID(end)) 280 lenList = len(glyphIDs) 281 keyList = [getGlyphName(glyphID) for glyphID in glyphIDs] 282 map(operator.setitem, [classDefs]*lenList, keyList, [cls]*lenList) 283 classDefs[end] = cls 284 else: 285 assert 0, "unknown format: %s" % self.Format 286 self.classDefs = classDefs 287 288 def preWrite(self, font): 289 classDefs = getattr(self, "classDefs", None) 290 if classDefs is None: 291 classDefs = self.classDefs = {} 292 items = classDefs.items() 293 getGlyphID = font.getGlyphID 294 for i in range(len(items)): 295 glyphName, cls = items[i] 296 items[i] = getGlyphID(glyphName), glyphName, cls 297 items.sort() 298 if items: 299 last, lastName, lastCls = items[0] 300 rec = ClassRangeRecord() 301 rec.Start = lastName 302 rec.Class = lastCls 303 ranges = [rec] 304 for glyphID, glyphName, cls in items[1:]: 305 if glyphID != last + 1 or cls != lastCls: 306 rec.End = lastName 307 rec = ClassRangeRecord() 308 rec.Start = glyphName 309 rec.Class = cls 310 ranges.append(rec) 311 last = glyphID 312 lastName = glyphName 313 lastCls = cls 314 rec.End = lastName 315 else: 316 ranges = [] 317 self.Format = 2 # currently no support for Format 1 318 return {"ClassRangeRecord": ranges} 319 320 def toXML2(self, xmlWriter, font): 321 items = self.classDefs.items() 322 items.sort() 323 for glyphName, cls in items: 324 xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)]) 325 xmlWriter.newline() 326 327 def fromXML(self, (name, attrs, content), font): 328 classDefs = getattr(self, "classDefs", None) 329 if classDefs is None: 330 classDefs = {} 331 self.classDefs = classDefs 332 classDefs[attrs["glyph"]] = int(attrs["class"]) 333 334 335class AlternateSubst(FormatSwitchingBaseTable): 336 337 def postRead(self, rawTable, font): 338 alternates = {} 339 if self.Format == 1: 340 input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 341 alts = rawTable["AlternateSet"] 342 if len(input) != len(alts): 343 assert len(input) == len(alts) 344 for i in range(len(input)): 345 alternates[input[i]] = alts[i].Alternate 346 else: 347 assert 0, "unknown format: %s" % self.Format 348 self.alternates = alternates 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 = 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 = self.alternates.items() 378 items.sort() 379 for glyphName, alternates in items: 380 xmlWriter.begintag("AlternateSet", glyph=glyphName) 381 xmlWriter.newline() 382 for alt in alternates: 383 xmlWriter.simpletag("Alternate", glyph=alt) 384 xmlWriter.newline() 385 xmlWriter.endtag("AlternateSet") 386 xmlWriter.newline() 387 388 def fromXML(self, (name, attrs, content), font): 389 alternates = getattr(self, "alternates", None) 390 if alternates is None: 391 alternates = {} 392 self.alternates = alternates 393 glyphName = attrs["glyph"] 394 set = [] 395 alternates[glyphName] = set 396 for element in content: 397 if type(element) != TupleType: 398 continue 399 name, attrs, content = element 400 set.append(attrs["glyph"]) 401 402 403class LigatureSubst(FormatSwitchingBaseTable): 404 405 def postRead(self, rawTable, font): 406 ligatures = {} 407 if self.Format == 1: 408 input = rawTable["Coverage"].glyphs 409 ligSets = rawTable["LigatureSet"] 410 assert len(input) == len(ligSets) 411 for i in range(len(input)): 412 ligatures[input[i]] = ligSets[i].Ligature 413 else: 414 assert 0, "unknown format: %s" % self.Format 415 self.ligatures = ligatures 416 417 def preWrite(self, font): 418 ligatures = getattr(self, "ligatures", None) 419 if ligatures is None: 420 ligatures = self.ligatures = {} 421 items = ligatures.items() 422 for i in range(len(items)): 423 glyphName, set = items[i] 424 items[i] = font.getGlyphID(glyphName), glyphName, set 425 items.sort() 426 cov = Coverage() 427 cov.glyphs = [ item[1] for item in items] 428 429 ligSets = [] 430 setList = [ item[-1] for item in items ] 431 for set in setList: 432 ligSet = LigatureSet() 433 ligs = ligSet.Ligature = [] 434 for lig in set: 435 ligs.append(lig) 436 ligSets.append(ligSet) 437 # Useful in that when splitting a sub-table because of an offset overflow 438 # I don't need to calculate the change in subtabl offset due to the coverage table size. 439 # Allows packing more rules in subtable. 440 self.sortCoverageLast = 1 441 return {"Coverage": cov, "LigatureSet": ligSets} 442 443 def toXML2(self, xmlWriter, font): 444 items = self.ligatures.items() 445 items.sort() 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 type(element) != TupleType: 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'), 487 'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef', 488 'LookAheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'), 489 'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor', 490 'Mark2Anchor', 'MarkAnchor'), 491 'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice', 492 'XDeviceTable', 'YDeviceTable', 'DeviceTable'), 493 'Axis': ('HorizAxis', 'VertAxis',), 494 'MinMax': ('DefaultMinMax',), 495 'BaseCoord': ('MinCoord', 'MaxCoord',), 496 'JstfLangSys': ('DefJstfLangSys',), 497 'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB', 498 'ExtensionDisableGSUB',), 499 'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS', 500 'ExtensionDisableGPOS',), 501 'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',), 502} 503 504# 505# OverFlow logic, to automatically create ExtensionLookups 506# XXX This should probably move to otBase.py 507# 508 509def fixLookupOverFlows(ttf, overflowRecord): 510 """ Either the offset from the LookupList to a lookup overflowed, or 511 an offset from a lookup to a subtable overflowed. 512 The table layout is: 513 GPSO/GUSB 514 Script List 515 Feature List 516 LookUpList 517 Lookup[0] and contents 518 SubTable offset list 519 SubTable[0] and contents 520 ... 521 SubTable[n] and contents 522 ... 523 Lookup[n] and contents 524 SubTable offset list 525 SubTable[0] and contents 526 ... 527 SubTable[n] and contents 528 If the offset to a lookup overflowed (SubTableIndex == None) 529 we must promote the *previous* lookup to an Extension type. 530 If the offset from a lookup to subtable overflowed, then we must promote it 531 to an Extension Lookup type. 532 """ 533 ok = 0 534 lookupIndex = overflowRecord.LookupListIndex 535 if (overflowRecord.SubTableIndex == None): 536 lookupIndex = lookupIndex - 1 537 if lookupIndex < 0: 538 return ok 539 if overflowRecord.tableType == 'GSUB': 540 extType = 7 541 elif overflowRecord.tableType == 'GPOS': 542 extType = 9 543 544 lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup 545 lookup = lookups[lookupIndex] 546 # If the previous lookup is an extType, look further back. Very unlikely, but possible. 547 while lookup.LookupType == extType: 548 lookupIndex = lookupIndex -1 549 if lookupIndex < 0: 550 return ok 551 lookup = lookups[lookupIndex] 552 553 for si in range(len(lookup.SubTable)): 554 subTable = lookup.SubTable[si] 555 extSubTableClass = lookupTypes[overflowRecord.tableType][extType] 556 extSubTable = extSubTableClass() 557 extSubTable.Format = 1 558 extSubTable.ExtensionLookupType = lookup.LookupType 559 extSubTable.ExtSubTable = subTable 560 lookup.SubTable[si] = extSubTable 561 lookup.LookupType = extType 562 ok = 1 563 return ok 564 565def splitAlternateSubst(oldSubTable, newSubTable, overflowRecord): 566 ok = 1 567 newSubTable.Format = oldSubTable.Format 568 if hasattr(oldSubTable, 'sortCoverageLast'): 569 newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast 570 571 oldAlts = oldSubTable.alternates.items() 572 oldAlts.sort() 573 oldLen = len(oldAlts) 574 575 if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']: 576 # Coverage table is written last. overflow is to or within the 577 # the coverage table. We will just cut the subtable in half. 578 newLen = int(oldLen/2) 579 580 elif overflowRecord.itemName == 'AlternateSet': 581 # We just need to back up by two items 582 # from the overflowed AlternateSet index to make sure the offset 583 # to the Coverage table doesn't overflow. 584 newLen = overflowRecord.itemIndex - 1 585 586 newSubTable.alternates = {} 587 for i in range(newLen, oldLen): 588 item = oldAlts[i] 589 key = item[0] 590 newSubTable.alternates[key] = item[1] 591 del oldSubTable.alternates[key] 592 593 594 return ok 595 596 597def splitLigatureSubst(oldSubTable, newSubTable, overflowRecord): 598 ok = 1 599 newSubTable.Format = oldSubTable.Format 600 oldLigs = oldSubTable.ligatures.items() 601 oldLigs.sort() 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 = int(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 625splitTable = { '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 649def 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.ExtensionLookupType 664 extSubTable = subtable 665 subtable = extSubTable.ExtSubTable 666 newExtSubTableClass = lookupTypes[overflowRecord.tableType][lookup.LookupType] 667 newExtSubTable = newExtSubTableClass() 668 newExtSubTable.Format = extSubTable.Format 669 newExtSubTable.ExtensionLookupType = extSubTable.ExtensionLookupType 670 lookup.SubTable.insert(subIndex + 1, newExtSubTable) 671 672 newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] 673 newSubTable = newSubTableClass() 674 newExtSubTable.ExtSubTable = newSubTable 675 else: 676 subTableType = lookup.LookupType 677 newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] 678 newSubTable = newSubTableClass() 679 lookup.SubTable.insert(subIndex + 1, newSubTable) 680 681 if hasattr(lookup, 'SubTableCount'): # may not be defined yet. 682 lookup.SubTableCount = lookup.SubTableCount + 1 683 684 try: 685 splitFunc = splitTable[overflowRecord.tableType][subTableType] 686 except KeyError: 687 return ok 688 689 ok = splitFunc(subtable, newSubTable, overflowRecord) 690 return ok 691 692# End of OverFlow logic 693 694 695def _buildClasses(): 696 import new, re 697 from otData import otData 698 699 formatPat = re.compile("([A-Za-z0-9]+)Format(\d+)$") 700 namespace = globals() 701 702 # populate module with classes 703 for name, table in otData: 704 baseClass = BaseTable 705 m = formatPat.match(name) 706 if m: 707 # XxxFormatN subtable, we only add the "base" table 708 name = m.group(1) 709 baseClass = FormatSwitchingBaseTable 710 if not namespace.has_key(name): 711 # the class doesn't exist yet, so the base implementation is used. 712 cls = new.classobj(name, (baseClass,), {}) 713 namespace[name] = cls 714 715 for base, alts in _equivalents.items(): 716 base = namespace[base] 717 for alt in alts: 718 namespace[alt] = new.classobj(alt, (base,), {}) 719 720 global lookupTypes 721 lookupTypes = { 722 'GSUB': { 723 1: SingleSubst, 724 2: MultipleSubst, 725 3: AlternateSubst, 726 4: LigatureSubst, 727 5: ContextSubst, 728 6: ChainContextSubst, 729 7: ExtensionSubst, 730 8: ReverseChainSingleSubst, 731 }, 732 'GPOS': { 733 1: SinglePos, 734 2: PairPos, 735 3: CursivePos, 736 4: MarkBasePos, 737 5: MarkLigPos, 738 6: MarkMarkPos, 739 7: ContextPos, 740 8: ChainContextPos, 741 9: ExtensionPos, 742 }, 743 } 744 lookupTypes['JSTF'] = lookupTypes['GPOS'] # JSTF contains GPOS 745 for lookupEnum in lookupTypes.values(): 746 for enum, cls in lookupEnum.items(): 747 cls.LookupType = enum 748 749 # add converters to classes 750 from otConverters import buildConverters 751 for name, table in otData: 752 m = formatPat.match(name) 753 if m: 754 # XxxFormatN subtable, add converter to "base" table 755 name, format = m.groups() 756 format = int(format) 757 cls = namespace[name] 758 if not hasattr(cls, "converters"): 759 cls.converters = {} 760 cls.convertersByName = {} 761 converters, convertersByName = buildConverters(table[1:], namespace) 762 cls.converters[format] = converters 763 cls.convertersByName[format] = convertersByName 764 else: 765 cls = namespace[name] 766 cls.converters, cls.convertersByName = buildConverters(table, namespace) 767 768 769_buildClasses() 770 771 772def _getGlyphsFromCoverageTable(coverage): 773 if coverage is None: 774 # empty coverage table 775 return [] 776 else: 777 return coverage.glyphs 778