1from __future__ import print_function, division, absolute_import 2from fontTools import ttLib 3from fontTools.ttLib.tables import otTables as ot 4from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict 5 6 7def buildCoverage(glyphs, glyphMap): 8 if not glyphs: 9 return None 10 self = ot.Coverage() 11 self.glyphs = sorted(glyphs, key=glyphMap.__getitem__) 12 return self 13 14 15LOOKUP_FLAG_RIGHT_TO_LEFT = 0x0001 16LOOKUP_FLAG_IGNORE_BASE_GLYPHS = 0x0002 17LOOKUP_FLAG_IGNORE_LIGATURES = 0x0004 18LOOKUP_FLAG_IGNORE_MARKS = 0x0008 19LOOKUP_FLAG_USE_MARK_FILTERING_SET = 0x0010 20 21 22def buildLookup(subtables, flags=0, markFilterSet=None): 23 if subtables is None: 24 return None 25 subtables = [st for st in subtables if st is not None] 26 if not subtables: 27 return None 28 assert all(t.LookupType == subtables[0].LookupType for t in subtables), \ 29 ("all subtables must have the same LookupType; got %s" % 30 repr([t.LookupType for t in subtables])) 31 self = ot.Lookup() 32 self.LookupType = subtables[0].LookupType 33 self.LookupFlag = flags 34 self.SubTable = subtables 35 self.SubTableCount = len(self.SubTable) 36 if markFilterSet is not None: 37 assert self.LookupFlag & LOOKUP_FLAG_USE_MARK_FILTERING_SET, \ 38 ("if markFilterSet is not None, flags must set " 39 "LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x%04x" % flags) 40 assert isinstance(markFilterSet, int), markFilterSet 41 self.MarkFilteringSet = markFilterSet 42 else: 43 assert (self.LookupFlag & LOOKUP_FLAG_USE_MARK_FILTERING_SET) == 0, \ 44 ("if markFilterSet is None, flags must not set " 45 "LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x%04x" % flags) 46 return self 47 48 49# GSUB 50 51 52def buildSingleSubstSubtable(mapping): 53 if not mapping: 54 return None 55 self = ot.SingleSubst() 56 self.mapping = dict(mapping) 57 return self 58 59 60def buildMultipleSubstSubtable(mapping): 61 if not mapping: 62 return None 63 self = ot.MultipleSubst() 64 self.mapping = dict(mapping) 65 return self 66 67 68def buildAlternateSubstSubtable(mapping): 69 if not mapping: 70 return None 71 self = ot.AlternateSubst() 72 self.alternates = dict(mapping) 73 return self 74 75 76def _getLigatureKey(components): 77 """Computes a key for ordering ligatures in a GSUB Type-4 lookup. 78 79 When building the OpenType lookup, we need to make sure that 80 the longest sequence of components is listed first, so we 81 use the negative length as the primary key for sorting. 82 To make buildLigatureSubstSubtable() deterministic, we use the 83 component sequence as the secondary key. 84 85 For example, this will sort (f,f,f) < (f,f,i) < (f,f) < (f,i) < (f,l). 86 """ 87 return (-len(components), components) 88 89 90def buildLigatureSubstSubtable(mapping): 91 if not mapping: 92 return None 93 self = ot.LigatureSubst() 94 # The following single line can replace the rest of this function 95 # with fontTools >= 3.1: 96 # self.ligatures = dict(mapping) 97 self.ligatures = {} 98 for components in sorted(mapping.keys(), key=_getLigatureKey): 99 ligature = ot.Ligature() 100 ligature.Component = components[1:] 101 ligature.CompCount = len(ligature.Component) + 1 102 ligature.LigGlyph = mapping[components] 103 firstGlyph = components[0] 104 self.ligatures.setdefault(firstGlyph, []).append(ligature) 105 return self 106 107 108# GPOS 109 110 111def buildAnchor(x, y, point=None, deviceX=None, deviceY=None): 112 self = ot.Anchor() 113 self.XCoordinate, self.YCoordinate = x, y 114 self.Format = 1 115 if point is not None: 116 self.AnchorPoint = point 117 self.Format = 2 118 if deviceX is not None or deviceY is not None: 119 assert self.Format == 1, \ 120 "Either point, or both of deviceX/deviceY, must be None." 121 self.XDeviceTable = deviceX 122 self.YDeviceTable = deviceY 123 self.Format = 3 124 return self 125 126 127def buildBaseArray(bases, numMarkClasses, glyphMap): 128 self = ot.BaseArray() 129 self.BaseRecord = [] 130 for base in sorted(bases, key=glyphMap.__getitem__): 131 b = bases[base] 132 anchors = [b.get(markClass) for markClass in range(numMarkClasses)] 133 self.BaseRecord.append(buildBaseRecord(anchors)) 134 self.BaseCount = len(self.BaseRecord) 135 return self 136 137 138def buildBaseRecord(anchors): 139 """[otTables.Anchor, otTables.Anchor, ...] --> otTables.BaseRecord""" 140 self = ot.BaseRecord() 141 self.BaseAnchor = anchors 142 return self 143 144 145def buildComponentRecord(anchors): 146 """[otTables.Anchor, otTables.Anchor, ...] --> otTables.ComponentRecord""" 147 if not anchors: 148 return None 149 self = ot.ComponentRecord() 150 self.LigatureAnchor = anchors 151 return self 152 153 154def buildCursivePosSubtable(attach, glyphMap): 155 """{"alef": (entry, exit)} --> otTables.CursivePos""" 156 if not attach: 157 return None 158 self = ot.CursivePos() 159 self.Format = 1 160 self.Coverage = buildCoverage(attach.keys(), glyphMap) 161 self.EntryExitRecord = [] 162 for glyph in self.Coverage.glyphs: 163 entryAnchor, exitAnchor = attach[glyph] 164 rec = ot.EntryExitRecord() 165 rec.EntryAnchor = entryAnchor 166 rec.ExitAnchor = exitAnchor 167 self.EntryExitRecord.append(rec) 168 self.EntryExitCount = len(self.EntryExitRecord) 169 return self 170 171 172def buildDevice(deltas): 173 """{8:+1, 10:-3, ...} --> otTables.Device""" 174 if not deltas: 175 return None 176 self = ot.Device() 177 keys = deltas.keys() 178 self.StartSize = startSize = min(keys) 179 self.EndSize = endSize = max(keys) 180 assert 0 <= startSize <= endSize 181 self.DeltaValue = deltaValues = [ 182 deltas.get(size, 0) 183 for size in range(startSize, endSize + 1)] 184 maxDelta = max(deltaValues) 185 minDelta = min(deltaValues) 186 assert minDelta > -129 and maxDelta < 128 187 if minDelta > -3 and maxDelta < 2: 188 self.DeltaFormat = 1 189 elif minDelta > -9 and maxDelta < 8: 190 self.DeltaFormat = 2 191 else: 192 self.DeltaFormat = 3 193 return self 194 195 196def buildLigatureArray(ligs, numMarkClasses, glyphMap): 197 self = ot.LigatureArray() 198 self.LigatureAttach = [] 199 for lig in sorted(ligs, key=glyphMap.__getitem__): 200 anchors = [] 201 for component in ligs[lig]: 202 anchors.append([component.get(mc) for mc in range(numMarkClasses)]) 203 self.LigatureAttach.append(buildLigatureAttach(anchors)) 204 self.LigatureCount = len(self.LigatureAttach) 205 return self 206 207 208def buildLigatureAttach(components): 209 """[[Anchor, Anchor], [Anchor, Anchor, Anchor]] --> LigatureAttach""" 210 self = ot.LigatureAttach() 211 self.ComponentRecord = [buildComponentRecord(c) for c in components] 212 self.ComponentCount = len(self.ComponentRecord) 213 return self 214 215 216def buildMarkArray(marks, glyphMap): 217 """{"acute": (markClass, otTables.Anchor)} --> otTables.MarkArray""" 218 self = ot.MarkArray() 219 self.MarkRecord = [] 220 for mark in sorted(marks.keys(), key=glyphMap.__getitem__): 221 markClass, anchor = marks[mark] 222 markrec = buildMarkRecord(markClass, anchor) 223 self.MarkRecord.append(markrec) 224 self.MarkCount = len(self.MarkRecord) 225 return self 226 227 228def buildMarkBasePos(marks, bases, glyphMap): 229 """Build a list of MarkBasePos subtables. 230 231 a1, a2, a3, a4, a5 = buildAnchor(500, 100), ... 232 marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)} 233 bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}} 234 """ 235 # TODO: Consider emitting multiple subtables to save space. 236 # Partition the marks and bases into disjoint subsets, so that 237 # MarkBasePos rules would only access glyphs from a single 238 # subset. This would likely lead to smaller mark/base 239 # matrices, so we might be able to omit many of the empty 240 # anchor tables that we currently produce. Of course, this 241 # would only work if the MarkBasePos rules of real-world fonts 242 # allow partitioning into multiple subsets. We should find out 243 # whether this is the case; if so, implement the optimization. 244 # On the other hand, a very large number of subtables could 245 # slow down layout engines; so this would need profiling. 246 return [buildMarkBasePosSubtable(marks, bases, glyphMap)] 247 248 249def buildMarkBasePosSubtable(marks, bases, glyphMap): 250 """Build a single MarkBasePos subtable. 251 252 a1, a2, a3, a4, a5 = buildAnchor(500, 100), ... 253 marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)} 254 bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}} 255 """ 256 self = ot.MarkBasePos() 257 self.Format = 1 258 self.MarkCoverage = buildCoverage(marks, glyphMap) 259 self.MarkArray = buildMarkArray(marks, glyphMap) 260 self.ClassCount = max([mc for mc, _ in marks.values()]) + 1 261 self.BaseCoverage = buildCoverage(bases, glyphMap) 262 self.BaseArray = buildBaseArray(bases, self.ClassCount, glyphMap) 263 return self 264 265 266def buildMarkLigPos(marks, ligs, glyphMap): 267 """Build a list of MarkLigPos subtables. 268 269 a1, a2, a3, a4, a5 = buildAnchor(500, 100), ... 270 marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)} 271 ligs = {"f_i": [{0: a3, 1: a5}, {0: a4, 1: a5}], "c_t": [{...}, {...}]} 272 """ 273 # TODO: Consider splitting into multiple subtables to save space, 274 # as with MarkBasePos, this would be a trade-off that would need 275 # profiling. And, depending on how typical fonts are structured, 276 # it might not be worth doing at all. 277 return [buildMarkLigPosSubtable(marks, ligs, glyphMap)] 278 279 280def buildMarkLigPosSubtable(marks, ligs, glyphMap): 281 """Build a single MarkLigPos subtable. 282 283 a1, a2, a3, a4, a5 = buildAnchor(500, 100), ... 284 marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)} 285 ligs = {"f_i": [{0: a3, 1: a5}, {0: a4, 1: a5}], "c_t": [{...}, {...}]} 286 """ 287 self = ot.MarkLigPos() 288 self.Format = 1 289 self.MarkCoverage = buildCoverage(marks, glyphMap) 290 self.MarkArray = buildMarkArray(marks, glyphMap) 291 self.ClassCount = max([mc for mc, _ in marks.values()]) + 1 292 self.LigatureCoverage = buildCoverage(ligs, glyphMap) 293 self.LigatureArray = buildLigatureArray(ligs, self.ClassCount, glyphMap) 294 return self 295 296 297def buildMarkRecord(classID, anchor): 298 assert isinstance(classID, int) 299 assert isinstance(anchor, ot.Anchor) 300 self = ot.MarkRecord() 301 self.Class = classID 302 self.MarkAnchor = anchor 303 return self 304 305 306def buildMark2Record(anchors): 307 """[otTables.Anchor, otTables.Anchor, ...] --> otTables.Mark2Record""" 308 self = ot.Mark2Record() 309 self.Mark2Anchor = anchors 310 return self 311 312 313def _getValueFormat(f, values, i): 314 """Helper for buildPairPos{Glyphs|Classes}Subtable.""" 315 if f is not None: 316 return f 317 mask = 0 318 for value in values: 319 if value is not None and value[i] is not None: 320 mask |= value[i].getFormat() 321 return mask 322 323 324def buildPairPosClassesSubtable(pairs, glyphMap, 325 valueFormat1=None, valueFormat2=None): 326 coverage = set() 327 classDef1 = ClassDefBuilder(useClass0=True) 328 classDef2 = ClassDefBuilder(useClass0=False) 329 for gc1, gc2 in sorted(pairs): 330 coverage.update(gc1) 331 classDef1.add(gc1) 332 classDef2.add(gc2) 333 self = ot.PairPos() 334 self.Format = 2 335 self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0) 336 self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1) 337 self.Coverage = buildCoverage(coverage, glyphMap) 338 self.ClassDef1 = classDef1.build() 339 self.ClassDef2 = classDef2.build() 340 classes1 = classDef1.classes() 341 classes2 = classDef2.classes() 342 self.Class1Record = [] 343 for c1 in classes1: 344 rec1 = ot.Class1Record() 345 rec1.Class2Record = [] 346 self.Class1Record.append(rec1) 347 for c2 in classes2: 348 rec2 = ot.Class2Record() 349 rec2.Value1, rec2.Value2 = pairs.get((c1, c2), (None, None)) 350 rec1.Class2Record.append(rec2) 351 self.Class1Count = len(self.Class1Record) 352 self.Class2Count = len(classes2) 353 return self 354 355 356def buildPairPosGlyphs(pairs, glyphMap): 357 p = {} # (formatA, formatB) --> {(glyphA, glyphB): (valA, valB)} 358 for (glyphA, glyphB), (valA, valB) in pairs.items(): 359 formatA = valA.getFormat() if valA is not None else 0 360 formatB = valB.getFormat() if valB is not None else 0 361 pos = p.setdefault((formatA, formatB), {}) 362 pos[(glyphA, glyphB)] = (valA, valB) 363 return [ 364 buildPairPosGlyphsSubtable(pos, glyphMap, formatA, formatB) 365 for ((formatA, formatB), pos) in sorted(p.items())] 366 367 368def buildPairPosGlyphsSubtable(pairs, glyphMap, 369 valueFormat1=None, valueFormat2=None): 370 self = ot.PairPos() 371 self.Format = 1 372 self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0) 373 self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1) 374 p = {} 375 for (glyphA, glyphB), (valA, valB) in pairs.items(): 376 p.setdefault(glyphA, []).append((glyphB, valA, valB)) 377 self.Coverage = buildCoverage({g for g, _ in pairs.keys()}, glyphMap) 378 self.PairSet = [] 379 for glyph in self.Coverage.glyphs: 380 ps = ot.PairSet() 381 ps.PairValueRecord = [] 382 self.PairSet.append(ps) 383 for glyph2, val1, val2 in \ 384 sorted(p[glyph], key=lambda x: glyphMap[x[0]]): 385 pvr = ot.PairValueRecord() 386 pvr.SecondGlyph = glyph2 387 pvr.Value1 = val1 if val1 and val1.getFormat() != 0 else None 388 pvr.Value2 = val2 if val2 and val2.getFormat() != 0 else None 389 ps.PairValueRecord.append(pvr) 390 ps.PairValueCount = len(ps.PairValueRecord) 391 self.PairSetCount = len(self.PairSet) 392 return self 393 394 395def buildSinglePos(mapping, glyphMap): 396 """{"glyph": ValueRecord} --> [otTables.SinglePos*]""" 397 result, handled = [], set() 398 # In SinglePos format 1, the covered glyphs all share the same ValueRecord. 399 # In format 2, each glyph has its own ValueRecord, but these records 400 # all have the same properties (eg., all have an X but no Y placement). 401 coverages, masks, values = {}, {}, {} 402 for glyph, value in mapping.items(): 403 key = _getSinglePosValueKey(value) 404 coverages.setdefault(key, []).append(glyph) 405 masks.setdefault(key[0], []).append(key) 406 values[key] = value 407 408 # If a ValueRecord is shared between multiple glyphs, we generate 409 # a SinglePos format 1 subtable; that is the most compact form. 410 for key, glyphs in coverages.items(): 411 if len(glyphs) > 1: 412 format1Mapping = {g: values[key] for g in glyphs} 413 result.append(buildSinglePosSubtable(format1Mapping, glyphMap)) 414 handled.add(key) 415 416 # In the remaining ValueRecords, look for those whose valueFormat 417 # (the set of used properties) is shared between multiple records. 418 # These will get encoded in format 2. 419 for valueFormat, keys in masks.items(): 420 f2 = [k for k in keys if k not in handled] 421 if len(f2) > 1: 422 format2Mapping = {coverages[k][0]: values[k] for k in f2} 423 result.append(buildSinglePosSubtable(format2Mapping, glyphMap)) 424 handled.update(f2) 425 426 # The remaining ValueRecords are singletons in the sense that 427 # they are only used by a single glyph, and their valueFormat 428 # is unique as well. We encode these in format 1 again. 429 for key, glyphs in coverages.items(): 430 if key not in handled: 431 assert len(glyphs) == 1, glyphs 432 st = buildSinglePosSubtable({glyphs[0]: values[key]}, glyphMap) 433 result.append(st) 434 435 # When the OpenType layout engine traverses the subtables, it will 436 # stop after the first matching subtable. Therefore, we sort the 437 # resulting subtables by decreasing coverage size; this increases 438 # the chance that the layout engine can do an early exit. (Of course, 439 # this would only be true if all glyphs were equally frequent, which 440 # is not really the case; but we do not know their distribution). 441 # If two subtables cover the same number of glyphs, we sort them 442 # by glyph ID so that our output is deterministic. 443 result.sort(key=lambda t: _getSinglePosTableKey(t, glyphMap)) 444 return result 445 446 447def buildSinglePosSubtable(values, glyphMap): 448 """{glyphName: otBase.ValueRecord} --> otTables.SinglePos""" 449 self = ot.SinglePos() 450 self.Coverage = buildCoverage(values.keys(), glyphMap) 451 valueRecords = [values[g] for g in self.Coverage.glyphs] 452 self.ValueFormat = 0 453 for v in valueRecords: 454 self.ValueFormat |= v.getFormat() 455 if all(v == valueRecords[0] for v in valueRecords): 456 self.Format = 1 457 if self.ValueFormat != 0: 458 self.Value = valueRecords[0] 459 else: 460 self.Value = None 461 else: 462 self.Format = 2 463 self.Value = valueRecords 464 self.ValueCount = len(self.Value) 465 return self 466 467 468def _getSinglePosTableKey(subtable, glyphMap): 469 assert isinstance(subtable, ot.SinglePos), subtable 470 glyphs = subtable.Coverage.glyphs 471 return (-len(glyphs), glyphMap[glyphs[0]]) 472 473 474def _getSinglePosValueKey(valueRecord): 475 """otBase.ValueRecord --> (2, ("YPlacement": 12))""" 476 assert isinstance(valueRecord, ValueRecord), valueRecord 477 valueFormat, result = 0, [] 478 for name, value in valueRecord.__dict__.items(): 479 if isinstance(value, ot.Device): 480 result.append((name, _makeDeviceTuple(value))) 481 else: 482 result.append((name, value)) 483 valueFormat |= valueRecordFormatDict[name][0] 484 result.sort() 485 result.insert(0, valueFormat) 486 return tuple(result) 487 488 489def _makeDeviceTuple(device): 490 """otTables.Device --> tuple, for making device tables unique""" 491 return (device.DeltaFormat, device.StartSize, device.EndSize, 492 tuple(device.DeltaValue)) 493 494 495def buildValue(value): 496 self = ValueRecord() 497 for k, v in value.items(): 498 setattr(self, k, v) 499 return self 500 501 502# GDEF 503 504def buildAttachList(attachPoints, glyphMap): 505 """{"glyphName": [4, 23]} --> otTables.AttachList, or None""" 506 if not attachPoints: 507 return None 508 self = ot.AttachList() 509 self.Coverage = buildCoverage(attachPoints.keys(), glyphMap) 510 self.AttachPoint = [buildAttachPoint(attachPoints[g]) 511 for g in self.Coverage.glyphs] 512 self.GlyphCount = len(self.AttachPoint) 513 return self 514 515 516def buildAttachPoint(points): 517 """[4, 23, 41] --> otTables.AttachPoint""" 518 if not points: 519 return None 520 self = ot.AttachPoint() 521 self.PointIndex = sorted(set(points)) 522 self.PointCount = len(self.PointIndex) 523 return self 524 525 526def buildCaretValueForCoord(coord): 527 """500 --> otTables.CaretValue, format 1""" 528 self = ot.CaretValue() 529 self.Format = 1 530 self.Coordinate = coord 531 return self 532 533 534def buildCaretValueForPoint(point): 535 """4 --> otTables.CaretValue, format 2""" 536 self = ot.CaretValue() 537 self.Format = 2 538 self.CaretValuePoint = point 539 return self 540 541 542def buildLigCaretList(coords, points, glyphMap): 543 """{"f_f_i":[300,600]}, {"c_t":[28]} --> otTables.LigCaretList, or None""" 544 glyphs = set(coords.keys()) if coords else set() 545 if points: 546 glyphs.update(points.keys()) 547 carets = {g: buildLigGlyph(coords.get(g), points.get(g)) for g in glyphs} 548 carets = {g: c for g, c in carets.items() if c is not None} 549 if not carets: 550 return None 551 self = ot.LigCaretList() 552 self.Coverage = buildCoverage(carets.keys(), glyphMap) 553 self.LigGlyph = [carets[g] for g in self.Coverage.glyphs] 554 self.LigGlyphCount = len(self.LigGlyph) 555 return self 556 557 558def buildLigGlyph(coords, points): 559 """([500], [4]) --> otTables.LigGlyph; None for empty coords/points""" 560 carets = [] 561 if coords: 562 carets.extend([buildCaretValueForCoord(c) for c in sorted(coords)]) 563 if points: 564 carets.extend([buildCaretValueForPoint(p) for p in sorted(points)]) 565 if not carets: 566 return None 567 self = ot.LigGlyph() 568 self.CaretValue = carets 569 self.CaretCount = len(self.CaretValue) 570 return self 571 572 573def buildMarkGlyphSetsDef(markSets, glyphMap): 574 """[{"acute","grave"}, {"caron","grave"}] --> otTables.MarkGlyphSetsDef""" 575 if not markSets: 576 return None 577 self = ot.MarkGlyphSetsDef() 578 self.MarkSetTableFormat = 1 579 self.Coverage = [buildCoverage(m, glyphMap) for m in markSets] 580 self.MarkSetCount = len(self.Coverage) 581 return self 582 583 584class ClassDefBuilder(object): 585 """Helper for building ClassDef tables.""" 586 def __init__(self, useClass0): 587 self.classes_ = set() 588 self.glyphs_ = {} 589 self.useClass0_ = useClass0 590 591 def canAdd(self, glyphs): 592 if isinstance(glyphs, (set, frozenset)): 593 glyphs = sorted(glyphs) 594 glyphs = tuple(glyphs) 595 if glyphs in self.classes_: 596 return True 597 for glyph in glyphs: 598 if glyph in self.glyphs_: 599 return False 600 return True 601 602 def add(self, glyphs): 603 if isinstance(glyphs, (set, frozenset)): 604 glyphs = sorted(glyphs) 605 glyphs = tuple(glyphs) 606 if glyphs in self.classes_: 607 return 608 self.classes_.add(glyphs) 609 for glyph in glyphs: 610 assert glyph not in self.glyphs_ 611 self.glyphs_[glyph] = glyphs 612 613 def classes(self): 614 # In ClassDef1 tables, class id #0 does not need to be encoded 615 # because zero is the default. Therefore, we use id #0 for the 616 # glyph class that has the largest number of members. However, 617 # in other tables than ClassDef1, 0 means "every other glyph" 618 # so we should not use that ID for any real glyph classes; 619 # we implement this by inserting an empty set at position 0. 620 # 621 # TODO: Instead of counting the number of glyphs in each class, 622 # we should determine the encoded size. If the glyphs in a large 623 # class form a contiguous range, the encoding is actually quite 624 # compact, whereas a non-contiguous set might need a lot of bytes 625 # in the output file. We don't get this right with the key below. 626 result = sorted(self.classes_, key=lambda s: (len(s), s), reverse=True) 627 if not self.useClass0_: 628 result.insert(0, frozenset()) 629 return result 630 631 def build(self): 632 glyphClasses = {} 633 for classID, glyphs in enumerate(self.classes()): 634 if classID == 0: 635 continue 636 for glyph in glyphs: 637 glyphClasses[glyph] = classID 638 classDef = ot.ClassDef() 639 classDef.classDefs = glyphClasses 640 return classDef 641