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