otBase.py revision e84f9fd1b5d5baa4202d895b88cef65e544f201d
1from DefaultTable import DefaultTable 2import otData 3import struct 4from types import TupleType 5 6class OverflowErrorRecord: 7 def __init__(self, overflowTuple): 8 self.tableType = overflowTuple[0] 9 self.LookupListIndex = overflowTuple[1] 10 self.SubTableIndex = overflowTuple[2] 11 self.itemName = overflowTuple[3] 12 self.itemIndex = overflowTuple[4] 13 14 def __repr__(self): 15 return str((self.tableType, "LookupIndex:", self.LookupListIndex, "SubTableIndex:", self.SubTableIndex, "ItemName:", self.itemName, "ItemIndex:", self.itemIndex)) 16 17class OTLOffsetOverflowError(Exception): 18 def __init__(self, overflowErrorRecord): 19 self.value = overflowErrorRecord 20 21 def __str__(self): 22 return repr(self.value) 23 24 25class BaseTTXConverter(DefaultTable): 26 27 """Generic base class for TTX table converters. It functions as an 28 adapter between the TTX (ttLib actually) table model and the model 29 we use for OpenType tables, which is necessarily subtly different. 30 """ 31 32 def decompile(self, data, font): 33 import otTables 34 cachingStats = None if True else {} 35 reader = OTTableReader(data, self.tableTag, cachingStats=cachingStats) 36 tableClass = getattr(otTables, self.tableTag) 37 self.table = tableClass() 38 self.table.decompile(reader, font) 39 if cachingStats: 40 stats = [(v, k) for k, v in cachingStats.items()] 41 stats.sort() 42 stats.reverse() 43 print "cachingsstats for ", self.tableTag 44 for v, k in stats: 45 if v < 2: 46 break 47 print v, k 48 print "---", len(stats) 49 50 def compile(self, font): 51 """ Create a top-level OTFWriter for the GPOS/GSUB table. 52 Call the compile method for the the table 53 for each 'converter' record in the table converter list 54 call converter's write method for each item in the value. 55 - For simple items, the write method adds a string to the 56 writer's self.items list. 57 - For Struct/Table/Subtable items, it add first adds new writer to the 58 to the writer's self.items, then calls the item's compile method. 59 This creates a tree of writers, rooted at the GUSB/GPOS writer, with 60 each writer representing a table, and the writer.items list containing 61 the child data strings and writers. 62 call the getAllData method 63 call _doneWriting, which removes duplicates 64 call _gatherTables. This traverses the tables, adding unique occurences to a flat list of tables 65 Traverse the flat list of tables, calling getDataLength on each to update their position 66 Traverse the flat list of tables again, calling getData each get the data in the table, now that 67 pos's and offset are known. 68 69 If a lookup subtable overflows an offset, we have to start all over. 70 """ 71 writer = OTTableWriter(self.tableTag) 72 writer.parent = None 73 self.table.compile(writer, font) 74 return writer.getAllData() 75 76 def toXML(self, writer, font): 77 self.table.toXML2(writer, font) 78 79 def fromXML(self, (name, attrs, content), font): 80 import otTables 81 if not hasattr(self, "table"): 82 tableClass = getattr(otTables, self.tableTag) 83 self.table = tableClass() 84 self.table.fromXML((name, attrs, content), font) 85 86 87class OTTableReader(object): 88 89 """Helper class to retrieve data from an OpenType table.""" 90 91 __slots__ = ('data', 'offset', 'pos', 'tableType', 'valueFormat', 'counts', 'cachingStats') 92 93 def __init__(self, data, tableType, offset=0, valueFormat=None, counts=None, cachingStats=None): 94 self.data = data 95 self.offset = offset 96 self.pos = offset 97 self.tableType = tableType 98 if valueFormat is None: 99 valueFormat = [None, None] 100 self.counts = counts 101 self.valueFormat = valueFormat 102 self.cachingStats = cachingStats 103 104 def getSubReader(self, offset): 105 offset = self.offset + offset 106 if self.cachingStats is not None: 107 self.cachingStats[offset] = self.cachingStats.get(offset, 0) + 1 108 109 subReader = self.__class__(self.data, self.tableType, offset, 110 self.valueFormat, self.counts, self.cachingStats) 111 return subReader 112 113 def readUShort(self): 114 pos = self.pos 115 newpos = pos + 2 116 value, = struct.unpack(">H", self.data[pos:newpos]) 117 self.pos = newpos 118 return value 119 120 def readShort(self): 121 pos = self.pos 122 newpos = pos + 2 123 value, = struct.unpack(">h", self.data[pos:newpos]) 124 self.pos = newpos 125 return value 126 127 def readLong(self): 128 pos = self.pos 129 newpos = pos + 4 130 value, = struct.unpack(">l", self.data[pos:newpos]) 131 self.pos = newpos 132 return value 133 134 def readULong(self): 135 pos = self.pos 136 newpos = pos + 4 137 value, = struct.unpack(">L", self.data[pos:newpos]) 138 self.pos = newpos 139 return value 140 141 def readTag(self): 142 pos = self.pos 143 newpos = pos + 4 144 value = self.data[pos:newpos] 145 assert len(value) == 4 146 self.pos = newpos 147 return value 148 149 def readStruct(self, format, size=None): 150 if size is None: 151 size = struct.calcsize(format) 152 else: 153 assert size == struct.calcsize(format) 154 pos = self.pos 155 newpos = pos + size 156 values = struct.unpack(format, self.data[pos:newpos]) 157 self.pos = newpos 158 return values 159 160 def setValueFormat(self, format, which): 161 self.valueFormat[which] = ValueRecordFactory(format) 162 163 def readValueRecord(self, font, which): 164 return self.valueFormat[which].readValueRecord(self, font) 165 166 def setCount(self, name, value): 167 self.counts = self.counts.copy() if self.counts else dict() 168 self.counts[name] = value 169 170 def getCount(self, name): 171 return self.counts[name] 172 173 174class OTTableWriter(object): 175 176 """Helper class to gather and assemble data for OpenType tables.""" 177 178 def __init__(self, tableType, valueFormat=None, counts=None): 179 self.items = [] 180 self.tableType = tableType 181 if valueFormat is None: 182 valueFormat = [None, None] 183 self.valueFormat = valueFormat 184 self.counts = counts 185 self.pos = None 186 187 def setValueFormat(self, format, which): 188 self.valueFormat[which] = ValueRecordFactory(format) 189 190 def setCount(self, name, value): 191 self.counts = self.counts.copy() if self.counts else dict() 192 self.counts[name] = value 193 194 def getCount(self, name): 195 return self.counts[name] 196 197 # assembler interface 198 199 def getAllData(self): 200 """Assemble all data, including all subtables.""" 201 self._doneWriting() 202 tables, extTables = self._gatherTables() 203 tables.reverse() 204 extTables.reverse() 205 # Gather all data in two passes: the absolute positions of all 206 # subtable are needed before the actual data can be assembled. 207 pos = 0 208 for table in tables: 209 table.pos = pos 210 pos = pos + table.getDataLength() 211 212 for table in extTables: 213 table.pos = pos 214 pos = pos + table.getDataLength() 215 216 217 data = [] 218 for table in tables: 219 tableData = table.getData() 220 data.append(tableData) 221 222 for table in extTables: 223 tableData = table.getData() 224 data.append(tableData) 225 226 return "".join(data) 227 228 def getDataLength(self): 229 """Return the length of this table in bytes, without subtables.""" 230 l = 0 231 if hasattr(self, "Extension"): 232 longOffset = 1 233 else: 234 longOffset = 0 235 for item in self.items: 236 if hasattr(item, "getData") or hasattr(item, "getCountData"): 237 if longOffset: 238 l = l + 4 # sizeof(ULong) 239 else: 240 l = l + 2 # sizeof(UShort) 241 else: 242 l = l + len(item) 243 return l 244 245 def getData(self): 246 """Assemble the data for this writer/table, without subtables.""" 247 items = list(self.items) # make a shallow copy 248 if hasattr(self,"Extension"): 249 longOffset = 1 250 else: 251 longOffset = 0 252 pos = self.pos 253 numItems = len(items) 254 for i in range(numItems): 255 item = items[i] 256 257 if hasattr(item, "getData"): 258 if longOffset: 259 items[i] = packULong(item.pos - pos) 260 else: 261 try: 262 items[i] = packUShort(item.pos - pos) 263 except AssertionError: 264 # provide data to fix overflow problem. 265 # If the overflow is to a lookup, or from a lookup to a subtable, 266 # just report the current item. 267 if self.name in [ 'LookupList', 'Lookup']: 268 overflowErrorRecord = self.getOverflowErrorRecord(item) 269 else: 270 # overflow is within a subTable. Life is more complicated. 271 # If we split the sub-table just before the current item, we may still suffer overflow. 272 # This is because duplicate table merging is done only within an Extension subTable tree; 273 # when we split the subtable in two, some items may no longer be duplicates. 274 # Get worst case by adding up all the item lengths, depth first traversal. 275 # and then report the first item that overflows a short. 276 def getDeepItemLength(table): 277 if hasattr(table, "getDataLength"): 278 length = 0 279 for item in table.items: 280 length = length + getDeepItemLength(item) 281 else: 282 length = len(table) 283 return length 284 285 length = self.getDataLength() 286 if hasattr(self, "sortCoverageLast") and item.name == "Coverage": 287 # Coverage is first in the item list, but last in the table list, 288 # The original overflow is really in the item list. Skip the Coverage 289 # table in the following test. 290 items = items[i+1:] 291 292 for j in range(len(items)): 293 item = items[j] 294 length = length + getDeepItemLength(item) 295 if length > 65535: 296 break 297 overflowErrorRecord = self.getOverflowErrorRecord(item) 298 299 300 raise OTLOffsetOverflowError, overflowErrorRecord 301 302 return "".join(items) 303 304 def __hash__(self): 305 # only works after self._doneWriting() has been called 306 return hash(self.items) 307 308 def __cmp__(self, other): 309 if type(self) != type(other): return cmp(type(self), type(other)) 310 if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__) 311 312 return cmp(self.items, other.items) 313 314 def _doneWriting(self, internedTables=None): 315 # Convert CountData references to data string items 316 # collapse duplicate table references to a unique entry 317 # "tables" are OTTableWriter objects. 318 319 # For Extension Lookup types, we can 320 # eliminate duplicates only within the tree under the Extension Lookup, 321 # as offsets may exceed 64K even between Extension LookupTable subtables. 322 if internedTables is None: 323 internedTables = {} 324 items = self.items 325 iRange = range(len(items)) 326 327 if hasattr(self, "Extension"): 328 newTree = 1 329 else: 330 newTree = 0 331 for i in iRange: 332 item = items[i] 333 if hasattr(item, "getCountData"): 334 items[i] = item.getCountData() 335 elif hasattr(item, "getData"): 336 if newTree: 337 item._doneWriting() 338 else: 339 item._doneWriting(internedTables) 340 if internedTables.has_key(item): 341 items[i] = item = internedTables[item] 342 else: 343 internedTables[item] = item 344 self.items = tuple(items) 345 346 def _gatherTables(self, tables=None, extTables=None, done=None): 347 # Convert table references in self.items tree to a flat 348 # list of tables in depth-first traversal order. 349 # "tables" are OTTableWriter objects. 350 # We do the traversal in reverse order at each level, in order to 351 # resolve duplicate references to be the last reference in the list of tables. 352 # For extension lookups, duplicate references can be merged only within the 353 # writer tree under the extension lookup. 354 if tables is None: # init call for first time. 355 tables = [] 356 extTables = [] 357 done = {} 358 359 done[self] = 1 360 361 numItems = len(self.items) 362 iRange = range(numItems) 363 iRange.reverse() 364 365 if hasattr(self, "Extension"): 366 appendExtensions = 1 367 else: 368 appendExtensions = 0 369 370 # add Coverage table if it is sorted last. 371 sortCoverageLast = 0 372 if hasattr(self, "sortCoverageLast"): 373 # Find coverage table 374 for i in range(numItems): 375 item = self.items[i] 376 if hasattr(item, "name") and (item.name == "Coverage"): 377 sortCoverageLast = 1 378 break 379 if not done.has_key(item): 380 item._gatherTables(tables, extTables, done) 381 else: 382 index = max(item.parent.keys()) 383 item.parent[index + 1] = self 384 385 saveItem = None 386 for i in iRange: 387 item = self.items[i] 388 if not hasattr(item, "getData"): 389 continue 390 391 if sortCoverageLast and (i==1) and item.name == 'Coverage': 392 # we've already 'gathered' it above 393 continue 394 395 if appendExtensions: 396 assert extTables != None, "Program or XML editing error. Extension subtables cannot contain extensions subtables" 397 newDone = {} 398 item._gatherTables(extTables, None, newDone) 399 400 elif not done.has_key(item): 401 item._gatherTables(tables, extTables, done) 402 else: 403 index = max(item.parent.keys()) 404 item.parent[index + 1] = self 405 406 407 tables.append(self) 408 return tables, extTables 409 410 # interface for gathering data, as used by table.compile() 411 412 def getSubWriter(self): 413 subwriter = self.__class__(self.tableType, self.valueFormat, self.counts) 414 subwriter.parent = {0: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 return subwriter 418 419 def writeUShort(self, value): 420 assert 0 <= value < 0x10000 421 self.items.append(struct.pack(">H", value)) 422 423 def writeShort(self, value): 424 self.items.append(struct.pack(">h", value)) 425 426 def writeLong(self, value): 427 self.items.append(struct.pack(">l", value)) 428 429 def writeULong(self, value): 430 self.items.append(struct.pack(">L", value)) 431 432 def writeTag(self, tag): 433 assert len(tag) == 4 434 self.items.append(tag) 435 436 def writeSubTable(self, subWriter): 437 self.items.append(subWriter) 438 439 def writeCountReference(self, table, name): 440 ref = CountReference(table, name) 441 self.items.append(ref) 442 return ref 443 444 def writeStruct(self, format, values): 445 data = apply(struct.pack, (format,) + values) 446 self.items.append(data) 447 448 def writeData(self, data): 449 self.items.append(data) 450 451 def writeValueRecord(self, value, font, which): 452 return self.valueFormat[which].writeValueRecord(self, font, value) 453 454 def getOverflowErrorRecord(self, item): 455 LookupListIndex = SubTableIndex = itemName = itemIndex = None 456 if self.name == 'LookupList': 457 LookupListIndex = item.repeatIndex 458 elif self.name == 'Lookup': 459 LookupListIndex = self.repeatIndex 460 SubTableIndex = item.repeatIndex 461 else: 462 itemName = item.name 463 if hasattr(item, 'repeatIndex'): 464 itemIndex = item.repeatIndex 465 if self.name == 'SubTable': 466 LookupListIndex = self.parent[0].repeatIndex 467 SubTableIndex = self.repeatIndex 468 elif self.name == 'ExtSubTable': 469 LookupListIndex = self.parent[0].parent[0].repeatIndex 470 SubTableIndex = self.parent[0].repeatIndex 471 else: # who knows how far below the SubTable level we are! Climb back up to the nearest subtable. 472 itemName = ".".join(self.name, item.name) 473 p1 = self.parent[0] 474 while p1 and p1.name not in ['ExtSubTable', 'SubTable']: 475 itemName = ".".join(p1.name, item.name) 476 p1 = p1.parent[0] 477 if p1: 478 if p1.name == 'ExtSubTable': 479 LookupListIndex = self.parent[0].parent[0].repeatIndex 480 SubTableIndex = self.parent[0].repeatIndex 481 else: 482 LookupListIndex = self.parent[0].repeatIndex 483 SubTableIndex = self.repeatIndex 484 485 return OverflowErrorRecord( (self.tableType, LookupListIndex, SubTableIndex, itemName, itemIndex) ) 486 487 488class CountReference: 489 """A reference to a Count value, not a count of references.""" 490 def __init__(self, table, name): 491 self.table = table 492 self.name = name 493 def setValue(self, value): 494 table = self.table 495 name = self.name 496 if table[name] is None: 497 table[name] = value 498 else: 499 assert table[name] == value, (table[name], value) 500 def getCountData(self): 501 return packUShort(self.table[self.name]) 502 503 504def packUShort(value): 505 assert 0 <= value < 0x10000, value 506 return struct.pack(">H", value) 507 508 509def packULong(value): 510 assert 0 <= value < 0x100000000, value 511 return struct.pack(">L", value) 512 513 514class BaseTable(object): 515 def __init__(self): 516 self.compileStatus = 0 # 0 means table was created 517 # 1 means the table.read() function was called by a table which is subject 518 # to delayed compilation 519 # 2 means that it was subject to delayed compilation, and 520 # has been decompiled 521 # 3 means that the start and end fields have been filled out, and that we 522 # can use the data string rather than compiling from the table data. 523 524 self.recurse = 0 525 526 def __getattr__(self, attr): 527 # we get here only when the table does not have the attribute. 528 # This method ovveride exists so that we can try to de-compile 529 # a table which is subject to delayed decompilation, and then try 530 # to get the value again after decompilation. 531 self.recurse +=1 532 if self.recurse > 2: 533 # shouldn't ever get here - we should only get to two levels of recursion. 534 # this guards against self.decompile NOT setting compileStatus to other than 1. 535 raise AttributeError, attr 536 if self.compileStatus == 1: 537 self.ensureDecompiled() 538 val = getattr(self, attr) 539 self.recurse -=1 540 return val 541 542 raise AttributeError, attr 543 544 545 """Generic base class for all OpenType (sub)tables.""" 546 547 def getConverters(self): 548 return self.converters 549 550 def getConverterByName(self, name): 551 return self.convertersByName[name] 552 553 def decompile(self, reader, font): 554 self.compileStatus = 2 # table has been decompiled. 555 self.readFormat(reader) 556 table = {} 557 self.__rawTable = table # for debugging 558 converters = self.getConverters() 559 for conv in converters: 560 if conv.name == "SubTable": 561 conv = conv.getConverter(reader.tableType, 562 table["LookupType"]) 563 if conv.name == "ExtSubTable": 564 conv = conv.getConverter(reader.tableType, 565 table["ExtensionLookupType"]) 566 if conv.repeat: 567 l = [] 568 if conv.repeat in table: 569 countValue = table[conv.repeat] 570 else: 571 # conv.repeat is a propagated count 572 countValue = reader.getCount(conv.repeat) 573 for i in range(countValue + conv.repeatOffset): 574 l.append(conv.read(reader, font, table)) 575 table[conv.name] = l 576 else: 577 table[conv.name] = conv.read(reader, font, table) 578 if conv.isPropagatedCount: 579 reader.setCount(conv.name, table[conv.name]) 580 581 self.postRead(table, font) 582 583 del self.__rawTable # succeeded, get rid of debugging info 584 585 def ensureDecompiled(self): 586 if self.compileStatus != 1: 587 return 588 self.decompile(self.reader, self.font) 589 del self.reader, self.font 590 591 def preCompile(self): 592 pass # used only by the LookupList class 593 594 def compile(self, writer, font): 595 table = self.preWrite(font) 596 597 if hasattr(self, 'sortCoverageLast'): 598 writer.sortCoverageLast = 1 599 600 self.writeFormat(writer) 601 for conv in self.getConverters(): 602 value = table.get(conv.name) 603 if conv.repeat: 604 if value is None: 605 value = [] 606 countValue = len(value) - conv.repeatOffset 607 if conv.repeat in table: 608 ref = table[conv.repeat] 609 table[conv.repeat] = None 610 ref.setValue(countValue) 611 else: 612 # conv.repeat is a propagated count 613 writer.getCount(conv.repeat).setValue(countValue) 614 for i in range(len(value)): 615 conv.write(writer, font, table, value[i], i) 616 elif conv.isCount: 617 # Special-case Count values. 618 # Assumption: a Count field will *always* precede 619 # the actual array(s). 620 # We need a default value, as it may be set later by a nested 621 # table. We will later store it here. 622 # We add a reference: by the time the data is assembled 623 # the Count value will be filled in. 624 ref = writer.writeCountReference(table, conv.name) 625 if conv.isPropagatedCount: 626 table[conv.name] = None 627 writer.setCount(conv.name, ref) 628 else: 629 table[conv.name] = ref 630 else: 631 conv.write(writer, font, table, value) 632 633 def readFormat(self, reader): 634 pass 635 636 def writeFormat(self, writer): 637 pass 638 639 def postRead(self, table, font): 640 self.__dict__.update(table) 641 642 def preWrite(self, font): 643 self.ensureDecompiled() 644 return self.__dict__.copy() 645 646 def toXML(self, xmlWriter, font, attrs=None): 647 tableName = self.__class__.__name__ 648 if attrs is None: 649 attrs = [] 650 if hasattr(self, "Format"): 651 attrs = attrs + [("Format", self.Format)] 652 xmlWriter.begintag(tableName, attrs) 653 xmlWriter.newline() 654 self.toXML2(xmlWriter, font) 655 xmlWriter.endtag(tableName) 656 xmlWriter.newline() 657 658 def toXML2(self, xmlWriter, font): 659 # Simpler variant of toXML, *only* for the top level tables (like GPOS, GSUB). 660 # This is because in TTX our parent writes our main tag, and in otBase.py we 661 # do it ourselves. I think I'm getting schizophrenic... 662 for conv in self.getConverters(): 663 value = getattr(self, conv.name) 664 if conv.repeat: 665 for i in range(len(value)): 666 item = value[i] 667 conv.xmlWrite(xmlWriter, font, item, conv.name, 668 [("index", i)]) 669 else: 670 conv.xmlWrite(xmlWriter, font, value, conv.name, []) 671 672 def fromXML(self, (name, attrs, content), font): 673 try: 674 conv = self.getConverterByName(name) 675 except KeyError: 676 raise # XXX on KeyError, raise nice error 677 value = conv.xmlRead(attrs, content, font) 678 if conv.repeat: 679 seq = getattr(self, conv.name, None) 680 if seq is None: 681 seq = [] 682 setattr(self, conv.name, seq) 683 seq.append(value) 684 else: 685 setattr(self, conv.name, value) 686 687 def __cmp__(self, other): 688 if type(self) != type(other): return cmp(type(self), type(other)) 689 if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__) 690 691 self.ensureDecompiled() 692 693 return cmp(self.__dict__, other.__dict__) 694 695 696class FormatSwitchingBaseTable(BaseTable): 697 698 """Minor specialization of BaseTable, for tables that have multiple 699 formats, eg. CoverageFormat1 vs. CoverageFormat2.""" 700 701 def getConverters(self): 702 return self.converters[self.Format] 703 704 def getConverterByName(self, name): 705 return self.convertersByName[self.Format][name] 706 707 def readFormat(self, reader): 708 self.Format = reader.readUShort() 709 assert self.Format <> 0, (self, reader.pos, len(reader.data)) 710 711 def writeFormat(self, writer): 712 writer.writeUShort(self.Format) 713 714 715# 716# Support for ValueRecords 717# 718# This data type is so different from all other OpenType data types that 719# it requires quite a bit of code for itself. It even has special support 720# in OTTableReader and OTTableWriter... 721# 722 723valueRecordFormat = [ 724# Mask Name isDevice signed 725 (0x0001, "XPlacement", 0, 1), 726 (0x0002, "YPlacement", 0, 1), 727 (0x0004, "XAdvance", 0, 1), 728 (0x0008, "YAdvance", 0, 1), 729 (0x0010, "XPlaDevice", 1, 0), 730 (0x0020, "YPlaDevice", 1, 0), 731 (0x0040, "XAdvDevice", 1, 0), 732 (0x0080, "YAdvDevice", 1, 0), 733# reserved: 734 (0x0100, "Reserved1", 0, 0), 735 (0x0200, "Reserved2", 0, 0), 736 (0x0400, "Reserved3", 0, 0), 737 (0x0800, "Reserved4", 0, 0), 738 (0x1000, "Reserved5", 0, 0), 739 (0x2000, "Reserved6", 0, 0), 740 (0x4000, "Reserved7", 0, 0), 741 (0x8000, "Reserved8", 0, 0), 742] 743 744def _buildDict(): 745 d = {} 746 for mask, name, isDevice, signed in valueRecordFormat: 747 d[name] = mask, isDevice, signed 748 return d 749 750valueRecordFormatDict = _buildDict() 751 752 753class ValueRecordFactory: 754 755 """Given a format code, this object convert ValueRecords.""" 756 757 def __init__(self, valueFormat): 758 format = [] 759 for mask, name, isDevice, signed in valueRecordFormat: 760 if valueFormat & mask: 761 format.append((name, isDevice, signed)) 762 self.format = format 763 764 def readValueRecord(self, reader, font): 765 format = self.format 766 if not format: 767 return None 768 valueRecord = ValueRecord() 769 for name, isDevice, signed in format: 770 if signed: 771 value = reader.readShort() 772 else: 773 value = reader.readUShort() 774 if isDevice: 775 if value: 776 import otTables 777 subReader = reader.getSubReader(value) 778 value = getattr(otTables, name)() 779 value.decompile(subReader, font) 780 else: 781 value = None 782 setattr(valueRecord, name, value) 783 return valueRecord 784 785 def writeValueRecord(self, writer, font, valueRecord): 786 for name, isDevice, signed in self.format: 787 value = getattr(valueRecord, name, 0) 788 if isDevice: 789 if value: 790 subWriter = writer.getSubWriter() 791 writer.writeSubTable(subWriter) 792 value.compile(subWriter, font) 793 else: 794 writer.writeUShort(0) 795 elif signed: 796 writer.writeShort(value) 797 else: 798 writer.writeUShort(value) 799 800 801class ValueRecord: 802 803 # see ValueRecordFactory 804 805 def getFormat(self): 806 format = 0 807 for name in self.__dict__.keys(): 808 format = format | valueRecordFormatDict[name][0] 809 return format 810 811 def toXML(self, xmlWriter, font, valueName, attrs=None): 812 if attrs is None: 813 simpleItems = [] 814 else: 815 simpleItems = list(attrs) 816 for mask, name, isDevice, format in valueRecordFormat[:4]: # "simple" values 817 if hasattr(self, name): 818 simpleItems.append((name, getattr(self, name))) 819 deviceItems = [] 820 for mask, name, isDevice, format in valueRecordFormat[4:8]: # device records 821 if hasattr(self, name): 822 device = getattr(self, name) 823 if device is not None: 824 deviceItems.append((name, device)) 825 if deviceItems: 826 xmlWriter.begintag(valueName, simpleItems) 827 xmlWriter.newline() 828 for name, deviceRecord in deviceItems: 829 if deviceRecord is not None: 830 deviceRecord.toXML(xmlWriter, font) 831 xmlWriter.endtag(valueName) 832 xmlWriter.newline() 833 else: 834 xmlWriter.simpletag(valueName, simpleItems) 835 xmlWriter.newline() 836 837 def fromXML(self, (name, attrs, content), font): 838 import otTables 839 for k, v in attrs.items(): 840 setattr(self, k, int(v)) 841 for element in content: 842 if type(element) <> TupleType: 843 continue 844 name, attrs, content = element 845 value = getattr(otTables, name)() 846 for elem2 in content: 847 if type(elem2) <> TupleType: 848 continue 849 value.fromXML(elem2, font) 850 setattr(self, name, value) 851 852 def __cmp__(self, other): 853 if type(self) != type(other): return cmp(type(self), type(other)) 854 if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__) 855 856 return cmp(self.__dict__, other.__dict__) 857