from types import TupleType from fontTools.misc.textTools import safeEval def buildConverters(tableSpec, tableNamespace): """Given a table spec from otData.py, build a converter object for each field of the table. This is called for each table in otData.py, and the results are assigned to the corresponding class in otTables.py.""" converters = [] convertersByName = {} for tp, name, repeat, repeatOffset, descr in tableSpec: if name.startswith("ValueFormat"): assert tp == "uint16" converterClass = ValueFormat elif name == "DeltaValue": assert tp == "uint16" converterClass = DeltaValue elif name.endswith("Count"): assert tp == "uint16" converterClass = Count elif name == "SubTable": converterClass = SubTable elif name == "ExtSubTable": converterClass = ExtSubTable else: converterClass = converterMapping[tp] tableClass = tableNamespace.get(name) conv = converterClass(name, repeat, repeatOffset, tableClass) if name in ["SubTable", "ExtSubTable"]: conv.lookupTypes = tableNamespace['lookupTypes'] # also create reverse mapping for t in conv.lookupTypes.values(): for cls in t.values(): convertersByName[cls.__name__] = Table(name, repeat, repeatOffset, cls) converters.append(conv) assert not convertersByName.has_key(name) convertersByName[name] = conv return converters, convertersByName class BaseConverter: """Base class for converter objects. Apart from the constructor, this is an abstract class.""" def __init__(self, name, repeat, repeatOffset, tableClass): self.name = name self.repeat = repeat self.repeatOffset = repeatOffset self.tableClass = tableClass self.isCount = name.endswith("Count") def read(self, reader, font, countVars): """Read a value from the reader.""" raise NotImplementedError, self def write(self, writer, font, countVars, value, repeatIndex=None): """Write a value to the writer.""" raise NotImplementedError, self def xmlRead(self, attrs, content, font): """Read a value from XML.""" raise NotImplementedError, self def xmlWrite(self, xmlWriter, font, value, name, attrs): """Write a value to XML.""" raise NotImplementedError, self class SimpleValue(BaseConverter): def xmlWrite(self, xmlWriter, font, value, name, attrs): xmlWriter.simpletag(name, attrs + [("value", value)]) xmlWriter.newline() def xmlRead(self, attrs, content, font): return attrs["value"] class IntValue(SimpleValue): def xmlRead(self, attrs, content, font): return int(attrs["value"]) class Long(IntValue): def read(self, reader, font, countVars): return reader.readLong() def write(self, writer, font, countVars, value, repeatIndex=None): writer.writeLong(value) class Fixed(IntValue): def read(self, reader, font, countVars): return float(reader.readLong()) / 0x10000 def write(self, writer, font, countVars, value, repeatIndex=None): writer.writeLong(int(round(value * 0x10000))) def xmlRead(self, attrs, content, font): return float(attrs["value"]) class Short(IntValue): def read(self, reader, font, countVars): return reader.readShort() def write(self, writer, font, countVars, value, repeatIndex=None): writer.writeShort(value) class UShort(IntValue): def read(self, reader, font, countVars): return reader.readUShort() def write(self, writer, font, countVars, value, repeatIndex=None): writer.writeUShort(value) class Count(Short): def xmlWrite(self, xmlWriter, font, value, name, attrs): xmlWriter.comment("%s=%s" % (name, value)) xmlWriter.newline() class Tag(SimpleValue): def read(self, reader, font, countVars): return reader.readTag() def write(self, writer, font, countVars, value, repeatIndex=None): writer.writeTag(value) class GlyphID(SimpleValue): def read(self, reader, font, countVars): value = reader.readUShort() value = font.getGlyphName(value) return value def write(self, writer, font, countVars, value, repeatIndex=None): value = font.getGlyphID(value) writer.writeUShort(value) class Struct(BaseConverter): def read(self, reader, font, countVars): table = self.tableClass() table.decompile(reader, font, countVars) return table def write(self, writer, font, countVars, value, repeatIndex=None): value.compile(writer, font, countVars) def xmlWrite(self, xmlWriter, font, value, name, attrs): if value is None: pass # NULL table, ignore else: value.toXML(xmlWriter, font, attrs) def xmlRead(self, attrs, content, font): table = self.tableClass() Format = attrs.get("Format") if Format is not None: table.Format = int(Format) for element in content: if type(element) == TupleType: name, attrs, content = element table.fromXML((name, attrs, content), font) else: pass return table class Table(Struct): def read(self, reader, font, countVars, lazy=True): offset = reader.readUShort() if offset == 0: return None if offset <= 3: # XXX hack to work around buggy pala.ttf print "*** Warning: offset is not 0, yet suspiciously low (%s). table: %s" \ % (offset, self.tableClass.__name__) return None subReader = reader.getSubReader(offset, persistent=lazy) table = self.tableClass() if lazy: table.reader = subReader table.font = font table.compileStatus = 1 table.countVars = countVars.copy() else: table.decompile(subReader, font, countVars) return table def write(self, writer, font, countVars, value, repeatIndex=None): if value is None: writer.writeUShort(0) else: subWriter = writer.getSubWriter() subWriter.name = self.name if repeatIndex is not None: subWriter.repeatIndex = repeatIndex value.preCompile() writer.writeSubTable(subWriter) value.compile(subWriter, font, countVars) class SubTable(Table): def getConverter(self, tableType, lookupType): lookupTypes = self.lookupTypes[tableType] tableClass = lookupTypes[lookupType] return SubTable(self.name, self.repeat, self.repeatOffset, tableClass) class ExtSubTable(Table): def getConverter(self, tableType, lookupType): lookupTypes = self.lookupTypes[tableType] tableClass = lookupTypes[lookupType] return ExtSubTable(self.name, self.repeat, self.repeatOffset, tableClass) def read(self, reader, font, countVars, lazy=True): offset = reader.readULong() if offset == 0: return None subReader = reader.getSubReader(offset) table = self.tableClass() table.start = subReader.offset if lazy: table.reader = subReader table.font = font table.compileStatus = 1 table.countVars = countVars.copy() else: table.decompile(subReader, font, countVars) return table def write(self, writer, font, countVars, value, repeatIndex=None): writer.Extension = 1 # actually, mere presence of the field flags it as an Ext Subtable writer. if value is None: writer.writeULong(0) else: # If the subtable has not yet been decompiled, we need to do so. if value.compileStatus == 1: value.decompile(value.reader, value.font, countVars) subWriter = writer.getSubWriter() subWriter.name = self.name writer.writeSubTable(subWriter) # If the subtable has been sorted and we can just write the original # data, then do so. if value.compileStatus == 3: data = value.reader.data[value.start:value.end] subWriter.writeData(data) else: value.compile(subWriter, font, countVars) class ValueFormat(IntValue): def __init__(self, name, repeat, repeatOffset, tableClass): BaseConverter.__init__(self, name, repeat, repeatOffset, tableClass) self.which = name[-1] == "2" def read(self, reader, font, countVars): format = reader.readUShort() reader.setValueFormat(format, self.which) return format def write(self, writer, font, countVars, format, repeatIndex=None): writer.writeUShort(format) writer.setValueFormat(format, self.which) class ValueRecord(ValueFormat): def read(self, reader, font, countVars): return reader.readValueRecord(font, self.which) def write(self, writer, font, countVars, value, repeatIndex=None): writer.writeValueRecord(value, font, self.which) def xmlWrite(self, xmlWriter, font, value, name, attrs): if value is None: pass # NULL table, ignore else: value.toXML(xmlWriter, font, self.name, attrs) def xmlRead(self, attrs, content, font): from otBase import ValueRecord value = ValueRecord() value.fromXML((None, attrs, content), font) return value class DeltaValue(BaseConverter): def read(self, reader, font, countVars): table = countVars.getTop() StartSize = table["StartSize"] EndSize = table["EndSize"] DeltaFormat = table["DeltaFormat"] assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat" nItems = EndSize - StartSize + 1 nBits = 1 << DeltaFormat minusOffset = 1 << nBits mask = (1 << nBits) - 1 signMask = 1 << (nBits - 1) DeltaValue = [] tmp, shift = 0, 0 for i in range(nItems): if shift == 0: tmp, shift = reader.readUShort(), 16 shift = shift - nBits value = (tmp >> shift) & mask if value & signMask: value = value - minusOffset DeltaValue.append(value) return DeltaValue def write(self, writer, font, countVars, value, repeatIndex=None): table = countVars.getTop() StartSize = table["StartSize"] EndSize = table["EndSize"] DeltaFormat = table["DeltaFormat"] DeltaValue = table["DeltaValue"] assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat" nItems = EndSize - StartSize + 1 nBits = 1 << DeltaFormat assert len(DeltaValue) == nItems mask = (1 << nBits) - 1 tmp, shift = 0, 16 for value in DeltaValue: shift = shift - nBits tmp = tmp | ((value & mask) << shift) if shift == 0: writer.writeUShort(tmp) tmp, shift = 0, 16 if shift <> 16: writer.writeUShort(tmp) def xmlWrite(self, xmlWriter, font, value, name, attrs): # XXX this could do with a nicer format xmlWriter.simpletag(name, attrs + [("value", value)]) xmlWriter.newline() def xmlRead(self, attrs, content, font): return safeEval(attrs["value"]) converterMapping = { # type class "int16": Short, "uint16": UShort, "Fixed": Fixed, "Tag": Tag, "GlyphID": GlyphID, "struct": Struct, "Offset": Table, "LOffset": ExtSubTable, "ValueRecord": ValueRecord, } # equivalents: converterMapping["fixed32"] = converterMapping["Fixed"]