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