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