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