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