• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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