527 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			527 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | # Copyright 2013 Google, Inc. All Rights Reserved. | ||
|  | # | ||
|  | # Google Author(s): Behdad Esfahbod, Roozbeh Pournader | ||
|  | 
 | ||
|  | from fontTools import ttLib | ||
|  | from fontTools.ttLib.tables.DefaultTable import DefaultTable | ||
|  | from fontTools.ttLib.tables import otTables | ||
|  | from fontTools.merge.base import add_method, mergeObjects | ||
|  | from fontTools.merge.util import * | ||
|  | import logging | ||
|  | 
 | ||
|  | 
 | ||
|  | log = logging.getLogger("fontTools.merge") | ||
|  | 
 | ||
|  | 
 | ||
|  | def mergeLookupLists(lst): | ||
|  |     # TODO Do smarter merge. | ||
|  |     return sumLists(lst) | ||
|  | 
 | ||
|  | 
 | ||
|  | def mergeFeatures(lst): | ||
|  |     assert lst | ||
|  |     self = otTables.Feature() | ||
|  |     self.FeatureParams = None | ||
|  |     self.LookupListIndex = mergeLookupLists( | ||
|  |         [l.LookupListIndex for l in lst if l.LookupListIndex] | ||
|  |     ) | ||
|  |     self.LookupCount = len(self.LookupListIndex) | ||
|  |     return self | ||
|  | 
 | ||
|  | 
 | ||
|  | def mergeFeatureLists(lst): | ||
|  |     d = {} | ||
|  |     for l in lst: | ||
|  |         for f in l: | ||
|  |             tag = f.FeatureTag | ||
|  |             if tag not in d: | ||
|  |                 d[tag] = [] | ||
|  |             d[tag].append(f.Feature) | ||
|  |     ret = [] | ||
|  |     for tag in sorted(d.keys()): | ||
|  |         rec = otTables.FeatureRecord() | ||
|  |         rec.FeatureTag = tag | ||
|  |         rec.Feature = mergeFeatures(d[tag]) | ||
|  |         ret.append(rec) | ||
|  |     return ret | ||
|  | 
 | ||
|  | 
 | ||
|  | def mergeLangSyses(lst): | ||
|  |     assert lst | ||
|  | 
 | ||
|  |     # TODO Support merging ReqFeatureIndex | ||
|  |     assert all(l.ReqFeatureIndex == 0xFFFF for l in lst) | ||
|  | 
 | ||
|  |     self = otTables.LangSys() | ||
|  |     self.LookupOrder = None | ||
|  |     self.ReqFeatureIndex = 0xFFFF | ||
|  |     self.FeatureIndex = mergeFeatureLists( | ||
|  |         [l.FeatureIndex for l in lst if l.FeatureIndex] | ||
|  |     ) | ||
|  |     self.FeatureCount = len(self.FeatureIndex) | ||
|  |     return self | ||
|  | 
 | ||
|  | 
 | ||
|  | def mergeScripts(lst): | ||
|  |     assert lst | ||
|  | 
 | ||
|  |     if len(lst) == 1: | ||
|  |         return lst[0] | ||
|  |     langSyses = {} | ||
|  |     for sr in lst: | ||
|  |         for lsr in sr.LangSysRecord: | ||
|  |             if lsr.LangSysTag not in langSyses: | ||
|  |                 langSyses[lsr.LangSysTag] = [] | ||
|  |             langSyses[lsr.LangSysTag].append(lsr.LangSys) | ||
|  |     lsrecords = [] | ||
|  |     for tag, langSys_list in sorted(langSyses.items()): | ||
|  |         lsr = otTables.LangSysRecord() | ||
|  |         lsr.LangSys = mergeLangSyses(langSys_list) | ||
|  |         lsr.LangSysTag = tag | ||
|  |         lsrecords.append(lsr) | ||
|  | 
 | ||
|  |     self = otTables.Script() | ||
|  |     self.LangSysRecord = lsrecords | ||
|  |     self.LangSysCount = len(lsrecords) | ||
|  |     dfltLangSyses = [s.DefaultLangSys for s in lst if s.DefaultLangSys] | ||
|  |     if dfltLangSyses: | ||
|  |         self.DefaultLangSys = mergeLangSyses(dfltLangSyses) | ||
|  |     else: | ||
|  |         self.DefaultLangSys = None | ||
|  |     return self | ||
|  | 
 | ||
