123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- # 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
|