| """ttLib.tables.otCommon.py -- Various data structures used by various OpenType tables. |
| """ |
| |
| import struct, sstruct |
| import DefaultTable |
| from fontTools import ttLib |
| |
| |
| class base_GPOS_GSUB(DefaultTable.DefaultTable): |
| |
| """Base class for GPOS and GSUB tables; they share the same high-level structure.""" |
| |
| version = 0x00010000 |
| |
| def decompile(self, data, otFont): |
| reader = OTTableReader(data) |
| self.version = reader.readLong() |
| if self.version <> 0x00010000: |
| raise ttLib.TTLibError, "unknown table version: 0x%8x" % self.version |
| |
| self.scriptList = reader.readTable(ScriptList, otFont, self.tableTag) |
| self.featureList = reader.readTable(FeatureList, otFont, self.tableTag) |
| self.lookupList = reader.readTable(LookupList, otFont, self.tableTag) |
| |
| def compile(self, otFont): |
| XXXXXX |
| |
| def toXML(self, xmlWriter, otFont): |
| names = [("ScriptList", "scriptList"), |
| ("FeatureList", "featureList"), |
| ("LookupList", "lookupList")] |
| for name, attr in names: |
| xmlWriter.newline() |
| xmlWriter.begintag(name) |
| xmlWriter.newline() |
| table = getattr(self, attr) |
| table.toXML(xmlWriter, otFont) |
| xmlWriter.endtag(name) |
| xmlWriter.newline() |
| xmlWriter.newline() |
| |
| def fromXML(self, (name, attrs, content), otFont): |
| xxx |
| |
| |
| # |
| # Script List and subtables |
| # |
| |
| class ScriptList: |
| |
| def __init__(self, parentTag): |
| self.parentTag = parentTag |
| |
| def decompile(self, reader, otFont): |
| scriptCount = reader.readUShort() |
| self.scripts = reader.readTagList(scriptCount, Script, otFont) |
| |
| def compile(self, otFont): |
| XXXXXX |
| |
| def toXML(self, xmlWriter, otFont): |
| for tag, script in self.scripts: |
| xmlWriter.begintag("Script", tag=tag) |
| xmlWriter.newline() |
| script.toXML(xmlWriter, otFont) |
| xmlWriter.endtag("Script") |
| xmlWriter.newline() |
| |
| def fromXML(self, (name, attrs, content), otFont): |
| xxx |
| |
| |
| class Script: |
| |
| def decompile(self, reader, otFont): |
| self.defaultLangSystem = None |
| self.defaultLangSystem = reader.readTable(LanguageSystem, otFont) |
| langSysCount = reader.readUShort() |
| self.languageSystems = reader.readTagList(langSysCount, LanguageSystem, otFont) |
| |
| def compile(self, otFont): |
| XXXXX |
| |
| def toXML(self, xmlWriter, otFont): |
| xmlWriter.begintag("DefaultLanguageSystem") |
| xmlWriter.newline() |
| self.defaultLangSystem.toXML(xmlWriter, otFont) |
| xmlWriter.endtag("DefaultLanguageSystem") |
| xmlWriter.newline() |
| for tag, langSys in self.languageSystems: |
| xmlWriter.begintag("LanguageSystem", tag=tag) |
| xmlWriter.newline() |
| langSys.toXML(xmlWriter, otFont) |
| xmlWriter.endtag("LanguageSystem") |
| xmlWriter.newline() |
| |
| |
| class LanguageSystem: |
| |
| def decompile(self, reader, otFont): |
| self.lookupOrder = reader.readUShort() |
| self.reqFeatureIndex = reader.readUShort() |
| featureCount = reader.readUShort() |
| self.featureIndex = reader.readUShortArray(featureCount) |
| |
| def compile(self, otFont): |
| xxx |
| |
| def toXML(self, xmlWriter, otFont): |
| xmlWriter.simpletag("LookupOrder", value=self.lookupOrder) |
| xmlWriter.newline() |
| xmlWriter.simpletag("ReqFeature", index=hex(self.reqFeatureIndex)) |
| xmlWriter.newline() |
| for index in self.featureIndex: |
| xmlWriter.simpletag("Feature", index=index) |
| xmlWriter.newline() |
| |
| |
| # |
| # Feature List and subtables |
| # |
| |
| class FeatureList: |
| |
| def __init__(self, parentTag): |
| self.parentTag = parentTag |
| |
| def decompile(self, reader, otFont): |
| featureCount = reader.readUShort() |
| self.features = reader.readTagList(featureCount, Feature, otFont) |
| |
| def compile(self, otFont): |
| XXXXX |
| |
| def toXML(self, xmlWriter, otFont): |
| for index in range(len(self.features)): |
| tag, feature = self.features[index] |
| xmlWriter.begintag("Feature", index=index, tag=tag) |
| xmlWriter.newline() |
| feature.toXML(xmlWriter, otFont) |
| xmlWriter.endtag("Feature") |
| xmlWriter.newline() |
| |
| def fromXML(self, (name, attrs, content), otFont): |
| xxx |
| |
| |
| class Feature: |
| |
| def decompile(self, reader, otFont): |
| self.featureParams = reader.readUShort() |
| lookupCount = reader.readUShort() |
| self.lookupListIndex = reader.readUShortArray(lookupCount) |
| |
| def compile(self, otFont): |
| XXXXX |
| |
| def toXML(self, xmlWriter, otFont): |
| xmlWriter.simpletag("FeatureParams", value=hex(self.featureParams)) |
| xmlWriter.newline() |
| for lookupIndex in self.lookupListIndex: |
| xmlWriter.simpletag("LookupTable", index=lookupIndex) |
| xmlWriter.newline() |
| |
| def fromXML(self, (name, attrs, content), otFont): |
| xxx |
| |
| |
| # |
| # Lookup List and subtables |
| # |
| |
| class LookupList: |
| |
| def __init__(self, parentTag): |
| self.parentTag = parentTag |
| |
| def decompile(self, reader, otFont): |
| lookupCount = reader.readUShort() |
| self.lookup = lookup = [] |
| for i in range(lookupCount): |
| lookup.append(reader.readTable(LookupTable, otFont, self.parentTag)) |
| |
| def compile(self, otFont): |
| XXXXX |
| |
| def toXML(self, xmlWriter, otFont): |
| for i in range(len(self.lookup)): |
| xmlWriter.newline() |
| lookupTable = self.lookup[i] |
| xmlWriter.begintag("LookupTable", index=i) |
| xmlWriter.newline() |
| lookupTable.toXML(xmlWriter, otFont) |
| xmlWriter.endtag("LookupTable") |
| xmlWriter.newline() |
| xmlWriter.newline() |
| |
| def fromXML(self, (name, attrs, content), otFont): |
| xxx |
| |
| |
| class LookupTable: |
| |
| def __init__(self, parentTag): |
| self.parentTag = parentTag |
| |
| def decompile(self, reader, otFont): |
| parentTable = otFont[self.parentTag] |
| self.lookupType = reader.readUShort() |
| self.lookupFlag = reader.readUShort() |
| subTableCount = reader.readUShort() |
| self.subTables = subTables = [] |
| lookupTypeClass = parentTable.getLookupTypeClass(self.lookupType) |
| for i in range(subTableCount): |
| subTables.append(reader.readTable(lookupTypeClass, otFont)) |
| |
| def compile(self, otFont): |
| XXXXXX |
| |
| def __repr__(self): |
| if not hasattr(self, "lookupTypeName"): |
| m = ttLib.getTableModule(self.parentTag) |
| self.lookupTypeName = m.lookupTypeClasses[self.lookupType].__name__ |
| return "<%s LookupTable at %x>" % (self.lookupTypeName, id(self)) |
| |
| def toXML(self, xmlWriter, otFont): |
| xmlWriter.simpletag("LookupFlag", value=hex(self.lookupFlag)) |
| xmlWriter.newline() |
| for subTable in self.subTables: |
| name = subTable.__class__.__name__ |
| xmlWriter.begintag(name) |
| xmlWriter.newline() |
| subTable.toXML(xmlWriter, otFont) |
| xmlWriter.endtag(name) |
| xmlWriter.newline() |
| |
| def fromXML(self, (name, attrs, content), otFont): |
| xxx |
| |
| |
| # |
| # Other common formats |
| # |
| |
| class CoverageTable: |
| |
| def getGlyphIDs(self): |
| return self.glyphIDs |
| |
| def getGlyphNames(self): |
| return self.glyphNames |
| |
| def makeGlyphNames(self, otFont): |
| self.glyphNames = map(lambda i, o=otFont.getGlyphOrder(): o[i], self.glyphIDs) |
| |
| def decompile(self, reader, otFont): |
| format = reader.readUShort() |
| if format == 1: |
| self.decompileFormat1(reader, otFont) |
| elif format == 2: |
| self.decompileFormat2(reader, otFont) |
| else: |
| raise ttLib.TTLibError, "unknown Coverage table format: %d" % format |
| self.makeGlyphNames(otFont) |
| |
| def decompileFormat1(self, reader, otFont): |
| glyphCount = reader.readUShort() |
| self.glyphIDs = glyphIDs = [] |
| for i in range(glyphCount): |
| glyphID = reader.readUShort() |
| glyphIDs.append(glyphID) |
| |
| def decompileFormat2(self, reader, otFont): |
| rangeCount = reader.readUShort() |
| self.glyphIDs = glyphIDs = [] |
| for i in range(rangeCount): |
| startID = reader.readUShort() |
| endID = reader.readUShort() |
| startCoverageIndex = reader.readUShort() |
| for glyphID in range(startID, endID + 1): |
| glyphIDs.append(glyphID) |
| |
| def compile(self, otFont): |
| # brute force ;-) |
| data1 = self.compileFormat1(otFont) |
| data2 = self.compileFormat2(otFont) |
| if len(data1) <= len(data2): |
| format = 1 |
| reader = data1 |
| else: |
| format = 2 |
| reader = data2 |
| return struct.pack(">H", format) + reader |
| |
| def compileFormat1(self, otFont): |
| xxxxx |
| glyphIDs = map(otFont.getGlyphID, self.glyphNames) |
| data = pack(">H", len(glyphIDs)) |
| pack = struct.pack |
| for glyphID in glyphIDs: |
| data = data + pack(">H", glyphID) |
| return data |
| |
| def compileFormat2(self, otFont): |
| xxxxx |
| glyphIDs = map(otFont.getGlyphID, self.glyphNames) |
| ranges = [] |
| lastID = startID = glyphIDs[0] |
| startCoverageIndex = 0 |
| glyphCount = len(glyphIDs) |
| for i in range(1, glyphCount+1): |
| if i == glyphCount: |
| glyphID = 0x1ffff # arbitrary, but larger than 0x10000 |
| else: |
| glyphID = glyphIDs[i] |
| if glyphID <> (lastID + 1): |
| ranges.append((startID, lastID, startCoverageIndex)) |
| startCoverageIndex = i |
| startID = glyphID |
| lastID = glyphID |
| ranges.sort() # sort by startID |
| rangeData = "" |
| for startID, endID, startCoverageIndex in ranges: |
| rangeData = rangeData + struct.pack(">HHH", startID, endID, startCoverageIndex) |
| return pack(">H", len(ranges)) + rangeData |
| |
| |
| class ClassDefinitionTable: |
| |
| def decompile(self, reader, otFont): |
| format = reader.readUShort() |
| if format == 1: |
| self.decompileFormat1(reader, otFont) |
| elif format == 2: |
| self.decompileFormat2(reader, otFont) |
| else: |
| raise ttLib.TTLibError, "unknown Class table format: %d" % format |
| self.reverse() |
| |
| def reverse(self): |
| classDefs = {} |
| for glyphName, classCode in self.classDefs: |
| try: |
| classDefs[classCode].append(glyphName) |
| except KeyError: |
| classDefs[classCode] = [glyphName] |
| self.classDefs = classDefs |
| |
| def decompileFormat1(self, reader, otFont): |
| self.classDefs = classDefs = [] |
| startGlyphID = reader.readUShort() |
| glyphCount = reader.readUShort() |
| for i in range(glyphCount): |
| glyphName = otFont.getglyphName(startGlyphID + i) |
| classValue = reader.readUShort() |
| if classValue: |
| classDefs.append((glyphName, classValue)) |
| |
| def decompileFormat2(self, reader, otFont): |
| self.classDefs = classDefs = [] |
| classRangeCount = reader.readUShort() |
| for i in range(classRangeCount): |
| startID = reader.readUShort() |
| endID = reader.readUShort() |
| classValue = reader.readUShort() |
| for glyphID in range(startID, endID + 1): |
| if classValue: |
| glyphName = otFont.getGlyphName(glyphID) |
| classDefs.append((glyphName, classValue)) |
| |
| def compile(self, otFont): |
| # brute force again |
| data1 = self.compileFormat1(otFont) |
| data2 = self.compileFormat2(otFont) |
| if len(data1) <= len(data2): |
| format = 1 |
| data = data1 |
| else: |
| format = 2 |
| data = data2 |
| return struct.pack(">H", format) + data |
| |
| def compileFormat1(self, otFont): |
| items = map(lambda (glyphName, classValue), getGlyphID=otFont.getGlyphID: |
| (getGlyphID(glyphName), classValue), self.glyphs.items()) |
| items.sort() |
| startGlyphID = items[0][0] |
| endGlyphID = items[-1][0] |
| data = "" |
| lastID = startGlyphID |
| for glyphID, classValue in items: |
| for i in range(lastID + 1, glyphID - 1): |
| data = data + "\0\0" # 0 == default class |
| data = data + struct.pack(">H", classValue) |
| lastID = glyphID |
| return struct.pack(">H", endGlyphID - startGlyphID + 1) + data |
| |
| def compileFormat2(self, otFont): |
| items = map(lambda (glyphName, classValue), getGlyphID=otFont.getGlyphID: |
| (getGlyphID(glyphName), classValue), self.glyphs.items()) |
| items.sort() |
| ranges = [] |
| lastID, lastClassValue = items[0][0] |
| startID = lastID |
| itemCount = len(items) |
| for i in range(1, itemCount+1): |
| if i == itemCount: |
| glyphID = 0x1ffff # arbitrary, but larger than 0x10000 |
| classValue = 0 |
| else: |
| glyphID, classValue = items[i] |
| if glyphID <> (lastID + 1) or lastClassValue <> classValue: |
| ranges.append((startID, lastID, lastClassValue)) |
| startID = glyphID |
| lastClassValue = classValue |
| lastID = glyphID |
| lastClassValue = classValue |
| rangeData = "" |
| for startID, endID, classValue in ranges: |
| rangeData = rangeData + struct.pack(">HHH", startID, endID, classValue) |
| return pack(">H", len(ranges)) + rangeData |
| |
| def __getitem__(self, glyphName): |
| if self.glyphs.has_key(glyphName): |
| return self.glyphs[glyphName] |
| else: |
| return 0 # default class |
| |
| |
| class DeviceTable: |
| |
| def decompile(self, reader, otFont): |
| xxxxxx |
| self.startSize = unpack_uint16(reader[:2]) |
| endSize = unpack_uint16(reader[2:4]) |
| deltaFormat = unpack_uint16(reader[4:6]) |
| reader = reader[6:] |
| if deltaFormat == 1: |
| bits = 2 |
| elif deltaFormat == 2: |
| bits = 4 |
| elif deltaFormat == 3: |
| bits = 8 |
| else: |
| raise ttLib.TTLibError, "unknown Device table delta format: %d" % deltaFormat |
| numCount = 16 / bits |
| deltaCount = endSize - self.startSize + 1 |
| deltaValues = [] |
| mask = (1 << bits) - 1 |
| threshold = (1 << bits) / 2 |
| shift = 1 << bits |
| for i in range(0, deltaCount, numCount): |
| offset = 2*i/numCount |
| chunk = unpack_uint16(reader[offset:offset+2]) |
| deltas = [] |
| for j in range(numCount): |
| delta = chunk & mask |
| if delta >= threshold: |
| delta = delta - shift |
| deltas.append(delta) |
| chunk = chunk >> bits |
| deltas.reverse() |
| deltaValues = deltaValues + deltas |
| self.deltaValues = deltaValues[:deltaCount] |
| |
| def compile(self, otFont): |
| deltaValues = self.deltaValues |
| startSize = self.startSize |
| endSize = startSize + len(deltaValues) - 1 |
| smallestDelta = min(deltas) |
| largestDelta = ma(deltas) |
| if smallestDelta >= -2 and largestDelta < 2: |
| deltaFormat = 1 |
| bits = 2 |
| elif smallestDelta >= -8 and largestDelta < 8: |
| deltaFormat = 2 |
| bits = 4 |
| elif smallestDelta >= -128 and largestDelta < 128: |
| deltaFormat = 3 |
| bits = 8 |
| else: |
| raise ttLib.TTLibError, "delta value too large: min=%d, max=%d" % (smallestDelta, largestDelta) |
| data = struct.pack(">HHH", startSize, endSize, deltaFormat) |
| numCount = 16 / bits |
| # pad the list to a multiple of numCount values |
| remainder = len(deltaValues) % numCount |
| if remainder: |
| deltaValues = deltaValues + [0] * (numCount - remainder) |
| deltaData = "" |
| for i in range(0, len(deltaValues), numCount): |
| chunk = 0 |
| for j in range(numCount): |
| chunk = chunk << bits |
| chunk = chunk | deltaValues[i+j] |
| deltaData = deltaData + struct.pack(">H", chunk) |
| return data + deltaData |
| |
| |
| # |
| # Miscelaneous helper routines and classes |
| # |
| |
| class OTTableReader: |
| |
| """Data wrapper, mostly designed to make reading OT data less cumbersome.""" |
| |
| def __init__(self, data, offset=0): |
| self.data = data |
| self.offset = offset |
| self.pos = offset |
| |
| def readUShort(self): |
| pos = self.pos |
| newpos = pos + 2 |
| value = int(struct.unpack(">H", self.data[pos:newpos])[0]) |
| self.pos = newpos |
| return value |
| |
| readOffset = readUShort |
| |
| def readShort(self): |
| pos = self.pos |
| newpos = pos + 2 |
| value = int(struct.unpack(">h", self.data[pos:newpos])[0]) |
| self.pos = newpos |
| return value |
| |
| def readLong(self): |
| pos = self.pos |
| newpos = pos + 4 |
| value = int(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 readUShortArray(self, count): |
| return self.readArray(count, "H") |
| |
| readOffsetArray = readUShortArray |
| |
| def readShortArray(self, count): |
| return self.readArray(count, "h") |
| |
| def readArray(self, count, format): |
| assert format in "Hh" |
| from array import array |
| pos = self.pos |
| newpos = pos + 2 * count |
| a = array(format) |
| a.fromstring(self.data[pos:newpos]) |
| if ttLib.endian <> 'big': |
| a.byteswap() |
| self.pos = newpos |
| return a.tolist() |
| |
| def readTable(self, tableClass, otFont, *args): |
| offset = self.readOffset() |
| if offset == 0: |
| return None |
| newReader = self.getSubString(offset) |
| table = apply(tableClass, args) |
| table.decompile(newReader, otFont) |
| return table |
| |
| def readTableArray(self, count, tableClass, otFont, *args): |
| list = [] |
| for i in range(count): |
| list.append(apply(self.readTable, (tableClass, otFont) + args)) |
| return list |
| |
| def readTagList(self, count, tableClass, otFont, *args): |
| list = [] |
| for i in range(count): |
| tag = self.readTag() |
| table = apply(self.readTable, (tableClass, otFont) + args) |
| list.append((tag, table)) |
| return list |
| |
| 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 getSubString(self, offset): |
| return self.__class__(self.data, self.offset+offset) |
| |
| def seek(self, n): |
| """Relative seek.""" |
| self.pos = self.pos + n |
| |
| |