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