xref: /aosp_15_r20/external/fonttools/Lib/fontTools/varLib/merger.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughes"""
2*e1fe3e4aSElliott HughesMerge OpenType Layout tables (GDEF / GPOS / GSUB).
3*e1fe3e4aSElliott Hughes"""
4*e1fe3e4aSElliott Hughes
5*e1fe3e4aSElliott Hughesimport os
6*e1fe3e4aSElliott Hughesimport copy
7*e1fe3e4aSElliott Hughesimport enum
8*e1fe3e4aSElliott Hughesfrom operator import ior
9*e1fe3e4aSElliott Hughesimport logging
10*e1fe3e4aSElliott Hughesfrom fontTools.colorLib.builder import MAX_PAINT_COLR_LAYER_COUNT, LayerReuseCache
11*e1fe3e4aSElliott Hughesfrom fontTools.misc import classifyTools
12*e1fe3e4aSElliott Hughesfrom fontTools.misc.roundTools import otRound
13*e1fe3e4aSElliott Hughesfrom fontTools.misc.treeTools import build_n_ary_tree
14*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables import otTables as ot
15*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables import otBase as otBase
16*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables.otConverters import BaseFixedValue
17*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables.otTraverse import dfs_base_table
18*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables.DefaultTable import DefaultTable
19*e1fe3e4aSElliott Hughesfrom fontTools.varLib import builder, models, varStore
20*e1fe3e4aSElliott Hughesfrom fontTools.varLib.models import nonNone, allNone, allEqual, allEqualTo, subList
21*e1fe3e4aSElliott Hughesfrom fontTools.varLib.varStore import VarStoreInstancer
22*e1fe3e4aSElliott Hughesfrom functools import reduce
23*e1fe3e4aSElliott Hughesfrom fontTools.otlLib.builder import buildSinglePos
24*e1fe3e4aSElliott Hughesfrom fontTools.otlLib.optimize.gpos import (
25*e1fe3e4aSElliott Hughes    _compression_level_from_env,
26*e1fe3e4aSElliott Hughes    compact_pair_pos,
27*e1fe3e4aSElliott Hughes)
28*e1fe3e4aSElliott Hughes
29*e1fe3e4aSElliott Hugheslog = logging.getLogger("fontTools.varLib.merger")
30*e1fe3e4aSElliott Hughes
31*e1fe3e4aSElliott Hughesfrom .errors import (
32*e1fe3e4aSElliott Hughes    ShouldBeConstant,
33*e1fe3e4aSElliott Hughes    FoundANone,
34*e1fe3e4aSElliott Hughes    MismatchedTypes,
35*e1fe3e4aSElliott Hughes    NotANone,
36*e1fe3e4aSElliott Hughes    LengthsDiffer,
37*e1fe3e4aSElliott Hughes    KeysDiffer,
38*e1fe3e4aSElliott Hughes    InconsistentGlyphOrder,
39*e1fe3e4aSElliott Hughes    InconsistentExtensions,
40*e1fe3e4aSElliott Hughes    InconsistentFormats,
41*e1fe3e4aSElliott Hughes    UnsupportedFormat,
42*e1fe3e4aSElliott Hughes    VarLibMergeError,
43*e1fe3e4aSElliott Hughes)
44*e1fe3e4aSElliott Hughes
45*e1fe3e4aSElliott Hughes
46*e1fe3e4aSElliott Hughesclass Merger(object):
47*e1fe3e4aSElliott Hughes    def __init__(self, font=None):
48*e1fe3e4aSElliott Hughes        self.font = font
49*e1fe3e4aSElliott Hughes        # mergeTables populates this from the parent's master ttfs
50*e1fe3e4aSElliott Hughes        self.ttfs = None
51*e1fe3e4aSElliott Hughes
52*e1fe3e4aSElliott Hughes    @classmethod
53*e1fe3e4aSElliott Hughes    def merger(celf, clazzes, attrs=(None,)):
54*e1fe3e4aSElliott Hughes        assert celf != Merger, "Subclass Merger instead."
55*e1fe3e4aSElliott Hughes        if "mergers" not in celf.__dict__:
56*e1fe3e4aSElliott Hughes            celf.mergers = {}
57*e1fe3e4aSElliott Hughes        if type(clazzes) in (type, enum.EnumMeta):
58*e1fe3e4aSElliott Hughes            clazzes = (clazzes,)
59*e1fe3e4aSElliott Hughes        if type(attrs) == str:
60*e1fe3e4aSElliott Hughes            attrs = (attrs,)
61*e1fe3e4aSElliott Hughes
62*e1fe3e4aSElliott Hughes        def wrapper(method):
63*e1fe3e4aSElliott Hughes            assert method.__name__ == "merge"
64*e1fe3e4aSElliott Hughes            done = []
65*e1fe3e4aSElliott Hughes            for clazz in clazzes:
66*e1fe3e4aSElliott Hughes                if clazz in done:
67*e1fe3e4aSElliott Hughes                    continue  # Support multiple names of a clazz
68*e1fe3e4aSElliott Hughes                done.append(clazz)
69*e1fe3e4aSElliott Hughes                mergers = celf.mergers.setdefault(clazz, {})
70*e1fe3e4aSElliott Hughes                for attr in attrs:
71*e1fe3e4aSElliott Hughes                    assert attr not in mergers, (
72*e1fe3e4aSElliott Hughes                        "Oops, class '%s' has merge function for '%s' defined already."
73*e1fe3e4aSElliott Hughes                        % (clazz.__name__, attr)
74*e1fe3e4aSElliott Hughes                    )
75*e1fe3e4aSElliott Hughes                    mergers[attr] = method
76*e1fe3e4aSElliott Hughes            return None
77*e1fe3e4aSElliott Hughes
78*e1fe3e4aSElliott Hughes        return wrapper
79*e1fe3e4aSElliott Hughes
80*e1fe3e4aSElliott Hughes    @classmethod
81*e1fe3e4aSElliott Hughes    def mergersFor(celf, thing, _default={}):
82*e1fe3e4aSElliott Hughes        typ = type(thing)
83*e1fe3e4aSElliott Hughes
84*e1fe3e4aSElliott Hughes        for celf in celf.mro():
85*e1fe3e4aSElliott Hughes            mergers = getattr(celf, "mergers", None)
86*e1fe3e4aSElliott Hughes            if mergers is None:
87*e1fe3e4aSElliott Hughes                break
88*e1fe3e4aSElliott Hughes
89*e1fe3e4aSElliott Hughes            m = celf.mergers.get(typ, None)
90*e1fe3e4aSElliott Hughes            if m is not None:
91*e1fe3e4aSElliott Hughes                return m
92*e1fe3e4aSElliott Hughes
93*e1fe3e4aSElliott Hughes        return _default
94*e1fe3e4aSElliott Hughes
95*e1fe3e4aSElliott Hughes    def mergeObjects(self, out, lst, exclude=()):
96*e1fe3e4aSElliott Hughes        if hasattr(out, "ensureDecompiled"):
97*e1fe3e4aSElliott Hughes            out.ensureDecompiled(recurse=False)
98*e1fe3e4aSElliott Hughes        for item in lst:
99*e1fe3e4aSElliott Hughes            if hasattr(item, "ensureDecompiled"):
100*e1fe3e4aSElliott Hughes                item.ensureDecompiled(recurse=False)
101*e1fe3e4aSElliott Hughes        keys = sorted(vars(out).keys())
102*e1fe3e4aSElliott Hughes        if not all(keys == sorted(vars(v).keys()) for v in lst):
103*e1fe3e4aSElliott Hughes            raise KeysDiffer(
104*e1fe3e4aSElliott Hughes                self, expected=keys, got=[sorted(vars(v).keys()) for v in lst]
105*e1fe3e4aSElliott Hughes            )
106*e1fe3e4aSElliott Hughes        mergers = self.mergersFor(out)
107*e1fe3e4aSElliott Hughes        defaultMerger = mergers.get("*", self.__class__.mergeThings)
108*e1fe3e4aSElliott Hughes        try:
109*e1fe3e4aSElliott Hughes            for key in keys:
110*e1fe3e4aSElliott Hughes                if key in exclude:
111*e1fe3e4aSElliott Hughes                    continue
112*e1fe3e4aSElliott Hughes                value = getattr(out, key)
113*e1fe3e4aSElliott Hughes                values = [getattr(table, key) for table in lst]
114*e1fe3e4aSElliott Hughes                mergerFunc = mergers.get(key, defaultMerger)
115*e1fe3e4aSElliott Hughes                mergerFunc(self, value, values)
116*e1fe3e4aSElliott Hughes        except VarLibMergeError as e:
117*e1fe3e4aSElliott Hughes            e.stack.append("." + key)
118*e1fe3e4aSElliott Hughes            raise
119*e1fe3e4aSElliott Hughes
120*e1fe3e4aSElliott Hughes    def mergeLists(self, out, lst):
121*e1fe3e4aSElliott Hughes        if not allEqualTo(out, lst, len):
122*e1fe3e4aSElliott Hughes            raise LengthsDiffer(self, expected=len(out), got=[len(x) for x in lst])
123*e1fe3e4aSElliott Hughes        for i, (value, values) in enumerate(zip(out, zip(*lst))):
124*e1fe3e4aSElliott Hughes            try:
125*e1fe3e4aSElliott Hughes                self.mergeThings(value, values)
126*e1fe3e4aSElliott Hughes            except VarLibMergeError as e:
127*e1fe3e4aSElliott Hughes                e.stack.append("[%d]" % i)
128*e1fe3e4aSElliott Hughes                raise
129*e1fe3e4aSElliott Hughes
130*e1fe3e4aSElliott Hughes    def mergeThings(self, out, lst):
131*e1fe3e4aSElliott Hughes        if not allEqualTo(out, lst, type):
132*e1fe3e4aSElliott Hughes            raise MismatchedTypes(
133*e1fe3e4aSElliott Hughes                self, expected=type(out).__name__, got=[type(x).__name__ for x in lst]
134*e1fe3e4aSElliott Hughes            )
135*e1fe3e4aSElliott Hughes        mergerFunc = self.mergersFor(out).get(None, None)
136*e1fe3e4aSElliott Hughes        if mergerFunc is not None:
137*e1fe3e4aSElliott Hughes            mergerFunc(self, out, lst)
138*e1fe3e4aSElliott Hughes        elif isinstance(out, enum.Enum):
139*e1fe3e4aSElliott Hughes            # need to special-case Enums as have __dict__ but are not regular 'objects',
140*e1fe3e4aSElliott Hughes            # otherwise mergeObjects/mergeThings get trapped in a RecursionError
141*e1fe3e4aSElliott Hughes            if not allEqualTo(out, lst):
142*e1fe3e4aSElliott Hughes                raise ShouldBeConstant(self, expected=out, got=lst)
143*e1fe3e4aSElliott Hughes        elif hasattr(out, "__dict__"):
144*e1fe3e4aSElliott Hughes            self.mergeObjects(out, lst)
145*e1fe3e4aSElliott Hughes        elif isinstance(out, list):
146*e1fe3e4aSElliott Hughes            self.mergeLists(out, lst)
147*e1fe3e4aSElliott Hughes        else:
148*e1fe3e4aSElliott Hughes            if not allEqualTo(out, lst):
149*e1fe3e4aSElliott Hughes                raise ShouldBeConstant(self, expected=out, got=lst)
150*e1fe3e4aSElliott Hughes
151*e1fe3e4aSElliott Hughes    def mergeTables(self, font, master_ttfs, tableTags):
152*e1fe3e4aSElliott Hughes        for tag in tableTags:
153*e1fe3e4aSElliott Hughes            if tag not in font:
154*e1fe3e4aSElliott Hughes                continue
155*e1fe3e4aSElliott Hughes            try:
156*e1fe3e4aSElliott Hughes                self.ttfs = master_ttfs
157*e1fe3e4aSElliott Hughes                self.mergeThings(font[tag], [m.get(tag) for m in master_ttfs])
158*e1fe3e4aSElliott Hughes            except VarLibMergeError as e:
159*e1fe3e4aSElliott Hughes                e.stack.append(tag)
160*e1fe3e4aSElliott Hughes                raise
161*e1fe3e4aSElliott Hughes
162*e1fe3e4aSElliott Hughes
163*e1fe3e4aSElliott Hughes#
164*e1fe3e4aSElliott Hughes# Aligning merger
165*e1fe3e4aSElliott Hughes#
166*e1fe3e4aSElliott Hughesclass AligningMerger(Merger):
167*e1fe3e4aSElliott Hughes    pass
168*e1fe3e4aSElliott Hughes
169*e1fe3e4aSElliott Hughes
170*e1fe3e4aSElliott Hughes@AligningMerger.merger(ot.GDEF, "GlyphClassDef")
171*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
172*e1fe3e4aSElliott Hughes    if self is None:
173*e1fe3e4aSElliott Hughes        if not allNone(lst):
174*e1fe3e4aSElliott Hughes            raise NotANone(merger, expected=None, got=lst)
175*e1fe3e4aSElliott Hughes        return
176*e1fe3e4aSElliott Hughes
177*e1fe3e4aSElliott Hughes    lst = [l.classDefs for l in lst]
178*e1fe3e4aSElliott Hughes    self.classDefs = {}
179*e1fe3e4aSElliott Hughes    # We only care about the .classDefs
180*e1fe3e4aSElliott Hughes    self = self.classDefs
181*e1fe3e4aSElliott Hughes
182*e1fe3e4aSElliott Hughes    allKeys = set()
183*e1fe3e4aSElliott Hughes    allKeys.update(*[l.keys() for l in lst])
184*e1fe3e4aSElliott Hughes    for k in allKeys:
185*e1fe3e4aSElliott Hughes        allValues = nonNone(l.get(k) for l in lst)
186*e1fe3e4aSElliott Hughes        if not allEqual(allValues):
187*e1fe3e4aSElliott Hughes            raise ShouldBeConstant(
188*e1fe3e4aSElliott Hughes                merger, expected=allValues[0], got=lst, stack=["." + k]
189*e1fe3e4aSElliott Hughes            )
190*e1fe3e4aSElliott Hughes        if not allValues:
191*e1fe3e4aSElliott Hughes            self[k] = None
192*e1fe3e4aSElliott Hughes        else:
193*e1fe3e4aSElliott Hughes            self[k] = allValues[0]
194*e1fe3e4aSElliott Hughes
195*e1fe3e4aSElliott Hughes
196*e1fe3e4aSElliott Hughesdef _SinglePosUpgradeToFormat2(self):
197*e1fe3e4aSElliott Hughes    if self.Format == 2:
198*e1fe3e4aSElliott Hughes        return self
199*e1fe3e4aSElliott Hughes
200*e1fe3e4aSElliott Hughes    ret = ot.SinglePos()
201*e1fe3e4aSElliott Hughes    ret.Format = 2
202*e1fe3e4aSElliott Hughes    ret.Coverage = self.Coverage
203*e1fe3e4aSElliott Hughes    ret.ValueFormat = self.ValueFormat
204*e1fe3e4aSElliott Hughes    ret.Value = [self.Value for _ in ret.Coverage.glyphs]
205*e1fe3e4aSElliott Hughes    ret.ValueCount = len(ret.Value)
206*e1fe3e4aSElliott Hughes
207*e1fe3e4aSElliott Hughes    return ret
208*e1fe3e4aSElliott Hughes
209*e1fe3e4aSElliott Hughes
210*e1fe3e4aSElliott Hughesdef _merge_GlyphOrders(font, lst, values_lst=None, default=None):
211*e1fe3e4aSElliott Hughes    """Takes font and list of glyph lists (must be sorted by glyph id), and returns
212*e1fe3e4aSElliott Hughes    two things:
213*e1fe3e4aSElliott Hughes    - Combined glyph list,
214*e1fe3e4aSElliott Hughes    - If values_lst is None, return input glyph lists, but padded with None when a glyph
215*e1fe3e4aSElliott Hughes      was missing in a list.  Otherwise, return values_lst list-of-list, padded with None
216*e1fe3e4aSElliott Hughes      to match combined glyph lists.
217*e1fe3e4aSElliott Hughes    """
218*e1fe3e4aSElliott Hughes    if values_lst is None:
219*e1fe3e4aSElliott Hughes        dict_sets = [set(l) for l in lst]
220*e1fe3e4aSElliott Hughes    else:
221*e1fe3e4aSElliott Hughes        dict_sets = [{g: v for g, v in zip(l, vs)} for l, vs in zip(lst, values_lst)]
222*e1fe3e4aSElliott Hughes    combined = set()
223*e1fe3e4aSElliott Hughes    combined.update(*dict_sets)
224*e1fe3e4aSElliott Hughes
225*e1fe3e4aSElliott Hughes    sortKey = font.getReverseGlyphMap().__getitem__
226*e1fe3e4aSElliott Hughes    order = sorted(combined, key=sortKey)
227*e1fe3e4aSElliott Hughes    # Make sure all input glyphsets were in proper order
228*e1fe3e4aSElliott Hughes    if not all(sorted(vs, key=sortKey) == vs for vs in lst):
229*e1fe3e4aSElliott Hughes        raise InconsistentGlyphOrder()
230*e1fe3e4aSElliott Hughes    del combined
231*e1fe3e4aSElliott Hughes
232*e1fe3e4aSElliott Hughes    paddedValues = None
233*e1fe3e4aSElliott Hughes    if values_lst is None:
234*e1fe3e4aSElliott Hughes        padded = [
235*e1fe3e4aSElliott Hughes            [glyph if glyph in dict_set else default for glyph in order]
236*e1fe3e4aSElliott Hughes            for dict_set in dict_sets
237*e1fe3e4aSElliott Hughes        ]
238*e1fe3e4aSElliott Hughes    else:
239*e1fe3e4aSElliott Hughes        assert len(lst) == len(values_lst)
240*e1fe3e4aSElliott Hughes        padded = [
241*e1fe3e4aSElliott Hughes            [dict_set[glyph] if glyph in dict_set else default for glyph in order]
242*e1fe3e4aSElliott Hughes            for dict_set in dict_sets
243*e1fe3e4aSElliott Hughes        ]
244*e1fe3e4aSElliott Hughes    return order, padded
245*e1fe3e4aSElliott Hughes
246*e1fe3e4aSElliott Hughes
247*e1fe3e4aSElliott Hughes@AligningMerger.merger(otBase.ValueRecord)
248*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
249*e1fe3e4aSElliott Hughes    # Code below sometimes calls us with self being
250*e1fe3e4aSElliott Hughes    # a new object. Copy it from lst and recurse.
251*e1fe3e4aSElliott Hughes    self.__dict__ = lst[0].__dict__.copy()
252*e1fe3e4aSElliott Hughes    merger.mergeObjects(self, lst)
253*e1fe3e4aSElliott Hughes
254*e1fe3e4aSElliott Hughes
255*e1fe3e4aSElliott Hughes@AligningMerger.merger(ot.Anchor)
256*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
257*e1fe3e4aSElliott Hughes    # Code below sometimes calls us with self being
258*e1fe3e4aSElliott Hughes    # a new object. Copy it from lst and recurse.
259*e1fe3e4aSElliott Hughes    self.__dict__ = lst[0].__dict__.copy()
260*e1fe3e4aSElliott Hughes    merger.mergeObjects(self, lst)
261*e1fe3e4aSElliott Hughes
262*e1fe3e4aSElliott Hughes
263*e1fe3e4aSElliott Hughesdef _Lookup_SinglePos_get_effective_value(merger, subtables, glyph):
264*e1fe3e4aSElliott Hughes    for self in subtables:
265*e1fe3e4aSElliott Hughes        if (
266*e1fe3e4aSElliott Hughes            self is None
267*e1fe3e4aSElliott Hughes            or type(self) != ot.SinglePos
268*e1fe3e4aSElliott Hughes            or self.Coverage is None
269*e1fe3e4aSElliott Hughes            or glyph not in self.Coverage.glyphs
270*e1fe3e4aSElliott Hughes        ):
271*e1fe3e4aSElliott Hughes            continue
272*e1fe3e4aSElliott Hughes        if self.Format == 1:
273*e1fe3e4aSElliott Hughes            return self.Value
274*e1fe3e4aSElliott Hughes        elif self.Format == 2:
275*e1fe3e4aSElliott Hughes            return self.Value[self.Coverage.glyphs.index(glyph)]
276*e1fe3e4aSElliott Hughes        else:
277*e1fe3e4aSElliott Hughes            raise UnsupportedFormat(merger, subtable="single positioning lookup")
278*e1fe3e4aSElliott Hughes    return None
279*e1fe3e4aSElliott Hughes
280*e1fe3e4aSElliott Hughes
281*e1fe3e4aSElliott Hughesdef _Lookup_PairPos_get_effective_value_pair(
282*e1fe3e4aSElliott Hughes    merger, subtables, firstGlyph, secondGlyph
283*e1fe3e4aSElliott Hughes):
284*e1fe3e4aSElliott Hughes    for self in subtables:
285*e1fe3e4aSElliott Hughes        if (
286*e1fe3e4aSElliott Hughes            self is None
287*e1fe3e4aSElliott Hughes            or type(self) != ot.PairPos
288*e1fe3e4aSElliott Hughes            or self.Coverage is None
289*e1fe3e4aSElliott Hughes            or firstGlyph not in self.Coverage.glyphs
290*e1fe3e4aSElliott Hughes        ):
291*e1fe3e4aSElliott Hughes            continue
292*e1fe3e4aSElliott Hughes        if self.Format == 1:
293*e1fe3e4aSElliott Hughes            ps = self.PairSet[self.Coverage.glyphs.index(firstGlyph)]
294*e1fe3e4aSElliott Hughes            pvr = ps.PairValueRecord
295*e1fe3e4aSElliott Hughes            for rec in pvr:  # TODO Speed up
296*e1fe3e4aSElliott Hughes                if rec.SecondGlyph == secondGlyph:
297*e1fe3e4aSElliott Hughes                    return rec
298*e1fe3e4aSElliott Hughes            continue
299*e1fe3e4aSElliott Hughes        elif self.Format == 2:
300*e1fe3e4aSElliott Hughes            klass1 = self.ClassDef1.classDefs.get(firstGlyph, 0)
301*e1fe3e4aSElliott Hughes            klass2 = self.ClassDef2.classDefs.get(secondGlyph, 0)
302*e1fe3e4aSElliott Hughes            return self.Class1Record[klass1].Class2Record[klass2]
303*e1fe3e4aSElliott Hughes        else:
304*e1fe3e4aSElliott Hughes            raise UnsupportedFormat(merger, subtable="pair positioning lookup")
305*e1fe3e4aSElliott Hughes    return None
306*e1fe3e4aSElliott Hughes
307*e1fe3e4aSElliott Hughes
308*e1fe3e4aSElliott Hughes@AligningMerger.merger(ot.SinglePos)
309*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
310*e1fe3e4aSElliott Hughes    self.ValueFormat = valueFormat = reduce(int.__or__, [l.ValueFormat for l in lst], 0)
311*e1fe3e4aSElliott Hughes    if not (len(lst) == 1 or (valueFormat & ~0xF == 0)):
312*e1fe3e4aSElliott Hughes        raise UnsupportedFormat(merger, subtable="single positioning lookup")
313*e1fe3e4aSElliott Hughes
314*e1fe3e4aSElliott Hughes    # If all have same coverage table and all are format 1,
315*e1fe3e4aSElliott Hughes    coverageGlyphs = self.Coverage.glyphs
316*e1fe3e4aSElliott Hughes    if all(v.Format == 1 for v in lst) and all(
317*e1fe3e4aSElliott Hughes        coverageGlyphs == v.Coverage.glyphs for v in lst
318*e1fe3e4aSElliott Hughes    ):
319*e1fe3e4aSElliott Hughes        self.Value = otBase.ValueRecord(valueFormat, self.Value)
320*e1fe3e4aSElliott Hughes        if valueFormat != 0:
321*e1fe3e4aSElliott Hughes            # If v.Value is None, it means a kerning of 0; we want
322*e1fe3e4aSElliott Hughes            # it to participate in the model still.
323*e1fe3e4aSElliott Hughes            # https://github.com/fonttools/fonttools/issues/3111
324*e1fe3e4aSElliott Hughes            merger.mergeThings(
325*e1fe3e4aSElliott Hughes                self.Value,
326*e1fe3e4aSElliott Hughes                [v.Value if v.Value is not None else otBase.ValueRecord() for v in lst],
327*e1fe3e4aSElliott Hughes            )
328*e1fe3e4aSElliott Hughes        self.ValueFormat = self.Value.getFormat()
329*e1fe3e4aSElliott Hughes        return
330*e1fe3e4aSElliott Hughes
331*e1fe3e4aSElliott Hughes    # Upgrade everything to Format=2
332*e1fe3e4aSElliott Hughes    self.Format = 2
333*e1fe3e4aSElliott Hughes    lst = [_SinglePosUpgradeToFormat2(v) for v in lst]
334*e1fe3e4aSElliott Hughes
335*e1fe3e4aSElliott Hughes    # Align them
336*e1fe3e4aSElliott Hughes    glyphs, padded = _merge_GlyphOrders(
337*e1fe3e4aSElliott Hughes        merger.font, [v.Coverage.glyphs for v in lst], [v.Value for v in lst]
338*e1fe3e4aSElliott Hughes    )
339*e1fe3e4aSElliott Hughes
340*e1fe3e4aSElliott Hughes    self.Coverage.glyphs = glyphs
341*e1fe3e4aSElliott Hughes    self.Value = [otBase.ValueRecord(valueFormat) for _ in glyphs]
342*e1fe3e4aSElliott Hughes    self.ValueCount = len(self.Value)
343*e1fe3e4aSElliott Hughes
344*e1fe3e4aSElliott Hughes    for i, values in enumerate(padded):
345*e1fe3e4aSElliott Hughes        for j, glyph in enumerate(glyphs):
346*e1fe3e4aSElliott Hughes            if values[j] is not None:
347*e1fe3e4aSElliott Hughes                continue
348*e1fe3e4aSElliott Hughes            # Fill in value from other subtables
349*e1fe3e4aSElliott Hughes            # Note!!! This *might* result in behavior change if ValueFormat2-zeroedness
350*e1fe3e4aSElliott Hughes            # is different between used subtable and current subtable!
351*e1fe3e4aSElliott Hughes            # TODO(behdad) Check and warn if that happens?
352*e1fe3e4aSElliott Hughes            v = _Lookup_SinglePos_get_effective_value(
353*e1fe3e4aSElliott Hughes                merger, merger.lookup_subtables[i], glyph
354*e1fe3e4aSElliott Hughes            )
355*e1fe3e4aSElliott Hughes            if v is None:
356*e1fe3e4aSElliott Hughes                v = otBase.ValueRecord(valueFormat)
357*e1fe3e4aSElliott Hughes            values[j] = v
358*e1fe3e4aSElliott Hughes
359*e1fe3e4aSElliott Hughes    merger.mergeLists(self.Value, padded)
360*e1fe3e4aSElliott Hughes
361*e1fe3e4aSElliott Hughes    # Merge everything else; though, there shouldn't be anything else. :)
362*e1fe3e4aSElliott Hughes    merger.mergeObjects(
363*e1fe3e4aSElliott Hughes        self, lst, exclude=("Format", "Coverage", "Value", "ValueCount", "ValueFormat")
364*e1fe3e4aSElliott Hughes    )
365*e1fe3e4aSElliott Hughes    self.ValueFormat = reduce(
366*e1fe3e4aSElliott Hughes        int.__or__, [v.getEffectiveFormat() for v in self.Value], 0
367*e1fe3e4aSElliott Hughes    )
368*e1fe3e4aSElliott Hughes
369*e1fe3e4aSElliott Hughes
370*e1fe3e4aSElliott Hughes@AligningMerger.merger(ot.PairSet)
371*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
372*e1fe3e4aSElliott Hughes    # Align them
373*e1fe3e4aSElliott Hughes    glyphs, padded = _merge_GlyphOrders(
374*e1fe3e4aSElliott Hughes        merger.font,
375*e1fe3e4aSElliott Hughes        [[v.SecondGlyph for v in vs.PairValueRecord] for vs in lst],
376*e1fe3e4aSElliott Hughes        [vs.PairValueRecord for vs in lst],
377*e1fe3e4aSElliott Hughes    )
378*e1fe3e4aSElliott Hughes
379*e1fe3e4aSElliott Hughes    self.PairValueRecord = pvrs = []
380*e1fe3e4aSElliott Hughes    for glyph in glyphs:
381*e1fe3e4aSElliott Hughes        pvr = ot.PairValueRecord()
382*e1fe3e4aSElliott Hughes        pvr.SecondGlyph = glyph
383*e1fe3e4aSElliott Hughes        pvr.Value1 = (
384*e1fe3e4aSElliott Hughes            otBase.ValueRecord(merger.valueFormat1) if merger.valueFormat1 else None
385*e1fe3e4aSElliott Hughes        )
386*e1fe3e4aSElliott Hughes        pvr.Value2 = (
387*e1fe3e4aSElliott Hughes            otBase.ValueRecord(merger.valueFormat2) if merger.valueFormat2 else None
388*e1fe3e4aSElliott Hughes        )
389*e1fe3e4aSElliott Hughes        pvrs.append(pvr)
390*e1fe3e4aSElliott Hughes    self.PairValueCount = len(self.PairValueRecord)
391*e1fe3e4aSElliott Hughes
392*e1fe3e4aSElliott Hughes    for i, values in enumerate(padded):
393*e1fe3e4aSElliott Hughes        for j, glyph in enumerate(glyphs):
394*e1fe3e4aSElliott Hughes            # Fill in value from other subtables
395*e1fe3e4aSElliott Hughes            v = ot.PairValueRecord()
396*e1fe3e4aSElliott Hughes            v.SecondGlyph = glyph
397*e1fe3e4aSElliott Hughes            if values[j] is not None:
398*e1fe3e4aSElliott Hughes                vpair = values[j]
399*e1fe3e4aSElliott Hughes            else:
400*e1fe3e4aSElliott Hughes                vpair = _Lookup_PairPos_get_effective_value_pair(
401*e1fe3e4aSElliott Hughes                    merger, merger.lookup_subtables[i], self._firstGlyph, glyph
402*e1fe3e4aSElliott Hughes                )
403*e1fe3e4aSElliott Hughes            if vpair is None:
404*e1fe3e4aSElliott Hughes                v1, v2 = None, None
405*e1fe3e4aSElliott Hughes            else:
406*e1fe3e4aSElliott Hughes                v1 = getattr(vpair, "Value1", None)
407*e1fe3e4aSElliott Hughes                v2 = getattr(vpair, "Value2", None)
408*e1fe3e4aSElliott Hughes            v.Value1 = (
409*e1fe3e4aSElliott Hughes                otBase.ValueRecord(merger.valueFormat1, src=v1)
410*e1fe3e4aSElliott Hughes                if merger.valueFormat1
411*e1fe3e4aSElliott Hughes                else None
412*e1fe3e4aSElliott Hughes            )
413*e1fe3e4aSElliott Hughes            v.Value2 = (
414*e1fe3e4aSElliott Hughes                otBase.ValueRecord(merger.valueFormat2, src=v2)
415*e1fe3e4aSElliott Hughes                if merger.valueFormat2
416*e1fe3e4aSElliott Hughes                else None
417*e1fe3e4aSElliott Hughes            )
418*e1fe3e4aSElliott Hughes            values[j] = v
419*e1fe3e4aSElliott Hughes    del self._firstGlyph
420*e1fe3e4aSElliott Hughes
421*e1fe3e4aSElliott Hughes    merger.mergeLists(self.PairValueRecord, padded)
422*e1fe3e4aSElliott Hughes
423*e1fe3e4aSElliott Hughes
424*e1fe3e4aSElliott Hughesdef _PairPosFormat1_merge(self, lst, merger):
425*e1fe3e4aSElliott Hughes    assert allEqual(
426*e1fe3e4aSElliott Hughes        [l.ValueFormat2 == 0 for l in lst if l.PairSet]
427*e1fe3e4aSElliott Hughes    ), "Report bug against fonttools."
428*e1fe3e4aSElliott Hughes
429*e1fe3e4aSElliott Hughes    # Merge everything else; makes sure Format is the same.
430*e1fe3e4aSElliott Hughes    merger.mergeObjects(
431*e1fe3e4aSElliott Hughes        self,
432*e1fe3e4aSElliott Hughes        lst,
433*e1fe3e4aSElliott Hughes        exclude=("Coverage", "PairSet", "PairSetCount", "ValueFormat1", "ValueFormat2"),
434*e1fe3e4aSElliott Hughes    )
435*e1fe3e4aSElliott Hughes
436*e1fe3e4aSElliott Hughes    empty = ot.PairSet()
437*e1fe3e4aSElliott Hughes    empty.PairValueRecord = []
438*e1fe3e4aSElliott Hughes    empty.PairValueCount = 0
439*e1fe3e4aSElliott Hughes
440*e1fe3e4aSElliott Hughes    # Align them
441*e1fe3e4aSElliott Hughes    glyphs, padded = _merge_GlyphOrders(
442*e1fe3e4aSElliott Hughes        merger.font,
443*e1fe3e4aSElliott Hughes        [v.Coverage.glyphs for v in lst],
444*e1fe3e4aSElliott Hughes        [v.PairSet for v in lst],
445*e1fe3e4aSElliott Hughes        default=empty,
446*e1fe3e4aSElliott Hughes    )
447*e1fe3e4aSElliott Hughes
448*e1fe3e4aSElliott Hughes    self.Coverage.glyphs = glyphs
449*e1fe3e4aSElliott Hughes    self.PairSet = [ot.PairSet() for _ in glyphs]
450*e1fe3e4aSElliott Hughes    self.PairSetCount = len(self.PairSet)
451*e1fe3e4aSElliott Hughes    for glyph, ps in zip(glyphs, self.PairSet):
452*e1fe3e4aSElliott Hughes        ps._firstGlyph = glyph
453*e1fe3e4aSElliott Hughes
454*e1fe3e4aSElliott Hughes    merger.mergeLists(self.PairSet, padded)
455*e1fe3e4aSElliott Hughes
456*e1fe3e4aSElliott Hughes
457*e1fe3e4aSElliott Hughesdef _ClassDef_invert(self, allGlyphs=None):
458*e1fe3e4aSElliott Hughes    if isinstance(self, dict):
459*e1fe3e4aSElliott Hughes        classDefs = self
460*e1fe3e4aSElliott Hughes    else:
461*e1fe3e4aSElliott Hughes        classDefs = self.classDefs if self and self.classDefs else {}
462*e1fe3e4aSElliott Hughes    m = max(classDefs.values()) if classDefs else 0
463*e1fe3e4aSElliott Hughes
464*e1fe3e4aSElliott Hughes    ret = []
465*e1fe3e4aSElliott Hughes    for _ in range(m + 1):
466*e1fe3e4aSElliott Hughes        ret.append(set())
467*e1fe3e4aSElliott Hughes
468*e1fe3e4aSElliott Hughes    for k, v in classDefs.items():
469*e1fe3e4aSElliott Hughes        ret[v].add(k)
470*e1fe3e4aSElliott Hughes
471*e1fe3e4aSElliott Hughes    # Class-0 is special.  It's "everything else".
472*e1fe3e4aSElliott Hughes    if allGlyphs is None:
473*e1fe3e4aSElliott Hughes        ret[0] = None
474*e1fe3e4aSElliott Hughes    else:
475*e1fe3e4aSElliott Hughes        # Limit all classes to glyphs in allGlyphs.
476*e1fe3e4aSElliott Hughes        # Collect anything without a non-zero class into class=zero.
477*e1fe3e4aSElliott Hughes        ret[0] = class0 = set(allGlyphs)
478*e1fe3e4aSElliott Hughes        for s in ret[1:]:
479*e1fe3e4aSElliott Hughes            s.intersection_update(class0)
480*e1fe3e4aSElliott Hughes            class0.difference_update(s)
481*e1fe3e4aSElliott Hughes
482*e1fe3e4aSElliott Hughes    return ret
483*e1fe3e4aSElliott Hughes
484*e1fe3e4aSElliott Hughes
485*e1fe3e4aSElliott Hughesdef _ClassDef_merge_classify(lst, allGlyphses=None):
486*e1fe3e4aSElliott Hughes    self = ot.ClassDef()
487*e1fe3e4aSElliott Hughes    self.classDefs = classDefs = {}
488*e1fe3e4aSElliott Hughes    allGlyphsesWasNone = allGlyphses is None
489*e1fe3e4aSElliott Hughes    if allGlyphsesWasNone:
490*e1fe3e4aSElliott Hughes        allGlyphses = [None] * len(lst)
491*e1fe3e4aSElliott Hughes
492*e1fe3e4aSElliott Hughes    classifier = classifyTools.Classifier()
493*e1fe3e4aSElliott Hughes    for classDef, allGlyphs in zip(lst, allGlyphses):
494*e1fe3e4aSElliott Hughes        sets = _ClassDef_invert(classDef, allGlyphs)
495*e1fe3e4aSElliott Hughes        if allGlyphs is None:
496*e1fe3e4aSElliott Hughes            sets = sets[1:]
497*e1fe3e4aSElliott Hughes        classifier.update(sets)
498*e1fe3e4aSElliott Hughes    classes = classifier.getClasses()
499*e1fe3e4aSElliott Hughes
500*e1fe3e4aSElliott Hughes    if allGlyphsesWasNone:
501*e1fe3e4aSElliott Hughes        classes.insert(0, set())
502*e1fe3e4aSElliott Hughes
503*e1fe3e4aSElliott Hughes    for i, classSet in enumerate(classes):
504*e1fe3e4aSElliott Hughes        if i == 0:
505*e1fe3e4aSElliott Hughes            continue
506*e1fe3e4aSElliott Hughes        for g in classSet:
507*e1fe3e4aSElliott Hughes            classDefs[g] = i
508*e1fe3e4aSElliott Hughes
509*e1fe3e4aSElliott Hughes    return self, classes
510*e1fe3e4aSElliott Hughes
511*e1fe3e4aSElliott Hughes
512*e1fe3e4aSElliott Hughesdef _PairPosFormat2_align_matrices(self, lst, font, transparent=False):
513*e1fe3e4aSElliott Hughes    matrices = [l.Class1Record for l in lst]
514*e1fe3e4aSElliott Hughes
515*e1fe3e4aSElliott Hughes    # Align first classes
516*e1fe3e4aSElliott Hughes    self.ClassDef1, classes = _ClassDef_merge_classify(
517*e1fe3e4aSElliott Hughes        [l.ClassDef1 for l in lst], [l.Coverage.glyphs for l in lst]
518*e1fe3e4aSElliott Hughes    )
519*e1fe3e4aSElliott Hughes    self.Class1Count = len(classes)
520*e1fe3e4aSElliott Hughes    new_matrices = []
521*e1fe3e4aSElliott Hughes    for l, matrix in zip(lst, matrices):
522*e1fe3e4aSElliott Hughes        nullRow = None
523*e1fe3e4aSElliott Hughes        coverage = set(l.Coverage.glyphs)
524*e1fe3e4aSElliott Hughes        classDef1 = l.ClassDef1.classDefs
525*e1fe3e4aSElliott Hughes        class1Records = []
526*e1fe3e4aSElliott Hughes        for classSet in classes:
527*e1fe3e4aSElliott Hughes            exemplarGlyph = next(iter(classSet))
528*e1fe3e4aSElliott Hughes            if exemplarGlyph not in coverage:
529*e1fe3e4aSElliott Hughes                # Follow-up to e6125b353e1f54a0280ded5434b8e40d042de69f,
530*e1fe3e4aSElliott Hughes                # Fixes https://github.com/googlei18n/fontmake/issues/470
531*e1fe3e4aSElliott Hughes                # Again, revert 8d441779e5afc664960d848f62c7acdbfc71d7b9
532*e1fe3e4aSElliott Hughes                # when merger becomes selfless.
533*e1fe3e4aSElliott Hughes                nullRow = None
534*e1fe3e4aSElliott Hughes                if nullRow is None:
535*e1fe3e4aSElliott Hughes                    nullRow = ot.Class1Record()
536*e1fe3e4aSElliott Hughes                    class2records = nullRow.Class2Record = []
537*e1fe3e4aSElliott Hughes                    # TODO: When merger becomes selfless, revert e6125b353e1f54a0280ded5434b8e40d042de69f
538*e1fe3e4aSElliott Hughes                    for _ in range(l.Class2Count):
539*e1fe3e4aSElliott Hughes                        if transparent:
540*e1fe3e4aSElliott Hughes                            rec2 = None
541*e1fe3e4aSElliott Hughes                        else:
542*e1fe3e4aSElliott Hughes                            rec2 = ot.Class2Record()
543*e1fe3e4aSElliott Hughes                            rec2.Value1 = (
544*e1fe3e4aSElliott Hughes                                otBase.ValueRecord(self.ValueFormat1)
545*e1fe3e4aSElliott Hughes                                if self.ValueFormat1
546*e1fe3e4aSElliott Hughes                                else None
547*e1fe3e4aSElliott Hughes                            )
548*e1fe3e4aSElliott Hughes                            rec2.Value2 = (
549*e1fe3e4aSElliott Hughes                                otBase.ValueRecord(self.ValueFormat2)
550*e1fe3e4aSElliott Hughes                                if self.ValueFormat2
551*e1fe3e4aSElliott Hughes                                else None
552*e1fe3e4aSElliott Hughes                            )
553*e1fe3e4aSElliott Hughes                        class2records.append(rec2)
554*e1fe3e4aSElliott Hughes                rec1 = nullRow
555*e1fe3e4aSElliott Hughes            else:
556*e1fe3e4aSElliott Hughes                klass = classDef1.get(exemplarGlyph, 0)
557*e1fe3e4aSElliott Hughes                rec1 = matrix[klass]  # TODO handle out-of-range?
558*e1fe3e4aSElliott Hughes            class1Records.append(rec1)
559*e1fe3e4aSElliott Hughes        new_matrices.append(class1Records)
560*e1fe3e4aSElliott Hughes    matrices = new_matrices
561*e1fe3e4aSElliott Hughes    del new_matrices
562*e1fe3e4aSElliott Hughes
563*e1fe3e4aSElliott Hughes    # Align second classes
564*e1fe3e4aSElliott Hughes    self.ClassDef2, classes = _ClassDef_merge_classify([l.ClassDef2 for l in lst])
565*e1fe3e4aSElliott Hughes    self.Class2Count = len(classes)
566*e1fe3e4aSElliott Hughes    new_matrices = []
567*e1fe3e4aSElliott Hughes    for l, matrix in zip(lst, matrices):
568*e1fe3e4aSElliott Hughes        classDef2 = l.ClassDef2.classDefs
569*e1fe3e4aSElliott Hughes        class1Records = []
570*e1fe3e4aSElliott Hughes        for rec1old in matrix:
571*e1fe3e4aSElliott Hughes            oldClass2Records = rec1old.Class2Record
572*e1fe3e4aSElliott Hughes            rec1new = ot.Class1Record()
573*e1fe3e4aSElliott Hughes            class2Records = rec1new.Class2Record = []
574*e1fe3e4aSElliott Hughes            for classSet in classes:
575*e1fe3e4aSElliott Hughes                if not classSet:  # class=0
576*e1fe3e4aSElliott Hughes                    rec2 = oldClass2Records[0]
577*e1fe3e4aSElliott Hughes                else:
578*e1fe3e4aSElliott Hughes                    exemplarGlyph = next(iter(classSet))
579*e1fe3e4aSElliott Hughes                    klass = classDef2.get(exemplarGlyph, 0)
580*e1fe3e4aSElliott Hughes                    rec2 = oldClass2Records[klass]
581*e1fe3e4aSElliott Hughes                class2Records.append(copy.deepcopy(rec2))
582*e1fe3e4aSElliott Hughes            class1Records.append(rec1new)
583*e1fe3e4aSElliott Hughes        new_matrices.append(class1Records)
584*e1fe3e4aSElliott Hughes    matrices = new_matrices
585*e1fe3e4aSElliott Hughes    del new_matrices
586*e1fe3e4aSElliott Hughes
587*e1fe3e4aSElliott Hughes    return matrices
588*e1fe3e4aSElliott Hughes
589*e1fe3e4aSElliott Hughes
590*e1fe3e4aSElliott Hughesdef _PairPosFormat2_merge(self, lst, merger):
591*e1fe3e4aSElliott Hughes    assert allEqual(
592*e1fe3e4aSElliott Hughes        [l.ValueFormat2 == 0 for l in lst if l.Class1Record]
593*e1fe3e4aSElliott Hughes    ), "Report bug against fonttools."
594*e1fe3e4aSElliott Hughes
595*e1fe3e4aSElliott Hughes    merger.mergeObjects(
596*e1fe3e4aSElliott Hughes        self,
597*e1fe3e4aSElliott Hughes        lst,
598*e1fe3e4aSElliott Hughes        exclude=(
599*e1fe3e4aSElliott Hughes            "Coverage",
600*e1fe3e4aSElliott Hughes            "ClassDef1",
601*e1fe3e4aSElliott Hughes            "Class1Count",
602*e1fe3e4aSElliott Hughes            "ClassDef2",
603*e1fe3e4aSElliott Hughes            "Class2Count",
604*e1fe3e4aSElliott Hughes            "Class1Record",
605*e1fe3e4aSElliott Hughes            "ValueFormat1",
606*e1fe3e4aSElliott Hughes            "ValueFormat2",
607*e1fe3e4aSElliott Hughes        ),
608*e1fe3e4aSElliott Hughes    )
609*e1fe3e4aSElliott Hughes
610*e1fe3e4aSElliott Hughes    # Align coverages
611*e1fe3e4aSElliott Hughes    glyphs, _ = _merge_GlyphOrders(merger.font, [v.Coverage.glyphs for v in lst])
612*e1fe3e4aSElliott Hughes    self.Coverage.glyphs = glyphs
613*e1fe3e4aSElliott Hughes
614*e1fe3e4aSElliott Hughes    # Currently, if the coverage of PairPosFormat2 subtables are different,
615*e1fe3e4aSElliott Hughes    # we do NOT bother walking down the subtable list when filling in new
616*e1fe3e4aSElliott Hughes    # rows for alignment.  As such, this is only correct if current subtable
617*e1fe3e4aSElliott Hughes    # is the last subtable in the lookup.  Ensure that.
618*e1fe3e4aSElliott Hughes    #
619*e1fe3e4aSElliott Hughes    # Note that our canonicalization process merges trailing PairPosFormat2's,
620*e1fe3e4aSElliott Hughes    # so in reality this is rare.
621*e1fe3e4aSElliott Hughes    for l, subtables in zip(lst, merger.lookup_subtables):
622*e1fe3e4aSElliott Hughes        if l.Coverage.glyphs != glyphs:
623*e1fe3e4aSElliott Hughes            assert l == subtables[-1]
624*e1fe3e4aSElliott Hughes
625*e1fe3e4aSElliott Hughes    matrices = _PairPosFormat2_align_matrices(self, lst, merger.font)
626*e1fe3e4aSElliott Hughes
627*e1fe3e4aSElliott Hughes    self.Class1Record = list(matrices[0])  # TODO move merger to be selfless
628*e1fe3e4aSElliott Hughes    merger.mergeLists(self.Class1Record, matrices)
629*e1fe3e4aSElliott Hughes
630*e1fe3e4aSElliott Hughes
631*e1fe3e4aSElliott Hughes@AligningMerger.merger(ot.PairPos)
632*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
633*e1fe3e4aSElliott Hughes    merger.valueFormat1 = self.ValueFormat1 = reduce(
634*e1fe3e4aSElliott Hughes        int.__or__, [l.ValueFormat1 for l in lst], 0
635*e1fe3e4aSElliott Hughes    )
636*e1fe3e4aSElliott Hughes    merger.valueFormat2 = self.ValueFormat2 = reduce(
637*e1fe3e4aSElliott Hughes        int.__or__, [l.ValueFormat2 for l in lst], 0
638*e1fe3e4aSElliott Hughes    )
639*e1fe3e4aSElliott Hughes
640*e1fe3e4aSElliott Hughes    if self.Format == 1:
641*e1fe3e4aSElliott Hughes        _PairPosFormat1_merge(self, lst, merger)
642*e1fe3e4aSElliott Hughes    elif self.Format == 2:
643*e1fe3e4aSElliott Hughes        _PairPosFormat2_merge(self, lst, merger)
644*e1fe3e4aSElliott Hughes    else:
645*e1fe3e4aSElliott Hughes        raise UnsupportedFormat(merger, subtable="pair positioning lookup")
646*e1fe3e4aSElliott Hughes
647*e1fe3e4aSElliott Hughes    del merger.valueFormat1, merger.valueFormat2
648*e1fe3e4aSElliott Hughes
649*e1fe3e4aSElliott Hughes    # Now examine the list of value records, and update to the union of format values,
650*e1fe3e4aSElliott Hughes    # as merge might have created new values.
651*e1fe3e4aSElliott Hughes    vf1 = 0
652*e1fe3e4aSElliott Hughes    vf2 = 0
653*e1fe3e4aSElliott Hughes    if self.Format == 1:
654*e1fe3e4aSElliott Hughes        for pairSet in self.PairSet:
655*e1fe3e4aSElliott Hughes            for pairValueRecord in pairSet.PairValueRecord:
656*e1fe3e4aSElliott Hughes                pv1 = getattr(pairValueRecord, "Value1", None)
657*e1fe3e4aSElliott Hughes                if pv1 is not None:
658*e1fe3e4aSElliott Hughes                    vf1 |= pv1.getFormat()
659*e1fe3e4aSElliott Hughes                pv2 = getattr(pairValueRecord, "Value2", None)
660*e1fe3e4aSElliott Hughes                if pv2 is not None:
661*e1fe3e4aSElliott Hughes                    vf2 |= pv2.getFormat()
662*e1fe3e4aSElliott Hughes    elif self.Format == 2:
663*e1fe3e4aSElliott Hughes        for class1Record in self.Class1Record:
664*e1fe3e4aSElliott Hughes            for class2Record in class1Record.Class2Record:
665*e1fe3e4aSElliott Hughes                pv1 = getattr(class2Record, "Value1", None)
666*e1fe3e4aSElliott Hughes                if pv1 is not None:
667*e1fe3e4aSElliott Hughes                    vf1 |= pv1.getFormat()
668*e1fe3e4aSElliott Hughes                pv2 = getattr(class2Record, "Value2", None)
669*e1fe3e4aSElliott Hughes                if pv2 is not None:
670*e1fe3e4aSElliott Hughes                    vf2 |= pv2.getFormat()
671*e1fe3e4aSElliott Hughes    self.ValueFormat1 = vf1
672*e1fe3e4aSElliott Hughes    self.ValueFormat2 = vf2
673*e1fe3e4aSElliott Hughes
674*e1fe3e4aSElliott Hughes
675*e1fe3e4aSElliott Hughesdef _MarkBasePosFormat1_merge(self, lst, merger, Mark="Mark", Base="Base"):
676*e1fe3e4aSElliott Hughes    self.ClassCount = max(l.ClassCount for l in lst)
677*e1fe3e4aSElliott Hughes
678*e1fe3e4aSElliott Hughes    MarkCoverageGlyphs, MarkRecords = _merge_GlyphOrders(
679*e1fe3e4aSElliott Hughes        merger.font,
680*e1fe3e4aSElliott Hughes        [getattr(l, Mark + "Coverage").glyphs for l in lst],
681*e1fe3e4aSElliott Hughes        [getattr(l, Mark + "Array").MarkRecord for l in lst],
682*e1fe3e4aSElliott Hughes    )
683*e1fe3e4aSElliott Hughes    getattr(self, Mark + "Coverage").glyphs = MarkCoverageGlyphs
684*e1fe3e4aSElliott Hughes
685*e1fe3e4aSElliott Hughes    BaseCoverageGlyphs, BaseRecords = _merge_GlyphOrders(
686*e1fe3e4aSElliott Hughes        merger.font,
687*e1fe3e4aSElliott Hughes        [getattr(l, Base + "Coverage").glyphs for l in lst],
688*e1fe3e4aSElliott Hughes        [getattr(getattr(l, Base + "Array"), Base + "Record") for l in lst],
689*e1fe3e4aSElliott Hughes    )
690*e1fe3e4aSElliott Hughes    getattr(self, Base + "Coverage").glyphs = BaseCoverageGlyphs
691*e1fe3e4aSElliott Hughes
692*e1fe3e4aSElliott Hughes    # MarkArray
693*e1fe3e4aSElliott Hughes    records = []
694*e1fe3e4aSElliott Hughes    for g, glyphRecords in zip(MarkCoverageGlyphs, zip(*MarkRecords)):
695*e1fe3e4aSElliott Hughes        allClasses = [r.Class for r in glyphRecords if r is not None]
696*e1fe3e4aSElliott Hughes
697*e1fe3e4aSElliott Hughes        # TODO Right now we require that all marks have same class in
698*e1fe3e4aSElliott Hughes        # all masters that cover them.  This is not required.
699*e1fe3e4aSElliott Hughes        #
700*e1fe3e4aSElliott Hughes        # We can relax that by just requiring that all marks that have
701*e1fe3e4aSElliott Hughes        # the same class in a master, have the same class in every other
702*e1fe3e4aSElliott Hughes        # master.  Indeed, if, say, a sparse master only covers one mark,
703*e1fe3e4aSElliott Hughes        # that mark probably will get class 0, which would possibly be
704*e1fe3e4aSElliott Hughes        # different from its class in other masters.
705*e1fe3e4aSElliott Hughes        #
706*e1fe3e4aSElliott Hughes        # We can even go further and reclassify marks to support any
707*e1fe3e4aSElliott Hughes        # input.  But, since, it's unlikely that two marks being both,
708*e1fe3e4aSElliott Hughes        # say, "top" in one master, and one being "top" and other being
709*e1fe3e4aSElliott Hughes        # "top-right" in another master, we shouldn't do that, as any
710*e1fe3e4aSElliott Hughes        # failures in that case will probably signify mistakes in the
711*e1fe3e4aSElliott Hughes        # input masters.
712*e1fe3e4aSElliott Hughes
713*e1fe3e4aSElliott Hughes        if not allEqual(allClasses):
714*e1fe3e4aSElliott Hughes            raise ShouldBeConstant(merger, expected=allClasses[0], got=allClasses)
715*e1fe3e4aSElliott Hughes        else:
716*e1fe3e4aSElliott Hughes            rec = ot.MarkRecord()
717*e1fe3e4aSElliott Hughes            rec.Class = allClasses[0]
718*e1fe3e4aSElliott Hughes            allAnchors = [None if r is None else r.MarkAnchor for r in glyphRecords]
719*e1fe3e4aSElliott Hughes            if allNone(allAnchors):
720*e1fe3e4aSElliott Hughes                anchor = None
721*e1fe3e4aSElliott Hughes            else:
722*e1fe3e4aSElliott Hughes                anchor = ot.Anchor()
723*e1fe3e4aSElliott Hughes                anchor.Format = 1
724*e1fe3e4aSElliott Hughes                merger.mergeThings(anchor, allAnchors)
725*e1fe3e4aSElliott Hughes            rec.MarkAnchor = anchor
726*e1fe3e4aSElliott Hughes        records.append(rec)
727*e1fe3e4aSElliott Hughes    array = ot.MarkArray()
728*e1fe3e4aSElliott Hughes    array.MarkRecord = records
729*e1fe3e4aSElliott Hughes    array.MarkCount = len(records)
730*e1fe3e4aSElliott Hughes    setattr(self, Mark + "Array", array)
731*e1fe3e4aSElliott Hughes
732*e1fe3e4aSElliott Hughes    # BaseArray
733*e1fe3e4aSElliott Hughes    records = []
734*e1fe3e4aSElliott Hughes    for g, glyphRecords in zip(BaseCoverageGlyphs, zip(*BaseRecords)):
735*e1fe3e4aSElliott Hughes        if allNone(glyphRecords):
736*e1fe3e4aSElliott Hughes            rec = None
737*e1fe3e4aSElliott Hughes        else:
738*e1fe3e4aSElliott Hughes            rec = getattr(ot, Base + "Record")()
739*e1fe3e4aSElliott Hughes            anchors = []
740*e1fe3e4aSElliott Hughes            setattr(rec, Base + "Anchor", anchors)
741*e1fe3e4aSElliott Hughes            glyphAnchors = [
742*e1fe3e4aSElliott Hughes                [] if r is None else getattr(r, Base + "Anchor") for r in glyphRecords
743*e1fe3e4aSElliott Hughes            ]
744*e1fe3e4aSElliott Hughes            for l in glyphAnchors:
745*e1fe3e4aSElliott Hughes                l.extend([None] * (self.ClassCount - len(l)))
746*e1fe3e4aSElliott Hughes            for allAnchors in zip(*glyphAnchors):
747*e1fe3e4aSElliott Hughes                if allNone(allAnchors):
748*e1fe3e4aSElliott Hughes                    anchor = None
749*e1fe3e4aSElliott Hughes                else:
750*e1fe3e4aSElliott Hughes                    anchor = ot.Anchor()
751*e1fe3e4aSElliott Hughes                    anchor.Format = 1
752*e1fe3e4aSElliott Hughes                    merger.mergeThings(anchor, allAnchors)
753*e1fe3e4aSElliott Hughes                anchors.append(anchor)
754*e1fe3e4aSElliott Hughes        records.append(rec)
755*e1fe3e4aSElliott Hughes    array = getattr(ot, Base + "Array")()
756*e1fe3e4aSElliott Hughes    setattr(array, Base + "Record", records)
757*e1fe3e4aSElliott Hughes    setattr(array, Base + "Count", len(records))
758*e1fe3e4aSElliott Hughes    setattr(self, Base + "Array", array)
759*e1fe3e4aSElliott Hughes
760*e1fe3e4aSElliott Hughes
761*e1fe3e4aSElliott Hughes@AligningMerger.merger(ot.MarkBasePos)
762*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
763*e1fe3e4aSElliott Hughes    if not allEqualTo(self.Format, (l.Format for l in lst)):
764*e1fe3e4aSElliott Hughes        raise InconsistentFormats(
765*e1fe3e4aSElliott Hughes            merger,
766*e1fe3e4aSElliott Hughes            subtable="mark-to-base positioning lookup",
767*e1fe3e4aSElliott Hughes            expected=self.Format,
768*e1fe3e4aSElliott Hughes            got=[l.Format for l in lst],
769*e1fe3e4aSElliott Hughes        )
770*e1fe3e4aSElliott Hughes    if self.Format == 1:
771*e1fe3e4aSElliott Hughes        _MarkBasePosFormat1_merge(self, lst, merger)
772*e1fe3e4aSElliott Hughes    else:
773*e1fe3e4aSElliott Hughes        raise UnsupportedFormat(merger, subtable="mark-to-base positioning lookup")
774*e1fe3e4aSElliott Hughes
775*e1fe3e4aSElliott Hughes
776*e1fe3e4aSElliott Hughes@AligningMerger.merger(ot.MarkMarkPos)
777*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
778*e1fe3e4aSElliott Hughes    if not allEqualTo(self.Format, (l.Format for l in lst)):
779*e1fe3e4aSElliott Hughes        raise InconsistentFormats(
780*e1fe3e4aSElliott Hughes            merger,
781*e1fe3e4aSElliott Hughes            subtable="mark-to-mark positioning lookup",
782*e1fe3e4aSElliott Hughes            expected=self.Format,
783*e1fe3e4aSElliott Hughes            got=[l.Format for l in lst],
784*e1fe3e4aSElliott Hughes        )
785*e1fe3e4aSElliott Hughes    if self.Format == 1:
786*e1fe3e4aSElliott Hughes        _MarkBasePosFormat1_merge(self, lst, merger, "Mark1", "Mark2")
787*e1fe3e4aSElliott Hughes    else:
788*e1fe3e4aSElliott Hughes        raise UnsupportedFormat(merger, subtable="mark-to-mark positioning lookup")
789*e1fe3e4aSElliott Hughes
790*e1fe3e4aSElliott Hughes
791*e1fe3e4aSElliott Hughesdef _PairSet_flatten(lst, font):
792*e1fe3e4aSElliott Hughes    self = ot.PairSet()
793*e1fe3e4aSElliott Hughes    self.Coverage = ot.Coverage()
794*e1fe3e4aSElliott Hughes
795*e1fe3e4aSElliott Hughes    # Align them
796*e1fe3e4aSElliott Hughes    glyphs, padded = _merge_GlyphOrders(
797*e1fe3e4aSElliott Hughes        font,
798*e1fe3e4aSElliott Hughes        [[v.SecondGlyph for v in vs.PairValueRecord] for vs in lst],
799*e1fe3e4aSElliott Hughes        [vs.PairValueRecord for vs in lst],
800*e1fe3e4aSElliott Hughes    )
801*e1fe3e4aSElliott Hughes
802*e1fe3e4aSElliott Hughes    self.Coverage.glyphs = glyphs
803*e1fe3e4aSElliott Hughes    self.PairValueRecord = pvrs = []
804*e1fe3e4aSElliott Hughes    for values in zip(*padded):
805*e1fe3e4aSElliott Hughes        for v in values:
806*e1fe3e4aSElliott Hughes            if v is not None:
807*e1fe3e4aSElliott Hughes                pvrs.append(v)
808*e1fe3e4aSElliott Hughes                break
809*e1fe3e4aSElliott Hughes        else:
810*e1fe3e4aSElliott Hughes            assert False
811*e1fe3e4aSElliott Hughes    self.PairValueCount = len(self.PairValueRecord)
812*e1fe3e4aSElliott Hughes
813*e1fe3e4aSElliott Hughes    return self
814*e1fe3e4aSElliott Hughes
815*e1fe3e4aSElliott Hughes
816*e1fe3e4aSElliott Hughesdef _Lookup_PairPosFormat1_subtables_flatten(lst, font):
817*e1fe3e4aSElliott Hughes    assert allEqual(
818*e1fe3e4aSElliott Hughes        [l.ValueFormat2 == 0 for l in lst if l.PairSet]
819*e1fe3e4aSElliott Hughes    ), "Report bug against fonttools."
820*e1fe3e4aSElliott Hughes
821*e1fe3e4aSElliott Hughes    self = ot.PairPos()
822*e1fe3e4aSElliott Hughes    self.Format = 1
823*e1fe3e4aSElliott Hughes    self.Coverage = ot.Coverage()
824*e1fe3e4aSElliott Hughes    self.ValueFormat1 = reduce(int.__or__, [l.ValueFormat1 for l in lst], 0)
825*e1fe3e4aSElliott Hughes    self.ValueFormat2 = reduce(int.__or__, [l.ValueFormat2 for l in lst], 0)
826*e1fe3e4aSElliott Hughes
827*e1fe3e4aSElliott Hughes    # Align them
828*e1fe3e4aSElliott Hughes    glyphs, padded = _merge_GlyphOrders(
829*e1fe3e4aSElliott Hughes        font, [v.Coverage.glyphs for v in lst], [v.PairSet for v in lst]
830*e1fe3e4aSElliott Hughes    )
831*e1fe3e4aSElliott Hughes
832*e1fe3e4aSElliott Hughes    self.Coverage.glyphs = glyphs
833*e1fe3e4aSElliott Hughes    self.PairSet = [
834*e1fe3e4aSElliott Hughes        _PairSet_flatten([v for v in values if v is not None], font)
835*e1fe3e4aSElliott Hughes        for values in zip(*padded)
836*e1fe3e4aSElliott Hughes    ]
837*e1fe3e4aSElliott Hughes    self.PairSetCount = len(self.PairSet)
838*e1fe3e4aSElliott Hughes    return self
839*e1fe3e4aSElliott Hughes
840*e1fe3e4aSElliott Hughes
841*e1fe3e4aSElliott Hughesdef _Lookup_PairPosFormat2_subtables_flatten(lst, font):
842*e1fe3e4aSElliott Hughes    assert allEqual(
843*e1fe3e4aSElliott Hughes        [l.ValueFormat2 == 0 for l in lst if l.Class1Record]
844*e1fe3e4aSElliott Hughes    ), "Report bug against fonttools."
845*e1fe3e4aSElliott Hughes
846*e1fe3e4aSElliott Hughes    self = ot.PairPos()
847*e1fe3e4aSElliott Hughes    self.Format = 2
848*e1fe3e4aSElliott Hughes    self.Coverage = ot.Coverage()
849*e1fe3e4aSElliott Hughes    self.ValueFormat1 = reduce(int.__or__, [l.ValueFormat1 for l in lst], 0)
850*e1fe3e4aSElliott Hughes    self.ValueFormat2 = reduce(int.__or__, [l.ValueFormat2 for l in lst], 0)
851*e1fe3e4aSElliott Hughes
852*e1fe3e4aSElliott Hughes    # Align them
853*e1fe3e4aSElliott Hughes    glyphs, _ = _merge_GlyphOrders(font, [v.Coverage.glyphs for v in lst])
854*e1fe3e4aSElliott Hughes    self.Coverage.glyphs = glyphs
855*e1fe3e4aSElliott Hughes
856*e1fe3e4aSElliott Hughes    matrices = _PairPosFormat2_align_matrices(self, lst, font, transparent=True)
857*e1fe3e4aSElliott Hughes
858*e1fe3e4aSElliott Hughes    matrix = self.Class1Record = []
859*e1fe3e4aSElliott Hughes    for rows in zip(*matrices):
860*e1fe3e4aSElliott Hughes        row = ot.Class1Record()
861*e1fe3e4aSElliott Hughes        matrix.append(row)
862*e1fe3e4aSElliott Hughes        row.Class2Record = []
863*e1fe3e4aSElliott Hughes        row = row.Class2Record
864*e1fe3e4aSElliott Hughes        for cols in zip(*list(r.Class2Record for r in rows)):
865*e1fe3e4aSElliott Hughes            col = next(iter(c for c in cols if c is not None))
866*e1fe3e4aSElliott Hughes            row.append(col)
867*e1fe3e4aSElliott Hughes
868*e1fe3e4aSElliott Hughes    return self
869*e1fe3e4aSElliott Hughes
870*e1fe3e4aSElliott Hughes
871*e1fe3e4aSElliott Hughesdef _Lookup_PairPos_subtables_canonicalize(lst, font):
872*e1fe3e4aSElliott Hughes    """Merge multiple Format1 subtables at the beginning of lst,
873*e1fe3e4aSElliott Hughes    and merge multiple consecutive Format2 subtables that have the same
874*e1fe3e4aSElliott Hughes    Class2 (ie. were split because of offset overflows).  Returns new list."""
875*e1fe3e4aSElliott Hughes    lst = list(lst)
876*e1fe3e4aSElliott Hughes
877*e1fe3e4aSElliott Hughes    l = len(lst)
878*e1fe3e4aSElliott Hughes    i = 0
879*e1fe3e4aSElliott Hughes    while i < l and lst[i].Format == 1:
880*e1fe3e4aSElliott Hughes        i += 1
881*e1fe3e4aSElliott Hughes    lst[:i] = [_Lookup_PairPosFormat1_subtables_flatten(lst[:i], font)]
882*e1fe3e4aSElliott Hughes
883*e1fe3e4aSElliott Hughes    l = len(lst)
884*e1fe3e4aSElliott Hughes    i = l
885*e1fe3e4aSElliott Hughes    while i > 0 and lst[i - 1].Format == 2:
886*e1fe3e4aSElliott Hughes        i -= 1
887*e1fe3e4aSElliott Hughes    lst[i:] = [_Lookup_PairPosFormat2_subtables_flatten(lst[i:], font)]
888*e1fe3e4aSElliott Hughes
889*e1fe3e4aSElliott Hughes    return lst
890*e1fe3e4aSElliott Hughes
891*e1fe3e4aSElliott Hughes
892*e1fe3e4aSElliott Hughesdef _Lookup_SinglePos_subtables_flatten(lst, font, min_inclusive_rec_format):
893*e1fe3e4aSElliott Hughes    glyphs, _ = _merge_GlyphOrders(font, [v.Coverage.glyphs for v in lst], None)
894*e1fe3e4aSElliott Hughes    num_glyphs = len(glyphs)
895*e1fe3e4aSElliott Hughes    new = ot.SinglePos()
896*e1fe3e4aSElliott Hughes    new.Format = 2
897*e1fe3e4aSElliott Hughes    new.ValueFormat = min_inclusive_rec_format
898*e1fe3e4aSElliott Hughes    new.Coverage = ot.Coverage()
899*e1fe3e4aSElliott Hughes    new.Coverage.glyphs = glyphs
900*e1fe3e4aSElliott Hughes    new.ValueCount = num_glyphs
901*e1fe3e4aSElliott Hughes    new.Value = [None] * num_glyphs
902*e1fe3e4aSElliott Hughes    for singlePos in lst:
903*e1fe3e4aSElliott Hughes        if singlePos.Format == 1:
904*e1fe3e4aSElliott Hughes            val_rec = singlePos.Value
905*e1fe3e4aSElliott Hughes            for gname in singlePos.Coverage.glyphs:
906*e1fe3e4aSElliott Hughes                i = glyphs.index(gname)
907*e1fe3e4aSElliott Hughes                new.Value[i] = copy.deepcopy(val_rec)
908*e1fe3e4aSElliott Hughes        elif singlePos.Format == 2:
909*e1fe3e4aSElliott Hughes            for j, gname in enumerate(singlePos.Coverage.glyphs):
910*e1fe3e4aSElliott Hughes                val_rec = singlePos.Value[j]
911*e1fe3e4aSElliott Hughes                i = glyphs.index(gname)
912*e1fe3e4aSElliott Hughes                new.Value[i] = copy.deepcopy(val_rec)
913*e1fe3e4aSElliott Hughes    return [new]
914*e1fe3e4aSElliott Hughes
915*e1fe3e4aSElliott Hughes
916*e1fe3e4aSElliott Hughes@AligningMerger.merger(ot.CursivePos)
917*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
918*e1fe3e4aSElliott Hughes    # Align them
919*e1fe3e4aSElliott Hughes    glyphs, padded = _merge_GlyphOrders(
920*e1fe3e4aSElliott Hughes        merger.font,
921*e1fe3e4aSElliott Hughes        [l.Coverage.glyphs for l in lst],
922*e1fe3e4aSElliott Hughes        [l.EntryExitRecord for l in lst],
923*e1fe3e4aSElliott Hughes    )
924*e1fe3e4aSElliott Hughes
925*e1fe3e4aSElliott Hughes    self.Format = 1
926*e1fe3e4aSElliott Hughes    self.Coverage = ot.Coverage()
927*e1fe3e4aSElliott Hughes    self.Coverage.glyphs = glyphs
928*e1fe3e4aSElliott Hughes    self.EntryExitRecord = []
929*e1fe3e4aSElliott Hughes    for _ in glyphs:
930*e1fe3e4aSElliott Hughes        rec = ot.EntryExitRecord()
931*e1fe3e4aSElliott Hughes        rec.EntryAnchor = ot.Anchor()
932*e1fe3e4aSElliott Hughes        rec.EntryAnchor.Format = 1
933*e1fe3e4aSElliott Hughes        rec.ExitAnchor = ot.Anchor()
934*e1fe3e4aSElliott Hughes        rec.ExitAnchor.Format = 1
935*e1fe3e4aSElliott Hughes        self.EntryExitRecord.append(rec)
936*e1fe3e4aSElliott Hughes    merger.mergeLists(self.EntryExitRecord, padded)
937*e1fe3e4aSElliott Hughes    self.EntryExitCount = len(self.EntryExitRecord)
938*e1fe3e4aSElliott Hughes
939*e1fe3e4aSElliott Hughes
940*e1fe3e4aSElliott Hughes@AligningMerger.merger(ot.EntryExitRecord)
941*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
942*e1fe3e4aSElliott Hughes    if all(master.EntryAnchor is None for master in lst):
943*e1fe3e4aSElliott Hughes        self.EntryAnchor = None
944*e1fe3e4aSElliott Hughes    if all(master.ExitAnchor is None for master in lst):
945*e1fe3e4aSElliott Hughes        self.ExitAnchor = None
946*e1fe3e4aSElliott Hughes    merger.mergeObjects(self, lst)
947*e1fe3e4aSElliott Hughes
948*e1fe3e4aSElliott Hughes
949*e1fe3e4aSElliott Hughes@AligningMerger.merger(ot.Lookup)
950*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
951*e1fe3e4aSElliott Hughes    subtables = merger.lookup_subtables = [l.SubTable for l in lst]
952*e1fe3e4aSElliott Hughes
953*e1fe3e4aSElliott Hughes    # Remove Extension subtables
954*e1fe3e4aSElliott Hughes    for l, sts in list(zip(lst, subtables)) + [(self, self.SubTable)]:
955*e1fe3e4aSElliott Hughes        if not sts:
956*e1fe3e4aSElliott Hughes            continue
957*e1fe3e4aSElliott Hughes        if sts[0].__class__.__name__.startswith("Extension"):
958*e1fe3e4aSElliott Hughes            if not allEqual([st.__class__ for st in sts]):
959*e1fe3e4aSElliott Hughes                raise InconsistentExtensions(
960*e1fe3e4aSElliott Hughes                    merger,
961*e1fe3e4aSElliott Hughes                    expected="Extension",
962*e1fe3e4aSElliott Hughes                    got=[st.__class__.__name__ for st in sts],
963*e1fe3e4aSElliott Hughes                )
964*e1fe3e4aSElliott Hughes            if not allEqual([st.ExtensionLookupType for st in sts]):
965*e1fe3e4aSElliott Hughes                raise InconsistentExtensions(merger)
966*e1fe3e4aSElliott Hughes            l.LookupType = sts[0].ExtensionLookupType
967*e1fe3e4aSElliott Hughes            new_sts = [st.ExtSubTable for st in sts]
968*e1fe3e4aSElliott Hughes            del sts[:]
969*e1fe3e4aSElliott Hughes            sts.extend(new_sts)
970*e1fe3e4aSElliott Hughes
971*e1fe3e4aSElliott Hughes    isPairPos = self.SubTable and isinstance(self.SubTable[0], ot.PairPos)
972*e1fe3e4aSElliott Hughes
973*e1fe3e4aSElliott Hughes    if isPairPos:
974*e1fe3e4aSElliott Hughes        # AFDKO and feaLib sometimes generate two Format1 subtables instead of one.
975*e1fe3e4aSElliott Hughes        # Merge those before continuing.
976*e1fe3e4aSElliott Hughes        # https://github.com/fonttools/fonttools/issues/719
977*e1fe3e4aSElliott Hughes        self.SubTable = _Lookup_PairPos_subtables_canonicalize(
978*e1fe3e4aSElliott Hughes            self.SubTable, merger.font
979*e1fe3e4aSElliott Hughes        )
980*e1fe3e4aSElliott Hughes        subtables = merger.lookup_subtables = [
981*e1fe3e4aSElliott Hughes            _Lookup_PairPos_subtables_canonicalize(st, merger.font) for st in subtables
982*e1fe3e4aSElliott Hughes        ]
983*e1fe3e4aSElliott Hughes    else:
984*e1fe3e4aSElliott Hughes        isSinglePos = self.SubTable and isinstance(self.SubTable[0], ot.SinglePos)
985*e1fe3e4aSElliott Hughes        if isSinglePos:
986*e1fe3e4aSElliott Hughes            numSubtables = [len(st) for st in subtables]
987*e1fe3e4aSElliott Hughes            if not all([nums == numSubtables[0] for nums in numSubtables]):
988*e1fe3e4aSElliott Hughes                # Flatten list of SinglePos subtables to single Format 2 subtable,
989*e1fe3e4aSElliott Hughes                # with all value records set to the rec format type.
990*e1fe3e4aSElliott Hughes                # We use buildSinglePos() to optimize the lookup after merging.
991*e1fe3e4aSElliott Hughes                valueFormatList = [t.ValueFormat for st in subtables for t in st]
992*e1fe3e4aSElliott Hughes                # Find the minimum value record that can accomodate all the singlePos subtables.
993*e1fe3e4aSElliott Hughes                mirf = reduce(ior, valueFormatList)
994*e1fe3e4aSElliott Hughes                self.SubTable = _Lookup_SinglePos_subtables_flatten(
995*e1fe3e4aSElliott Hughes                    self.SubTable, merger.font, mirf
996*e1fe3e4aSElliott Hughes                )
997*e1fe3e4aSElliott Hughes                subtables = merger.lookup_subtables = [
998*e1fe3e4aSElliott Hughes                    _Lookup_SinglePos_subtables_flatten(st, merger.font, mirf)
999*e1fe3e4aSElliott Hughes                    for st in subtables
1000*e1fe3e4aSElliott Hughes                ]
1001*e1fe3e4aSElliott Hughes                flattened = True
1002*e1fe3e4aSElliott Hughes            else:
1003*e1fe3e4aSElliott Hughes                flattened = False
1004*e1fe3e4aSElliott Hughes
1005*e1fe3e4aSElliott Hughes    merger.mergeLists(self.SubTable, subtables)
1006*e1fe3e4aSElliott Hughes    self.SubTableCount = len(self.SubTable)
1007*e1fe3e4aSElliott Hughes
1008*e1fe3e4aSElliott Hughes    if isPairPos:
1009*e1fe3e4aSElliott Hughes        # If format-1 subtable created during canonicalization is empty, remove it.
1010*e1fe3e4aSElliott Hughes        assert len(self.SubTable) >= 1 and self.SubTable[0].Format == 1
1011*e1fe3e4aSElliott Hughes        if not self.SubTable[0].Coverage.glyphs:
1012*e1fe3e4aSElliott Hughes            self.SubTable.pop(0)
1013*e1fe3e4aSElliott Hughes            self.SubTableCount -= 1
1014*e1fe3e4aSElliott Hughes
1015*e1fe3e4aSElliott Hughes        # If format-2 subtable created during canonicalization is empty, remove it.
1016*e1fe3e4aSElliott Hughes        assert len(self.SubTable) >= 1 and self.SubTable[-1].Format == 2
1017*e1fe3e4aSElliott Hughes        if not self.SubTable[-1].Coverage.glyphs:
1018*e1fe3e4aSElliott Hughes            self.SubTable.pop(-1)
1019*e1fe3e4aSElliott Hughes            self.SubTableCount -= 1
1020*e1fe3e4aSElliott Hughes
1021*e1fe3e4aSElliott Hughes        # Compact the merged subtables
1022*e1fe3e4aSElliott Hughes        # This is a good moment to do it because the compaction should create
1023*e1fe3e4aSElliott Hughes        # smaller subtables, which may prevent overflows from happening.
1024*e1fe3e4aSElliott Hughes        # Keep reading the value from the ENV until ufo2ft switches to the config system
1025*e1fe3e4aSElliott Hughes        level = merger.font.cfg.get(
1026*e1fe3e4aSElliott Hughes            "fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL",
1027*e1fe3e4aSElliott Hughes            default=_compression_level_from_env(),
1028*e1fe3e4aSElliott Hughes        )
1029*e1fe3e4aSElliott Hughes        if level != 0:
1030*e1fe3e4aSElliott Hughes            log.info("Compacting GPOS...")
1031*e1fe3e4aSElliott Hughes            self.SubTable = compact_pair_pos(merger.font, level, self.SubTable)
1032*e1fe3e4aSElliott Hughes            self.SubTableCount = len(self.SubTable)
1033*e1fe3e4aSElliott Hughes
1034*e1fe3e4aSElliott Hughes    elif isSinglePos and flattened:
1035*e1fe3e4aSElliott Hughes        singlePosTable = self.SubTable[0]
1036*e1fe3e4aSElliott Hughes        glyphs = singlePosTable.Coverage.glyphs
1037*e1fe3e4aSElliott Hughes        # We know that singlePosTable is Format 2, as this is set
1038*e1fe3e4aSElliott Hughes        # in _Lookup_SinglePos_subtables_flatten.
1039*e1fe3e4aSElliott Hughes        singlePosMapping = {
1040*e1fe3e4aSElliott Hughes            gname: valRecord for gname, valRecord in zip(glyphs, singlePosTable.Value)
1041*e1fe3e4aSElliott Hughes        }
1042*e1fe3e4aSElliott Hughes        self.SubTable = buildSinglePos(
1043*e1fe3e4aSElliott Hughes            singlePosMapping, merger.font.getReverseGlyphMap()
1044*e1fe3e4aSElliott Hughes        )
1045*e1fe3e4aSElliott Hughes    merger.mergeObjects(self, lst, exclude=["SubTable", "SubTableCount"])
1046*e1fe3e4aSElliott Hughes
1047*e1fe3e4aSElliott Hughes    del merger.lookup_subtables
1048*e1fe3e4aSElliott Hughes
1049*e1fe3e4aSElliott Hughes
1050*e1fe3e4aSElliott Hughes#
1051*e1fe3e4aSElliott Hughes# InstancerMerger
1052*e1fe3e4aSElliott Hughes#
1053*e1fe3e4aSElliott Hughes
1054*e1fe3e4aSElliott Hughes
1055*e1fe3e4aSElliott Hughesclass InstancerMerger(AligningMerger):
1056*e1fe3e4aSElliott Hughes    """A merger that takes multiple master fonts, and instantiates
1057*e1fe3e4aSElliott Hughes    an instance."""
1058*e1fe3e4aSElliott Hughes
1059*e1fe3e4aSElliott Hughes    def __init__(self, font, model, location):
1060*e1fe3e4aSElliott Hughes        Merger.__init__(self, font)
1061*e1fe3e4aSElliott Hughes        self.model = model
1062*e1fe3e4aSElliott Hughes        self.location = location
1063*e1fe3e4aSElliott Hughes        self.masterScalars = model.getMasterScalars(location)
1064*e1fe3e4aSElliott Hughes
1065*e1fe3e4aSElliott Hughes
1066*e1fe3e4aSElliott Hughes@InstancerMerger.merger(ot.CaretValue)
1067*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1068*e1fe3e4aSElliott Hughes    assert self.Format == 1
1069*e1fe3e4aSElliott Hughes    Coords = [a.Coordinate for a in lst]
1070*e1fe3e4aSElliott Hughes    model = merger.model
1071*e1fe3e4aSElliott Hughes    masterScalars = merger.masterScalars
1072*e1fe3e4aSElliott Hughes    self.Coordinate = otRound(
1073*e1fe3e4aSElliott Hughes        model.interpolateFromValuesAndScalars(Coords, masterScalars)
1074*e1fe3e4aSElliott Hughes    )
1075*e1fe3e4aSElliott Hughes
1076*e1fe3e4aSElliott Hughes
1077*e1fe3e4aSElliott Hughes@InstancerMerger.merger(ot.Anchor)
1078*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1079*e1fe3e4aSElliott Hughes    assert self.Format == 1
1080*e1fe3e4aSElliott Hughes    XCoords = [a.XCoordinate for a in lst]
1081*e1fe3e4aSElliott Hughes    YCoords = [a.YCoordinate for a in lst]
1082*e1fe3e4aSElliott Hughes    model = merger.model
1083*e1fe3e4aSElliott Hughes    masterScalars = merger.masterScalars
1084*e1fe3e4aSElliott Hughes    self.XCoordinate = otRound(
1085*e1fe3e4aSElliott Hughes        model.interpolateFromValuesAndScalars(XCoords, masterScalars)
1086*e1fe3e4aSElliott Hughes    )
1087*e1fe3e4aSElliott Hughes    self.YCoordinate = otRound(
1088*e1fe3e4aSElliott Hughes        model.interpolateFromValuesAndScalars(YCoords, masterScalars)
1089*e1fe3e4aSElliott Hughes    )
1090*e1fe3e4aSElliott Hughes
1091*e1fe3e4aSElliott Hughes
1092*e1fe3e4aSElliott Hughes@InstancerMerger.merger(otBase.ValueRecord)
1093*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1094*e1fe3e4aSElliott Hughes    model = merger.model
1095*e1fe3e4aSElliott Hughes    masterScalars = merger.masterScalars
1096*e1fe3e4aSElliott Hughes    # TODO Handle differing valueformats
1097*e1fe3e4aSElliott Hughes    for name, tableName in [
1098*e1fe3e4aSElliott Hughes        ("XAdvance", "XAdvDevice"),
1099*e1fe3e4aSElliott Hughes        ("YAdvance", "YAdvDevice"),
1100*e1fe3e4aSElliott Hughes        ("XPlacement", "XPlaDevice"),
1101*e1fe3e4aSElliott Hughes        ("YPlacement", "YPlaDevice"),
1102*e1fe3e4aSElliott Hughes    ]:
1103*e1fe3e4aSElliott Hughes        assert not hasattr(self, tableName)
1104*e1fe3e4aSElliott Hughes
1105*e1fe3e4aSElliott Hughes        if hasattr(self, name):
1106*e1fe3e4aSElliott Hughes            values = [getattr(a, name, 0) for a in lst]
1107*e1fe3e4aSElliott Hughes            value = otRound(
1108*e1fe3e4aSElliott Hughes                model.interpolateFromValuesAndScalars(values, masterScalars)
1109*e1fe3e4aSElliott Hughes            )
1110*e1fe3e4aSElliott Hughes            setattr(self, name, value)
1111*e1fe3e4aSElliott Hughes
1112*e1fe3e4aSElliott Hughes
1113*e1fe3e4aSElliott Hughes#
1114*e1fe3e4aSElliott Hughes# MutatorMerger
1115*e1fe3e4aSElliott Hughes#
1116*e1fe3e4aSElliott Hughes
1117*e1fe3e4aSElliott Hughes
1118*e1fe3e4aSElliott Hughesclass MutatorMerger(AligningMerger):
1119*e1fe3e4aSElliott Hughes    """A merger that takes a variable font, and instantiates
1120*e1fe3e4aSElliott Hughes    an instance.  While there's no "merging" to be done per se,
1121*e1fe3e4aSElliott Hughes    the operation can benefit from many operations that the
1122*e1fe3e4aSElliott Hughes    aligning merger does."""
1123*e1fe3e4aSElliott Hughes
1124*e1fe3e4aSElliott Hughes    def __init__(self, font, instancer, deleteVariations=True):
1125*e1fe3e4aSElliott Hughes        Merger.__init__(self, font)
1126*e1fe3e4aSElliott Hughes        self.instancer = instancer
1127*e1fe3e4aSElliott Hughes        self.deleteVariations = deleteVariations
1128*e1fe3e4aSElliott Hughes
1129*e1fe3e4aSElliott Hughes
1130*e1fe3e4aSElliott Hughes@MutatorMerger.merger(ot.CaretValue)
1131*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1132*e1fe3e4aSElliott Hughes    # Hack till we become selfless.
1133*e1fe3e4aSElliott Hughes    self.__dict__ = lst[0].__dict__.copy()
1134*e1fe3e4aSElliott Hughes
1135*e1fe3e4aSElliott Hughes    if self.Format != 3:
1136*e1fe3e4aSElliott Hughes        return
1137*e1fe3e4aSElliott Hughes
1138*e1fe3e4aSElliott Hughes    instancer = merger.instancer
1139*e1fe3e4aSElliott Hughes    dev = self.DeviceTable
1140*e1fe3e4aSElliott Hughes    if merger.deleteVariations:
1141*e1fe3e4aSElliott Hughes        del self.DeviceTable
1142*e1fe3e4aSElliott Hughes    if dev:
1143*e1fe3e4aSElliott Hughes        assert dev.DeltaFormat == 0x8000
1144*e1fe3e4aSElliott Hughes        varidx = (dev.StartSize << 16) + dev.EndSize
1145*e1fe3e4aSElliott Hughes        delta = otRound(instancer[varidx])
1146*e1fe3e4aSElliott Hughes        self.Coordinate += delta
1147*e1fe3e4aSElliott Hughes
1148*e1fe3e4aSElliott Hughes    if merger.deleteVariations:
1149*e1fe3e4aSElliott Hughes        self.Format = 1
1150*e1fe3e4aSElliott Hughes
1151*e1fe3e4aSElliott Hughes
1152*e1fe3e4aSElliott Hughes@MutatorMerger.merger(ot.Anchor)
1153*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1154*e1fe3e4aSElliott Hughes    # Hack till we become selfless.
1155*e1fe3e4aSElliott Hughes    self.__dict__ = lst[0].__dict__.copy()
1156*e1fe3e4aSElliott Hughes
1157*e1fe3e4aSElliott Hughes    if self.Format != 3:
1158*e1fe3e4aSElliott Hughes        return
1159*e1fe3e4aSElliott Hughes
1160*e1fe3e4aSElliott Hughes    instancer = merger.instancer
1161*e1fe3e4aSElliott Hughes    for v in "XY":
1162*e1fe3e4aSElliott Hughes        tableName = v + "DeviceTable"
1163*e1fe3e4aSElliott Hughes        if not hasattr(self, tableName):
1164*e1fe3e4aSElliott Hughes            continue
1165*e1fe3e4aSElliott Hughes        dev = getattr(self, tableName)
1166*e1fe3e4aSElliott Hughes        if merger.deleteVariations:
1167*e1fe3e4aSElliott Hughes            delattr(self, tableName)
1168*e1fe3e4aSElliott Hughes        if dev is None:
1169*e1fe3e4aSElliott Hughes            continue
1170*e1fe3e4aSElliott Hughes
1171*e1fe3e4aSElliott Hughes        assert dev.DeltaFormat == 0x8000
1172*e1fe3e4aSElliott Hughes        varidx = (dev.StartSize << 16) + dev.EndSize
1173*e1fe3e4aSElliott Hughes        delta = otRound(instancer[varidx])
1174*e1fe3e4aSElliott Hughes
1175*e1fe3e4aSElliott Hughes        attr = v + "Coordinate"
1176*e1fe3e4aSElliott Hughes        setattr(self, attr, getattr(self, attr) + delta)
1177*e1fe3e4aSElliott Hughes
1178*e1fe3e4aSElliott Hughes    if merger.deleteVariations:
1179*e1fe3e4aSElliott Hughes        self.Format = 1
1180*e1fe3e4aSElliott Hughes
1181*e1fe3e4aSElliott Hughes
1182*e1fe3e4aSElliott Hughes@MutatorMerger.merger(otBase.ValueRecord)
1183*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1184*e1fe3e4aSElliott Hughes    # Hack till we become selfless.
1185*e1fe3e4aSElliott Hughes    self.__dict__ = lst[0].__dict__.copy()
1186*e1fe3e4aSElliott Hughes
1187*e1fe3e4aSElliott Hughes    instancer = merger.instancer
1188*e1fe3e4aSElliott Hughes    for name, tableName in [
1189*e1fe3e4aSElliott Hughes        ("XAdvance", "XAdvDevice"),
1190*e1fe3e4aSElliott Hughes        ("YAdvance", "YAdvDevice"),
1191*e1fe3e4aSElliott Hughes        ("XPlacement", "XPlaDevice"),
1192*e1fe3e4aSElliott Hughes        ("YPlacement", "YPlaDevice"),
1193*e1fe3e4aSElliott Hughes    ]:
1194*e1fe3e4aSElliott Hughes        if not hasattr(self, tableName):
1195*e1fe3e4aSElliott Hughes            continue
1196*e1fe3e4aSElliott Hughes        dev = getattr(self, tableName)
1197*e1fe3e4aSElliott Hughes        if merger.deleteVariations:
1198*e1fe3e4aSElliott Hughes            delattr(self, tableName)
1199*e1fe3e4aSElliott Hughes        if dev is None:
1200*e1fe3e4aSElliott Hughes            continue
1201*e1fe3e4aSElliott Hughes
1202*e1fe3e4aSElliott Hughes        assert dev.DeltaFormat == 0x8000
1203*e1fe3e4aSElliott Hughes        varidx = (dev.StartSize << 16) + dev.EndSize
1204*e1fe3e4aSElliott Hughes        delta = otRound(instancer[varidx])
1205*e1fe3e4aSElliott Hughes
1206*e1fe3e4aSElliott Hughes        setattr(self, name, getattr(self, name, 0) + delta)
1207*e1fe3e4aSElliott Hughes
1208*e1fe3e4aSElliott Hughes
1209*e1fe3e4aSElliott Hughes#
1210*e1fe3e4aSElliott Hughes# VariationMerger
1211*e1fe3e4aSElliott Hughes#
1212*e1fe3e4aSElliott Hughes
1213*e1fe3e4aSElliott Hughes
1214*e1fe3e4aSElliott Hughesclass VariationMerger(AligningMerger):
1215*e1fe3e4aSElliott Hughes    """A merger that takes multiple master fonts, and builds a
1216*e1fe3e4aSElliott Hughes    variable font."""
1217*e1fe3e4aSElliott Hughes
1218*e1fe3e4aSElliott Hughes    def __init__(self, model, axisTags, font):
1219*e1fe3e4aSElliott Hughes        Merger.__init__(self, font)
1220*e1fe3e4aSElliott Hughes        self.store_builder = varStore.OnlineVarStoreBuilder(axisTags)
1221*e1fe3e4aSElliott Hughes        self.setModel(model)
1222*e1fe3e4aSElliott Hughes
1223*e1fe3e4aSElliott Hughes    def setModel(self, model):
1224*e1fe3e4aSElliott Hughes        self.model = model
1225*e1fe3e4aSElliott Hughes        self.store_builder.setModel(model)
1226*e1fe3e4aSElliott Hughes
1227*e1fe3e4aSElliott Hughes    def mergeThings(self, out, lst):
1228*e1fe3e4aSElliott Hughes        masterModel = None
1229*e1fe3e4aSElliott Hughes        origTTFs = None
1230*e1fe3e4aSElliott Hughes        if None in lst:
1231*e1fe3e4aSElliott Hughes            if allNone(lst):
1232*e1fe3e4aSElliott Hughes                if out is not None:
1233*e1fe3e4aSElliott Hughes                    raise FoundANone(self, got=lst)
1234*e1fe3e4aSElliott Hughes                return
1235*e1fe3e4aSElliott Hughes
1236*e1fe3e4aSElliott Hughes            # temporarily subset the list of master ttfs to the ones for which
1237*e1fe3e4aSElliott Hughes            # master values are not None
1238*e1fe3e4aSElliott Hughes            origTTFs = self.ttfs
1239*e1fe3e4aSElliott Hughes            if self.ttfs:
1240*e1fe3e4aSElliott Hughes                self.ttfs = subList([v is not None for v in lst], self.ttfs)
1241*e1fe3e4aSElliott Hughes
1242*e1fe3e4aSElliott Hughes            masterModel = self.model
1243*e1fe3e4aSElliott Hughes            model, lst = masterModel.getSubModel(lst)
1244*e1fe3e4aSElliott Hughes            self.setModel(model)
1245*e1fe3e4aSElliott Hughes
1246*e1fe3e4aSElliott Hughes        super(VariationMerger, self).mergeThings(out, lst)
1247*e1fe3e4aSElliott Hughes
1248*e1fe3e4aSElliott Hughes        if masterModel:
1249*e1fe3e4aSElliott Hughes            self.setModel(masterModel)
1250*e1fe3e4aSElliott Hughes        if origTTFs:
1251*e1fe3e4aSElliott Hughes            self.ttfs = origTTFs
1252*e1fe3e4aSElliott Hughes
1253*e1fe3e4aSElliott Hughes
1254*e1fe3e4aSElliott Hughesdef buildVarDevTable(store_builder, master_values):
1255*e1fe3e4aSElliott Hughes    if allEqual(master_values):
1256*e1fe3e4aSElliott Hughes        return master_values[0], None
1257*e1fe3e4aSElliott Hughes    base, varIdx = store_builder.storeMasters(master_values)
1258*e1fe3e4aSElliott Hughes    return base, builder.buildVarDevTable(varIdx)
1259*e1fe3e4aSElliott Hughes
1260*e1fe3e4aSElliott Hughes
1261*e1fe3e4aSElliott Hughes@VariationMerger.merger(ot.BaseCoord)
1262*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1263*e1fe3e4aSElliott Hughes    if self.Format != 1:
1264*e1fe3e4aSElliott Hughes        raise UnsupportedFormat(merger, subtable="a baseline coordinate")
1265*e1fe3e4aSElliott Hughes    self.Coordinate, DeviceTable = buildVarDevTable(
1266*e1fe3e4aSElliott Hughes        merger.store_builder, [a.Coordinate for a in lst]
1267*e1fe3e4aSElliott Hughes    )
1268*e1fe3e4aSElliott Hughes    if DeviceTable:
1269*e1fe3e4aSElliott Hughes        self.Format = 3
1270*e1fe3e4aSElliott Hughes        self.DeviceTable = DeviceTable
1271*e1fe3e4aSElliott Hughes
1272*e1fe3e4aSElliott Hughes
1273*e1fe3e4aSElliott Hughes@VariationMerger.merger(ot.CaretValue)
1274*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1275*e1fe3e4aSElliott Hughes    if self.Format != 1:
1276*e1fe3e4aSElliott Hughes        raise UnsupportedFormat(merger, subtable="a caret")
1277*e1fe3e4aSElliott Hughes    self.Coordinate, DeviceTable = buildVarDevTable(
1278*e1fe3e4aSElliott Hughes        merger.store_builder, [a.Coordinate for a in lst]
1279*e1fe3e4aSElliott Hughes    )
1280*e1fe3e4aSElliott Hughes    if DeviceTable:
1281*e1fe3e4aSElliott Hughes        self.Format = 3
1282*e1fe3e4aSElliott Hughes        self.DeviceTable = DeviceTable
1283*e1fe3e4aSElliott Hughes
1284*e1fe3e4aSElliott Hughes
1285*e1fe3e4aSElliott Hughes@VariationMerger.merger(ot.Anchor)
1286*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1287*e1fe3e4aSElliott Hughes    if self.Format != 1:
1288*e1fe3e4aSElliott Hughes        raise UnsupportedFormat(merger, subtable="an anchor")
1289*e1fe3e4aSElliott Hughes    self.XCoordinate, XDeviceTable = buildVarDevTable(
1290*e1fe3e4aSElliott Hughes        merger.store_builder, [a.XCoordinate for a in lst]
1291*e1fe3e4aSElliott Hughes    )
1292*e1fe3e4aSElliott Hughes    self.YCoordinate, YDeviceTable = buildVarDevTable(
1293*e1fe3e4aSElliott Hughes        merger.store_builder, [a.YCoordinate for a in lst]
1294*e1fe3e4aSElliott Hughes    )
1295*e1fe3e4aSElliott Hughes    if XDeviceTable or YDeviceTable:
1296*e1fe3e4aSElliott Hughes        self.Format = 3
1297*e1fe3e4aSElliott Hughes        self.XDeviceTable = XDeviceTable
1298*e1fe3e4aSElliott Hughes        self.YDeviceTable = YDeviceTable
1299*e1fe3e4aSElliott Hughes
1300*e1fe3e4aSElliott Hughes
1301*e1fe3e4aSElliott Hughes@VariationMerger.merger(otBase.ValueRecord)
1302*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1303*e1fe3e4aSElliott Hughes    for name, tableName in [
1304*e1fe3e4aSElliott Hughes        ("XAdvance", "XAdvDevice"),
1305*e1fe3e4aSElliott Hughes        ("YAdvance", "YAdvDevice"),
1306*e1fe3e4aSElliott Hughes        ("XPlacement", "XPlaDevice"),
1307*e1fe3e4aSElliott Hughes        ("YPlacement", "YPlaDevice"),
1308*e1fe3e4aSElliott Hughes    ]:
1309*e1fe3e4aSElliott Hughes        if hasattr(self, name):
1310*e1fe3e4aSElliott Hughes            value, deviceTable = buildVarDevTable(
1311*e1fe3e4aSElliott Hughes                merger.store_builder, [getattr(a, name, 0) for a in lst]
1312*e1fe3e4aSElliott Hughes            )
1313*e1fe3e4aSElliott Hughes            setattr(self, name, value)
1314*e1fe3e4aSElliott Hughes            if deviceTable:
1315*e1fe3e4aSElliott Hughes                setattr(self, tableName, deviceTable)
1316*e1fe3e4aSElliott Hughes
1317*e1fe3e4aSElliott Hughes
1318*e1fe3e4aSElliott Hughesclass COLRVariationMerger(VariationMerger):
1319*e1fe3e4aSElliott Hughes    """A specialized VariationMerger that takes multiple master fonts containing
1320*e1fe3e4aSElliott Hughes    COLRv1 tables, and builds a variable COLR font.
1321*e1fe3e4aSElliott Hughes
1322*e1fe3e4aSElliott Hughes    COLR tables are special in that variable subtables can be associated with
1323*e1fe3e4aSElliott Hughes    multiple delta-set indices (via VarIndexBase).
1324*e1fe3e4aSElliott Hughes    They also contain tables that must change their type (not simply the Format)
1325*e1fe3e4aSElliott Hughes    as they become variable (e.g. Affine2x3 -> VarAffine2x3) so this merger takes
1326*e1fe3e4aSElliott Hughes    care of that too.
1327*e1fe3e4aSElliott Hughes    """
1328*e1fe3e4aSElliott Hughes
1329*e1fe3e4aSElliott Hughes    def __init__(self, model, axisTags, font, allowLayerReuse=True):
1330*e1fe3e4aSElliott Hughes        VariationMerger.__init__(self, model, axisTags, font)
1331*e1fe3e4aSElliott Hughes        # maps {tuple(varIdxes): VarIndexBase} to facilitate reuse of VarIndexBase
1332*e1fe3e4aSElliott Hughes        # between variable tables with same varIdxes.
1333*e1fe3e4aSElliott Hughes        self.varIndexCache = {}
1334*e1fe3e4aSElliott Hughes        # flat list of all the varIdxes generated while merging
1335*e1fe3e4aSElliott Hughes        self.varIdxes = []
1336*e1fe3e4aSElliott Hughes        # set of id()s of the subtables that contain variations after merging
1337*e1fe3e4aSElliott Hughes        # and need to be upgraded to the associated VarType.
1338*e1fe3e4aSElliott Hughes        self.varTableIds = set()
1339*e1fe3e4aSElliott Hughes        # we keep these around for rebuilding a LayerList while merging PaintColrLayers
1340*e1fe3e4aSElliott Hughes        self.layers = []
1341*e1fe3e4aSElliott Hughes        self.layerReuseCache = None
1342*e1fe3e4aSElliott Hughes        if allowLayerReuse:
1343*e1fe3e4aSElliott Hughes            self.layerReuseCache = LayerReuseCache()
1344*e1fe3e4aSElliott Hughes        # flag to ensure BaseGlyphList is fully merged before LayerList gets processed
1345*e1fe3e4aSElliott Hughes        self._doneBaseGlyphs = False
1346*e1fe3e4aSElliott Hughes
1347*e1fe3e4aSElliott Hughes    def mergeTables(self, font, master_ttfs, tableTags=("COLR",)):
1348*e1fe3e4aSElliott Hughes        if "COLR" in tableTags and "COLR" in font:
1349*e1fe3e4aSElliott Hughes            # The merger modifies the destination COLR table in-place. If this contains
1350*e1fe3e4aSElliott Hughes            # multiple PaintColrLayers referencing the same layers from LayerList, it's
1351*e1fe3e4aSElliott Hughes            # a problem because we may risk modifying the same paint more than once, or
1352*e1fe3e4aSElliott Hughes            # worse, fail while attempting to do that.
1353*e1fe3e4aSElliott Hughes            # We don't know whether the master COLR table was built with layer reuse
1354*e1fe3e4aSElliott Hughes            # disabled, thus to be safe we rebuild its LayerList so that it contains only
1355*e1fe3e4aSElliott Hughes            # unique layers referenced from non-overlapping PaintColrLayers throughout
1356*e1fe3e4aSElliott Hughes            # the base paint graphs.
1357*e1fe3e4aSElliott Hughes            self.expandPaintColrLayers(font["COLR"].table)
1358*e1fe3e4aSElliott Hughes        VariationMerger.mergeTables(self, font, master_ttfs, tableTags)
1359*e1fe3e4aSElliott Hughes
1360*e1fe3e4aSElliott Hughes    def checkFormatEnum(self, out, lst, validate=lambda _: True):
1361*e1fe3e4aSElliott Hughes        fmt = out.Format
1362*e1fe3e4aSElliott Hughes        formatEnum = out.formatEnum
1363*e1fe3e4aSElliott Hughes        ok = False
1364*e1fe3e4aSElliott Hughes        try:
1365*e1fe3e4aSElliott Hughes            fmt = formatEnum(fmt)
1366*e1fe3e4aSElliott Hughes        except ValueError:
1367*e1fe3e4aSElliott Hughes            pass
1368*e1fe3e4aSElliott Hughes        else:
1369*e1fe3e4aSElliott Hughes            ok = validate(fmt)
1370*e1fe3e4aSElliott Hughes        if not ok:
1371*e1fe3e4aSElliott Hughes            raise UnsupportedFormat(self, subtable=type(out).__name__, value=fmt)
1372*e1fe3e4aSElliott Hughes        expected = fmt
1373*e1fe3e4aSElliott Hughes        got = []
1374*e1fe3e4aSElliott Hughes        for v in lst:
1375*e1fe3e4aSElliott Hughes            fmt = getattr(v, "Format", None)
1376*e1fe3e4aSElliott Hughes            try:
1377*e1fe3e4aSElliott Hughes                fmt = formatEnum(fmt)
1378*e1fe3e4aSElliott Hughes            except ValueError:
1379*e1fe3e4aSElliott Hughes                pass
1380*e1fe3e4aSElliott Hughes            got.append(fmt)
1381*e1fe3e4aSElliott Hughes        if not allEqualTo(expected, got):
1382*e1fe3e4aSElliott Hughes            raise InconsistentFormats(
1383*e1fe3e4aSElliott Hughes                self,
1384*e1fe3e4aSElliott Hughes                subtable=type(out).__name__,
1385*e1fe3e4aSElliott Hughes                expected=expected,
1386*e1fe3e4aSElliott Hughes                got=got,
1387*e1fe3e4aSElliott Hughes            )
1388*e1fe3e4aSElliott Hughes        return expected
1389*e1fe3e4aSElliott Hughes
1390*e1fe3e4aSElliott Hughes    def mergeSparseDict(self, out, lst):
1391*e1fe3e4aSElliott Hughes        for k in out.keys():
1392*e1fe3e4aSElliott Hughes            try:
1393*e1fe3e4aSElliott Hughes                self.mergeThings(out[k], [v.get(k) for v in lst])
1394*e1fe3e4aSElliott Hughes            except VarLibMergeError as e:
1395*e1fe3e4aSElliott Hughes                e.stack.append(f"[{k!r}]")
1396*e1fe3e4aSElliott Hughes                raise
1397*e1fe3e4aSElliott Hughes
1398*e1fe3e4aSElliott Hughes    def mergeAttrs(self, out, lst, attrs):
1399*e1fe3e4aSElliott Hughes        for attr in attrs:
1400*e1fe3e4aSElliott Hughes            value = getattr(out, attr)
1401*e1fe3e4aSElliott Hughes            values = [getattr(item, attr) for item in lst]
1402*e1fe3e4aSElliott Hughes            try:
1403*e1fe3e4aSElliott Hughes                self.mergeThings(value, values)
1404*e1fe3e4aSElliott Hughes            except VarLibMergeError as e:
1405*e1fe3e4aSElliott Hughes                e.stack.append(f".{attr}")
1406*e1fe3e4aSElliott Hughes                raise
1407*e1fe3e4aSElliott Hughes
1408*e1fe3e4aSElliott Hughes    def storeMastersForAttr(self, out, lst, attr):
1409*e1fe3e4aSElliott Hughes        master_values = [getattr(item, attr) for item in lst]
1410*e1fe3e4aSElliott Hughes
1411*e1fe3e4aSElliott Hughes        # VarStore treats deltas for fixed-size floats as integers, so we
1412*e1fe3e4aSElliott Hughes        # must convert master values to int before storing them in the builder
1413*e1fe3e4aSElliott Hughes        # then back to float.
1414*e1fe3e4aSElliott Hughes        is_fixed_size_float = False
1415*e1fe3e4aSElliott Hughes        conv = out.getConverterByName(attr)
1416*e1fe3e4aSElliott Hughes        if isinstance(conv, BaseFixedValue):
1417*e1fe3e4aSElliott Hughes            is_fixed_size_float = True
1418*e1fe3e4aSElliott Hughes            master_values = [conv.toInt(v) for v in master_values]
1419*e1fe3e4aSElliott Hughes
1420*e1fe3e4aSElliott Hughes        baseValue = master_values[0]
1421*e1fe3e4aSElliott Hughes        varIdx = ot.NO_VARIATION_INDEX
1422*e1fe3e4aSElliott Hughes        if not allEqual(master_values):
1423*e1fe3e4aSElliott Hughes            baseValue, varIdx = self.store_builder.storeMasters(master_values)
1424*e1fe3e4aSElliott Hughes
1425*e1fe3e4aSElliott Hughes        if is_fixed_size_float:
1426*e1fe3e4aSElliott Hughes            baseValue = conv.fromInt(baseValue)
1427*e1fe3e4aSElliott Hughes
1428*e1fe3e4aSElliott Hughes        return baseValue, varIdx
1429*e1fe3e4aSElliott Hughes
1430*e1fe3e4aSElliott Hughes    def storeVariationIndices(self, varIdxes) -> int:
1431*e1fe3e4aSElliott Hughes        # try to reuse an existing VarIndexBase for the same varIdxes, or else
1432*e1fe3e4aSElliott Hughes        # create a new one
1433*e1fe3e4aSElliott Hughes        key = tuple(varIdxes)
1434*e1fe3e4aSElliott Hughes        varIndexBase = self.varIndexCache.get(key)
1435*e1fe3e4aSElliott Hughes
1436*e1fe3e4aSElliott Hughes        if varIndexBase is None:
1437*e1fe3e4aSElliott Hughes            # scan for a full match anywhere in the self.varIdxes
1438*e1fe3e4aSElliott Hughes            for i in range(len(self.varIdxes) - len(varIdxes) + 1):
1439*e1fe3e4aSElliott Hughes                if self.varIdxes[i : i + len(varIdxes)] == varIdxes:
1440*e1fe3e4aSElliott Hughes                    self.varIndexCache[key] = varIndexBase = i
1441*e1fe3e4aSElliott Hughes                    break
1442*e1fe3e4aSElliott Hughes
1443*e1fe3e4aSElliott Hughes        if varIndexBase is None:
1444*e1fe3e4aSElliott Hughes            # try find a partial match at the end of the self.varIdxes
1445*e1fe3e4aSElliott Hughes            for n in range(len(varIdxes) - 1, 0, -1):
1446*e1fe3e4aSElliott Hughes                if self.varIdxes[-n:] == varIdxes[:n]:
1447*e1fe3e4aSElliott Hughes                    varIndexBase = len(self.varIdxes) - n
1448*e1fe3e4aSElliott Hughes                    self.varIndexCache[key] = varIndexBase
1449*e1fe3e4aSElliott Hughes                    self.varIdxes.extend(varIdxes[n:])
1450*e1fe3e4aSElliott Hughes                    break
1451*e1fe3e4aSElliott Hughes
1452*e1fe3e4aSElliott Hughes        if varIndexBase is None:
1453*e1fe3e4aSElliott Hughes            # no match found, append at the end
1454*e1fe3e4aSElliott Hughes            self.varIndexCache[key] = varIndexBase = len(self.varIdxes)
1455*e1fe3e4aSElliott Hughes            self.varIdxes.extend(varIdxes)
1456*e1fe3e4aSElliott Hughes
1457*e1fe3e4aSElliott Hughes        return varIndexBase
1458*e1fe3e4aSElliott Hughes
1459*e1fe3e4aSElliott Hughes    def mergeVariableAttrs(self, out, lst, attrs) -> int:
1460*e1fe3e4aSElliott Hughes        varIndexBase = ot.NO_VARIATION_INDEX
1461*e1fe3e4aSElliott Hughes        varIdxes = []
1462*e1fe3e4aSElliott Hughes        for attr in attrs:
1463*e1fe3e4aSElliott Hughes            baseValue, varIdx = self.storeMastersForAttr(out, lst, attr)
1464*e1fe3e4aSElliott Hughes            setattr(out, attr, baseValue)
1465*e1fe3e4aSElliott Hughes            varIdxes.append(varIdx)
1466*e1fe3e4aSElliott Hughes
1467*e1fe3e4aSElliott Hughes        if any(v != ot.NO_VARIATION_INDEX for v in varIdxes):
1468*e1fe3e4aSElliott Hughes            varIndexBase = self.storeVariationIndices(varIdxes)
1469*e1fe3e4aSElliott Hughes
1470*e1fe3e4aSElliott Hughes        return varIndexBase
1471*e1fe3e4aSElliott Hughes
1472*e1fe3e4aSElliott Hughes    @classmethod
1473*e1fe3e4aSElliott Hughes    def convertSubTablesToVarType(cls, table):
1474*e1fe3e4aSElliott Hughes        for path in dfs_base_table(
1475*e1fe3e4aSElliott Hughes            table,
1476*e1fe3e4aSElliott Hughes            skip_root=True,
1477*e1fe3e4aSElliott Hughes            predicate=lambda path: (
1478*e1fe3e4aSElliott Hughes                getattr(type(path[-1].value), "VarType", None) is not None
1479*e1fe3e4aSElliott Hughes            ),
1480*e1fe3e4aSElliott Hughes        ):
1481*e1fe3e4aSElliott Hughes            st = path[-1]
1482*e1fe3e4aSElliott Hughes            subTable = st.value
1483*e1fe3e4aSElliott Hughes            varType = type(subTable).VarType
1484*e1fe3e4aSElliott Hughes            newSubTable = varType()
1485*e1fe3e4aSElliott Hughes            newSubTable.__dict__.update(subTable.__dict__)
1486*e1fe3e4aSElliott Hughes            newSubTable.populateDefaults()
1487*e1fe3e4aSElliott Hughes            parent = path[-2].value
1488*e1fe3e4aSElliott Hughes            if st.index is not None:
1489*e1fe3e4aSElliott Hughes                getattr(parent, st.name)[st.index] = newSubTable
1490*e1fe3e4aSElliott Hughes            else:
1491*e1fe3e4aSElliott Hughes                setattr(parent, st.name, newSubTable)
1492*e1fe3e4aSElliott Hughes
1493*e1fe3e4aSElliott Hughes    @staticmethod
1494*e1fe3e4aSElliott Hughes    def expandPaintColrLayers(colr):
1495*e1fe3e4aSElliott Hughes        """Rebuild LayerList without PaintColrLayers reuse.
1496*e1fe3e4aSElliott Hughes
1497*e1fe3e4aSElliott Hughes        Each base paint graph is fully DFS-traversed (with exception of PaintColrGlyph
1498*e1fe3e4aSElliott Hughes        which are irrelevant for this); any layers referenced via PaintColrLayers are
1499*e1fe3e4aSElliott Hughes        collected into a new LayerList and duplicated when reuse is detected, to ensure
1500*e1fe3e4aSElliott Hughes        that all paints are distinct objects at the end of the process.
1501*e1fe3e4aSElliott Hughes        PaintColrLayers's FirstLayerIndex/NumLayers are updated so that no overlap
1502*e1fe3e4aSElliott Hughes        is left. Also, any consecutively nested PaintColrLayers are flattened.
1503*e1fe3e4aSElliott Hughes        The COLR table's LayerList is replaced with the new unique layers.
1504*e1fe3e4aSElliott Hughes        A side effect is also that any layer from the old LayerList which is not
1505*e1fe3e4aSElliott Hughes        referenced by any PaintColrLayers is dropped.
1506*e1fe3e4aSElliott Hughes        """
1507*e1fe3e4aSElliott Hughes        if not colr.LayerList:
1508*e1fe3e4aSElliott Hughes            # if no LayerList, there's nothing to expand
1509*e1fe3e4aSElliott Hughes            return
1510*e1fe3e4aSElliott Hughes        uniqueLayerIDs = set()
1511*e1fe3e4aSElliott Hughes        newLayerList = []
1512*e1fe3e4aSElliott Hughes        for rec in colr.BaseGlyphList.BaseGlyphPaintRecord:
1513*e1fe3e4aSElliott Hughes            frontier = [rec.Paint]
1514*e1fe3e4aSElliott Hughes            while frontier:
1515*e1fe3e4aSElliott Hughes                paint = frontier.pop()
1516*e1fe3e4aSElliott Hughes                if paint.Format == ot.PaintFormat.PaintColrGlyph:
1517*e1fe3e4aSElliott Hughes                    # don't traverse these, we treat them as constant for merging
1518*e1fe3e4aSElliott Hughes                    continue
1519*e1fe3e4aSElliott Hughes                elif paint.Format == ot.PaintFormat.PaintColrLayers:
1520*e1fe3e4aSElliott Hughes                    # de-treeify any nested PaintColrLayers, append unique copies to
1521*e1fe3e4aSElliott Hughes                    # the new layer list and update PaintColrLayers index/count
1522*e1fe3e4aSElliott Hughes                    children = list(_flatten_layers(paint, colr))
1523*e1fe3e4aSElliott Hughes                    first_layer_index = len(newLayerList)
1524*e1fe3e4aSElliott Hughes                    for layer in children:
1525*e1fe3e4aSElliott Hughes                        if id(layer) in uniqueLayerIDs:
1526*e1fe3e4aSElliott Hughes                            layer = copy.deepcopy(layer)
1527*e1fe3e4aSElliott Hughes                            assert id(layer) not in uniqueLayerIDs
1528*e1fe3e4aSElliott Hughes                        newLayerList.append(layer)
1529*e1fe3e4aSElliott Hughes                        uniqueLayerIDs.add(id(layer))
1530*e1fe3e4aSElliott Hughes                    paint.FirstLayerIndex = first_layer_index
1531*e1fe3e4aSElliott Hughes                    paint.NumLayers = len(children)
1532*e1fe3e4aSElliott Hughes                else:
1533*e1fe3e4aSElliott Hughes                    children = paint.getChildren(colr)
1534*e1fe3e4aSElliott Hughes                frontier.extend(reversed(children))
1535*e1fe3e4aSElliott Hughes        # sanity check all the new layers are distinct objects
1536*e1fe3e4aSElliott Hughes        assert len(newLayerList) == len(uniqueLayerIDs)
1537*e1fe3e4aSElliott Hughes        colr.LayerList.Paint = newLayerList
1538*e1fe3e4aSElliott Hughes        colr.LayerList.LayerCount = len(newLayerList)
1539*e1fe3e4aSElliott Hughes
1540*e1fe3e4aSElliott Hughes
1541*e1fe3e4aSElliott Hughes@COLRVariationMerger.merger(ot.BaseGlyphList)
1542*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1543*e1fe3e4aSElliott Hughes    # ignore BaseGlyphCount, allow sparse glyph sets across masters
1544*e1fe3e4aSElliott Hughes    out = {rec.BaseGlyph: rec for rec in self.BaseGlyphPaintRecord}
1545*e1fe3e4aSElliott Hughes    masters = [{rec.BaseGlyph: rec for rec in m.BaseGlyphPaintRecord} for m in lst]
1546*e1fe3e4aSElliott Hughes
1547*e1fe3e4aSElliott Hughes    for i, g in enumerate(out.keys()):
1548*e1fe3e4aSElliott Hughes        try:
1549*e1fe3e4aSElliott Hughes            # missing base glyphs don't participate in the merge
1550*e1fe3e4aSElliott Hughes            merger.mergeThings(out[g], [v.get(g) for v in masters])
1551*e1fe3e4aSElliott Hughes        except VarLibMergeError as e:
1552*e1fe3e4aSElliott Hughes            e.stack.append(f".BaseGlyphPaintRecord[{i}]")
1553*e1fe3e4aSElliott Hughes            e.cause["location"] = f"base glyph {g!r}"
1554*e1fe3e4aSElliott Hughes            raise
1555*e1fe3e4aSElliott Hughes
1556*e1fe3e4aSElliott Hughes    merger._doneBaseGlyphs = True
1557*e1fe3e4aSElliott Hughes
1558*e1fe3e4aSElliott Hughes
1559*e1fe3e4aSElliott Hughes@COLRVariationMerger.merger(ot.LayerList)
1560*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1561*e1fe3e4aSElliott Hughes    # nothing to merge for LayerList, assuming we have already merged all PaintColrLayers
1562*e1fe3e4aSElliott Hughes    # found while traversing the paint graphs rooted at BaseGlyphPaintRecords.
1563*e1fe3e4aSElliott Hughes    assert merger._doneBaseGlyphs, "BaseGlyphList must be merged before LayerList"
1564*e1fe3e4aSElliott Hughes    # Simply flush the final list of layers and go home.
1565*e1fe3e4aSElliott Hughes    self.LayerCount = len(merger.layers)
1566*e1fe3e4aSElliott Hughes    self.Paint = merger.layers
1567*e1fe3e4aSElliott Hughes
1568*e1fe3e4aSElliott Hughes
1569*e1fe3e4aSElliott Hughesdef _flatten_layers(root, colr):
1570*e1fe3e4aSElliott Hughes    assert root.Format == ot.PaintFormat.PaintColrLayers
1571*e1fe3e4aSElliott Hughes    for paint in root.getChildren(colr):
1572*e1fe3e4aSElliott Hughes        if paint.Format == ot.PaintFormat.PaintColrLayers:
1573*e1fe3e4aSElliott Hughes            yield from _flatten_layers(paint, colr)
1574*e1fe3e4aSElliott Hughes        else:
1575*e1fe3e4aSElliott Hughes            yield paint
1576*e1fe3e4aSElliott Hughes
1577*e1fe3e4aSElliott Hughes
1578*e1fe3e4aSElliott Hughesdef _merge_PaintColrLayers(self, out, lst):
1579*e1fe3e4aSElliott Hughes    # we only enforce that the (flat) number of layers is the same across all masters
1580*e1fe3e4aSElliott Hughes    # but we allow FirstLayerIndex to differ to acommodate for sparse glyph sets.
1581*e1fe3e4aSElliott Hughes
1582*e1fe3e4aSElliott Hughes    out_layers = list(_flatten_layers(out, self.font["COLR"].table))
1583*e1fe3e4aSElliott Hughes
1584*e1fe3e4aSElliott Hughes    # sanity check ttfs are subset to current values (see VariationMerger.mergeThings)
1585*e1fe3e4aSElliott Hughes    # before matching each master PaintColrLayers to its respective COLR by position
1586*e1fe3e4aSElliott Hughes    assert len(self.ttfs) == len(lst)
1587*e1fe3e4aSElliott Hughes    master_layerses = [
1588*e1fe3e4aSElliott Hughes        list(_flatten_layers(lst[i], self.ttfs[i]["COLR"].table))
1589*e1fe3e4aSElliott Hughes        for i in range(len(lst))
1590*e1fe3e4aSElliott Hughes    ]
1591*e1fe3e4aSElliott Hughes
1592*e1fe3e4aSElliott Hughes    try:
1593*e1fe3e4aSElliott Hughes        self.mergeLists(out_layers, master_layerses)
1594*e1fe3e4aSElliott Hughes    except VarLibMergeError as e:
1595*e1fe3e4aSElliott Hughes        # NOTE: This attribute doesn't actually exist in PaintColrLayers but it's
1596*e1fe3e4aSElliott Hughes        # handy to have it in the stack trace for debugging.
1597*e1fe3e4aSElliott Hughes        e.stack.append(".Layers")
1598*e1fe3e4aSElliott Hughes        raise
1599*e1fe3e4aSElliott Hughes
1600*e1fe3e4aSElliott Hughes    # following block is very similar to LayerListBuilder._beforeBuildPaintColrLayers
1601*e1fe3e4aSElliott Hughes    # but I couldn't find a nice way to share the code between the two...
1602*e1fe3e4aSElliott Hughes
1603*e1fe3e4aSElliott Hughes    if self.layerReuseCache is not None:
1604*e1fe3e4aSElliott Hughes        # successful reuse can make the list smaller
1605*e1fe3e4aSElliott Hughes        out_layers = self.layerReuseCache.try_reuse(out_layers)
1606*e1fe3e4aSElliott Hughes
1607*e1fe3e4aSElliott Hughes    # if the list is still too big we need to tree-fy it
1608*e1fe3e4aSElliott Hughes    is_tree = len(out_layers) > MAX_PAINT_COLR_LAYER_COUNT
1609*e1fe3e4aSElliott Hughes    out_layers = build_n_ary_tree(out_layers, n=MAX_PAINT_COLR_LAYER_COUNT)
1610*e1fe3e4aSElliott Hughes
1611*e1fe3e4aSElliott Hughes    # We now have a tree of sequences with Paint leaves.
1612*e1fe3e4aSElliott Hughes    # Convert the sequences into PaintColrLayers.
1613*e1fe3e4aSElliott Hughes    def listToColrLayers(paint):
1614*e1fe3e4aSElliott Hughes        if isinstance(paint, list):
1615*e1fe3e4aSElliott Hughes            layers = [listToColrLayers(l) for l in paint]
1616*e1fe3e4aSElliott Hughes            paint = ot.Paint()
1617*e1fe3e4aSElliott Hughes            paint.Format = int(ot.PaintFormat.PaintColrLayers)
1618*e1fe3e4aSElliott Hughes            paint.NumLayers = len(layers)
1619*e1fe3e4aSElliott Hughes            paint.FirstLayerIndex = len(self.layers)
1620*e1fe3e4aSElliott Hughes            self.layers.extend(layers)
1621*e1fe3e4aSElliott Hughes            if self.layerReuseCache is not None:
1622*e1fe3e4aSElliott Hughes                self.layerReuseCache.add(layers, paint.FirstLayerIndex)
1623*e1fe3e4aSElliott Hughes        return paint
1624*e1fe3e4aSElliott Hughes
1625*e1fe3e4aSElliott Hughes    out_layers = [listToColrLayers(l) for l in out_layers]
1626*e1fe3e4aSElliott Hughes
1627*e1fe3e4aSElliott Hughes    if len(out_layers) == 1 and out_layers[0].Format == ot.PaintFormat.PaintColrLayers:
1628*e1fe3e4aSElliott Hughes        # special case when the reuse cache finds a single perfect PaintColrLayers match
1629*e1fe3e4aSElliott Hughes        # (it can only come from a successful reuse, _flatten_layers has gotten rid of
1630*e1fe3e4aSElliott Hughes        # all nested PaintColrLayers already); we assign it directly and avoid creating
1631*e1fe3e4aSElliott Hughes        # an extra table
1632*e1fe3e4aSElliott Hughes        out.NumLayers = out_layers[0].NumLayers
1633*e1fe3e4aSElliott Hughes        out.FirstLayerIndex = out_layers[0].FirstLayerIndex
1634*e1fe3e4aSElliott Hughes    else:
1635*e1fe3e4aSElliott Hughes        out.NumLayers = len(out_layers)
1636*e1fe3e4aSElliott Hughes        out.FirstLayerIndex = len(self.layers)
1637*e1fe3e4aSElliott Hughes
1638*e1fe3e4aSElliott Hughes        self.layers.extend(out_layers)
1639*e1fe3e4aSElliott Hughes
1640*e1fe3e4aSElliott Hughes        # Register our parts for reuse provided we aren't a tree
1641*e1fe3e4aSElliott Hughes        # If we are a tree the leaves registered for reuse and that will suffice
1642*e1fe3e4aSElliott Hughes        if self.layerReuseCache is not None and not is_tree:
1643*e1fe3e4aSElliott Hughes            self.layerReuseCache.add(out_layers, out.FirstLayerIndex)
1644*e1fe3e4aSElliott Hughes
1645*e1fe3e4aSElliott Hughes
1646*e1fe3e4aSElliott Hughes@COLRVariationMerger.merger((ot.Paint, ot.ClipBox))
1647*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1648*e1fe3e4aSElliott Hughes    fmt = merger.checkFormatEnum(self, lst, lambda fmt: not fmt.is_variable())
1649*e1fe3e4aSElliott Hughes
1650*e1fe3e4aSElliott Hughes    if fmt is ot.PaintFormat.PaintColrLayers:
1651*e1fe3e4aSElliott Hughes        _merge_PaintColrLayers(merger, self, lst)
1652*e1fe3e4aSElliott Hughes        return
1653*e1fe3e4aSElliott Hughes
1654*e1fe3e4aSElliott Hughes    varFormat = fmt.as_variable()
1655*e1fe3e4aSElliott Hughes
1656*e1fe3e4aSElliott Hughes    varAttrs = ()
1657*e1fe3e4aSElliott Hughes    if varFormat is not None:
1658*e1fe3e4aSElliott Hughes        varAttrs = otBase.getVariableAttrs(type(self), varFormat)
1659*e1fe3e4aSElliott Hughes    staticAttrs = (c.name for c in self.getConverters() if c.name not in varAttrs)
1660*e1fe3e4aSElliott Hughes
1661*e1fe3e4aSElliott Hughes    merger.mergeAttrs(self, lst, staticAttrs)
1662*e1fe3e4aSElliott Hughes
1663*e1fe3e4aSElliott Hughes    varIndexBase = merger.mergeVariableAttrs(self, lst, varAttrs)
1664*e1fe3e4aSElliott Hughes
1665*e1fe3e4aSElliott Hughes    subTables = [st.value for st in self.iterSubTables()]
1666*e1fe3e4aSElliott Hughes
1667*e1fe3e4aSElliott Hughes    # Convert table to variable if itself has variations or any subtables have
1668*e1fe3e4aSElliott Hughes    isVariable = varIndexBase != ot.NO_VARIATION_INDEX or any(
1669*e1fe3e4aSElliott Hughes        id(table) in merger.varTableIds for table in subTables
1670*e1fe3e4aSElliott Hughes    )
1671*e1fe3e4aSElliott Hughes
1672*e1fe3e4aSElliott Hughes    if isVariable:
1673*e1fe3e4aSElliott Hughes        if varAttrs:
1674*e1fe3e4aSElliott Hughes            # Some PaintVar* don't have any scalar attributes that can vary,
1675*e1fe3e4aSElliott Hughes            # only indirect offsets to other variable subtables, thus have
1676*e1fe3e4aSElliott Hughes            # no VarIndexBase of their own (e.g. PaintVarTransform)
1677*e1fe3e4aSElliott Hughes            self.VarIndexBase = varIndexBase
1678*e1fe3e4aSElliott Hughes
1679*e1fe3e4aSElliott Hughes        if subTables:
1680*e1fe3e4aSElliott Hughes            # Convert Affine2x3 -> VarAffine2x3, ColorLine -> VarColorLine, etc.
1681*e1fe3e4aSElliott Hughes            merger.convertSubTablesToVarType(self)
1682*e1fe3e4aSElliott Hughes
1683*e1fe3e4aSElliott Hughes        assert varFormat is not None
1684*e1fe3e4aSElliott Hughes        self.Format = int(varFormat)
1685*e1fe3e4aSElliott Hughes
1686*e1fe3e4aSElliott Hughes
1687*e1fe3e4aSElliott Hughes@COLRVariationMerger.merger((ot.Affine2x3, ot.ColorStop))
1688*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1689*e1fe3e4aSElliott Hughes    varType = type(self).VarType
1690*e1fe3e4aSElliott Hughes
1691*e1fe3e4aSElliott Hughes    varAttrs = otBase.getVariableAttrs(varType)
1692*e1fe3e4aSElliott Hughes    staticAttrs = (c.name for c in self.getConverters() if c.name not in varAttrs)
1693*e1fe3e4aSElliott Hughes
1694*e1fe3e4aSElliott Hughes    merger.mergeAttrs(self, lst, staticAttrs)
1695*e1fe3e4aSElliott Hughes
1696*e1fe3e4aSElliott Hughes    varIndexBase = merger.mergeVariableAttrs(self, lst, varAttrs)
1697*e1fe3e4aSElliott Hughes
1698*e1fe3e4aSElliott Hughes    if varIndexBase != ot.NO_VARIATION_INDEX:
1699*e1fe3e4aSElliott Hughes        self.VarIndexBase = varIndexBase
1700*e1fe3e4aSElliott Hughes        # mark as having variations so the parent table will convert to Var{Type}
1701*e1fe3e4aSElliott Hughes        merger.varTableIds.add(id(self))
1702*e1fe3e4aSElliott Hughes
1703*e1fe3e4aSElliott Hughes
1704*e1fe3e4aSElliott Hughes@COLRVariationMerger.merger(ot.ColorLine)
1705*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1706*e1fe3e4aSElliott Hughes    merger.mergeAttrs(self, lst, (c.name for c in self.getConverters()))
1707*e1fe3e4aSElliott Hughes
1708*e1fe3e4aSElliott Hughes    if any(id(stop) in merger.varTableIds for stop in self.ColorStop):
1709*e1fe3e4aSElliott Hughes        merger.convertSubTablesToVarType(self)
1710*e1fe3e4aSElliott Hughes        merger.varTableIds.add(id(self))
1711*e1fe3e4aSElliott Hughes
1712*e1fe3e4aSElliott Hughes
1713*e1fe3e4aSElliott Hughes@COLRVariationMerger.merger(ot.ClipList, "clips")
1714*e1fe3e4aSElliott Hughesdef merge(merger, self, lst):
1715*e1fe3e4aSElliott Hughes    # 'sparse' in that we allow non-default masters to omit ClipBox entries
1716*e1fe3e4aSElliott Hughes    # for some/all glyphs (i.e. they don't participate)
1717*e1fe3e4aSElliott Hughes    merger.mergeSparseDict(self, lst)
1718