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