1""" 2Merge OpenType Layout tables (GDEF / GPOS / GSUB). 3""" 4import os 5import copy 6from operator import ior 7import logging 8from fontTools.misc import classifyTools 9from fontTools.misc.roundTools import otRound 10from fontTools.ttLib.tables import otTables as ot 11from fontTools.ttLib.tables import otBase as otBase 12from fontTools.ttLib.tables.DefaultTable import DefaultTable 13from fontTools.varLib import builder, models, varStore 14from fontTools.varLib.models import nonNone, allNone, allEqual, allEqualTo 15from fontTools.varLib.varStore import VarStoreInstancer 16from functools import reduce 17from fontTools.otlLib.builder import buildSinglePos 18from fontTools.otlLib.optimize.gpos import ( 19 compact_pair_pos, 20 GPOS_COMPACT_MODE_DEFAULT, 21 GPOS_COMPACT_MODE_ENV_KEY, 22) 23 24log = logging.getLogger("fontTools.varLib.merger") 25 26from .errors import ( 27 ShouldBeConstant, 28 FoundANone, 29 MismatchedTypes, 30 LengthsDiffer, 31 KeysDiffer, 32 InconsistentGlyphOrder, 33 InconsistentExtensions, 34 UnsupportedFormat, 35 UnsupportedFormat, 36 VarLibMergeError, 37) 38 39class Merger(object): 40 41 def __init__(self, font=None): 42 self.font = font 43 44 @classmethod 45 def merger(celf, clazzes, attrs=(None,)): 46 assert celf != Merger, 'Subclass Merger instead.' 47 if 'mergers' not in celf.__dict__: 48 celf.mergers = {} 49 if type(clazzes) == type: 50 clazzes = (clazzes,) 51 if type(attrs) == str: 52 attrs = (attrs,) 53 def wrapper(method): 54 assert method.__name__ == 'merge' 55 done = [] 56 for clazz in clazzes: 57 if clazz in done: continue # Support multiple names of a clazz 58 done.append(clazz) 59 mergers = celf.mergers.setdefault(clazz, {}) 60 for attr in attrs: 61 assert attr not in mergers, \ 62 "Oops, class '%s' has merge function for '%s' defined already." % (clazz.__name__, attr) 63 mergers[attr] = method 64 return None 65 return wrapper 66 67 @classmethod 68 def mergersFor(celf, thing, _default={}): 69 typ = type(thing) 70 71 for celf in celf.mro(): 72 73 mergers = getattr(celf, 'mergers', None) 74 if mergers is None: 75 break; 76 77 m = celf.mergers.get(typ, None) 78 if m is not None: 79 return m 80 81 return _default 82 83 def mergeObjects(self, out, lst, exclude=()): 84 if hasattr(out, "ensureDecompiled"): 85 out.ensureDecompiled() 86 for item in lst: 87 if hasattr(item, "ensureDecompiled"): 88 item.ensureDecompiled() 89 keys = sorted(vars(out).keys()) 90 if not all(keys == sorted(vars(v).keys()) for v in lst): 91 raise KeysDiffer(self, expected=keys, 92 got=[sorted(vars(v).keys()) for v in lst] 93 ) 94 mergers = self.mergersFor(out) 95 defaultMerger = mergers.get('*', self.__class__.mergeThings) 96 try: 97 for key in keys: 98 if key in exclude: continue 99 value = getattr(out, key) 100 values = [getattr(table, key) for table in lst] 101 mergerFunc = mergers.get(key, defaultMerger) 102 mergerFunc(self, value, values) 103 except VarLibMergeError as e: 104 e.stack.append('.'+key) 105 raise 106 107 def mergeLists(self, out, lst): 108 if not allEqualTo(out, lst, len): 109 raise LengthsDiffer(self, expected=len(out), got=[len(x) for x in lst]) 110 for i,(value,values) in enumerate(zip(out, zip(*lst))): 111 try: 112 self.mergeThings(value, values) 113 except VarLibMergeError as e: 114 e.stack.append('[%d]' % i) 115 raise 116 117 def mergeThings(self, out, lst): 118 if not allEqualTo(out, lst, type): 119 raise MismatchedTypes(self, 120 expected=type(out).__name__, 121 got=[type(x).__name__ for x in lst] 122 ) 123 mergerFunc = self.mergersFor(out).get(None, None) 124 if mergerFunc is not None: 125 mergerFunc(self, out, lst) 126 elif hasattr(out, '__dict__'): 127 self.mergeObjects(out, lst) 128 elif isinstance(out, list): 129 self.mergeLists(out, lst) 130 else: 131 if not allEqualTo(out, lst): 132 raise ShouldBeConstant(self, expected=out, got=lst) 133 134 def mergeTables(self, font, master_ttfs, tableTags): 135 for tag in tableTags: 136 if tag not in font: continue 137 try: 138 self.ttfs = [m for m in master_ttfs if tag in m] 139 self.mergeThings(font[tag], [m[tag] if tag in m else None 140 for m in master_ttfs]) 141 except VarLibMergeError as e: 142 e.stack.append(tag) 143 raise 144 145# 146# Aligning merger 147# 148class AligningMerger(Merger): 149 pass 150 151@AligningMerger.merger(ot.GDEF, "GlyphClassDef") 152def merge(merger, self, lst): 153 if self is None: 154 if not allNone(lst): 155 raise NotANone(merger, expected=None, got=lst) 156 return 157 158 lst = [l.classDefs for l in lst] 159 self.classDefs = {} 160 # We only care about the .classDefs 161 self = self.classDefs 162 163 allKeys = set() 164 allKeys.update(*[l.keys() for l in lst]) 165 for k in allKeys: 166 allValues = nonNone(l.get(k) for l in lst) 167 if not allEqual(allValues): 168 raise ShouldBeConstant(merger, expected=allValues[0], got=lst, stack=["." + k]) 169 if not allValues: 170 self[k] = None 171 else: 172 self[k] = allValues[0] 173 174def _SinglePosUpgradeToFormat2(self): 175 if self.Format == 2: return self 176 177 ret = ot.SinglePos() 178 ret.Format = 2 179 ret.Coverage = self.Coverage 180 ret.ValueFormat = self.ValueFormat 181 ret.Value = [self.Value for _ in ret.Coverage.glyphs] 182 ret.ValueCount = len(ret.Value) 183 184 return ret 185 186def _merge_GlyphOrders(font, lst, values_lst=None, default=None): 187 """Takes font and list of glyph lists (must be sorted by glyph id), and returns 188 two things: 189 - Combined glyph list, 190 - If values_lst is None, return input glyph lists, but padded with None when a glyph 191 was missing in a list. Otherwise, return values_lst list-of-list, padded with None 192 to match combined glyph lists. 193 """ 194 if values_lst is None: 195 dict_sets = [set(l) for l in lst] 196 else: 197 dict_sets = [{g:v for g,v in zip(l,vs)} for l,vs in zip(lst,values_lst)] 198 combined = set() 199 combined.update(*dict_sets) 200 201 sortKey = font.getReverseGlyphMap().__getitem__ 202 order = sorted(combined, key=sortKey) 203 # Make sure all input glyphsets were in proper order 204 if not all(sorted(vs, key=sortKey) == vs for vs in lst): 205 raise InconsistentGlyphOrder() 206 del combined 207 208 paddedValues = None 209 if values_lst is None: 210 padded = [[glyph if glyph in dict_set else default 211 for glyph in order] 212 for dict_set in dict_sets] 213 else: 214 assert len(lst) == len(values_lst) 215 padded = [[dict_set[glyph] if glyph in dict_set else default 216 for glyph in order] 217 for dict_set in dict_sets] 218 return order, padded 219 220def _Lookup_SinglePos_get_effective_value(merger, subtables, glyph): 221 for self in subtables: 222 if self is None or \ 223 type(self) != ot.SinglePos or \ 224 self.Coverage is None or \ 225 glyph not in self.Coverage.glyphs: 226 continue 227 if self.Format == 1: 228 return self.Value 229 elif self.Format == 2: 230 return self.Value[self.Coverage.glyphs.index(glyph)] 231 else: 232 raise UnsupportedFormat(merger, subtable="single positioning lookup") 233 return None 234 235def _Lookup_PairPos_get_effective_value_pair(merger, subtables, firstGlyph, secondGlyph): 236 for self in subtables: 237 if self is None or \ 238 type(self) != ot.PairPos or \ 239 self.Coverage is None or \ 240 firstGlyph not in self.Coverage.glyphs: 241 continue 242 if self.Format == 1: 243 ps = self.PairSet[self.Coverage.glyphs.index(firstGlyph)] 244 pvr = ps.PairValueRecord 245 for rec in pvr: # TODO Speed up 246 if rec.SecondGlyph == secondGlyph: 247 return rec 248 continue 249 elif self.Format == 2: 250 klass1 = self.ClassDef1.classDefs.get(firstGlyph, 0) 251 klass2 = self.ClassDef2.classDefs.get(secondGlyph, 0) 252 return self.Class1Record[klass1].Class2Record[klass2] 253 else: 254 raise UnsupportedFormat(merger, subtable="pair positioning lookup") 255 return None 256 257@AligningMerger.merger(ot.SinglePos) 258def merge(merger, self, lst): 259 self.ValueFormat = valueFormat = reduce(int.__or__, [l.ValueFormat for l in lst], 0) 260 if not (len(lst) == 1 or (valueFormat & ~0xF == 0)): 261 raise UnsupportedFormat(merger, subtable="single positioning lookup") 262 263 # If all have same coverage table and all are format 1, 264 coverageGlyphs = self.Coverage.glyphs 265 if all(v.Format == 1 for v in lst) and all(coverageGlyphs == v.Coverage.glyphs for v in lst): 266 self.Value = otBase.ValueRecord(valueFormat, self.Value) 267 if valueFormat != 0: 268 merger.mergeThings(self.Value, [v.Value for v in lst]) 269 self.ValueFormat = self.Value.getFormat() 270 return 271 272 # Upgrade everything to Format=2 273 self.Format = 2 274 lst = [_SinglePosUpgradeToFormat2(v) for v in lst] 275 276 # Align them 277 glyphs, padded = _merge_GlyphOrders(merger.font, 278 [v.Coverage.glyphs for v in lst], 279 [v.Value for v in lst]) 280 281 self.Coverage.glyphs = glyphs 282 self.Value = [otBase.ValueRecord(valueFormat) for _ in glyphs] 283 self.ValueCount = len(self.Value) 284 285 for i,values in enumerate(padded): 286 for j,glyph in enumerate(glyphs): 287 if values[j] is not None: continue 288 # Fill in value from other subtables 289 # Note!!! This *might* result in behavior change if ValueFormat2-zeroedness 290 # is different between used subtable and current subtable! 291 # TODO(behdad) Check and warn if that happens? 292 v = _Lookup_SinglePos_get_effective_value(merger, merger.lookup_subtables[i], glyph) 293 if v is None: 294 v = otBase.ValueRecord(valueFormat) 295 values[j] = v 296 297 merger.mergeLists(self.Value, padded) 298 299 # Merge everything else; though, there shouldn't be anything else. :) 300 merger.mergeObjects(self, lst, 301 exclude=('Format', 'Coverage', 'Value', 'ValueCount', 'ValueFormat')) 302 self.ValueFormat = reduce(int.__or__, [v.getEffectiveFormat() for v in self.Value], 0) 303 304@AligningMerger.merger(ot.PairSet) 305def merge(merger, self, lst): 306 # Align them 307 glyphs, padded = _merge_GlyphOrders(merger.font, 308 [[v.SecondGlyph for v in vs.PairValueRecord] for vs in lst], 309 [vs.PairValueRecord for vs in lst]) 310 311 self.PairValueRecord = pvrs = [] 312 for glyph in glyphs: 313 pvr = ot.PairValueRecord() 314 pvr.SecondGlyph = glyph 315 pvr.Value1 = otBase.ValueRecord(merger.valueFormat1) if merger.valueFormat1 else None 316 pvr.Value2 = otBase.ValueRecord(merger.valueFormat2) if merger.valueFormat2 else None 317 pvrs.append(pvr) 318 self.PairValueCount = len(self.PairValueRecord) 319 320 for i,values in enumerate(padded): 321 for j,glyph in enumerate(glyphs): 322 # Fill in value from other subtables 323 v = ot.PairValueRecord() 324 v.SecondGlyph = glyph 325 if values[j] is not None: 326 vpair = values[j] 327 else: 328 vpair = _Lookup_PairPos_get_effective_value_pair( 329 merger, merger.lookup_subtables[i], self._firstGlyph, glyph 330 ) 331 if vpair is None: 332 v1, v2 = None, None 333 else: 334 v1 = getattr(vpair, "Value1", None) 335 v2 = getattr(vpair, "Value2", None) 336 v.Value1 = otBase.ValueRecord(merger.valueFormat1, src=v1) if merger.valueFormat1 else None 337 v.Value2 = otBase.ValueRecord(merger.valueFormat2, src=v2) if merger.valueFormat2 else None 338 values[j] = v 339 del self._firstGlyph 340 341 merger.mergeLists(self.PairValueRecord, padded) 342 343def _PairPosFormat1_merge(self, lst, merger): 344 assert allEqual([l.ValueFormat2 == 0 for l in lst if l.PairSet]), "Report bug against fonttools." 345 346 # Merge everything else; makes sure Format is the same. 347 merger.mergeObjects(self, lst, 348 exclude=('Coverage', 349 'PairSet', 'PairSetCount', 350 'ValueFormat1', 'ValueFormat2')) 351 352 empty = ot.PairSet() 353 empty.PairValueRecord = [] 354 empty.PairValueCount = 0 355 356 # Align them 357 glyphs, padded = _merge_GlyphOrders(merger.font, 358 [v.Coverage.glyphs for v in lst], 359 [v.PairSet for v in lst], 360 default=empty) 361 362 self.Coverage.glyphs = glyphs 363 self.PairSet = [ot.PairSet() for _ in glyphs] 364 self.PairSetCount = len(self.PairSet) 365 for glyph, ps in zip(glyphs, self.PairSet): 366 ps._firstGlyph = glyph 367 368 merger.mergeLists(self.PairSet, padded) 369 370def _ClassDef_invert(self, allGlyphs=None): 371 372 if isinstance(self, dict): 373 classDefs = self 374 else: 375 classDefs = self.classDefs if self and self.classDefs else {} 376 m = max(classDefs.values()) if classDefs else 0 377 378 ret = [] 379 for _ in range(m + 1): 380 ret.append(set()) 381 382 for k,v in classDefs.items(): 383 ret[v].add(k) 384 385 # Class-0 is special. It's "everything else". 386 if allGlyphs is None: 387 ret[0] = None 388 else: 389 # Limit all classes to glyphs in allGlyphs. 390 # Collect anything without a non-zero class into class=zero. 391 ret[0] = class0 = set(allGlyphs) 392 for s in ret[1:]: 393 s.intersection_update(class0) 394 class0.difference_update(s) 395 396 return ret 397 398def _ClassDef_merge_classify(lst, allGlyphses=None): 399 self = ot.ClassDef() 400 self.classDefs = classDefs = {} 401 allGlyphsesWasNone = allGlyphses is None 402 if allGlyphsesWasNone: 403 allGlyphses = [None] * len(lst) 404 405 classifier = classifyTools.Classifier() 406 for classDef,allGlyphs in zip(lst, allGlyphses): 407 sets = _ClassDef_invert(classDef, allGlyphs) 408 if allGlyphs is None: 409 sets = sets[1:] 410 classifier.update(sets) 411 classes = classifier.getClasses() 412 413 if allGlyphsesWasNone: 414 classes.insert(0, set()) 415 416 for i,classSet in enumerate(classes): 417 if i == 0: 418 continue 419 for g in classSet: 420 classDefs[g] = i 421 422 return self, classes 423 424def _PairPosFormat2_align_matrices(self, lst, font, transparent=False): 425 426 matrices = [l.Class1Record for l in lst] 427 428 # Align first classes 429 self.ClassDef1, classes = _ClassDef_merge_classify([l.ClassDef1 for l in lst], [l.Coverage.glyphs for l in lst]) 430 self.Class1Count = len(classes) 431 new_matrices = [] 432 for l,matrix in zip(lst, matrices): 433 nullRow = None 434 coverage = set(l.Coverage.glyphs) 435 classDef1 = l.ClassDef1.classDefs 436 class1Records = [] 437 for classSet in classes: 438 exemplarGlyph = next(iter(classSet)) 439 if exemplarGlyph not in coverage: 440 # Follow-up to e6125b353e1f54a0280ded5434b8e40d042de69f, 441 # Fixes https://github.com/googlei18n/fontmake/issues/470 442 # Again, revert 8d441779e5afc664960d848f62c7acdbfc71d7b9 443 # when merger becomes selfless. 444 nullRow = None 445 if nullRow is None: 446 nullRow = ot.Class1Record() 447 class2records = nullRow.Class2Record = [] 448 # TODO: When merger becomes selfless, revert e6125b353e1f54a0280ded5434b8e40d042de69f 449 for _ in range(l.Class2Count): 450 if transparent: 451 rec2 = None 452 else: 453 rec2 = ot.Class2Record() 454 rec2.Value1 = otBase.ValueRecord(self.ValueFormat1) if self.ValueFormat1 else None 455 rec2.Value2 = otBase.ValueRecord(self.ValueFormat2) if self.ValueFormat2 else None 456 class2records.append(rec2) 457 rec1 = nullRow 458 else: 459 klass = classDef1.get(exemplarGlyph, 0) 460 rec1 = matrix[klass] # TODO handle out-of-range? 461 class1Records.append(rec1) 462 new_matrices.append(class1Records) 463 matrices = new_matrices 464 del new_matrices 465 466 # Align second classes 467 self.ClassDef2, classes = _ClassDef_merge_classify([l.ClassDef2 for l in lst]) 468 self.Class2Count = len(classes) 469 new_matrices = [] 470 for l,matrix in zip(lst, matrices): 471 classDef2 = l.ClassDef2.classDefs 472 class1Records = [] 473 for rec1old in matrix: 474 oldClass2Records = rec1old.Class2Record 475 rec1new = ot.Class1Record() 476 class2Records = rec1new.Class2Record = [] 477 for classSet in classes: 478 if not classSet: # class=0 479 rec2 = oldClass2Records[0] 480 else: 481 exemplarGlyph = next(iter(classSet)) 482 klass = classDef2.get(exemplarGlyph, 0) 483 rec2 = oldClass2Records[klass] 484 class2Records.append(copy.deepcopy(rec2)) 485 class1Records.append(rec1new) 486 new_matrices.append(class1Records) 487 matrices = new_matrices 488 del new_matrices 489 490 return matrices 491 492def _PairPosFormat2_merge(self, lst, merger): 493 assert allEqual([l.ValueFormat2 == 0 for l in lst if l.Class1Record]), "Report bug against fonttools." 494 495 merger.mergeObjects(self, lst, 496 exclude=('Coverage', 497 'ClassDef1', 'Class1Count', 498 'ClassDef2', 'Class2Count', 499 'Class1Record', 500 'ValueFormat1', 'ValueFormat2')) 501 502 # Align coverages 503 glyphs, _ = _merge_GlyphOrders(merger.font, 504 [v.Coverage.glyphs for v in lst]) 505 self.Coverage.glyphs = glyphs 506 507 # Currently, if the coverage of PairPosFormat2 subtables are different, 508 # we do NOT bother walking down the subtable list when filling in new 509 # rows for alignment. As such, this is only correct if current subtable 510 # is the last subtable in the lookup. Ensure that. 511 # 512 # Note that our canonicalization process merges trailing PairPosFormat2's, 513 # so in reality this is rare. 514 for l,subtables in zip(lst,merger.lookup_subtables): 515 if l.Coverage.glyphs != glyphs: 516 assert l == subtables[-1] 517 518 matrices = _PairPosFormat2_align_matrices(self, lst, merger.font) 519 520 self.Class1Record = list(matrices[0]) # TODO move merger to be selfless 521 merger.mergeLists(self.Class1Record, matrices) 522 523@AligningMerger.merger(ot.PairPos) 524def merge(merger, self, lst): 525 merger.valueFormat1 = self.ValueFormat1 = reduce(int.__or__, [l.ValueFormat1 for l in lst], 0) 526 merger.valueFormat2 = self.ValueFormat2 = reduce(int.__or__, [l.ValueFormat2 for l in lst], 0) 527 528 if self.Format == 1: 529 _PairPosFormat1_merge(self, lst, merger) 530 elif self.Format == 2: 531 _PairPosFormat2_merge(self, lst, merger) 532 else: 533 raise UnsupportedFormat(merger, subtable="pair positioning lookup") 534 535 del merger.valueFormat1, merger.valueFormat2 536 537 # Now examine the list of value records, and update to the union of format values, 538 # as merge might have created new values. 539 vf1 = 0 540 vf2 = 0 541 if self.Format == 1: 542 for pairSet in self.PairSet: 543 for pairValueRecord in pairSet.PairValueRecord: 544 pv1 = getattr(pairValueRecord, "Value1", None) 545 if pv1 is not None: 546 vf1 |= pv1.getFormat() 547 pv2 = getattr(pairValueRecord, "Value2", None) 548 if pv2 is not None: 549 vf2 |= pv2.getFormat() 550 elif self.Format == 2: 551 for class1Record in self.Class1Record: 552 for class2Record in class1Record.Class2Record: 553 pv1 = getattr(class2Record, "Value1", None) 554 if pv1 is not None: 555 vf1 |= pv1.getFormat() 556 pv2 = getattr(class2Record, "Value2", None) 557 if pv2 is not None: 558 vf2 |= pv2.getFormat() 559 self.ValueFormat1 = vf1 560 self.ValueFormat2 = vf2 561 562def _MarkBasePosFormat1_merge(self, lst, merger, Mark='Mark', Base='Base'): 563 self.ClassCount = max(l.ClassCount for l in lst) 564 565 MarkCoverageGlyphs, MarkRecords = \ 566 _merge_GlyphOrders(merger.font, 567 [getattr(l, Mark+'Coverage').glyphs for l in lst], 568 [getattr(l, Mark+'Array').MarkRecord for l in lst]) 569 getattr(self, Mark+'Coverage').glyphs = MarkCoverageGlyphs 570 571 BaseCoverageGlyphs, BaseRecords = \ 572 _merge_GlyphOrders(merger.font, 573 [getattr(l, Base+'Coverage').glyphs for l in lst], 574 [getattr(getattr(l, Base+'Array'), Base+'Record') for l in lst]) 575 getattr(self, Base+'Coverage').glyphs = BaseCoverageGlyphs 576 577 # MarkArray 578 records = [] 579 for g,glyphRecords in zip(MarkCoverageGlyphs, zip(*MarkRecords)): 580 allClasses = [r.Class for r in glyphRecords if r is not None] 581 582 # TODO Right now we require that all marks have same class in 583 # all masters that cover them. This is not required. 584 # 585 # We can relax that by just requiring that all marks that have 586 # the same class in a master, have the same class in every other 587 # master. Indeed, if, say, a sparse master only covers one mark, 588 # that mark probably will get class 0, which would possibly be 589 # different from its class in other masters. 590 # 591 # We can even go further and reclassify marks to support any 592 # input. But, since, it's unlikely that two marks being both, 593 # say, "top" in one master, and one being "top" and other being 594 # "top-right" in another master, we shouldn't do that, as any 595 # failures in that case will probably signify mistakes in the 596 # input masters. 597 598 if not allEqual(allClasses): 599 raise ShouldBeConstant(merger, expected=allClasses[0], got=allClasses) 600 else: 601 rec = ot.MarkRecord() 602 rec.Class = allClasses[0] 603 allAnchors = [None if r is None else r.MarkAnchor for r in glyphRecords] 604 if allNone(allAnchors): 605 anchor = None 606 else: 607 anchor = ot.Anchor() 608 anchor.Format = 1 609 merger.mergeThings(anchor, allAnchors) 610 rec.MarkAnchor = anchor 611 records.append(rec) 612 array = ot.MarkArray() 613 array.MarkRecord = records 614 array.MarkCount = len(records) 615 setattr(self, Mark+"Array", array) 616 617 # BaseArray 618 records = [] 619 for g,glyphRecords in zip(BaseCoverageGlyphs, zip(*BaseRecords)): 620 if allNone(glyphRecords): 621 rec = None 622 else: 623 rec = getattr(ot, Base+'Record')() 624 anchors = [] 625 setattr(rec, Base+'Anchor', anchors) 626 glyphAnchors = [[] if r is None else getattr(r, Base+'Anchor') 627 for r in glyphRecords] 628 for l in glyphAnchors: 629 l.extend([None] * (self.ClassCount - len(l))) 630 for allAnchors in zip(*glyphAnchors): 631 if allNone(allAnchors): 632 anchor = None 633 else: 634 anchor = ot.Anchor() 635 anchor.Format = 1 636 merger.mergeThings(anchor, allAnchors) 637 anchors.append(anchor) 638 records.append(rec) 639 array = getattr(ot, Base+'Array')() 640 setattr(array, Base+'Record', records) 641 setattr(array, Base+'Count', len(records)) 642 setattr(self, Base+'Array', array) 643 644@AligningMerger.merger(ot.MarkBasePos) 645def merge(merger, self, lst): 646 if not allEqualTo(self.Format, (l.Format for l in lst)): 647 raise InconsistentFormats( 648 merger, 649 subtable="mark-to-base positioning lookup", 650 expected=self.Format, 651 got=[l.Format for l in lst] 652 ) 653 if self.Format == 1: 654 _MarkBasePosFormat1_merge(self, lst, merger) 655 else: 656 raise UnsupportedFormat(merger, subtable="mark-to-base positioning lookup") 657 658@AligningMerger.merger(ot.MarkMarkPos) 659def merge(merger, self, lst): 660 if not allEqualTo(self.Format, (l.Format for l in lst)): 661 raise InconsistentFormats( 662 merger, 663 subtable="mark-to-mark positioning lookup", 664 expected=self.Format, 665 got=[l.Format for l in lst] 666 ) 667 if self.Format == 1: 668 _MarkBasePosFormat1_merge(self, lst, merger, 'Mark1', 'Mark2') 669 else: 670 raise UnsupportedFormat(merger, subtable="mark-to-mark positioning lookup") 671 672def _PairSet_flatten(lst, font): 673 self = ot.PairSet() 674 self.Coverage = ot.Coverage() 675 676 # Align them 677 glyphs, padded = _merge_GlyphOrders(font, 678 [[v.SecondGlyph for v in vs.PairValueRecord] for vs in lst], 679 [vs.PairValueRecord for vs in lst]) 680 681 self.Coverage.glyphs = glyphs 682 self.PairValueRecord = pvrs = [] 683 for values in zip(*padded): 684 for v in values: 685 if v is not None: 686 pvrs.append(v) 687 break 688 else: 689 assert False 690 self.PairValueCount = len(self.PairValueRecord) 691 692 return self 693 694def _Lookup_PairPosFormat1_subtables_flatten(lst, font): 695 assert allEqual([l.ValueFormat2 == 0 for l in lst if l.PairSet]), "Report bug against fonttools." 696 697 self = ot.PairPos() 698 self.Format = 1 699 self.Coverage = ot.Coverage() 700 self.ValueFormat1 = reduce(int.__or__, [l.ValueFormat1 for l in lst], 0) 701 self.ValueFormat2 = reduce(int.__or__, [l.ValueFormat2 for l in lst], 0) 702 703 # Align them 704 glyphs, padded = _merge_GlyphOrders(font, 705 [v.Coverage.glyphs for v in lst], 706 [v.PairSet for v in lst]) 707 708 self.Coverage.glyphs = glyphs 709 self.PairSet = [_PairSet_flatten([v for v in values if v is not None], font) 710 for values in zip(*padded)] 711 self.PairSetCount = len(self.PairSet) 712 return self 713 714def _Lookup_PairPosFormat2_subtables_flatten(lst, font): 715 assert allEqual([l.ValueFormat2 == 0 for l in lst if l.Class1Record]), "Report bug against fonttools." 716 717 self = ot.PairPos() 718 self.Format = 2 719 self.Coverage = ot.Coverage() 720 self.ValueFormat1 = reduce(int.__or__, [l.ValueFormat1 for l in lst], 0) 721 self.ValueFormat2 = reduce(int.__or__, [l.ValueFormat2 for l in lst], 0) 722 723 # Align them 724 glyphs, _ = _merge_GlyphOrders(font, 725 [v.Coverage.glyphs for v in lst]) 726 self.Coverage.glyphs = glyphs 727 728 matrices = _PairPosFormat2_align_matrices(self, lst, font, transparent=True) 729 730 matrix = self.Class1Record = [] 731 for rows in zip(*matrices): 732 row = ot.Class1Record() 733 matrix.append(row) 734 row.Class2Record = [] 735 row = row.Class2Record 736 for cols in zip(*list(r.Class2Record for r in rows)): 737 col = next(iter(c for c in cols if c is not None)) 738 row.append(col) 739 740 return self 741 742def _Lookup_PairPos_subtables_canonicalize(lst, font): 743 """Merge multiple Format1 subtables at the beginning of lst, 744 and merge multiple consecutive Format2 subtables that have the same 745 Class2 (ie. were split because of offset overflows). Returns new list.""" 746 lst = list(lst) 747 748 l = len(lst) 749 i = 0 750 while i < l and lst[i].Format == 1: 751 i += 1 752 lst[:i] = [_Lookup_PairPosFormat1_subtables_flatten(lst[:i], font)] 753 754 l = len(lst) 755 i = l 756 while i > 0 and lst[i - 1].Format == 2: 757 i -= 1 758 lst[i:] = [_Lookup_PairPosFormat2_subtables_flatten(lst[i:], font)] 759 760 return lst 761 762def _Lookup_SinglePos_subtables_flatten(lst, font, min_inclusive_rec_format): 763 glyphs, _ = _merge_GlyphOrders(font, 764 [v.Coverage.glyphs for v in lst], None) 765 num_glyphs = len(glyphs) 766 new = ot.SinglePos() 767 new.Format = 2 768 new.ValueFormat = min_inclusive_rec_format 769 new.Coverage = ot.Coverage() 770 new.Coverage.glyphs = glyphs 771 new.ValueCount = num_glyphs 772 new.Value = [None] * num_glyphs 773 for singlePos in lst: 774 if singlePos.Format == 1: 775 val_rec = singlePos.Value 776 for gname in singlePos.Coverage.glyphs: 777 i = glyphs.index(gname) 778 new.Value[i] = copy.deepcopy(val_rec) 779 elif singlePos.Format == 2: 780 for j, gname in enumerate(singlePos.Coverage.glyphs): 781 val_rec = singlePos.Value[j] 782 i = glyphs.index(gname) 783 new.Value[i] = copy.deepcopy(val_rec) 784 return [new] 785 786@AligningMerger.merger(ot.Lookup) 787def merge(merger, self, lst): 788 subtables = merger.lookup_subtables = [l.SubTable for l in lst] 789 790 # Remove Extension subtables 791 for l,sts in list(zip(lst,subtables))+[(self,self.SubTable)]: 792 if not sts: 793 continue 794 if sts[0].__class__.__name__.startswith('Extension'): 795 if not allEqual([st.__class__ for st in sts]): 796 raise InconsistentExtensions( 797 merger, 798 expected="Extension", 799 got=[st.__class__.__name__ for st in sts] 800 ) 801 if not allEqual([st.ExtensionLookupType for st in sts]): 802 raise InconsistentExtensions(merger) 803 l.LookupType = sts[0].ExtensionLookupType 804 new_sts = [st.ExtSubTable for st in sts] 805 del sts[:] 806 sts.extend(new_sts) 807 808 isPairPos = self.SubTable and isinstance(self.SubTable[0], ot.PairPos) 809 810 if isPairPos: 811 # AFDKO and feaLib sometimes generate two Format1 subtables instead of one. 812 # Merge those before continuing. 813 # https://github.com/fonttools/fonttools/issues/719 814 self.SubTable = _Lookup_PairPos_subtables_canonicalize(self.SubTable, merger.font) 815 subtables = merger.lookup_subtables = [_Lookup_PairPos_subtables_canonicalize(st, merger.font) for st in subtables] 816 else: 817 isSinglePos = self.SubTable and isinstance(self.SubTable[0], ot.SinglePos) 818 if isSinglePos: 819 numSubtables = [len(st) for st in subtables] 820 if not all([nums == numSubtables[0] for nums in numSubtables]): 821 # Flatten list of SinglePos subtables to single Format 2 subtable, 822 # with all value records set to the rec format type. 823 # We use buildSinglePos() to optimize the lookup after merging. 824 valueFormatList = [t.ValueFormat for st in subtables for t in st] 825 # Find the minimum value record that can accomodate all the singlePos subtables. 826 mirf = reduce(ior, valueFormatList) 827 self.SubTable = _Lookup_SinglePos_subtables_flatten(self.SubTable, merger.font, mirf) 828 subtables = merger.lookup_subtables = [ 829 _Lookup_SinglePos_subtables_flatten(st, merger.font, mirf) for st in subtables] 830 flattened = True 831 else: 832 flattened = False 833 834 merger.mergeLists(self.SubTable, subtables) 835 self.SubTableCount = len(self.SubTable) 836 837 if isPairPos: 838 # If format-1 subtable created during canonicalization is empty, remove it. 839 assert len(self.SubTable) >= 1 and self.SubTable[0].Format == 1 840 if not self.SubTable[0].Coverage.glyphs: 841 self.SubTable.pop(0) 842 self.SubTableCount -= 1 843 844 # If format-2 subtable created during canonicalization is empty, remove it. 845 assert len(self.SubTable) >= 1 and self.SubTable[-1].Format == 2 846 if not self.SubTable[-1].Coverage.glyphs: 847 self.SubTable.pop(-1) 848 self.SubTableCount -= 1 849 850 # Compact the merged subtables 851 # This is a good moment to do it because the compaction should create 852 # smaller subtables, which may prevent overflows from happening. 853 mode = os.environ.get(GPOS_COMPACT_MODE_ENV_KEY, GPOS_COMPACT_MODE_DEFAULT) 854 if mode and mode != "0": 855 log.info("Compacting GPOS...") 856 self.SubTable = compact_pair_pos(merger.font, mode, self.SubTable) 857 self.SubTableCount = len(self.SubTable) 858 859 elif isSinglePos and flattened: 860 singlePosTable = self.SubTable[0] 861 glyphs = singlePosTable.Coverage.glyphs 862 # We know that singlePosTable is Format 2, as this is set 863 # in _Lookup_SinglePos_subtables_flatten. 864 singlePosMapping = { 865 gname: valRecord 866 for gname, valRecord in zip(glyphs, singlePosTable.Value) 867 } 868 self.SubTable = buildSinglePos(singlePosMapping, merger.font.getReverseGlyphMap()) 869 merger.mergeObjects(self, lst, exclude=['SubTable', 'SubTableCount']) 870 871 del merger.lookup_subtables 872 873# 874# InstancerMerger 875# 876 877class InstancerMerger(AligningMerger): 878 """A merger that takes multiple master fonts, and instantiates 879 an instance.""" 880 881 def __init__(self, font, model, location): 882 Merger.__init__(self, font) 883 self.model = model 884 self.location = location 885 self.scalars = model.getScalars(location) 886 887@InstancerMerger.merger(ot.CaretValue) 888def merge(merger, self, lst): 889 assert self.Format == 1 890 Coords = [a.Coordinate for a in lst] 891 model = merger.model 892 scalars = merger.scalars 893 self.Coordinate = otRound(model.interpolateFromMastersAndScalars(Coords, scalars)) 894 895@InstancerMerger.merger(ot.Anchor) 896def merge(merger, self, lst): 897 assert self.Format == 1 898 XCoords = [a.XCoordinate for a in lst] 899 YCoords = [a.YCoordinate for a in lst] 900 model = merger.model 901 scalars = merger.scalars 902 self.XCoordinate = otRound(model.interpolateFromMastersAndScalars(XCoords, scalars)) 903 self.YCoordinate = otRound(model.interpolateFromMastersAndScalars(YCoords, scalars)) 904 905@InstancerMerger.merger(otBase.ValueRecord) 906def merge(merger, self, lst): 907 model = merger.model 908 scalars = merger.scalars 909 # TODO Handle differing valueformats 910 for name, tableName in [('XAdvance','XAdvDevice'), 911 ('YAdvance','YAdvDevice'), 912 ('XPlacement','XPlaDevice'), 913 ('YPlacement','YPlaDevice')]: 914 915 assert not hasattr(self, tableName) 916 917 if hasattr(self, name): 918 values = [getattr(a, name, 0) for a in lst] 919 value = otRound(model.interpolateFromMastersAndScalars(values, scalars)) 920 setattr(self, name, value) 921 922 923# 924# MutatorMerger 925# 926 927class MutatorMerger(AligningMerger): 928 """A merger that takes a variable font, and instantiates 929 an instance. While there's no "merging" to be done per se, 930 the operation can benefit from many operations that the 931 aligning merger does.""" 932 933 def __init__(self, font, instancer, deleteVariations=True): 934 Merger.__init__(self, font) 935 self.instancer = instancer 936 self.deleteVariations = deleteVariations 937 938@MutatorMerger.merger(ot.CaretValue) 939def merge(merger, self, lst): 940 941 # Hack till we become selfless. 942 self.__dict__ = lst[0].__dict__.copy() 943 944 if self.Format != 3: 945 return 946 947 instancer = merger.instancer 948 dev = self.DeviceTable 949 if merger.deleteVariations: 950 del self.DeviceTable 951 if dev: 952 assert dev.DeltaFormat == 0x8000 953 varidx = (dev.StartSize << 16) + dev.EndSize 954 delta = otRound(instancer[varidx]) 955 self.Coordinate += delta 956 957 if merger.deleteVariations: 958 self.Format = 1 959 960@MutatorMerger.merger(ot.Anchor) 961def merge(merger, self, lst): 962 963 # Hack till we become selfless. 964 self.__dict__ = lst[0].__dict__.copy() 965 966 if self.Format != 3: 967 return 968 969 instancer = merger.instancer 970 for v in "XY": 971 tableName = v+'DeviceTable' 972 if not hasattr(self, tableName): 973 continue 974 dev = getattr(self, tableName) 975 if merger.deleteVariations: 976 delattr(self, tableName) 977 if dev is None: 978 continue 979 980 assert dev.DeltaFormat == 0x8000 981 varidx = (dev.StartSize << 16) + dev.EndSize 982 delta = otRound(instancer[varidx]) 983 984 attr = v+'Coordinate' 985 setattr(self, attr, getattr(self, attr) + delta) 986 987 if merger.deleteVariations: 988 self.Format = 1 989 990@MutatorMerger.merger(otBase.ValueRecord) 991def merge(merger, self, lst): 992 993 # Hack till we become selfless. 994 self.__dict__ = lst[0].__dict__.copy() 995 996 instancer = merger.instancer 997 for name, tableName in [('XAdvance','XAdvDevice'), 998 ('YAdvance','YAdvDevice'), 999 ('XPlacement','XPlaDevice'), 1000 ('YPlacement','YPlaDevice')]: 1001 1002 if not hasattr(self, tableName): 1003 continue 1004 dev = getattr(self, tableName) 1005 if merger.deleteVariations: 1006 delattr(self, tableName) 1007 if dev is None: 1008 continue 1009 1010 assert dev.DeltaFormat == 0x8000 1011 varidx = (dev.StartSize << 16) + dev.EndSize 1012 delta = otRound(instancer[varidx]) 1013 1014 setattr(self, name, getattr(self, name, 0) + delta) 1015 1016 1017# 1018# VariationMerger 1019# 1020 1021class VariationMerger(AligningMerger): 1022 """A merger that takes multiple master fonts, and builds a 1023 variable font.""" 1024 1025 def __init__(self, model, axisTags, font): 1026 Merger.__init__(self, font) 1027 self.store_builder = varStore.OnlineVarStoreBuilder(axisTags) 1028 self.setModel(model) 1029 1030 def setModel(self, model): 1031 self.model = model 1032 self.store_builder.setModel(model) 1033 1034 def mergeThings(self, out, lst): 1035 masterModel = None 1036 if None in lst: 1037 if allNone(lst): 1038 if out is not None: 1039 raise FoundANone(self, got=lst) 1040 return 1041 masterModel = self.model 1042 model, lst = masterModel.getSubModel(lst) 1043 self.setModel(model) 1044 1045 super(VariationMerger, self).mergeThings(out, lst) 1046 1047 if masterModel: 1048 self.setModel(masterModel) 1049 1050 1051def buildVarDevTable(store_builder, master_values): 1052 if allEqual(master_values): 1053 return master_values[0], None 1054 base, varIdx = store_builder.storeMasters(master_values) 1055 return base, builder.buildVarDevTable(varIdx) 1056 1057@VariationMerger.merger(ot.BaseCoord) 1058def merge(merger, self, lst): 1059 if self.Format != 1: 1060 raise UnsupportedFormat(merger, subtable="a baseline coordinate") 1061 self.Coordinate, DeviceTable = buildVarDevTable(merger.store_builder, [a.Coordinate for a in lst]) 1062 if DeviceTable: 1063 self.Format = 3 1064 self.DeviceTable = DeviceTable 1065 1066@VariationMerger.merger(ot.CaretValue) 1067def merge(merger, self, lst): 1068 if self.Format != 1: 1069 raise UnsupportedFormat(merger, subtable="a caret") 1070 self.Coordinate, DeviceTable = buildVarDevTable(merger.store_builder, [a.Coordinate for a in lst]) 1071 if DeviceTable: 1072 self.Format = 3 1073 self.DeviceTable = DeviceTable 1074 1075@VariationMerger.merger(ot.Anchor) 1076def merge(merger, self, lst): 1077 if self.Format != 1: 1078 raise UnsupportedFormat(merger, subtable="an anchor") 1079 self.XCoordinate, XDeviceTable = buildVarDevTable(merger.store_builder, [a.XCoordinate for a in lst]) 1080 self.YCoordinate, YDeviceTable = buildVarDevTable(merger.store_builder, [a.YCoordinate for a in lst]) 1081 if XDeviceTable or YDeviceTable: 1082 self.Format = 3 1083 self.XDeviceTable = XDeviceTable 1084 self.YDeviceTable = YDeviceTable 1085 1086@VariationMerger.merger(otBase.ValueRecord) 1087def merge(merger, self, lst): 1088 for name, tableName in [('XAdvance','XAdvDevice'), 1089 ('YAdvance','YAdvDevice'), 1090 ('XPlacement','XPlaDevice'), 1091 ('YPlacement','YPlaDevice')]: 1092 1093 if hasattr(self, name): 1094 value, deviceTable = buildVarDevTable(merger.store_builder, 1095 [getattr(a, name, 0) for a in lst]) 1096 setattr(self, name, value) 1097 if deviceTable: 1098 setattr(self, tableName, deviceTable) 1099