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