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