|  | 
 | ||
|  | def mergeScriptRecords(lst): | ||
|  |     d = {} | ||
|  |     for l in lst: | ||
|  |         for s in l: | ||
|  |             tag = s.ScriptTag | ||
|  |             if tag not in d: | ||
|  |                 d[tag] = [] | ||
|  |             d[tag].append(s.Script) | ||
|  |     ret = [] | ||
|  |     for tag in sorted(d.keys()): | ||
|  |         rec = otTables.ScriptRecord() | ||
|  |         rec.ScriptTag = tag | ||
|  |         rec.Script = mergeScripts(d[tag]) | ||
|  |         ret.append(rec) | ||
|  |     return ret | ||
|  | 
 | ||
|  | 
 | ||
|  | otTables.ScriptList.mergeMap = { | ||
|  |     "ScriptCount": lambda lst: None,  # TODO | ||
|  |     "ScriptRecord": mergeScriptRecords, | ||
|  | } | ||
|  | otTables.BaseScriptList.mergeMap = { | ||
|  |     "BaseScriptCount": lambda lst: None,  # TODO | ||
|  |     # TODO: Merge duplicate entries | ||
|  |     "BaseScriptRecord": lambda lst: sorted( | ||
|  |         sumLists(lst), key=lambda s: s.BaseScriptTag | ||
|  |     ), | ||
|  | } | ||
|  | 
 | ||
|  | otTables.FeatureList.mergeMap = { | ||
|  |     "FeatureCount": sum, | ||
|  |     "FeatureRecord": lambda lst: sorted(sumLists(lst), key=lambda s: s.FeatureTag), | ||
|  | } | ||
|  | 
 | ||
|  | otTables.LookupList.mergeMap = { | ||
|  |     "LookupCount": sum, | ||
|  |     "Lookup": sumLists, | ||
|  | } | ||
|  | 
 | ||
|  | otTables.Coverage.mergeMap = { | ||
|  |     "Format": min, | ||
|  |     "glyphs": sumLists, | ||
|  | } | ||
|  | 
 | ||
|  | otTables.ClassDef.mergeMap = { | ||
|  |     "Format": min, | ||
|  |     "classDefs": sumDicts, | ||
|  | } | ||
|  | 
 | ||
|  | otTables.LigCaretList.mergeMap = { | ||
|  |     "Coverage": mergeObjects, | ||
|  |     "LigGlyphCount": sum, | ||
|  |     "LigGlyph": sumLists, | ||
|  | } | ||
|  | 
 | ||
|  | otTables.AttachList.mergeMap = { | ||
|  |     "Coverage": mergeObjects, | ||
|  |     "GlyphCount": sum, | ||
|  |     "AttachPoint": sumLists, | ||
|  | } | ||
|  | 
 | ||
|  | # XXX Renumber MarkFilterSets of lookups | ||
|  | otTables.MarkGlyphSetsDef.mergeMap = { | ||
|  |     "MarkSetTableFormat": equal, | ||
|  |     "MarkSetCount": sum, | ||
|  |     "Coverage": sumLists, | ||
|  | } | ||
|  | 
 | ||
|  | otTables.Axis.mergeMap = { | ||
|  |     "*": mergeObjects, | ||
|  | } | ||
|  | 
 | ||
|  | # XXX Fix BASE table merging | ||
|  | otTables.BaseTagList.mergeMap = { | ||
|  |     "BaseTagCount": sum, | ||
|  |     "BaselineTag": sumLists, | ||
|  | } | ||
|  | 
 | ||
|  | otTables.GDEF.mergeMap = otTables.GSUB.mergeMap = otTables.GPOS.mergeMap = ( | ||
|  |     otTables.BASE.mergeMap | ||
|  | ) = otTables.JSTF.mergeMap = otTables.MATH.mergeMap = { | ||
|  |     "*": mergeObjects, | ||
|  |     "Version": max, | ||
|  | } | ||
|  | 
 | ||
