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