xref: /aosp_15_r20/external/fonttools/Lib/fontTools/cu2qu/ufo.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughes# Copyright 2015 Google Inc. All Rights Reserved.
2*e1fe3e4aSElliott Hughes#
3*e1fe3e4aSElliott Hughes# Licensed under the Apache License, Version 2.0 (the "License");
4*e1fe3e4aSElliott Hughes# you may not use this file except in compliance with the License.
5*e1fe3e4aSElliott Hughes# You may obtain a copy of the License at
6*e1fe3e4aSElliott Hughes#
7*e1fe3e4aSElliott Hughes#     http://www.apache.org/licenses/LICENSE-2.0
8*e1fe3e4aSElliott Hughes#
9*e1fe3e4aSElliott Hughes# Unless required by applicable law or agreed to in writing, software
10*e1fe3e4aSElliott Hughes# distributed under the License is distributed on an "AS IS" BASIS,
11*e1fe3e4aSElliott Hughes# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*e1fe3e4aSElliott Hughes# See the License for the specific language governing permissions and
13*e1fe3e4aSElliott Hughes# limitations under the License.
14*e1fe3e4aSElliott Hughes
15*e1fe3e4aSElliott Hughes
16*e1fe3e4aSElliott Hughes"""Converts cubic bezier curves to quadratic splines.
17*e1fe3e4aSElliott Hughes
18*e1fe3e4aSElliott HughesConversion is performed such that the quadratic splines keep the same end-curve
19*e1fe3e4aSElliott Hughestangents as the original cubics. The approach is iterative, increasing the
20*e1fe3e4aSElliott Hughesnumber of segments for a spline until the error gets below a bound.
21*e1fe3e4aSElliott Hughes
22*e1fe3e4aSElliott HughesRespective curves from multiple fonts will be converted at once to ensure that
23*e1fe3e4aSElliott Hughesthe resulting splines are interpolation-compatible.
24*e1fe3e4aSElliott Hughes"""
25*e1fe3e4aSElliott Hughes
26*e1fe3e4aSElliott Hughesimport logging
27*e1fe3e4aSElliott Hughesfrom fontTools.pens.basePen import AbstractPen
28*e1fe3e4aSElliott Hughesfrom fontTools.pens.pointPen import PointToSegmentPen
29*e1fe3e4aSElliott Hughesfrom fontTools.pens.reverseContourPen import ReverseContourPen
30*e1fe3e4aSElliott Hughes
31*e1fe3e4aSElliott Hughesfrom . import curves_to_quadratic
32*e1fe3e4aSElliott Hughesfrom .errors import (
33*e1fe3e4aSElliott Hughes    UnequalZipLengthsError,
34*e1fe3e4aSElliott Hughes    IncompatibleSegmentNumberError,
35*e1fe3e4aSElliott Hughes    IncompatibleSegmentTypesError,
36*e1fe3e4aSElliott Hughes    IncompatibleGlyphsError,
37*e1fe3e4aSElliott Hughes    IncompatibleFontsError,
38*e1fe3e4aSElliott Hughes)
39*e1fe3e4aSElliott Hughes
40*e1fe3e4aSElliott Hughes
41*e1fe3e4aSElliott Hughes__all__ = ["fonts_to_quadratic", "font_to_quadratic"]
42*e1fe3e4aSElliott Hughes
43*e1fe3e4aSElliott Hughes# The default approximation error below is a relative value (1/1000 of the EM square).
44*e1fe3e4aSElliott Hughes# Later on, we convert it to absolute font units by multiplying it by a font's UPEM
45*e1fe3e4aSElliott Hughes# (see fonts_to_quadratic).
46*e1fe3e4aSElliott HughesDEFAULT_MAX_ERR = 0.001
47*e1fe3e4aSElliott HughesCURVE_TYPE_LIB_KEY = "com.github.googlei18n.cu2qu.curve_type"
48*e1fe3e4aSElliott Hughes
49*e1fe3e4aSElliott Hugheslogger = logging.getLogger(__name__)
50*e1fe3e4aSElliott Hughes
51*e1fe3e4aSElliott Hughes
52*e1fe3e4aSElliott Hughes_zip = zip
53*e1fe3e4aSElliott Hughes
54*e1fe3e4aSElliott Hughes
55*e1fe3e4aSElliott Hughesdef zip(*args):
56*e1fe3e4aSElliott Hughes    """Ensure each argument to zip has the same length. Also make sure a list is
57*e1fe3e4aSElliott Hughes    returned for python 2/3 compatibility.
58*e1fe3e4aSElliott Hughes    """
59*e1fe3e4aSElliott Hughes
60*e1fe3e4aSElliott Hughes    if len(set(len(a) for a in args)) != 1:
61*e1fe3e4aSElliott Hughes        raise UnequalZipLengthsError(*args)
62*e1fe3e4aSElliott Hughes    return list(_zip(*args))
63*e1fe3e4aSElliott Hughes
64*e1fe3e4aSElliott Hughes
65*e1fe3e4aSElliott Hughesclass GetSegmentsPen(AbstractPen):
66*e1fe3e4aSElliott Hughes    """Pen to collect segments into lists of points for conversion.
67*e1fe3e4aSElliott Hughes
68*e1fe3e4aSElliott Hughes    Curves always include their initial on-curve point, so some points are
69*e1fe3e4aSElliott Hughes    duplicated between segments.
70*e1fe3e4aSElliott Hughes    """
71*e1fe3e4aSElliott Hughes
72*e1fe3e4aSElliott Hughes    def __init__(self):
73*e1fe3e4aSElliott Hughes        self._last_pt = None
74*e1fe3e4aSElliott Hughes        self.segments = []
75*e1fe3e4aSElliott Hughes
76*e1fe3e4aSElliott Hughes    def _add_segment(self, tag, *args):
77*e1fe3e4aSElliott Hughes        if tag in ["move", "line", "qcurve", "curve"]:
78*e1fe3e4aSElliott Hughes            self._last_pt = args[-1]
79*e1fe3e4aSElliott Hughes        self.segments.append((tag, args))
80*e1fe3e4aSElliott Hughes
81*e1fe3e4aSElliott Hughes    def moveTo(self, pt):
82*e1fe3e4aSElliott Hughes        self._add_segment("move", pt)
83*e1fe3e4aSElliott Hughes
84*e1fe3e4aSElliott Hughes    def lineTo(self, pt):
85*e1fe3e4aSElliott Hughes        self._add_segment("line", pt)
86*e1fe3e4aSElliott Hughes
87*e1fe3e4aSElliott Hughes    def qCurveTo(self, *points):
88*e1fe3e4aSElliott Hughes        self._add_segment("qcurve", self._last_pt, *points)
89*e1fe3e4aSElliott Hughes
90*e1fe3e4aSElliott Hughes    def curveTo(self, *points):
91*e1fe3e4aSElliott Hughes        self._add_segment("curve", self._last_pt, *points)
92*e1fe3e4aSElliott Hughes
93*e1fe3e4aSElliott Hughes    def closePath(self):
94*e1fe3e4aSElliott Hughes        self._add_segment("close")
95*e1fe3e4aSElliott Hughes
96*e1fe3e4aSElliott Hughes    def endPath(self):
97*e1fe3e4aSElliott Hughes        self._add_segment("end")
98*e1fe3e4aSElliott Hughes
99*e1fe3e4aSElliott Hughes    def addComponent(self, glyphName, transformation):
100*e1fe3e4aSElliott Hughes        pass
101*e1fe3e4aSElliott Hughes
102*e1fe3e4aSElliott Hughes
103*e1fe3e4aSElliott Hughesdef _get_segments(glyph):
104*e1fe3e4aSElliott Hughes    """Get a glyph's segments as extracted by GetSegmentsPen."""
105*e1fe3e4aSElliott Hughes
106*e1fe3e4aSElliott Hughes    pen = GetSegmentsPen()
107*e1fe3e4aSElliott Hughes    # glyph.draw(pen)
108*e1fe3e4aSElliott Hughes    # We can't simply draw the glyph with the pen, but we must initialize the
109*e1fe3e4aSElliott Hughes    # PointToSegmentPen explicitly with outputImpliedClosingLine=True.
110*e1fe3e4aSElliott Hughes    # By default PointToSegmentPen does not outputImpliedClosingLine -- unless
111*e1fe3e4aSElliott Hughes    # last and first point on closed contour are duplicated. Because we are
112*e1fe3e4aSElliott Hughes    # converting multiple glyphs at the same time, we want to make sure
113*e1fe3e4aSElliott Hughes    # this function returns the same number of segments, whether or not
114*e1fe3e4aSElliott Hughes    # the last and first point overlap.
115*e1fe3e4aSElliott Hughes    # https://github.com/googlefonts/fontmake/issues/572
116*e1fe3e4aSElliott Hughes    # https://github.com/fonttools/fonttools/pull/1720
117*e1fe3e4aSElliott Hughes    pointPen = PointToSegmentPen(pen, outputImpliedClosingLine=True)
118*e1fe3e4aSElliott Hughes    glyph.drawPoints(pointPen)
119*e1fe3e4aSElliott Hughes    return pen.segments
120*e1fe3e4aSElliott Hughes
121*e1fe3e4aSElliott Hughes
122*e1fe3e4aSElliott Hughesdef _set_segments(glyph, segments, reverse_direction):
123*e1fe3e4aSElliott Hughes    """Draw segments as extracted by GetSegmentsPen back to a glyph."""
124*e1fe3e4aSElliott Hughes
125*e1fe3e4aSElliott Hughes    glyph.clearContours()
126*e1fe3e4aSElliott Hughes    pen = glyph.getPen()
127*e1fe3e4aSElliott Hughes    if reverse_direction:
128*e1fe3e4aSElliott Hughes        pen = ReverseContourPen(pen)
129*e1fe3e4aSElliott Hughes    for tag, args in segments:
130*e1fe3e4aSElliott Hughes        if tag == "move":
131*e1fe3e4aSElliott Hughes            pen.moveTo(*args)
132*e1fe3e4aSElliott Hughes        elif tag == "line":
133*e1fe3e4aSElliott Hughes            pen.lineTo(*args)
134*e1fe3e4aSElliott Hughes        elif tag == "curve":
135*e1fe3e4aSElliott Hughes            pen.curveTo(*args[1:])
136*e1fe3e4aSElliott Hughes        elif tag == "qcurve":
137*e1fe3e4aSElliott Hughes            pen.qCurveTo(*args[1:])
138*e1fe3e4aSElliott Hughes        elif tag == "close":
139*e1fe3e4aSElliott Hughes            pen.closePath()
140*e1fe3e4aSElliott Hughes        elif tag == "end":
141*e1fe3e4aSElliott Hughes            pen.endPath()
142*e1fe3e4aSElliott Hughes        else:
143*e1fe3e4aSElliott Hughes            raise AssertionError('Unhandled segment type "%s"' % tag)
144*e1fe3e4aSElliott Hughes
145*e1fe3e4aSElliott Hughes
146*e1fe3e4aSElliott Hughesdef _segments_to_quadratic(segments, max_err, stats, all_quadratic=True):
147*e1fe3e4aSElliott Hughes    """Return quadratic approximations of cubic segments."""
148*e1fe3e4aSElliott Hughes
149*e1fe3e4aSElliott Hughes    assert all(s[0] == "curve" for s in segments), "Non-cubic given to convert"
150*e1fe3e4aSElliott Hughes
151*e1fe3e4aSElliott Hughes    new_points = curves_to_quadratic([s[1] for s in segments], max_err, all_quadratic)
152*e1fe3e4aSElliott Hughes    n = len(new_points[0])
153*e1fe3e4aSElliott Hughes    assert all(len(s) == n for s in new_points[1:]), "Converted incompatibly"
154*e1fe3e4aSElliott Hughes
155*e1fe3e4aSElliott Hughes    spline_length = str(n - 2)
156*e1fe3e4aSElliott Hughes    stats[spline_length] = stats.get(spline_length, 0) + 1
157*e1fe3e4aSElliott Hughes
158*e1fe3e4aSElliott Hughes    if all_quadratic or n == 3:
159*e1fe3e4aSElliott Hughes        return [("qcurve", p) for p in new_points]
160*e1fe3e4aSElliott Hughes    else:
161*e1fe3e4aSElliott Hughes        return [("curve", p) for p in new_points]
162*e1fe3e4aSElliott Hughes
163*e1fe3e4aSElliott Hughes
164*e1fe3e4aSElliott Hughesdef _glyphs_to_quadratic(glyphs, max_err, reverse_direction, stats, all_quadratic=True):
165*e1fe3e4aSElliott Hughes    """Do the actual conversion of a set of compatible glyphs, after arguments
166*e1fe3e4aSElliott Hughes    have been set up.
167*e1fe3e4aSElliott Hughes
168*e1fe3e4aSElliott Hughes    Return True if the glyphs were modified, else return False.
169*e1fe3e4aSElliott Hughes    """
170*e1fe3e4aSElliott Hughes
171*e1fe3e4aSElliott Hughes    try:
172*e1fe3e4aSElliott Hughes        segments_by_location = zip(*[_get_segments(g) for g in glyphs])
173*e1fe3e4aSElliott Hughes    except UnequalZipLengthsError:
174*e1fe3e4aSElliott Hughes        raise IncompatibleSegmentNumberError(glyphs)
175*e1fe3e4aSElliott Hughes    if not any(segments_by_location):
176*e1fe3e4aSElliott Hughes        return False
177*e1fe3e4aSElliott Hughes
178*e1fe3e4aSElliott Hughes    # always modify input glyphs if reverse_direction is True
179*e1fe3e4aSElliott Hughes    glyphs_modified = reverse_direction
180*e1fe3e4aSElliott Hughes
181*e1fe3e4aSElliott Hughes    new_segments_by_location = []
182*e1fe3e4aSElliott Hughes    incompatible = {}
183*e1fe3e4aSElliott Hughes    for i, segments in enumerate(segments_by_location):
184*e1fe3e4aSElliott Hughes        tag = segments[0][0]
185*e1fe3e4aSElliott Hughes        if not all(s[0] == tag for s in segments[1:]):
186*e1fe3e4aSElliott Hughes            incompatible[i] = [s[0] for s in segments]
187*e1fe3e4aSElliott Hughes        elif tag == "curve":
188*e1fe3e4aSElliott Hughes            new_segments = _segments_to_quadratic(
189*e1fe3e4aSElliott Hughes                segments, max_err, stats, all_quadratic
190*e1fe3e4aSElliott Hughes            )
191*e1fe3e4aSElliott Hughes            if all_quadratic or new_segments != segments:
192*e1fe3e4aSElliott Hughes                glyphs_modified = True
193*e1fe3e4aSElliott Hughes            segments = new_segments
194*e1fe3e4aSElliott Hughes        new_segments_by_location.append(segments)
195*e1fe3e4aSElliott Hughes
196*e1fe3e4aSElliott Hughes    if glyphs_modified:
197*e1fe3e4aSElliott Hughes        new_segments_by_glyph = zip(*new_segments_by_location)
198*e1fe3e4aSElliott Hughes        for glyph, new_segments in zip(glyphs, new_segments_by_glyph):
199*e1fe3e4aSElliott Hughes            _set_segments(glyph, new_segments, reverse_direction)
200*e1fe3e4aSElliott Hughes
201*e1fe3e4aSElliott Hughes    if incompatible:
202*e1fe3e4aSElliott Hughes        raise IncompatibleSegmentTypesError(glyphs, segments=incompatible)
203*e1fe3e4aSElliott Hughes    return glyphs_modified
204*e1fe3e4aSElliott Hughes
205*e1fe3e4aSElliott Hughes
206*e1fe3e4aSElliott Hughesdef glyphs_to_quadratic(
207*e1fe3e4aSElliott Hughes    glyphs, max_err=None, reverse_direction=False, stats=None, all_quadratic=True
208*e1fe3e4aSElliott Hughes):
209*e1fe3e4aSElliott Hughes    """Convert the curves of a set of compatible of glyphs to quadratic.
210*e1fe3e4aSElliott Hughes
211*e1fe3e4aSElliott Hughes    All curves will be converted to quadratic at once, ensuring interpolation
212*e1fe3e4aSElliott Hughes    compatibility. If this is not required, calling glyphs_to_quadratic with one
213*e1fe3e4aSElliott Hughes    glyph at a time may yield slightly more optimized results.
214*e1fe3e4aSElliott Hughes
215*e1fe3e4aSElliott Hughes    Return True if glyphs were modified, else return False.
216*e1fe3e4aSElliott Hughes
217*e1fe3e4aSElliott Hughes    Raises IncompatibleGlyphsError if glyphs have non-interpolatable outlines.
218*e1fe3e4aSElliott Hughes    """
219*e1fe3e4aSElliott Hughes    if stats is None:
220*e1fe3e4aSElliott Hughes        stats = {}
221*e1fe3e4aSElliott Hughes
222*e1fe3e4aSElliott Hughes    if not max_err:
223*e1fe3e4aSElliott Hughes        # assume 1000 is the default UPEM
224*e1fe3e4aSElliott Hughes        max_err = DEFAULT_MAX_ERR * 1000
225*e1fe3e4aSElliott Hughes
226*e1fe3e4aSElliott Hughes    if isinstance(max_err, (list, tuple)):
227*e1fe3e4aSElliott Hughes        max_errors = max_err
228*e1fe3e4aSElliott Hughes    else:
229*e1fe3e4aSElliott Hughes        max_errors = [max_err] * len(glyphs)
230*e1fe3e4aSElliott Hughes    assert len(max_errors) == len(glyphs)
231*e1fe3e4aSElliott Hughes
232*e1fe3e4aSElliott Hughes    return _glyphs_to_quadratic(
233*e1fe3e4aSElliott Hughes        glyphs, max_errors, reverse_direction, stats, all_quadratic
234*e1fe3e4aSElliott Hughes    )
235*e1fe3e4aSElliott Hughes
236*e1fe3e4aSElliott Hughes
237*e1fe3e4aSElliott Hughesdef fonts_to_quadratic(
238*e1fe3e4aSElliott Hughes    fonts,
239*e1fe3e4aSElliott Hughes    max_err_em=None,
240*e1fe3e4aSElliott Hughes    max_err=None,
241*e1fe3e4aSElliott Hughes    reverse_direction=False,
242*e1fe3e4aSElliott Hughes    stats=None,
243*e1fe3e4aSElliott Hughes    dump_stats=False,
244*e1fe3e4aSElliott Hughes    remember_curve_type=True,
245*e1fe3e4aSElliott Hughes    all_quadratic=True,
246*e1fe3e4aSElliott Hughes):
247*e1fe3e4aSElliott Hughes    """Convert the curves of a collection of fonts to quadratic.
248*e1fe3e4aSElliott Hughes
249*e1fe3e4aSElliott Hughes    All curves will be converted to quadratic at once, ensuring interpolation
250*e1fe3e4aSElliott Hughes    compatibility. If this is not required, calling fonts_to_quadratic with one
251*e1fe3e4aSElliott Hughes    font at a time may yield slightly more optimized results.
252*e1fe3e4aSElliott Hughes
253*e1fe3e4aSElliott Hughes    Return True if fonts were modified, else return False.
254*e1fe3e4aSElliott Hughes
255*e1fe3e4aSElliott Hughes    By default, cu2qu stores the curve type in the fonts' lib, under a private
256*e1fe3e4aSElliott Hughes    key "com.github.googlei18n.cu2qu.curve_type", and will not try to convert
257*e1fe3e4aSElliott Hughes    them again if the curve type is already set to "quadratic".
258*e1fe3e4aSElliott Hughes    Setting 'remember_curve_type' to False disables this optimization.
259*e1fe3e4aSElliott Hughes
260*e1fe3e4aSElliott Hughes    Raises IncompatibleFontsError if same-named glyphs from different fonts
261*e1fe3e4aSElliott Hughes    have non-interpolatable outlines.
262*e1fe3e4aSElliott Hughes    """
263*e1fe3e4aSElliott Hughes
264*e1fe3e4aSElliott Hughes    if remember_curve_type:
265*e1fe3e4aSElliott Hughes        curve_types = {f.lib.get(CURVE_TYPE_LIB_KEY, "cubic") for f in fonts}
266*e1fe3e4aSElliott Hughes        if len(curve_types) == 1:
267*e1fe3e4aSElliott Hughes            curve_type = next(iter(curve_types))
268*e1fe3e4aSElliott Hughes            if curve_type in ("quadratic", "mixed"):
269*e1fe3e4aSElliott Hughes                logger.info("Curves already converted to quadratic")
270*e1fe3e4aSElliott Hughes                return False
271*e1fe3e4aSElliott Hughes            elif curve_type == "cubic":
272*e1fe3e4aSElliott Hughes                pass  # keep converting
273*e1fe3e4aSElliott Hughes            else:
274*e1fe3e4aSElliott Hughes                raise NotImplementedError(curve_type)
275*e1fe3e4aSElliott Hughes        elif len(curve_types) > 1:
276*e1fe3e4aSElliott Hughes            # going to crash later if they do differ
277*e1fe3e4aSElliott Hughes            logger.warning("fonts may contain different curve types")
278*e1fe3e4aSElliott Hughes
279*e1fe3e4aSElliott Hughes    if stats is None:
280*e1fe3e4aSElliott Hughes        stats = {}
281*e1fe3e4aSElliott Hughes
282*e1fe3e4aSElliott Hughes    if max_err_em and max_err:
283*e1fe3e4aSElliott Hughes        raise TypeError("Only one of max_err and max_err_em can be specified.")
284*e1fe3e4aSElliott Hughes    if not (max_err_em or max_err):
285*e1fe3e4aSElliott Hughes        max_err_em = DEFAULT_MAX_ERR
286*e1fe3e4aSElliott Hughes
287*e1fe3e4aSElliott Hughes    if isinstance(max_err, (list, tuple)):
288*e1fe3e4aSElliott Hughes        assert len(max_err) == len(fonts)
289*e1fe3e4aSElliott Hughes        max_errors = max_err
290*e1fe3e4aSElliott Hughes    elif max_err:
291*e1fe3e4aSElliott Hughes        max_errors = [max_err] * len(fonts)
292*e1fe3e4aSElliott Hughes
293*e1fe3e4aSElliott Hughes    if isinstance(max_err_em, (list, tuple)):
294*e1fe3e4aSElliott Hughes        assert len(fonts) == len(max_err_em)
295*e1fe3e4aSElliott Hughes        max_errors = [f.info.unitsPerEm * e for f, e in zip(fonts, max_err_em)]
296*e1fe3e4aSElliott Hughes    elif max_err_em:
297*e1fe3e4aSElliott Hughes        max_errors = [f.info.unitsPerEm * max_err_em for f in fonts]
298*e1fe3e4aSElliott Hughes
299*e1fe3e4aSElliott Hughes    modified = False
300*e1fe3e4aSElliott Hughes    glyph_errors = {}
301*e1fe3e4aSElliott Hughes    for name in set().union(*(f.keys() for f in fonts)):
302*e1fe3e4aSElliott Hughes        glyphs = []
303*e1fe3e4aSElliott Hughes        cur_max_errors = []
304*e1fe3e4aSElliott Hughes        for font, error in zip(fonts, max_errors):
305*e1fe3e4aSElliott Hughes            if name in font:
306*e1fe3e4aSElliott Hughes                glyphs.append(font[name])
307*e1fe3e4aSElliott Hughes                cur_max_errors.append(error)
308*e1fe3e4aSElliott Hughes        try:
309*e1fe3e4aSElliott Hughes            modified |= _glyphs_to_quadratic(
310*e1fe3e4aSElliott Hughes                glyphs, cur_max_errors, reverse_direction, stats, all_quadratic
311*e1fe3e4aSElliott Hughes            )
312*e1fe3e4aSElliott Hughes        except IncompatibleGlyphsError as exc:
313*e1fe3e4aSElliott Hughes            logger.error(exc)
314*e1fe3e4aSElliott Hughes            glyph_errors[name] = exc
315*e1fe3e4aSElliott Hughes
316*e1fe3e4aSElliott Hughes    if glyph_errors:
317*e1fe3e4aSElliott Hughes        raise IncompatibleFontsError(glyph_errors)
318*e1fe3e4aSElliott Hughes
319*e1fe3e4aSElliott Hughes    if modified and dump_stats:
320*e1fe3e4aSElliott Hughes        spline_lengths = sorted(stats.keys())
321*e1fe3e4aSElliott Hughes        logger.info(
322*e1fe3e4aSElliott Hughes            "New spline lengths: %s"
323*e1fe3e4aSElliott Hughes            % (", ".join("%s: %d" % (l, stats[l]) for l in spline_lengths))
324*e1fe3e4aSElliott Hughes        )
325*e1fe3e4aSElliott Hughes
326*e1fe3e4aSElliott Hughes    if remember_curve_type:
327*e1fe3e4aSElliott Hughes        for font in fonts:
328*e1fe3e4aSElliott Hughes            curve_type = font.lib.get(CURVE_TYPE_LIB_KEY, "cubic")
329*e1fe3e4aSElliott Hughes            new_curve_type = "quadratic" if all_quadratic else "mixed"
330*e1fe3e4aSElliott Hughes            if curve_type != new_curve_type:
331*e1fe3e4aSElliott Hughes                font.lib[CURVE_TYPE_LIB_KEY] = new_curve_type
332*e1fe3e4aSElliott Hughes                modified = True
333*e1fe3e4aSElliott Hughes    return modified
334*e1fe3e4aSElliott Hughes
335*e1fe3e4aSElliott Hughes
336*e1fe3e4aSElliott Hughesdef glyph_to_quadratic(glyph, **kwargs):
337*e1fe3e4aSElliott Hughes    """Convenience wrapper around glyphs_to_quadratic, for just one glyph.
338*e1fe3e4aSElliott Hughes    Return True if the glyph was modified, else return False.
339*e1fe3e4aSElliott Hughes    """
340*e1fe3e4aSElliott Hughes
341*e1fe3e4aSElliott Hughes    return glyphs_to_quadratic([glyph], **kwargs)
342*e1fe3e4aSElliott Hughes
343*e1fe3e4aSElliott Hughes
344*e1fe3e4aSElliott Hughesdef font_to_quadratic(font, **kwargs):
345*e1fe3e4aSElliott Hughes    """Convenience wrapper around fonts_to_quadratic, for just one font.
346*e1fe3e4aSElliott Hughes    Return True if the font was modified, else return False.
347*e1fe3e4aSElliott Hughes    """
348*e1fe3e4aSElliott Hughes
349*e1fe3e4aSElliott Hughes    return fonts_to_quadratic([font], **kwargs)
350