|  | ttLib.getTableClass("GDEF").mergeMap = ttLib.getTableClass("GSUB").mergeMap = ( | ||
|  |     ttLib.getTableClass("GPOS").mergeMap | ||
|  | ) = ttLib.getTableClass("BASE").mergeMap = ttLib.getTableClass( | ||
|  |     "JSTF" | ||
|  | ).mergeMap = ttLib.getTableClass( | ||
|  |     "MATH" | ||
|  | ).mergeMap = { | ||
|  |     "tableTag": onlyExisting(equal),  # XXX clean me up | ||
|  |     "table": mergeObjects, | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | @add_method(ttLib.getTableClass("GSUB")) | ||
|  | def merge(self, m, tables): | ||
|  |     assert len(tables) == len(m.duplicateGlyphsPerFont) | ||
|  |     for i, (table, dups) in enumerate(zip(tables, m.duplicateGlyphsPerFont)): | ||
|  |         if not dups: | ||
|  |             continue | ||
|  |         if table is None or table is NotImplemented: | ||
|  |             log.warning( | ||
|  |                 "Have non-identical duplicates to resolve for '%s' but no GSUB. Are duplicates intended?: %s", | ||
|  |                 m.fonts[i]._merger__name, | ||
|  |                 dups, | ||
|  |             ) | ||
|  |             continue | ||
|  | 
 | ||
|  |         synthFeature = None | ||
|  |         synthLookup = None | ||
|  |         for script in table.table.ScriptList.ScriptRecord: | ||
|  |             if script.ScriptTag == "DFLT": | ||
|  |                 continue  # XXX | ||
|  |             for langsys in [script.Script.DefaultLangSys] + [ | ||
|  |                 l.LangSys for l in script.Script.LangSysRecord | ||
|  |             ]: | ||
|  |                 if langsys is None: | ||
|  |                     continue  # XXX Create! | ||
|  |                 feature = [v for v in langsys.FeatureIndex if v.FeatureTag == "locl"] | ||
|  |                 assert len(feature) <= 1 | ||
|  |                 if feature: | ||
|  |                     feature = feature[0] | ||
|  |                 else: | ||
|  |                     if not synthFeature: | ||
|  |                         synthFeature = otTables.FeatureRecord() | ||
|  |                         synthFeature.FeatureTag = "locl" | ||
|  |                         f = synthFeature.Feature = otTables.Feature() | ||
|  |                         f.FeatureParams = None | ||
|  |                         f.LookupCount = 0 | ||
|  |                         f.LookupListIndex = [] | ||
|  |                         table.table.FeatureList.FeatureRecord.append(synthFeature) | ||
|  |                         table.table.FeatureList.FeatureCount += 1 | ||
|  |                     feature = synthFeature | ||
|  |                     langsys.FeatureIndex.append(feature) | ||
|  |                     langsys.FeatureIndex.sort(key=lambda v: v.FeatureTag) | ||
|  | 
 | ||
|  |                 if not synthLookup: | ||
|  |                     subtable = otTables.SingleSubst() | ||
|  |                     subtable.mapping = dups | ||
|  |                     synthLookup = otTables.Lookup() | ||
|  |                     synthLookup.LookupFlag = 0 | ||
|  |                     synthLookup.LookupType = 1 | ||
|  |                     synthLookup.SubTableCount = 1 | ||
|  |                     synthLookup.SubTable = [subtable] | ||
|  |                     if table.table.LookupList is None: | ||
|  |                         # mtiLib uses None as default value for LookupList, | ||
|  |                         # while feaLib points to an empty array with count 0 | ||
|  |                         # TODO: make them do the same | ||
|  |                         table.table.LookupList = otTables.LookupList() | ||
|  |                         table.table.LookupList.Lookup = [] | ||
|  |                         table.table.LookupList.LookupCount = 0 | ||
|  |                     table.table.LookupList.Lookup.append(synthLookup) | ||
|  |                     table.table.LookupList.LookupCount += 1 | ||
|  | 
 | ||
|  |                 if feature.Feature.LookupListIndex[:1] != [synthLookup]: | ||
|  |                     feature.Feature.LookupListIndex[:0] = [synthLookup] | ||
|  |                     feature.Feature.LookupCount += 1 | ||
|  | 
 | ||
|  |     DefaultTable.merge(self, m, tables) | ||
|  |     return self | ||
|  | 
 | ||
|  | 
 | ||
|  | @add_method( | ||
|  |     otTables.SingleSubst, | ||
|  |     otTables.MultipleSubst, | ||
|  |     otTables.AlternateSubst, | ||
|  |     otTables.LigatureSubst, | ||
|  |     otTables.ReverseChainSingleSubst, | ||
|  |     otTables.SinglePos, | ||
|  |     otTables.PairPos, | ||
|  |     otTables.CursivePos, | ||
|  |     otTables.MarkBasePos, | ||
|  |     otTables.MarkLigPos, | ||
|  |     otTables.MarkMarkPos, | ||
|  | ) | ||
|  | def mapLookups(self, lookupMap): | ||
|  |     pass | ||
|  | 
 | ||
|  | 
 | ||
|  | # Copied and trimmed down from subset.py | ||
|  | @add_method( | ||
|  |     otTables.ContextSubst, | ||
|  |     otTables.ChainContextSubst, | ||
|  |     otTables.ContextPos, | ||
|  |     otTables.ChainContextPos, | ||
|  | ) | ||
|  | def __merge_classify_context(self): | ||
|  |     class ContextHelper(object): | ||
|  |         def __init__(self, klass, Format): | ||
|  |             if klass.__name__.endswith("Subst"): | ||
|  |                 Typ = "Sub" | ||
|  |                 Type = "Subst" | ||
|  |             else: | ||
|  |                 Typ = "Pos" | ||
|  |                 Type = "Pos" | ||
|  |             if klass.__name__.startswith("Chain"): | ||
|  |                 Chain = "Chain" | ||
|  |             else: | ||
|  |                 Chain = "" | ||
|  |             ChainTyp = Chain + Typ | ||
|  | 
 | ||
|  |             self.Typ = Typ | ||
|  |             self.Type = Type | ||
|  |             self.Chain = Chain | ||
|  |             self.ChainTyp = ChainTyp | ||
|  | 
 | ||
|  |             self.LookupRecord = Type + "LookupRecord" | ||
|  | 
 | ||
|  |             if Format == 1: | ||
|  |                 self.Rule = ChainTyp + "Rule" | ||
|  |                 self.RuleSet = ChainTyp + "RuleSet" | ||
|  |             elif Format == 2: | ||
|  |                 self.Rule = ChainTyp + "ClassRule" | ||
|  |                 self.RuleSet = ChainTyp + "ClassSet" | ||
|  | 
 | ||
|  |     if self.Format not in [1, 2, 3]: | ||
|  |         return None  # Don't shoot the messenger; let it go | ||
|  |     if not hasattr(self.__class__, "_merge__ContextHelpers"): | ||
|  |         self.__class__._merge__ContextHelpers = {} | ||
|  |     if self.Format not in self.__class__._merge__ContextHelpers: | ||
|  |         helper = ContextHelper(self.__class__, self.Format) | ||
|  |         self.__class__._merge__ContextHelpers[self.Format] = helper | ||
|  |     return self.__class__._merge__ContextHelpers[self.Format] | ||
|  | 
 | ||
|  | 
 | ||
|  | @add_method( | ||
|  |     otTables.ContextSubst, | ||
|  |     otTables.ChainContextSubst, | ||
|  |     otTables.ContextPos, | ||
|  |     otTables.ChainContextPos, | ||
|  | ) | ||
|  | def mapLookups(self, lookupMap): | ||
|  |     c = self.__merge_classify_context() | ||
|  | 
 | ||
|  |     if self.Format in [1, 2]: | ||
|  |         for rs in getattr(self, c.RuleSet): | ||
|  |             if not rs: | ||
|  |                 continue | ||
|  |             for r in getattr(rs, c.Rule): | ||
|  |                 if not r: | ||
|  |                     continue | ||
|  |                 for ll in getattr(r, c.LookupRecord): | ||
|  |                     if not ll: | ||
|  |                         continue | ||
|  |                     ll.LookupListIndex = lookupMap[ll.LookupListIndex] | ||
|  |     elif self.Format == 3: | ||
|  |         for ll in getattr(self, c.LookupRecord): | ||
|  |             if not ll: | ||
|  |                 continue | ||
|  |             ll.LookupListIndex = lookupMap[ll.LookupListIndex] | ||
|  |     else: | ||
|  |         assert 0, "unknown format: %s" % self.Format | ||
|  | 
 | ||
|  | 
 | ||
|  | @add_method(otTables.ExtensionSubst, otTables.ExtensionPos) | ||
|  | def mapLookups(self, lookupMap): | ||
|  |     if self.Format == 1: | ||
|  |         self.ExtSubTable.mapLookups(lookupMap) | ||
|  |     else: | ||
|  |         assert 0, "unknown format: %s" % self.Format | ||
|  | 
 | ||
|  | 
 | ||
|  | @add_method(otTables.Lookup) | ||
|  | def mapLookups(self, lookupMap): | ||
|  |     for st in self.SubTable: | ||
|  |         if not st: | ||
|  |             continue | ||
|  |         st.mapLookups(lookupMap) | ||
|  | 
 | ||
|  | 
 | ||
|  | @add_method(otTables.LookupList) | ||
|  | def mapLookups(self, lookupMap): | ||
|  |     for l in self.Lookup: | ||
|  |         if not l: | ||
|  |             continue | ||
|  |         l.mapLookups(lookupMap) | ||
|  | 
 | ||
|  | 
 | ||
|  | @add_method(otTables.Lookup) | ||
|  | def mapMarkFilteringSets(self, markFilteringSetMap): | ||
|  |     if self.LookupFlag & 0x0010: | ||
|  |         self.MarkFilteringSet = markFilteringSetMap[self.MarkFilteringSet] | ||
|  | 
 | ||
|  | 
 | ||
|  | @add_method(otTables.LookupList) | ||
|  | def mapMarkFilteringSets(self, markFilteringSetMap): | ||
|  |     for l in self.Lookup: | ||
|  |         if not l: | ||
|  |             continue | ||
|  |         l.mapMarkFilteringSets(markFilteringSetMap) | ||
|  | 
 | ||
|  | 
 | ||
|  | @add_method(otTables.Feature) | ||
|  | def mapLookups(self, lookupMap): | ||
|  |     self.LookupListIndex = [lookupMap[i] for i in self.LookupListIndex] | ||
|  | 
 | ||
|  | 
 | ||
|  | @add_method(otTables.FeatureList) | ||
|  | def mapLookups(self, lookupMap): | ||
|  |     for f in self.FeatureRecord: | ||
|  |         if not f or not f.Feature: | ||
|  |             continue | ||
|  |         f.Feature.mapLookups(lookupMap) | ||
|  | 
 | ||
|  | 
 | ||
|  | @add_method(otTables.DefaultLangSys, otTables.LangSys) | ||
|  | def mapFeatures(self, featureMap): | ||
|  |     self.FeatureIndex = [featureMap[i] for i in self.FeatureIndex] | ||
|  |     if self.ReqFeatureIndex != 65535: | ||
|  |         self.ReqFeatureIndex = featureMap[self.ReqFeatureIndex] | ||
|  | 
 | ||
|  | 
 | ||
|  | @add_method(otTables.Script) | ||
|  | def mapFeatures(self, featureMap): | ||
|  |     if self.DefaultLangSys: | ||
|  |         self.DefaultLangSys.mapFeatures(featureMap) | ||
|  |     for l in self.LangSysRecord: | ||
|  |         if not l or not l.LangSys: | ||
|  |             continue | ||
|  |         l.LangSys.mapFeatures(featureMap) | ||
|  | 
 | ||
|  | 
 | ||
|  | @add_method(otTables.ScriptList) | ||
|  | def mapFeatures(self, featureMap): | ||
|  |     for s in self.ScriptRecord: | ||
|  |         if not s or not s.Script: | ||
|  |             continue | ||
|  |         s.Script.mapFeatures(featureMap) | ||
|  | 
 | ||
|  | 
 | ||
|  | def layoutPreMerge(font): | ||
|  |     # Map indices to references | ||
|  | 
 | ||
|  |     GDEF = font.get("GDEF") | ||
|  |     GSUB = font.get("GSUB") | ||
|  |     GPOS = font.get("GPOS") | ||
|  | 
 | ||
|  |     for t in [GSUB, GPOS]: | ||
|  |         if not t: | ||
|  |             continue | ||
|  | 
 | ||
|  |         if t.table.LookupList: | ||
|  |             lookupMap = {i: v for i, v in enumerate(t.table.LookupList.Lookup)} | ||
|  |             t.table.LookupList.mapLookups(lookupMap) | ||
|  |             t.table.FeatureList.mapLookups(lookupMap) | ||
|  | 
 | ||
|  |             if ( | ||
|  |                 GDEF | ||
|  |                 and GDEF.table.Version >= 0x00010002 | ||
|  |                 and GDEF.table.MarkGlyphSetsDef | ||
|  |             ): | ||
|  |                 markFilteringSetMap = { | ||
|  |                     i: v for i, v in enumerate(GDEF.table.MarkGlyphSetsDef.Coverage) | ||
|  |                 } | ||
|  |                 t.table.LookupList.mapMarkFilteringSets(markFilteringSetMap) | ||
|  | 
 | ||
|  |         if t.table.FeatureList and t.table.ScriptList: | ||
|  |             featureMap = {i: v for i, v in enumerate(t.table.FeatureList.FeatureRecord)} | ||
|  |             t.table.ScriptList.mapFeatures(featureMap) | ||
|  | 
 | ||
|  |     # TODO FeatureParams nameIDs | ||
|  | 
 | ||
|  | 
 | ||
|  | def layoutPostMerge(font): | ||
|  |     # Map references back to indices | ||
|  | 
 | ||
|  |     GDEF = font.get("GDEF") | ||
|  |     GSUB = font.get("GSUB") | ||
|  |     GPOS = font.get("GPOS") | ||
|  | 
 | ||
|  |     for t in [GSUB, GPOS]: | ||
|  |         if not t: | ||
|  |             continue | ||
|  | 
 | ||
|  |         if t.table.FeatureList and t.table.ScriptList: | ||
|  |             # Collect unregistered (new) features. | ||
|  |             featureMap = GregariousIdentityDict(t.table.FeatureList.FeatureRecord) | ||
|  |             t.table.ScriptList.mapFeatures(featureMap) | ||
|  | 
 | ||
|  |             # Record used features. | ||
|  |             featureMap = AttendanceRecordingIdentityDict( | ||
|  |                 t.table.FeatureList.FeatureRecord | ||
|  |             ) | ||
|  |             t.table.ScriptList.mapFeatures(featureMap) | ||
|  |             usedIndices = featureMap.s | ||
|  | 
 | ||
|  |             # Remove unused features | ||
|  |             t.table.FeatureList.FeatureRecord = [ | ||
|  |                 f | ||
|  |                 for i, f in enumerate(t.table.FeatureList.FeatureRecord) | ||
|  |                 if i in usedIndices | ||
|  |             ] | ||
|  | 
 | ||
|  |             # Map back to indices. | ||
|  |             featureMap = NonhashableDict(t.table.FeatureList.FeatureRecord) | ||
|  |             t.table.ScriptList.mapFeatures(featureMap) | ||
|  | 
 | ||
|  |             t.table.FeatureList.FeatureCount = len(t.table.FeatureList.FeatureRecord) | ||
|  | 
 | ||
|  |         if t.table.LookupList: | ||
|  |             # Collect unregistered (new) lookups. | ||
|  |             lookupMap = GregariousIdentityDict(t.table.LookupList.Lookup) | ||
|  |             t.table.FeatureList.mapLookups(lookupMap) | ||
|  |             t.table.LookupList.mapLookups(lookupMap) | ||
|  | 
 | ||
|  |             # Record used lookups. | ||
|  |             lookupMap = AttendanceRecordingIdentityDict(t.table.LookupList.Lookup) | ||
|  |             t.table.FeatureList.mapLookups(lookupMap) | ||
|  |             t.table.LookupList.mapLookups(lookupMap) | ||
|  |             usedIndices = lookupMap.s | ||
|  | 
 | ||
|  |             # Remove unused lookups | ||
|  |             t.table.LookupList.Lookup = [ | ||
|  |                 l for i, l in enumerate(t.table.LookupList.Lookup) if i in usedIndices | ||
|  |             ] | ||
|  | 
 | ||
|  |             # Map back to indices. | ||
|  |             lookupMap = NonhashableDict(t.table.LookupList.Lookup) | ||
|  |             t.table.FeatureList.mapLookups(lookupMap) | ||
|  |             t.table.LookupList.mapLookups(lookupMap) | ||
|  | 
 | ||
|  |             t.table.LookupList.LookupCount = len(t.table.LookupList.Lookup) | ||
|  | 
 | ||
|  |             if GDEF and GDEF.table.Version >= 0x00010002: | ||
|  |                 markFilteringSetMap = NonhashableDict( | ||
|  |                     GDEF.table.MarkGlyphSetsDef.Coverage | ||
|  |                 ) | ||
|  |                 t.table.LookupList.mapMarkFilteringSets(markFilteringSetMap) | ||
|  | 
 | ||
|  |     # TODO FeatureParams nameIDs |