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