Home | History | Annotate | Download | only in otlLib
      1 from __future__ import print_function, division, absolute_import
      2 from fontTools import ttLib
      3 from fontTools.ttLib.tables import otTables as ot
      4 from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict
      5 
      6 
      7 def 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 
     15 LOOKUP_FLAG_RIGHT_TO_LEFT = 0x0001
     16 LOOKUP_FLAG_IGNORE_BASE_GLYPHS = 0x0002
     17 LOOKUP_FLAG_IGNORE_LIGATURES = 0x0004
     18 LOOKUP_FLAG_IGNORE_MARKS = 0x0008
     19 LOOKUP_FLAG_USE_MARK_FILTERING_SET = 0x0010
     20 
     21 
     22 def 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 
     52 def buildSingleSubstSubtable(mapping):
     53     if not mapping:
     54         return None
     55     self = ot.SingleSubst()
     56     self.mapping = dict(mapping)
     57     return self
     58 
     59 
     60 def buildMultipleSubstSubtable(mapping):
     61     if not mapping:
     62         return None
     63     self = ot.MultipleSubst()
     64     self.mapping = dict(mapping)
     65     return self
     66 
     67 
     68 def buildAlternateSubstSubtable(mapping):
     69     if not mapping:
     70         return None
     71     self = ot.AlternateSubst()
     72     self.alternates = dict(mapping)
     73     return self
     74 
     75 
     76 def _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 
     90 def 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 
    111 def 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 
    127 def 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 
    138 def buildBaseRecord(anchors):
    139     """[otTables.Anchor, otTables.Anchor, ...] --> otTables.BaseRecord"""
    140     self = ot.BaseRecord()
    141     self.BaseAnchor = anchors
    142     return self
    143 
    144 
    145 def 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 
    154 def 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 
    172 def 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 
    196 def 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 
    208 def 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 
    216 def 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 
    228 def 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 
    249 def 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 
    266 def 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 
    280 def 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 
    297 def 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 
    306 def buildMark2Record(anchors):
    307     """[otTables.Anchor, otTables.Anchor, ...] --> otTables.Mark2Record"""
    308     self = ot.Mark2Record()
    309     self.Mark2Anchor = anchors
    310     return self
    311 
    312 
    313 def _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 
    324 def 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 
    356 def 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 
    368 def 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 
    395 def 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 
    447 def 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 
    468 def _getSinglePosTableKey(subtable, glyphMap):
    469     assert isinstance(subtable, ot.SinglePos), subtable
    470     glyphs = subtable.Coverage.glyphs
    471     return (-len(glyphs), glyphMap[glyphs[0]])
    472 
    473 
    474 def _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 
    489 def _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 
    495 def 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 
    504 def 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 
    516 def 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 
    526 def 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 
    534 def 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 
    542 def 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 
    558 def 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 
    573 def 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 
    584 class 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