Remove tableStack in favor of countVars
We only needed tableStack to look back for counts. So, just store
counts. Gives ~8 speedup for large fonts I tested. We are not
faster than not loading fonts lazily without this patch, so the
laziness patch combined with this doesn't have a net negative
performance impact anymore.
diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py
index 2b405f0..5f742ae 100644
--- a/Lib/fontTools/ttLib/tables/otBase.py
+++ b/Lib/fontTools/ttLib/tables/otBase.py
@@ -490,33 +490,6 @@
return struct.pack(">L", value)
-
-class TableStack:
- """A stack of table dicts, working as a stack of namespaces so we can
- retrieve values from (and store values to) tables higher up the stack."""
- def __init__(self, other=None):
- self.stack = other.stack[:] if other else []
- def push(self, table):
- self.stack.append(table)
- def pop(self):
- self.stack.pop()
- def getTop(self):
- return self.stack[-1]
- def getValue(self, name):
- return self.__findTable(name)[name]
- def storeValue(self, name, value):
- table = self.__findTable(name)
- if table[name] is None:
- table[name] = value
- else:
- assert table[name] == value, (table[name], value)
- def __findTable(self, name):
- for table in reversed(self.stack):
- if table.has_key(name):
- return table
- raise KeyError, name
-
-
class BaseTable(object):
def __init__(self):
self.compileStatus = 0 # 0 means table was created
@@ -556,15 +529,16 @@
def getConverterByName(self, name):
return self.convertersByName[name]
- def decompile(self, reader, font, tableStack=None):
+ def decompile(self, reader, font, countVars=None):
self.compileStatus = 2 # table has been decompiled.
- if tableStack is None:
- tableStack = TableStack()
+ if countVars is None:
+ countVars = {}
self.readFormat(reader)
+ counts = []
table = {}
self.__rawTable = table # for debugging
- tableStack.push(table)
- for conv in self.getConverters():
+ converters = self.getConverters()
+ for conv in converters:
if conv.name == "SubTable":
conv = conv.getConverter(reader.tableType,
table["LookupType"])
@@ -573,55 +547,79 @@
table["ExtensionLookupType"])
if conv.repeat:
l = []
- for i in range(tableStack.getValue(conv.repeat) + conv.repeatOffset):
- l.append(conv.read(reader, font, tableStack))
+ for i in range(countVars[conv.repeat] + conv.repeatOffset):
+ l.append(conv.read(reader, font, countVars))
table[conv.name] = l
+ if conv.repeat in counts:
+ del countVars[conv.repeat]
+ counts.remove(conv.repeat)
+
else:
- table[conv.name] = conv.read(reader, font, tableStack)
- tableStack.pop()
+ table[conv.name] = conv.read(reader, font, countVars)
+ if conv.isCount:
+ counts.append(conv.name)
+ countVars[conv.name] = table[conv.name]
+
+ for count in counts:
+ del countVars[count]
+
self.postRead(table, font)
+
del self.__rawTable # succeeded, get rid of debugging info
def ensureDecompiled(self):
if self.compileStatus != 1:
return
- self.decompile(self.reader, self.font, self.tableStack)
- del self.reader, self.font, self.tableStack
+ self.decompile(self.reader, self.font, self.countVars)
+ del self.reader, self.font, self.countVars
def preCompile(self):
pass # used only by the LookupList class
- def compile(self, writer, font, tableStack=None):
- if tableStack is None:
- tableStack = TableStack()
+ def compile(self, writer, font, countVars=None):
+ if countVars is None:
+ countVars = {}
+ counts = []
table = self.preWrite(font)
if hasattr(self, 'sortCoverageLast'):
writer.sortCoverageLast = 1
self.writeFormat(writer)
- tableStack.push(table)
for conv in self.getConverters():
value = table.get(conv.name)
if conv.repeat:
if value is None:
value = []
- tableStack.storeValue(conv.repeat, len(value) - conv.repeatOffset)
+ countVars[conv.repeat](len(value) - conv.repeatOffset)
for i in range(len(value)):
- conv.write(writer, font, tableStack, value[i], i)
+ conv.write(writer, font, countVars, value[i], i)
+ if conv.repeat in counts:
+ del countVars[conv.repeat]
+ counts.remove(conv.repeat)
elif conv.isCount:
# Special-case Count values.
# Assumption: a Count field will *always* precede
# the actual array.
# We need a default value, as it may be set later by a nested
- # table. TableStack.storeValue() will then find it here.
+ # table. We will later store it here.
table[conv.name] = None
# We add a reference: by the time the data is assembled
# the Count value will be filled in.
- writer.writeCountReference(table, conv.name)
+ name = conv.name
+ writer.writeCountReference(table, name)
+ counts.append(name)
+ def storeValue(value):
+ if table[name] is None:
+ table[name] = value
+ else:
+ assert table[name] == value, (table[name], value)
+ countVars[name] = storeValue
else:
- conv.write(writer, font, tableStack, value)
- tableStack.pop()
+ conv.write(writer, font, countVars, value)
+
+ for count in counts:
+ del countVars[count]
def readFormat(self, reader):
pass
diff --git a/Lib/fontTools/ttLib/tables/otConverters.py b/Lib/fontTools/ttLib/tables/otConverters.py
index edf2999..7f5970a 100644
--- a/Lib/fontTools/ttLib/tables/otConverters.py
+++ b/Lib/fontTools/ttLib/tables/otConverters.py
@@ -1,6 +1,5 @@
from types import TupleType
from fontTools.misc.textTools import safeEval
-from otBase import TableStack
def buildConverters(tableSpec, tableNamespace):
@@ -51,11 +50,11 @@
self.tableClass = tableClass
self.isCount = name.endswith("Count")
- def read(self, reader, font, tableStack):
+ def read(self, reader, font, countVars):
"""Read a value from the reader."""
raise NotImplementedError, self
- def write(self, writer, font, tableStack, value, repeatIndex=None):
+ def write(self, writer, font, countVars, value, repeatIndex=None):
"""Write a value to the writer."""
raise NotImplementedError, self
@@ -80,29 +79,29 @@
return int(attrs["value"])
class Long(IntValue):
- def read(self, reader, font, tableStack):
+ def read(self, reader, font, countVars):
return reader.readLong()
- def write(self, writer, font, tableStack, value, repeatIndex=None):
+ def write(self, writer, font, countVars, value, repeatIndex=None):
writer.writeLong(value)
class Fixed(IntValue):
- def read(self, reader, font, tableStack):
+ def read(self, reader, font, countVars):
return float(reader.readLong()) / 0x10000
- def write(self, writer, font, tableStack, value, repeatIndex=None):
+ def write(self, writer, font, countVars, value, repeatIndex=None):
writer.writeLong(int(round(value * 0x10000)))
def xmlRead(self, attrs, content, font):
return float(attrs["value"])
class Short(IntValue):
- def read(self, reader, font, tableStack):
+ def read(self, reader, font, countVars):
return reader.readShort()
- def write(self, writer, font, tableStack, value, repeatIndex=None):
+ def write(self, writer, font, countVars, value, repeatIndex=None):
writer.writeShort(value)
class UShort(IntValue):
- def read(self, reader, font, tableStack):
+ def read(self, reader, font, countVars):
return reader.readUShort()
- def write(self, writer, font, tableStack, value, repeatIndex=None):
+ def write(self, writer, font, countVars, value, repeatIndex=None):
writer.writeUShort(value)
class Count(Short):
@@ -111,31 +110,31 @@
xmlWriter.newline()
class Tag(SimpleValue):
- def read(self, reader, font, tableStack):
+ def read(self, reader, font, countVars):
return reader.readTag()
- def write(self, writer, font, tableStack, value, repeatIndex=None):
+ def write(self, writer, font, countVars, value, repeatIndex=None):
writer.writeTag(value)
class GlyphID(SimpleValue):
- def read(self, reader, font, tableStack):
+ def read(self, reader, font, countVars):
value = reader.readUShort()
value = font.getGlyphName(value)
return value
- def write(self, writer, font, tableStack, value, repeatIndex=None):
+ def write(self, writer, font, countVars, value, repeatIndex=None):
value = font.getGlyphID(value)
writer.writeUShort(value)
class Struct(BaseConverter):
- def read(self, reader, font, tableStack):
+ def read(self, reader, font, countVars):
table = self.tableClass()
- table.decompile(reader, font, tableStack)
+ table.decompile(reader, font, countVars)
return table
- def write(self, writer, font, tableStack, value, repeatIndex=None):
- value.compile(writer, font, tableStack)
+ def write(self, writer, font, countVars, value, repeatIndex=None):
+ value.compile(writer, font, countVars)
def xmlWrite(self, xmlWriter, font, value, name, attrs):
if value is None:
@@ -159,9 +158,7 @@
class Table(Struct):
- def read(self, reader, font, tableStack, lazy=True):
- # For now, we lazy-decompile all tables. Perhaps we should
- # use a more sophisticated heuristic here.
+ def read(self, reader, font, countVars, lazy=True):
offset = reader.readUShort()
if offset == 0:
return None
@@ -176,12 +173,12 @@
table.reader = subReader
table.font = font
table.compileStatus = 1
- table.tableStack = TableStack(tableStack)
+ table.countVars = countVars.copy()
else:
- table.decompile(subReader, font, tableStack)
+ table.decompile(subReader, font, countVars)
return table
- def write(self, writer, font, tableStack, value, repeatIndex=None):
+ def write(self, writer, font, countVars, value, repeatIndex=None):
if value is None:
writer.writeUShort(0)
else:
@@ -191,7 +188,7 @@
subWriter.repeatIndex = repeatIndex
value.preCompile()
writer.writeSubTable(subWriter)
- value.compile(subWriter, font, tableStack)
+ value.compile(subWriter, font, countVars)
class SubTable(Table):
def getConverter(self, tableType, lookupType):
@@ -206,7 +203,7 @@
tableClass = lookupTypes[lookupType]
return ExtSubTable(self.name, self.repeat, self.repeatOffset, tableClass)
- def read(self, reader, font, tableStack, lazy=True):
+ def read(self, reader, font, countVars, lazy=True):
offset = reader.readULong()
if offset == 0:
return None
@@ -217,19 +214,19 @@
table.reader = subReader
table.font = font
table.compileStatus = 1
- table.tableStack = TableStack(tableStack)
+ table.countVars = countVars.copy()
else:
- table.decompile(subReader, font, tableStack)
+ table.decompile(subReader, font, countVars)
return table
- def write(self, writer, font, tableStack, value, repeatIndex=None):
+ def write(self, writer, font, countVars, value, repeatIndex=None):
writer.Extension = 1 # actually, mere presence of the field flags it as an Ext Subtable writer.
if value is None:
writer.writeULong(0)
else:
# If the subtable has not yet been decompiled, we need to do so.
if value.compileStatus == 1:
- value.decompile(value.reader, value.font, tableStack)
+ value.decompile(value.reader, value.font, countVars)
subWriter = writer.getSubWriter()
subWriter.name = self.name
writer.writeSubTable(subWriter)
@@ -239,26 +236,26 @@
data = value.reader.data[value.start:value.end]
subWriter.writeData(data)
else:
- value.compile(subWriter, font, tableStack)
+ value.compile(subWriter, font, countVars)
class ValueFormat(IntValue):
def __init__(self, name, repeat, repeatOffset, tableClass):
BaseConverter.__init__(self, name, repeat, repeatOffset, tableClass)
self.which = name[-1] == "2"
- def read(self, reader, font, tableStack):
+ def read(self, reader, font, countVars):
format = reader.readUShort()
reader.setValueFormat(format, self.which)
return format
- def write(self, writer, font, tableStack, format, repeatIndex=None):
+ def write(self, writer, font, countVars, format, repeatIndex=None):
writer.writeUShort(format)
writer.setValueFormat(format, self.which)
class ValueRecord(ValueFormat):
- def read(self, reader, font, tableStack):
+ def read(self, reader, font, countVars):
return reader.readValueRecord(font, self.which)
- def write(self, writer, font, tableStack, value, repeatIndex=None):
+ def write(self, writer, font, countVars, value, repeatIndex=None):
writer.writeValueRecord(value, font, self.which)
def xmlWrite(self, xmlWriter, font, value, name, attrs):
if value is None:
@@ -274,8 +271,8 @@
class DeltaValue(BaseConverter):
- def read(self, reader, font, tableStack):
- table = tableStack.getTop()
+ def read(self, reader, font, countVars):
+ table = countVars.getTop()
StartSize = table["StartSize"]
EndSize = table["EndSize"]
DeltaFormat = table["DeltaFormat"]
@@ -298,8 +295,8 @@
DeltaValue.append(value)
return DeltaValue
- def write(self, writer, font, tableStack, value, repeatIndex=None):
- table = tableStack.getTop()
+ def write(self, writer, font, countVars, value, repeatIndex=None):
+ table = countVars.getTop()
StartSize = table["StartSize"]
EndSize = table["EndSize"]
DeltaFormat = table["DeltaFormat"]