1from fontTools.misc.py23 import Tag, bytesjoin 2from .DefaultTable import DefaultTable 3import sys 4import array 5import struct 6import logging 7 8log = logging.getLogger(__name__) 9 10class OverflowErrorRecord(object): 11 def __init__(self, overflowTuple): 12 self.tableType = overflowTuple[0] 13 self.LookupListIndex = overflowTuple[1] 14 self.SubTableIndex = overflowTuple[2] 15 self.itemName = overflowTuple[3] 16 self.itemIndex = overflowTuple[4] 17 18 def __repr__(self): 19 return str((self.tableType, "LookupIndex:", self.LookupListIndex, "SubTableIndex:", self.SubTableIndex, "ItemName:", self.itemName, "ItemIndex:", self.itemIndex)) 20 21class OTLOffsetOverflowError(Exception): 22 def __init__(self, overflowErrorRecord): 23 self.value = overflowErrorRecord 24 25 def __str__(self): 26 return repr(self.value) 27 28 29class BaseTTXConverter(DefaultTable): 30 31 """Generic base class for TTX table converters. It functions as an 32 adapter between the TTX (ttLib actually) table model and the model 33 we use for OpenType tables, which is necessarily subtly different. 34 """ 35 36 def decompile(self, data, font): 37 from . import otTables 38 reader = OTTableReader(data, tableTag=self.tableTag) 39 tableClass = getattr(otTables, self.tableTag) 40 self.table = tableClass() 41 self.table.decompile(reader, font) 42 43 def compile(self, font): 44 """ Create a top-level OTTableWriter for the GPOS/GSUB table. 45 Call the compile method for the the table 46 for each 'converter' record in the table converter list 47 call converter's write method for each item in the value. 48 - For simple items, the write method adds a string to the 49 writer's self.items list. 50 - For Struct/Table/Subtable items, it add first adds new writer to the 51 to the writer's self.items, then calls the item's compile method. 52 This creates a tree of writers, rooted at the GUSB/GPOS writer, with 53 each writer representing a table, and the writer.items list containing 54 the child data strings and writers. 55 call the getAllData method 56 call _doneWriting, which removes duplicates 57 call _gatherTables. This traverses the tables, adding unique occurences to a flat list of tables 58 Traverse the flat list of tables, calling getDataLength on each to update their position 59 Traverse the flat list of tables again, calling getData each get the data in the table, now that 60 pos's and offset are known. 61 62 If a lookup subtable overflows an offset, we have to start all over. 63 """ 64 overflowRecord = None 65 66 while True: 67 try: 68 writer = OTTableWriter(tableTag=self.tableTag) 69 self.table.compile(writer, font) 70 return writer.getAllData() 71 72 except OTLOffsetOverflowError as e: 73 74 if overflowRecord == e.value: 75 raise # Oh well... 76 77 overflowRecord = e.value 78 log.info("Attempting to fix OTLOffsetOverflowError %s", e) 79 lastItem = overflowRecord 80 81 ok = 0 82 if overflowRecord.itemName is None: 83 from .otTables import fixLookupOverFlows 84 ok = fixLookupOverFlows(font, overflowRecord) 85 else: 86 from .otTables import fixSubTableOverFlows 87 ok = fixSubTableOverFlows(font, overflowRecord) 88 if not ok: 89 # Try upgrading lookup to Extension and hope 90 # that cross-lookup sharing not happening would 91 # fix overflow... 92 from .otTables import fixLookupOverFlows 93 ok = fixLookupOverFlows(font, overflowRecord) 94 if not ok: 95 raise 96 97 def toXML(self, writer, font): 98 self.table.toXML2(writer, font) 99 100 def fromXML(self, name, attrs, content, font): 101 from . import otTables 102 if not hasattr(self, "table"): 103 tableClass = getattr(otTables, self.tableTag) 104 self.table = tableClass() 105 self.table.fromXML(name, attrs, content, font) 106 self.table.populateDefaults() 107 108 109class OTTableReader(object): 110 111 """Helper class to retrieve data from an OpenType table.""" 112 113 __slots__ = ('data', 'offset', 'pos', 'localState', 'tableTag') 114 115 def __init__(self, data, localState=None, offset=0, tableTag=None): 116 self.data = data 117 self.offset = offset 118 self.pos = offset 119 self.localState = localState 120 self.tableTag = tableTag 121 122 def advance(self, count): 123 self.pos += count 124 125 def seek(self, pos): 126 self.pos = pos 127 128 def copy(self): 129 other = self.__class__(self.data, self.localState, self.offset, self.tableTag) 130 other.pos = self.pos 131 return other 132 133 def getSubReader(self, offset): 134 offset = self.offset + offset 135 return self.__class__(self.data, self.localState, offset, self.tableTag) 136 137 def readValue(self, typecode, staticSize): 138 pos = self.pos 139 newpos = pos + staticSize 140 value, = struct.unpack(f">{typecode}", self.data[pos:newpos]) 141 self.pos = newpos 142 return value 143 144 def readUShort(self): 145 return self.readValue("H", staticSize=2) 146 147 def readArray(self, typecode, staticSize, count): 148 pos = self.pos 149 newpos = pos + count * staticSize 150 value = array.array(typecode, self.data[pos:newpos]) 151 if sys.byteorder != "big": value.byteswap() 152 self.pos = newpos 153 return value 154 155 def readUShortArray(self, count): 156 return self.readArray("H", staticSize=2, count=count) 157 158 def readInt8(self): 159 return self.readValue("b", staticSize=1) 160 161 def readShort(self): 162 return self.readValue("h", staticSize=2) 163 164 def readLong(self): 165 return self.readValue("l", staticSize=4) 166 167 def readUInt8(self): 168 return self.readValue("B", staticSize=1) 169 170 def readUInt24(self): 171 pos = self.pos 172 newpos = pos + 3 173 value, = struct.unpack(">l", b'\0'+self.data[pos:newpos]) 174 self.pos = newpos 175 return value 176 177 def readULong(self): 178 return self.readValue("L", staticSize=4) 179 180 def readTag(self): 181 pos = self.pos 182 newpos = pos + 4 183 value = Tag(self.data[pos:newpos]) 184 assert len(value) == 4, value 185 self.pos = newpos 186 return value 187 188 def readData(self, count): 189 pos = self.pos 190 newpos = pos + count 191 value = self.data[pos:newpos] 192 self.pos = newpos 193 return value 194 195 def __setitem__(self, name, value): 196 state = self.localState.copy() if self.localState else dict() 197 state[name] = value 198 self.localState = state 199 200 def __getitem__(self, name): 201 return self.localState and self.localState[name] 202 203 def __contains__(self, name): 204 return self.localState and name in self.localState 205 206 207class OTTableWriter(object): 208 209 """Helper class to gather and assemble data for OpenType tables.""" 210 211 def __init__(self, localState=None, tableTag=None, offsetSize=2): 212 self.items = [] 213 self.pos = None 214 self.localState = localState 215 self.tableTag = tableTag 216 self.offsetSize = offsetSize 217 self.parent = None 218 219 # DEPRECATED: 'longOffset' is kept as a property for backward compat with old code. 220 # You should use 'offsetSize' instead (2, 3 or 4 bytes). 221 @property 222 def longOffset(self): 223 return self.offsetSize == 4 224 225 @longOffset.setter 226 def longOffset(self, value): 227 self.offsetSize = 4 if value else 2 228 229 def __setitem__(self, name, value): 230 state = self.localState.copy() if self.localState else dict() 231 state[name] = value 232 self.localState = state 233 234 def __getitem__(self, name): 235 return self.localState[name] 236 237 def __delitem__(self, name): 238 del self.localState[name] 239 240 # assembler interface 241 242 def getDataLength(self): 243 """Return the length of this table in bytes, without subtables.""" 244 l = 0 245 for item in self.items: 246 if hasattr(item, "getCountData"): 247 l += item.size 248 elif hasattr(item, "getData"): 249 l += item.offsetSize 250 else: 251 l = l + len(item) 252 return l 253 254 def getData(self): 255 """Assemble the data for this writer/table, without subtables.""" 256 items = list(self.items) # make a shallow copy 257 pos = self.pos 258 numItems = len(items) 259 for i in range(numItems): 260 item = items[i] 261 262 if hasattr(item, "getData"): 263 if item.offsetSize == 4: 264 items[i] = packULong(item.pos - pos) 265 elif item.offsetSize == 2: 266 try: 267 items[i] = packUShort(item.pos - pos) 268 except struct.error: 269 # provide data to fix overflow problem. 270 overflowErrorRecord = self.getOverflowErrorRecord(item) 271 272 raise OTLOffsetOverflowError(overflowErrorRecord) 273 elif item.offsetSize == 3: 274 items[i] = packUInt24(item.pos - pos) 275 else: 276 raise ValueError(item.offsetSize) 277 278 return bytesjoin(items) 279 280 def __hash__(self): 281 # only works after self._doneWriting() has been called 282 return hash(self.items) 283 284 def __ne__(self, other): 285 result = self.__eq__(other) 286 return result if result is NotImplemented else not result 287 288 def __eq__(self, other): 289 if type(self) != type(other): 290 return NotImplemented 291 return self.offsetSize == other.offsetSize and self.items == other.items 292 293 def _doneWriting(self, internedTables): 294 # Convert CountData references to data string items 295 # collapse duplicate table references to a unique entry 296 # "tables" are OTTableWriter objects. 297 298 # For Extension Lookup types, we can 299 # eliminate duplicates only within the tree under the Extension Lookup, 300 # as offsets may exceed 64K even between Extension LookupTable subtables. 301 isExtension = hasattr(self, "Extension") 302 303 # Certain versions of Uniscribe reject the font if the GSUB/GPOS top-level 304 # arrays (ScriptList, FeatureList, LookupList) point to the same, possibly 305 # empty, array. So, we don't share those. 306 # See: https://github.com/fonttools/fonttools/issues/518 307 dontShare = hasattr(self, 'DontShare') 308 309 if isExtension: 310 internedTables = {} 311 312 items = self.items 313 for i in range(len(items)): 314 item = items[i] 315 if hasattr(item, "getCountData"): 316 items[i] = item.getCountData() 317 elif hasattr(item, "getData"): 318 item._doneWriting(internedTables) 319 if not dontShare: 320 items[i] = item = internedTables.setdefault(item, item) 321 self.items = tuple(items) 322 323 def _gatherTables(self, tables, extTables, done): 324 # Convert table references in self.items tree to a flat 325 # list of tables in depth-first traversal order. 326 # "tables" are OTTableWriter objects. 327 # We do the traversal in reverse order at each level, in order to 328 # resolve duplicate references to be the last reference in the list of tables. 329 # For extension lookups, duplicate references can be merged only within the 330 # writer tree under the extension lookup. 331 332 done[id(self)] = True 333 334 numItems = len(self.items) 335 iRange = list(range(numItems)) 336 iRange.reverse() 337 338 isExtension = hasattr(self, "Extension") 339 340 selfTables = tables 341 342 if isExtension: 343 assert extTables is not None, "Program or XML editing error. Extension subtables cannot contain extensions subtables" 344 tables, extTables, done = extTables, None, {} 345 346 # add Coverage table if it is sorted last. 347 sortCoverageLast = 0 348 if hasattr(self, "sortCoverageLast"): 349 # Find coverage table 350 for i in range(numItems): 351 item = self.items[i] 352 if hasattr(item, "name") and (item.name == "Coverage"): 353 sortCoverageLast = 1 354 break 355 if id(item) not in done: 356 item._gatherTables(tables, extTables, done) 357 else: 358 # We're a new parent of item 359 pass 360 361 for i in iRange: 362 item = self.items[i] 363 if not hasattr(item, "getData"): 364 continue 365 366 if sortCoverageLast and (i==1) and item.name == 'Coverage': 367 # we've already 'gathered' it above 368 continue 369 370 if id(item) not in done: 371 item._gatherTables(tables, extTables, done) 372 else: 373 # Item is already written out by other parent 374 pass 375 376 selfTables.append(self) 377 378 def getAllData(self): 379 """Assemble all data, including all subtables.""" 380 internedTables = {} 381 self._doneWriting(internedTables) 382 tables = [] 383 extTables = [] 384 done = {} 385 self._gatherTables(tables, extTables, done) 386 tables.reverse() 387 extTables.reverse() 388 # Gather all data in two passes: the absolute positions of all 389 # subtable are needed before the actual data can be assembled. 390 pos = 0 391 for table in tables: 392 table.pos = pos 393 pos = pos + table.getDataLength() 394 395 for table in extTables: 396 table.pos = pos 397 pos = pos + table.getDataLength() 398 399 data = [] 400 for table in tables: 401 tableData = table.getData() 402 data.append(tableData) 403 404 for table in extTables: 405 tableData = table.getData() 406 data.append(tableData) 407 408 return bytesjoin(data) 409 410 # interface for gathering data, as used by table.compile() 411 412 def getSubWriter(self, offsetSize=2): 413 subwriter = self.__class__(self.localState, self.tableTag, offsetSize=offsetSize) 414 subwriter.parent = self # because some subtables have idential values, we discard 415 # the duplicates under the getAllData method. Hence some 416 # subtable writers can have more than one parent writer. 417 # But we just care about first one right now. 418 return subwriter 419 420 def writeValue(self, typecode, value): 421 self.items.append(struct.pack(f">{typecode}", value)) 422 423 def writeUShort(self, value): 424 assert 0 <= value < 0x10000, value 425 self.items.append(struct.pack(">H", value)) 426 427 def writeShort(self, value): 428 assert -32768 <= value < 32768, value 429 self.items.append(struct.pack(">h", value)) 430 431 def writeUInt8(self, value): 432 assert 0 <= value < 256, value 433 self.items.append(struct.pack(">B", value)) 434 435 def writeInt8(self, value): 436 assert -128 <= value < 128, value 437 self.items.append(struct.pack(">b", value)) 438 439 def writeUInt24(self, value): 440 assert 0 <= value < 0x1000000, value 441 b = struct.pack(">L", value) 442 self.items.append(b[1:]) 443 444 def writeLong(self, value): 445 self.items.append(struct.pack(">l", value)) 446 447 def writeULong(self, value): 448 self.items.append(struct.pack(">L", value)) 449 450 def writeTag(self, tag): 451 tag = Tag(tag).tobytes() 452 assert len(tag) == 4, tag 453 self.items.append(tag) 454 455 def writeSubTable(self, subWriter): 456 self.items.append(subWriter) 457 458 def writeCountReference(self, table, name, size=2, value=None): 459 ref = CountReference(table, name, size=size, value=value) 460 self.items.append(ref) 461 return ref 462 463 def writeStruct(self, format, values): 464 data = struct.pack(*(format,) + values) 465 self.items.append(data) 466 467 def writeData(self, data): 468 self.items.append(data) 469 470 def getOverflowErrorRecord(self, item): 471 LookupListIndex = SubTableIndex = itemName = itemIndex = None 472 if self.name == 'LookupList': 473 LookupListIndex = item.repeatIndex 474 elif self.name == 'Lookup': 475 LookupListIndex = self.repeatIndex 476 SubTableIndex = item.repeatIndex 477 else: 478 itemName = getattr(item, 'name', '<none>') 479 if hasattr(item, 'repeatIndex'): 480 itemIndex = item.repeatIndex 481 if self.name == 'SubTable': 482 LookupListIndex = self.parent.repeatIndex 483 SubTableIndex = self.repeatIndex 484 elif self.name == 'ExtSubTable': 485 LookupListIndex = self.parent.parent.repeatIndex 486 SubTableIndex = self.parent.repeatIndex 487 else: # who knows how far below the SubTable level we are! Climb back up to the nearest subtable. 488 itemName = ".".join([self.name, itemName]) 489 p1 = self.parent 490 while p1 and p1.name not in ['ExtSubTable', 'SubTable']: 491 itemName = ".".join([p1.name, itemName]) 492 p1 = p1.parent 493 if p1: 494 if p1.name == 'ExtSubTable': 495 LookupListIndex = p1.parent.parent.repeatIndex 496 SubTableIndex = p1.parent.repeatIndex 497 else: 498 LookupListIndex = p1.parent.repeatIndex 499 SubTableIndex = p1.repeatIndex 500 501 return OverflowErrorRecord( (self.tableTag, LookupListIndex, SubTableIndex, itemName, itemIndex) ) 502 503 504class CountReference(object): 505 """A reference to a Count value, not a count of references.""" 506 def __init__(self, table, name, size=None, value=None): 507 self.table = table 508 self.name = name 509 self.size = size 510 if value is not None: 511 self.setValue(value) 512 def setValue(self, value): 513 table = self.table 514 name = self.name 515 if table[name] is None: 516 table[name] = value 517 else: 518 assert table[name] == value, (name, table[name], value) 519 def getValue(self): 520 return self.table[self.name] 521 def getCountData(self): 522 v = self.table[self.name] 523 if v is None: v = 0 524 return {1:packUInt8, 2:packUShort, 4:packULong}[self.size](v) 525 526 527def packUInt8 (value): 528 return struct.pack(">B", value) 529 530def packUShort(value): 531 return struct.pack(">H", value) 532 533def packULong(value): 534 assert 0 <= value < 0x100000000, value 535 return struct.pack(">L", value) 536 537def packUInt24(value): 538 assert 0 <= value < 0x1000000, value 539 return struct.pack(">L", value)[1:] 540 541 542class BaseTable(object): 543 544 """Generic base class for all OpenType (sub)tables.""" 545 546 def __getattr__(self, attr): 547 reader = self.__dict__.get("reader") 548 if reader: 549 del self.reader 550 font = self.font 551 del self.font 552 self.decompile(reader, font) 553 return getattr(self, attr) 554 555 raise AttributeError(attr) 556 557 def ensureDecompiled(self): 558 reader = self.__dict__.get("reader") 559 if reader: 560 del self.reader 561 font = self.font 562 del self.font 563 self.decompile(reader, font) 564 565 @classmethod 566 def getRecordSize(cls, reader): 567 totalSize = 0 568 for conv in cls.converters: 569 size = conv.getRecordSize(reader) 570 if size is NotImplemented: return NotImplemented 571 countValue = 1 572 if conv.repeat: 573 if conv.repeat in reader: 574 countValue = reader[conv.repeat] 575 else: 576 return NotImplemented 577 totalSize += size * countValue 578 return totalSize 579 580 def getConverters(self): 581 return self.converters 582 583 def getConverterByName(self, name): 584 return self.convertersByName[name] 585 586 def populateDefaults(self, propagator=None): 587 for conv in self.getConverters(): 588 if conv.repeat: 589 if not hasattr(self, conv.name): 590 setattr(self, conv.name, []) 591 countValue = len(getattr(self, conv.name)) - conv.aux 592 try: 593 count_conv = self.getConverterByName(conv.repeat) 594 setattr(self, conv.repeat, countValue) 595 except KeyError: 596 # conv.repeat is a propagated count 597 if propagator and conv.repeat in propagator: 598 propagator[conv.repeat].setValue(countValue) 599 else: 600 if conv.aux and not eval(conv.aux, None, self.__dict__): 601 continue 602 if hasattr(self, conv.name): 603 continue # Warn if it should NOT be present?! 604 if hasattr(conv, 'writeNullOffset'): 605 setattr(self, conv.name, None) # Warn? 606 #elif not conv.isCount: 607 # # Warn? 608 # pass 609 610 def decompile(self, reader, font): 611 self.readFormat(reader) 612 table = {} 613 self.__rawTable = table # for debugging 614 for conv in self.getConverters(): 615 if conv.name == "SubTable": 616 conv = conv.getConverter(reader.tableTag, 617 table["LookupType"]) 618 if conv.name == "ExtSubTable": 619 conv = conv.getConverter(reader.tableTag, 620 table["ExtensionLookupType"]) 621 if conv.name == "FeatureParams": 622 conv = conv.getConverter(reader["FeatureTag"]) 623 if conv.name == "SubStruct": 624 conv = conv.getConverter(reader.tableTag, 625 table["MorphType"]) 626 try: 627 if conv.repeat: 628 if isinstance(conv.repeat, int): 629 countValue = conv.repeat 630 elif conv.repeat in table: 631 countValue = table[conv.repeat] 632 else: 633 # conv.repeat is a propagated count 634 countValue = reader[conv.repeat] 635 countValue += conv.aux 636 table[conv.name] = conv.readArray(reader, font, table, countValue) 637 else: 638 if conv.aux and not eval(conv.aux, None, table): 639 continue 640 table[conv.name] = conv.read(reader, font, table) 641 if conv.isPropagated: 642 reader[conv.name] = table[conv.name] 643 except Exception as e: 644 name = conv.name 645 e.args = e.args + (name,) 646 raise 647 648 if hasattr(self, 'postRead'): 649 self.postRead(table, font) 650 else: 651 self.__dict__.update(table) 652 653 del self.__rawTable # succeeded, get rid of debugging info 654 655 def compile(self, writer, font): 656 self.ensureDecompiled() 657 # TODO Following hack to be removed by rewriting how FormatSwitching tables 658 # are handled. 659 # https://github.com/fonttools/fonttools/pull/2238#issuecomment-805192631 660 if hasattr(self, 'preWrite'): 661 deleteFormat = not hasattr(self, 'Format') 662 table = self.preWrite(font) 663 deleteFormat = deleteFormat and hasattr(self, 'Format') 664 else: 665 deleteFormat = False 666 table = self.__dict__.copy() 667 668 # some count references may have been initialized in a custom preWrite; we set 669 # these in the writer's state beforehand (instead of sequentially) so they will 670 # be propagated to all nested subtables even if the count appears in the current 671 # table only *after* the offset to the subtable that it is counting. 672 for conv in self.getConverters(): 673 if conv.isCount and conv.isPropagated: 674 value = table.get(conv.name) 675 if isinstance(value, CountReference): 676 writer[conv.name] = value 677 678 if hasattr(self, 'sortCoverageLast'): 679 writer.sortCoverageLast = 1 680 681 if hasattr(self, 'DontShare'): 682 writer.DontShare = True 683 684 if hasattr(self.__class__, 'LookupType'): 685 writer['LookupType'].setValue(self.__class__.LookupType) 686 687 self.writeFormat(writer) 688 for conv in self.getConverters(): 689 value = table.get(conv.name) # TODO Handle defaults instead of defaulting to None! 690 if conv.repeat: 691 if value is None: 692 value = [] 693 countValue = len(value) - conv.aux 694 if isinstance(conv.repeat, int): 695 assert len(value) == conv.repeat, 'expected %d values, got %d' % (conv.repeat, len(value)) 696 elif conv.repeat in table: 697 CountReference(table, conv.repeat, value=countValue) 698 else: 699 # conv.repeat is a propagated count 700 writer[conv.repeat].setValue(countValue) 701 values = value 702 for i, value in enumerate(values): 703 try: 704 conv.write(writer, font, table, value, i) 705 except Exception as e: 706 name = value.__class__.__name__ if value is not None else conv.name 707 e.args = e.args + (name+'['+str(i)+']',) 708 raise 709 elif conv.isCount: 710 # Special-case Count values. 711 # Assumption: a Count field will *always* precede 712 # the actual array(s). 713 # We need a default value, as it may be set later by a nested 714 # table. We will later store it here. 715 # We add a reference: by the time the data is assembled 716 # the Count value will be filled in. 717 # We ignore the current count value since it will be recomputed, 718 # unless it's a CountReference that was already initialized in a custom preWrite. 719 if isinstance(value, CountReference): 720 ref = value 721 ref.size = conv.staticSize 722 writer.writeData(ref) 723 table[conv.name] = ref.getValue() 724 else: 725 ref = writer.writeCountReference(table, conv.name, conv.staticSize) 726 table[conv.name] = None 727 if conv.isPropagated: 728 writer[conv.name] = ref 729 elif conv.isLookupType: 730 # We make sure that subtables have the same lookup type, 731 # and that the type is the same as the one set on the 732 # Lookup object, if any is set. 733 if conv.name not in table: 734 table[conv.name] = None 735 ref = writer.writeCountReference(table, conv.name, conv.staticSize, table[conv.name]) 736 writer['LookupType'] = ref 737 else: 738 if conv.aux and not eval(conv.aux, None, table): 739 continue 740 try: 741 conv.write(writer, font, table, value) 742 except Exception as e: 743 name = value.__class__.__name__ if value is not None else conv.name 744 e.args = e.args + (name,) 745 raise 746 if conv.isPropagated: 747 writer[conv.name] = value 748 749 if deleteFormat: 750 del self.Format 751 752 def readFormat(self, reader): 753 pass 754 755 def writeFormat(self, writer): 756 pass 757 758 def toXML(self, xmlWriter, font, attrs=None, name=None): 759 tableName = name if name else self.__class__.__name__ 760 if attrs is None: 761 attrs = [] 762 if hasattr(self, "Format"): 763 attrs = attrs + [("Format", self.Format)] 764 xmlWriter.begintag(tableName, attrs) 765 xmlWriter.newline() 766 self.toXML2(xmlWriter, font) 767 xmlWriter.endtag(tableName) 768 xmlWriter.newline() 769 770 def toXML2(self, xmlWriter, font): 771 # Simpler variant of toXML, *only* for the top level tables (like GPOS, GSUB). 772 # This is because in TTX our parent writes our main tag, and in otBase.py we 773 # do it ourselves. I think I'm getting schizophrenic... 774 for conv in self.getConverters(): 775 if conv.repeat: 776 value = getattr(self, conv.name, []) 777 for i in range(len(value)): 778 item = value[i] 779 conv.xmlWrite(xmlWriter, font, item, conv.name, 780 [("index", i)]) 781 else: 782 if conv.aux and not eval(conv.aux, None, vars(self)): 783 continue 784 value = getattr(self, conv.name, None) # TODO Handle defaults instead of defaulting to None! 785 conv.xmlWrite(xmlWriter, font, value, conv.name, []) 786 787 def fromXML(self, name, attrs, content, font): 788 try: 789 conv = self.getConverterByName(name) 790 except KeyError: 791 raise # XXX on KeyError, raise nice error 792 value = conv.xmlRead(attrs, content, font) 793 if conv.repeat: 794 seq = getattr(self, conv.name, None) 795 if seq is None: 796 seq = [] 797 setattr(self, conv.name, seq) 798 seq.append(value) 799 else: 800 setattr(self, conv.name, value) 801 802 def __ne__(self, other): 803 result = self.__eq__(other) 804 return result if result is NotImplemented else not result 805 806 def __eq__(self, other): 807 if type(self) != type(other): 808 return NotImplemented 809 810 self.ensureDecompiled() 811 other.ensureDecompiled() 812 813 return self.__dict__ == other.__dict__ 814 815 816class FormatSwitchingBaseTable(BaseTable): 817 818 """Minor specialization of BaseTable, for tables that have multiple 819 formats, eg. CoverageFormat1 vs. CoverageFormat2.""" 820 821 @classmethod 822 def getRecordSize(cls, reader): 823 return NotImplemented 824 825 def getConverters(self): 826 return self.converters.get(self.Format, []) 827 828 def getConverterByName(self, name): 829 return self.convertersByName[self.Format][name] 830 831 def readFormat(self, reader): 832 self.Format = reader.readUShort() 833 834 def writeFormat(self, writer): 835 writer.writeUShort(self.Format) 836 837 def toXML(self, xmlWriter, font, attrs=None, name=None): 838 BaseTable.toXML(self, xmlWriter, font, attrs, name) 839 840 841class UInt8FormatSwitchingBaseTable(FormatSwitchingBaseTable): 842 def readFormat(self, reader): 843 self.Format = reader.readUInt8() 844 845 def writeFormat(self, writer): 846 writer.writeUInt8(self.Format) 847 848 849formatSwitchingBaseTables = { 850 "uint16": FormatSwitchingBaseTable, 851 "uint8": UInt8FormatSwitchingBaseTable, 852} 853 854def getFormatSwitchingBaseTableClass(formatType): 855 try: 856 return formatSwitchingBaseTables[formatType] 857 except KeyError: 858 raise TypeError(f"Unsupported format type: {formatType!r}") 859 860 861# 862# Support for ValueRecords 863# 864# This data type is so different from all other OpenType data types that 865# it requires quite a bit of code for itself. It even has special support 866# in OTTableReader and OTTableWriter... 867# 868 869valueRecordFormat = [ 870# Mask Name isDevice signed 871 (0x0001, "XPlacement", 0, 1), 872 (0x0002, "YPlacement", 0, 1), 873 (0x0004, "XAdvance", 0, 1), 874 (0x0008, "YAdvance", 0, 1), 875 (0x0010, "XPlaDevice", 1, 0), 876 (0x0020, "YPlaDevice", 1, 0), 877 (0x0040, "XAdvDevice", 1, 0), 878 (0x0080, "YAdvDevice", 1, 0), 879# reserved: 880 (0x0100, "Reserved1", 0, 0), 881 (0x0200, "Reserved2", 0, 0), 882 (0x0400, "Reserved3", 0, 0), 883 (0x0800, "Reserved4", 0, 0), 884 (0x1000, "Reserved5", 0, 0), 885 (0x2000, "Reserved6", 0, 0), 886 (0x4000, "Reserved7", 0, 0), 887 (0x8000, "Reserved8", 0, 0), 888] 889 890def _buildDict(): 891 d = {} 892 for mask, name, isDevice, signed in valueRecordFormat: 893 d[name] = mask, isDevice, signed 894 return d 895 896valueRecordFormatDict = _buildDict() 897 898 899class ValueRecordFactory(object): 900 901 """Given a format code, this object convert ValueRecords.""" 902 903 def __init__(self, valueFormat): 904 format = [] 905 for mask, name, isDevice, signed in valueRecordFormat: 906 if valueFormat & mask: 907 format.append((name, isDevice, signed)) 908 self.format = format 909 910 def __len__(self): 911 return len(self.format) 912 913 def readValueRecord(self, reader, font): 914 format = self.format 915 if not format: 916 return None 917 valueRecord = ValueRecord() 918 for name, isDevice, signed in format: 919 if signed: 920 value = reader.readShort() 921 else: 922 value = reader.readUShort() 923 if isDevice: 924 if value: 925 from . import otTables 926 subReader = reader.getSubReader(value) 927 value = getattr(otTables, name)() 928 value.decompile(subReader, font) 929 else: 930 value = None 931 setattr(valueRecord, name, value) 932 return valueRecord 933 934 def writeValueRecord(self, writer, font, valueRecord): 935 for name, isDevice, signed in self.format: 936 value = getattr(valueRecord, name, 0) 937 if isDevice: 938 if value: 939 subWriter = writer.getSubWriter() 940 writer.writeSubTable(subWriter) 941 value.compile(subWriter, font) 942 else: 943 writer.writeUShort(0) 944 elif signed: 945 writer.writeShort(value) 946 else: 947 writer.writeUShort(value) 948 949 950class ValueRecord(object): 951 952 # see ValueRecordFactory 953 954 def __init__(self, valueFormat=None, src=None): 955 if valueFormat is not None: 956 for mask, name, isDevice, signed in valueRecordFormat: 957 if valueFormat & mask: 958 setattr(self, name, None if isDevice else 0) 959 if src is not None: 960 for key,val in src.__dict__.items(): 961 if not hasattr(self, key): 962 continue 963 setattr(self, key, val) 964 elif src is not None: 965 self.__dict__ = src.__dict__.copy() 966 967 def getFormat(self): 968 format = 0 969 for name in self.__dict__.keys(): 970 format = format | valueRecordFormatDict[name][0] 971 return format 972 973 def toXML(self, xmlWriter, font, valueName, attrs=None): 974 if attrs is None: 975 simpleItems = [] 976 else: 977 simpleItems = list(attrs) 978 for mask, name, isDevice, format in valueRecordFormat[:4]: # "simple" values 979 if hasattr(self, name): 980 simpleItems.append((name, getattr(self, name))) 981 deviceItems = [] 982 for mask, name, isDevice, format in valueRecordFormat[4:8]: # device records 983 if hasattr(self, name): 984 device = getattr(self, name) 985 if device is not None: 986 deviceItems.append((name, device)) 987 if deviceItems: 988 xmlWriter.begintag(valueName, simpleItems) 989 xmlWriter.newline() 990 for name, deviceRecord in deviceItems: 991 if deviceRecord is not None: 992 deviceRecord.toXML(xmlWriter, font, name=name) 993 xmlWriter.endtag(valueName) 994 xmlWriter.newline() 995 else: 996 xmlWriter.simpletag(valueName, simpleItems) 997 xmlWriter.newline() 998 999 def fromXML(self, name, attrs, content, font): 1000 from . import otTables 1001 for k, v in attrs.items(): 1002 setattr(self, k, int(v)) 1003 for element in content: 1004 if not isinstance(element, tuple): 1005 continue 1006 name, attrs, content = element 1007 value = getattr(otTables, name)() 1008 for elem2 in content: 1009 if not isinstance(elem2, tuple): 1010 continue 1011 name2, attrs2, content2 = elem2 1012 value.fromXML(name2, attrs2, content2, font) 1013 setattr(self, name, value) 1014 1015 def __ne__(self, other): 1016 result = self.__eq__(other) 1017 return result if result is NotImplemented else not result 1018 1019 def __eq__(self, other): 1020 if type(self) != type(other): 1021 return NotImplemented 1022 return self.__dict__ == other.__dict__ 1023