xref: /aosp_15_r20/external/fonttools/Lib/fontTools/ttLib/ttGlyphSet.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughes"""GlyphSets returned by a TTFont."""
2*e1fe3e4aSElliott Hughes
3*e1fe3e4aSElliott Hughesfrom abc import ABC, abstractmethod
4*e1fe3e4aSElliott Hughesfrom collections.abc import Mapping
5*e1fe3e4aSElliott Hughesfrom contextlib import contextmanager
6*e1fe3e4aSElliott Hughesfrom copy import copy
7*e1fe3e4aSElliott Hughesfrom types import SimpleNamespace
8*e1fe3e4aSElliott Hughesfrom fontTools.misc.fixedTools import otRound
9*e1fe3e4aSElliott Hughesfrom fontTools.misc.loggingTools import deprecateFunction
10*e1fe3e4aSElliott Hughesfrom fontTools.misc.transform import Transform
11*e1fe3e4aSElliott Hughesfrom fontTools.pens.transformPen import TransformPen, TransformPointPen
12*e1fe3e4aSElliott Hughesfrom fontTools.pens.recordingPen import (
13*e1fe3e4aSElliott Hughes    DecomposingRecordingPen,
14*e1fe3e4aSElliott Hughes    lerpRecordings,
15*e1fe3e4aSElliott Hughes    replayRecording,
16*e1fe3e4aSElliott Hughes)
17*e1fe3e4aSElliott Hughes
18*e1fe3e4aSElliott Hughes
19*e1fe3e4aSElliott Hughesclass _TTGlyphSet(Mapping):
20*e1fe3e4aSElliott Hughes    """Generic dict-like GlyphSet class that pulls metrics from hmtx and
21*e1fe3e4aSElliott Hughes    glyph shape from TrueType or CFF.
22*e1fe3e4aSElliott Hughes    """
23*e1fe3e4aSElliott Hughes
24*e1fe3e4aSElliott Hughes    def __init__(self, font, location, glyphsMapping, *, recalcBounds=True):
25*e1fe3e4aSElliott Hughes        self.recalcBounds = recalcBounds
26*e1fe3e4aSElliott Hughes        self.font = font
27*e1fe3e4aSElliott Hughes        self.defaultLocationNormalized = (
28*e1fe3e4aSElliott Hughes            {axis.axisTag: 0 for axis in self.font["fvar"].axes}
29*e1fe3e4aSElliott Hughes            if "fvar" in self.font
30*e1fe3e4aSElliott Hughes            else {}
31*e1fe3e4aSElliott Hughes        )
32*e1fe3e4aSElliott Hughes        self.location = location if location is not None else {}
33*e1fe3e4aSElliott Hughes        self.rawLocation = {}  # VarComponent-only location
34*e1fe3e4aSElliott Hughes        self.originalLocation = location if location is not None else {}
35*e1fe3e4aSElliott Hughes        self.depth = 0
36*e1fe3e4aSElliott Hughes        self.locationStack = []
37*e1fe3e4aSElliott Hughes        self.rawLocationStack = []
38*e1fe3e4aSElliott Hughes        self.glyphsMapping = glyphsMapping
39*e1fe3e4aSElliott Hughes        self.hMetrics = font["hmtx"].metrics
40*e1fe3e4aSElliott Hughes        self.vMetrics = getattr(font.get("vmtx"), "metrics", None)
41*e1fe3e4aSElliott Hughes        self.hvarTable = None
42*e1fe3e4aSElliott Hughes        if location:
43*e1fe3e4aSElliott Hughes            from fontTools.varLib.varStore import VarStoreInstancer
44*e1fe3e4aSElliott Hughes
45*e1fe3e4aSElliott Hughes            self.hvarTable = getattr(font.get("HVAR"), "table", None)
46*e1fe3e4aSElliott Hughes            if self.hvarTable is not None:
47*e1fe3e4aSElliott Hughes                self.hvarInstancer = VarStoreInstancer(
48*e1fe3e4aSElliott Hughes                    self.hvarTable.VarStore, font["fvar"].axes, location
49*e1fe3e4aSElliott Hughes                )
50*e1fe3e4aSElliott Hughes            # TODO VVAR, VORG
51*e1fe3e4aSElliott Hughes
52*e1fe3e4aSElliott Hughes    @contextmanager
53*e1fe3e4aSElliott Hughes    def pushLocation(self, location, reset: bool):
54*e1fe3e4aSElliott Hughes        self.locationStack.append(self.location)
55*e1fe3e4aSElliott Hughes        self.rawLocationStack.append(self.rawLocation)
56*e1fe3e4aSElliott Hughes        if reset:
57*e1fe3e4aSElliott Hughes            self.location = self.originalLocation.copy()
58*e1fe3e4aSElliott Hughes            self.rawLocation = self.defaultLocationNormalized.copy()
59*e1fe3e4aSElliott Hughes        else:
60*e1fe3e4aSElliott Hughes            self.location = self.location.copy()
61*e1fe3e4aSElliott Hughes            self.rawLocation = {}
62*e1fe3e4aSElliott Hughes        self.location.update(location)
63*e1fe3e4aSElliott Hughes        self.rawLocation.update(location)
64*e1fe3e4aSElliott Hughes
65*e1fe3e4aSElliott Hughes        try:
66*e1fe3e4aSElliott Hughes            yield None
67*e1fe3e4aSElliott Hughes        finally:
68*e1fe3e4aSElliott Hughes            self.location = self.locationStack.pop()
69*e1fe3e4aSElliott Hughes            self.rawLocation = self.rawLocationStack.pop()
70*e1fe3e4aSElliott Hughes
71*e1fe3e4aSElliott Hughes    @contextmanager
72*e1fe3e4aSElliott Hughes    def pushDepth(self):
73*e1fe3e4aSElliott Hughes        try:
74*e1fe3e4aSElliott Hughes            depth = self.depth
75*e1fe3e4aSElliott Hughes            self.depth += 1
76*e1fe3e4aSElliott Hughes            yield depth
77*e1fe3e4aSElliott Hughes        finally:
78*e1fe3e4aSElliott Hughes            self.depth -= 1
79*e1fe3e4aSElliott Hughes
80*e1fe3e4aSElliott Hughes    def __contains__(self, glyphName):
81*e1fe3e4aSElliott Hughes        return glyphName in self.glyphsMapping
82*e1fe3e4aSElliott Hughes
83*e1fe3e4aSElliott Hughes    def __iter__(self):
84*e1fe3e4aSElliott Hughes        return iter(self.glyphsMapping.keys())
85*e1fe3e4aSElliott Hughes
86*e1fe3e4aSElliott Hughes    def __len__(self):
87*e1fe3e4aSElliott Hughes        return len(self.glyphsMapping)
88*e1fe3e4aSElliott Hughes
89*e1fe3e4aSElliott Hughes    @deprecateFunction(
90*e1fe3e4aSElliott Hughes        "use 'glyphName in glyphSet' instead", category=DeprecationWarning
91*e1fe3e4aSElliott Hughes    )
92*e1fe3e4aSElliott Hughes    def has_key(self, glyphName):
93*e1fe3e4aSElliott Hughes        return glyphName in self.glyphsMapping
94*e1fe3e4aSElliott Hughes
95*e1fe3e4aSElliott Hughes
96*e1fe3e4aSElliott Hughesclass _TTGlyphSetGlyf(_TTGlyphSet):
97*e1fe3e4aSElliott Hughes    def __init__(self, font, location, recalcBounds=True):
98*e1fe3e4aSElliott Hughes        self.glyfTable = font["glyf"]
99*e1fe3e4aSElliott Hughes        super().__init__(font, location, self.glyfTable, recalcBounds=recalcBounds)
100*e1fe3e4aSElliott Hughes        self.gvarTable = font.get("gvar")
101*e1fe3e4aSElliott Hughes
102*e1fe3e4aSElliott Hughes    def __getitem__(self, glyphName):
103*e1fe3e4aSElliott Hughes        return _TTGlyphGlyf(self, glyphName, recalcBounds=self.recalcBounds)
104*e1fe3e4aSElliott Hughes
105*e1fe3e4aSElliott Hughes
106*e1fe3e4aSElliott Hughesclass _TTGlyphSetCFF(_TTGlyphSet):
107*e1fe3e4aSElliott Hughes    def __init__(self, font, location):
108*e1fe3e4aSElliott Hughes        tableTag = "CFF2" if "CFF2" in font else "CFF "
109*e1fe3e4aSElliott Hughes        self.charStrings = list(font[tableTag].cff.values())[0].CharStrings
110*e1fe3e4aSElliott Hughes        super().__init__(font, location, self.charStrings)
111*e1fe3e4aSElliott Hughes        self.blender = None
112*e1fe3e4aSElliott Hughes        if location:
113*e1fe3e4aSElliott Hughes            from fontTools.varLib.varStore import VarStoreInstancer
114*e1fe3e4aSElliott Hughes
115*e1fe3e4aSElliott Hughes            varStore = getattr(self.charStrings, "varStore", None)
116*e1fe3e4aSElliott Hughes            if varStore is not None:
117*e1fe3e4aSElliott Hughes                instancer = VarStoreInstancer(
118*e1fe3e4aSElliott Hughes                    varStore.otVarStore, font["fvar"].axes, location
119*e1fe3e4aSElliott Hughes                )
120*e1fe3e4aSElliott Hughes                self.blender = instancer.interpolateFromDeltas
121*e1fe3e4aSElliott Hughes
122*e1fe3e4aSElliott Hughes    def __getitem__(self, glyphName):
123*e1fe3e4aSElliott Hughes        return _TTGlyphCFF(self, glyphName)
124*e1fe3e4aSElliott Hughes
125*e1fe3e4aSElliott Hughes
126*e1fe3e4aSElliott Hughesclass _TTGlyph(ABC):
127*e1fe3e4aSElliott Hughes    """Glyph object that supports the Pen protocol, meaning that it has
128*e1fe3e4aSElliott Hughes    .draw() and .drawPoints() methods that take a pen object as their only
129*e1fe3e4aSElliott Hughes    argument. Additionally there are 'width' and 'lsb' attributes, read from
130*e1fe3e4aSElliott Hughes    the 'hmtx' table.
131*e1fe3e4aSElliott Hughes
132*e1fe3e4aSElliott Hughes    If the font contains a 'vmtx' table, there will also be 'height' and 'tsb'
133*e1fe3e4aSElliott Hughes    attributes.
134*e1fe3e4aSElliott Hughes    """
135*e1fe3e4aSElliott Hughes
136*e1fe3e4aSElliott Hughes    def __init__(self, glyphSet, glyphName, *, recalcBounds=True):
137*e1fe3e4aSElliott Hughes        self.glyphSet = glyphSet
138*e1fe3e4aSElliott Hughes        self.name = glyphName
139*e1fe3e4aSElliott Hughes        self.recalcBounds = recalcBounds
140*e1fe3e4aSElliott Hughes        self.width, self.lsb = glyphSet.hMetrics[glyphName]
141*e1fe3e4aSElliott Hughes        if glyphSet.vMetrics is not None:
142*e1fe3e4aSElliott Hughes            self.height, self.tsb = glyphSet.vMetrics[glyphName]
143*e1fe3e4aSElliott Hughes        else:
144*e1fe3e4aSElliott Hughes            self.height, self.tsb = None, None
145*e1fe3e4aSElliott Hughes        if glyphSet.location and glyphSet.hvarTable is not None:
146*e1fe3e4aSElliott Hughes            varidx = (
147*e1fe3e4aSElliott Hughes                glyphSet.font.getGlyphID(glyphName)
148*e1fe3e4aSElliott Hughes                if glyphSet.hvarTable.AdvWidthMap is None
149*e1fe3e4aSElliott Hughes                else glyphSet.hvarTable.AdvWidthMap.mapping[glyphName]
150*e1fe3e4aSElliott Hughes            )
151*e1fe3e4aSElliott Hughes            self.width += glyphSet.hvarInstancer[varidx]
152*e1fe3e4aSElliott Hughes        # TODO: VVAR/VORG
153*e1fe3e4aSElliott Hughes
154*e1fe3e4aSElliott Hughes    @abstractmethod
155*e1fe3e4aSElliott Hughes    def draw(self, pen):
156*e1fe3e4aSElliott Hughes        """Draw the glyph onto ``pen``. See fontTools.pens.basePen for details
157*e1fe3e4aSElliott Hughes        how that works.
158*e1fe3e4aSElliott Hughes        """
159*e1fe3e4aSElliott Hughes        raise NotImplementedError
160*e1fe3e4aSElliott Hughes
161*e1fe3e4aSElliott Hughes    def drawPoints(self, pen):
162*e1fe3e4aSElliott Hughes        """Draw the glyph onto ``pen``. See fontTools.pens.pointPen for details
163*e1fe3e4aSElliott Hughes        how that works.
164*e1fe3e4aSElliott Hughes        """
165*e1fe3e4aSElliott Hughes        from fontTools.pens.pointPen import SegmentToPointPen
166*e1fe3e4aSElliott Hughes
167*e1fe3e4aSElliott Hughes        self.draw(SegmentToPointPen(pen))
168*e1fe3e4aSElliott Hughes
169*e1fe3e4aSElliott Hughes
170*e1fe3e4aSElliott Hughesclass _TTGlyphGlyf(_TTGlyph):
171*e1fe3e4aSElliott Hughes    def draw(self, pen):
172*e1fe3e4aSElliott Hughes        """Draw the glyph onto ``pen``. See fontTools.pens.basePen for details
173*e1fe3e4aSElliott Hughes        how that works.
174*e1fe3e4aSElliott Hughes        """
175*e1fe3e4aSElliott Hughes        glyph, offset = self._getGlyphAndOffset()
176*e1fe3e4aSElliott Hughes
177*e1fe3e4aSElliott Hughes        with self.glyphSet.pushDepth() as depth:
178*e1fe3e4aSElliott Hughes            if depth:
179*e1fe3e4aSElliott Hughes                offset = 0  # Offset should only apply at top-level
180*e1fe3e4aSElliott Hughes
181*e1fe3e4aSElliott Hughes            if glyph.isVarComposite():
182*e1fe3e4aSElliott Hughes                self._drawVarComposite(glyph, pen, False)
183*e1fe3e4aSElliott Hughes                return
184*e1fe3e4aSElliott Hughes
185*e1fe3e4aSElliott Hughes            glyph.draw(pen, self.glyphSet.glyfTable, offset)
186*e1fe3e4aSElliott Hughes
187*e1fe3e4aSElliott Hughes    def drawPoints(self, pen):
188*e1fe3e4aSElliott Hughes        """Draw the glyph onto ``pen``. See fontTools.pens.pointPen for details
189*e1fe3e4aSElliott Hughes        how that works.
190*e1fe3e4aSElliott Hughes        """
191*e1fe3e4aSElliott Hughes        glyph, offset = self._getGlyphAndOffset()
192*e1fe3e4aSElliott Hughes
193*e1fe3e4aSElliott Hughes        with self.glyphSet.pushDepth() as depth:
194*e1fe3e4aSElliott Hughes            if depth:
195*e1fe3e4aSElliott Hughes                offset = 0  # Offset should only apply at top-level
196*e1fe3e4aSElliott Hughes
197*e1fe3e4aSElliott Hughes            if glyph.isVarComposite():
198*e1fe3e4aSElliott Hughes                self._drawVarComposite(glyph, pen, True)
199*e1fe3e4aSElliott Hughes                return
200*e1fe3e4aSElliott Hughes
201*e1fe3e4aSElliott Hughes            glyph.drawPoints(pen, self.glyphSet.glyfTable, offset)
202*e1fe3e4aSElliott Hughes
203*e1fe3e4aSElliott Hughes    def _drawVarComposite(self, glyph, pen, isPointPen):
204*e1fe3e4aSElliott Hughes        from fontTools.ttLib.tables._g_l_y_f import (
205*e1fe3e4aSElliott Hughes            VarComponentFlags,
206*e1fe3e4aSElliott Hughes            VAR_COMPONENT_TRANSFORM_MAPPING,
207*e1fe3e4aSElliott Hughes        )
208*e1fe3e4aSElliott Hughes
209*e1fe3e4aSElliott Hughes        for comp in glyph.components:
210*e1fe3e4aSElliott Hughes            with self.glyphSet.pushLocation(
211*e1fe3e4aSElliott Hughes                comp.location, comp.flags & VarComponentFlags.RESET_UNSPECIFIED_AXES
212*e1fe3e4aSElliott Hughes            ):
213*e1fe3e4aSElliott Hughes                try:
214*e1fe3e4aSElliott Hughes                    pen.addVarComponent(
215*e1fe3e4aSElliott Hughes                        comp.glyphName, comp.transform, self.glyphSet.rawLocation
216*e1fe3e4aSElliott Hughes                    )
217*e1fe3e4aSElliott Hughes                except AttributeError:
218*e1fe3e4aSElliott Hughes                    t = comp.transform.toTransform()
219*e1fe3e4aSElliott Hughes                    if isPointPen:
220*e1fe3e4aSElliott Hughes                        tPen = TransformPointPen(pen, t)
221*e1fe3e4aSElliott Hughes                        self.glyphSet[comp.glyphName].drawPoints(tPen)
222*e1fe3e4aSElliott Hughes                    else:
223*e1fe3e4aSElliott Hughes                        tPen = TransformPen(pen, t)
224*e1fe3e4aSElliott Hughes                        self.glyphSet[comp.glyphName].draw(tPen)
225*e1fe3e4aSElliott Hughes
226*e1fe3e4aSElliott Hughes    def _getGlyphAndOffset(self):
227*e1fe3e4aSElliott Hughes        if self.glyphSet.location and self.glyphSet.gvarTable is not None:
228*e1fe3e4aSElliott Hughes            glyph = self._getGlyphInstance()
229*e1fe3e4aSElliott Hughes        else:
230*e1fe3e4aSElliott Hughes            glyph = self.glyphSet.glyfTable[self.name]
231*e1fe3e4aSElliott Hughes
232*e1fe3e4aSElliott Hughes        offset = self.lsb - glyph.xMin if hasattr(glyph, "xMin") else 0
233*e1fe3e4aSElliott Hughes        return glyph, offset
234*e1fe3e4aSElliott Hughes
235*e1fe3e4aSElliott Hughes    def _getGlyphInstance(self):
236*e1fe3e4aSElliott Hughes        from fontTools.varLib.iup import iup_delta
237*e1fe3e4aSElliott Hughes        from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
238*e1fe3e4aSElliott Hughes        from fontTools.varLib.models import supportScalar
239*e1fe3e4aSElliott Hughes
240*e1fe3e4aSElliott Hughes        glyphSet = self.glyphSet
241*e1fe3e4aSElliott Hughes        glyfTable = glyphSet.glyfTable
242*e1fe3e4aSElliott Hughes        variations = glyphSet.gvarTable.variations[self.name]
243*e1fe3e4aSElliott Hughes        hMetrics = glyphSet.hMetrics
244*e1fe3e4aSElliott Hughes        vMetrics = glyphSet.vMetrics
245*e1fe3e4aSElliott Hughes        coordinates, _ = glyfTable._getCoordinatesAndControls(
246*e1fe3e4aSElliott Hughes            self.name, hMetrics, vMetrics
247*e1fe3e4aSElliott Hughes        )
248*e1fe3e4aSElliott Hughes        origCoords, endPts = None, None
249*e1fe3e4aSElliott Hughes        for var in variations:
250*e1fe3e4aSElliott Hughes            scalar = supportScalar(glyphSet.location, var.axes)
251*e1fe3e4aSElliott Hughes            if not scalar:
252*e1fe3e4aSElliott Hughes                continue
253*e1fe3e4aSElliott Hughes            delta = var.coordinates
254*e1fe3e4aSElliott Hughes            if None in delta:
255*e1fe3e4aSElliott Hughes                if origCoords is None:
256*e1fe3e4aSElliott Hughes                    origCoords, control = glyfTable._getCoordinatesAndControls(
257*e1fe3e4aSElliott Hughes                        self.name, hMetrics, vMetrics
258*e1fe3e4aSElliott Hughes                    )
259*e1fe3e4aSElliott Hughes                    endPts = (
260*e1fe3e4aSElliott Hughes                        control[1] if control[0] >= 1 else list(range(len(control[1])))
261*e1fe3e4aSElliott Hughes                    )
262*e1fe3e4aSElliott Hughes                delta = iup_delta(delta, origCoords, endPts)
263*e1fe3e4aSElliott Hughes            coordinates += GlyphCoordinates(delta) * scalar
264*e1fe3e4aSElliott Hughes
265*e1fe3e4aSElliott Hughes        glyph = copy(glyfTable[self.name])  # Shallow copy
266*e1fe3e4aSElliott Hughes        width, lsb, height, tsb = _setCoordinates(
267*e1fe3e4aSElliott Hughes            glyph, coordinates, glyfTable, recalcBounds=self.recalcBounds
268*e1fe3e4aSElliott Hughes        )
269*e1fe3e4aSElliott Hughes        self.lsb = lsb
270*e1fe3e4aSElliott Hughes        self.tsb = tsb
271*e1fe3e4aSElliott Hughes        if glyphSet.hvarTable is None:
272*e1fe3e4aSElliott Hughes            # no HVAR: let's set metrics from the phantom points
273*e1fe3e4aSElliott Hughes            self.width = width
274*e1fe3e4aSElliott Hughes            self.height = height
275*e1fe3e4aSElliott Hughes        return glyph
276*e1fe3e4aSElliott Hughes
277*e1fe3e4aSElliott Hughes
278*e1fe3e4aSElliott Hughesclass _TTGlyphCFF(_TTGlyph):
279*e1fe3e4aSElliott Hughes    def draw(self, pen):
280*e1fe3e4aSElliott Hughes        """Draw the glyph onto ``pen``. See fontTools.pens.basePen for details
281*e1fe3e4aSElliott Hughes        how that works.
282*e1fe3e4aSElliott Hughes        """
283*e1fe3e4aSElliott Hughes        self.glyphSet.charStrings[self.name].draw(pen, self.glyphSet.blender)
284*e1fe3e4aSElliott Hughes
285*e1fe3e4aSElliott Hughes
286*e1fe3e4aSElliott Hughesdef _setCoordinates(glyph, coord, glyfTable, *, recalcBounds=True):
287*e1fe3e4aSElliott Hughes    # Handle phantom points for (left, right, top, bottom) positions.
288*e1fe3e4aSElliott Hughes    assert len(coord) >= 4
289*e1fe3e4aSElliott Hughes    leftSideX = coord[-4][0]
290*e1fe3e4aSElliott Hughes    rightSideX = coord[-3][0]
291*e1fe3e4aSElliott Hughes    topSideY = coord[-2][1]
292*e1fe3e4aSElliott Hughes    bottomSideY = coord[-1][1]
293*e1fe3e4aSElliott Hughes
294*e1fe3e4aSElliott Hughes    for _ in range(4):
295*e1fe3e4aSElliott Hughes        del coord[-1]
296*e1fe3e4aSElliott Hughes
297*e1fe3e4aSElliott Hughes    if glyph.isComposite():
298*e1fe3e4aSElliott Hughes        assert len(coord) == len(glyph.components)
299*e1fe3e4aSElliott Hughes        glyph.components = [copy(comp) for comp in glyph.components]  # Shallow copy
300*e1fe3e4aSElliott Hughes        for p, comp in zip(coord, glyph.components):
301*e1fe3e4aSElliott Hughes            if hasattr(comp, "x"):
302*e1fe3e4aSElliott Hughes                comp.x, comp.y = p
303*e1fe3e4aSElliott Hughes    elif glyph.isVarComposite():
304*e1fe3e4aSElliott Hughes        glyph.components = [copy(comp) for comp in glyph.components]  # Shallow copy
305*e1fe3e4aSElliott Hughes        for comp in glyph.components:
306*e1fe3e4aSElliott Hughes            coord = comp.setCoordinates(coord)
307*e1fe3e4aSElliott Hughes        assert not coord
308*e1fe3e4aSElliott Hughes    elif glyph.numberOfContours == 0:
309*e1fe3e4aSElliott Hughes        assert len(coord) == 0
310*e1fe3e4aSElliott Hughes    else:
311*e1fe3e4aSElliott Hughes        assert len(coord) == len(glyph.coordinates)
312*e1fe3e4aSElliott Hughes        glyph.coordinates = coord
313*e1fe3e4aSElliott Hughes
314*e1fe3e4aSElliott Hughes    if recalcBounds:
315*e1fe3e4aSElliott Hughes        glyph.recalcBounds(glyfTable)
316*e1fe3e4aSElliott Hughes
317*e1fe3e4aSElliott Hughes    horizontalAdvanceWidth = otRound(rightSideX - leftSideX)
318*e1fe3e4aSElliott Hughes    verticalAdvanceWidth = otRound(topSideY - bottomSideY)
319*e1fe3e4aSElliott Hughes    leftSideBearing = otRound(glyph.xMin - leftSideX)
320*e1fe3e4aSElliott Hughes    topSideBearing = otRound(topSideY - glyph.yMax)
321*e1fe3e4aSElliott Hughes    return (
322*e1fe3e4aSElliott Hughes        horizontalAdvanceWidth,
323*e1fe3e4aSElliott Hughes        leftSideBearing,
324*e1fe3e4aSElliott Hughes        verticalAdvanceWidth,
325*e1fe3e4aSElliott Hughes        topSideBearing,
326*e1fe3e4aSElliott Hughes    )
327*e1fe3e4aSElliott Hughes
328*e1fe3e4aSElliott Hughes
329*e1fe3e4aSElliott Hughesclass LerpGlyphSet(Mapping):
330*e1fe3e4aSElliott Hughes    """A glyphset that interpolates between two other glyphsets.
331*e1fe3e4aSElliott Hughes
332*e1fe3e4aSElliott Hughes    Factor is typically between 0 and 1. 0 means the first glyphset,
333*e1fe3e4aSElliott Hughes    1 means the second glyphset, and 0.5 means the average of the
334*e1fe3e4aSElliott Hughes    two glyphsets. Other values are possible, and can be useful to
335*e1fe3e4aSElliott Hughes    extrapolate. Defaults to 0.5.
336*e1fe3e4aSElliott Hughes    """
337*e1fe3e4aSElliott Hughes
338*e1fe3e4aSElliott Hughes    def __init__(self, glyphset1, glyphset2, factor=0.5):
339*e1fe3e4aSElliott Hughes        self.glyphset1 = glyphset1
340*e1fe3e4aSElliott Hughes        self.glyphset2 = glyphset2
341*e1fe3e4aSElliott Hughes        self.factor = factor
342*e1fe3e4aSElliott Hughes
343*e1fe3e4aSElliott Hughes    def __getitem__(self, glyphname):
344*e1fe3e4aSElliott Hughes        if glyphname in self.glyphset1 and glyphname in self.glyphset2:
345*e1fe3e4aSElliott Hughes            return LerpGlyph(glyphname, self)
346*e1fe3e4aSElliott Hughes        raise KeyError(glyphname)
347*e1fe3e4aSElliott Hughes
348*e1fe3e4aSElliott Hughes    def __contains__(self, glyphname):
349*e1fe3e4aSElliott Hughes        return glyphname in self.glyphset1 and glyphname in self.glyphset2
350*e1fe3e4aSElliott Hughes
351*e1fe3e4aSElliott Hughes    def __iter__(self):
352*e1fe3e4aSElliott Hughes        set1 = set(self.glyphset1)
353*e1fe3e4aSElliott Hughes        set2 = set(self.glyphset2)
354*e1fe3e4aSElliott Hughes        return iter(set1.intersection(set2))
355*e1fe3e4aSElliott Hughes
356*e1fe3e4aSElliott Hughes    def __len__(self):
357*e1fe3e4aSElliott Hughes        set1 = set(self.glyphset1)
358*e1fe3e4aSElliott Hughes        set2 = set(self.glyphset2)
359*e1fe3e4aSElliott Hughes        return len(set1.intersection(set2))
360*e1fe3e4aSElliott Hughes
361*e1fe3e4aSElliott Hughes
362*e1fe3e4aSElliott Hughesclass LerpGlyph:
363*e1fe3e4aSElliott Hughes    def __init__(self, glyphname, glyphset):
364*e1fe3e4aSElliott Hughes        self.glyphset = glyphset
365*e1fe3e4aSElliott Hughes        self.glyphname = glyphname
366*e1fe3e4aSElliott Hughes
367*e1fe3e4aSElliott Hughes    def draw(self, pen):
368*e1fe3e4aSElliott Hughes        recording1 = DecomposingRecordingPen(self.glyphset.glyphset1)
369*e1fe3e4aSElliott Hughes        self.glyphset.glyphset1[self.glyphname].draw(recording1)
370*e1fe3e4aSElliott Hughes        recording2 = DecomposingRecordingPen(self.glyphset.glyphset2)
371*e1fe3e4aSElliott Hughes        self.glyphset.glyphset2[self.glyphname].draw(recording2)
372*e1fe3e4aSElliott Hughes
373*e1fe3e4aSElliott Hughes        factor = self.glyphset.factor
374*e1fe3e4aSElliott Hughes
375*e1fe3e4aSElliott Hughes        replayRecording(lerpRecordings(recording1.value, recording2.value, factor), pen)
376