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 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 readUShort(self): 138 pos = self.pos 139 newpos = pos + 2 140 value, = struct.unpack(">H", self.data[pos:newpos]) 141 self.pos = newpos 142 return value 143 144 def readUShortArray(self, count): 145 pos = self.pos 146 newpos = pos + count * 2 147 value = array.array("H", self.data[pos:newpos]) 148 if sys.byteorder != "big": value.byteswap() 149 self.pos = newpos 150 return value 151 152 def readInt8(self): 153 pos = self.pos 154 newpos = pos + 1 155 value, = struct.unpack(">b", self.data[pos:newpos]) 156 self.pos = newpos 157 return value 158 159 def readShort(self): 160 pos = self.pos 161 newpos = pos + 2 162 value, = struct.unpack(">h", self.data[pos:newpos]) 163 self.pos = newpos 164 return value 165 166 def readLong(self): 167 pos = self.pos 168 newpos = pos + 4 169 value, = struct.unpack(">l", self.data[pos:newpos]) 170 self.pos = newpos 171 return value 172 173 def readUInt8(self): 174 pos = self.pos 175 newpos = pos + 1 176 value, = struct.unpack(">B", self.data[pos:newpos]) 177 self.pos = newpos 178 return value 179 180 def readUInt24(self): 181 pos = self.pos 182 newpos = pos + 3 183 value, = struct.unpack(">l", b'\0'+self.data[pos:newpos]) 184 self.pos = newpos 185 return value 186 187 def readULong(self): 188 pos = self.pos 189 newpos = pos + 4 190 value, = struct.unpack(">L", self.data[pos:newpos]) 191 self.pos = newpos 192 return value 193 194 def readTag(self): 195 pos = self.pos 196 newpos = pos + 4 197 value = Tag(self.data[pos:newpos]) 198 assert len(value) == 4, value 199 self.pos = newpos 200 return value 201 202 def readData(self, count): 203 pos = self.pos 204 newpos = pos + count 205 value = self.data[pos:newpos] 206 self.pos = newpos 207 return value 208 209 def __setitem__(self, name, value): 210 state = self.localState.copy() if self.localState else dict() 211 state[name] = value 212 self.localState = state 213 214 def __getitem__(self, name): 215 return self.localState and self.localState[name] 216 217 def __contains__(self, name): 218 return self.localState and name in self.localState 219 220 221class OTTableWriter(object): 222 223 """Helper class to gather and assemble data for OpenType tables.""" 224 225 def __init__(self, localState=None, tableTag=None): 226 self.items = [] 227 self.pos = None 228 self.localState = localState 229 self.tableTag = tableTag 230 self.longOffset = False 231 self.parent = None 232 233 def __setitem__(self, name, value): 234 state = self.localState.copy() if self.localState else dict() 235 state[name] = value 236 self.localState = state 237 238 def __getitem__(self, name): 239 return self.localState[name] 240 241 def __delitem__(self, name): 242 del self.localState[name] 243 244 # assembler interface 245 246 def getDataLength(self): 247 """Return the length of this table in bytes, without subtables.""" 248 l = 0 249 for item in self.items: 250 if hasattr(item, "getCountData"): 251 l += item.size 252 elif hasattr(item, "getData"): 253 l += 4 if item.longOffset else 2 254 else: 255 l = l + len(item) 256 return l 257 258 def getData(self): 259 """Assemble the data for this writer/table, without subtables.""" 260 items = list(self.items) # make a shallow copy 261 pos = self.pos 262 numItems = len(items) 263 for i in range(numItems): 264 item = items[i] 265 266 if hasattr(item, "getData"): 267 if item.longOffset: 268 items[i] = packULong(item.pos - pos) 269 else: 270 try: 271 items[i] = packUShort(item.pos - pos) 272 except struct.error: 273 # provide data to fix overflow problem. 274 overflowErrorRecord = self.getOverflowErrorRecord(item) 275 276 raise OTLOffsetOverflowError(overflowErrorRecord) 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.longOffset == other.longOffset 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): 413 subwriter = self.__class__(self.localState, self.tableTag) 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 writeUShort(self, value): 421 assert 0 <= value < 0x10000, value 422 self.items.append(struct.pack(">H", value)) 423 424 def writeShort(self, value): 425 assert -32768 <= value < 32768, value 426 self.items.append(struct.pack(">h", value)) 427 428 def writeUInt8(self, value): 429 assert 0 <= value < 256, value 430 self.items.append(struct.pack(">B", value)) 431 432 def writeInt8(self, value): 433 assert -128 <= value < 128, value 434 self.items.append(struct.pack(">b", value)) 435 436 def writeUInt24(self, value): 437 assert 0 <= value < 0x1000000, value 438 b = struct.pack(">L", value) 439 self.items.append(b[1:]) 440 441 def writeLong(self, value): 442 self.items.append(struct.pack(">l", value)) 443 444 def writeULong(self, value): 445 self.items.append(struct.pack(">L", value)) 446 447 def writeTag(self, tag): 448 tag = Tag(tag).tobytes() 449 assert len(tag) == 4, tag 450 self.items.append(tag) 451 452 def writeSubTable(self, subWriter): 453 self.items.append(subWriter) 454 455 def writeCountReference(self, table, name, size=2, value=None): 456 ref = CountReference(table, name, size=size, value=value) 457 self.items.append(ref) 458 return ref 459 460 def writeStruct(self, format, values): 461 data = struct.pack(*(format,) + values) 462 self.items.append(data) 463 464 def writeData(self, data): 465 self.items.append(data) 466 467 def getOverflowErrorRecord(self, item): 468 LookupListIndex = SubTableIndex = itemName = itemIndex = None 469 if self.name == 'LookupList': 470 LookupListIndex = item.repeatIndex 471 elif self.name == 'Lookup': 472 LookupListIndex = self.repeatIndex 473 SubTableIndex = item.repeatIndex 474 else: 475 itemName = getattr(item, 'name', '<none>') 476 if hasattr(item, 'repeatIndex'): 477 itemIndex = item.repeatIndex 478 if self.name == 'SubTable': 479 LookupListIndex = self.parent.repeatIndex 480 SubTableIndex = self.repeatIndex 481 elif self.name == 'ExtSubTable': 482 LookupListIndex = self.parent.parent.repeatIndex 483 SubTableIndex = self.parent.repeatIndex 484 else: # who knows how far below the SubTable level we are! Climb back up to the nearest subtable. 485 itemName = ".".join([self.name, itemName]) 486 p1 = self.parent 487 while p1 and p1.name not in ['ExtSubTable', 'SubTable']: 488 itemName = ".".join([p1.name, itemName]) 489 p1 = p1.parent 490 if p1: 491 if p1.name == 'ExtSubTable': 492 LookupListIndex = p1.parent.parent.repeatIndex 493 SubTableIndex = p1.parent.repeatIndex 494 else: 495 LookupListIndex = p1.parent.repeatIndex 496 SubTableIndex = p1.repeatIndex 497 498 return OverflowErrorRecord( (self.tableTag, LookupListIndex, SubTableIndex, itemName, itemIndex) ) 499 500 501class CountReference(object): 502 """A reference to a Count value, not a count of references.""" 503 def __init__(self, table, name, size=None, value=None): 504 self.table = table 505 self.name = name 506 self.size = size 507 if value is not None: 508 self.setValue(value) 509 def setValue(self, value): 510 table = self.table 511 name = self.name 512 if table[name] is None: 513 table[name] = value 514 else: 515 assert table[name] == value, (name, table[name], value) 516 def getCountData(self): 517 v = self.table[self.name] 518 if v is None: v = 0 519 return {1:packUInt8, 2:packUShort, 4:packULong}[self.size](v) 520 521 522def packUInt8 (value): 523 return struct.pack(">B", value) 524 525def packUShort(value): 526 return struct.pack(">H", value) 527 528def packULong(value): 529 assert 0 <= value < 0x100000000, value 530 return struct.pack(">L", value) 531 532 533class BaseTable(object): 534 535 """Generic base class for all OpenType (sub)tables.""" 536 537 def __getattr__(self, attr): 538 reader = self.__dict__.get("reader") 539 if reader: 540 del self.reader 541 font = self.font 542 del self.font 543 self.decompile(reader, font) 544 return getattr(self, attr) 545 546 raise AttributeError(attr) 547 548 def ensureDecompiled(self): 549 reader = self.__dict__.get("reader") 550 if reader: 551 del self.reader 552 font = self.font 553 del self.font 554 self.decompile(reader, font) 555 556 @classmethod 557 def getRecordSize(cls, reader): 558 totalSize = 0 559 for conv in cls.converters: 560 size = conv.getRecordSize(reader) 561 if size is NotImplemented: return NotImplemented 562 countValue = 1 563 if conv.repeat: 564 if conv.repeat in reader: 565 countValue = reader[conv.repeat] 566 else: 567 return NotImplemented 568 totalSize += size * countValue 569 return totalSize 570 571 def getConverters(self): 572 return self.converters 573 574 def getConverterByName(self, name): 575 return self.convertersByName[name] 576 577 def populateDefaults(self, propagator=None): 578 for conv in self.getConverters(): 579 if conv.repeat: 580 if not hasattr(self, conv.name): 581 setattr(self, conv.name, []) 582 countValue = len(getattr(self, conv.name)) - conv.aux 583 try: 584 count_conv = self.getConverterByName(conv.repeat) 585 setattr(self, conv.repeat, countValue) 586 except KeyError: 587 # conv.repeat is a propagated count 588 if propagator and conv.repeat in propagator: 589 propagator[conv.repeat].setValue(countValue) 590 else: 591 if conv.aux and not eval(conv.aux, None, self.__dict__): 592 continue 593 if hasattr(self, conv.name): 594 continue # Warn if it should NOT be present?! 595 if hasattr(conv, 'writeNullOffset'): 596 setattr(self, conv.name, None) # Warn? 597 #elif not conv.isCount: 598 # # Warn? 599 # pass 600 601 def decompile(self, reader, font): 602 self.readFormat(reader) 603 table = {} 604 self.__rawTable = table # for debugging 605 for conv in self.getConverters(): 606 if conv.name == "SubTable": 607 conv = conv.getConverter(reader.tableTag, 608 table["LookupType"]) 609 if conv.name == "ExtSubTable": 610 conv = conv.getConverter(reader.tableTag, 611 table["ExtensionLookupType"]) 612 if conv.name == "FeatureParams": 613 conv = conv.getConverter(reader["FeatureTag"]) 614 if conv.name == "SubStruct": 615 conv = conv.getConverter(reader.tableTag, 616 table["MorphType"]) 617 try: 618 if conv.repeat: 619 if isinstance(conv.repeat, int): 620 countValue = conv.repeat 621 elif conv.repeat in table: 622 countValue = table[conv.repeat] 623 else: 624 # conv.repeat is a propagated count 625 countValue = reader[conv.repeat] 626 countValue += conv.aux 627 table[conv.name] = conv.readArray(reader, font, table, countValue) 628 else: 629 if conv.aux and not eval(conv.aux, None, table): 630 continue 631 table[conv.name] = conv.read(reader, font, table) 632 if conv.isPropagated: 633 reader[conv.name] = table[conv.name] 634 except Exception as e: 635 name = conv.name 636 e.args = e.args + (name,) 637 raise 638 639 if hasattr(self, 'postRead'): 640 self.postRead(table, font) 641 else: 642 self.__dict__.update(table) 643 644 del self.__rawTable # succeeded, get rid of debugging info 645 646 def compile(self, writer, font): 647 self.ensureDecompiled() 648 if hasattr(self, 'preWrite'): 649 table = self.preWrite(font) 650 else: 651 table = self.__dict__.copy() 652 653 654 if hasattr(self, 'sortCoverageLast'): 655 writer.sortCoverageLast = 1 656 657 if hasattr(self, 'DontShare'): 658 writer.DontShare = True 659 660 if hasattr(self.__class__, 'LookupType'): 661 writer['LookupType'].setValue(self.__class__.LookupType) 662 663 self.writeFormat(writer) 664 for conv in self.getConverters(): 665 value = table.get(conv.name) # TODO Handle defaults instead of defaulting to None! 666 if conv.repeat: 667 if value is None: 668 value = [] 669 countValue = len(value) - conv.aux 670 if isinstance(conv.repeat, int): 671 assert len(value) == conv.repeat, 'expected %d values, got %d' % (conv.repeat, len(value)) 672 elif conv.repeat in table: 673 CountReference(table, conv.repeat, value=countValue) 674 else: 675 # conv.repeat is a propagated count 676 writer[conv.repeat].setValue(countValue) 677 values = value 678 for i, value in enumerate(values): 679 try: 680 conv.write(writer, font, table, value, i) 681 except Exception as e: 682 name = value.__class__.__name__ if value is not None else conv.name 683 e.args = e.args + (name+'['+str(i)+']',) 684 raise 685 elif conv.isCount: 686 # Special-case Count values. 687 # Assumption: a Count field will *always* precede 688 # the actual array(s). 689 # We need a default value, as it may be set later by a nested 690 # table. We will later store it here. 691 # We add a reference: by the time the data is assembled 692 # the Count value will be filled in. 693 ref = writer.writeCountReference(table, conv.name, conv.staticSize) 694 table[conv.name] = None 695 if conv.isPropagated: 696 writer[conv.name] = ref 697 elif conv.isLookupType: 698 # We make sure that subtables have the same lookup type, 699 # and that the type is the same as the one set on the 700 # Lookup object, if any is set. 701 if conv.name not in table: 702 table[conv.name] = None 703 ref = writer.writeCountReference(table, conv.name, conv.staticSize, table[conv.name]) 704 writer['LookupType'] = ref 705 else: 706 if conv.aux and not eval(conv.aux, None, table): 707 continue 708 try: 709 conv.write(writer, font, table, value) 710 except Exception as e: 711 name = value.__class__.__name__ if value is not None else conv.name 712 e.args = e.args + (name,) 713 raise 714 if conv.isPropagated: 715 writer[conv.name] = value 716 717 def readFormat(self, reader): 718 pass 719 720 def writeFormat(self, writer): 721 pass 722 723 def toXML(self, xmlWriter, font, attrs=None, name=None): 724 tableName = name if name else self.__class__.__name__ 725 if attrs is None: 726 attrs = [] 727 if hasattr(self, "Format"): 728 attrs = attrs + [("Format", self.Format)] 729 xmlWriter.begintag(tableName, attrs) 730 xmlWriter.newline() 731 self.toXML2(xmlWriter, font) 732 xmlWriter.endtag(tableName) 733 xmlWriter.newline() 734 735 def toXML2(self, xmlWriter, font): 736 # Simpler variant of toXML, *only* for the top level tables (like GPOS, GSUB). 737 # This is because in TTX our parent writes our main tag, and in otBase.py we 738 # do it ourselves. I think I'm getting schizophrenic... 739 for conv in self.getConverters(): 740 if conv.repeat: 741 value = getattr(self, conv.name, []) 742 for i in range(len(value)): 743 item = value[i] 744 conv.xmlWrite(xmlWriter, font, item, conv.name, 745 [("index", i)]) 746 else: 747 if conv.aux and not eval(conv.aux, None, vars(self)): 748 continue 749 value = getattr(self, conv.name, None) # TODO Handle defaults instead of defaulting to None! 750 conv.xmlWrite(xmlWriter, font, value, conv.name, []) 751 752 def fromXML(self, name, attrs, content, font): 753 try: 754 conv = self.getConverterByName(name) 755 except KeyError: 756 raise # XXX on KeyError, raise nice error 757 value = conv.xmlRead(attrs, content, font) 758 if conv.repeat: 759 seq = getattr(self, conv.name, None) 760 if seq is None: 761 seq = [] 762 setattr(self, conv.name, seq) 763 seq.append(value) 764 else: 765 setattr(self, conv.name, value) 766 767 def __ne__(self, other): 768 result = self.__eq__(other) 769 return result if result is NotImplemented else not result 770 771 def __eq__(self, other): 772 if type(self) != type(other): 773 return NotImplemented 774 775 self.ensureDecompiled() 776 other.ensureDecompiled() 777 778 return self.__dict__ == other.__dict__ 779 780 781class FormatSwitchingBaseTable(BaseTable): 782 783 """Minor specialization of BaseTable, for tables that have multiple 784 formats, eg. CoverageFormat1 vs. CoverageFormat2.""" 785 786 @classmethod 787 def getRecordSize(cls, reader): 788 return NotImplemented 789 790 def getConverters(self): 791 return self.converters.get(self.Format, []) 792 793 def getConverterByName(self, name): 794 return self.convertersByName[self.Format][name] 795 796 def readFormat(self, reader): 797 self.Format = reader.readUShort() 798 799 def writeFormat(self, writer): 800 writer.writeUShort(self.Format) 801 802 def toXML(self, xmlWriter, font, attrs=None, name=None): 803 BaseTable.toXML(self, xmlWriter, font, attrs, name) 804 805 806# 807# Support for ValueRecords 808# 809# This data type is so different from all other OpenType data types that 810# it requires quite a bit of code for itself. It even has special support 811# in OTTableReader and OTTableWriter... 812# 813 814valueRecordFormat = [ 815# Mask Name isDevice signed 816 (0x0001, "XPlacement", 0, 1), 817 (0x0002, "YPlacement", 0, 1), 818 (0x0004, "XAdvance", 0, 1), 819 (0x0008, "YAdvance", 0, 1), 820 (0x0010, "XPlaDevice", 1, 0), 821 (0x0020, "YPlaDevice", 1, 0), 822 (0x0040, "XAdvDevice", 1, 0), 823 (0x0080, "YAdvDevice", 1, 0), 824# reserved: 825 (0x0100, "Reserved1", 0, 0), 826 (0x0200, "Reserved2", 0, 0), 827 (0x0400, "Reserved3", 0, 0), 828 (0x0800, "Reserved4", 0, 0), 829 (0x1000, "Reserved5", 0, 0), 830 (0x2000, "Reserved6", 0, 0), 831 (0x4000, "Reserved7", 0, 0), 832 (0x8000, "Reserved8", 0, 0), 833] 834 835def _buildDict(): 836 d = {} 837 for mask, name, isDevice, signed in valueRecordFormat: 838 d[name] = mask, isDevice, signed 839 return d 840 841valueRecordFormatDict = _buildDict() 842 843 844class ValueRecordFactory(object): 845 846 """Given a format code, this object convert ValueRecords.""" 847 848 def __init__(self, valueFormat): 849 format = [] 850 for mask, name, isDevice, signed in valueRecordFormat: 851 if valueFormat & mask: 852 format.append((name, isDevice, signed)) 853 self.format = format 854 855 def __len__(self): 856 return len(self.format) 857 858 def readValueRecord(self, reader, font): 859 format = self.format 860 if not format: 861 return None 862 valueRecord = ValueRecord() 863 for name, isDevice, signed in format: 864 if signed: 865 value = reader.readShort() 866 else: 867 value = reader.readUShort() 868 if isDevice: 869 if value: 870 from . import otTables 871 subReader = reader.getSubReader(value) 872 value = getattr(otTables, name)() 873 value.decompile(subReader, font) 874 else: 875 value = None 876 setattr(valueRecord, name, value) 877 return valueRecord 878 879 def writeValueRecord(self, writer, font, valueRecord): 880 for name, isDevice, signed in self.format: 881 value = getattr(valueRecord, name, 0) 882 if isDevice: 883 if value: 884 subWriter = writer.getSubWriter() 885 writer.writeSubTable(subWriter) 886 value.compile(subWriter, font) 887 else: 888 writer.writeUShort(0) 889 elif signed: 890 writer.writeShort(value) 891 else: 892 writer.writeUShort(value) 893 894 895class ValueRecord(object): 896 897 # see ValueRecordFactory 898 899 def __init__(self, valueFormat=None, src=None): 900 if valueFormat is not None: 901 for mask, name, isDevice, signed in valueRecordFormat: 902 if valueFormat & mask: 903 setattr(self, name, None if isDevice else 0) 904 if src is not None: 905 for key,val in src.__dict__.items(): 906 if not hasattr(self, key): 907 continue 908 setattr(self, key, val) 909 elif src is not None: 910 self.__dict__ = src.__dict__.copy() 911 912 def getFormat(self): 913 format = 0 914 for name in self.__dict__.keys(): 915 format = format | valueRecordFormatDict[name][0] 916 return format 917 918 def toXML(self, xmlWriter, font, valueName, attrs=None): 919 if attrs is None: 920 simpleItems = [] 921 else: 922 simpleItems = list(attrs) 923 for mask, name, isDevice, format in valueRecordFormat[:4]: # "simple" values 924 if hasattr(self, name): 925 simpleItems.append((name, getattr(self, name))) 926 deviceItems = [] 927 for mask, name, isDevice, format in valueRecordFormat[4:8]: # device records 928 if hasattr(self, name): 929 device = getattr(self, name) 930 if device is not None: 931 deviceItems.append((name, device)) 932 if deviceItems: 933 xmlWriter.begintag(valueName, simpleItems) 934 xmlWriter.newline() 935 for name, deviceRecord in deviceItems: 936 if deviceRecord is not None: 937 deviceRecord.toXML(xmlWriter, font, name=name) 938 xmlWriter.endtag(valueName) 939 xmlWriter.newline() 940 else: 941 xmlWriter.simpletag(valueName, simpleItems) 942 xmlWriter.newline() 943 944 def fromXML(self, name, attrs, content, font): 945 from . import otTables 946 for k, v in attrs.items(): 947 setattr(self, k, int(v)) 948 for element in content: 949 if not isinstance(element, tuple): 950 continue 951 name, attrs, content = element 952 value = getattr(otTables, name)() 953 for elem2 in content: 954 if not isinstance(elem2, tuple): 955 continue 956 name2, attrs2, content2 = elem2 957 value.fromXML(name2, attrs2, content2, font) 958 setattr(self, name, value) 959 960 def __ne__(self, other): 961 result = self.__eq__(other) 962 return result if result is NotImplemented else not result 963 964 def __eq__(self, other): 965 if type(self) != type(other): 966 return NotImplemented 967 return self.__dict__ == other.__dict__ 968