[mtiLib] Implement forward references

This makes mtiLib feature complete.  Next step would be to add tests.
This commit is contained in:
Behdad Esfahbod 2016-01-19 18:01:00 +01:00
parent 057c4faa33
commit 4fd3b2c4ca

View File

@ -12,12 +12,16 @@ from fontTools.ttLib.tables import otTables as ot
from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict
from fontTools.otlLib import builder from fontTools.otlLib import builder
from contextlib import contextmanager from contextlib import contextmanager
from operator import setitem
class MtiLibError(Exception): pass class MtiLibError(Exception): pass
class FeatureNotFoundError(MtiLibError): pass class ReferenceNotFoundError(MtiLibError): pass
class LookupNotFoundError(MtiLibError): pass class FeatureNotFoundError(ReferenceNotFoundError): pass
class LookupNotFoundError(ReferenceNotFoundError): pass
debug = print def debug(*args):
#print(*args)
pass
def makeGlyph(s): def makeGlyph(s):
if s[:2] == 'U ': if s[:2] == 'U ':
@ -54,6 +58,40 @@ def mapFeature(sym, mapping):
raise FeatureNotFoundError(sym) raise FeatureNotFoundError(sym)
return idx return idx
def setReference(mapper, mapping, sym, setter, collection, key):
try:
mapped = mapper(sym, mapping)
except ReferenceNotFoundError as e:
try:
if mapping is not None:
mapping.addDeferredMapping(lambda ref: setter(collection, key, ref), sym, e)
return
except AttributeError:
pass
raise
setter(collection, key, mapped)
class DeferredMapping(dict):
def __init__(self):
self._deferredMappings = []
def addDeferredMapping(self, setter, sym, e):
debug("Adding deferred mapping for symbol '%s'" % sym, type(e).__name__)
self._deferredMappings.append((setter,sym, e))
def applyDeferredMappings(self):
for setter,sym,e in self._deferredMappings:
debug("Applying deferred mapping for symbol '%s'" % sym, type(e).__name__)
try:
mapped = self[sym]
except KeyError:
raise e
setter(mapped)
debug("Set to %s" % mapped)
self._deferredMappings = []
def parseScriptList(lines, featureMap=None): def parseScriptList(lines, featureMap=None):
self = ot.ScriptList() self = ot.ScriptList()
records = [] records = []
@ -64,8 +102,14 @@ def parseScriptList(lines, featureMap=None):
langSys = ot.LangSys() langSys = ot.LangSys()
langSys.LookupOrder = None langSys.LookupOrder = None
langSys.ReqFeatureIndex = mapFeature(defaultFeature, featureMap) if defaultFeature else 0xFFFF if defaultFeature:
langSys.FeatureIndex = [mapFeature(sym, featureMap) for sym in stripSplitComma(features)] setReference(mapFeature, featureMap, defaultFeature, setattr, langSys, 'ReqFeatureIndex')
else:
langSys.ReqFeatureIndex = 0xFFFF
syms = stripSplitComma(features)
langSys.FeatureIndex = theList = [3] * len(syms)
for i,sym in enumerate(syms):
setReference(mapFeature, featureMap, sym, setitem, theList, i)
langSys.FeatureCount = len(langSys.FeatureIndex) langSys.FeatureCount = len(langSys.FeatureIndex)
script = [s for s in records if s.ScriptTag == scriptTag] script = [s for s in records if s.ScriptTag == scriptTag]
@ -116,7 +160,10 @@ def parseFeatureList(lines, lookupMap=None, featureMap=None):
self.FeatureRecord.append(featureRec) self.FeatureRecord.append(featureRec)
feature = featureRec.Feature feature = featureRec.Feature
feature.FeatureParams = None feature.FeatureParams = None
feature.LookupListIndex = [mapLookup(sym, lookupMap) for sym in stripSplitComma(lookups)] syms = stripSplitComma(lookups)
feature.LookupListIndex = theList = [None] * len(syms)
for i,sym in enumerate(syms):
setReference(mapLookup, lookupMap, sym, setitem, theList, i)
feature.LookupCount = len(feature.LookupListIndex) feature.LookupCount = len(feature.LookupListIndex)
self.FeatureCount = len(self.FeatureRecord) self.FeatureCount = len(self.FeatureRecord)
@ -576,7 +623,7 @@ def parseLookupRecords(items, klassName, lookupMap=None):
idx = int(item[0]) idx = int(item[0])
assert idx > 0, idx assert idx > 0, idx
rec.SequenceIndex = idx - 1 rec.SequenceIndex = idx - 1
rec.LookupListIndex = mapLookup(item[1], lookupMap) setReference(mapLookup, lookupMap, item[1], setattr, rec, 'LookupListIndex')
lst.append(rec) lst.append(rec)
return lst return lst
@ -797,8 +844,8 @@ def parseLookup(lines, tableTag, font, lookupMap=None):
return lookup return lookup
def parseGSUBGPOS(lines, font, tableTag): def parseGSUBGPOS(lines, font, tableTag):
lookupMap = None#{} Until we support forward references... lookupMap = DeferredMapping()
featureMap = {} featureMap = DeferredMapping()
assert tableTag in ('GSUB', 'GPOS') assert tableTag in ('GSUB', 'GPOS')
debug("Parsing", tableTag) debug("Parsing", tableTag)
self = getattr(ot, tableTag)() self = getattr(ot, tableTag)()
@ -829,7 +876,7 @@ def parseGSUBGPOS(lines, font, tableTag):
self.LookupList.Lookup = [] self.LookupList.Lookup = []
_, name, _ = lines.peek() _, name, _ = lines.peek()
lookup = parseLookup(lines, tableTag, font, lookupMap) lookup = parseLookup(lines, tableTag, font, lookupMap)
if lookupMap: if lookupMap is not None:
assert name not in lookupMap, "Duplicate lookup name: %s" % name assert name not in lookupMap, "Duplicate lookup name: %s" % name
lookupMap[name] = len(self.LookupList.Lookup) lookupMap[name] = len(self.LookupList.Lookup)
else: else:
@ -840,6 +887,10 @@ def parseGSUBGPOS(lines, font, tableTag):
setattr(self, attr, parser(lines)) setattr(self, attr, parser(lines))
if self.LookupList: if self.LookupList:
self.LookupList.LookupCount = len(self.LookupList.Lookup) self.LookupList.LookupCount = len(self.LookupList.Lookup)
if lookupMap is not None:
lookupMap.applyDeferredMappings()
if featureMap is not None:
featureMap.applyDeferredMappings()
return self return self
def parseGSUB(lines, font): def parseGSUB(lines, font):