Fix GSUB/GPOS recursive lookup subsetting
Subsets IranNastaliq2.ttf correctly now. Yay!
diff --git a/pyotlss.py b/pyotlss.py
index cee5572..5ac99c6 100755
--- a/pyotlss.py
+++ b/pyotlss.py
@@ -12,6 +12,10 @@
setattr (clazz, method.func_name, method)
return wrapper
+def unique_sorted (l):
+ return sorted ({v:1 for v in l}.keys ())
+
+
# Subset
@add_method(fontTools.ttLib.tables.otTables.Coverage)
@@ -28,7 +32,7 @@
def subset (self, glyphs):
"Returns ascending list of remaining classes."
self.classDefs = {g:v for g,v in self.classDefs.items() if g in glyphs}
- return {v:1 for v in self.classDefs.values ()}.keys ()
+ return unique_sorted (self.classDefs.values ())
@add_method(fontTools.ttLib.tables.otTables.ClassDef)
def remap (self, class_map):
@@ -144,7 +148,7 @@
self.BaseArray.BaseRecord = [self.BaseArray.BaseRecord[i] for i in base_indices]
self.BaseArray.BaseCount = len (self.BaseArray.BaseRecord)
# Prune empty classes
- class_indices = {v.Class:1 for v in self.MarkArray.MarkRecord}.keys ()
+ class_indices = unique_sorted (v.Class for v in self.MarkArray.MarkRecord)
self.ClassCount = len (class_indices)
for m in self.MarkArray.MarkRecord:
m.Class = class_indices.index (m.Class)
@@ -164,7 +168,7 @@
self.LigatureArray.LigatureAttach = [self.LigatureArray.LigatureAttach[i] for i in ligature_indices]
self.LigatureArray.LigatureCount = len (self.LigatureArray.LigatureAttach)
# Prune empty classes
- class_indices = {v.Class:1 for v in self.MarkArray.MarkRecord}.keys ()
+ class_indices = unique_sorted (v.Class for v in self.MarkArray.MarkRecord)
self.ClassCount = len (class_indices)
for m in self.MarkArray.MarkRecord:
m.Class = class_indices.index (m.Class)
@@ -185,7 +189,7 @@
self.Mark2Array.Mark2Record = [self.Mark2Array.Mark2Record[i] for i in mark2_indices]
self.Mark2Array.MarkCount = len (self.Mark2Array.Mark2Record)
# Prune empty classes
- class_indices = {v.Class:1 for v in self.Mark1Array.MarkRecord}.keys ()
+ class_indices = unique_sorted (v.Class for v in self.Mark1Array.MarkRecord)
self.ClassCount = len (class_indices)
for m in self.Mark1Array.MarkRecord:
m.Class = class_indices.index (m.Class)
@@ -195,6 +199,34 @@
else:
assert 0, "unknown format: %s" % self.Format
+@add_method(fontTools.ttLib.tables.otTables.SingleSubst,
+ fontTools.ttLib.tables.otTables.MultipleSubst,
+ fontTools.ttLib.tables.otTables.AlternateSubst,
+ fontTools.ttLib.tables.otTables.LigatureSubst,
+ fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
+ fontTools.ttLib.tables.otTables.SinglePos,
+ fontTools.ttLib.tables.otTables.PairPos,
+ fontTools.ttLib.tables.otTables.CursivePos,
+ fontTools.ttLib.tables.otTables.MarkBasePos,
+ fontTools.ttLib.tables.otTables.MarkLigPos,
+ fontTools.ttLib.tables.otTables.MarkMarkPos)
+def subset_lookups (self, lookup_indices):
+ pass
+
+@add_method(fontTools.ttLib.tables.otTables.SingleSubst,
+ fontTools.ttLib.tables.otTables.MultipleSubst,
+ fontTools.ttLib.tables.otTables.AlternateSubst,
+ fontTools.ttLib.tables.otTables.LigatureSubst,
+ fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
+ fontTools.ttLib.tables.otTables.SinglePos,
+ fontTools.ttLib.tables.otTables.PairPos,
+ fontTools.ttLib.tables.otTables.CursivePos,
+ fontTools.ttLib.tables.otTables.MarkBasePos,
+ fontTools.ttLib.tables.otTables.MarkLigPos,
+ fontTools.ttLib.tables.otTables.MarkMarkPos)
+def collect_lookups (self):
+ return []
+
@add_method(fontTools.ttLib.tables.otTables.ContextSubst, fontTools.ttLib.tables.otTables.ContextPos)
def subset (self, glyphs):
if self.Format == 1:
@@ -236,6 +268,18 @@
else:
assert 0, "unknown format: %s" % self.Format
+@add_method(fontTools.ttLib.tables.otTables.ContextSubst, fontTools.ttLib.tables.otTables.ContextPos,
+ fontTools.ttLib.tables.otTables.ChainContextSubst, fontTools.ttLib.tables.otTables.ChainContextPos)
+def subset_lookups (self, lookup_indices):
+ self.SubstLookupRecord = [ll for ll in self.SubstLookupRecord if ll.LookupListIndex in lookup_indices]
+ for ll in self.SubstLookupRecord:
+ ll.LookupListIndex = lookup_indices.index (ll.LookupListIndex)
+
+@add_method(fontTools.ttLib.tables.otTables.ContextSubst, fontTools.ttLib.tables.otTables.ContextPos,
+ fontTools.ttLib.tables.otTables.ChainContextSubst, fontTools.ttLib.tables.otTables.ChainContextPos)
+def collect_lookups (self):
+ return [ll.LookupListIndex for ll in self.SubstLookupRecord]
+
@add_method(fontTools.ttLib.tables.otTables.ExtensionSubst, fontTools.ttLib.tables.otTables.ExtensionPos)
def subset (self, glyphs):
if self.Format == 1:
@@ -243,12 +287,35 @@
else:
assert 0, "unknown format: %s" % self.Format
+@add_method(fontTools.ttLib.tables.otTables.ExtensionSubst, fontTools.ttLib.tables.otTables.ExtensionPos)
+def subset_lookups (self, lookup_indices):
+ if self.Format == 1:
+ return self.ExtSubTable.subset_lookups (lookup_indices)
+ else:
+ assert 0, "unknown format: %s" % self.Format
+
+@add_method(fontTools.ttLib.tables.otTables.ExtensionSubst, fontTools.ttLib.tables.otTables.ExtensionPos)
+def collect_lookups (self):
+ if self.Format == 1:
+ return self.ExtSubTable.collect_lookups ()
+ else:
+ assert 0, "unknown format: %s" % self.Format
+
@add_method(fontTools.ttLib.tables.otTables.Lookup)
def subset (self, glyphs):
self.SubTable = [s for s in self.SubTable if s.subset (glyphs)]
self.SubTableCount = len (self.SubTable)
return self.SubTableCount
+@add_method(fontTools.ttLib.tables.otTables.Lookup)
+def subset_lookups (self, lookup_indices):
+ for s in self.SubTable:
+ s.subset_lookups (lookup_indices)
+
+@add_method(fontTools.ttLib.tables.otTables.Lookup)
+def collect_lookups (self):
+ return unique_sorted (sum ((s.collect_lookups () for s in self.SubTable), []))
+
@add_method(fontTools.ttLib.tables.otTables.LookupList)
def subset (self, glyphs):
"Returns the indices of nonempty lookups."
@@ -258,6 +325,17 @@
def subset_lookups (self, lookup_indices):
self.Lookup = [self.Lookup[i] for i in lookup_indices]
self.LookupCount = len (self.Lookup)
+ for l in self.Lookup:
+ l.subset_lookups (lookup_indices)
+
+@add_method(fontTools.ttLib.tables.otTables.LookupList)
+def closure_lookups (self, lookup_indices):
+ while True:
+ recurse_lookups = sum ((self.Lookup[i].collect_lookups () for i in lookup_indices), [])
+ recurse_lookups = [l for l in recurse_lookups if l not in lookup_indices]
+ if not recurse_lookups:
+ return lookup_indices
+ lookup_indices = unique_sorted (lookup_indices + recurse_lookups)
@add_method(fontTools.ttLib.tables.otTables.Feature)
def subset_lookups (self, lookup_indices):
@@ -267,6 +345,10 @@
self.LookupCount = len (self.LookupListIndex)
return self.LookupCount
+@add_method(fontTools.ttLib.tables.otTables.Feature)
+def collect_lookups (self):
+ return self.LookupListIndex[:]
+
@add_method(fontTools.ttLib.tables.otTables.FeatureList)
def subset_lookups (self, lookup_indices):
"Returns the indices of nonempty features."
@@ -275,6 +357,11 @@
self.FeatureCount = len (self.FeatureRecord)
return feature_indices
+@add_method(fontTools.ttLib.tables.otTables.FeatureList)
+def collect_lookups (self, feature_indices):
+ return unique_sorted (sum ((self.FeatureRecord[i].Feature.collect_lookups () for i in feature_indices
+ if i < self.FeatureCount), []))
+
@add_method(fontTools.ttLib.tables.otTables.DefaultLangSys, fontTools.ttLib.tables.otTables.LangSys)
def subset_features (self, feature_indices):
if self.ReqFeatureIndex in feature_indices:
@@ -285,7 +372,14 @@
# Now map them.
self.FeatureIndex = [feature_indices.index (f) for f in self.FeatureIndex if f in feature_indices]
self.FeatureCount = len (self.FeatureIndex)
- return self.FeatureCount
+ return self.FeatureCount or self.ReqFeatureIndex != 65535
+
+@add_method(fontTools.ttLib.tables.otTables.DefaultLangSys, fontTools.ttLib.tables.otTables.LangSys)
+def collect_features (self):
+ feature_indices = self.FeatureIndex[:]
+ if self.ReqFeatureIndex != 65535:
+ feature_indices.append (self.ReqFeatureIndex)
+ return unique_sorted (feature_indices)
@add_method(fontTools.ttLib.tables.otTables.Script)
def subset_features (self, feature_indices):
@@ -293,7 +387,14 @@
self.DefaultLangSys = None
self.LangSysRecord = [l for l in self.LangSysRecord if l.LangSys.subset_features (feature_indices)]
self.LangSysCount = len (self.LangSysRecord)
- return self.LangSysCount
+ return self.LangSysCount or self.DefaultLangSys
+
+@add_method(fontTools.ttLib.tables.otTables.Script)
+def collect_features (self):
+ feature_indices = [l.LangSys.collectFeatures () for l in self.LangSysRecord]
+ if self.DefaultLangSys:
+ feature_indices.append (self.DefaultLangSys.collect_features ())
+ return unique_sorted (sum (feature_indices, []))
@add_method(fontTools.ttLib.tables.otTables.ScriptList)
def subset_features (self, feature_indices):
@@ -301,11 +402,16 @@
self.ScriptCount = len (self.ScriptRecord)
return self.ScriptCount
+@add_method(fontTools.ttLib.tables.otTables.ScriptList)
+def collect_features (self):
+ return unique_sorted (sum ((s.Script.collect_features () for s in self.ScriptRecord), []))
+
@add_method(fontTools.ttLib.getTableClass('GSUB'), fontTools.ttLib.getTableClass('GPOS'))
def subset (self, glyphs):
lookup_indices = self.table.LookupList.subset (glyphs)
self.subset_lookups (lookup_indices)
- return True # Retain the possibly empty table
+ self.prune_lookups ()
+ return True
@add_method(fontTools.ttLib.getTableClass('GSUB'), fontTools.ttLib.getTableClass('GPOS'))
def subset_lookups (self, lookup_indices):
@@ -314,6 +420,14 @@
feature_indices = self.table.FeatureList.subset_lookups (lookup_indices)
self.table.ScriptList.subset_features (feature_indices)
+@add_method(fontTools.ttLib.getTableClass('GSUB'), fontTools.ttLib.getTableClass('GPOS'))
+def prune_lookups (self):
+ "Remove unreferenced lookups"
+ feature_indices = self.table.ScriptList.collect_features ()
+ lookup_indices = self.table.FeatureList.collect_lookups (feature_indices)
+ lookup_indices = self.table.LookupList.closure_lookups (lookup_indices)
+ self.subset_lookups (lookup_indices)
+
@add_method(fontTools.ttLib.getTableClass('GDEF'))
def subset (self, glyphs):
table = self.table