• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from collections import namedtuple, OrderedDict
2import os
3from fontTools.misc.fixedTools import fixedToFloat
4from fontTools import ttLib
5from fontTools.ttLib.tables import otTables as ot
6from fontTools.ttLib.tables.otBase import (
7    ValueRecord,
8    valueRecordFormatDict,
9    OTTableWriter,
10    CountReference,
11)
12from fontTools.ttLib.tables import otBase
13from fontTools.feaLib.ast import STATNameStatement
14from fontTools.otlLib.optimize.gpos import (
15    GPOS_COMPACT_MODE_DEFAULT,
16    GPOS_COMPACT_MODE_ENV_KEY,
17    compact_lookup,
18)
19from fontTools.otlLib.error import OpenTypeLibError
20from functools import reduce
21import logging
22import copy
23
24
25log = logging.getLogger(__name__)
26
27
28def buildCoverage(glyphs, glyphMap):
29    """Builds a coverage table.
30
31    Coverage tables (as defined in the `OpenType spec <https://docs.microsoft.com/en-gb/typography/opentype/spec/chapter2#coverage-table>`__)
32    are used in all OpenType Layout lookups apart from the Extension type, and
33    define the glyphs involved in a layout subtable. This allows shaping engines
34    to compare the glyph stream with the coverage table and quickly determine
35    whether a subtable should be involved in a shaping operation.
36
37    This function takes a list of glyphs and a glyphname-to-ID map, and
38    returns a ``Coverage`` object representing the coverage table.
39
40    Example::
41
42        glyphMap = font.getReverseGlyphMap()
43        glyphs = [ "A", "B", "C" ]
44        coverage = buildCoverage(glyphs, glyphMap)
45
46    Args:
47        glyphs: a sequence of glyph names.
48        glyphMap: a glyph name to ID map, typically returned from
49            ``font.getReverseGlyphMap()``.
50
51    Returns:
52        An ``otTables.Coverage`` object or ``None`` if there are no glyphs
53        supplied.
54    """
55
56    if not glyphs:
57        return None
58    self = ot.Coverage()
59    self.glyphs = sorted(set(glyphs), key=glyphMap.__getitem__)
60    return self
61
62
63LOOKUP_FLAG_RIGHT_TO_LEFT = 0x0001
64LOOKUP_FLAG_IGNORE_BASE_GLYPHS = 0x0002
65LOOKUP_FLAG_IGNORE_LIGATURES = 0x0004
66LOOKUP_FLAG_IGNORE_MARKS = 0x0008
67LOOKUP_FLAG_USE_MARK_FILTERING_SET = 0x0010
68
69
70def buildLookup(subtables, flags=0, markFilterSet=None):
71    """Turns a collection of rules into a lookup.
72
73    A Lookup (as defined in the `OpenType Spec <https://docs.microsoft.com/en-gb/typography/opentype/spec/chapter2#lookupTbl>`__)
74    wraps the individual rules in a layout operation (substitution or
75    positioning) in a data structure expressing their overall lookup type -
76    for example, single substitution, mark-to-base attachment, and so on -
77    as well as the lookup flags and any mark filtering sets. You may import
78    the following constants to express lookup flags:
79
80    - ``LOOKUP_FLAG_RIGHT_TO_LEFT``
81    - ``LOOKUP_FLAG_IGNORE_BASE_GLYPHS``
82    - ``LOOKUP_FLAG_IGNORE_LIGATURES``
83    - ``LOOKUP_FLAG_IGNORE_MARKS``
84    - ``LOOKUP_FLAG_USE_MARK_FILTERING_SET``
85
86    Args:
87        subtables: A list of layout subtable objects (e.g.
88            ``MultipleSubst``, ``PairPos``, etc.) or ``None``.
89        flags (int): This lookup's flags.
90        markFilterSet: Either ``None`` if no mark filtering set is used, or
91            an integer representing the filtering set to be used for this
92            lookup. If a mark filtering set is provided,
93            `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
94            flags.
95
96    Returns:
97        An ``otTables.Lookup`` object or ``None`` if there are no subtables
98        supplied.
99    """
100    if subtables is None:
101        return None
102    subtables = [st for st in subtables if st is not None]
103    if not subtables:
104        return None
105    assert all(
106        t.LookupType == subtables[0].LookupType for t in subtables
107    ), "all subtables must have the same LookupType; got %s" % repr(
108        [t.LookupType for t in subtables]
109    )
110    self = ot.Lookup()
111    self.LookupType = subtables[0].LookupType
112    self.LookupFlag = flags
113    self.SubTable = subtables
114    self.SubTableCount = len(self.SubTable)
115    if markFilterSet is not None:
116        self.LookupFlag |= LOOKUP_FLAG_USE_MARK_FILTERING_SET
117        assert isinstance(markFilterSet, int), markFilterSet
118        self.MarkFilteringSet = markFilterSet
119    else:
120        assert (self.LookupFlag & LOOKUP_FLAG_USE_MARK_FILTERING_SET) == 0, (
121            "if markFilterSet is None, flags must not set "
122            "LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x%04x" % flags
123        )
124    return self
125
126
127class LookupBuilder(object):
128    SUBTABLE_BREAK_ = "SUBTABLE_BREAK"
129
130    def __init__(self, font, location, table, lookup_type):
131        self.font = font
132        self.glyphMap = font.getReverseGlyphMap()
133        self.location = location
134        self.table, self.lookup_type = table, lookup_type
135        self.lookupflag = 0
136        self.markFilterSet = None
137        self.lookup_index = None  # assigned when making final tables
138        assert table in ("GPOS", "GSUB")
139
140    def equals(self, other):
141        return (
142            isinstance(other, self.__class__)
143            and self.table == other.table
144            and self.lookupflag == other.lookupflag
145            and self.markFilterSet == other.markFilterSet
146        )
147
148    def inferGlyphClasses(self):
149        """Infers glyph glasses for the GDEF table, such as {"cedilla":3}."""
150        return {}
151
152    def getAlternateGlyphs(self):
153        """Helper for building 'aalt' features."""
154        return {}
155
156    def buildLookup_(self, subtables):
157        return buildLookup(subtables, self.lookupflag, self.markFilterSet)
158
159    def buildMarkClasses_(self, marks):
160        """{"cedilla": ("BOTTOM", ast.Anchor), ...} --> {"BOTTOM":0, "TOP":1}
161
162        Helper for MarkBasePostBuilder, MarkLigPosBuilder, and
163        MarkMarkPosBuilder. Seems to return the same numeric IDs
164        for mark classes as the AFDKO makeotf tool.
165        """
166        ids = {}
167        for mark in sorted(marks.keys(), key=self.font.getGlyphID):
168            markClassName, _markAnchor = marks[mark]
169            if markClassName not in ids:
170                ids[markClassName] = len(ids)
171        return ids
172
173    def setBacktrackCoverage_(self, prefix, subtable):
174        subtable.BacktrackGlyphCount = len(prefix)
175        subtable.BacktrackCoverage = []
176        for p in reversed(prefix):
177            coverage = buildCoverage(p, self.glyphMap)
178            subtable.BacktrackCoverage.append(coverage)
179
180    def setLookAheadCoverage_(self, suffix, subtable):
181        subtable.LookAheadGlyphCount = len(suffix)
182        subtable.LookAheadCoverage = []
183        for s in suffix:
184            coverage = buildCoverage(s, self.glyphMap)
185            subtable.LookAheadCoverage.append(coverage)
186
187    def setInputCoverage_(self, glyphs, subtable):
188        subtable.InputGlyphCount = len(glyphs)
189        subtable.InputCoverage = []
190        for g in glyphs:
191            coverage = buildCoverage(g, self.glyphMap)
192            subtable.InputCoverage.append(coverage)
193
194    def setCoverage_(self, glyphs, subtable):
195        subtable.GlyphCount = len(glyphs)
196        subtable.Coverage = []
197        for g in glyphs:
198            coverage = buildCoverage(g, self.glyphMap)
199            subtable.Coverage.append(coverage)
200
201    def build_subst_subtables(self, mapping, klass):
202        substitutions = [{}]
203        for key in mapping:
204            if key[0] == self.SUBTABLE_BREAK_:
205                substitutions.append({})
206            else:
207                substitutions[-1][key] = mapping[key]
208        subtables = [klass(s) for s in substitutions]
209        return subtables
210
211    def add_subtable_break(self, location):
212        """Add an explicit subtable break.
213
214        Args:
215            location: A string or tuple representing the location in the
216                original source which produced this break, or ``None`` if
217                no location is provided.
218        """
219        log.warning(
220            OpenTypeLibError(
221                'unsupported "subtable" statement for lookup type', location
222            )
223        )
224
225
226class AlternateSubstBuilder(LookupBuilder):
227    """Builds an Alternate Substitution (GSUB3) lookup.
228
229    Users are expected to manually add alternate glyph substitutions to
230    the ``alternates`` attribute after the object has been initialized,
231    e.g.::
232
233        builder.alternates["A"] = ["A.alt1", "A.alt2"]
234
235    Attributes:
236        font (``fontTools.TTLib.TTFont``): A font object.
237        location: A string or tuple representing the location in the original
238            source which produced this lookup.
239        alternates: An ordered dictionary of alternates, mapping glyph names
240            to a list of names of alternates.
241        lookupflag (int): The lookup's flag
242        markFilterSet: Either ``None`` if no mark filtering set is used, or
243            an integer representing the filtering set to be used for this
244            lookup. If a mark filtering set is provided,
245            `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
246            flags.
247    """
248
249    def __init__(self, font, location):
250        LookupBuilder.__init__(self, font, location, "GSUB", 3)
251        self.alternates = OrderedDict()
252
253    def equals(self, other):
254        return LookupBuilder.equals(self, other) and self.alternates == other.alternates
255
256    def build(self):
257        """Build the lookup.
258
259        Returns:
260            An ``otTables.Lookup`` object representing the alternate
261            substitution lookup.
262        """
263        subtables = self.build_subst_subtables(
264            self.alternates, buildAlternateSubstSubtable
265        )
266        return self.buildLookup_(subtables)
267
268    def getAlternateGlyphs(self):
269        return self.alternates
270
271    def add_subtable_break(self, location):
272        self.alternates[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
273
274
275class ChainContextualRule(
276    namedtuple("ChainContextualRule", ["prefix", "glyphs", "suffix", "lookups"])
277):
278    @property
279    def is_subtable_break(self):
280        return self.prefix == LookupBuilder.SUBTABLE_BREAK_
281
282
283class ChainContextualRuleset:
284    def __init__(self):
285        self.rules = []
286
287    def addRule(self, rule):
288        self.rules.append(rule)
289
290    @property
291    def hasPrefixOrSuffix(self):
292        # Do we have any prefixes/suffixes? If this is False for all
293        # rulesets, we can express the whole lookup as GPOS5/GSUB7.
294        for rule in self.rules:
295            if len(rule.prefix) > 0 or len(rule.suffix) > 0:
296                return True
297        return False
298
299    @property
300    def hasAnyGlyphClasses(self):
301        # Do we use glyph classes anywhere in the rules? If this is False
302        # we can express this subtable as a Format 1.
303        for rule in self.rules:
304            for coverage in (rule.prefix, rule.glyphs, rule.suffix):
305                if any(len(x) > 1 for x in coverage):
306                    return True
307        return False
308
309    def format2ClassDefs(self):
310        PREFIX, GLYPHS, SUFFIX = 0, 1, 2
311        classDefBuilders = []
312        for ix in [PREFIX, GLYPHS, SUFFIX]:
313            context = []
314            for r in self.rules:
315                context.append(r[ix])
316            classes = self._classBuilderForContext(context)
317            if not classes:
318                return None
319            classDefBuilders.append(classes)
320        return classDefBuilders
321
322    def _classBuilderForContext(self, context):
323        classdefbuilder = ClassDefBuilder(useClass0=False)
324        for position in context:
325            for glyphset in position:
326                glyphs = set(glyphset)
327                if not classdefbuilder.canAdd(glyphs):
328                    return None
329                classdefbuilder.add(glyphs)
330        return classdefbuilder
331
332
333class ChainContextualBuilder(LookupBuilder):
334    def equals(self, other):
335        return LookupBuilder.equals(self, other) and self.rules == other.rules
336
337    def rulesets(self):
338        # Return a list of ChainContextRuleset objects, taking explicit
339        # subtable breaks into account
340        ruleset = [ChainContextualRuleset()]
341        for rule in self.rules:
342            if rule.is_subtable_break:
343                ruleset.append(ChainContextualRuleset())
344                continue
345            ruleset[-1].addRule(rule)
346        # Squish any empty subtables
347        return [x for x in ruleset if len(x.rules) > 0]
348
349    def getCompiledSize_(self, subtables):
350        size = 0
351        for st in subtables:
352            w = OTTableWriter()
353            w["LookupType"] = CountReference(
354                {"LookupType": st.LookupType}, "LookupType"
355            )
356            # We need to make a copy here because compiling
357            # modifies the subtable (finalizing formats etc.)
358            copy.deepcopy(st).compile(w, self.font)
359            size += len(w.getAllData())
360        return size
361
362    def build(self):
363        """Build the lookup.
364
365        Returns:
366            An ``otTables.Lookup`` object representing the chained
367            contextual positioning lookup.
368        """
369        subtables = []
370        chaining = False
371        rulesets = self.rulesets()
372        chaining = any(ruleset.hasPrefixOrSuffix for ruleset in rulesets)
373        for ruleset in rulesets:
374            # Determine format strategy. We try to build formats 1, 2 and 3
375            # subtables and then work out which is best. candidates list holds
376            # the subtables in each format for this ruleset (including a dummy
377            # "format 0" to make the addressing match the format numbers).
378
379            # We can always build a format 3 lookup by accumulating each of
380            # the rules into a list, so start with that.
381            candidates = [None, None, None, []]
382            for rule in ruleset.rules:
383                candidates[3].append(self.buildFormat3Subtable(rule, chaining))
384
385            # Can we express the whole ruleset as a format 2 subtable?
386            classdefs = ruleset.format2ClassDefs()
387            if classdefs:
388                candidates[2] = [
389                    self.buildFormat2Subtable(ruleset, classdefs, chaining)
390                ]
391
392            if not ruleset.hasAnyGlyphClasses:
393                candidates[1] = [self.buildFormat1Subtable(ruleset, chaining)]
394
395            for i in [1, 2, 3]:
396                if candidates[i]:
397                    try:
398                        self.getCompiledSize_(candidates[i])
399                    except Exception as e:
400                        log.warning(
401                            "Contextual format %i at %s overflowed (%s)"
402                            % (i, str(self.location), e)
403                        )
404                        candidates[i] = None
405
406            candidates = [x for x in candidates if x is not None]
407            if not candidates:
408                raise OpenTypeLibError("All candidates overflowed", self.location)
409
410            winner = min(candidates, key=self.getCompiledSize_)
411            subtables.extend(winner)
412
413        # If we are not chaining, lookup type will be automatically fixed by
414        # buildLookup_
415        return self.buildLookup_(subtables)
416
417    def buildFormat1Subtable(self, ruleset, chaining=True):
418        st = self.newSubtable_(chaining=chaining)
419        st.Format = 1
420        st.populateDefaults()
421        coverage = set()
422        rulesetsByFirstGlyph = {}
423        ruleAttr = self.ruleAttr_(format=1, chaining=chaining)
424
425        for rule in ruleset.rules:
426            ruleAsSubtable = self.newRule_(format=1, chaining=chaining)
427
428            if chaining:
429                ruleAsSubtable.BacktrackGlyphCount = len(rule.prefix)
430                ruleAsSubtable.LookAheadGlyphCount = len(rule.suffix)
431                ruleAsSubtable.Backtrack = [list(x)[0] for x in reversed(rule.prefix)]
432                ruleAsSubtable.LookAhead = [list(x)[0] for x in rule.suffix]
433
434                ruleAsSubtable.InputGlyphCount = len(rule.glyphs)
435            else:
436                ruleAsSubtable.GlyphCount = len(rule.glyphs)
437
438            ruleAsSubtable.Input = [list(x)[0] for x in rule.glyphs[1:]]
439
440            self.buildLookupList(rule, ruleAsSubtable)
441
442            firstGlyph = list(rule.glyphs[0])[0]
443            if firstGlyph not in rulesetsByFirstGlyph:
444                coverage.add(firstGlyph)
445                rulesetsByFirstGlyph[firstGlyph] = []
446            rulesetsByFirstGlyph[firstGlyph].append(ruleAsSubtable)
447
448        st.Coverage = buildCoverage(coverage, self.glyphMap)
449        ruleSets = []
450        for g in st.Coverage.glyphs:
451            ruleSet = self.newRuleSet_(format=1, chaining=chaining)
452            setattr(ruleSet, ruleAttr, rulesetsByFirstGlyph[g])
453            setattr(ruleSet, f"{ruleAttr}Count", len(rulesetsByFirstGlyph[g]))
454            ruleSets.append(ruleSet)
455
456        setattr(st, self.ruleSetAttr_(format=1, chaining=chaining), ruleSets)
457        setattr(
458            st, self.ruleSetAttr_(format=1, chaining=chaining) + "Count", len(ruleSets)
459        )
460
461        return st
462
463    def buildFormat2Subtable(self, ruleset, classdefs, chaining=True):
464        st = self.newSubtable_(chaining=chaining)
465        st.Format = 2
466        st.populateDefaults()
467
468        if chaining:
469            (
470                st.BacktrackClassDef,
471                st.InputClassDef,
472                st.LookAheadClassDef,
473            ) = [c.build() for c in classdefs]
474        else:
475            st.ClassDef = classdefs[1].build()
476
477        inClasses = classdefs[1].classes()
478
479        classSets = []
480        for _ in inClasses:
481            classSet = self.newRuleSet_(format=2, chaining=chaining)
482            classSets.append(classSet)
483
484        coverage = set()
485        classRuleAttr = self.ruleAttr_(format=2, chaining=chaining)
486
487        for rule in ruleset.rules:
488            ruleAsSubtable = self.newRule_(format=2, chaining=chaining)
489            if chaining:
490                ruleAsSubtable.BacktrackGlyphCount = len(rule.prefix)
491                ruleAsSubtable.LookAheadGlyphCount = len(rule.suffix)
492                # The glyphs in the rule may be list, tuple, odict_keys...
493                # Order is not important anyway because they are guaranteed
494                # to be members of the same class.
495                ruleAsSubtable.Backtrack = [
496                    st.BacktrackClassDef.classDefs[list(x)[0]]
497                    for x in reversed(rule.prefix)
498                ]
499                ruleAsSubtable.LookAhead = [
500                    st.LookAheadClassDef.classDefs[list(x)[0]] for x in rule.suffix
501                ]
502
503                ruleAsSubtable.InputGlyphCount = len(rule.glyphs)
504                ruleAsSubtable.Input = [
505                    st.InputClassDef.classDefs[list(x)[0]] for x in rule.glyphs[1:]
506                ]
507                setForThisRule = classSets[
508                    st.InputClassDef.classDefs[list(rule.glyphs[0])[0]]
509                ]
510            else:
511                ruleAsSubtable.GlyphCount = len(rule.glyphs)
512                ruleAsSubtable.Class = [  # The spec calls this InputSequence
513                    st.ClassDef.classDefs[list(x)[0]] for x in rule.glyphs[1:]
514                ]
515                setForThisRule = classSets[
516                    st.ClassDef.classDefs[list(rule.glyphs[0])[0]]
517                ]
518
519            self.buildLookupList(rule, ruleAsSubtable)
520            coverage |= set(rule.glyphs[0])
521
522            getattr(setForThisRule, classRuleAttr).append(ruleAsSubtable)
523            setattr(
524                setForThisRule,
525                f"{classRuleAttr}Count",
526                getattr(setForThisRule, f"{classRuleAttr}Count") + 1,
527            )
528        setattr(st, self.ruleSetAttr_(format=2, chaining=chaining), classSets)
529        setattr(
530            st, self.ruleSetAttr_(format=2, chaining=chaining) + "Count", len(classSets)
531        )
532        st.Coverage = buildCoverage(coverage, self.glyphMap)
533        return st
534
535    def buildFormat3Subtable(self, rule, chaining=True):
536        st = self.newSubtable_(chaining=chaining)
537        st.Format = 3
538        if chaining:
539            self.setBacktrackCoverage_(rule.prefix, st)
540            self.setLookAheadCoverage_(rule.suffix, st)
541            self.setInputCoverage_(rule.glyphs, st)
542        else:
543            self.setCoverage_(rule.glyphs, st)
544        self.buildLookupList(rule, st)
545        return st
546
547    def buildLookupList(self, rule, st):
548        for sequenceIndex, lookupList in enumerate(rule.lookups):
549            if lookupList is not None:
550                if not isinstance(lookupList, list):
551                    # Can happen with synthesised lookups
552                    lookupList = [lookupList]
553                for l in lookupList:
554                    if l.lookup_index is None:
555                        if isinstance(self, ChainContextPosBuilder):
556                            other = "substitution"
557                        else:
558                            other = "positioning"
559                        raise OpenTypeLibError(
560                            "Missing index of the specified "
561                            f"lookup, might be a {other} lookup",
562                            self.location,
563                        )
564                    rec = self.newLookupRecord_(st)
565                    rec.SequenceIndex = sequenceIndex
566                    rec.LookupListIndex = l.lookup_index
567
568    def add_subtable_break(self, location):
569        self.rules.append(
570            ChainContextualRule(
571                self.SUBTABLE_BREAK_,
572                self.SUBTABLE_BREAK_,
573                self.SUBTABLE_BREAK_,
574                [self.SUBTABLE_BREAK_],
575            )
576        )
577
578    def newSubtable_(self, chaining=True):
579        subtablename = f"Context{self.subtable_type}"
580        if chaining:
581            subtablename = "Chain" + subtablename
582        st = getattr(ot, subtablename)()  # ot.ChainContextPos()/ot.ChainSubst()/etc.
583        setattr(st, f"{self.subtable_type}Count", 0)
584        setattr(st, f"{self.subtable_type}LookupRecord", [])
585        return st
586
587    # Format 1 and format 2 GSUB5/GSUB6/GPOS7/GPOS8 rulesets and rules form a family:
588    #
589    #       format 1 ruleset      format 1 rule      format 2 ruleset      format 2 rule
590    # GSUB5 SubRuleSet            SubRule            SubClassSet           SubClassRule
591    # GSUB6 ChainSubRuleSet       ChainSubRule       ChainSubClassSet      ChainSubClassRule
592    # GPOS7 PosRuleSet            PosRule            PosClassSet           PosClassRule
593    # GPOS8 ChainPosRuleSet       ChainPosRule       ChainPosClassSet      ChainPosClassRule
594    #
595    # The following functions generate the attribute names and subtables according
596    # to this naming convention.
597    def ruleSetAttr_(self, format=1, chaining=True):
598        if format == 1:
599            formatType = "Rule"
600        elif format == 2:
601            formatType = "Class"
602        else:
603            raise AssertionError(formatType)
604        subtablename = f"{self.subtable_type[0:3]}{formatType}Set"  # Sub, not Subst.
605        if chaining:
606            subtablename = "Chain" + subtablename
607        return subtablename
608
609    def ruleAttr_(self, format=1, chaining=True):
610        if format == 1:
611            formatType = ""
612        elif format == 2:
613            formatType = "Class"
614        else:
615            raise AssertionError(formatType)
616        subtablename = f"{self.subtable_type[0:3]}{formatType}Rule"  # Sub, not Subst.
617        if chaining:
618            subtablename = "Chain" + subtablename
619        return subtablename
620
621    def newRuleSet_(self, format=1, chaining=True):
622        st = getattr(
623            ot, self.ruleSetAttr_(format, chaining)
624        )()  # ot.ChainPosRuleSet()/ot.SubRuleSet()/etc.
625        st.populateDefaults()
626        return st
627
628    def newRule_(self, format=1, chaining=True):
629        st = getattr(
630            ot, self.ruleAttr_(format, chaining)
631        )()  # ot.ChainPosClassRule()/ot.SubClassRule()/etc.
632        st.populateDefaults()
633        return st
634
635    def attachSubtableWithCount_(
636        self, st, subtable_name, count_name, existing=None, index=None, chaining=False
637    ):
638        if chaining:
639            subtable_name = "Chain" + subtable_name
640            count_name = "Chain" + count_name
641
642        if not hasattr(st, count_name):
643            setattr(st, count_name, 0)
644            setattr(st, subtable_name, [])
645
646        if existing:
647            new_subtable = existing
648        else:
649            # Create a new, empty subtable from otTables
650            new_subtable = getattr(ot, subtable_name)()
651
652        setattr(st, count_name, getattr(st, count_name) + 1)
653
654        if index:
655            getattr(st, subtable_name).insert(index, new_subtable)
656        else:
657            getattr(st, subtable_name).append(new_subtable)
658
659        return new_subtable
660
661    def newLookupRecord_(self, st):
662        return self.attachSubtableWithCount_(
663            st,
664            f"{self.subtable_type}LookupRecord",
665            f"{self.subtable_type}Count",
666            chaining=False,
667        )  # Oddly, it isn't ChainSubstLookupRecord
668
669
670class ChainContextPosBuilder(ChainContextualBuilder):
671    """Builds a Chained Contextual Positioning (GPOS8) lookup.
672
673    Users are expected to manually add rules to the ``rules`` attribute after
674    the object has been initialized, e.g.::
675
676        # pos [A B] [C D] x' lookup lu1 y' z' lookup lu2 E;
677
678        prefix  = [ ["A", "B"], ["C", "D"] ]
679        suffix  = [ ["E"] ]
680        glyphs  = [ ["x"], ["y"], ["z"] ]
681        lookups = [ [lu1], None,  [lu2] ]
682        builder.rules.append( (prefix, glyphs, suffix, lookups) )
683
684    Attributes:
685        font (``fontTools.TTLib.TTFont``): A font object.
686        location: A string or tuple representing the location in the original
687            source which produced this lookup.
688        rules: A list of tuples representing the rules in this lookup.
689        lookupflag (int): The lookup's flag
690        markFilterSet: Either ``None`` if no mark filtering set is used, or
691            an integer representing the filtering set to be used for this
692            lookup. If a mark filtering set is provided,
693            `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
694            flags.
695    """
696
697    def __init__(self, font, location):
698        LookupBuilder.__init__(self, font, location, "GPOS", 8)
699        self.rules = []
700        self.subtable_type = "Pos"
701
702    def find_chainable_single_pos(self, lookups, glyphs, value):
703        """Helper for add_single_pos_chained_()"""
704        res = None
705        for lookup in lookups[::-1]:
706            if lookup == self.SUBTABLE_BREAK_:
707                return res
708            if isinstance(lookup, SinglePosBuilder) and all(
709                lookup.can_add(glyph, value) for glyph in glyphs
710            ):
711                res = lookup
712        return res
713
714
715class ChainContextSubstBuilder(ChainContextualBuilder):
716    """Builds a Chained Contextual Substitution (GSUB6) lookup.
717
718    Users are expected to manually add rules to the ``rules`` attribute after
719    the object has been initialized, e.g.::
720
721        # sub [A B] [C D] x' lookup lu1 y' z' lookup lu2 E;
722
723        prefix  = [ ["A", "B"], ["C", "D"] ]
724        suffix  = [ ["E"] ]
725        glyphs  = [ ["x"], ["y"], ["z"] ]
726        lookups = [ [lu1], None,  [lu2] ]
727        builder.rules.append( (prefix, glyphs, suffix, lookups) )
728
729    Attributes:
730        font (``fontTools.TTLib.TTFont``): A font object.
731        location: A string or tuple representing the location in the original
732            source which produced this lookup.
733        rules: A list of tuples representing the rules in this lookup.
734        lookupflag (int): The lookup's flag
735        markFilterSet: Either ``None`` if no mark filtering set is used, or
736            an integer representing the filtering set to be used for this
737            lookup. If a mark filtering set is provided,
738            `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
739            flags.
740    """
741
742    def __init__(self, font, location):
743        LookupBuilder.__init__(self, font, location, "GSUB", 6)
744        self.rules = []  # (prefix, input, suffix, lookups)
745        self.subtable_type = "Subst"
746
747    def getAlternateGlyphs(self):
748        result = {}
749        for rule in self.rules:
750            if rule.is_subtable_break:
751                continue
752            for lookups in rule.lookups:
753                if not isinstance(lookups, list):
754                    lookups = [lookups]
755                for lookup in lookups:
756                    if lookup is not None:
757                        alts = lookup.getAlternateGlyphs()
758                        for glyph, replacements in alts.items():
759                            result.setdefault(glyph, set()).update(replacements)
760        return result
761
762    def find_chainable_single_subst(self, glyphs):
763        """Helper for add_single_subst_chained_()"""
764        res = None
765        for rule in self.rules[::-1]:
766            if rule.is_subtable_break:
767                return res
768            for sub in rule.lookups:
769                if isinstance(sub, SingleSubstBuilder) and not any(
770                    g in glyphs for g in sub.mapping.keys()
771                ):
772                    res = sub
773        return res
774
775
776class LigatureSubstBuilder(LookupBuilder):
777    """Builds a Ligature Substitution (GSUB4) lookup.
778
779    Users are expected to manually add ligatures to the ``ligatures``
780    attribute after the object has been initialized, e.g.::
781
782        # sub f i by f_i;
783        builder.ligatures[("f","f","i")] = "f_f_i"
784
785    Attributes:
786        font (``fontTools.TTLib.TTFont``): A font object.
787        location: A string or tuple representing the location in the original
788            source which produced this lookup.
789        ligatures: An ordered dictionary mapping a tuple of glyph names to the
790            ligature glyphname.
791        lookupflag (int): The lookup's flag
792        markFilterSet: Either ``None`` if no mark filtering set is used, or
793            an integer representing the filtering set to be used for this
794            lookup. If a mark filtering set is provided,
795            `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
796            flags.
797    """
798
799    def __init__(self, font, location):
800        LookupBuilder.__init__(self, font, location, "GSUB", 4)
801        self.ligatures = OrderedDict()  # {('f','f','i'): 'f_f_i'}
802
803    def equals(self, other):
804        return LookupBuilder.equals(self, other) and self.ligatures == other.ligatures
805
806    def build(self):
807        """Build the lookup.
808
809        Returns:
810            An ``otTables.Lookup`` object representing the ligature
811            substitution lookup.
812        """
813        subtables = self.build_subst_subtables(
814            self.ligatures, buildLigatureSubstSubtable
815        )
816        return self.buildLookup_(subtables)
817
818    def add_subtable_break(self, location):
819        self.ligatures[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
820
821
822class MultipleSubstBuilder(LookupBuilder):
823    """Builds a Multiple Substitution (GSUB2) lookup.
824
825    Users are expected to manually add substitutions to the ``mapping``
826    attribute after the object has been initialized, e.g.::
827
828        # sub uni06C0 by uni06D5.fina hamza.above;
829        builder.mapping["uni06C0"] = [ "uni06D5.fina", "hamza.above"]
830
831    Attributes:
832        font (``fontTools.TTLib.TTFont``): A font object.
833        location: A string or tuple representing the location in the original
834            source which produced this lookup.
835        mapping: An ordered dictionary mapping a glyph name to a list of
836            substituted glyph names.
837        lookupflag (int): The lookup's flag
838        markFilterSet: Either ``None`` if no mark filtering set is used, or
839            an integer representing the filtering set to be used for this
840            lookup. If a mark filtering set is provided,
841            `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
842            flags.
843    """
844
845    def __init__(self, font, location):
846        LookupBuilder.__init__(self, font, location, "GSUB", 2)
847        self.mapping = OrderedDict()
848
849    def equals(self, other):
850        return LookupBuilder.equals(self, other) and self.mapping == other.mapping
851
852    def build(self):
853        subtables = self.build_subst_subtables(self.mapping, buildMultipleSubstSubtable)
854        return self.buildLookup_(subtables)
855
856    def add_subtable_break(self, location):
857        self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
858
859
860class CursivePosBuilder(LookupBuilder):
861    """Builds a Cursive Positioning (GPOS3) lookup.
862
863    Attributes:
864        font (``fontTools.TTLib.TTFont``): A font object.
865        location: A string or tuple representing the location in the original
866            source which produced this lookup.
867        attachments: An ordered dictionary mapping a glyph name to a two-element
868            tuple of ``otTables.Anchor`` objects.
869        lookupflag (int): The lookup's flag
870        markFilterSet: Either ``None`` if no mark filtering set is used, or
871            an integer representing the filtering set to be used for this
872            lookup. If a mark filtering set is provided,
873            `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
874            flags.
875    """
876
877    def __init__(self, font, location):
878        LookupBuilder.__init__(self, font, location, "GPOS", 3)
879        self.attachments = {}
880
881    def equals(self, other):
882        return (
883            LookupBuilder.equals(self, other) and self.attachments == other.attachments
884        )
885
886    def add_attachment(self, location, glyphs, entryAnchor, exitAnchor):
887        """Adds attachment information to the cursive positioning lookup.
888
889        Args:
890            location: A string or tuple representing the location in the
891                original source which produced this lookup. (Unused.)
892            glyphs: A list of glyph names sharing these entry and exit
893                anchor locations.
894            entryAnchor: A ``otTables.Anchor`` object representing the
895                entry anchor, or ``None`` if no entry anchor is present.
896            exitAnchor: A ``otTables.Anchor`` object representing the
897                exit anchor, or ``None`` if no exit anchor is present.
898        """
899        for glyph in glyphs:
900            self.attachments[glyph] = (entryAnchor, exitAnchor)
901
902    def build(self):
903        """Build the lookup.
904
905        Returns:
906            An ``otTables.Lookup`` object representing the cursive
907            positioning lookup.
908        """
909        st = buildCursivePosSubtable(self.attachments, self.glyphMap)
910        return self.buildLookup_([st])
911
912
913class MarkBasePosBuilder(LookupBuilder):
914    """Builds a Mark-To-Base Positioning (GPOS4) lookup.
915
916    Users are expected to manually add marks and bases to the ``marks``
917    and ``bases`` attributes after the object has been initialized, e.g.::
918
919        builder.marks["acute"]   = (0, a1)
920        builder.marks["grave"]   = (0, a1)
921        builder.marks["cedilla"] = (1, a2)
922        builder.bases["a"] = {0: a3, 1: a5}
923        builder.bases["b"] = {0: a4, 1: a5}
924
925    Attributes:
926        font (``fontTools.TTLib.TTFont``): A font object.
927        location: A string or tuple representing the location in the original
928            source which produced this lookup.
929        marks: An dictionary mapping a glyph name to a two-element
930            tuple containing a mark class ID and ``otTables.Anchor`` object.
931        bases: An dictionary mapping a glyph name to a dictionary of
932            mark class IDs and ``otTables.Anchor`` object.
933        lookupflag (int): The lookup's flag
934        markFilterSet: Either ``None`` if no mark filtering set is used, or
935            an integer representing the filtering set to be used for this
936            lookup. If a mark filtering set is provided,
937            `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
938            flags.
939    """
940
941    def __init__(self, font, location):
942        LookupBuilder.__init__(self, font, location, "GPOS", 4)
943        self.marks = {}  # glyphName -> (markClassName, anchor)
944        self.bases = {}  # glyphName -> {markClassName: anchor}
945
946    def equals(self, other):
947        return (
948            LookupBuilder.equals(self, other)
949            and self.marks == other.marks
950            and self.bases == other.bases
951        )
952
953    def inferGlyphClasses(self):
954        result = {glyph: 1 for glyph in self.bases}
955        result.update({glyph: 3 for glyph in self.marks})
956        return result
957
958    def build(self):
959        """Build the lookup.
960
961        Returns:
962            An ``otTables.Lookup`` object representing the mark-to-base
963            positioning lookup.
964        """
965        markClasses = self.buildMarkClasses_(self.marks)
966        marks = {}
967        for mark, (mc, anchor) in self.marks.items():
968            if mc not in markClasses:
969                raise ValueError(
970                    "Mark class %s not found for mark glyph %s" % (mc, mark)
971                )
972            marks[mark] = (markClasses[mc], anchor)
973        bases = {}
974        for glyph, anchors in self.bases.items():
975            bases[glyph] = {}
976            for mc, anchor in anchors.items():
977                if mc not in markClasses:
978                    raise ValueError(
979                        "Mark class %s not found for base glyph %s" % (mc, mark)
980                    )
981                bases[glyph][markClasses[mc]] = anchor
982        subtables = buildMarkBasePos(marks, bases, self.glyphMap)
983        return self.buildLookup_(subtables)
984
985
986class MarkLigPosBuilder(LookupBuilder):
987    """Builds a Mark-To-Ligature Positioning (GPOS5) lookup.
988
989    Users are expected to manually add marks and bases to the ``marks``
990    and ``ligatures`` attributes after the object has been initialized, e.g.::
991
992        builder.marks["acute"]   = (0, a1)
993        builder.marks["grave"]   = (0, a1)
994        builder.marks["cedilla"] = (1, a2)
995        builder.ligatures["f_i"] = [
996            { 0: a3, 1: a5 }, # f
997            { 0: a4, 1: a5 }  # i
998        ]
999
1000    Attributes:
1001        font (``fontTools.TTLib.TTFont``): A font object.
1002        location: A string or tuple representing the location in the original
1003            source which produced this lookup.
1004        marks: An dictionary mapping a glyph name to a two-element
1005            tuple containing a mark class ID and ``otTables.Anchor`` object.
1006        ligatures: An dictionary mapping a glyph name to an array with one
1007            element for each ligature component. Each array element should be
1008            a dictionary mapping mark class IDs to ``otTables.Anchor`` objects.
1009        lookupflag (int): The lookup's flag
1010        markFilterSet: Either ``None`` if no mark filtering set is used, or
1011            an integer representing the filtering set to be used for this
1012            lookup. If a mark filtering set is provided,
1013            `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
1014            flags.
1015    """
1016
1017    def __init__(self, font, location):
1018        LookupBuilder.__init__(self, font, location, "GPOS", 5)
1019        self.marks = {}  # glyphName -> (markClassName, anchor)
1020        self.ligatures = {}  # glyphName -> [{markClassName: anchor}, ...]
1021
1022    def equals(self, other):
1023        return (
1024            LookupBuilder.equals(self, other)
1025            and self.marks == other.marks
1026            and self.ligatures == other.ligatures
1027        )
1028
1029    def inferGlyphClasses(self):
1030        result = {glyph: 2 for glyph in self.ligatures}
1031        result.update({glyph: 3 for glyph in self.marks})
1032        return result
1033
1034    def build(self):
1035        """Build the lookup.
1036
1037        Returns:
1038            An ``otTables.Lookup`` object representing the mark-to-ligature
1039            positioning lookup.
1040        """
1041        markClasses = self.buildMarkClasses_(self.marks)
1042        marks = {
1043            mark: (markClasses[mc], anchor) for mark, (mc, anchor) in self.marks.items()
1044        }
1045        ligs = {}
1046        for lig, components in self.ligatures.items():
1047            ligs[lig] = []
1048            for c in components:
1049                ligs[lig].append({markClasses[mc]: a for mc, a in c.items()})
1050        subtables = buildMarkLigPos(marks, ligs, self.glyphMap)
1051        return self.buildLookup_(subtables)
1052
1053
1054class MarkMarkPosBuilder(LookupBuilder):
1055    """Builds a Mark-To-Mark Positioning (GPOS6) lookup.
1056
1057    Users are expected to manually add marks and bases to the ``marks``
1058    and ``baseMarks`` attributes after the object has been initialized, e.g.::
1059
1060        builder.marks["acute"]     = (0, a1)
1061        builder.marks["grave"]     = (0, a1)
1062        builder.marks["cedilla"]   = (1, a2)
1063        builder.baseMarks["acute"] = {0: a3}
1064
1065    Attributes:
1066        font (``fontTools.TTLib.TTFont``): A font object.
1067        location: A string or tuple representing the location in the original
1068            source which produced this lookup.
1069        marks: An dictionary mapping a glyph name to a two-element
1070            tuple containing a mark class ID and ``otTables.Anchor`` object.
1071        baseMarks: An dictionary mapping a glyph name to a dictionary
1072            containing one item: a mark class ID and a ``otTables.Anchor`` object.
1073        lookupflag (int): The lookup's flag
1074        markFilterSet: Either ``None`` if no mark filtering set is used, or
1075            an integer representing the filtering set to be used for this
1076            lookup. If a mark filtering set is provided,
1077            `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
1078            flags.
1079    """
1080
1081    def __init__(self, font, location):
1082        LookupBuilder.__init__(self, font, location, "GPOS", 6)
1083        self.marks = {}  # glyphName -> (markClassName, anchor)
1084        self.baseMarks = {}  # glyphName -> {markClassName: anchor}
1085
1086    def equals(self, other):
1087        return (
1088            LookupBuilder.equals(self, other)
1089            and self.marks == other.marks
1090            and self.baseMarks == other.baseMarks
1091        )
1092
1093    def inferGlyphClasses(self):
1094        result = {glyph: 3 for glyph in self.baseMarks}
1095        result.update({glyph: 3 for glyph in self.marks})
1096        return result
1097
1098    def build(self):
1099        """Build the lookup.
1100
1101        Returns:
1102            An ``otTables.Lookup`` object representing the mark-to-mark
1103            positioning lookup.
1104        """
1105        markClasses = self.buildMarkClasses_(self.marks)
1106        markClassList = sorted(markClasses.keys(), key=markClasses.get)
1107        marks = {
1108            mark: (markClasses[mc], anchor) for mark, (mc, anchor) in self.marks.items()
1109        }
1110
1111        st = ot.MarkMarkPos()
1112        st.Format = 1
1113        st.ClassCount = len(markClasses)
1114        st.Mark1Coverage = buildCoverage(marks, self.glyphMap)
1115        st.Mark2Coverage = buildCoverage(self.baseMarks, self.glyphMap)
1116        st.Mark1Array = buildMarkArray(marks, self.glyphMap)
1117        st.Mark2Array = ot.Mark2Array()
1118        st.Mark2Array.Mark2Count = len(st.Mark2Coverage.glyphs)
1119        st.Mark2Array.Mark2Record = []
1120        for base in st.Mark2Coverage.glyphs:
1121            anchors = [self.baseMarks[base].get(mc) for mc in markClassList]
1122            st.Mark2Array.Mark2Record.append(buildMark2Record(anchors))
1123        return self.buildLookup_([st])
1124
1125
1126class ReverseChainSingleSubstBuilder(LookupBuilder):
1127    """Builds a Reverse Chaining Contextual Single Substitution (GSUB8) lookup.
1128
1129    Users are expected to manually add substitutions to the ``substitutions``
1130    attribute after the object has been initialized, e.g.::
1131
1132        # reversesub [a e n] d' by d.alt;
1133        prefix = [ ["a", "e", "n"] ]
1134        suffix = []
1135        mapping = { "d": "d.alt" }
1136        builder.substitutions.append( (prefix, suffix, mapping) )
1137
1138    Attributes:
1139        font (``fontTools.TTLib.TTFont``): A font object.
1140        location: A string or tuple representing the location in the original
1141            source which produced this lookup.
1142        substitutions: A three-element tuple consisting of a prefix sequence,
1143            a suffix sequence, and a dictionary of single substitutions.
1144        lookupflag (int): The lookup's flag
1145        markFilterSet: Either ``None`` if no mark filtering set is used, or
1146            an integer representing the filtering set to be used for this
1147            lookup. If a mark filtering set is provided,
1148            `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
1149            flags.
1150    """
1151
1152    def __init__(self, font, location):
1153        LookupBuilder.__init__(self, font, location, "GSUB", 8)
1154        self.rules = []  # (prefix, suffix, mapping)
1155
1156    def equals(self, other):
1157        return LookupBuilder.equals(self, other) and self.rules == other.rules
1158
1159    def build(self):
1160        """Build the lookup.
1161
1162        Returns:
1163            An ``otTables.Lookup`` object representing the chained
1164            contextual substitution lookup.
1165        """
1166        subtables = []
1167        for prefix, suffix, mapping in self.rules:
1168            st = ot.ReverseChainSingleSubst()
1169            st.Format = 1
1170            self.setBacktrackCoverage_(prefix, st)
1171            self.setLookAheadCoverage_(suffix, st)
1172            st.Coverage = buildCoverage(mapping.keys(), self.glyphMap)
1173            st.GlyphCount = len(mapping)
1174            st.Substitute = [mapping[g] for g in st.Coverage.glyphs]
1175            subtables.append(st)
1176        return self.buildLookup_(subtables)
1177
1178    def add_subtable_break(self, location):
1179        # Nothing to do here, each substitution is in its own subtable.
1180        pass
1181
1182
1183class SingleSubstBuilder(LookupBuilder):
1184    """Builds a Single Substitution (GSUB1) lookup.
1185
1186    Users are expected to manually add substitutions to the ``mapping``
1187    attribute after the object has been initialized, e.g.::
1188
1189        # sub x by y;
1190        builder.mapping["x"] = "y"
1191
1192    Attributes:
1193        font (``fontTools.TTLib.TTFont``): A font object.
1194        location: A string or tuple representing the location in the original
1195            source which produced this lookup.
1196        mapping: A dictionary mapping a single glyph name to another glyph name.
1197        lookupflag (int): The lookup's flag
1198        markFilterSet: Either ``None`` if no mark filtering set is used, or
1199            an integer representing the filtering set to be used for this
1200            lookup. If a mark filtering set is provided,
1201            `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
1202            flags.
1203    """
1204
1205    def __init__(self, font, location):
1206        LookupBuilder.__init__(self, font, location, "GSUB", 1)
1207        self.mapping = OrderedDict()
1208
1209    def equals(self, other):
1210        return LookupBuilder.equals(self, other) and self.mapping == other.mapping
1211
1212    def build(self):
1213        """Build the lookup.
1214
1215        Returns:
1216            An ``otTables.Lookup`` object representing the multiple
1217            substitution lookup.
1218        """
1219        subtables = self.build_subst_subtables(self.mapping, buildSingleSubstSubtable)
1220        return self.buildLookup_(subtables)
1221
1222    def getAlternateGlyphs(self):
1223        return {glyph: set([repl]) for glyph, repl in self.mapping.items()}
1224
1225    def add_subtable_break(self, location):
1226        self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
1227
1228
1229class ClassPairPosSubtableBuilder(object):
1230    """Builds class-based Pair Positioning (GPOS2 format 2) subtables.
1231
1232    Note that this does *not* build a GPOS2 ``otTables.Lookup`` directly,
1233    but builds a list of ``otTables.PairPos`` subtables. It is used by the
1234    :class:`PairPosBuilder` below.
1235
1236    Attributes:
1237        builder (PairPosBuilder): A pair positioning lookup builder.
1238    """
1239
1240    def __init__(self, builder):
1241        self.builder_ = builder
1242        self.classDef1_, self.classDef2_ = None, None
1243        self.values_ = {}  # (glyphclass1, glyphclass2) --> (value1, value2)
1244        self.forceSubtableBreak_ = False
1245        self.subtables_ = []
1246
1247    def addPair(self, gc1, value1, gc2, value2):
1248        """Add a pair positioning rule.
1249
1250        Args:
1251            gc1: A set of glyph names for the "left" glyph
1252            value1: An ``otTables.ValueRecord`` object for the left glyph's
1253                positioning.
1254            gc2: A set of glyph names for the "right" glyph
1255            value2: An ``otTables.ValueRecord`` object for the right glyph's
1256                positioning.
1257        """
1258        mergeable = (
1259            not self.forceSubtableBreak_
1260            and self.classDef1_ is not None
1261            and self.classDef1_.canAdd(gc1)
1262            and self.classDef2_ is not None
1263            and self.classDef2_.canAdd(gc2)
1264        )
1265        if not mergeable:
1266            self.flush_()
1267            self.classDef1_ = ClassDefBuilder(useClass0=True)
1268            self.classDef2_ = ClassDefBuilder(useClass0=False)
1269            self.values_ = {}
1270        self.classDef1_.add(gc1)
1271        self.classDef2_.add(gc2)
1272        self.values_[(gc1, gc2)] = (value1, value2)
1273
1274    def addSubtableBreak(self):
1275        """Add an explicit subtable break at this point."""
1276        self.forceSubtableBreak_ = True
1277
1278    def subtables(self):
1279        """Return the list of ``otTables.PairPos`` subtables constructed."""
1280        self.flush_()
1281        return self.subtables_
1282
1283    def flush_(self):
1284        if self.classDef1_ is None or self.classDef2_ is None:
1285            return
1286        st = buildPairPosClassesSubtable(self.values_, self.builder_.glyphMap)
1287        if st.Coverage is None:
1288            return
1289        self.subtables_.append(st)
1290        self.forceSubtableBreak_ = False
1291
1292
1293class PairPosBuilder(LookupBuilder):
1294    """Builds a Pair Positioning (GPOS2) lookup.
1295
1296    Attributes:
1297        font (``fontTools.TTLib.TTFont``): A font object.
1298        location: A string or tuple representing the location in the original
1299            source which produced this lookup.
1300        pairs: An array of class-based pair positioning tuples. Usually
1301            manipulated with the :meth:`addClassPair` method below.
1302        glyphPairs: A dictionary mapping a tuple of glyph names to a tuple
1303            of ``otTables.ValueRecord`` objects. Usually manipulated with the
1304            :meth:`addGlyphPair` method below.
1305        lookupflag (int): The lookup's flag
1306        markFilterSet: Either ``None`` if no mark filtering set is used, or
1307            an integer representing the filtering set to be used for this
1308            lookup. If a mark filtering set is provided,
1309            `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
1310            flags.
1311    """
1312
1313    def __init__(self, font, location):
1314        LookupBuilder.__init__(self, font, location, "GPOS", 2)
1315        self.pairs = []  # [(gc1, value1, gc2, value2)*]
1316        self.glyphPairs = {}  # (glyph1, glyph2) --> (value1, value2)
1317        self.locations = {}  # (gc1, gc2) --> (filepath, line, column)
1318
1319    def addClassPair(self, location, glyphclass1, value1, glyphclass2, value2):
1320        """Add a class pair positioning rule to the current lookup.
1321
1322        Args:
1323            location: A string or tuple representing the location in the
1324                original source which produced this rule. Unused.
1325            glyphclass1: A set of glyph names for the "left" glyph in the pair.
1326            value1: A ``otTables.ValueRecord`` for positioning the left glyph.
1327            glyphclass2: A set of glyph names for the "right" glyph in the pair.
1328            value2: A ``otTables.ValueRecord`` for positioning the right glyph.
1329        """
1330        self.pairs.append((glyphclass1, value1, glyphclass2, value2))
1331
1332    def addGlyphPair(self, location, glyph1, value1, glyph2, value2):
1333        """Add a glyph pair positioning rule to the current lookup.
1334
1335        Args:
1336            location: A string or tuple representing the location in the
1337                original source which produced this rule.
1338            glyph1: A glyph name for the "left" glyph in the pair.
1339            value1: A ``otTables.ValueRecord`` for positioning the left glyph.
1340            glyph2: A glyph name for the "right" glyph in the pair.
1341            value2: A ``otTables.ValueRecord`` for positioning the right glyph.
1342        """
1343        key = (glyph1, glyph2)
1344        oldValue = self.glyphPairs.get(key, None)
1345        if oldValue is not None:
1346            # the Feature File spec explicitly allows specific pairs generated
1347            # by an 'enum' rule to be overridden by preceding single pairs
1348            otherLoc = self.locations[key]
1349            log.debug(
1350                "Already defined position for pair %s %s at %s; "
1351                "choosing the first value",
1352                glyph1,
1353                glyph2,
1354                otherLoc,
1355            )
1356        else:
1357            self.glyphPairs[key] = (value1, value2)
1358            self.locations[key] = location
1359
1360    def add_subtable_break(self, location):
1361        self.pairs.append(
1362            (
1363                self.SUBTABLE_BREAK_,
1364                self.SUBTABLE_BREAK_,
1365                self.SUBTABLE_BREAK_,
1366                self.SUBTABLE_BREAK_,
1367            )
1368        )
1369
1370    def equals(self, other):
1371        return (
1372            LookupBuilder.equals(self, other)
1373            and self.glyphPairs == other.glyphPairs
1374            and self.pairs == other.pairs
1375        )
1376
1377    def build(self):
1378        """Build the lookup.
1379
1380        Returns:
1381            An ``otTables.Lookup`` object representing the pair positioning
1382            lookup.
1383        """
1384        builders = {}
1385        builder = None
1386        for glyphclass1, value1, glyphclass2, value2 in self.pairs:
1387            if glyphclass1 is self.SUBTABLE_BREAK_:
1388                if builder is not None:
1389                    builder.addSubtableBreak()
1390                continue
1391            valFormat1, valFormat2 = 0, 0
1392            if value1:
1393                valFormat1 = value1.getFormat()
1394            if value2:
1395                valFormat2 = value2.getFormat()
1396            builder = builders.get((valFormat1, valFormat2))
1397            if builder is None:
1398                builder = ClassPairPosSubtableBuilder(self)
1399                builders[(valFormat1, valFormat2)] = builder
1400            builder.addPair(glyphclass1, value1, glyphclass2, value2)
1401        subtables = []
1402        if self.glyphPairs:
1403            subtables.extend(buildPairPosGlyphs(self.glyphPairs, self.glyphMap))
1404        for key in sorted(builders.keys()):
1405            subtables.extend(builders[key].subtables())
1406        lookup = self.buildLookup_(subtables)
1407
1408        # Compact the lookup
1409        # This is a good moment to do it because the compaction should create
1410        # smaller subtables, which may prevent overflows from happening.
1411        mode = os.environ.get(GPOS_COMPACT_MODE_ENV_KEY, GPOS_COMPACT_MODE_DEFAULT)
1412        if mode and mode != "0":
1413            log.info("Compacting GPOS...")
1414            compact_lookup(self.font, mode, lookup)
1415
1416        return lookup
1417
1418
1419class SinglePosBuilder(LookupBuilder):
1420    """Builds a Single Positioning (GPOS1) lookup.
1421
1422    Attributes:
1423        font (``fontTools.TTLib.TTFont``): A font object.
1424        location: A string or tuple representing the location in the original
1425            source which produced this lookup.
1426        mapping: A dictionary mapping a glyph name to a ``otTables.ValueRecord``
1427            objects. Usually manipulated with the :meth:`add_pos` method below.
1428        lookupflag (int): The lookup's flag
1429        markFilterSet: Either ``None`` if no mark filtering set is used, or
1430            an integer representing the filtering set to be used for this
1431            lookup. If a mark filtering set is provided,
1432            `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
1433            flags.
1434    """
1435
1436    def __init__(self, font, location):
1437        LookupBuilder.__init__(self, font, location, "GPOS", 1)
1438        self.locations = {}  # glyph -> (filename, line, column)
1439        self.mapping = {}  # glyph -> ot.ValueRecord
1440
1441    def add_pos(self, location, glyph, otValueRecord):
1442        """Add a single positioning rule.
1443
1444        Args:
1445            location: A string or tuple representing the location in the
1446                original source which produced this lookup.
1447            glyph: A glyph name.
1448            otValueRection: A ``otTables.ValueRecord`` used to position the
1449                glyph.
1450        """
1451        if not self.can_add(glyph, otValueRecord):
1452            otherLoc = self.locations[glyph]
1453            raise OpenTypeLibError(
1454                'Already defined different position for glyph "%s" at %s'
1455                % (glyph, otherLoc),
1456                location,
1457            )
1458        if otValueRecord:
1459            self.mapping[glyph] = otValueRecord
1460        self.locations[glyph] = location
1461
1462    def can_add(self, glyph, value):
1463        assert isinstance(value, ValueRecord)
1464        curValue = self.mapping.get(glyph)
1465        return curValue is None or curValue == value
1466
1467    def equals(self, other):
1468        return LookupBuilder.equals(self, other) and self.mapping == other.mapping
1469
1470    def build(self):
1471        """Build the lookup.
1472
1473        Returns:
1474            An ``otTables.Lookup`` object representing the single positioning
1475            lookup.
1476        """
1477        subtables = buildSinglePos(self.mapping, self.glyphMap)
1478        return self.buildLookup_(subtables)
1479
1480
1481# GSUB
1482
1483
1484def buildSingleSubstSubtable(mapping):
1485    """Builds a single substitution (GSUB1) subtable.
1486
1487    Note that if you are implementing a layout compiler, you may find it more
1488    flexible to use
1489    :py:class:`fontTools.otlLib.lookupBuilders.SingleSubstBuilder` instead.
1490
1491    Args:
1492        mapping: A dictionary mapping input glyph names to output glyph names.
1493
1494    Returns:
1495        An ``otTables.SingleSubst`` object, or ``None`` if the mapping dictionary
1496        is empty.
1497    """
1498    if not mapping:
1499        return None
1500    self = ot.SingleSubst()
1501    self.mapping = dict(mapping)
1502    return self
1503
1504
1505def buildMultipleSubstSubtable(mapping):
1506    """Builds a multiple substitution (GSUB2) subtable.
1507
1508    Note that if you are implementing a layout compiler, you may find it more
1509    flexible to use
1510    :py:class:`fontTools.otlLib.lookupBuilders.MultipleSubstBuilder` instead.
1511
1512    Example::
1513
1514        # sub uni06C0 by uni06D5.fina hamza.above
1515        # sub uni06C2 by uni06C1.fina hamza.above;
1516
1517        subtable = buildMultipleSubstSubtable({
1518            "uni06C0": [ "uni06D5.fina", "hamza.above"],
1519            "uni06C2": [ "uni06D1.fina", "hamza.above"]
1520        })
1521
1522    Args:
1523        mapping: A dictionary mapping input glyph names to a list of output
1524            glyph names.
1525
1526    Returns:
1527        An ``otTables.MultipleSubst`` object or ``None`` if the mapping dictionary
1528        is empty.
1529    """
1530    if not mapping:
1531        return None
1532    self = ot.MultipleSubst()
1533    self.mapping = dict(mapping)
1534    return self
1535
1536
1537def buildAlternateSubstSubtable(mapping):
1538    """Builds an alternate substitution (GSUB3) subtable.
1539
1540    Note that if you are implementing a layout compiler, you may find it more
1541    flexible to use
1542    :py:class:`fontTools.otlLib.lookupBuilders.AlternateSubstBuilder` instead.
1543
1544    Args:
1545        mapping: A dictionary mapping input glyph names to a list of output
1546            glyph names.
1547
1548    Returns:
1549        An ``otTables.AlternateSubst`` object or ``None`` if the mapping dictionary
1550        is empty.
1551    """
1552    if not mapping:
1553        return None
1554    self = ot.AlternateSubst()
1555    self.alternates = dict(mapping)
1556    return self
1557
1558
1559def _getLigatureKey(components):
1560    # Computes a key for ordering ligatures in a GSUB Type-4 lookup.
1561
1562    # When building the OpenType lookup, we need to make sure that
1563    # the longest sequence of components is listed first, so we
1564    # use the negative length as the primary key for sorting.
1565    # To make buildLigatureSubstSubtable() deterministic, we use the
1566    # component sequence as the secondary key.
1567
1568    # For example, this will sort (f,f,f) < (f,f,i) < (f,f) < (f,i) < (f,l).
1569    return (-len(components), components)
1570
1571
1572def buildLigatureSubstSubtable(mapping):
1573    """Builds a ligature substitution (GSUB4) subtable.
1574
1575    Note that if you are implementing a layout compiler, you may find it more
1576    flexible to use
1577    :py:class:`fontTools.otlLib.lookupBuilders.LigatureSubstBuilder` instead.
1578
1579    Example::
1580
1581        # sub f f i by f_f_i;
1582        # sub f i by f_i;
1583
1584        subtable = buildLigatureSubstSubtable({
1585            ("f", "f", "i"): "f_f_i",
1586            ("f", "i"): "f_i",
1587        })
1588
1589    Args:
1590        mapping: A dictionary mapping tuples of glyph names to output
1591            glyph names.
1592
1593    Returns:
1594        An ``otTables.LigatureSubst`` object or ``None`` if the mapping dictionary
1595        is empty.
1596    """
1597
1598    if not mapping:
1599        return None
1600    self = ot.LigatureSubst()
1601    # The following single line can replace the rest of this function
1602    # with fontTools >= 3.1:
1603    # self.ligatures = dict(mapping)
1604    self.ligatures = {}
1605    for components in sorted(mapping.keys(), key=_getLigatureKey):
1606        ligature = ot.Ligature()
1607        ligature.Component = components[1:]
1608        ligature.CompCount = len(ligature.Component) + 1
1609        ligature.LigGlyph = mapping[components]
1610        firstGlyph = components[0]
1611        self.ligatures.setdefault(firstGlyph, []).append(ligature)
1612    return self
1613
1614
1615# GPOS
1616
1617
1618def buildAnchor(x, y, point=None, deviceX=None, deviceY=None):
1619    """Builds an Anchor table.
1620
1621    This determines the appropriate anchor format based on the passed parameters.
1622
1623    Args:
1624        x (int): X coordinate.
1625        y (int): Y coordinate.
1626        point (int): Index of glyph contour point, if provided.
1627        deviceX (``otTables.Device``): X coordinate device table, if provided.
1628        deviceY (``otTables.Device``): Y coordinate device table, if provided.
1629
1630    Returns:
1631        An ``otTables.Anchor`` object.
1632    """
1633    self = ot.Anchor()
1634    self.XCoordinate, self.YCoordinate = x, y
1635    self.Format = 1
1636    if point is not None:
1637        self.AnchorPoint = point
1638        self.Format = 2
1639    if deviceX is not None or deviceY is not None:
1640        assert (
1641            self.Format == 1
1642        ), "Either point, or both of deviceX/deviceY, must be None."
1643        self.XDeviceTable = deviceX
1644        self.YDeviceTable = deviceY
1645        self.Format = 3
1646    return self
1647
1648
1649def buildBaseArray(bases, numMarkClasses, glyphMap):
1650    """Builds a base array record.
1651
1652    As part of building mark-to-base positioning rules, you will need to define
1653    a ``BaseArray`` record, which "defines for each base glyph an array of
1654    anchors, one for each mark class." This function builds the base array
1655    subtable.
1656
1657    Example::
1658
1659        bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}}
1660        basearray = buildBaseArray(bases, 2, font.getReverseGlyphMap())
1661
1662    Args:
1663        bases (dict): A dictionary mapping anchors to glyphs; the keys being
1664            glyph names, and the values being dictionaries mapping mark class ID
1665            to the appropriate ``otTables.Anchor`` object used for attaching marks
1666            of that class.
1667        numMarkClasses (int): The total number of mark classes for which anchors
1668            are defined.
1669        glyphMap: a glyph name to ID map, typically returned from
1670            ``font.getReverseGlyphMap()``.
1671
1672    Returns:
1673        An ``otTables.BaseArray`` object.
1674    """
1675    self = ot.BaseArray()
1676    self.BaseRecord = []
1677    for base in sorted(bases, key=glyphMap.__getitem__):
1678        b = bases[base]
1679        anchors = [b.get(markClass) for markClass in range(numMarkClasses)]
1680        self.BaseRecord.append(buildBaseRecord(anchors))
1681    self.BaseCount = len(self.BaseRecord)
1682    return self
1683
1684
1685def buildBaseRecord(anchors):
1686    # [otTables.Anchor, otTables.Anchor, ...] --> otTables.BaseRecord
1687    self = ot.BaseRecord()
1688    self.BaseAnchor = anchors
1689    return self
1690
1691
1692def buildComponentRecord(anchors):
1693    """Builds a component record.
1694
1695    As part of building mark-to-ligature positioning rules, you will need to
1696    define ``ComponentRecord`` objects, which contain "an array of offsets...
1697    to the Anchor tables that define all the attachment points used to attach
1698    marks to the component." This function builds the component record.
1699
1700    Args:
1701        anchors: A list of ``otTables.Anchor`` objects or ``None``.
1702
1703    Returns:
1704        A ``otTables.ComponentRecord`` object or ``None`` if no anchors are
1705        supplied.
1706    """
1707    if not anchors:
1708        return None
1709    self = ot.ComponentRecord()
1710    self.LigatureAnchor = anchors
1711    return self
1712
1713
1714def buildCursivePosSubtable(attach, glyphMap):
1715    """Builds a cursive positioning (GPOS3) subtable.
1716
1717    Cursive positioning lookups are made up of a coverage table of glyphs,
1718    and a set of ``EntryExitRecord`` records containing the anchors for
1719    each glyph. This function builds the cursive positioning subtable.
1720
1721    Example::
1722
1723        subtable = buildCursivePosSubtable({
1724            "AlifIni": (None, buildAnchor(0, 50)),
1725            "BehMed": (buildAnchor(500,250), buildAnchor(0,50)),
1726            # ...
1727        }, font.getReverseGlyphMap())
1728
1729    Args:
1730        attach (dict): A mapping between glyph names and a tuple of two
1731            ``otTables.Anchor`` objects representing entry and exit anchors.
1732        glyphMap: a glyph name to ID map, typically returned from
1733            ``font.getReverseGlyphMap()``.
1734
1735    Returns:
1736        An ``otTables.CursivePos`` object, or ``None`` if the attachment
1737        dictionary was empty.
1738    """
1739    if not attach:
1740        return None
1741    self = ot.CursivePos()
1742    self.Format = 1
1743    self.Coverage = buildCoverage(attach.keys(), glyphMap)
1744    self.EntryExitRecord = []
1745    for glyph in self.Coverage.glyphs:
1746        entryAnchor, exitAnchor = attach[glyph]
1747        rec = ot.EntryExitRecord()
1748        rec.EntryAnchor = entryAnchor
1749        rec.ExitAnchor = exitAnchor
1750        self.EntryExitRecord.append(rec)
1751    self.EntryExitCount = len(self.EntryExitRecord)
1752    return self
1753
1754
1755def buildDevice(deltas):
1756    """Builds a Device record as part of a ValueRecord or Anchor.
1757
1758    Device tables specify size-specific adjustments to value records
1759    and anchors to reflect changes based on the resolution of the output.
1760    For example, one could specify that an anchor's Y position should be
1761    increased by 1 pixel when displayed at 8 pixels per em. This routine
1762    builds device records.
1763
1764    Args:
1765        deltas: A dictionary mapping pixels-per-em sizes to the delta
1766            adjustment in pixels when the font is displayed at that size.
1767
1768    Returns:
1769        An ``otTables.Device`` object if any deltas were supplied, or
1770        ``None`` otherwise.
1771    """
1772    if not deltas:
1773        return None
1774    self = ot.Device()
1775    keys = deltas.keys()
1776    self.StartSize = startSize = min(keys)
1777    self.EndSize = endSize = max(keys)
1778    assert 0 <= startSize <= endSize
1779    self.DeltaValue = deltaValues = [
1780        deltas.get(size, 0) for size in range(startSize, endSize + 1)
1781    ]
1782    maxDelta = max(deltaValues)
1783    minDelta = min(deltaValues)
1784    assert minDelta > -129 and maxDelta < 128
1785    if minDelta > -3 and maxDelta < 2:
1786        self.DeltaFormat = 1
1787    elif minDelta > -9 and maxDelta < 8:
1788        self.DeltaFormat = 2
1789    else:
1790        self.DeltaFormat = 3
1791    return self
1792
1793
1794def buildLigatureArray(ligs, numMarkClasses, glyphMap):
1795    """Builds a LigatureArray subtable.
1796
1797    As part of building a mark-to-ligature lookup, you will need to define
1798    the set of anchors (for each mark class) on each component of the ligature
1799    where marks can be attached. For example, for an Arabic divine name ligature
1800    (lam lam heh), you may want to specify mark attachment positioning for
1801    superior marks (fatha, etc.) and inferior marks (kasra, etc.) on each glyph
1802    of the ligature. This routine builds the ligature array record.
1803
1804    Example::
1805
1806        buildLigatureArray({
1807            "lam-lam-heh": [
1808                { 0: superiorAnchor1, 1: inferiorAnchor1 }, # attach points for lam1
1809                { 0: superiorAnchor2, 1: inferiorAnchor2 }, # attach points for lam2
1810                { 0: superiorAnchor3, 1: inferiorAnchor3 }, # attach points for heh
1811            ]
1812        }, 2, font.getReverseGlyphMap())
1813
1814    Args:
1815        ligs (dict): A mapping of ligature names to an array of dictionaries:
1816            for each component glyph in the ligature, an dictionary mapping
1817            mark class IDs to anchors.
1818        numMarkClasses (int): The number of mark classes.
1819        glyphMap: a glyph name to ID map, typically returned from
1820            ``font.getReverseGlyphMap()``.
1821
1822    Returns:
1823        An ``otTables.LigatureArray`` object if deltas were supplied.
1824    """
1825    self = ot.LigatureArray()
1826    self.LigatureAttach = []
1827    for lig in sorted(ligs, key=glyphMap.__getitem__):
1828        anchors = []
1829        for component in ligs[lig]:
1830            anchors.append([component.get(mc) for mc in range(numMarkClasses)])
1831        self.LigatureAttach.append(buildLigatureAttach(anchors))
1832    self.LigatureCount = len(self.LigatureAttach)
1833    return self
1834
1835
1836def buildLigatureAttach(components):
1837    # [[Anchor, Anchor], [Anchor, Anchor, Anchor]] --> LigatureAttach
1838    self = ot.LigatureAttach()
1839    self.ComponentRecord = [buildComponentRecord(c) for c in components]
1840    self.ComponentCount = len(self.ComponentRecord)
1841    return self
1842
1843
1844def buildMarkArray(marks, glyphMap):
1845    """Builds a mark array subtable.
1846
1847    As part of building mark-to-* positioning rules, you will need to define
1848    a MarkArray subtable, which "defines the class and the anchor point
1849    for a mark glyph." This function builds the mark array subtable.
1850
1851    Example::
1852
1853        mark = {
1854            "acute": (0, buildAnchor(300,712)),
1855            # ...
1856        }
1857        markarray = buildMarkArray(marks, font.getReverseGlyphMap())
1858
1859    Args:
1860        marks (dict): A dictionary mapping anchors to glyphs; the keys being
1861            glyph names, and the values being a tuple of mark class number and
1862            an ``otTables.Anchor`` object representing the mark's attachment
1863            point.
1864        glyphMap: a glyph name to ID map, typically returned from
1865            ``font.getReverseGlyphMap()``.
1866
1867    Returns:
1868        An ``otTables.MarkArray`` object.
1869    """
1870    self = ot.MarkArray()
1871    self.MarkRecord = []
1872    for mark in sorted(marks.keys(), key=glyphMap.__getitem__):
1873        markClass, anchor = marks[mark]
1874        markrec = buildMarkRecord(markClass, anchor)
1875        self.MarkRecord.append(markrec)
1876    self.MarkCount = len(self.MarkRecord)
1877    return self
1878
1879
1880def buildMarkBasePos(marks, bases, glyphMap):
1881    """Build a list of MarkBasePos (GPOS4) subtables.
1882
1883    This routine turns a set of marks and bases into a list of mark-to-base
1884    positioning subtables. Currently the list will contain a single subtable
1885    containing all marks and bases, although at a later date it may return the
1886    optimal list of subtables subsetting the marks and bases into groups which
1887    save space. See :func:`buildMarkBasePosSubtable` below.
1888
1889    Note that if you are implementing a layout compiler, you may find it more
1890    flexible to use
1891    :py:class:`fontTools.otlLib.lookupBuilders.MarkBasePosBuilder` instead.
1892
1893    Example::
1894
1895        # a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
1896
1897        marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)}
1898        bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}}
1899        markbaseposes = buildMarkBasePos(marks, bases, font.getReverseGlyphMap())
1900
1901    Args:
1902        marks (dict): A dictionary mapping anchors to glyphs; the keys being
1903            glyph names, and the values being a tuple of mark class number and
1904            an ``otTables.Anchor`` object representing the mark's attachment
1905            point. (See :func:`buildMarkArray`.)
1906        bases (dict): A dictionary mapping anchors to glyphs; the keys being
1907            glyph names, and the values being dictionaries mapping mark class ID
1908            to the appropriate ``otTables.Anchor`` object used for attaching marks
1909            of that class. (See :func:`buildBaseArray`.)
1910        glyphMap: a glyph name to ID map, typically returned from
1911            ``font.getReverseGlyphMap()``.
1912
1913    Returns:
1914        A list of ``otTables.MarkBasePos`` objects.
1915    """
1916    # TODO: Consider emitting multiple subtables to save space.
1917    # Partition the marks and bases into disjoint subsets, so that
1918    # MarkBasePos rules would only access glyphs from a single
1919    # subset. This would likely lead to smaller mark/base
1920    # matrices, so we might be able to omit many of the empty
1921    # anchor tables that we currently produce. Of course, this
1922    # would only work if the MarkBasePos rules of real-world fonts
1923    # allow partitioning into multiple subsets. We should find out
1924    # whether this is the case; if so, implement the optimization.
1925    # On the other hand, a very large number of subtables could
1926    # slow down layout engines; so this would need profiling.
1927    return [buildMarkBasePosSubtable(marks, bases, glyphMap)]
1928
1929
1930def buildMarkBasePosSubtable(marks, bases, glyphMap):
1931    """Build a single MarkBasePos (GPOS4) subtable.
1932
1933    This builds a mark-to-base lookup subtable containing all of the referenced
1934    marks and bases. See :func:`buildMarkBasePos`.
1935
1936    Args:
1937        marks (dict): A dictionary mapping anchors to glyphs; the keys being
1938            glyph names, and the values being a tuple of mark class number and
1939            an ``otTables.Anchor`` object representing the mark's attachment
1940            point. (See :func:`buildMarkArray`.)
1941        bases (dict): A dictionary mapping anchors to glyphs; the keys being
1942            glyph names, and the values being dictionaries mapping mark class ID
1943            to the appropriate ``otTables.Anchor`` object used for attaching marks
1944            of that class. (See :func:`buildBaseArray`.)
1945        glyphMap: a glyph name to ID map, typically returned from
1946            ``font.getReverseGlyphMap()``.
1947
1948    Returns:
1949        A ``otTables.MarkBasePos`` object.
1950    """
1951    self = ot.MarkBasePos()
1952    self.Format = 1
1953    self.MarkCoverage = buildCoverage(marks, glyphMap)
1954    self.MarkArray = buildMarkArray(marks, glyphMap)
1955    self.ClassCount = max([mc for mc, _ in marks.values()]) + 1
1956    self.BaseCoverage = buildCoverage(bases, glyphMap)
1957    self.BaseArray = buildBaseArray(bases, self.ClassCount, glyphMap)
1958    return self
1959
1960
1961def buildMarkLigPos(marks, ligs, glyphMap):
1962    """Build a list of MarkLigPos (GPOS5) subtables.
1963
1964    This routine turns a set of marks and ligatures into a list of mark-to-ligature
1965    positioning subtables. Currently the list will contain a single subtable
1966    containing all marks and ligatures, although at a later date it may return
1967    the optimal list of subtables subsetting the marks and ligatures into groups
1968    which save space. See :func:`buildMarkLigPosSubtable` below.
1969
1970    Note that if you are implementing a layout compiler, you may find it more
1971    flexible to use
1972    :py:class:`fontTools.otlLib.lookupBuilders.MarkLigPosBuilder` instead.
1973
1974    Example::
1975
1976        # a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
1977        marks = {
1978            "acute": (0, a1),
1979            "grave": (0, a1),
1980            "cedilla": (1, a2)
1981        }
1982        ligs = {
1983            "f_i": [
1984                { 0: a3, 1: a5 }, # f
1985                { 0: a4, 1: a5 }  # i
1986                ],
1987        #   "c_t": [{...}, {...}]
1988        }
1989        markligposes = buildMarkLigPos(marks, ligs,
1990            font.getReverseGlyphMap())
1991
1992    Args:
1993        marks (dict): A dictionary mapping anchors to glyphs; the keys being
1994            glyph names, and the values being a tuple of mark class number and
1995            an ``otTables.Anchor`` object representing the mark's attachment
1996            point. (See :func:`buildMarkArray`.)
1997        ligs (dict): A mapping of ligature names to an array of dictionaries:
1998            for each component glyph in the ligature, an dictionary mapping
1999            mark class IDs to anchors. (See :func:`buildLigatureArray`.)
2000        glyphMap: a glyph name to ID map, typically returned from
2001            ``font.getReverseGlyphMap()``.
2002
2003    Returns:
2004        A list of ``otTables.MarkLigPos`` objects.
2005
2006    """
2007    # TODO: Consider splitting into multiple subtables to save space,
2008    # as with MarkBasePos, this would be a trade-off that would need
2009    # profiling. And, depending on how typical fonts are structured,
2010    # it might not be worth doing at all.
2011    return [buildMarkLigPosSubtable(marks, ligs, glyphMap)]
2012
2013
2014def buildMarkLigPosSubtable(marks, ligs, glyphMap):
2015    """Build a single MarkLigPos (GPOS5) subtable.
2016
2017    This builds a mark-to-base lookup subtable containing all of the referenced
2018    marks and bases. See :func:`buildMarkLigPos`.
2019
2020    Args:
2021        marks (dict): A dictionary mapping anchors to glyphs; the keys being
2022            glyph names, and the values being a tuple of mark class number and
2023            an ``otTables.Anchor`` object representing the mark's attachment
2024            point. (See :func:`buildMarkArray`.)
2025        ligs (dict): A mapping of ligature names to an array of dictionaries:
2026            for each component glyph in the ligature, an dictionary mapping
2027            mark class IDs to anchors. (See :func:`buildLigatureArray`.)
2028        glyphMap: a glyph name to ID map, typically returned from
2029            ``font.getReverseGlyphMap()``.
2030
2031    Returns:
2032        A ``otTables.MarkLigPos`` object.
2033    """
2034    self = ot.MarkLigPos()
2035    self.Format = 1
2036    self.MarkCoverage = buildCoverage(marks, glyphMap)
2037    self.MarkArray = buildMarkArray(marks, glyphMap)
2038    self.ClassCount = max([mc for mc, _ in marks.values()]) + 1
2039    self.LigatureCoverage = buildCoverage(ligs, glyphMap)
2040    self.LigatureArray = buildLigatureArray(ligs, self.ClassCount, glyphMap)
2041    return self
2042
2043
2044def buildMarkRecord(classID, anchor):
2045    assert isinstance(classID, int)
2046    assert isinstance(anchor, ot.Anchor)
2047    self = ot.MarkRecord()
2048    self.Class = classID
2049    self.MarkAnchor = anchor
2050    return self
2051
2052
2053def buildMark2Record(anchors):
2054    # [otTables.Anchor, otTables.Anchor, ...] --> otTables.Mark2Record
2055    self = ot.Mark2Record()
2056    self.Mark2Anchor = anchors
2057    return self
2058
2059
2060def _getValueFormat(f, values, i):
2061    # Helper for buildPairPos{Glyphs|Classes}Subtable.
2062    if f is not None:
2063        return f
2064    mask = 0
2065    for value in values:
2066        if value is not None and value[i] is not None:
2067            mask |= value[i].getFormat()
2068    return mask
2069
2070
2071def buildPairPosClassesSubtable(pairs, glyphMap, valueFormat1=None, valueFormat2=None):
2072    """Builds a class pair adjustment (GPOS2 format 2) subtable.
2073
2074    Kerning tables are generally expressed as pair positioning tables using
2075    class-based pair adjustments. This routine builds format 2 PairPos
2076    subtables.
2077
2078    Note that if you are implementing a layout compiler, you may find it more
2079    flexible to use
2080    :py:class:`fontTools.otlLib.lookupBuilders.ClassPairPosSubtableBuilder`
2081    instead, as this takes care of ensuring that the supplied pairs can be
2082    formed into non-overlapping classes and emitting individual subtables
2083    whenever the non-overlapping requirement means that a new subtable is
2084    required.
2085
2086    Example::
2087
2088        pairs = {}
2089
2090        pairs[(
2091            [ "K", "X" ],
2092            [ "W", "V" ]
2093        )] = ( buildValue(xAdvance=+5), buildValue() )
2094        # pairs[(... , ...)] = (..., ...)
2095
2096        pairpos = buildPairPosClassesSubtable(pairs, font.getReverseGlyphMap())
2097
2098    Args:
2099        pairs (dict): Pair positioning data; the keys being a two-element
2100            tuple of lists of glyphnames, and the values being a two-element
2101            tuple of ``otTables.ValueRecord`` objects.
2102        glyphMap: a glyph name to ID map, typically returned from
2103            ``font.getReverseGlyphMap()``.
2104        valueFormat1: Force the "left" value records to the given format.
2105        valueFormat2: Force the "right" value records to the given format.
2106
2107    Returns:
2108        A ``otTables.PairPos`` object.
2109    """
2110    coverage = set()
2111    classDef1 = ClassDefBuilder(useClass0=True)
2112    classDef2 = ClassDefBuilder(useClass0=False)
2113    for gc1, gc2 in sorted(pairs):
2114        coverage.update(gc1)
2115        classDef1.add(gc1)
2116        classDef2.add(gc2)
2117    self = ot.PairPos()
2118    self.Format = 2
2119    valueFormat1 = self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0)
2120    valueFormat2 = self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1)
2121    self.Coverage = buildCoverage(coverage, glyphMap)
2122    self.ClassDef1 = classDef1.build()
2123    self.ClassDef2 = classDef2.build()
2124    classes1 = classDef1.classes()
2125    classes2 = classDef2.classes()
2126    self.Class1Record = []
2127    for c1 in classes1:
2128        rec1 = ot.Class1Record()
2129        rec1.Class2Record = []
2130        self.Class1Record.append(rec1)
2131        for c2 in classes2:
2132            rec2 = ot.Class2Record()
2133            val1, val2 = pairs.get((c1, c2), (None, None))
2134            rec2.Value1 = (
2135                ValueRecord(src=val1, valueFormat=valueFormat1)
2136                if valueFormat1
2137                else None
2138            )
2139            rec2.Value2 = (
2140                ValueRecord(src=val2, valueFormat=valueFormat2)
2141                if valueFormat2
2142                else None
2143            )
2144            rec1.Class2Record.append(rec2)
2145    self.Class1Count = len(self.Class1Record)
2146    self.Class2Count = len(classes2)
2147    return self
2148
2149
2150def buildPairPosGlyphs(pairs, glyphMap):
2151    """Builds a list of glyph-based pair adjustment (GPOS2 format 1) subtables.
2152
2153    This organises a list of pair positioning adjustments into subtables based
2154    on common value record formats.
2155
2156    Note that if you are implementing a layout compiler, you may find it more
2157    flexible to use
2158    :py:class:`fontTools.otlLib.lookupBuilders.PairPosBuilder`
2159    instead.
2160
2161    Example::
2162
2163        pairs = {
2164            ("K", "W"): ( buildValue(xAdvance=+5), buildValue() ),
2165            ("K", "V"): ( buildValue(xAdvance=+5), buildValue() ),
2166            # ...
2167        }
2168
2169        subtables = buildPairPosGlyphs(pairs, font.getReverseGlyphMap())
2170
2171    Args:
2172        pairs (dict): Pair positioning data; the keys being a two-element
2173            tuple of glyphnames, and the values being a two-element
2174            tuple of ``otTables.ValueRecord`` objects.
2175        glyphMap: a glyph name to ID map, typically returned from
2176            ``font.getReverseGlyphMap()``.
2177
2178    Returns:
2179        A list of ``otTables.PairPos`` objects.
2180    """
2181
2182    p = {}  # (formatA, formatB) --> {(glyphA, glyphB): (valA, valB)}
2183    for (glyphA, glyphB), (valA, valB) in pairs.items():
2184        formatA = valA.getFormat() if valA is not None else 0
2185        formatB = valB.getFormat() if valB is not None else 0
2186        pos = p.setdefault((formatA, formatB), {})
2187        pos[(glyphA, glyphB)] = (valA, valB)
2188    return [
2189        buildPairPosGlyphsSubtable(pos, glyphMap, formatA, formatB)
2190        for ((formatA, formatB), pos) in sorted(p.items())
2191    ]
2192
2193
2194def buildPairPosGlyphsSubtable(pairs, glyphMap, valueFormat1=None, valueFormat2=None):
2195    """Builds a single glyph-based pair adjustment (GPOS2 format 1) subtable.
2196
2197    This builds a PairPos subtable from a dictionary of glyph pairs and
2198    their positioning adjustments. See also :func:`buildPairPosGlyphs`.
2199
2200    Note that if you are implementing a layout compiler, you may find it more
2201    flexible to use
2202    :py:class:`fontTools.otlLib.lookupBuilders.PairPosBuilder` instead.
2203
2204    Example::
2205
2206        pairs = {
2207            ("K", "W"): ( buildValue(xAdvance=+5), buildValue() ),
2208            ("K", "V"): ( buildValue(xAdvance=+5), buildValue() ),
2209            # ...
2210        }
2211
2212        pairpos = buildPairPosGlyphsSubtable(pairs, font.getReverseGlyphMap())
2213
2214    Args:
2215        pairs (dict): Pair positioning data; the keys being a two-element
2216            tuple of glyphnames, and the values being a two-element
2217            tuple of ``otTables.ValueRecord`` objects.
2218        glyphMap: a glyph name to ID map, typically returned from
2219            ``font.getReverseGlyphMap()``.
2220        valueFormat1: Force the "left" value records to the given format.
2221        valueFormat2: Force the "right" value records to the given format.
2222
2223    Returns:
2224        A ``otTables.PairPos`` object.
2225    """
2226    self = ot.PairPos()
2227    self.Format = 1
2228    valueFormat1 = self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0)
2229    valueFormat2 = self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1)
2230    p = {}
2231    for (glyphA, glyphB), (valA, valB) in pairs.items():
2232        p.setdefault(glyphA, []).append((glyphB, valA, valB))
2233    self.Coverage = buildCoverage({g for g, _ in pairs.keys()}, glyphMap)
2234    self.PairSet = []
2235    for glyph in self.Coverage.glyphs:
2236        ps = ot.PairSet()
2237        ps.PairValueRecord = []
2238        self.PairSet.append(ps)
2239        for glyph2, val1, val2 in sorted(p[glyph], key=lambda x: glyphMap[x[0]]):
2240            pvr = ot.PairValueRecord()
2241            pvr.SecondGlyph = glyph2
2242            pvr.Value1 = (
2243                ValueRecord(src=val1, valueFormat=valueFormat1)
2244                if valueFormat1
2245                else None
2246            )
2247            pvr.Value2 = (
2248                ValueRecord(src=val2, valueFormat=valueFormat2)
2249                if valueFormat2
2250                else None
2251            )
2252            ps.PairValueRecord.append(pvr)
2253        ps.PairValueCount = len(ps.PairValueRecord)
2254    self.PairSetCount = len(self.PairSet)
2255    return self
2256
2257
2258def buildSinglePos(mapping, glyphMap):
2259    """Builds a list of single adjustment (GPOS1) subtables.
2260
2261    This builds a list of SinglePos subtables from a dictionary of glyph
2262    names and their positioning adjustments. The format of the subtables are
2263    determined to optimize the size of the resulting subtables.
2264    See also :func:`buildSinglePosSubtable`.
2265
2266    Note that if you are implementing a layout compiler, you may find it more
2267    flexible to use
2268    :py:class:`fontTools.otlLib.lookupBuilders.SinglePosBuilder` instead.
2269
2270    Example::
2271
2272        mapping = {
2273            "V": buildValue({ "xAdvance" : +5 }),
2274            # ...
2275        }
2276
2277        subtables = buildSinglePos(pairs, font.getReverseGlyphMap())
2278
2279    Args:
2280        mapping (dict): A mapping between glyphnames and
2281            ``otTables.ValueRecord`` objects.
2282        glyphMap: a glyph name to ID map, typically returned from
2283            ``font.getReverseGlyphMap()``.
2284
2285    Returns:
2286        A list of ``otTables.SinglePos`` objects.
2287    """
2288    result, handled = [], set()
2289    # In SinglePos format 1, the covered glyphs all share the same ValueRecord.
2290    # In format 2, each glyph has its own ValueRecord, but these records
2291    # all have the same properties (eg., all have an X but no Y placement).
2292    coverages, masks, values = {}, {}, {}
2293    for glyph, value in mapping.items():
2294        key = _getSinglePosValueKey(value)
2295        coverages.setdefault(key, []).append(glyph)
2296        masks.setdefault(key[0], []).append(key)
2297        values[key] = value
2298
2299    # If a ValueRecord is shared between multiple glyphs, we generate
2300    # a SinglePos format 1 subtable; that is the most compact form.
2301    for key, glyphs in coverages.items():
2302        # 5 ushorts is the length of introducing another sublookup
2303        if len(glyphs) * _getSinglePosValueSize(key) > 5:
2304            format1Mapping = {g: values[key] for g in glyphs}
2305            result.append(buildSinglePosSubtable(format1Mapping, glyphMap))
2306            handled.add(key)
2307
2308    # In the remaining ValueRecords, look for those whose valueFormat
2309    # (the set of used properties) is shared between multiple records.
2310    # These will get encoded in format 2.
2311    for valueFormat, keys in masks.items():
2312        f2 = [k for k in keys if k not in handled]
2313        if len(f2) > 1:
2314            format2Mapping = {}
2315            for k in f2:
2316                format2Mapping.update((g, values[k]) for g in coverages[k])
2317            result.append(buildSinglePosSubtable(format2Mapping, glyphMap))
2318            handled.update(f2)
2319
2320    # The remaining ValueRecords are only used by a few glyphs, normally
2321    # one. We encode these in format 1 again.
2322    for key, glyphs in coverages.items():
2323        if key not in handled:
2324            for g in glyphs:
2325                st = buildSinglePosSubtable({g: values[key]}, glyphMap)
2326            result.append(st)
2327
2328    # When the OpenType layout engine traverses the subtables, it will
2329    # stop after the first matching subtable.  Therefore, we sort the
2330    # resulting subtables by decreasing coverage size; this increases
2331    # the chance that the layout engine can do an early exit. (Of course,
2332    # this would only be true if all glyphs were equally frequent, which
2333    # is not really the case; but we do not know their distribution).
2334    # If two subtables cover the same number of glyphs, we sort them
2335    # by glyph ID so that our output is deterministic.
2336    result.sort(key=lambda t: _getSinglePosTableKey(t, glyphMap))
2337    return result
2338
2339
2340def buildSinglePosSubtable(values, glyphMap):
2341    """Builds a single adjustment (GPOS1) subtable.
2342
2343    This builds a list of SinglePos subtables from a dictionary of glyph
2344    names and their positioning adjustments. The format of the subtable is
2345    determined to optimize the size of the output.
2346    See also :func:`buildSinglePos`.
2347
2348    Note that if you are implementing a layout compiler, you may find it more
2349    flexible to use
2350    :py:class:`fontTools.otlLib.lookupBuilders.SinglePosBuilder` instead.
2351
2352    Example::
2353
2354        mapping = {
2355            "V": buildValue({ "xAdvance" : +5 }),
2356            # ...
2357        }
2358
2359        subtable = buildSinglePos(pairs, font.getReverseGlyphMap())
2360
2361    Args:
2362        mapping (dict): A mapping between glyphnames and
2363            ``otTables.ValueRecord`` objects.
2364        glyphMap: a glyph name to ID map, typically returned from
2365            ``font.getReverseGlyphMap()``.
2366
2367    Returns:
2368        A ``otTables.SinglePos`` object.
2369    """
2370    self = ot.SinglePos()
2371    self.Coverage = buildCoverage(values.keys(), glyphMap)
2372    valueFormat = self.ValueFormat = reduce(
2373        int.__or__, [v.getFormat() for v in values.values()], 0
2374    )
2375    valueRecords = [
2376        ValueRecord(src=values[g], valueFormat=valueFormat)
2377        for g in self.Coverage.glyphs
2378    ]
2379    if all(v == valueRecords[0] for v in valueRecords):
2380        self.Format = 1
2381        if self.ValueFormat != 0:
2382            self.Value = valueRecords[0]
2383        else:
2384            self.Value = None
2385    else:
2386        self.Format = 2
2387        self.Value = valueRecords
2388        self.ValueCount = len(self.Value)
2389    return self
2390
2391
2392def _getSinglePosTableKey(subtable, glyphMap):
2393    assert isinstance(subtable, ot.SinglePos), subtable
2394    glyphs = subtable.Coverage.glyphs
2395    return (-len(glyphs), glyphMap[glyphs[0]])
2396
2397
2398def _getSinglePosValueKey(valueRecord):
2399    # otBase.ValueRecord --> (2, ("YPlacement": 12))
2400    assert isinstance(valueRecord, ValueRecord), valueRecord
2401    valueFormat, result = 0, []
2402    for name, value in valueRecord.__dict__.items():
2403        if isinstance(value, ot.Device):
2404            result.append((name, _makeDeviceTuple(value)))
2405        else:
2406            result.append((name, value))
2407        valueFormat |= valueRecordFormatDict[name][0]
2408    result.sort()
2409    result.insert(0, valueFormat)
2410    return tuple(result)
2411
2412
2413_DeviceTuple = namedtuple("_DeviceTuple", "DeltaFormat StartSize EndSize DeltaValue")
2414
2415
2416def _makeDeviceTuple(device):
2417    # otTables.Device --> tuple, for making device tables unique
2418    return _DeviceTuple(
2419        device.DeltaFormat,
2420        device.StartSize,
2421        device.EndSize,
2422        () if device.DeltaFormat & 0x8000 else tuple(device.DeltaValue),
2423    )
2424
2425
2426def _getSinglePosValueSize(valueKey):
2427    # Returns how many ushorts this valueKey (short form of ValueRecord) takes up
2428    count = 0
2429    for _, v in valueKey[1:]:
2430        if isinstance(v, _DeviceTuple):
2431            count += len(v.DeltaValue) + 3
2432        else:
2433            count += 1
2434    return count
2435
2436
2437def buildValue(value):
2438    """Builds a positioning value record.
2439
2440    Value records are used to specify coordinates and adjustments for
2441    positioning and attaching glyphs. Many of the positioning functions
2442    in this library take ``otTables.ValueRecord`` objects as arguments.
2443    This function builds value records from dictionaries.
2444
2445    Args:
2446        value (dict): A dictionary with zero or more of the following keys:
2447            - ``xPlacement``
2448            - ``yPlacement``
2449            - ``xAdvance``
2450            - ``yAdvance``
2451            - ``xPlaDevice``
2452            - ``yPlaDevice``
2453            - ``xAdvDevice``
2454            - ``yAdvDevice``
2455
2456    Returns:
2457        An ``otTables.ValueRecord`` object.
2458    """
2459    self = ValueRecord()
2460    for k, v in value.items():
2461        setattr(self, k, v)
2462    return self
2463
2464
2465# GDEF
2466
2467
2468def buildAttachList(attachPoints, glyphMap):
2469    """Builds an AttachList subtable.
2470
2471    A GDEF table may contain an Attachment Point List table (AttachList)
2472    which stores the contour indices of attachment points for glyphs with
2473    attachment points. This routine builds AttachList subtables.
2474
2475    Args:
2476        attachPoints (dict): A mapping between glyph names and a list of
2477            contour indices.
2478
2479    Returns:
2480        An ``otTables.AttachList`` object if attachment points are supplied,
2481            or ``None`` otherwise.
2482    """
2483    if not attachPoints:
2484        return None
2485    self = ot.AttachList()
2486    self.Coverage = buildCoverage(attachPoints.keys(), glyphMap)
2487    self.AttachPoint = [buildAttachPoint(attachPoints[g]) for g in self.Coverage.glyphs]
2488    self.GlyphCount = len(self.AttachPoint)
2489    return self
2490
2491
2492def buildAttachPoint(points):
2493    # [4, 23, 41] --> otTables.AttachPoint
2494    # Only used by above.
2495    if not points:
2496        return None
2497    self = ot.AttachPoint()
2498    self.PointIndex = sorted(set(points))
2499    self.PointCount = len(self.PointIndex)
2500    return self
2501
2502
2503def buildCaretValueForCoord(coord):
2504    # 500 --> otTables.CaretValue, format 1
2505    self = ot.CaretValue()
2506    self.Format = 1
2507    self.Coordinate = coord
2508    return self
2509
2510
2511def buildCaretValueForPoint(point):
2512    # 4 --> otTables.CaretValue, format 2
2513    self = ot.CaretValue()
2514    self.Format = 2
2515    self.CaretValuePoint = point
2516    return self
2517
2518
2519def buildLigCaretList(coords, points, glyphMap):
2520    """Builds a ligature caret list table.
2521
2522    Ligatures appear as a single glyph representing multiple characters; however
2523    when, for example, editing text containing a ``f_i`` ligature, the user may
2524    want to place the cursor between the ``f`` and the ``i``. The ligature caret
2525    list in the GDEF table specifies the position to display the "caret" (the
2526    character insertion indicator, typically a flashing vertical bar) "inside"
2527    the ligature to represent an insertion point. The insertion positions may
2528    be specified either by coordinate or by contour point.
2529
2530    Example::
2531
2532        coords = {
2533            "f_f_i": [300, 600] # f|fi cursor at 300 units, ff|i cursor at 600.
2534        }
2535        points = {
2536            "c_t": [28] # c|t cursor appears at coordinate of contour point 28.
2537        }
2538        ligcaretlist = buildLigCaretList(coords, points, font.getReverseGlyphMap())
2539
2540    Args:
2541        coords: A mapping between glyph names and a list of coordinates for
2542            the insertion point of each ligature component after the first one.
2543        points: A mapping between glyph names and a list of contour points for
2544            the insertion point of each ligature component after the first one.
2545        glyphMap: a glyph name to ID map, typically returned from
2546            ``font.getReverseGlyphMap()``.
2547
2548    Returns:
2549        A ``otTables.LigCaretList`` object if any carets are present, or
2550            ``None`` otherwise."""
2551    glyphs = set(coords.keys()) if coords else set()
2552    if points:
2553        glyphs.update(points.keys())
2554    carets = {g: buildLigGlyph(coords.get(g), points.get(g)) for g in glyphs}
2555    carets = {g: c for g, c in carets.items() if c is not None}
2556    if not carets:
2557        return None
2558    self = ot.LigCaretList()
2559    self.Coverage = buildCoverage(carets.keys(), glyphMap)
2560    self.LigGlyph = [carets[g] for g in self.Coverage.glyphs]
2561    self.LigGlyphCount = len(self.LigGlyph)
2562    return self
2563
2564
2565def buildLigGlyph(coords, points):
2566    # ([500], [4]) --> otTables.LigGlyph; None for empty coords/points
2567    carets = []
2568    if coords:
2569        carets.extend([buildCaretValueForCoord(c) for c in sorted(coords)])
2570    if points:
2571        carets.extend([buildCaretValueForPoint(p) for p in sorted(points)])
2572    if not carets:
2573        return None
2574    self = ot.LigGlyph()
2575    self.CaretValue = carets
2576    self.CaretCount = len(self.CaretValue)
2577    return self
2578
2579
2580def buildMarkGlyphSetsDef(markSets, glyphMap):
2581    """Builds a mark glyph sets definition table.
2582
2583    OpenType Layout lookups may choose to use mark filtering sets to consider
2584    or ignore particular combinations of marks. These sets are specified by
2585    setting a flag on the lookup, but the mark filtering sets are defined in
2586    the ``GDEF`` table. This routine builds the subtable containing the mark
2587    glyph set definitions.
2588
2589    Example::
2590
2591        set0 = set("acute", "grave")
2592        set1 = set("caron", "grave")
2593
2594        markglyphsets = buildMarkGlyphSetsDef([set0, set1], font.getReverseGlyphMap())
2595
2596    Args:
2597
2598        markSets: A list of sets of glyphnames.
2599        glyphMap: a glyph name to ID map, typically returned from
2600            ``font.getReverseGlyphMap()``.
2601
2602    Returns
2603        An ``otTables.MarkGlyphSetsDef`` object.
2604    """
2605    if not markSets:
2606        return None
2607    self = ot.MarkGlyphSetsDef()
2608    self.MarkSetTableFormat = 1
2609    self.Coverage = [buildCoverage(m, glyphMap) for m in markSets]
2610    self.MarkSetCount = len(self.Coverage)
2611    return self
2612
2613
2614class ClassDefBuilder(object):
2615    """Helper for building ClassDef tables."""
2616
2617    def __init__(self, useClass0):
2618        self.classes_ = set()
2619        self.glyphs_ = {}
2620        self.useClass0_ = useClass0
2621
2622    def canAdd(self, glyphs):
2623        if isinstance(glyphs, (set, frozenset)):
2624            glyphs = sorted(glyphs)
2625        glyphs = tuple(glyphs)
2626        if glyphs in self.classes_:
2627            return True
2628        for glyph in glyphs:
2629            if glyph in self.glyphs_:
2630                return False
2631        return True
2632
2633    def add(self, glyphs):
2634        if isinstance(glyphs, (set, frozenset)):
2635            glyphs = sorted(glyphs)
2636        glyphs = tuple(glyphs)
2637        if glyphs in self.classes_:
2638            return
2639        self.classes_.add(glyphs)
2640        for glyph in glyphs:
2641            if glyph in self.glyphs_:
2642                raise OpenTypeLibError(
2643                    f"Glyph {glyph} is already present in class.", None
2644                )
2645            self.glyphs_[glyph] = glyphs
2646
2647    def classes(self):
2648        # In ClassDef1 tables, class id #0 does not need to be encoded
2649        # because zero is the default. Therefore, we use id #0 for the
2650        # glyph class that has the largest number of members. However,
2651        # in other tables than ClassDef1, 0 means "every other glyph"
2652        # so we should not use that ID for any real glyph classes;
2653        # we implement this by inserting an empty set at position 0.
2654        #
2655        # TODO: Instead of counting the number of glyphs in each class,
2656        # we should determine the encoded size. If the glyphs in a large
2657        # class form a contiguous range, the encoding is actually quite
2658        # compact, whereas a non-contiguous set might need a lot of bytes
2659        # in the output file. We don't get this right with the key below.
2660        result = sorted(self.classes_, key=lambda s: (len(s), s), reverse=True)
2661        if not self.useClass0_:
2662            result.insert(0, frozenset())
2663        return result
2664
2665    def build(self):
2666        glyphClasses = {}
2667        for classID, glyphs in enumerate(self.classes()):
2668            if classID == 0:
2669                continue
2670            for glyph in glyphs:
2671                glyphClasses[glyph] = classID
2672        classDef = ot.ClassDef()
2673        classDef.classDefs = glyphClasses
2674        return classDef
2675
2676
2677AXIS_VALUE_NEGATIVE_INFINITY = fixedToFloat(-0x80000000, 16)
2678AXIS_VALUE_POSITIVE_INFINITY = fixedToFloat(0x7FFFFFFF, 16)
2679
2680
2681def buildStatTable(
2682    ttFont, axes, locations=None, elidedFallbackName=2, windowsNames=True, macNames=True
2683):
2684    """Add a 'STAT' table to 'ttFont'.
2685
2686    'axes' is a list of dictionaries describing axes and their
2687    values.
2688
2689    Example::
2690
2691        axes = [
2692            dict(
2693                tag="wght",
2694                name="Weight",
2695                ordering=0,  # optional
2696                values=[
2697                    dict(value=100, name='Thin'),
2698                    dict(value=300, name='Light'),
2699                    dict(value=400, name='Regular', flags=0x2),
2700                    dict(value=900, name='Black'),
2701                ],
2702            )
2703        ]
2704
2705    Each axis dict must have 'tag' and 'name' items. 'tag' maps
2706    to the 'AxisTag' field. 'name' can be a name ID (int), a string,
2707    or a dictionary containing multilingual names (see the
2708    addMultilingualName() name table method), and will translate to
2709    the AxisNameID field.
2710
2711    An axis dict may contain an 'ordering' item that maps to the
2712    AxisOrdering field. If omitted, the order of the axes list is
2713    used to calculate AxisOrdering fields.
2714
2715    The axis dict may contain a 'values' item, which is a list of
2716    dictionaries describing AxisValue records belonging to this axis.
2717
2718    Each value dict must have a 'name' item, which can be a name ID
2719    (int), a string, or a dictionary containing multilingual names,
2720    like the axis name. It translates to the ValueNameID field.
2721
2722    Optionally the value dict can contain a 'flags' item. It maps to
2723    the AxisValue Flags field, and will be 0 when omitted.
2724
2725    The format of the AxisValue is determined by the remaining contents
2726    of the value dictionary:
2727
2728    If the value dict contains a 'value' item, an AxisValue record
2729    Format 1 is created. If in addition to the 'value' item it contains
2730    a 'linkedValue' item, an AxisValue record Format 3 is built.
2731
2732    If the value dict contains a 'nominalValue' item, an AxisValue
2733    record Format 2 is built. Optionally it may contain 'rangeMinValue'
2734    and 'rangeMaxValue' items. These map to -Infinity and +Infinity
2735    respectively if omitted.
2736
2737    You cannot specify Format 4 AxisValue tables this way, as they are
2738    not tied to a single axis, and specify a name for a location that
2739    is defined by multiple axes values. Instead, you need to supply the
2740    'locations' argument.
2741
2742    The optional 'locations' argument specifies AxisValue Format 4
2743    tables. It should be a list of dicts, where each dict has a 'name'
2744    item, which works just like the value dicts above, an optional
2745    'flags' item (defaulting to 0x0), and a 'location' dict. A
2746    location dict key is an axis tag, and the associated value is the
2747    location on the specified axis. They map to the AxisIndex and Value
2748    fields of the AxisValueRecord.
2749
2750    Example::
2751
2752        locations = [
2753            dict(name='Regular ABCD', location=dict(wght=300, ABCD=100)),
2754            dict(name='Bold ABCD XYZ', location=dict(wght=600, ABCD=200)),
2755        ]
2756
2757    The optional 'elidedFallbackName' argument can be a name ID (int),
2758    a string, a dictionary containing multilingual names, or a list of
2759    STATNameStatements. It translates to the ElidedFallbackNameID field.
2760
2761    The 'ttFont' argument must be a TTFont instance that already has a
2762    'name' table. If a 'STAT' table already exists, it will be
2763    overwritten by the newly created one.
2764    """
2765    ttFont["STAT"] = ttLib.newTable("STAT")
2766    statTable = ttFont["STAT"].table = ot.STAT()
2767    nameTable = ttFont["name"]
2768    statTable.ElidedFallbackNameID = _addName(
2769        nameTable, elidedFallbackName, windows=windowsNames, mac=macNames
2770    )
2771
2772    # 'locations' contains data for AxisValue Format 4
2773    axisRecords, axisValues = _buildAxisRecords(
2774        axes, nameTable, windowsNames=windowsNames, macNames=macNames
2775    )
2776    if not locations:
2777        statTable.Version = 0x00010001
2778    else:
2779        # We'll be adding Format 4 AxisValue records, which
2780        # requires a higher table version
2781        statTable.Version = 0x00010002
2782        multiAxisValues = _buildAxisValuesFormat4(
2783            locations, axes, nameTable, windowsNames=windowsNames, macNames=macNames
2784        )
2785        axisValues = multiAxisValues + axisValues
2786
2787    # Store AxisRecords
2788    axisRecordArray = ot.AxisRecordArray()
2789    axisRecordArray.Axis = axisRecords
2790    # XXX these should not be hard-coded but computed automatically
2791    statTable.DesignAxisRecordSize = 8
2792    statTable.DesignAxisRecord = axisRecordArray
2793    statTable.DesignAxisCount = len(axisRecords)
2794
2795    if axisValues:
2796        # Store AxisValueRecords
2797        axisValueArray = ot.AxisValueArray()
2798        axisValueArray.AxisValue = axisValues
2799        statTable.AxisValueArray = axisValueArray
2800        statTable.AxisValueCount = len(axisValues)
2801
2802
2803def _buildAxisRecords(axes, nameTable, windowsNames=True, macNames=True):
2804    axisRecords = []
2805    axisValues = []
2806    for axisRecordIndex, axisDict in enumerate(axes):
2807        axis = ot.AxisRecord()
2808        axis.AxisTag = axisDict["tag"]
2809        axis.AxisNameID = _addName(
2810            nameTable, axisDict["name"], 256, windows=windowsNames, mac=macNames
2811        )
2812        axis.AxisOrdering = axisDict.get("ordering", axisRecordIndex)
2813        axisRecords.append(axis)
2814
2815        for axisVal in axisDict.get("values", ()):
2816            axisValRec = ot.AxisValue()
2817            axisValRec.AxisIndex = axisRecordIndex
2818            axisValRec.Flags = axisVal.get("flags", 0)
2819            axisValRec.ValueNameID = _addName(
2820                nameTable, axisVal["name"], windows=windowsNames, mac=macNames
2821            )
2822
2823            if "value" in axisVal:
2824                axisValRec.Value = axisVal["value"]
2825                if "linkedValue" in axisVal:
2826                    axisValRec.Format = 3
2827                    axisValRec.LinkedValue = axisVal["linkedValue"]
2828                else:
2829                    axisValRec.Format = 1
2830            elif "nominalValue" in axisVal:
2831                axisValRec.Format = 2
2832                axisValRec.NominalValue = axisVal["nominalValue"]
2833                axisValRec.RangeMinValue = axisVal.get(
2834                    "rangeMinValue", AXIS_VALUE_NEGATIVE_INFINITY
2835                )
2836                axisValRec.RangeMaxValue = axisVal.get(
2837                    "rangeMaxValue", AXIS_VALUE_POSITIVE_INFINITY
2838                )
2839            else:
2840                raise ValueError("Can't determine format for AxisValue")
2841
2842            axisValues.append(axisValRec)
2843    return axisRecords, axisValues
2844
2845
2846def _buildAxisValuesFormat4(
2847    locations, axes, nameTable, windowsNames=True, macNames=True
2848):
2849    axisTagToIndex = {}
2850    for axisRecordIndex, axisDict in enumerate(axes):
2851        axisTagToIndex[axisDict["tag"]] = axisRecordIndex
2852
2853    axisValues = []
2854    for axisLocationDict in locations:
2855        axisValRec = ot.AxisValue()
2856        axisValRec.Format = 4
2857        axisValRec.ValueNameID = _addName(
2858            nameTable, axisLocationDict["name"], windows=windowsNames, mac=macNames
2859        )
2860        axisValRec.Flags = axisLocationDict.get("flags", 0)
2861        axisValueRecords = []
2862        for tag, value in axisLocationDict["location"].items():
2863            avr = ot.AxisValueRecord()
2864            avr.AxisIndex = axisTagToIndex[tag]
2865            avr.Value = value
2866            axisValueRecords.append(avr)
2867        axisValueRecords.sort(key=lambda avr: avr.AxisIndex)
2868        axisValRec.AxisCount = len(axisValueRecords)
2869        axisValRec.AxisValueRecord = axisValueRecords
2870        axisValues.append(axisValRec)
2871    return axisValues
2872
2873
2874def _addName(nameTable, value, minNameID=0, windows=True, mac=True):
2875    if isinstance(value, int):
2876        # Already a nameID
2877        return value
2878    if isinstance(value, str):
2879        names = dict(en=value)
2880    elif isinstance(value, dict):
2881        names = value
2882    elif isinstance(value, list):
2883        nameID = nameTable._findUnusedNameID()
2884        for nameRecord in value:
2885            if isinstance(nameRecord, STATNameStatement):
2886                nameTable.setName(
2887                    nameRecord.string,
2888                    nameID,
2889                    nameRecord.platformID,
2890                    nameRecord.platEncID,
2891                    nameRecord.langID,
2892                )
2893            else:
2894                raise TypeError("value must be a list of STATNameStatements")
2895        return nameID
2896    else:
2897        raise TypeError("value must be int, str, dict or list")
2898    return nameTable.addMultilingualName(
2899        names, windows=windows, mac=mac, minNameID=minNameID
2900    )
2901