Completely revamped OT support; this time it works and is complete. XML output is not yet as pretty as can be.
git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@208 4cde692c-a291-49d1-8350-778aa11640f8
diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py
new file mode 100644
index 0000000..85ffcd7
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/otBase.py
@@ -0,0 +1,479 @@
+from DefaultTable import DefaultTable
+import otData
+import struct
+from types import TupleType
+
+
+class BaseTTXConverter(DefaultTable):
+
+ def decompile(self, data, font):
+ import otTables
+ reader = OTTableReader(data, self.tableTag)
+ tableClass = getattr(otTables, self.tableTag)
+ self.table = tableClass()
+ self.table.decompile(reader, font)
+
+ def compile(self, font):
+ writer = OTTableWriter(self.tableTag)
+ self.table.compile(writer, font)
+ return writer.getData()
+
+ def toXML(self, writer, font):
+ self.table.toXML2(writer, font)
+
+ def fromXML(self, (name, attrs, content), font):
+ import otTables
+ if not hasattr(self, "table"):
+ tableClass = getattr(otTables, self.tableTag)
+ self.table = tableClass()
+ self.table.fromXML((name, attrs, content), font)
+
+
+class OTTableReader:
+
+ def __init__(self, data, tableType, offset=0, valueFormat=None, cachingStats=None):
+ self.data = data
+ self.offset = offset
+ self.pos = offset
+ self.tableType = tableType
+ if valueFormat is None:
+ valueFormat = (ValueRecordFactory(), ValueRecordFactory())
+ self.valueFormat = valueFormat
+ self.cachingStats = cachingStats
+
+ def getSubReader(self, offset):
+ offset = self.offset + offset
+ if self.cachingStats is not None:
+ try:
+ self.cachingStats[offset] = self.cachingStats[offset] + 1
+ except KeyError:
+ self.cachingStats[offset] = 1
+
+ subReader = self.__class__(self.data, self.tableType, offset,
+ self.valueFormat, self.cachingStats)
+ return subReader
+
+ def readUShort(self):
+ pos = self.pos
+ newpos = pos + 2
+ value = struct.unpack(">H", self.data[pos:newpos])[0]
+ self.pos = newpos
+ return value
+
+ def readShort(self):
+ pos = self.pos
+ newpos = pos + 2
+ value = struct.unpack(">h", self.data[pos:newpos])[0]
+ self.pos = newpos
+ return value
+
+ def readLong(self):
+ pos = self.pos
+ newpos = pos + 4
+ value = struct.unpack(">l", self.data[pos:newpos])[0]
+ self.pos = newpos
+ return value
+
+ def readTag(self):
+ pos = self.pos
+ newpos = pos + 4
+ value = self.data[pos:newpos]
+ assert len(value) == 4
+ self.pos = newpos
+ return value
+
+ def readStruct(self, format, size=None):
+ if size is None:
+ size = struct.calcsize(format)
+ else:
+ assert size == struct.calcsize(format)
+ pos = self.pos
+ newpos = pos + size
+ values = struct.unpack(format, self.data[pos:newpos])
+ self.pos = newpos
+ return values
+
+ def setValueFormat(self, format, which):
+ self.valueFormat[which].setFormat(format)
+
+ def readValueRecord(self, font, which):
+ return self.valueFormat[which].readValueRecord(self, font)
+
+
+class OTTableWriter:
+
+ def __init__(self, tableType, valueFormat=None):
+ self.items = []
+ self.tableType = tableType
+ if valueFormat is None:
+ valueFormat = ValueRecordFactory(), ValueRecordFactory()
+ self.valueFormat = valueFormat
+
+ def getSubWriter(self):
+ return self.__class__(self.tableType, self.valueFormat)
+
+ def getData(self):
+ items = list(self.items)
+ offset = 0
+ for item in items:
+ if hasattr(item, "getData") or hasattr(item, "getCount"):
+ offset = offset + 2 # sizeof(UShort)
+ else:
+ offset = offset + len(item)
+ subTables = []
+ cache = {}
+ for i in range(len(items)):
+ item = items[i]
+ if hasattr(item, "getData"):
+ subTableData = item.getData()
+ if cache.has_key(subTableData):
+ items[i] = packUShort(cache[subTableData])
+ else:
+ items[i] = packUShort(offset)
+ subTables.append(subTableData)
+ cache[subTableData] = offset
+ offset = offset + len(subTableData)
+ elif hasattr(item, "getCount"):
+ items[i] = item.getCount()
+ return "".join(items + subTables)
+
+ def writeUShort(self, value):
+ assert 0 <= value < 0x10000
+ self.items.append(struct.pack(">H", value))
+
+ def writeShort(self, value):
+ self.items.append(struct.pack(">h", value))
+
+ def writeLong(self, value):
+ self.items.append(struct.pack(">l", value))
+
+ def writeTag(self, tag):
+ assert len(tag) == 4
+ self.items.append(tag)
+
+ def writeSubTable(self, subWriter):
+ self.items.append(subWriter)
+
+ def writeCountReference(self, table, name):
+ self.items.append(CountReference(table, name))
+
+ def writeStruct(self, format, values):
+ data = apply(struct.pack, (format,) + values)
+ self.items.append(data)
+
+ def setValueFormat(self, format, which):
+ self.valueFormat[which].setFormat(format)
+
+ def writeValueRecord(self, value, font, which):
+ return self.valueFormat[which].writeValueRecord(self, font, value)
+
+
+class CountReference:
+ def __init__(self, table, name):
+ self.table = table
+ self.name = name
+ def getCount(self):
+ return packUShort(self.table[self.name])
+
+
+def packUShort(offset):
+ assert 0 <= offset < 0x10000
+ return struct.pack(">H", offset)
+
+
+
+class BaseTable:
+
+ def getConverters(self):
+ return self.converters
+
+ def getConverterByName(self, name):
+ return self.convertersByName[name]
+
+ def decompile(self, reader, font, tableStack=None):
+ if tableStack is None:
+ tableStack = TableStack()
+ table = {}
+ self.__rawTable = table # for debugging
+ tableStack.push(table)
+ for conv in self.getConverters():
+ if conv.name == "SubTable":
+ conv = conv.getConverter(reader.tableType,
+ table["LookupType"])
+ if conv.repeat:
+ l = []
+ for i in range(tableStack.getValue(conv.repeat) + conv.repeatOffset):
+ l.append(conv.read(reader, font, tableStack))
+ table[conv.name] = l
+ else:
+ table[conv.name] = conv.read(reader, font, tableStack)
+ tableStack.pop()
+ self.postRead(table, font)
+ del self.__rawTable # succeeded, get rid of debugging info
+
+ def compile(self, writer, font, tableStack=None):
+ if tableStack is None:
+ tableStack = TableStack()
+ table = self.preWrite(font)
+ tableStack.push(table)
+ for conv in self.getConverters():
+ value = table.get(conv.name)
+ if conv.repeat:
+ if value is None:
+ value = [] # XXXXXX
+ tableStack.storeValue(conv.repeat, len(value) - conv.repeatOffset)
+ for item in value:
+ conv.write(writer, font, tableStack, item)
+ elif conv.isCount:
+ # Special-case Count values.
+ # Assumption: a Count field will *always* precede
+ # the actual array.
+ # We need a default value, as it may be set later by a nested
+ # table. TableStack.storeValue() will then find it here.
+ table[conv.name] = None
+ # We add a reference: by the time the data is assembled
+ # the Count value will be filled in.
+ writer.writeCountReference(table, conv.name)
+ else:
+ conv.write(writer, font, tableStack, value)
+ tableStack.pop()
+
+ def postRead(self, table, font):
+ self.__dict__.update(table)
+
+ def preWrite(self, font):
+ return self.__dict__.copy()
+
+ def toXML(self, xmlWriter, font, attrs=None):
+ tableName = self.__class__.__name__
+ if attrs is None:
+ attrs = []
+ if hasattr(self, "Format"):
+ attrs = attrs + [("Format", str(self.Format))]
+ xmlWriter.begintag(tableName, attrs)
+ xmlWriter.newline()
+ self.toXML2(xmlWriter, font)
+ xmlWriter.endtag(tableName)
+ xmlWriter.newline()
+
+ def toXML2(self, xmlWriter, font):
+ # Simpler variant of toXML, *only* for the top level tables (like GPOS, GSUB).
+ # This is because in TTX our parent writes our main tag, and in otBase.py we
+ # do it ourselves. I think I'm getting schizophrenic...
+ for conv in self.getConverters():
+ value = getattr(self, conv.name)
+ if not conv.repeat:
+ conv.xmlWrite(xmlWriter, font, value, conv.name, [])
+ else:
+ for i in range(len(value)):
+ item = value[i]
+ conv.xmlWrite(xmlWriter, font, item, conv.name, [("index", i)])
+
+ def fromXML(self, (name, attrs, content), font):
+ try:
+ conv = self.getConverterByName(name)
+ except KeyError:
+ print self, name, attrs, content
+ raise # XXX on KeyError, raise nice error
+ value = conv.xmlRead(attrs, content, font)
+ name = conv.name
+ if conv.repeat:
+ try:
+ seq = getattr(self, name)
+ except AttributeError:
+ seq = []
+ setattr(self, name, seq)
+ seq.append(value)
+ else:
+ setattr(self, name, value)
+
+ def __cmp__(self, other):
+ # this is only for debugging, so it's ok to barf
+ # when 'other' has no __dict__ or __class__
+ rv = cmp(self.__class__, other.__class__)
+ if not rv:
+ rv = cmp(self.__dict__, other.__dict__)
+ return rv
+ else:
+ return rv
+
+
+class FormatSwitchingBaseTable(BaseTable):
+
+ def getConverters(self):
+ return self.converters[self.Format]
+
+ def getConverterByName(self, name):
+ return self.convertersByName[self.Format][name]
+
+ def decompile(self, reader, font, tableStack=None):
+ self.Format = reader.readUShort()
+ assert self.Format <> 0, (self, reader.pos, len(reader.data))
+ BaseTable.decompile(self, reader, font, tableStack)
+
+ def compile(self, writer, font, tableStack=None):
+ writer.writeUShort(self.Format)
+ BaseTable.compile(self, writer, font, tableStack)
+
+
+valueRecordFormat = [
+# Mask Name isDevice signed
+ (0x0001, "XPlacement", 0, 1),
+ (0x0002, "YPlacement", 0, 1),
+ (0x0004, "XAdvance", 0, 1),
+ (0x0008, "YAdvance", 0, 1),
+ (0x0010, "XPlaDevice", 1, 0),
+ (0x0020, "YPlaDevice", 1, 0),
+ (0x0040, "XAdvDevice", 1, 0),
+ (0x0080, "YAdvDevice", 1, 0),
+# reserved:
+ (0x0100, "Reserved1", 0, 0),
+ (0x0200, "Reserved2", 0, 0),
+ (0x0400, "Reserved3", 0, 0),
+ (0x0800, "Reserved4", 0, 0),
+ (0x1000, "Reserved5", 0, 0),
+ (0x2000, "Reserved6", 0, 0),
+ (0x4000, "Reserved7", 0, 0),
+ (0x8000, "Reserved8", 0, 0),
+]
+
+def _buildDict():
+ d = {}
+ for mask, name, isDevice, signed in valueRecordFormat:
+ d[name] = mask, isDevice, signed
+ return d
+
+valueRecordFormatDict = _buildDict()
+
+
+class ValueRecordFactory:
+
+ def setFormat(self, valueFormat):
+ format = []
+ for mask, name, isDevice, signed in valueRecordFormat:
+ if valueFormat & mask:
+ format.append((name, isDevice, signed))
+ self.format = format
+
+ def readValueRecord(self, reader, font):
+ format = self.format
+ if not format:
+ return None
+ valueRecord = ValueRecord()
+ for name, isDevice, signed in format:
+ if signed:
+ value = reader.readShort()
+ else:
+ value = reader.readUShort()
+ if isDevice:
+ if value:
+ import otTables
+ subReader = reader.getSubReader(value)
+ value = getattr(otTables, name)()
+ value.decompile(subReader, font)
+ else:
+ value = None
+ setattr(valueRecord, name, value)
+ return valueRecord
+
+ def writeValueRecord(self, writer, font, valueRecord):
+ for name, isDevice, signed in self.format:
+ value = getattr(valueRecord, name, 0)
+ if isDevice:
+ if value:
+ subWriter = writer.getSubWriter()
+ writer.writeSubTable(subWriter)
+ value.compile(subWriter, font)
+ else:
+ writer.writeUShort(0)
+ elif signed:
+ writer.writeShort(value)
+ else:
+ writer.writeUShort(value)
+
+
+class ValueRecord:
+
+ # see ValueRecordFactory
+
+ def getFormat(self):
+ format = 0
+ for name in self.__dict__.keys():
+ format = format | valueRecordFormatDict[name][0]
+ return format
+
+ def toXML(self, xmlWriter, font, valueName, attrs=None):
+ if attrs is None:
+ simpleItems = []
+ else:
+ simpleItems = list(attrs)
+ for mask, name, isDevice, format in valueRecordFormat[:4]: # "simple" values
+ if hasattr(self, name):
+ simpleItems.append((name, getattr(self, name)))
+ deviceItems = []
+ for mask, name, isDevice, format in valueRecordFormat[4:8]: # device records
+ if hasattr(self, name):
+ device = getattr(self, name)
+ if device is not None:
+ deviceItems.append((name, device))
+ if deviceItems:
+ xmlWriter.begintag(valueName, simpleItems)
+ xmlWriter.newline()
+ for name, deviceRecord in deviceItems:
+ if deviceRecord is not None:
+ deviceRecord.toXML(xmlWriter, font)
+ xmlWriter.endtag(valueName)
+ xmlWriter.newline()
+ else:
+ xmlWriter.simpletag(valueName, simpleItems)
+ xmlWriter.newline()
+
+ def fromXML(self, (name, attrs, content), font):
+ import otTables
+ for k, v in attrs.items():
+ setattr(self, k, int(v))
+ for element in content:
+ if type(element) <> TupleType:
+ continue
+ name, attrs, content = element
+ value = getattr(otTables, name)()
+ for elem2 in content:
+ if type(elem2) <> TupleType:
+ continue
+ value.fromXML(elem2, font)
+ setattr(self, name, value)
+
+ def __cmp__(self, other):
+ # this is only for debugging, so it's ok to barf
+ # when 'other' has no __dict__ or __class__
+ rv = cmp(self.__class__, other.__class__)
+ if not rv:
+ rv = cmp(self.__dict__, other.__dict__)
+ return rv
+ else:
+ return rv
+
+
+class TableStack:
+ def __init__(self):
+ self.stack = []
+ def push(self, table):
+ self.stack.insert(0, table)
+ def pop(self):
+ self.stack.pop(0)
+ def getTop(self):
+ return self.stack[0]
+ def getValue(self, name):
+ return self.__findTable(name)[name]
+ def storeValue(self, name, value):
+ table = self.__findTable(name)
+ if table[name] is None:
+ table[name] = value
+ else:
+ assert table[name] == value, (table[name], value)
+ def __findTable(self, name):
+ for table in self.stack:
+ if table.has_key(name):
+ return table
+ raise KeyError, name
+