Created a new library directory called "FreeLib". All OpenSource RFMKII components will reside there, fontTools being the flagship.
git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@2 4cde692c-a291-49d1-8350-778aa11640f8
diff --git a/Lib/fontTools/ttLib/tables/C_F_F_.py b/Lib/fontTools/ttLib/tables/C_F_F_.py
new file mode 100644
index 0000000..8148f06
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/C_F_F_.py
@@ -0,0 +1,35 @@
+import DefaultTable
+from fontTools import cffLib
+
+
+class table_C_F_F_(DefaultTable.DefaultTable, cffLib.CFFFontSet):
+
+ def __init__(self, tag):
+ DefaultTable.DefaultTable.__init__(self, tag)
+ cffLib.CFFFontSet.__init__(self)
+ self._gaveGlyphOrder = 0
+
+ def decompile(self, data, otFont):
+ self.data = data # XXX while work is in progress...
+ cffLib.CFFFontSet.decompile(self, data)
+ assert len(self.fonts) == 1, "can't deal with multi-font CFF tables."
+
+ #def compile(self, otFont):
+ # xxx
+
+ def getGlyphOrder(self):
+ if self._gaveGlyphOrder:
+ from fontTools import ttLib
+ raise ttLib.TTLibError, "illegal use of getGlyphOrder()"
+ self._gaveGlyphOrder = 1
+ return self.fonts[self.fontNames[0]].getGlyphOrder()
+
+ def setGlyphOrder(self, glyphOrder):
+ self.fonts[self.fontNames[0]].setGlyphOrder(glyphOrder)
+
+ def toXML(self, writer, otFont, progress=None):
+ cffLib.CFFFontSet.toXML(self, writer, progress)
+
+ #def fromXML(self, (name, attrs, content), otFont):
+ # xxx
+
diff --git a/Lib/fontTools/ttLib/tables/D_S_I_G_.py b/Lib/fontTools/ttLib/tables/D_S_I_G_.py
new file mode 100644
index 0000000..bea5654
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/D_S_I_G_.py
@@ -0,0 +1,12 @@
+import DefaultTable
+
+class table_D_S_I_G_(DefaultTable.DefaultTable):
+
+ def toXML(self, xmlWriter, ttFont):
+ xmlWriter.comment("note that the Digital Signature will be invalid after recompilation!")
+ xmlWriter.newline()
+ xmlWriter.begintag("hexdata")
+ xmlWriter.newline()
+ xmlWriter.dumphex(self.compile(ttFont))
+ xmlWriter.endtag("hexdata")
+ xmlWriter.newline()
diff --git a/Lib/fontTools/ttLib/tables/DefaultTable.py b/Lib/fontTools/ttLib/tables/DefaultTable.py
new file mode 100644
index 0000000..745f237
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/DefaultTable.py
@@ -0,0 +1,36 @@
+import string
+import sys
+
+class DefaultTable:
+
+ dependencies = []
+
+ def __init__(self, tag):
+ self.tableTag = tag
+
+ def decompile(self, data, ttFont):
+ self.data = data
+
+ def compile(self, ttFont):
+ return self.data
+
+ def toXML(self, writer, ttFont):
+ writer.begintag("hexdata")
+ writer.newline()
+ writer.dumphex(self.compile(ttFont))
+ writer.endtag("hexdata")
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ from fontTools.misc.textTools import readHex
+ from fontTools import ttLib
+ if name <> "hexdata":
+ raise ttLib.TTLibError, "can't handle '%s' element" % name
+ self.decompile(readHex(content), ttFont)
+
+ def __repr__(self):
+ return "<'%s' table at %x>" % (self.tableTag, id(self))
+
+ def __cmp__(self, other):
+ return cmp(self.__dict__, other.__dict__)
+
diff --git a/Lib/fontTools/ttLib/tables/G_P_O_S_.py b/Lib/fontTools/ttLib/tables/G_P_O_S_.py
new file mode 100644
index 0000000..7bec887
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/G_P_O_S_.py
@@ -0,0 +1,294 @@
+import otCommon
+
+
+class table_G_P_O_S_(otCommon.base_GPOS_GSUB):
+
+ def getLookupTypeClass(self, lookupType):
+ return lookupTypeClasses[lookupType]
+
+
+class SinglePos:
+
+ def decompile(self, reader, otFont):
+ pass
+
+ def compile(self, otFont):
+ xxx
+
+ def toXML(self, xmlWriter, otFont):
+ xmlWriter.comment("XXX")
+ xmlWriter.newline()
+
+ def fromXML(self, (name, attrs, content), otFont):
+ xxx
+
+
+class PairPos:
+
+ def decompile(self, reader, otFont):
+ self.format = reader.readUShort()
+ if self.format == 1:
+ self.decompileFormat1(reader, otFont)
+ elif self.format == 2:
+ self.decompileFormat2(reader, otFont)
+ else:
+ from fontTools import ttLib
+ raise ttLib.TTLibError, "unknown PairPos format: %d" % self.format
+
+ def decompileFormat1(self, reader, otFont):
+ coverage = reader.readTable(otCommon.CoverageTable, otFont)
+ glyphNames = coverage.glyphNames
+ valueFactory1 = ValueRecordFactory(reader.readUShort())
+ valueFactory2 = ValueRecordFactory(reader.readUShort())
+ self.pairs = pairs = {}
+ for i in range(reader.readUShort()):
+ firstGlyphName = glyphNames[i]
+ offset = reader.readOffset()
+ setData = reader.getSubString(offset)
+ set = PairSet()
+ set.decompile(setData, otFont, valueFactory1, valueFactory2)
+ pairs[firstGlyphName] = set.values
+
+ def decompileFormat2(self, reader, otFont):
+ coverage = reader.readTable(otCommon.CoverageTable, otFont)
+ glyphNames = coverage.glyphNames
+ valueFactory1 = ValueRecordFactory(reader.readUShort())
+ valueFactory2 = ValueRecordFactory(reader.readUShort())
+ self.classDef1 = reader.readTable(otCommon.ClassDefinitionTable, otFont)
+ self.classDef2 = reader.readTable(otCommon.ClassDefinitionTable, otFont)
+ class1Count = reader.readUShort()
+ class2Count = reader.readUShort()
+ self.pairs = pairs = {} # sparse matrix
+ for i in range(class1Count):
+ row = {}
+ for j in range(class2Count):
+ value1 = valueFactory1.getValueRecord(reader)
+ value2 = valueFactory2.getValueRecord(reader)
+ if value1 or value2:
+ row[j] = (value1, value2)
+ if row:
+ pairs[i] = row
+
+ def compile(self, otFont):
+ xxx
+
+ def toXML(self, xmlWriter, otFont):
+ if self.format == 1:
+ self.toXMLFormat1(xmlWriter, otFont)
+ elif self.format == 2:
+ self.toXMLFormat2(xmlWriter, otFont)
+ else:
+ from fontTools import ttLib
+ raise ttLib.TTLibError, "unknown PairPos format: %d" % self.format
+
+ def toXMLFormat1(self, xmlWriter, otFont):
+ pairs = self.pairs.items()
+ pairs.sort()
+ for firstGlyph, secondGlyphs in pairs:
+ for secondGlyph, value1, value2 in secondGlyphs:
+ #XXXXXXXXX
+ xmlWriter.begintag("Pair", first=firstGlyph, second=secondGlyph)
+ xmlWriter.newline()
+ if value1:
+ value1.toXML(xmlWriter, otFont)
+ if value2:
+ value2.toXML(xmlWriter, otFont)
+ xmlWriter.endtag("Pair")
+ xmlWriter.newline()
+
+ def toXMLFormat2(self, xmlWriter, otFont):
+ xmlWriter.comment("XXX")
+ xmlWriter.newline()
+
+ def fromXML(self, (name, attrs, content), otFont):
+ xxx
+
+
+class PairSet:
+
+ def decompile(self, reader, otFont, valueFactory1, valueFactory2):
+ pairValueCount = reader.readUShort()
+ self.values = values = []
+ for j in range(pairValueCount):
+ secondGlyphID = reader.readUShort()
+ secondGlyphName = otFont.getGlyphName(secondGlyphID)
+ value1 = valueFactory1.getValueRecord(reader)
+ value2 = valueFactory2.getValueRecord(reader)
+ values.append((secondGlyphName, value1, value2))
+
+ def compile(self, otFont):
+ xxx
+
+#
+# ------------------
+#
+
+class CursivePos:
+
+ def decompile(self, reader, otFont):
+ pass
+
+ def compile(self, otFont):
+ xxx
+
+ def toXML(self, xmlWriter, otFont):
+ xmlWriter.comment("XXX")
+ xmlWriter.newline()
+
+
+class MarkBasePos:
+
+ def decompile(self, reader, otFont):
+ pass
+
+ def compile(self, otFont):
+ xxx
+
+ def toXML(self, xmlWriter, otFont):
+ xmlWriter.comment("XXX")
+ xmlWriter.newline()
+
+
+class MarkLigPos:
+
+ def decompile(self, reader, otFont):
+ pass
+
+ def compile(self, otFont):
+ xxx
+
+ def toXML(self, xmlWriter, otFont):
+ xmlWriter.comment("XXX")
+ xmlWriter.newline()
+
+
+class MarkMarkPos:
+
+ def decompile(self, reader, otFont):
+ pass
+
+ def compile(self, otFont):
+ xxx
+
+ def toXML(self, xmlWriter, otFont):
+ xmlWriter.comment("XXX")
+ xmlWriter.newline()
+
+
+class ContextPos:
+
+ def decompile(self, reader, otFont):
+ pass
+
+ def compile(self, otFont):
+ xxx
+
+ def toXML(self, xmlWriter, otFont):
+ xmlWriter.comment("XXX")
+ xmlWriter.newline()
+
+
+class ChainContextPos:
+
+ def decompile(self, reader, otFont):
+ pass
+
+ def compile(self, otFont):
+ xxx
+
+ def toXML(self, xmlWriter, otFont):
+ xmlWriter.comment("XXX")
+ xmlWriter.newline()
+
+
+lookupTypeClasses = {
+ 1: SinglePos,
+ 2: PairPos,
+ 3: CursivePos,
+ 4: MarkBasePos,
+ 5: MarkLigPos,
+ 6: MarkMarkPos,
+ 7: ContextPos,
+ 8: ChainContextPos,
+}
+
+
+valueRecordFormat = [
+# Mask Name struct format char
+ (0x0001, "XPlacement", "h"),
+ (0x0002, "YPlacement", "h"),
+ (0x0004, "XAdvance", "h"),
+ (0x0008, "YAdvance", "h"),
+ (0x0010, "XPlaDevice", "H"),
+ (0x0020, "YPlaDevice", "H"),
+ (0x0040, "XAdvDevice", "H"),
+ (0x0080, "YAdvDevice", "H"),
+# reserved:
+ (0x0100, "Reserved1", "H"),
+ (0x0200, "Reserved2", "H"),
+ (0x0400, "Reserved3", "H"),
+ (0x0800, "Reserved4", "H"),
+ (0x1000, "Reserved5", "H"),
+ (0x2000, "Reserved6", "H"),
+ (0x4000, "Reserved7", "H"),
+ (0x8000, "Reserved8", "H"),
+]
+
+
+class ValueRecordFactory:
+
+ def __init__(self, valueFormat):
+ format = ">"
+ names = []
+ for mask, name, formatChar in valueRecordFormat:
+ if valueFormat & mask:
+ names.append(name)
+ format = format + formatChar
+ self.names, self.format = names, format
+ self.size = 2 * len(names)
+
+ def getValueRecord(self, reader):
+ names = self.names
+ if not names:
+ return None
+ values = reader.readStruct(self.format, self.size)
+ values = map(int, values)
+ valueRecord = ValueRecord()
+ items = map(None, names, values)
+ for name, value in items:
+ setattr(valueRecord, name, value)
+ return valueRecord
+
+
+class ValueRecord:
+ # see ValueRecordFactory
+
+ def __nonzero__(self):
+ for value in self.__dict__.values():
+ if value:
+ return 1
+ return 0
+
+ def toXML(self, xmlWriter, otFont):
+ simpleItems = []
+ for mask, name, format in valueRecordFormat[:4]: # "simple" values
+ if hasattr(self, name):
+ simpleItems.append((name, getattr(self, name)))
+ deviceItems = []
+ for mask, name, format in valueRecordFormat[4:8]: # device records
+ if hasattr(self, name):
+ deviceItems.append((name, getattr(self, name)))
+ if deviceItems:
+ xmlWriter.begintag("ValueRecord", simpleItems)
+ xmlWriter.newline()
+ for name, deviceRecord in deviceItems:
+ xxx
+ xmlWriter.endtag("ValueRecord")
+ xmlWriter.newline()
+ else:
+ xmlWriter.simpletag("ValueRecord", simpleItems)
+ xmlWriter.newline()
+
+ def __repr__(self):
+ return "<ValueRecord>"
+
diff --git a/Lib/fontTools/ttLib/tables/G_S_U_B_.py b/Lib/fontTools/ttLib/tables/G_S_U_B_.py
new file mode 100644
index 0000000..d0c4dce
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/G_S_U_B_.py
@@ -0,0 +1,283 @@
+import otCommon
+
+
+class table_G_S_U_B_(otCommon.base_GPOS_GSUB):
+
+ def getLookupTypeClass(self, lookupType):
+ return lookupTypeClasses[lookupType]
+
+
+class SingleSubst:
+
+ def decompile(self, reader, otFont):
+ self.format = reader.readUShort()
+ if self.format == 1:
+ self.decompileFormat1(reader, otFont)
+ elif self.format == 2:
+ self.decompileFormat2(reader, otFont)
+ else:
+ from fontTools import ttLib
+ raise ttLib.TTLibError, "unknown SingleSub format: %d" % self.format
+
+ def decompileFormat1(self, reader, otFont):
+ coverage = reader.readTable(otCommon.CoverageTable, otFont)
+ glyphIDs = coverage.getGlyphIDs()
+ glyphNames = coverage.getGlyphNames()
+ self.substitutions = substitutions = {}
+ deltaGlyphID = reader.readShort()
+ for i in range(len(glyphIDs)):
+ input = glyphNames[i]
+ output = otFont.getGlyphName(glyphIDs[i] + deltaGlyphID)
+ substitutions[input] = output
+
+ def decompileFormat2(self, reader, otFont):
+ coverage = reader.readTable(otCommon.CoverageTable, otFont)
+ glyphNames = coverage.getGlyphNames()
+ glyphCount = reader.readUShort()
+ self.substitutions = substitutions = {}
+ for i in range(glyphCount):
+ glyphID = reader.readUShort()
+ output = otFont.getGlyphName(glyphID)
+ input = glyphNames[i]
+ substitutions[input] = output
+
+ def compile(self, otFont):
+ xxx
+
+ def toXML(self, xmlWriter, otFont):
+ substitutions = self.substitutions.items()
+ substitutions.sort()
+ for input, output in substitutions:
+ xmlWriter.simpletag("Subst", [("in", input), ("out", output)])
+ xmlWriter.newline()
+
+ def fromXML(self, (name, attrs, content), otFont):
+ xxx
+
+
+class MultipleSubst:
+
+ def decompile(self, reader, otFont):
+ format = reader.readUShort()
+ if format <> 1:
+ from fontTools import ttLib
+ raise ttLib.TTLibError, "unknown MultipleSubst format: %d" % format
+ glyphNames = reader.readTable(otCommon.CoverageTable, otFont).getGlyphNames()
+ sequenceCount = reader.readUShort()
+ self.substitutions = substitutions = {}
+ for i in range(sequenceCount):
+ sequence = reader.readTable(Sequence, otFont)
+ substitutions[glyphNames[i]] = sequence.glyphs
+
+ def compile(self, otFont):
+ xxx
+
+ def toXML(self, xmlWriter, otFont):
+ import string
+ items = self.substitutions.items()
+ items.sort()
+ for input, output in items:
+ xmlWriter.simpletag("Subst", [("in", input), ("out", string.join(output, ","))])
+ xmlWriter.newline()
+
+
+class Sequence:
+
+ def decompile(self, reader, otFont):
+ self.glyphs = []
+ for i in range(reader.readUShort()):
+ self.glyphs.append(otFont.getGlyphName(reader.readUShort()))
+
+ def compile(self, otFont):
+ xxx
+
+
+class AlternateSubst:
+
+ def decompile(self, reader, otFont):
+ format = reader.readUShort()
+ if format <> 1:
+ from fontTools import ttLib
+ raise ttLib.TTLibError, "unknown AlternateSubst format: %d" % format
+ coverage = reader.readTable(otCommon.CoverageTable, otFont)
+ glyphNames = coverage.getGlyphNames()
+ alternateSetCount = reader.readUShort()
+ self.alternateSet = alternateSet = {}
+ for i in range(alternateSetCount):
+ set = reader.readTable(AlternateSet, otFont)
+ alternateSet[glyphNames[i]] = set.glyphs
+
+ def compile(self, otFont):
+ xxx
+
+ def toXML(self, xmlWriter, otFont):
+ alternates = self.alternateSet.items()
+ alternates.sort()
+ for input, substList in alternates:
+ xmlWriter.begintag("AlternateSet", [("in", input)])
+ xmlWriter.newline()
+ for output in substList:
+ xmlWriter.simpletag("Subst", out=output)
+ xmlWriter.newline()
+ xmlWriter.endtag("AlternateSet")
+ xmlWriter.newline()
+
+
+class AlternateSet:
+
+ def decompile(self, reader, otFont):
+ glyphCount = reader.readUShort()
+ glyphIDs = reader.readUShortArray(glyphCount)
+ self.glyphs = map(otFont.getGlyphName, glyphIDs)
+
+ def compile(self, otFont):
+ xxx
+
+
+class LigatureSubst:
+
+ def decompile(self, reader, otFont):
+ format = reader.readUShort()
+ if format <> 1:
+ from fontTools import ttLib
+ raise ttLib.TTLibError, "unknown LigatureSubst format: %d" % format
+ coverage = reader.readTable(otCommon.CoverageTable, otFont)
+ glyphNames = coverage.getGlyphNames()
+ ligSetCount = reader.readUShort()
+ self.ligatures = ligatures = []
+ for i in range(ligSetCount):
+ firstGlyph = glyphNames[i]
+ ligSet = reader.readTable(LigatureSet, otFont)
+ for ligatureGlyph, components in ligSet.ligatures:
+ ligatures.append(((firstGlyph,) + tuple(components)), ligatureGlyph)
+
+ def compile(self, otFont):
+ xxx
+
+ def toXML(self, xmlWriter, otFont):
+ import string
+ for input, output in self.ligatures:
+ xmlWriter.simpletag("Subst", [("in", string.join(input, ",")), ("out", output)])
+ xmlWriter.newline()
+
+
+class LigatureSet:
+
+ def decompile(self, reader, otFont):
+ ligatureCount = reader.readUShort()
+ self.ligatures = ligatures = []
+ for i in range(ligatureCount):
+ lig = reader.readTable(Ligature, otFont)
+ ligatures.append((lig.ligatureGlyph, lig.components))
+
+ def compile(self, otFont):
+ xxx
+
+
+class Ligature:
+
+ def decompile(self, reader, otFont):
+ self.ligatureGlyph = otFont.getGlyphName(reader.readUShort())
+ compCount = reader.readUShort()
+ self.components = components = []
+ for i in range(compCount-1):
+ components.append(otFont.getGlyphName(reader.readUShort()))
+
+ def compile(self, otFont):
+ xxx
+
+
+class ContextSubst:
+
+ def decompile(self, reader, otFont):
+ format = reader.readUShort()
+ if format == 1:
+ self.decompileFormat1(reader, otFont)
+ elif format == 2:
+ self.decompileFormat2(reader, otFont)
+ elif format == 3:
+ self.decompileFormat3(reader, otFont)
+ else:
+ from fontTools import ttLib
+ raise ttLib.TTLibError, "unknown ContextSubst format: %d" % format
+
+ def decompileFormat1(self, reader, otFont):
+ xxx
+
+ def decompileFormat2(self, reader, otFont):
+ xxx
+
+ def decompileFormat3(self, reader, otFont):
+ glyphCount = reader.readUShort()
+ substCount = reader.readUShort()
+ coverage = []
+ for i in range(glyphCount):
+ coverage.append(reader.readTable(otCommon.CoverageTable, otFont))
+ self.substitutions = substitutions = []
+ for i in range(substCount):
+ lookupRecord = SubstLookupRecord()
+ lookupRecord.decompile(reader, otFont)
+ substitutions.append((coverage[i].getGlyphNames(), lookupRecord))
+
+ def compile(self, otFont):
+ xxx
+
+ def toXML(self, xmlWriter, otFont):
+ xmlWriter.comment("XXX")
+ xmlWriter.newline()
+
+
+class ChainContextSubst:
+
+ def decompile(self, reader, otFont):
+ self.format = reader.readUShort()
+ if self.format == 1:
+ self.decompileFormat1(reader, otFont)
+ elif self.format == 2:
+ self.decompileFormat2(reader, otFont)
+ elif self.format == 3:
+ self.decompileFormat3(reader, otFont)
+ else:
+ from fontTools import ttLib
+ raise ttLib.TTLibError, "unknown ChainContextSubst format: %d" % self.format
+
+ def decompileFormat1(self, reader, otFont):
+ pass
+
+ def decompileFormat2(self, reader, otFont):
+ pass
+
+ def decompileFormat3(self, reader, otFont):
+ pass
+
+ def compile(self, otFont):
+ xxx
+
+ def toXML(self, xmlWriter, otFont):
+ xmlWriter.comment("XXX")
+ xmlWriter.newline()
+
+
+lookupTypeClasses = {
+ 1: SingleSubst,
+ 2: MultipleSubst,
+ 3: AlternateSubst,
+ 4: LigatureSubst,
+ 5: ContextSubst,
+ 6: ChainContextSubst,
+}
+
+
+#
+# Shared classes
+#
+
+class SubstLookupRecord:
+
+ def decompile(self, reader, otFont):
+ self.sequenceIndex = reader.readUShort()
+ self.lookupListIndex = reader.readUShort()
+
+ def compile(self, otFont):
+ xxx
+
diff --git a/Lib/fontTools/ttLib/tables/L_T_S_H_.py b/Lib/fontTools/ttLib/tables/L_T_S_H_.py
new file mode 100644
index 0000000..20701d6
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/L_T_S_H_.py
@@ -0,0 +1,50 @@
+import DefaultTable
+import array
+import struct
+from fontTools.misc.textTools import safeEval
+
+# XXX I've lowered the strictness, to make sure Apple's own Chicago
+# XXX gets through. They're looking into it, I hope to raise the standards
+# XXX back to normal eventually.
+
+class table_L_T_S_H_(DefaultTable.DefaultTable):
+
+ def decompile(self, data, ttFont):
+ version, numGlyphs = struct.unpack(">HH", data[:4])
+ data = data[4:]
+ assert version == 0
+ assert len(data) == numGlyphs
+ # ouch: the assertion is not true in Chicago!
+ #assert numGlyphs == ttFont['maxp'].numGlyphs
+ yPels = array.array("B")
+ yPels.fromstring(data)
+ self.yPels = {}
+ for i in range(numGlyphs):
+ self.yPels[ttFont.getGlyphName(i)] = yPels[i]
+
+ def compile(self, ttFont):
+ version = 0
+ names = self.yPels.keys()
+ numGlyphs = len(names)
+ yPels = [0] * numGlyphs
+ # ouch: the assertion is not true in Chicago!
+ #assert len(self.yPels) == ttFont['maxp'].numGlyphs == numGlyphs
+ for name in names:
+ yPels[ttFont.getGlyphID(name)] = self.yPels[name]
+ yPels = array.array("B", yPels)
+ return struct.pack(">HH", version, numGlyphs) + yPels.tostring()
+
+ def toXML(self, writer, ttFont):
+ names = self.yPels.keys()
+ names.sort()
+ for name in names:
+ writer.simpletag("yPel", name=name, value=self.yPels[name])
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ if not hasattr(self, "yPels"):
+ self.yPels = {}
+ if name <> "yPel":
+ return # ignore unknown tags
+ self.yPels[attrs["name"]] = safeEval(attrs["value"])
+
diff --git a/Lib/fontTools/ttLib/tables/O_S_2f_2.py b/Lib/fontTools/ttLib/tables/O_S_2f_2.py
new file mode 100644
index 0000000..498275f
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/O_S_2f_2.py
@@ -0,0 +1,164 @@
+import DefaultTable
+import sstruct
+from fontTools.misc.textTools import safeEval, num2binary, binary2num
+
+# panose classification
+
+panoseFormat = """
+ bFamilyType: B
+ bSerifStyle: B
+ bWeight: B
+ bProportion: B
+ bContrast: B
+ bStrokeVariation: B
+ bArmStyle: B
+ bLetterForm: B
+ bMidline: B
+ bXHeight: B
+"""
+
+class Panose:
+
+ def toXML(self, writer, ttFont):
+ formatstring, names, fixes = sstruct.getformat(panoseFormat)
+ for name in names:
+ writer.simpletag(name, value=getattr(self, name))
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ setattr(self, name, safeEval(attrs["value"]))
+
+
+# 'sfnt' OS/2 and Windows Metrics table - 'OS/2'
+
+OS2_format_0 = """
+ > # big endian
+ version: H # version
+ xAvgCharWidth: h # average character width
+ usWeightClass: H # degree of thickness of strokes
+ usWidthClass: H # aspect ratio
+ fsType: h # type flags
+ ySubscriptXSize: h # subscript horizontal font size
+ ySubscriptYSize: h # subscript vertical font size
+ ySubscriptXOffset: h # subscript x offset
+ ySubscriptYOffset: h # subscript y offset
+ ySuperscriptXSize: h # superscript horizontal font size
+ ySuperscriptYSize: h # superscript vertical font size
+ ySuperscriptXOffset: h # superscript x offset
+ ySuperscriptYOffset: h # superscript y offset
+ yStrikeoutSize: h # strikeout size
+ yStrikeoutPosition: h # strikeout position
+ sFamilyClass: h # font family class and subclass
+ panose: 10s # panose classification number
+ ulUnicodeRange1: l # character range
+ ulUnicodeRange2: l # character range
+ ulUnicodeRange3: l # character range
+ ulUnicodeRange4: l # character range
+ achVendID: 4s # font vendor identification
+ fsSelection: H # font selection flags
+ fsFirstCharIndex: H # first unicode character index
+ fsLastCharIndex: H # last unicode character index
+ usTypoAscender: H # typographic ascender
+ usTypoDescender: H # typographic descender
+ usTypoLineGap: H # typographic line gap
+ usWinAscent: H # Windows ascender
+ usWinDescent: H # Windows descender
+"""
+
+OS2_format_1_addition = """
+ ulCodePageRange1: l
+ ulCodePageRange2: l
+"""
+
+OS2_format_2_addition = OS2_format_1_addition + """
+ sxHeight: h
+ sCapHeight: h
+ usDefaultChar: H
+ usBreakChar: H
+ usMaxContex: H
+"""
+
+bigendian = " > # big endian\n"
+
+OS2_format_1 = OS2_format_0 + OS2_format_1_addition
+OS2_format_2 = OS2_format_0 + OS2_format_2_addition
+OS2_format_1_addition = bigendian + OS2_format_1_addition
+OS2_format_2_addition = bigendian + OS2_format_2_addition
+
+
+class table_O_S_2f_2(DefaultTable.DefaultTable):
+
+ """the OS/2 table"""
+
+ def decompile(self, data, ttFont):
+ dummy, data = sstruct.unpack2(OS2_format_0, data, self)
+ if self.version == 1:
+ sstruct.unpack(OS2_format_1_addition, data, self)
+ elif self.version == 2:
+ sstruct.unpack(OS2_format_2_addition, data, self)
+ elif self.version <> 0:
+ from fontTools import ttLib
+ raise ttLib.TTLibError, "unknown format for OS/2 table: version %s" % self.version
+ self.panose = sstruct.unpack(panoseFormat, self.panose, Panose())
+
+ def compile(self, ttFont):
+ panose = self.panose
+ self.panose = sstruct.pack(panoseFormat, self.panose)
+ if self.version == 0:
+ data = sstruct.pack(OS2_format_0, self)
+ elif self.version == 1:
+ data = sstruct.pack(OS2_format_1, self)
+ elif self.version == 2:
+ data = sstruct.pack(OS2_format_2, self)
+ else:
+ from fontTools import ttLib
+ raise ttLib.TTLibError, "unknown format for OS/2 table: version %s" % self.version
+ self.panose = panose
+ return data
+
+ def toXML(self, writer, ttFont):
+ if self.version == 1:
+ format = OS2_format_1
+ elif self.version == 2:
+ format = OS2_format_2
+ else:
+ format = OS2_format_0
+ formatstring, names, fixes = sstruct.getformat(format)
+ for name in names:
+ value = getattr(self, name)
+ if type(value) == type(0L):
+ value = int(value)
+ if name=="panose":
+ writer.begintag("panose")
+ writer.newline()
+ value.toXML(writer, ttFont)
+ writer.endtag("panose")
+ elif name in ("ulUnicodeRange1", "ulUnicodeRange2",
+ "ulUnicodeRange3", "ulUnicodeRange4",
+ "ulCodePageRange1", "ulCodePageRange2"):
+ writer.simpletag(name, value=num2binary(value))
+ elif name in ("fsType", "fsSelection"):
+ writer.simpletag(name, value=num2binary(value, 16))
+ elif name == "achVendID":
+ writer.simpletag(name, value=repr(value)[1:-1])
+ else:
+ writer.simpletag(name, value=value)
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ if name == "panose":
+ self.panose = panose = Panose()
+ for element in content:
+ if type(element) == type(()):
+ panose.fromXML(element, ttFont)
+ elif name in ("ulUnicodeRange1", "ulUnicodeRange2",
+ "ulUnicodeRange3", "ulUnicodeRange4",
+ "ulCodePageRange1", "ulCodePageRange2",
+ "fsType", "fsSelection"):
+ setattr(self, name, binary2num(attrs["value"]))
+ elif name == "achVendID":
+ setattr(self, name, safeEval("'''" + attrs["value"] + "'''"))
+ else:
+ setattr(self, name, safeEval(attrs["value"]))
+
+
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__0.py b/Lib/fontTools/ttLib/tables/T_S_I__0.py
new file mode 100644
index 0000000..988d29a
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/T_S_I__0.py
@@ -0,0 +1,45 @@
+import DefaultTable
+import struct
+
+tsi0Format = '>HHl'
+
+def fixlongs((glyphID, textLength, textOffset)):
+ return int(glyphID), int(textLength), textOffset
+
+
+class table_T_S_I__0(DefaultTable.DefaultTable):
+
+ dependencies = ["TSI1"]
+
+ def decompile(self, data, ttFont):
+ numGlyphs = ttFont['maxp'].numGlyphs
+ indices = []
+ size = struct.calcsize(tsi0Format)
+ for i in range(numGlyphs + 5):
+ glyphID, textLength, textOffset = fixlongs(struct.unpack(tsi0Format, data[:size]))
+ indices.append((glyphID, textLength, textOffset))
+ data = data[size:]
+ assert len(data) == 0
+ assert indices[-5] == (0XFFFE, 0, 0xABFC1F34), "bad magic number"
+ self.indices = indices[:-5]
+ self.extra_indices = indices[-4:]
+
+ def compile(self, ttFont):
+ data = ""
+ size = struct.calcsize(tsi0Format)
+ for index, textLength, textOffset in self.indices:
+ data = data + struct.pack(tsi0Format, index, textLength, textOffset)
+ data = data + struct.pack(tsi0Format, 0XFFFE, 0, 0xABFC1F34)
+ for index, textLength, textOffset in self.extra_indices:
+ data = data + struct.pack(tsi0Format, index, textLength, textOffset)
+ return data
+
+ def set(self, indices, extra_indices):
+ # gets called by 'TSI1' or 'TSI3'
+ self.indices = indices
+ self.extra_indices = extra_indices
+
+ def toXML(self, writer, ttFont):
+ writer.comment("This table will be calculated by the compiler")
+ writer.newline()
+
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__1.py b/Lib/fontTools/ttLib/tables/T_S_I__1.py
new file mode 100644
index 0000000..8ee4e16
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/T_S_I__1.py
@@ -0,0 +1,116 @@
+import DefaultTable
+import string
+
+class table_T_S_I__1(DefaultTable.DefaultTable):
+
+ extras = {0xfffa: "ppgm", 0xfffb: "cvt", 0xfffc: "reserved", 0xfffd: "fpgm"}
+
+ indextable = "TSI0"
+
+ def decompile(self, data, ttFont):
+ indextable = ttFont[self.indextable]
+ self.glyphPrograms = {}
+ for i in range(len(indextable.indices)):
+ glyphID, textLength, textOffset = indextable.indices[i]
+ if textLength == 0x8000:
+ # Ugh. Hi Beat!
+ textLength = indextable.indices[i+1][1]
+ if textLength > 0x8000:
+ pass # XXX Hmmm.
+ text = data[textOffset:textOffset+textLength]
+ assert len(text) == textLength
+ if text:
+ self.glyphPrograms[ttFont.getGlyphName(glyphID)] = text
+
+ self.extraPrograms = {}
+ for i in range(len(indextable.extra_indices)):
+ extraCode, textLength, textOffset = indextable.extra_indices[i]
+ if textLength == 0x8000:
+ if extraName == "fpgm": # this is the last one
+ textLength = len(data) - textOffset
+ else:
+ textLength = indextable.extra_indices[i+1][1]
+ text = data[textOffset:textOffset+textLength]
+ assert len(text) == textLength
+ if text:
+ self.extraPrograms[self.extras[extraCode]] = text
+
+ def compile(self, ttFont):
+ data = ''
+ indextable = ttFont[self.indextable]
+ glyphNames = ttFont.getGlyphOrder()
+
+ indices = []
+ for i in range(len(glyphNames)):
+ if len(data) % 2:
+ data = data + "\015" # align on 2-byte boundaries, fill with return chars. Yum.
+ name = glyphNames[i]
+ if self.glyphPrograms.has_key(name):
+ text = self.glyphPrograms[name]
+ else:
+ text = ""
+ textLength = len(text)
+ if textLength >= 0x8000:
+ textLength = 0x8000 # XXX ???
+ indices.append((i, textLength, len(data)))
+ data = data + text
+
+ extra_indices = []
+ codes = self.extras.items()
+ codes.sort()
+ for i in range(len(codes)):
+ if len(data) % 2:
+ data = data + "\015" # align on 2-byte boundaries, fill with return chars.
+ code, name = codes[i]
+ if self.extraPrograms.has_key(name):
+ text = self.extraPrograms[name]
+ else:
+ text = ""
+ textLength = len(text)
+ if textLength >= 0x8000:
+ textLength = 0x8000 # XXX ???
+ extra_indices.append((code, textLength, len(data)))
+ data = data + text
+ indextable.set(indices, extra_indices)
+ return data
+
+ def toXML(self, writer, ttFont):
+ names = self.glyphPrograms.keys()
+ names.sort()
+ writer.newline()
+ for name in names:
+ text = self.glyphPrograms[name]
+ if not text:
+ continue
+ writer.begintag("glyphProgram", name=name)
+ writer.newline()
+ writer.write_noindent(string.replace(text, "\r", "\n"))
+ writer.newline()
+ writer.endtag("glyphProgram")
+ writer.newline()
+ writer.newline()
+ extra_names = self.extraPrograms.keys()
+ extra_names.sort()
+ for name in extra_names:
+ text = self.extraPrograms[name]
+ if not text:
+ continue
+ writer.begintag("extraProgram", name=name)
+ writer.newline()
+ writer.write_noindent(string.replace(text, "\r", "\n"))
+ writer.newline()
+ writer.endtag("extraProgram")
+ writer.newline()
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ if not hasattr(self, "glyphPrograms"):
+ self.glyphPrograms = {}
+ self.extraPrograms = {}
+ lines = string.split(string.replace(string.join(content, ""), "\r", "\n"), "\n")
+ text = string.join(lines[1:-1], "\r")
+ if name == "glyphProgram":
+ self.glyphPrograms[attrs["name"]] = text
+ elif name == "extraProgram":
+ self.extraPrograms[attrs["name"]] = text
+
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__2.py b/Lib/fontTools/ttLib/tables/T_S_I__2.py
new file mode 100644
index 0000000..15c02ab
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/T_S_I__2.py
@@ -0,0 +1,8 @@
+from fontTools import ttLib
+
+superclass = ttLib.getTableClass("TSI0")
+
+class table_T_S_I__2(superclass):
+
+ dependencies = ["TSI3"]
+
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__3.py b/Lib/fontTools/ttLib/tables/T_S_I__3.py
new file mode 100644
index 0000000..eb4087c
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/T_S_I__3.py
@@ -0,0 +1,11 @@
+from fontTools import ttLib
+
+superclass = ttLib.getTableClass("TSI1")
+
+class table_T_S_I__3(superclass):
+
+ extras = {0xfffa: "reserved0", 0xfffb: "reserved1", 0xfffc: "reserved2", 0xfffd: "reserved3"}
+
+ indextable = "TSI2"
+
+
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__5.py b/Lib/fontTools/ttLib/tables/T_S_I__5.py
new file mode 100644
index 0000000..35b9ee1
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/T_S_I__5.py
@@ -0,0 +1,42 @@
+import DefaultTable
+import array
+from fontTools import ttLib
+from fontTools.misc.textTools import safeEval
+
+
+class table_T_S_I__5(DefaultTable.DefaultTable):
+
+ def decompile(self, data, ttFont):
+ numGlyphs = ttFont['maxp'].numGlyphs
+ assert len(data) == 2 * numGlyphs
+ a = array.array("H")
+ a.fromstring(data)
+ if ttLib.endian <> "big":
+ a.byteswap()
+ self.glyphGrouping = {}
+ for i in range(numGlyphs):
+ self.glyphGrouping[ttFont.getGlyphName(i)] = a[i]
+
+ def compile(self, ttFont):
+ glyphNames = ttFont.getGlyphOrder()
+ a = array.array("H")
+ for i in range(len(glyphNames)):
+ a.append(self.glyphGrouping[glyphNames[i]])
+ if ttLib.endian <> "big":
+ a.byteswap()
+ return a.tostring()
+
+ def toXML(self, writer, ttFont):
+ names = self.glyphGrouping.keys()
+ names.sort()
+ for glyphName in names:
+ writer.simpletag("glyphgroup", name=glyphName, value=self.glyphGrouping[glyphName])
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ if not hasattr(self, "glyphGrouping"):
+ self.glyphGrouping = {}
+ if name <> "glyphgroup":
+ return
+ self.glyphGrouping[attrs["name"]] = safeEval(attrs["value"])
+
diff --git a/Lib/fontTools/ttLib/tables/__init__.py b/Lib/fontTools/ttLib/tables/__init__.py
new file mode 100644
index 0000000..2f17637
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/__init__.py
@@ -0,0 +1 @@
+# dummy file, so Python knows ttLib.tables is a subpackage
diff --git a/Lib/fontTools/ttLib/tables/_c_m_a_p.py b/Lib/fontTools/ttLib/tables/_c_m_a_p.py
new file mode 100644
index 0000000..b067ac6
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_c_m_a_p.py
@@ -0,0 +1,397 @@
+import DefaultTable
+import struct
+import string
+import array
+from fontTools import ttLib
+from fontTools.misc.textTools import safeEval, readHex
+
+
+class table__c_m_a_p(DefaultTable.DefaultTable):
+
+ def getcmap(self, platformID, platEncID):
+ for subtable in self.tables:
+ if (subtable.platformID == platformID and
+ subtable.platEncID == platEncID):
+ return subtable
+ return None # not found
+
+ def decompile(self, data, ttFont):
+ tableVersion, numSubTables = struct.unpack(">HH", data[:4])
+ self.tableVersion = int(tableVersion)
+ self.tables = tables = []
+ for i in range(numSubTables):
+ platformID, platEncID, offset = struct.unpack(
+ ">HHl", data[4+i*8:4+(i+1)*8])
+ platformID, platEncID = int(platformID), int(platEncID)
+ format, length = struct.unpack(">HH", data[offset:offset+4])
+ if not cmap_classes.has_key(format):
+ table = cmap_format_unknown(format)
+ else:
+ table = cmap_classes[format](format)
+ table.platformID = platformID
+ table.platEncID = platEncID
+ table.decompile(data[offset:offset+int(length)], ttFont)
+ tables.append(table)
+
+ def compile(self, ttFont):
+ self.tables.sort() # sort according to the spec; see CmapSubtable.__cmp__()
+ numSubTables = len(self.tables)
+ totalOffset = 4 + 8 * numSubTables
+ data = struct.pack(">HH", self.tableVersion, numSubTables)
+ tableData = ""
+ done = {} # remember the data so we can reuse the "pointers"
+ for table in self.tables:
+ chunk = table.compile(ttFont)
+ if done.has_key(chunk):
+ offset = done[chunk]
+ else:
+ offset = done[chunk] = totalOffset + len(tableData)
+ tableData = tableData + chunk
+ data = data + struct.pack(">HHl", table.platformID, table.platEncID, offset)
+ return data + tableData
+
+ def toXML(self, writer, ttFont):
+ writer.simpletag("tableVersion", version=self.tableVersion)
+ writer.newline()
+ for table in self.tables:
+ table.toXML(writer, ttFont)
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ if name == "tableVersion":
+ self.tableVersion = safeEval(attrs["version"])
+ return
+ if name[:12] <> "cmap_format_":
+ return
+ if not hasattr(self, "tables"):
+ self.tables = []
+ format = safeEval(name[12])
+ if not cmap_classes.has_key(format):
+ table = cmap_format_unknown(format)
+ else:
+ table = cmap_classes[format](format)
+ table.platformID = safeEval(attrs["platformID"])
+ table.platEncID = safeEval(attrs["platEncID"])
+ table.fromXML((name, attrs, content), ttFont)
+ self.tables.append(table)
+
+
+class CmapSubtable:
+
+ def __init__(self, format):
+ self.format = format
+
+ def toXML(self, writer, ttFont):
+ writer.begintag(self.__class__.__name__, [
+ ("platformID", self.platformID),
+ ("platEncID", self.platEncID),
+ ])
+ writer.newline()
+ writer.dumphex(self.compile(ttFont))
+ writer.endtag(self.__class__.__name__)
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ self.decompile(readHex(content), ttFont)
+
+ def __cmp__(self, other):
+ # implemented so that list.sort() sorts according to the cmap spec.
+ selfTuple = (
+ self.platformID,
+ self.platEncID,
+ self.version,
+ self.__dict__)
+ otherTuple = (
+ other.platformID,
+ other.platEncID,
+ other.version,
+ other.__dict__)
+ return cmp(selfTuple, otherTuple)
+
+
+class cmap_format_0(CmapSubtable):
+
+ def decompile(self, data, ttFont):
+ format, length, version = struct.unpack(">HHH", data[:6])
+ self.version = int(version)
+ assert len(data) == 262 == length
+ glyphIdArray = array.array("B")
+ glyphIdArray.fromstring(data[6:])
+ self.cmap = cmap = {}
+ for charCode in range(len(glyphIdArray)):
+ cmap[charCode] = ttFont.getGlyphName(glyphIdArray[charCode])
+
+ def compile(self, ttFont):
+ charCodes = self.cmap.keys()
+ charCodes.sort()
+ assert charCodes == range(256) # charCodes[charCode] == charCode
+ for charCode in charCodes:
+ # reusing the charCodes list!
+ charCodes[charCode] = ttFont.getGlyphID(self.cmap[charCode])
+ glyphIdArray = array.array("B", charCodes)
+ data = struct.pack(">HHH", 0, 262, self.version) + glyphIdArray.tostring()
+ assert len(data) == 262
+ return data
+
+ def toXML(self, writer, ttFont):
+ writer.begintag(self.__class__.__name__, [
+ ("platformID", self.platformID),
+ ("platEncID", self.platEncID),
+ ("version", self.version),
+ ])
+ writer.newline()
+ items = self.cmap.items()
+ items.sort()
+ for code, name in items:
+ writer.simpletag("map", code=hex(code), name=name)
+ writer.newline()
+ writer.endtag(self.__class__.__name__)
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ self.version = safeEval(attrs["version"])
+ self.cmap = {}
+ for element in content:
+ if type(element) <> type(()):
+ continue
+ name, attrs, content = element
+ if name <> "map":
+ continue
+ self.cmap[safeEval(attrs["code"])] = attrs["name"]
+
+
+
+class cmap_format_2(CmapSubtable):
+
+ def decompile(self, data, ttFont):
+ format, length, version = struct.unpack(">HHH", data[:6])
+ self.version = int(version)
+ self.data = data
+
+ def compile(self, ttFont):
+ return self.data
+
+
+cmap_format_4_format = ">7H"
+
+#uint16 endCode[segCount] # Ending character code for each segment, last = 0xFFFF.
+#uint16 reservedPad # This value should be zero
+#uint16 startCode[segCount] # Starting character code for each segment
+#uint16 idDelta[segCount] # Delta for all character codes in segment
+#uint16 idRangeOffset[segCount] # Offset in bytes to glyph indexArray, or 0
+#uint16 glyphIndexArray[variable] # Glyph index array
+
+class cmap_format_4(CmapSubtable):
+
+ def decompile(self, data, ttFont):
+ (format, length, self.version, segCountX2,
+ searchRange, entrySelector, rangeShift) = \
+ struct.unpack(cmap_format_4_format, data[:14])
+ assert len(data) == length, "corrupt cmap table (%d, %d)" % (len(data), length)
+ data = data[14:]
+ segCountX2 = int(segCountX2)
+ segCount = segCountX2 / 2
+
+ allcodes = array.array("H")
+ allcodes.fromstring(data)
+ if ttLib.endian <> "big":
+ allcodes.byteswap()
+
+ # divide the data
+ endCode = allcodes[:segCount]
+ allcodes = allcodes[segCount+1:]
+ startCode = allcodes[:segCount]
+ allcodes = allcodes[segCount:]
+ idDelta = allcodes[:segCount]
+ allcodes = allcodes[segCount:]
+ idRangeOffset = allcodes[:segCount]
+ glyphIndexArray = allcodes[segCount:]
+
+ # build 2-byte character mapping
+ cmap = {}
+ for i in range(len(startCode) - 1): # don't do 0xffff!
+ for charCode in range(startCode[i], endCode[i] + 1):
+ rangeOffset = idRangeOffset[i]
+ if rangeOffset == 0:
+ glyphID = charCode + idDelta[i]
+ else:
+ # *someone* needs to get killed.
+ index = idRangeOffset[i] / 2 + (charCode - startCode[i]) + i - len(idRangeOffset)
+ if glyphIndexArray[index] <> 0: # if not missing glyph
+ glyphID = glyphIndexArray[index] + idDelta[i]
+ else:
+ glyphID = 0 # missing glyph
+ cmap[charCode] = ttFont.getGlyphName(glyphID % 0x10000)
+ self.cmap = cmap
+
+ def compile(self, ttFont):
+ from fontTools.ttLib.sfnt import maxpoweroftwo
+
+ codes = self.cmap.items()
+ codes.sort()
+
+ # build startCode and endCode lists
+ last = codes[0][0]
+ endCode = []
+ startCode = [last]
+ for charCode, glyphName in codes[1:]: # skip the first code, it's the first start code
+ if charCode == last + 1:
+ last = charCode
+ continue
+ endCode.append(last)
+ startCode.append(charCode)
+ last = charCode
+ endCode.append(last)
+ startCode.append(0xffff)
+ endCode.append(0xffff)
+
+ # build up rest of cruft.
+ idDelta = []
+ idRangeOffset = []
+ glyphIndexArray = []
+
+ for i in range(len(endCode)-1): # skip the closing codes (0xffff)
+ indices = []
+ for charCode in range(startCode[i], endCode[i]+1):
+ indices.append(ttFont.getGlyphID(self.cmap[charCode]))
+ if indices == range(indices[0], indices[0] + len(indices)):
+ idDelta.append((indices[0] - startCode[i]) % 0x10000)
+ idRangeOffset.append(0)
+ else:
+ # someone *definitely* needs to get killed.
+ idDelta.append(0)
+ idRangeOffset.append(2 * (len(endCode) + len(glyphIndexArray) - i))
+ glyphIndexArray = glyphIndexArray + indices
+ idDelta.append(1) # 0xffff + 1 == (tadaa!) 0. So this end code maps to .notdef
+ idRangeOffset.append(0)
+
+ # Insane.
+ segCount = len(endCode)
+ segCountX2 = segCount * 2
+ maxexponent = maxpoweroftwo(segCount)
+ searchRange = 2 * (2 ** maxexponent)
+ entrySelector = maxexponent
+ rangeShift = 2 * segCount - searchRange
+
+ allcodes = array.array("H",
+ endCode + [0] + startCode + idDelta + idRangeOffset + glyphIndexArray)
+ if ttLib.endian <> "big":
+ allcodes.byteswap()
+ data = allcodes.tostring()
+ length = struct.calcsize(cmap_format_4_format) + len(data)
+ header = struct.pack(cmap_format_4_format, self.format, length, self.version,
+ segCountX2, searchRange, entrySelector, rangeShift)
+ return header + data
+
+ def toXML(self, writer, ttFont):
+ from fontTools.unicode import Unicode
+ codes = self.cmap.items()
+ codes.sort()
+ writer.begintag(self.__class__.__name__, [
+ ("platformID", self.platformID),
+ ("platEncID", self.platEncID),
+ ("version", self.version),
+ ])
+ writer.newline()
+
+ for code, name in codes:
+ writer.simpletag("map", code=hex(code), name=name)
+ writer.comment(Unicode[code])
+ writer.newline()
+
+ writer.endtag(self.__class__.__name__)
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ self.version = safeEval(attrs["version"])
+ self.cmap = {}
+ for element in content:
+ if type(element) <> type(()):
+ continue
+ name, attrs, content = element
+ if name <> "map":
+ continue
+ self.cmap[safeEval(attrs["code"])] = attrs["name"]
+
+
+class cmap_format_6(CmapSubtable):
+
+ def decompile(self, data, ttFont):
+ format, length, version, firstCode, entryCount = struct.unpack(
+ ">HHHHH", data[:10])
+ self.version = int(version)
+ firstCode = int(firstCode)
+ self.version = int(version)
+ data = data[10:]
+ assert len(data) == 2 * entryCount
+ glyphIndexArray = array.array("H")
+ glyphIndexArray.fromstring(data)
+ if ttLib.endian <> "big":
+ glyphIndexArray.byteswap()
+ self.cmap = cmap = {}
+ for i in range(len(glyphIndexArray)):
+ glyphID = glyphIndexArray[i]
+ glyphName = ttFont.getGlyphName(glyphID)
+ cmap[i+firstCode] = glyphName
+
+ def compile(self, ttFont):
+ codes = self.cmap.keys()
+ codes.sort()
+ assert codes == range(codes[0], codes[0] + len(codes))
+ glyphIndexArray = array.array("H", [0] * len(codes))
+ firstCode = codes[0]
+ for i in range(len(codes)):
+ code = codes[i]
+ glyphIndexArray[code-firstCode] = ttFont.getGlyphID(self.cmap[code])
+ if ttLib.endian <> "big":
+ glyphIndexArray.byteswap()
+ data = glyphIndexArray.tostring()
+ header = struct.pack(">HHHHH",
+ 6, len(data) + 10, self.version, firstCode, len(self.cmap))
+ return header + data
+
+ def toXML(self, writer, ttFont):
+ codes = self.cmap.items()
+ codes.sort()
+ writer.begintag(self.__class__.__name__, [
+ ("platformID", self.platformID),
+ ("platEncID", self.platEncID),
+ ("version", self.version),
+ ])
+ writer.newline()
+
+ for code, name in codes:
+ writer.simpletag("map", code=hex(code), name=name)
+ writer.newline()
+
+ writer.endtag(self.__class__.__name__)
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ self.version = safeEval(attrs["version"])
+ self.cmap = {}
+ for element in content:
+ if type(element) <> type(()):
+ continue
+ name, attrs, content = element
+ if name <> "map":
+ continue
+ self.cmap[safeEval(attrs["code"])] = attrs["name"]
+
+
+class cmap_format_unknown(CmapSubtable):
+
+ def decompile(self, data, ttFont):
+ self.data = data
+
+ def compile(self, ttFont):
+ return self.data
+
+
+cmap_classes = {
+ 0: cmap_format_0,
+ 2: cmap_format_2,
+ 4: cmap_format_4,
+ 6: cmap_format_6,
+ }
+
+
diff --git a/Lib/fontTools/ttLib/tables/_c_v_t.py b/Lib/fontTools/ttLib/tables/_c_v_t.py
new file mode 100644
index 0000000..ced5523
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_c_v_t.py
@@ -0,0 +1,48 @@
+import DefaultTable
+import array
+from fontTools import ttLib
+from fontTools.misc.textTools import safeEval
+
+class table__c_v_t(DefaultTable.DefaultTable):
+
+ def decompile(self, data, ttFont):
+ values = array.array("h")
+ values.fromstring(data)
+ if ttLib.endian <> "big":
+ values.byteswap()
+ self.values = values
+
+ def compile(self, ttFont):
+ values = self.values[:]
+ if ttLib.endian <> "big":
+ values.byteswap()
+ return values.tostring()
+
+ def toXML(self, writer, ttFont):
+ for i in range(len(self.values)):
+ value = self.values[i]
+ writer.simpletag("cv", value=value, index=i)
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ if not hasattr(self, "values"):
+ self.values = array.array("h")
+ if name == "cv":
+ index = safeEval(attrs["index"])
+ value = safeEval(attrs["value"])
+ for i in range(1 + index - len(self.values)):
+ self.values.append(0)
+ self.values[index] = value
+
+ def __len__(self):
+ return len(self.values)
+
+ def __getitem__(self, index):
+ return self.values[index]
+
+ def __setitem__(self, index, value):
+ self.values[index] = value
+
+ def __delitem__(self, index):
+ del self.values[index]
+
diff --git a/Lib/fontTools/ttLib/tables/_f_p_g_m.py b/Lib/fontTools/ttLib/tables/_f_p_g_m.py
new file mode 100644
index 0000000..f7cc8a3
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_f_p_g_m.py
@@ -0,0 +1,14 @@
+import DefaultTable
+import array
+
+class table__f_p_g_m(DefaultTable.DefaultTable):
+
+ def decompile(self, data, ttFont):
+ self.fpgm = data
+
+ def compile(self, ttFont):
+ return self.fpgm
+
+ def __len__(self):
+ return len(self.fpgm)
+
diff --git a/Lib/fontTools/ttLib/tables/_g_a_s_p.py b/Lib/fontTools/ttLib/tables/_g_a_s_p.py
new file mode 100644
index 0000000..e49cceb
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_g_a_s_p.py
@@ -0,0 +1,46 @@
+import DefaultTable
+import struct
+from fontTools.misc.textTools import safeEval
+
+
+GASP_DOGRAY = 0x0002
+GASP_GRIDFIT = 0x0001
+
+class table__g_a_s_p(DefaultTable.DefaultTable):
+
+ def decompile(self, data, ttFont):
+ self.version, numRanges = struct.unpack(">HH", data[:4])
+ assert self.version == 0, "unknown 'gasp' format: %s" % self.version
+ data = data[4:]
+ self.gaspRange = {}
+ for i in range(numRanges):
+ rangeMaxPPEM, rangeGaspBehavior = struct.unpack(">HH", data[:4])
+ self.gaspRange[int(rangeMaxPPEM)] = int(rangeGaspBehavior)
+ data = data[4:]
+ assert not data, "too much data"
+
+ def compile(self, ttFont):
+ numRanges = len(self.gaspRange)
+ data = struct.pack(">HH", 0, numRanges)
+ items = self.gaspRange.items()
+ items.sort()
+ for rangeMaxPPEM, rangeGaspBehavior in items:
+ data = data + struct.pack(">HH", rangeMaxPPEM, rangeGaspBehavior)
+ return data
+
+ def toXML(self, writer, ttFont):
+ items = self.gaspRange.items()
+ items.sort()
+ for rangeMaxPPEM, rangeGaspBehavior in items:
+ writer.simpletag("gaspRange", [
+ ("rangeMaxPPEM", rangeMaxPPEM),
+ ("rangeGaspBehavior", rangeGaspBehavior)])
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ if name <> "gaspRange":
+ return
+ if not hasattr(self, "gaspRange"):
+ self.gaspRange = {}
+ self.gaspRange[safeEval(attrs["rangeMaxPPEM"])] = safeEval(attrs["rangeGaspBehavior"])
+
diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
new file mode 100644
index 0000000..29f9be5
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
@@ -0,0 +1,778 @@
+"""_g_l_y_f.py -- Converter classes for the 'glyf' table."""
+
+
+#
+# The Apple and MS rasterizers behave differently for
+# scaled composite components: one does scale first and then translate
+# and the other does it vice versa. MS defined some flags to indicate
+# the difference, but it seems nobody actually _sets_ those flags.
+#
+# Funny thing: Apple seems to _only_ do their thing in the
+# WE_HAVE_A_SCALE (eg. Chicago) case, and not when it's WE_HAVE_AN_X_AND_Y_SCALE
+# (eg. Charcoal)...
+#
+SCALE_COMPONENT_OFFSET_DEFAULT = 0 # 0 == MS, 1 == Apple
+
+
+import struct, sstruct
+import DefaultTable
+from fontTools import ttLib
+from fontTools.misc.textTools import safeEval, readHex
+import array
+import Numeric
+import types
+
+class table__g_l_y_f(DefaultTable.DefaultTable):
+
+ def decompile(self, data, ttFont):
+ loca = ttFont['loca']
+ last = loca[0]
+ self.glyphs = {}
+ self.glyphOrder = []
+ self.glyphOrder = glyphOrder = ttFont.getGlyphOrder()
+ for i in range(0, len(loca)-1):
+ glyphName = glyphOrder[i]
+ next = loca[i+1]
+ glyphdata = data[last:next]
+ if len(glyphdata) <> (next - last):
+ raise ttLib.TTLibError, "not enough 'glyf' table data"
+ glyph = Glyph(glyphdata)
+ self.glyphs[glyphName] = glyph
+ last = next
+ if len(data) > next:
+ raise ttLib.TTLibError, "too much 'glyf' table data"
+
+ def compile(self, ttFont):
+ import string
+ locations = []
+ currentLocation = 0
+ dataList = []
+ for glyphName in ttFont.getGlyphOrder():
+ glyph = self[glyphName]
+ glyphData = glyph.compile(self)
+ locations.append(currentLocation)
+ currentLocation = currentLocation + len(glyphData)
+ dataList.append(glyphData)
+ locations.append(currentLocation)
+ data = string.join(dataList, "")
+ ttFont['loca'].set(locations)
+ ttFont['maxp'].numGlyphs = len(self.glyphs)
+ return data
+
+ def toXML(self, writer, ttFont, progress=None, compactGlyphs=0):
+ writer.newline()
+ glyphOrder = ttFont.getGlyphOrder()
+ writer.begintag("GlyphOrder")
+ writer.newline()
+ for i in range(len(glyphOrder)):
+ glyphName = glyphOrder[i]
+ writer.simpletag("GlyphID", id=i, name=glyphName)
+ writer.newline()
+ writer.endtag("GlyphOrder")
+ writer.newline()
+ writer.newline()
+ glyphNames = ttFont.getGlyphNames()
+ writer.comment("The xMin, yMin, xMax and yMax values\nwill be recalculated by the compiler.")
+ writer.newline()
+ writer.newline()
+ for glyphName in glyphNames:
+ if progress:
+ progress.setlabel("Dumping 'glyf' table... (%s)" % glyphName)
+ progress.increment()
+ glyph = self[glyphName]
+ if glyph.numberOfContours:
+ writer.begintag('TTGlyph', [
+ ("name", glyphName),
+ ("xMin", glyph.xMin),
+ ("yMin", glyph.yMin),
+ ("xMax", glyph.xMax),
+ ("yMax", glyph.yMax),
+ ])
+ writer.newline()
+ glyph.toXML(writer, ttFont)
+ if compactGlyphs:
+ glyph.compact(self)
+ writer.endtag('TTGlyph')
+ writer.newline()
+ else:
+ writer.simpletag('TTGlyph', name=glyphName)
+ writer.comment("contains no outline data")
+ writer.newline()
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ if name == "GlyphOrder":
+ glyphOrder = []
+ for element in content:
+ if type(element) == types.StringType:
+ continue
+ name, attrs, content = element
+ if name == "GlyphID":
+ index = safeEval(attrs["id"])
+ glyphName = attrs["name"]
+ glyphOrder = glyphOrder + (1 + index - len(glyphOrder)) * [".notdef"]
+ glyphOrder[index] = glyphName
+ ttFont.setGlyphOrder(glyphOrder)
+ elif name == "TTGlyph":
+ if not hasattr(self, "glyphs"):
+ self.glyphs = {}
+ glyphName = attrs["name"]
+ if ttFont.verbose:
+ ttLib.debugmsg("unpacking glyph '%s'" % glyphName)
+ glyph = Glyph()
+ for attr in ['xMin', 'yMin', 'xMax', 'yMax']:
+ setattr(glyph, attr, safeEval(attrs.get(attr, '0')))
+ self.glyphs[glyphName] = glyph
+ for element in content:
+ if type(element) == types.StringType:
+ continue
+ glyph.fromXML(element, ttFont)
+
+ def setGlyphOrder(self, glyphOrder):
+ self.glyphOrder = glyphOrder
+
+ def getGlyphName(self, glyphID):
+ return self.glyphOrder[glyphID]
+
+ def getGlyphID(self, glyphName):
+ # XXX optimize with reverse dict!!!
+ return self.glyphOrder.index(glyphName)
+
+ #def keys(self):
+ # return self.glyphOrder[:]
+ #
+ #def has_key(self, glyphName):
+ # return self.glyphs.has_key(glyphName)
+ #
+ def __getitem__(self, glyphName):
+ glyph = self.glyphs[glyphName]
+ glyph.expand(self)
+ return glyph
+
+ def __setitem__(self, glyphName, glyph):
+ self.glyphs[glyphName] = glyph
+ if glyphName not in self.glyphOrder:
+ self.glyphOrder.append(glyphName)
+
+ def __delitem__(self, glyphName):
+ del self.glyphs[glyphName]
+ self.glyphOrder.remove(glyphName)
+
+ def __len__(self):
+ assert len(self.glyphOrder) == len(self.glyphs)
+ return len(self.glyphs)
+
+
+glyphHeaderFormat = """
+ > # big endian
+ numberOfContours: h
+ xMin: h
+ yMin: h
+ xMax: h
+ yMax: h
+"""
+
+# flags
+flagOnCurve = 0x01
+flagXShort = 0x02
+flagYShort = 0x04
+flagRepeat = 0x08
+flagXsame = 0x10
+flagYsame = 0x20
+flagReserved1 = 0x40
+flagReserved2 = 0x80
+
+
+ARG_1_AND_2_ARE_WORDS = 0x0001 # if set args are words otherwise they are bytes
+ARGS_ARE_XY_VALUES = 0x0002 # if set args are xy values, otherwise they are points
+ROUND_XY_TO_GRID = 0x0004 # for the xy values if above is true
+WE_HAVE_A_SCALE = 0x0008 # Sx = Sy, otherwise scale == 1.0
+NON_OVERLAPPING = 0x0010 # set to same value for all components (obsolete!)
+MORE_COMPONENTS = 0x0020 # indicates at least one more glyph after this one
+WE_HAVE_AN_X_AND_Y_SCALE = 0x0040 # Sx, Sy
+WE_HAVE_A_TWO_BY_TWO = 0x0080 # t00, t01, t10, t11
+WE_HAVE_INSTRUCTIONS = 0x0100 # instructions follow
+USE_MY_METRICS = 0x0200 # apply these metrics to parent glyph
+OVERLAP_COMPOUND = 0x0400 # used by Apple in GX fonts
+SCALED_COMPONENT_OFFSET = 0x0800 # composite designed to have the component offset scaled (designed for Apple)
+UNSCALED_COMPONENT_OFFSET = 0x1000 # composite designed not to have the component offset scaled (designed for MS)
+
+
+class Glyph:
+
+ def __init__(self, data=""):
+ if not data:
+ # empty char
+ self.numberOfContours = 0
+ return
+ self.data = data
+
+ def compact(self, glyfTable):
+ data = self.compile(glyfTable)
+ self.__dict__.clear()
+ self.data = data
+
+ def expand(self, glyfTable):
+ if not hasattr(self, "data"):
+ # already unpacked
+ return
+ dummy, data = sstruct.unpack2(glyphHeaderFormat, self.data, self)
+ del self.data
+ if self.numberOfContours == -1:
+ self.decompileComponents(data, glyfTable)
+ else:
+ self.decompileCoordinates(data)
+
+ def compile(self, glyfTable):
+ if hasattr(self, "data"):
+ return self.data
+ if self.numberOfContours == 0:
+ return ""
+ self.recalcBounds(glyfTable)
+ data = sstruct.pack(glyphHeaderFormat, self)
+ if self.numberOfContours == -1:
+ data = data + self.compileComponents(glyfTable)
+ else:
+ data = data + self.compileCoordinates()
+ # from the spec: "Note that the local offsets should be word-aligned"
+ if len(data) % 2:
+ # ...so if the length of the data is odd, append a null byte
+ data = data + "\0"
+ return data
+
+ def toXML(self, writer, ttFont):
+ if self.numberOfContours == -1:
+ for compo in self.components:
+ compo.toXML(writer, ttFont)
+ if hasattr(self, "instructions"):
+ writer.begintag("instructions")
+ writer.newline()
+ writer.dumphex(self.instructions)
+ writer.endtag("instructions")
+ writer.newline()
+ else:
+ last = 0
+ for i in range(self.numberOfContours):
+ writer.begintag("contour")
+ writer.newline()
+ for j in range(last, self.endPtsOfContours[i] + 1):
+ writer.simpletag("pt", [
+ ("x", self.coordinates[j][0]),
+ ("y", self.coordinates[j][1]),
+ ("on", self.flags[j] & flagOnCurve)])
+ writer.newline()
+ last = self.endPtsOfContours[i] + 1
+ writer.endtag("contour")
+ writer.newline()
+ if self.numberOfContours:
+ writer.begintag("instructions")
+ writer.newline()
+ writer.dumphex(self.instructions)
+ writer.endtag("instructions")
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ if name == "contour":
+ self.numberOfContours = self.numberOfContours + 1
+ if self.numberOfContours < 0:
+ raise ttLib.TTLibError, "can't mix composites and contours in glyph"
+ coordinates = []
+ flags = []
+ for element in content:
+ if type(element) == types.StringType:
+ continue
+ name, attrs, content = element
+ if name <> "pt":
+ continue # ignore anything but "pt"
+ coordinates.append([safeEval(attrs["x"]), safeEval(attrs["y"])])
+ flags.append(not not safeEval(attrs["on"]))
+ coordinates = Numeric.array(coordinates, Numeric.Int16)
+ flags = Numeric.array(flags, Numeric.Int8)
+ if not hasattr(self, "coordinates"):
+ self.coordinates = coordinates
+ self.flags = flags
+ self.endPtsOfContours = [len(coordinates)-1]
+ else:
+ self.coordinates = Numeric.concatenate((self.coordinates, coordinates))
+ self.flags = Numeric.concatenate((self.flags, flags))
+ self.endPtsOfContours.append(len(self.coordinates)-1)
+ elif name == "component":
+ if self.numberOfContours > 0:
+ raise ttLib.TTLibError, "can't mix composites and contours in glyph"
+ self.numberOfContours = -1
+ if not hasattr(self, "components"):
+ self.components = []
+ component = GlyphComponent()
+ self.components.append(component)
+ component.fromXML((name, attrs, content), ttFont)
+ elif name == "instructions":
+ self.instructions = readHex(content)
+
+ def getCompositeMaxpValues(self, glyfTable, maxComponentDepth=1):
+ assert self.numberOfContours == -1
+ nContours = 0
+ nPoints = 0
+ for compo in self.components:
+ baseGlyph = glyfTable[compo.glyphName]
+ if baseGlyph.numberOfContours == 0:
+ continue
+ elif baseGlyph.numberOfContours > 0:
+ nP, nC = baseGlyph.getMaxpValues()
+ else:
+ nP, nC, maxComponentDepth = baseGlyph.getCompositeMaxpValues(
+ glyfTable, maxComponentDepth + 1)
+ nPoints = nPoints + nP
+ nContours = nContours + nC
+ return nPoints, nContours, maxComponentDepth
+
+ def getMaxpValues(self):
+ assert self.numberOfContours > 0
+ return len(self.coordinates), len(self.endPtsOfContours)
+
+ def decompileComponents(self, data, glyfTable):
+ self.components = []
+ more = 1
+ haveInstructions = 0
+ while more:
+ component = GlyphComponent()
+ more, haveInstr, data = component.decompile(data, glyfTable)
+ haveInstructions = haveInstructions | haveInstr
+ self.components.append(component)
+ if haveInstructions:
+ numInstructions, = struct.unpack(">h", data[:2])
+ data = data[2:]
+ self.instructions = data[:numInstructions]
+ data = data[numInstructions:]
+ assert len(data) in (0, 1), "bad composite data"
+
+ def decompileCoordinates(self, data):
+ endPtsOfContours = array.array("h")
+ endPtsOfContours.fromstring(data[:2*self.numberOfContours])
+ if ttLib.endian <> "big":
+ endPtsOfContours.byteswap()
+ self.endPtsOfContours = endPtsOfContours.tolist()
+
+ data = data[2*self.numberOfContours:]
+
+ instructionLength, = struct.unpack(">h", data[:2])
+ data = data[2:]
+ self.instructions = data[:instructionLength]
+ data = data[instructionLength:]
+ nCoordinates = self.endPtsOfContours[-1] + 1
+ flags, xCoordinates, yCoordinates = \
+ self.decompileCoordinatesRaw(nCoordinates, data)
+
+ # fill in repetitions and apply signs
+ coordinates = Numeric.zeros((nCoordinates, 2), Numeric.Int16)
+ xIndex = 0
+ yIndex = 0
+ for i in range(nCoordinates):
+ flag = flags[i]
+ # x coordinate
+ if flag & flagXShort:
+ if flag & flagXsame:
+ x = xCoordinates[xIndex]
+ else:
+ x = -xCoordinates[xIndex]
+ xIndex = xIndex + 1
+ elif flag & flagXsame:
+ x = 0
+ else:
+ x = xCoordinates[xIndex]
+ xIndex = xIndex + 1
+ # y coordinate
+ if flag & flagYShort:
+ if flag & flagYsame:
+ y = yCoordinates[yIndex]
+ else:
+ y = -yCoordinates[yIndex]
+ yIndex = yIndex + 1
+ elif flag & flagYsame:
+ y = 0
+ else:
+ y = yCoordinates[yIndex]
+ yIndex = yIndex + 1
+ coordinates[i] = (x, y)
+ assert xIndex == len(xCoordinates)
+ assert yIndex == len(yCoordinates)
+ # convert relative to absolute coordinates
+ self.coordinates = Numeric.add.accumulate(coordinates)
+ # discard all flags but for "flagOnCurve"
+ if hasattr(Numeric, "__version__"):
+ self.flags = Numeric.bitwise_and(flags, flagOnCurve).astype(Numeric.Int8)
+ else:
+ self.flags = Numeric.boolean_and(flags, flagOnCurve).astype(Numeric.Int8)
+
+ def decompileCoordinatesRaw(self, nCoordinates, data):
+ # unpack flags and prepare unpacking of coordinates
+ flags = Numeric.array([0] * nCoordinates, Numeric.Int8)
+ # Warning: deep Python trickery going on. We use the struct module to unpack
+ # the coordinates. We build a format string based on the flags, so we can
+ # unpack the coordinates in one struct.unpack() call.
+ xFormat = ">" # big endian
+ yFormat = ">" # big endian
+ i = j = 0
+ while 1:
+ flag = ord(data[i])
+ i = i + 1
+ repeat = 1
+ if flag & flagRepeat:
+ repeat = ord(data[i]) + 1
+ i = i + 1
+ for k in range(repeat):
+ if flag & flagXShort:
+ xFormat = xFormat + 'B'
+ elif not (flag & flagXsame):
+ xFormat = xFormat + 'h'
+ if flag & flagYShort:
+ yFormat = yFormat + 'B'
+ elif not (flag & flagYsame):
+ yFormat = yFormat + 'h'
+ flags[j] = flag
+ j = j + 1
+ if j >= nCoordinates:
+ break
+ assert j == nCoordinates, "bad glyph flags"
+ data = data[i:]
+ # unpack raw coordinates, krrrrrr-tching!
+ xDataLen = struct.calcsize(xFormat)
+ yDataLen = struct.calcsize(yFormat)
+ if (len(data) - (xDataLen + yDataLen)) not in (0, 1):
+ raise ttLib.TTLibError, "bad glyph record"
+ xCoordinates = struct.unpack(xFormat, data[:xDataLen])
+ yCoordinates = struct.unpack(yFormat, data[xDataLen:xDataLen+yDataLen])
+ return flags, xCoordinates, yCoordinates
+
+ def compileComponents(self, glyfTable):
+ data = ""
+ lastcomponent = len(self.components) - 1
+ more = 1
+ haveInstructions = 0
+ for i in range(len(self.components)):
+ if i == lastcomponent:
+ haveInstructions = hasattr(self, "instructions")
+ more = 0
+ compo = self.components[i]
+ data = data + compo.compile(more, haveInstructions, glyfTable)
+ if haveInstructions:
+ data = data + struct.pack(">h", len(self.instructions)) + self.instructions
+ return data
+
+
+ def compileCoordinates(self):
+ assert len(self.coordinates) == len(self.flags)
+ data = ""
+ endPtsOfContours = array.array("h", self.endPtsOfContours)
+ if ttLib.endian <> "big":
+ endPtsOfContours.byteswap()
+ data = data + endPtsOfContours.tostring()
+ data = data + struct.pack(">h", len(self.instructions))
+ data = data + self.instructions
+ nCoordinates = len(self.coordinates)
+
+ # make a copy
+ coordinates = self.coordinates.astype(self.coordinates.typecode())
+ # absolute to relative coordinates
+ coordinates[1:] = Numeric.subtract(coordinates[1:], coordinates[:-1])
+ flags = self.flags
+ compressedflags = []
+ xPoints = []
+ yPoints = []
+ xFormat = ">"
+ yFormat = ">"
+ lastflag = None
+ repeat = 0
+ for i in range(len(coordinates)):
+ # Oh, the horrors of TrueType
+ flag = self.flags[i]
+ x, y = coordinates[i]
+ # do x
+ if x == 0:
+ flag = flag | flagXsame
+ elif -255 <= x <= 255:
+ flag = flag | flagXShort
+ if x > 0:
+ flag = flag | flagXsame
+ else:
+ x = -x
+ xPoints.append(x)
+ xFormat = xFormat + 'B'
+ else:
+ xPoints.append(x)
+ xFormat = xFormat + 'h'
+ # do y
+ if y == 0:
+ flag = flag | flagYsame
+ elif -255 <= y <= 255:
+ flag = flag | flagYShort
+ if y > 0:
+ flag = flag | flagYsame
+ else:
+ y = -y
+ yPoints.append(y)
+ yFormat = yFormat + 'B'
+ else:
+ yPoints.append(y)
+ yFormat = yFormat + 'h'
+ # handle repeating flags
+ if flag == lastflag:
+ repeat = repeat + 1
+ if repeat == 1:
+ compressedflags.append(flag)
+ elif repeat > 1:
+ compressedflags[-2] = flag | flagRepeat
+ compressedflags[-1] = repeat
+ else:
+ compressedflags[-1] = repeat
+ else:
+ repeat = 0
+ compressedflags.append(flag)
+ lastflag = flag
+ data = data + array.array("B", compressedflags).tostring()
+ data = data + apply(struct.pack, (xFormat,)+tuple(xPoints))
+ data = data + apply(struct.pack, (yFormat,)+tuple(yPoints))
+ return data
+
+ def recalcBounds(self, glyfTable):
+ coordinates, endPts, flags = self.getCoordinates(glyfTable)
+ self.xMin, self.yMin = Numeric.minimum.reduce(coordinates)
+ self.xMax, self.yMax = Numeric.maximum.reduce(coordinates)
+
+ def getCoordinates(self, glyfTable):
+ if self.numberOfContours > 0:
+ return self.coordinates, self.endPtsOfContours, self.flags
+ elif self.numberOfContours == -1:
+ # it's a composite
+ allCoords = None
+ allFlags = None
+ allEndPts = None
+ for compo in self.components:
+ g = glyfTable[compo.glyphName]
+ coordinates, endPts, flags = g.getCoordinates(glyfTable)
+ if hasattr(compo, "firstpt"):
+ # move according to two reference points
+ move = allCoords[compo.firstpt] - coordinates[compo.secondpt]
+ else:
+ move = compo.x, compo.y
+
+ if not hasattr(compo, "transform"):
+ coordinates = coordinates + move # I love NumPy!
+ else:
+ apple_way = compo.flags & SCALED_COMPONENT_OFFSET
+ ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET
+ assert not (apple_way and ms_way)
+ if not (apple_way or ms_way):
+ scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT # see top of this file
+ else:
+ scale_component_offset = apple_way
+ if scale_component_offset:
+ # the Apple way: first move, then scale (ie. scale the component offset)
+ coordinates = coordinates + move
+ coordinates = Numeric.dot(coordinates, compo.transform)
+ else:
+ # the MS way: first scale, then move
+ coordinates = Numeric.dot(coordinates, compo.transform)
+ coordinates = coordinates + move
+ # due to the transformation the coords. are now floats;
+ # round them off nicely, and cast to short
+ coordinates = Numeric.floor(coordinates + 0.5).astype(Numeric.Int16)
+ if allCoords is None:
+ allCoords = coordinates
+ allEndPts = endPts
+ allFlags = flags
+ else:
+ allEndPts = allEndPts + (Numeric.array(endPts) + len(allCoords)).tolist()
+ allCoords = Numeric.concatenate((allCoords, coordinates))
+ allFlags = Numeric.concatenate((allFlags, flags))
+ return allCoords, allEndPts, allFlags
+ else:
+ return Numeric.array([], Numeric.Int16), [], Numeric.array([], Numeric.Int8)
+
+ def __cmp__(self, other):
+ if self.numberOfContours <= 0:
+ return cmp(self.__dict__, other.__dict__)
+ else:
+ if cmp(len(self.coordinates), len(other.coordinates)):
+ return 1
+ ctest = Numeric.alltrue(Numeric.alltrue(Numeric.equal(self.coordinates, other.coordinates)))
+ ftest = Numeric.alltrue(Numeric.equal(self.flags, other.flags))
+ if not ctest or not ftest:
+ return 1
+ return (
+ cmp(self.endPtsOfContours, other.endPtsOfContours) or
+ cmp(self.instructions, other.instructions)
+ )
+
+
+class GlyphComponent:
+
+ def __init__(self):
+ pass
+
+ def decompile(self, data, glyfTable):
+ flags, glyphID = struct.unpack(">HH", data[:4])
+ self.flags = int(flags)
+ glyphID = int(glyphID)
+ self.glyphName = glyfTable.getGlyphName(int(glyphID))
+ #print ">>", reprflag(self.flags)
+ data = data[4:]
+
+ if self.flags & ARG_1_AND_2_ARE_WORDS:
+ if self.flags & ARGS_ARE_XY_VALUES:
+ self.x, self.y = struct.unpack(">hh", data[:4])
+ else:
+ x, y = struct.unpack(">HH", data[:4])
+ self.firstpt, self.secondpt = int(x), int(y)
+ data = data[4:]
+ else:
+ if self.flags & ARGS_ARE_XY_VALUES:
+ self.x, self.y = struct.unpack(">bb", data[:2])
+ else:
+ x, y = struct.unpack(">BB", data[:4])
+ self.firstpt, self.secondpt = int(x), int(y)
+ data = data[2:]
+
+ if self.flags & WE_HAVE_A_SCALE:
+ scale, = struct.unpack(">h", data[:2])
+ self.transform = Numeric.array(
+ [[scale, 0], [0, scale]]) / float(0x4000) # fixed 2.14
+ data = data[2:]
+ elif self.flags & WE_HAVE_AN_X_AND_Y_SCALE:
+ xscale, yscale = struct.unpack(">hh", data[:4])
+ self.transform = Numeric.array(
+ [[xscale, 0], [0, yscale]]) / float(0x4000) # fixed 2.14
+ data = data[4:]
+ elif self.flags & WE_HAVE_A_TWO_BY_TWO:
+ (xscale, scale01,
+ scale10, yscale) = struct.unpack(">hhhh", data[:8])
+ self.transform = Numeric.array(
+ [[xscale, scale01], [scale10, yscale]]) / float(0x4000) # fixed 2.14
+ data = data[8:]
+ more = self.flags & MORE_COMPONENTS
+ haveInstructions = self.flags & WE_HAVE_INSTRUCTIONS
+ self.flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
+ SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
+ NON_OVERLAPPING)
+ return more, haveInstructions, data
+
+ def compile(self, more, haveInstructions, glyfTable):
+ data = ""
+
+ # reset all flags we will calculate ourselves
+ flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
+ SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
+ NON_OVERLAPPING)
+ if more:
+ flags = flags | MORE_COMPONENTS
+ if haveInstructions:
+ flags = flags | WE_HAVE_INSTRUCTIONS
+
+ if hasattr(self, "firstpt"):
+ if (0 <= self.firstpt <= 255) and (0 <= self.secondpt <= 255):
+ data = data + struct.pack(">BB", self.firstpt, self.secondpt)
+ else:
+ data = data + struct.pack(">HH", self.firstpt, self.secondpt)
+ flags = flags | ARG_1_AND_2_ARE_WORDS
+ else:
+ flags = flags | ARGS_ARE_XY_VALUES
+ if (-128 <= self.x <= 127) and (-128 <= self.y <= 127):
+ data = data + struct.pack(">bb", self.x, self.y)
+ else:
+ data = data + struct.pack(">hh", self.x, self.y)
+ flags = flags | ARG_1_AND_2_ARE_WORDS
+
+ if hasattr(self, "transform"):
+ # XXX needs more testing
+ transform = Numeric.floor(self.transform * 0x4000 + 0.5)
+ if transform[0][1] or transform[1][0]:
+ flags = flags | WE_HAVE_A_TWO_BY_TWO
+ data = data + struct.pack(">hhhh",
+ transform[0][0], transform[0][1],
+ transform[1][0], transform[1][1])
+ elif transform[0][0] <> transform[1][1]:
+ flags = flags | WE_HAVE_AN_X_AND_Y_SCALE
+ data = data + struct.pack(">hh",
+ transform[0][0], transform[1][1])
+ else:
+ flags = flags | WE_HAVE_A_SCALE
+ data = data + struct.pack(">h",
+ transform[0][0])
+
+ glyphID = glyfTable.getGlyphID(self.glyphName)
+ return struct.pack(">HH", flags, glyphID) + data
+
+ def toXML(self, writer, ttFont):
+ attrs = [("glyphName", self.glyphName)]
+ if not hasattr(self, "firstpt"):
+ attrs = attrs + [("x", self.x), ("y", self.y)]
+ else:
+ attrs = attrs + [("firstpt", self.firstpt), ("secondpt", self.secondpt)]
+
+ if hasattr(self, "transform"):
+ # XXX needs more testing
+ transform = self.transform
+ if transform[0][1] or transform[1][0]:
+ attrs = attrs + [
+ ("scalex", transform[0][0]), ("scale01", transform[0][1]),
+ ("scale10", transform[1][0]), ("scaley", transform[1][1]),
+ ]
+ elif transform[0][0] <> transform[1][1]:
+ attrs = attrs + [
+ ("scalex", transform[0][0]), ("scaley", transform[1][1]),
+ ]
+ else:
+ attrs = attrs + [("scale", transform[0][0])]
+ attrs = attrs + [("flags", hex(self.flags))]
+ writer.simpletag("component", attrs)
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ self.glyphName = attrs["glyphName"]
+ if attrs.has_key("firstpt"):
+ self.firstpt = safeEval(attrs["firstpt"])
+ self.secondpt = safeEval(attrs["secondpt"])
+ else:
+ self.x = safeEval(attrs["x"])
+ self.y = safeEval(attrs["y"])
+ if attrs.has_key("scale01"):
+ scalex = safeEval(attrs["scalex"])
+ scale01 = safeEval(attrs["scale01"])
+ scale10 = safeEval(attrs["scale10"])
+ scaley = safeEval(attrs["scaley"])
+ self.transform = Numeric.array([[scalex, scale01], [scale10, scaley]])
+ elif attrs.has_key("scalex"):
+ scalex = safeEval(attrs["scalex"])
+ scaley = safeEval(attrs["scaley"])
+ self.transform = Numeric.array([[scalex, 0], [0, scaley]])
+ elif attrs.has_key("scale"):
+ scale = safeEval(attrs["scale"])
+ self.transform = Numeric.array([[scale, 0], [0, scale]])
+ self.flags = safeEval(attrs["flags"])
+
+ def __cmp__(self, other):
+ if hasattr(self, "transform"):
+ if Numeric.alltrue(Numeric.equal(self.transform, other.transform)):
+ selfdict = self.__dict__.copy()
+ otherdict = other.__dict__.copy()
+ del selfdict["transform"]
+ del otherdict["transform"]
+ return cmp(selfdict, otherdict)
+ else:
+ return 1
+ else:
+ return cmp(self.__dict__, other.__dict__)
+
+
+def reprflag(flag):
+ bin = ""
+ if type(flag) == types.StringType:
+ flag = ord(flag)
+ while flag:
+ if flag & 0x01:
+ bin = "1" + bin
+ else:
+ bin = "0" + bin
+ flag = flag >> 1
+ bin = (14 - len(bin)) * "0" + bin
+ return bin
+
diff --git a/Lib/fontTools/ttLib/tables/_h_d_m_x.py b/Lib/fontTools/ttLib/tables/_h_d_m_x.py
new file mode 100644
index 0000000..e5c396c
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_h_d_m_x.py
@@ -0,0 +1,95 @@
+import DefaultTable
+import sstruct
+import string
+
+hdmxHeaderFormat = """
+ version: H
+ numRecords: H
+ recordSize: l
+"""
+
+class table__h_d_m_x(DefaultTable.DefaultTable):
+
+ def decompile(self, data, ttFont):
+ numGlyphs = ttFont['maxp'].numGlyphs
+ glyphOrder = ttFont.getGlyphOrder()
+ dummy, data = sstruct.unpack2(hdmxHeaderFormat, data, self)
+ self.hdmx = {}
+ for i in range(self.numRecords):
+ ppem = ord(data[0])
+ maxSize = ord(data[1])
+ widths = {}
+ for glyphID in range(numGlyphs):
+ widths[glyphOrder[glyphID]] = ord(data[glyphID+2])
+ self.hdmx[ppem] = widths
+ data = data[self.recordSize:]
+ assert len(data) == 0, "too much hdmx data"
+
+ def compile(self, ttFont):
+ self.version = 0
+ numGlyphs = ttFont['maxp'].numGlyphs
+ glyphOrder = ttFont.getGlyphOrder()
+ self.recordSize = 4 * ((2 + numGlyphs + 3) / 4)
+ pad = (self.recordSize - 2 - numGlyphs) * "\0"
+ self.numRecords = len(self.hdmx)
+ data = sstruct.pack(hdmxHeaderFormat, self)
+ items = self.hdmx.items()
+ items.sort()
+ for ppem, widths in items:
+ data = data + chr(ppem) + chr(max(widths.values()))
+ for glyphID in range(len(glyphOrder)):
+ width = widths[glyphOrder[glyphID]]
+ data = data + chr(width)
+ data = data + pad
+ return data
+
+ def toXML(self, writer, ttFont):
+ writer.begintag("hdmxData")
+ writer.newline()
+ ppems = self.hdmx.keys()
+ ppems.sort()
+ records = []
+ format = ""
+ for ppem in ppems:
+ widths = self.hdmx[ppem]
+ records.append(widths)
+ format = format + "%4d"
+ glyphNames = ttFont.getGlyphOrder()[:]
+ glyphNames.sort()
+ maxNameLen = max(map(len, glyphNames))
+ format = "%" + `maxNameLen` + 's:' + format + ' ;'
+ writer.write(format % (("ppem",) + tuple(ppems)))
+ writer.newline()
+ writer.newline()
+ for glyphName in glyphNames:
+ row = []
+ for ppem in ppems:
+ widths = self.hdmx[ppem]
+ row.append(widths[glyphName])
+ writer.write(format % ((glyphName,) + tuple(row)))
+ writer.newline()
+ writer.endtag("hdmxData")
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ if name <> "hdmxData":
+ return
+ content = string.join(content, " ")
+ lines = string.split(content, ";")
+ topRow = string.split(lines[0])
+ assert topRow[0] == "ppem:", "illegal hdmx format"
+ ppems = map(int, topRow[1:])
+ self.hdmx = hdmx = {}
+ for ppem in ppems:
+ hdmx[ppem] = {}
+ lines = map(string.split, lines[1:])
+ for line in lines:
+ if not line:
+ continue
+ assert line[0][-1] == ":", "illegal hdmx format"
+ glyphName = line[0][:-1]
+ line = map(int, line[1:])
+ assert len(line) == len(ppems), "illegal hdmx format"
+ for i in range(len(ppems)):
+ hdmx[ppems[i]][glyphName] = line[i]
+
diff --git a/Lib/fontTools/ttLib/tables/_h_e_a_d.py b/Lib/fontTools/ttLib/tables/_h_e_a_d.py
new file mode 100644
index 0000000..fc33edb
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_h_e_a_d.py
@@ -0,0 +1,136 @@
+import DefaultTable
+import sstruct
+import time
+import string
+import calendar
+from fontTools.misc.textTools import safeEval, num2binary, binary2num
+
+
+headFormat = """
+ > # big endian
+ tableVersion: 16.16F
+ fontRevision: 16.16F
+ checkSumAdjustment: l
+ magicNumber: l
+ x # pad byte
+ flags: b
+ unitsPerEm: H
+ created: 8s
+ modified: 8s
+ xMin: h
+ yMin: h
+ xMax: h
+ yMax: h
+ macStyle: H
+ lowestRecPPEM: H
+ fontDirectionHint: h
+ indexToLocFormat: h
+ glyphDataFormat: h
+"""
+
+class table__h_e_a_d(DefaultTable.DefaultTable):
+
+ dependencies = ['maxp', 'loca']
+
+ def decompile(self, data, ttFont):
+ sstruct.unpack(headFormat, data, self)
+ self.unitsPerEm = int(self.unitsPerEm)
+ self.strings2dates()
+
+ def compile(self, ttFont):
+ self.modified = long(time.time() - mac_epoch_diff)
+ self.dates2strings()
+ data = sstruct.pack(headFormat, self)
+ self.strings2dates()
+ return data
+
+ def strings2dates(self):
+ self.created = bin2long(self.created)
+ self.modified = bin2long(self.modified)
+
+ def dates2strings(self):
+ self.created = long2bin(self.created)
+ self.modified = long2bin(self.modified)
+
+ def toXML(self, writer, ttFont):
+ writer.comment("Most of this table will be recalculated by the compiler")
+ writer.newline()
+ formatstring, names, fixes = sstruct.getformat(headFormat)
+ for name in names:
+ value = getattr(self, name)
+ if name in ("created", "modified"):
+ value = time.asctime(time.gmtime(max(0, value + mac_epoch_diff)))
+ if type(value) == type(0L):
+ value=int(value)
+ if name in ("magicNumber", "checkSumAdjustment"):
+ value = hex(value)
+ elif name == "flags":
+ value = num2binary(value, 16)
+ writer.simpletag(name, value=value)
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ value = attrs["value"]
+ if name in ("created", "modified"):
+ value = parse_date(value) - mac_epoch_diff
+ elif name == "flags":
+ value = binary2num(value)
+ else:
+ value = safeEval(value)
+ setattr(self, name, value)
+
+ def __cmp__(self, other):
+ selfdict = self.__dict__.copy()
+ otherdict = other.__dict__.copy()
+ # for testing purposes, compare without the modified and checkSumAdjustment
+ # fields, since they are allowed to be different.
+ for key in ["modified", "checkSumAdjustment"]:
+ del selfdict[key]
+ del otherdict[key]
+ return cmp(selfdict, otherdict)
+
+
+def calc_mac_epoch_diff():
+ """calculate the difference between the original Mac epoch (1904)
+ to the epoch on this machine.
+ """
+ safe_epoch_t = (1971, 1, 1, 0, 0, 0, 0, 0, 0)
+ safe_epoch = time.mktime(safe_epoch_t) - time.timezone
+ assert time.gmtime(safe_epoch)[:6] == safe_epoch_t[:6]
+ seconds1904to1971 = 60 * 60 * 24 * (365 * (1971-1904) + 17) # thanks, Laurence!
+ return long(safe_epoch - seconds1904to1971)
+
+mac_epoch_diff = calc_mac_epoch_diff()
+
+
+_months = map(string.lower, calendar.month_abbr)
+_weekdays = map(string.lower, calendar.day_abbr)
+
+def parse_date(datestring):
+ datestring = string.lower(datestring)
+ weekday, month, day, tim, year = string.split(datestring)
+ weekday = _weekdays.index(weekday)
+ month = _months.index(month)
+ year = int(year)
+ day = int(day)
+ hour, minute, second = map(int, string.split(tim, ":"))
+ t = (year, month, day, hour, minute, second, weekday, 0, 0)
+ return long(time.mktime(t) - time.timezone)
+
+
+def bin2long(data):
+ # thanks </F>!
+ v = 0L
+ for i in map(ord, data):
+ v = v<<8 | i
+ return v
+
+def long2bin(v, bytes=8):
+ data = ""
+ while v:
+ data = chr(v & 0xff) + data
+ v = v >> 8
+ data = (bytes - len(data)) * "\0" + data
+ assert len(data) == 8, "long too long"
+ return data
+
diff --git a/Lib/fontTools/ttLib/tables/_h_h_e_a.py b/Lib/fontTools/ttLib/tables/_h_h_e_a.py
new file mode 100644
index 0000000..fca75cb
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_h_h_e_a.py
@@ -0,0 +1,78 @@
+import DefaultTable
+import sstruct
+from fontTools.misc.textTools import safeEval
+
+hheaFormat = """
+ > # big endian
+ tableVersion: 16.16F
+ ascent: h
+ descent: h
+ lineGap: h
+ advanceWidthMax: H
+ minLeftSideBearing: h
+ minRightSideBearing: h
+ xMaxExtent: h
+ caretSlopeRise: h
+ caretSlopeRun: h
+ reserved0: h
+ reserved1: h
+ reserved2: h
+ reserved3: h
+ reserved4: h
+ metricDataFormat: h
+ numberOfHMetrics: H
+"""
+
+class table__h_h_e_a(DefaultTable.DefaultTable):
+
+ dependencies = ['hmtx', 'glyf']
+
+ def decompile(self, data, ttFont):
+ sstruct.unpack(hheaFormat, data, self)
+
+ def compile(self, ttFont):
+ self.recalc(ttFont)
+ return sstruct.pack(hheaFormat, self)
+
+ def recalc(self, ttFont):
+ hmtxTable = ttFont['hmtx']
+ if ttFont.has_key('glyf'):
+ if not ttFont.isLoaded('glyf'):
+ return
+ glyfTable = ttFont['glyf']
+ advanceWidthMax = -100000 # arbitrary big negative number
+ minLeftSideBearing = 100000 # arbitrary big number
+ minRightSideBearing = 100000 # arbitrary big number
+ xMaxExtent = -100000 # arbitrary big negative number
+
+ for name in ttFont.getGlyphOrder():
+ width, lsb = hmtxTable[name]
+ g = glyfTable[name]
+ if g.numberOfContours <= 0:
+ continue
+ advanceWidthMax = max(advanceWidthMax, width)
+ minLeftSideBearing = min(minLeftSideBearing, lsb)
+ rsb = width - lsb - (g.xMax - g.xMin)
+ minRightSideBearing = min(minRightSideBearing, rsb)
+ extent = lsb + (g.xMax - g.xMin)
+ xMaxExtent = max(xMaxExtent, extent)
+ self.advanceWidthMax = advanceWidthMax
+ self.minLeftSideBearing = minLeftSideBearing
+ self.minRightSideBearing = minRightSideBearing
+ self.xMaxExtent = xMaxExtent
+ else:
+ # XXX CFF recalc...
+ pass
+
+ def toXML(self, writer, ttFont):
+ formatstring, names, fixes = sstruct.getformat(hheaFormat)
+ for name in names:
+ value = getattr(self, name)
+ if type(value) == type(0L):
+ value = int(value)
+ writer.simpletag(name, value=value)
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ setattr(self, name, safeEval(attrs["value"]))
+
diff --git a/Lib/fontTools/ttLib/tables/_h_m_t_x.py b/Lib/fontTools/ttLib/tables/_h_m_t_x.py
new file mode 100644
index 0000000..35fa8cd
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_h_m_t_x.py
@@ -0,0 +1,94 @@
+import DefaultTable
+import Numeric
+from fontTools import ttLib
+from fontTools.misc.textTools import safeEval
+
+
+class table__h_m_t_x(DefaultTable.DefaultTable):
+
+ headerTag = 'hhea'
+ advanceName = 'width'
+ sideBearingName = 'lsb'
+ numberOfMetricsName = 'numberOfHMetrics'
+
+ def decompile(self, data, ttFont):
+ numberOfMetrics = int(getattr(ttFont[self.headerTag], self.numberOfMetricsName))
+ metrics = Numeric.fromstring(data[:4 * numberOfMetrics],
+ Numeric.Int16)
+ if ttLib.endian <> "big":
+ metrics = metrics.byteswapped()
+ metrics.shape = (numberOfMetrics, 2)
+ data = data[4 * numberOfMetrics:]
+ numberOfSideBearings = ttFont['maxp'].numGlyphs - numberOfMetrics
+ numberOfSideBearings = int(numberOfSideBearings)
+ if numberOfSideBearings:
+ assert numberOfSideBearings > 0, "bad hmtx/vmtx table"
+ lastAdvance = metrics[-1][0]
+ advances = Numeric.array([lastAdvance] * numberOfSideBearings,
+ Numeric.Int16)
+ sideBearings = Numeric.fromstring(data[:2 * numberOfSideBearings],
+ Numeric.Int16)
+ if ttLib.endian <> "big":
+ sideBearings = sideBearings.byteswapped()
+ data = data[2 * numberOfSideBearings:]
+ additionalMetrics = Numeric.array([advances, sideBearings],
+ Numeric.Int16)
+ metrics = Numeric.concatenate((metrics,
+ Numeric.transpose(additionalMetrics)))
+ if data:
+ raise ttLib.TTLibError, "too much data for hmtx/vmtx table"
+ metrics = metrics.tolist()
+ self.metrics = {}
+ for i in range(len(metrics)):
+ glyphName = ttFont.getGlyphName(i)
+ self.metrics[glyphName] = metrics[i]
+
+ def compile(self, ttFont):
+ metrics = []
+ for glyphName in ttFont.getGlyphOrder():
+ metrics.append(self.metrics[glyphName])
+ lastAdvance = metrics[-1][0]
+ lastIndex = len(metrics)
+ while metrics[lastIndex-2][0] == lastAdvance:
+ lastIndex = lastIndex - 1
+ additionalMetrics = metrics[lastIndex:]
+ additionalMetrics = map(lambda (advance, sb): sb, additionalMetrics)
+ metrics = metrics[:lastIndex]
+ setattr(ttFont[self.headerTag], self.numberOfMetricsName, len(metrics))
+
+ metrics = Numeric.array(metrics, Numeric.Int16)
+ if ttLib.endian <> "big":
+ metrics = metrics.byteswapped()
+ data = metrics.tostring()
+
+ additionalMetrics = Numeric.array(additionalMetrics, Numeric.Int16)
+ if ttLib.endian <> "big":
+ additionalMetrics = additionalMetrics.byteswapped()
+ data = data + additionalMetrics.tostring()
+ return data
+
+ def toXML(self, writer, ttFont):
+ names = self.metrics.keys()
+ names.sort()
+ for glyphName in names:
+ advance, sb = self.metrics[glyphName]
+ writer.simpletag("mtx", [
+ ("name", glyphName),
+ (self.advanceName, advance),
+ (self.sideBearingName, sb),
+ ])
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ if not hasattr(self, "metrics"):
+ self.metrics = {}
+ if name == "mtx":
+ self.metrics[attrs["name"]] = [safeEval(attrs[self.advanceName]),
+ safeEval(attrs[self.sideBearingName])]
+
+ def __getitem__(self, glyphName):
+ return self.metrics[glyphName]
+
+ def __setitem__(self, glyphName, (advance, sb)):
+ self.metrics[glyphName] = advance, sb
+
diff --git a/Lib/fontTools/ttLib/tables/_k_e_r_n.py b/Lib/fontTools/ttLib/tables/_k_e_r_n.py
new file mode 100644
index 0000000..283c52f
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_k_e_r_n.py
@@ -0,0 +1,186 @@
+import DefaultTable
+import struct
+import ttLib.sfnt
+from fontTools.misc.textTools import safeEval, readHex
+
+
+class table__k_e_r_n(DefaultTable.DefaultTable):
+
+ def getkern(self, format):
+ for subtable in self.kernTables:
+ if subtable.version == format:
+ return subtable
+ return None # not found
+
+ def decompile(self, data, ttFont):
+ version, nTables = struct.unpack(">HH", data[:4])
+ if version == 1:
+ # Apple's new format. Hm.
+ version, nTables = struct.unpack(">ll", data[:8])
+ self.version = version / float(0x10000)
+ data = data[8:]
+ else:
+ self.version = version
+ data = data[4:]
+ tablesIndex = []
+ self.kernTables = []
+ for i in range(nTables):
+ version, length = struct.unpack(">HH", data[:4])
+ length = int(length)
+ if not kern_classes.has_key(version):
+ subtable = KernTable_format_unkown()
+ else:
+ subtable = kern_classes[version]()
+ subtable.decompile(data[:length], ttFont)
+ self.kernTables.append(subtable)
+ data = data[length:]
+
+ def compile(self, ttFont):
+ nTables = len(self.kernTables)
+ if self.version == 1.0:
+ # Apple's new format.
+ data = struct.pack(">ll", self.version * 0x1000, nTables)
+ else:
+ data = struct.pack(">HH", self.version, nTables)
+ for subtable in self.kernTables:
+ data = data + subtable.compile(ttFont)
+ return data
+
+ def toXML(self, writer, ttFont):
+ writer.simpletag("version", value=self.version)
+ writer.newline()
+ for subtable in self.kernTables:
+ subtable.toXML(writer, ttFont)
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ if name == "version":
+ self.version = safeEval(attrs["value"])
+ return
+ if name <> "kernsubtable":
+ return
+ if not hasattr(self, "kernTables"):
+ self.kernTables = []
+ format = safeEval(attrs["format"])
+ if not kern_classes.has_key(format):
+ subtable = KernTable_format_unkown()
+ else:
+ subtable = kern_classes[format]()
+ self.kernTables.append(subtable)
+ subtable.fromXML((name, attrs, content), ttFont)
+
+
+class KernTable_format_0:
+
+ def decompile(self, data, ttFont):
+ version, length, coverage = struct.unpack(">HHH", data[:6])
+ self.version, self.coverage = int(version), int(coverage)
+ data = data[6:]
+
+ self.kernTable = kernTable = {}
+
+ nPairs, searchRange, entrySelector, rangeShift = struct.unpack(">HHHH", data[:8])
+ data = data[8:]
+
+ for k in range(nPairs):
+ left, right, value = struct.unpack(">HHh", data[:6])
+ data = data[6:]
+ left, right = int(left), int(right)
+ kernTable[(ttFont.getGlyphName(left), ttFont.getGlyphName(right))] = value
+ assert len(data) == 0
+
+ def compile(self, ttFont):
+ nPairs = len(self.kernTable)
+ entrySelector = ttLib.sfnt.maxpoweroftwo(nPairs)
+ searchRange = (2 ** entrySelector) * 6
+ rangeShift = (nPairs - (2 ** entrySelector)) * 6
+ data = struct.pack(">HHHH", nPairs, searchRange, entrySelector, rangeShift)
+
+ # yeehee! (I mean, turn names into indices)
+ kernTable = map(lambda ((left, right), value), getGlyphID=ttFont.getGlyphID:
+ (getGlyphID(left), getGlyphID(right), value),
+ self.kernTable.items())
+ kernTable.sort()
+ for left, right, value in kernTable:
+ data = data + struct.pack(">HHh", left, right, value)
+ return struct.pack(">HHH", self.version, len(data) + 6, self.coverage) + data
+
+ def toXML(self, writer, ttFont):
+ writer.begintag("kernsubtable", coverage=self.coverage, format=0)
+ writer.newline()
+ items = self.kernTable.items()
+ items.sort()
+ for (left, right), value in items:
+ writer.simpletag("pair", [
+ ("l", left),
+ ("r", right),
+ ("v", value)
+ ])
+ writer.newline()
+ writer.endtag("kernsubtable")
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ self.coverage = safeEval(attrs["coverage"])
+ self.version = safeEval(attrs["format"])
+ if not hasattr(self, "kernTable"):
+ self.kernTable = {}
+ for element in content:
+ if type(element) <> type(()):
+ continue
+ name, attrs, content = element
+ self.kernTable[(attrs["l"], attrs["r"])] = safeEval(attrs["v"])
+
+ def __getitem__(self, pair):
+ return self.kernTable[pair]
+
+ def __setitem__(self, pair, value):
+ self.kernTable[pair] = value
+
+ def __delitem__(self, pair):
+ del self.kernTable[pair]
+
+ def __cmp__(self, other):
+ return cmp(self.__dict__, other.__dict__)
+
+
+class KernTable_format_2:
+
+ def decompile(self, data, ttFont):
+ self.data = data
+
+ def compile(self, ttFont, ttFont):
+ return data
+
+ def toXML(self, writer):
+ writer.begintag("kernsubtable", format=2)
+ writer.newline()
+ writer.dumphex(self.data)
+ writer.endtag("kernsubtable")
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ self.decompile(readHex(content))
+
+
+class KernTable_format_unkown:
+
+ def decompile(self, data, ttFont):
+ self.data = data
+
+ def compile(self, ttFont):
+ return data
+
+ def toXML(self, writer, ttFont):
+ writer.begintag("kernsubtable", format="-1")
+ writer.newline()
+ writer.comment("unknown 'kern' subtable format")
+ writer.dumphex(self.data)
+ writer.endtag("kernsubtable")
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ self.decompile(readHex(content))
+
+
+
+kern_classes = {0: KernTable_format_0, 1: KernTable_format_2}
diff --git a/Lib/fontTools/ttLib/tables/_l_o_c_a.py b/Lib/fontTools/ttLib/tables/_l_o_c_a.py
new file mode 100644
index 0000000..332a708
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_l_o_c_a.py
@@ -0,0 +1,55 @@
+import DefaultTable
+import array
+import Numeric
+from fontTools import ttLib
+import struct
+
+class table__l_o_c_a(DefaultTable.DefaultTable):
+
+ dependencies = ['glyf']
+
+ def decompile(self, data, ttFont):
+ longFormat = ttFont['head'].indexToLocFormat
+ if longFormat:
+ format = "l"
+ else:
+ format = "H"
+ locations = array.array(format)
+ locations.fromstring(data)
+ if ttLib.endian <> "big":
+ locations.byteswap()
+ locations = Numeric.array(locations, Numeric.Int32)
+ if not longFormat:
+ locations = locations * 2
+ if len(locations) <> (ttFont['maxp'].numGlyphs + 1):
+ raise ttLib.TTLibError, "corrupt 'loca' table"
+ self.locations = locations
+
+ def compile(self, ttFont):
+ locations = self.locations
+ if max(locations) < 0x20000:
+ locations = locations / 2
+ locations = locations.astype(Numeric.Int16)
+ ttFont['head'].indexToLocFormat = 0
+ else:
+ ttFont['head'].indexToLocFormat = 1
+ if ttLib.endian <> "big":
+ locations = locations.byteswapped()
+ return locations.tostring()
+
+ def set(self, locations):
+ self.locations = Numeric.array(locations, Numeric.Int32)
+
+ def toXML(self, writer, ttFont):
+ writer.comment("The 'loca' table will be calculated by the compiler")
+ writer.newline()
+
+ def __getitem__(self, index):
+ return self.locations[index]
+
+ def __len__(self):
+ return len(self.locations)
+
+ def __cmp__(self, other):
+ return cmp(len(self), len(other)) or not Numeric.alltrue(Numeric.equal(self.locations, other.locations))
+
diff --git a/Lib/fontTools/ttLib/tables/_m_a_x_p.py b/Lib/fontTools/ttLib/tables/_m_a_x_p.py
new file mode 100644
index 0000000..1c75e6d
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_m_a_x_p.py
@@ -0,0 +1,138 @@
+import DefaultTable
+import sstruct
+from fontTools.misc.textTools import safeEval
+
+maxpFormat_0_5 = """
+ > # big endian
+ tableVersion: i
+ numGlyphs: H
+"""
+
+maxpFormat_1_0_add = """
+ > # big endian
+ maxPoints: H
+ maxContours: H
+ maxCompositePoints: H
+ maxCompositeContours: H
+ maxZones: H
+ maxTwilightPoints: H
+ maxStorage: H
+ maxFunctionDefs: H
+ maxInstructionDefs: H
+ maxStackElements: H
+ maxSizeOfInstructions: H
+ maxComponentElements: H
+ maxComponentDepth: H
+"""
+
+
+class table__m_a_x_p(DefaultTable.DefaultTable):
+
+ dependencies = ['glyf']
+
+ def decompile(self, data, ttFont):
+ dummy, data = sstruct.unpack2(maxpFormat_0_5, data, self)
+ self.numGlyphs = int(self.numGlyphs)
+ if self.tableVersion == 0x00010000:
+ dummy, data = sstruct.unpack2(maxpFormat_1_0_add, data, self)
+ else:
+ assert self.tableVersion == 0x00005000, "unknown 'maxp' format: %x" % self.tableVersion
+ assert len(data) == 0
+
+ def compile(self, ttFont):
+ if ttFont.has_key('glyf'):
+ if ttFont.isLoaded('glyf'):
+ self.recalc(ttFont)
+ else:
+ pass # XXX CFF!!!
+ data = sstruct.pack(maxpFormat_0_5, self)
+ if self.tableVersion == 0x00010000:
+ data = data + sstruct.pack(maxpFormat_1_0_add, self)
+ else:
+ assert self.tableVersion == 0x00005000, "unknown 'maxp' format: %f" % self.tableVersion
+ return data
+
+ def recalc(self, ttFont):
+ """Recalculate the font bounding box, and most other maxp values except
+ for the TT instructions values. Also recalculate the value of bit 1
+ of the flags field of the 'head' table.
+ """
+ glyfTable = ttFont['glyf']
+ hmtxTable = ttFont['hmtx']
+ headTable = ttFont['head']
+ self.numGlyphs = len(glyfTable)
+ xMin = 100000
+ yMin = 100000
+ xMax = -100000
+ yMax = -100000
+ maxPoints = 0
+ maxContours = 0
+ maxCompositePoints = 0
+ maxCompositeContours = 0
+ maxComponentElements = 0
+ maxComponentDepth = 0
+ allXMaxIsLsb = 1
+ for glyphName in ttFont.getGlyphOrder():
+ g = glyfTable[glyphName]
+ if g.numberOfContours:
+ if hmtxTable[glyphName][1] <> g.xMin:
+ allXMaxIsLsb = 0
+ xMin = min(xMin, g.xMin)
+ yMin = min(yMin, g.yMin)
+ xMax = max(xMax, g.xMax)
+ yMax = max(yMax, g.yMax)
+ if g.numberOfContours > 0:
+ nPoints, nContours = g.getMaxpValues()
+ maxPoints = max(maxPoints, nPoints)
+ maxContours = max(maxContours, nContours)
+ else:
+ nPoints, nContours, componentDepth = g.getCompositeMaxpValues(glyfTable)
+ maxCompositePoints = max(maxCompositePoints, nPoints)
+ maxCompositeContours = max(maxCompositeContours, nContours)
+ maxComponentElements = max(maxComponentElements, len(g.components))
+ maxComponentDepth = max(maxComponentDepth, componentDepth)
+ self.xMin = xMin
+ self.yMin = yMin
+ self.xMax = xMax
+ self.yMax = yMax
+ self.maxPoints = maxPoints
+ self.maxContours = maxContours
+ self.maxCompositePoints = maxCompositePoints
+ self.maxCompositeContours = maxCompositeContours
+ self.maxComponentDepth = maxComponentDepth
+ if allXMaxIsLsb:
+ headTable.flags = headTable.flags | 0x2
+ else:
+ headTable.flags = headTable.flags & ~0x2
+
+ def testrepr(self):
+ items = self.__dict__.items()
+ items.sort()
+ print ". . . . . . . . ."
+ for combo in items:
+ print " %s: %s" % combo
+ print ". . . . . . . . ."
+
+ def toXML(self, writer, ttFont):
+ if self.tableVersion <> 0x00005000:
+ writer.comment("Most of this table will be recalculated by the compiler")
+ writer.newline()
+ formatstring, names, fixes = sstruct.getformat(maxpFormat_0_5)
+ if self.tableVersion == 0x00010000:
+ formatstring, names_1_0, fixes = sstruct.getformat(maxpFormat_1_0_add)
+ names = names + names_1_0
+ else:
+ assert self.tableVersion == 0x00005000, "unknown 'maxp' format: %f" % self.tableVersion
+ for name in names:
+ value = getattr(self, name)
+ if type(value) == type(0L):
+ value=int(value)
+ if name == "tableVersion":
+ value = hex(value)
+ writer.simpletag(name, value=value)
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ setattr(self, name, safeEval(attrs["value"]))
+
+
diff --git a/Lib/fontTools/ttLib/tables/_n_a_m_e.py b/Lib/fontTools/ttLib/tables/_n_a_m_e.py
new file mode 100644
index 0000000..05cde33
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_n_a_m_e.py
@@ -0,0 +1,136 @@
+import DefaultTable
+import struct, sstruct
+from fontTools.misc.textTools import safeEval
+import string
+import types
+
+nameRecordFormat = """
+ > # big endian
+ platformID: H
+ platEncID: H
+ langID: H
+ nameID: H
+ length: H
+ offset: H
+"""
+
+class table__n_a_m_e(DefaultTable.DefaultTable):
+
+ def decompile(self, data, ttFont):
+ format, n, stringoffset = struct.unpack(">HHH", data[:6])
+ stringoffset = int(stringoffset)
+ stringData = data[stringoffset:]
+ data = data[6:stringoffset]
+ self.names = []
+ for i in range(n):
+ name, data = sstruct.unpack2(nameRecordFormat, data, NameRecord())
+ name.fixlongs()
+ name.string = stringData[name.offset:name.offset+name.length]
+ del name.offset, name.length
+ self.names.append(name)
+
+ def compile(self, ttFont):
+ self.names.sort() # sort according to the spec; see NameRecord.__cmp__()
+ stringData = ""
+ format = 0
+ n = len(self.names)
+ stringoffset = 6 + n * sstruct.calcsize(nameRecordFormat)
+ data = struct.pack(">HHH", format, n, stringoffset)
+ lastoffset = 0
+ done = {} # remember the data so we can reuse the "pointers"
+ for name in self.names:
+ if done.has_key(name.string):
+ name.offset, name.length = done[name.string]
+ else:
+ name.offset, name.length = done[name.string] = len(stringData), len(name.string)
+ stringData = stringData + name.string
+ data = data + sstruct.pack(nameRecordFormat, name)
+ return data + stringData
+
+ def toXML(self, writer, ttFont):
+ for name in self.names:
+ name.toXML(writer, ttFont)
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ if name <> "namerecord":
+ return # ignore unknown tags
+ if not hasattr(self, "names"):
+ self.names = []
+ name = NameRecord()
+ self.names.append(name)
+ name.fromXML((name, attrs, content), ttFont)
+
+ def getname(self, nameID, platformID, platEncID, langID=None):
+ for namerecord in self.names:
+ if ( namerecord.nameID == nameID and
+ namerecord.platformID == platformID and
+ namerecord.platEncID == platEncID):
+ if langID is None or namerecord.langID == langID:
+ return namerecord
+ return None # not found
+
+ def __cmp__(self, other):
+ return cmp(self.names, other.names)
+
+
+class NameRecord:
+
+ def toXML(self, writer, ttFont):
+ writer.begintag("namerecord", [
+ ("nameID", self.nameID),
+ ("platformID", self.platformID),
+ ("platEncID", self.platEncID),
+ ("langID", hex(self.langID)),
+ ])
+ writer.newline()
+ if self.platformID == 0 or (self.platformID == 3 and self.platEncID == 1):
+ writer.write16bit(self.string)
+ else:
+ writer.write8bit(self.string)
+ writer.newline()
+ writer.endtag("namerecord")
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ self.nameID = safeEval(attrs["nameID"])
+ self.platformID = safeEval(attrs["platformID"])
+ self.platEncID = safeEval(attrs["platEncID"])
+ self.langID = safeEval(attrs["langID"])
+ if self.platformID == 0 or (self.platformID == 3 and self.platEncID == 1):
+ from fontTools.ttLib.xmlImport import UnicodeString
+ str = UnicodeString("")
+ for element in content:
+ str = str + element
+ self.string = str.stripped().tostring()
+ else:
+ self.string = string.strip(string.join(content, ""))
+
+ def __cmp__(self, other):
+ """Compare method, so a list of NameRecords can be sorted
+ according to the spec by just sorting it..."""
+ selftuple = (self.platformID,
+ self.platEncID,
+ self.langID,
+ self.nameID,
+ self.string)
+ othertuple = (other.platformID,
+ other.platEncID,
+ other.langID,
+ other.nameID,
+ other.string)
+ return cmp(selftuple, othertuple)
+
+ def __repr__(self):
+ return "<NameRecord NameID=%d; PlatformID=%d; LanguageID=%d>" % (
+ self.nameID, self.platformID, self.langID)
+
+ def fixlongs(self):
+ """correct effects from bug in Python 1.5.1, where "H"
+ returns a Python Long int.
+ This has been fixed in Python 1.5.2.
+ """
+ for attr in dir(self):
+ val = getattr(self, attr)
+ if type(val) == types.LongType:
+ setattr(self, attr, int(val))
+
diff --git a/Lib/fontTools/ttLib/tables/_p_o_s_t.py b/Lib/fontTools/ttLib/tables/_p_o_s_t.py
new file mode 100644
index 0000000..273de79
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_p_o_s_t.py
@@ -0,0 +1,214 @@
+from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder
+import DefaultTable
+import struct, sstruct
+import array
+from fontTools import ttLib
+from fontTools.misc.textTools import safeEval, readHex
+
+
+postFormat = """
+ >
+ formatType: 16.16F
+ italicAngle: 16.16F # italic angle in degrees
+ underlinePosition: h
+ underlineThickness: h
+ isFixedPitch: l
+ minMemType42: l # minimum memory if TrueType font is downloaded
+ maxMemType42: l # maximum memory if TrueType font is downloaded
+ minMemType1: l # minimum memory if Type1 font is downloaded
+ maxMemType1: l # maximum memory if Type1 font is downloaded
+"""
+
+postFormatSize = sstruct.calcsize(postFormat)
+
+
+class table__p_o_s_t(DefaultTable.DefaultTable):
+
+ def decompile(self, data, ttFont):
+ sstruct.unpack(postFormat, data[:postFormatSize], self)
+ data = data[postFormatSize:]
+ if self.formatType == 1.0:
+ self.decode_format_1_0(data, ttFont)
+ elif self.formatType == 2.0:
+ self.decode_format_2_0(data, ttFont)
+ elif self.formatType == 3.0:
+ self.decode_format_3_0(data, ttFont)
+ else:
+ # supported format
+ raise ttLib.TTLibError, "'post' table format %f not supported" % self.formatType
+
+ def compile(self, ttFont):
+ data = sstruct.pack(postFormat, self)
+ if self.formatType == 1.0:
+ pass # we're done
+ elif self.formatType == 2.0:
+ data = data + self.encode_format_2_0(ttFont)
+ elif self.formatType == 3.0:
+ pass # we're done
+ else:
+ # supported format
+ raise ttLib.TTLibError, "'post' table format %f not supported" % self.formatType
+ return data
+
+ def getGlyphOrder(self):
+ """This function will get called by a ttLib.TTFont instance.
+ Do not call this function yourself, use TTFont().getGlyphOrder()
+ or its relatives instead!
+ """
+ if not hasattr(self, "glyphOrder"):
+ raise ttLib.TTLibError, "illegal use of getGlyphOrder()"
+ glyphOrder = self.glyphOrder
+ del self.glyphOrder
+ return glyphOrder
+
+ def decode_format_1_0(self, data, ttFont):
+ self.glyphOrder = standardGlyphOrder[:]
+
+ def decode_format_2_0(self, data, ttFont):
+ numGlyphs, = struct.unpack(">H", data[:2])
+ numGlyphs = int(numGlyphs)
+ data = data[2:]
+ indices = array.array("H")
+ indices.fromstring(data[:2*numGlyphs])
+ if ttLib.endian <> "big":
+ indices.byteswap()
+ data = data[2*numGlyphs:]
+ self.extraNames = extraNames = unpackPStrings(data)
+ self.glyphOrder = glyphOrder = [None] * int(ttFont['maxp'].numGlyphs)
+ for glyphID in range(numGlyphs):
+ index = indices[glyphID]
+ if index > 257:
+ name = extraNames[index-258]
+ else:
+ # fetch names from standard list
+ name = standardGlyphOrder[index]
+ glyphOrder[glyphID] = name
+ #AL990511: code added to handle the case of new glyphs without
+ # entries into the 'post' table
+ if numGlyphs < ttFont['maxp'].numGlyphs:
+ for i in range(numGlyphs, ttFont['maxp'].numGlyphs):
+ glyphOrder[i] = "glyph#%.5d" % i
+ self.extraNames.append(glyphOrder[i])
+ self.build_psNameMapping(ttFont)
+
+ def build_psNameMapping(self, ttFont):
+ mapping = {}
+ allNames = {}
+ for i in range(ttFont['maxp'].numGlyphs):
+ glyphName = psName = self.glyphOrder[i]
+ if allNames.has_key(glyphName):
+ # make up a new glyphName that's unique
+ n = 1
+ while allNames.has_key(glyphName + "#" + `n`):
+ n = n + 1
+ glyphName = glyphName + "#" + `n`
+ self.glyphOrder[i] = glyphName
+ mapping[glyphName] = psName
+ allNames[glyphName] = psName
+ self.mapping = mapping
+
+ def decode_format_3_0(self, data, ttFont):
+ # Setting self.glyphOrder to None will cause the TTFont object
+ # try and construct glyph names from a Unicode cmap table.
+ self.glyphOrder = None
+
+ def encode_format_2_0(self, ttFont):
+ numGlyphs = ttFont['maxp'].numGlyphs
+ glyphOrder = ttFont.getGlyphOrder()
+ assert len(glyphOrder) == numGlyphs
+ indices = array.array("H")
+ for glyphID in range(numGlyphs):
+ glyphName = glyphOrder[glyphID]
+ if self.mapping.has_key(glyphName):
+ psName = self.mapping[glyphName]
+ else:
+ psName = glyphName
+ if psName in self.extraNames:
+ index = 258 + self.extraNames.index(psName)
+ elif psName in standardGlyphOrder:
+ index = standardGlyphOrder.index(psName)
+ else:
+ index = 258 + len(self.extraNames)
+ extraNames.append(psName)
+ indices.append(index)
+ if ttLib.endian <> "big":
+ indices.byteswap()
+ return struct.pack(">H", numGlyphs) + indices.tostring() + packPStrings(self.extraNames)
+
+ def toXML(self, writer, ttFont):
+ formatstring, names, fixes = sstruct.getformat(postFormat)
+ for name in names:
+ value = getattr(self, name)
+ writer.simpletag(name, value=value)
+ writer.newline()
+ if hasattr(self, "mapping"):
+ writer.begintag("psNames")
+ writer.newline()
+ writer.comment("This file uses unique glyph names based on the information\n"
+ "found in the 'post' table. Since these names might not be unique,\n"
+ "we have to invent artificial names in case of clashes. In order to\n"
+ "be able to retain the original information, we need a name to\n"
+ "ps name mapping for those cases where they differ. That's what\n"
+ "you see below.\n")
+ writer.newline()
+ items = self.mapping.items()
+ items.sort()
+ for name, psName in items:
+ writer.simpletag("psName", name=name, psName=psName)
+ writer.newline()
+ writer.endtag("psNames")
+ writer.newline()
+ if hasattr(self, "extraNames"):
+ writer.begintag("extraNames")
+ writer.newline()
+ writer.comment("following are the name that are not taken from the standard Mac glyph order")
+ writer.newline()
+ for name in self.extraNames:
+ writer.simpletag("psName", name=name)
+ writer.newline()
+ writer.endtag("extraNames")
+ writer.newline()
+ if hasattr(self, "data"):
+ writer.begintag("hexdata")
+ writer.newline()
+ writer.dumphex(self.data)
+ writer.endtag("hexdata")
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ if name not in ("psNames", "extraNames", "hexdata"):
+ setattr(self, name, safeEval(attrs["value"]))
+ elif name == "psNames":
+ self.mapping = {}
+ for element in content:
+ if type(element) <> type(()):
+ continue
+ name, attrs, content = element
+ if name == "psName":
+ self.mapping[attrs["name"]] = attrs["psName"]
+ elif name == "extraNames":
+ self.extraNames = []
+ for element in content:
+ if type(element) <> type(()):
+ continue
+ name, attrs, content = element
+ if name == "psName":
+ self.extraNames.append(attrs["name"])
+ else:
+ self.data = readHex(content)
+
+
+def unpackPStrings(data):
+ strings = []
+ while data:
+ length = ord(data[0])
+ strings.append(data[1:1+length])
+ data = data[1+length:]
+ return strings
+
+def packPStrings(strings):
+ data = ""
+ for s in strings:
+ data = data + chr(len(s)) + s
+ return data
+
diff --git a/Lib/fontTools/ttLib/tables/_p_r_e_p.py b/Lib/fontTools/ttLib/tables/_p_r_e_p.py
new file mode 100644
index 0000000..005c8ee
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_p_r_e_p.py
@@ -0,0 +1,14 @@
+import DefaultTable
+import array
+
+class table__p_r_e_p(DefaultTable.DefaultTable):
+
+ def decompile(self, data, ttFont):
+ self.prep = data
+
+ def compile(self, ttFont):
+ return self.prep
+
+ def __len__(self):
+ return len(self.prep)
+
diff --git a/Lib/fontTools/ttLib/tables/_v_h_e_a.py b/Lib/fontTools/ttLib/tables/_v_h_e_a.py
new file mode 100644
index 0000000..43fd614
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_v_h_e_a.py
@@ -0,0 +1,78 @@
+import DefaultTable
+import sstruct
+from fontTools.misc.textTools import safeEval
+
+vheaFormat = """
+ > # big endian
+ tableVersion: 16.16F
+ ascent: h
+ descent: h
+ lineGap: h
+ advanceHeightMax: H
+ minTopSideBearing: h
+ minBottomSideBearing: h
+ yMaxExtent: h
+ caretSlopeRise: h
+ caretSlopeRun: h
+ reserved0: h
+ reserved1: h
+ reserved2: h
+ reserved3: h
+ reserved4: h
+ metricDataFormat: h
+ numberOfVMetrics: H
+"""
+
+class table__v_h_e_a(DefaultTable.DefaultTable):
+
+ dependencies = ['vmtx', 'glyf']
+
+ def decompile(self, data, ttFont):
+ sstruct.unpack(vheaFormat, data, self)
+
+ def compile(self, ttFont):
+ self.recalc(ttFont)
+ return sstruct.pack(vheaFormat, self)
+
+ def recalc(self, ttFont):
+ vtmxTable = ttFont['vmtx']
+ if ttFont.has_key('glyf'):
+ if not ttFont.isLoaded('glyf'):
+ return
+ glyfTable = ttFont['glyf']
+ advanceHeightMax = -100000 # arbitrary big negative number
+ minTopSideBearing = 100000 # arbitrary big number
+ minBottomSideBearing = 100000 # arbitrary big number
+ yMaxExtent = -100000 # arbitrary big negative number
+
+ for name in ttFont.getGlyphOrder():
+ height, tsb = vtmxTable[name]
+ g = glyfTable[name]
+ if g.numberOfContours <= 0:
+ continue
+ advanceHeightMax = max(advanceHeightMax, height)
+ minTopSideBearing = min(minTopSideBearing, tsb)
+ rsb = height - tsb - (g.yMax - g.yMin)
+ minBottomSideBearing = min(minBottomSideBearing, rsb)
+ extent = tsb + (g.yMax - g.yMin)
+ yMaxExtent = max(yMaxExtent, extent)
+ self.advanceHeightMax = advanceHeightMax
+ self.minTopSideBearing = minTopSideBearing
+ self.minBottomSideBearing = minBottomSideBearing
+ self.yMaxExtent = yMaxExtent
+ else:
+ # XXX CFF recalc...
+ pass
+
+ def toXML(self, writer, ttFont):
+ formatstring, names, fixes = sstruct.getformat(vheaFormat)
+ for name in names:
+ value = getattr(self, name)
+ if type(value) == type(0L):
+ value = int(value)
+ writer.simpletag(name, value=value)
+ writer.newline()
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ setattr(self, name, safeEval(attrs["value"]))
+
diff --git a/Lib/fontTools/ttLib/tables/_v_m_t_x.py b/Lib/fontTools/ttLib/tables/_v_m_t_x.py
new file mode 100644
index 0000000..dabefe6
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/_v_m_t_x.py
@@ -0,0 +1,14 @@
+import DefaultTable
+import Numeric
+from fontTools import ttLib
+from fontTools.misc.textTools import safeEval
+
+superclass = ttLib.getTableClass("hmtx")
+
+class table__v_m_t_x(superclass):
+
+ headerTag = 'vhea'
+ advanceName = 'height'
+ sideBearingName = 'tsb'
+ numberOfMetricsName = 'numberOfVMetrics'
+
diff --git a/Lib/fontTools/ttLib/tables/otCommon.py b/Lib/fontTools/ttLib/tables/otCommon.py
new file mode 100644
index 0000000..c312c63
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/otCommon.py
@@ -0,0 +1,605 @@
+"""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
+
+
diff --git a/Lib/fontTools/ttLib/tables/table_API_readme.txt b/Lib/fontTools/ttLib/tables/table_API_readme.txt
new file mode 100644
index 0000000..2c04bf8
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/table_API_readme.txt
@@ -0,0 +1,91 @@
+This folder is a subpackage of ttLib. Each module here is a
+specialized TT/OT table converter: they can convert raw data
+to Python objects and vice versa. Usually you don't need to
+use the modules directly: they are imported and used
+automatically when needed by ttLib.
+
+If you are writing you own table converter the following is
+important.
+
+The modules here have pretty strange names: this is due to the
+fact that we need to map TT table tags (which are case sensitive)
+to filenames (which on Mac and Win aren't case sensitive) as well
+as to Python identifiers. The latter means it can only contain
+[A-Za-z0-9_] and cannot start with a number.
+
+ttLib provides functions to expand a tag into the format used here:
+
+>>> from fontTools import ttLib
+>>> ttLib.tag2identifier("FOO ")
+'F_O_O_'
+>>> ttLib.tag2identifier("cvt ")
+'_c_v_t'
+>>> ttLib.tag2identifier("OS/2")
+'O_S_2f_2'
+>>> ttLib.tag2identifier("glyf")
+'_g_l_y_f'
+>>>
+
+And vice versa:
+
+>>> ttLib.identifier2tag("F_O_O_")
+'FOO '
+>>> ttLib.identifier2tag("_c_v_t")
+'cvt '
+>>> ttLib.identifier2tag("O_S_2f_2")
+'OS/2'
+>>> ttLib.identifier2tag("_g_l_y_f")
+'glyf'
+>>>
+
+Eg. the 'glyf' table converter lives in a Python file called:
+
+ _g_l_y_f.py
+
+The converter itself is a class, named "table_" + expandedtag. Eg:
+
+ class table__g_l_y_f:
+ etc.
+
+Note that if you _do_ need to use such modules or classes manually,
+there are two convenient API functions that let you find them by tag:
+
+>>> ttLib.getTableModule('glyf')
+<module 'ttLib.tables._g_l_y_f'>
+>>> ttLib.getTableClass('glyf')
+<class ttLib.tables._g_l_y_f.table__g_l_y_f at 645f400>
+>>>
+
+You must subclass from DefaultTable.DefaultTable. It provides some default
+behavior, as well as a constructor method (__init__) that you don't need to
+override.
+
+Your converter should minimally provide two methods:
+
+class table_F_O_O_(DefaultTable.DefaultTable): # converter for table 'FOO '
+
+ def decompile(self, data, ttFont):
+ # 'data' is the raw table data. Unpack it into a
+ # Python data structure.
+ # 'ttFont' is a ttLib.TTfile instance, enabling you to
+ # refer to other tables. Do ***not*** keep a reference to
+ # it: it will cause a circular reference (ttFont saves
+ # a reference to us), and that means we'll be leaking
+ # memory. If you need to use it in other methods, just
+ # pass it around as a method argument.
+
+ def compile(self, ttFont):
+ # Return the raw data, as converted from the Python
+ # data structure.
+ # Again, 'ttFont' is there so you can access other tables.
+ # Same warning applies.
+
+If you want to support XML import/export as well, you need to provide two
+additional methods:
+
+ def toXML(self, writer, ttFont):
+ # XXX
+
+ def fromXML(self, (name, attrs, content), ttFont):
+ # XXX
+
diff --git a/Lib/fontTools/ttLib/tables/ttProgram.py b/Lib/fontTools/ttLib/tables/ttProgram.py
new file mode 100644
index 0000000..8dd9faa
--- /dev/null
+++ b/Lib/fontTools/ttLib/tables/ttProgram.py
@@ -0,0 +1,249 @@
+"""ttLib.tables.ttProgram.py -- Assembler/disassembler for TrueType bytecode programs."""
+
+import array
+
+
+# first, the list of instructions that eat bytes or words from the instruction stream
+
+streamInstructions = [
+# ------ ----------- ----- ------------------------ --- ------ ---------------------------------- --------------
+# opcode mnemonic argbits descriptive name pops pushes eats from instruction stream pushes
+# ------ ----------- ----- ------------------------ --- ------ ---------------------------------- --------------
+ (0x40, 'NPUSHB', 0, 'PushNBytes', 0, -1), # n, b1, b2,...bn b1,b2...bn
+ (0x41, 'NPUSHW', 0, 'PushNWords', 0, -1), # n, w1, w2,...w w1,w2...wn
+ (0xb0, 'PUSHB', 3, 'PushBytes', 0, -1), # b0, b1,..bn b0, b1, ...,bn
+ (0xb8, 'PUSHW', 3, 'PushWords', 0, -1), # w0,w1,..wn w0 ,w1, ...wn
+# ------ ----------- ----- ------------------------ --- ------ ---------------------------------- --------------
+]
+
+
+# next, the list of "normal" instructions
+
+instructions = [
+# ------ ----------- ----- ------------------------ --- ------ ---------------------------------- --------------
+# opcode mnemonic argbits descriptive name pops pushes pops pushes
+# ------ ----------- ----- ------------------------ --- ------ ---------------------------------- --------------
+ (0x7f, 'AA', 0, 'AdjustAngle', 1, 0), # p -
+ (0x64, 'ABS', 0, 'Absolute', 1, 1), # n |n|
+ (0x60, 'ADD', 0, 'Add', 2, 1), # n2, n1 (n1 + n2)
+ (0x27, 'ALIGNPTS', 0, 'AlignPts', 2, 0), # p2, p1 -
+ (0x3c, 'ALIGNRP', 0, 'AlignRelativePt', -1, 0), # p1, p2, ... , ploopvalue -
+ (0x5a, 'AND', 0, 'LogicalAnd', 2, 1), # e2, e1 b
+ (0x2b, 'CALL', 0, 'CallFunction', 1, 0), # f -
+ (0x67, 'CEILING', 0, 'Ceiling', 1, 1), # n ceil(n)
+ (0x25, 'CINDEX', 0, 'CopyXToTopStack', 1, 1), # k ek
+ (0x22, 'CLEAR', 0, 'ClearStack', -1, 0), # all items on the stack -
+ (0x4f, 'DEBUG', 0, 'DebugCall', 1, 0), # n -
+ (0x73, 'DELTAC1', 0, 'DeltaExceptionC1', -1, 0), # argn, cn, argn-1,cn-1, , arg1, c1 -
+ (0x74, 'DELTAC2', 0, 'DeltaExceptionC2', -1, 0), # argn, cn, argn-1,cn-1, , arg1, c1 -
+ (0x75, 'DELTAC3', 0, 'DeltaExceptionC3', -1, 0), # argn, cn, argn-1,cn-1, , arg1, c1 -
+ (0x5d, 'DELTAP1', 0, 'DeltaExceptionP1', -1, 0), # argn, pn, argn-1, pn-1, , arg1, p1 -
+ (0x71, 'DELTAP2', 0, 'DeltaExceptionP2', -1, 0), # argn, pn, argn-1, pn-1, , arg1, p1 -
+ (0x72, 'DELTAP3', 0, 'DeltaExceptionP3', -1, 0), # argn, pn, argn-1, pn-1, , arg1, p1 -
+ (0x24, 'DEPTH', 0, 'GetDepthStack', 0, 1), # - n
+ (0x62, 'DIV', 0, 'Divide', 2, 1), # n2, n1 (n1 * 64)/ n2
+ (0x20, 'DUP', 0, 'DuplicateTopStack', 1, 2), # e e, e
+ (0x59, 'EIF', 0, 'EndIf', 0, 0), # - -
+ (0x1b, 'ELSE', 0, 'Else', 0, 0), # - -
+ (0x2d, 'ENDF', 0, 'EndFunctionDefinition', 0, 0), # - -
+ (0x54, 'EQ', 0, 'Equal', 2, 1), # e2, e1 b
+ (0x57, 'EVEN', 0, 'Even', 1, 1), # e b
+ (0x2c, 'FDEF', 0, 'FunctionDefinition', 1, 0), # f -
+ (0x4e, 'FLIPOFF', 0, 'SetAutoFlipOff', 0, 0), # - -
+ (0x4d, 'FLIPON', 0, 'SetAutoFlipOn', 0, 0), # - -
+ (0x80, 'FLIPPT', 0, 'FlipPoint', -1, 0), # p1, p2, ..., ploopvalue -
+ (0x82, 'FLIPRGOFF', 0, 'FlipRangeOff', 2, 0), # h, l -
+ (0x81, 'FLIPRGON', 0, 'FlipRangeOn', 2, 0), # h, l -
+ (0x66, 'FLOOR', 0, 'Floor', 1, 1), # n floor(n)
+ (0x46, 'GC', 1, 'GetCoordOnPVector', 1, 1), # p c
+ (0x88, 'GETINFO', 0, 'GetInfo', 1, 1), # selector result
+ (0x0d, 'GFV', 0, 'GetFVector', 0, 2), # - px, py
+ (0x0c, 'GPV', 0, 'GetPVector', 0, 2), # - px, py
+ (0x52, 'GT', 0, 'GreaterThan', 2, 1), # e2, e1 b
+ (0x53, 'GTEQ', 0, 'GreaterThanOrEqual', 2, 1), # e2, e1 b
+ (0x89, 'IDEF', 0, 'InstructionDefinition', 1, 0), # f -
+ (0x58, 'IF', 0, 'If', 1, 0), # e -
+ (0x8e, 'INSTCTRL', 0, 'SetInstrExecControl', 2, 0), # s, v -
+ (0x39, 'IP', 0, 'InterpolatePts', -1, 0), # p1, p2, ... , ploopvalue -
+ (0x0f, 'ISECT', 0, 'MovePtToIntersect', 5, 0), # a1, a0, b1, b0, p -
+ (0x30, 'IUP', 1, 'InterpolateUntPts', 0, 0), # - -
+ (0x1c, 'JMPR', 0, 'Jump', 1, 0), # offset -
+ (0x79, 'JROF', 0, 'JumpRelativeOnFalse', 2, 0), # e, offset -
+ (0x78, 'JROT', 0, 'JumpRelativeOnTrue', 2, 0), # e, offset -
+ (0x2a, 'LOOPCALL', 0, 'LoopAndCallFunction', 2, 0), # f, count -
+ (0x50, 'LT', 0, 'LessThan', 2, 1), # e2, e1 b
+ (0x51, 'LTEQ', 0, 'LessThenOrEqual', 2, 1), # e2, e1 b
+ (0x8b, 'MAX', 0, 'Maximum', 2, 1), # e2, e1 max(e1, e2)
+ (0x49, 'MD', 1, 'MeasureDistance', 2, 1), # p2,p1 d
+ (0x2e, 'MDAP', 1, 'MoveDirectAbsPt', 1, 0), # p -
+ (0xc0, 'MDRP', 5, 'MoveDirectRelPt', 1, 0), # p -
+ (0x3e, 'MIAP', 1, 'MoveIndirectAbsPt', 2, 0), # n, p -
+ (0x8c, 'MIN', 0, 'Minimum', 2, 1), # e2, e1 min(e1, e2)
+ (0x26, 'MINDEX', 0, 'MoveXToTopStack', 2, 1), # k ek
+ (0xe0, 'MIRP', 5, 'MoveIndirectRelPt', 1, 0), # n, p -
+ (0x4b, 'MPPEM', 0, 'MeasurePixelPerEm', 0, 1), # - ppem
+ (0x4c, 'MPS', 0, 'MeasurePointSize', 0, 1), # - pointSize
+ (0x3a, 'MSIRP', 1, 'MoveStackIndirRelPt', 2, 0), # d, p -
+ (0x63, 'MUL', 0, 'Multiply', 2, 1), # n2, n1 (n1 * n2)/64
+ (0x65, 'NEG', 0, 'Negate', 1, 1), # n -n
+ (0x55, 'NEQ', 0, 'NotEqual', 2, 1), # e2, e1 b
+ (0x5c, 'NOT', 0, 'LogicalNot', 1, 1), # e ( not e )
+ (0x6c, 'NROUND', 2, 'NoRound', 1, 1), # n1 n2
+ (0x56, 'ODD', 0, 'Odd', 1, 1), # e b
+ (0x5b, 'OR', 0, 'LogicalOr', 2, 1), # e2, e1 b
+ (0x21, 'POP', 0, 'PopTopStack', 1, 0), # e -
+ (0x45, 'RCVT', 0, 'ReadCVT', 1, 1), # location value
+ (0x7d, 'RDTG', 0, 'RoundDownToGrid', 0, 0), # - -
+ (0x7a, 'ROFF', 0, 'RoundOff', 0, 0), # - -
+ (0x8a, 'ROLL', 0, 'RollTopThreeStack', 3, 3), # a,b,c b,a,c
+ (0x68, 'ROUND', 2, 'Round', 1, 1), # n1 n2
+ (0x43, 'RS', 0, 'ReadStore', 1, 1), # n v
+ (0x3d, 'RTDG', 0, 'RoundToDoubleGrid', 0, 0), # - -
+ (0x18, 'RTG', 0, 'RoundToGrid', 0, 0), # - -
+ (0x19, 'RTHG', 0, 'RoundToHalfGrid', 0, 0), # - -
+ (0x7c, 'RUTG', 0, 'RoundUpToGrid', 0, 0), # - -
+ (0x77, 'S45ROUND', 0, 'SuperRound45Degrees', 1, 0), # n -
+ (0x7e, 'SANGW', 0, 'SetAngleWeight', 1, 0), # weight -
+ (0x85, 'SCANCTRL', 0, 'ScanConversionControl', 1, 0), # n -
+ (0x8d, 'SCANTYPE', 0, 'ScanType', 1, 0), # n -
+ (0x48, 'SCFS', 0, 'SetCoordFromStackFP', 2, 0), # c, p -
+ (0x1d, 'SCVTCI', 0, 'SetCVTCutIn', 1, 0), # n -
+ (0x5e, 'SDB', 0, 'SetDeltaBaseInGState', 1, 0), # n -
+ (0x86, 'SDPVTL', 1, 'SetDualPVectorToLine', 2, 0), # p2, p1 -
+ (0x5f, 'SDS', 0, 'SetDeltaShiftInGState', 1, 0), # n -
+ (0x0b, 'SFVFS', 0, 'SetFVectorFromStack', 2, 0), # y, x -
+ (0x04, 'SFVTCA', 1, 'SetFVectorToAxis', 0, 0), # - -
+ (0x08, 'SFVTL', 1, 'SetFVectorToLine', 2, 0), # p2, p1 -
+ (0x0e, 'SFVTPV', 0, 'SetFVectorToPVector', 0, 0), # - -
+ (0x34, 'SHC', 1, 'ShiftContourByLastPt', 1, 0), # c -
+ (0x32, 'SHP', 1, 'ShiftPointByLastPoint', -1, 0), # p1, p2, ..., ploopvalue -
+ (0x38, 'SHPIX', 0, 'ShiftZoneByPixel', -1, 0), # d, p1, p2, ..., ploopvalue -
+ (0x36, 'SHZ', 1, 'ShiftZoneByLastPoint', 1, 0), # e -
+ (0x17, 'SLOOP', 0, 'SetLoopVariable', 1, 0), # n -
+ (0x1a, 'SMD', 0, 'SetMinimumDistance', 1, 0), # distance -
+ (0x0a, 'SPVFS', 0, 'SetPVectorFromStack', 2, 0), # y, x -
+ (0x02, 'SPVTCA', 1, 'SetPVectorToAxis', 0, 0), # - -
+ (0x06, 'SPVTL', 1, 'SetPVectorToLine', 2, 0), # p2, p1 -
+ (0x76, 'SROUND', 0, 'SuperRound', 1, 0), # n -
+ (0x10, 'SRP0', 0, 'SetRefPoint0', 1, 0), # p -
+ (0x11, 'SRP1', 0, 'SetRefPoint1', 1, 0), # p -
+ (0x12, 'SRP2', 0, 'SetRefPoint2', 1, 0), # p -
+ (0x1f, 'SSW', 0, 'SetSingleWidth', 1, 0), # n -
+ (0x1e, 'SSWCI', 0, 'SetSingleWidthCutIn', 1, 0), # n -
+ (0x61, 'SUB', 0, 'Subtract', 2, 1), # n2, n1 (n1 - n2)
+ (0x00, 'SVTCA', 1, 'SetFPVectorToAxis', 0, 0), # - -
+ (0x23, 'SWAP', 0, 'SwapTopStack', 2, 2), # e2, e1 e1, e2
+ (0x13, 'SZP0', 0, 'SetZonePointer0', 1, 0), # n -
+ (0x14, 'SZP1', 0, 'SetZonePointer1', 1, 0), # n -
+ (0x15, 'SZP2', 0, 'SetZonePointer2', 1, 0), # n -
+ (0x16, 'SZPS', 0, 'SetZonePointerS', 1, 0), # n -
+ (0x29, 'UTP', 0, 'UnTouchPt', 1, 0), # p -
+ (0x70, 'WCVTF', 0, 'WriteCVTInFUnits', 2, 0), # n, l -
+ (0x44, 'WCVTP', 0, 'WriteCVTInPixels', 2, 0), # v, l -
+ (0x42, 'WS', 0, 'WriteStore', 2, 0), # v, l -
+# ------ ----------- ----- ------------------------ --- ------ ---------------------------------- --------------
+]
+
+
+def bitRepr(value, bits):
+ s = ""
+ for i in range(bits):
+ s = "01"[value & 0x1] + s
+ value = value >> 1
+ return s
+
+def makeOpcodeDict(instructionList):
+ opcodeDict = {}
+ for op, mnemonic, argbits, name, pops, pushes in instructionList:
+ if argbits:
+ argoffset = op
+ for i in range(1 << argbits):
+ opcodeDict[op+i] = mnemonic, argbits, argoffset, name
+ else:
+ opcodeDict[op] = mnemonic, 0, 0, name
+ return opcodeDict
+
+streamOpcodeDict = makeOpcodeDict(streamInstructions)
+opcodeDict = makeOpcodeDict(instructions)
+
+tt_instructions_error = "TT instructions error"
+
+
+class Program:
+
+ def __init__(self):
+ pass
+
+ def fromBytecode(self, bytecode):
+ self.bytecode = array.array("B")
+ self.bytecode.fromstring(bytecode)
+
+ def fromAssembly(self, assembly):
+ self.assembly = assembly
+
+ def getBytecode(self):
+ if not hasattr(self, "bytecode"):
+ self._assemble()
+ return self.bytecode.tostring()
+
+ def getAssembly(self):
+ if not hasattr(self, "assembly"):
+ self._disassemble()
+ return self.assembly
+
+ def _assemble(self):
+ xxx
+
+ def _disassemble(self):
+ assembly = []
+ i = 0
+ bytecode = self.bytecode
+ numBytecode = len(bytecode)
+ while i < numBytecode:
+ op = bytecode[i]
+ arg = 0
+ try:
+ mnemonic, argbits, argoffset, name = opcodeDict[op]
+ except KeyError:
+ try:
+ mnemonic, argbits, argoffset, name = streamOpcodeDict[op]
+ except KeyError:
+ raise tt_instructions_error, "illegal opcode: 0x%.2x" % op
+ pushbytes = pushwords = 0
+ if argbits:
+ if mnemonic == "PUSHB":
+ pushbytes = op - argoffset + 1
+ else:
+ pushwords = op - argoffset + 1
+ else:
+ i = i + 1
+ if mnemonic == "NPUSHB":
+ pushbytes = bytecode[i]
+ else:
+ pushwords = bytecode[i]
+ i = i + 1
+ assembly.append(mnemonic + "[ ]")
+ for j in range(pushbytes):
+ assembly.append(`bytecode[i]`)
+ i = i + 1
+ for j in range(0, pushwords, 2):
+ assembly.append(`(bytecode[i] << 8) + bytecode[i+1]`)
+ i = i + 2
+ else:
+ if argbits:
+ assembly.append(mnemonic + "[%s]" % bitRepr(op - argoffset, argbits))
+ else:
+ assembly.append(mnemonic + "[ ]")
+ i = i + 1
+ self.assembly = assembly
+ del self.bytecode
+
+
+fpgm = '@\01476&%\037\023\022\015\014\005\004\002, \260\003%E#E#ah\212 Eh \212#D`D-,KRXED\033!!Y-, EhD \260\001` E\260Fvh\030\212E`D-,\260\022+\260\002%E\260\002%Ej\260@\213`\260\002%#D!!!-,\260\023+\260\002%E\260\002%Ej\270\377\300\214`\260\002%#D!!!-,\261\000\003%EhTX\260\003%E\260\003%E`h \260\004%#D\260\004%#D\033\260\003% Eh \212#D\260\003%Eh`\260\003%#DY-,\260\003% Eh \212#D\260\003%Eh`\260\003%#D-,KRXED\033!!Y-,F#F`\212\212F# F\212`\212a\270\377\200b# \020#\212\261KK\212pE` \260\000PX\260\001a\270\377\272\213\033\260F\214Y\260\020`h\001:-, E\260\003%FRX?\033!\021Y-,KS#KQZX E\212`D\033!!Y-,KS#KQZX8\033!!Y-'
+gpgm = '@\022\011\003\207@\005\200\004\207\000\010\007\202\001\010\004\202\000\010\000\020\320\355\020\336\355\001\020\336\375\032}\336\032\030\375\31610'
+
+p = Program()
+p.fromBytecode(fpgm)
+for line in p.getAssembly():
+ print line
+