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