git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@48 4cde692c-a291-49d1-8350-778aa11640f8
611 lines
16 KiB
Python
611 lines
16 KiB
Python
"""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):
|
|
self.data = data # while work is in progress, dump as hex
|
|
return
|
|
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):
|
|
return self.data
|
|
|
|
|
|
class Dummy:
|
|
|
|
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
|
|
|
|
|