1*e1fe3e4aSElliott Hughes# Copyright 2016 Google Inc. All Rights Reserved. 2*e1fe3e4aSElliott Hughes# Copyright 2023 Behdad Esfahbod. All Rights Reserved. 3*e1fe3e4aSElliott Hughes# 4*e1fe3e4aSElliott Hughes# Licensed under the Apache License, Version 2.0 (the "License"); 5*e1fe3e4aSElliott Hughes# you may not use this file except in compliance with the License. 6*e1fe3e4aSElliott Hughes# You may obtain a copy of the License at 7*e1fe3e4aSElliott Hughes# 8*e1fe3e4aSElliott Hughes# http://www.apache.org/licenses/LICENSE-2.0 9*e1fe3e4aSElliott Hughes# 10*e1fe3e4aSElliott Hughes# Unless required by applicable law or agreed to in writing, software 11*e1fe3e4aSElliott Hughes# distributed under the License is distributed on an "AS IS" BASIS, 12*e1fe3e4aSElliott Hughes# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*e1fe3e4aSElliott Hughes# See the License for the specific language governing permissions and 14*e1fe3e4aSElliott Hughes# limitations under the License. 15*e1fe3e4aSElliott Hughes 16*e1fe3e4aSElliott Hughesfrom fontTools.qu2cu import quadratic_to_curves 17*e1fe3e4aSElliott Hughesfrom fontTools.pens.filterPen import ContourFilterPen 18*e1fe3e4aSElliott Hughesfrom fontTools.pens.reverseContourPen import ReverseContourPen 19*e1fe3e4aSElliott Hughesimport math 20*e1fe3e4aSElliott Hughes 21*e1fe3e4aSElliott Hughes 22*e1fe3e4aSElliott Hughesclass Qu2CuPen(ContourFilterPen): 23*e1fe3e4aSElliott Hughes """A filter pen to convert quadratic bezier splines to cubic curves 24*e1fe3e4aSElliott Hughes using the FontTools SegmentPen protocol. 25*e1fe3e4aSElliott Hughes 26*e1fe3e4aSElliott Hughes Args: 27*e1fe3e4aSElliott Hughes 28*e1fe3e4aSElliott Hughes other_pen: another SegmentPen used to draw the transformed outline. 29*e1fe3e4aSElliott Hughes max_err: maximum approximation error in font units. For optimal results, 30*e1fe3e4aSElliott Hughes if you know the UPEM of the font, we recommend setting this to a 31*e1fe3e4aSElliott Hughes value equal, or close to UPEM / 1000. 32*e1fe3e4aSElliott Hughes reverse_direction: flip the contours' direction but keep starting point. 33*e1fe3e4aSElliott Hughes stats: a dictionary counting the point numbers of cubic segments. 34*e1fe3e4aSElliott Hughes """ 35*e1fe3e4aSElliott Hughes 36*e1fe3e4aSElliott Hughes def __init__( 37*e1fe3e4aSElliott Hughes self, 38*e1fe3e4aSElliott Hughes other_pen, 39*e1fe3e4aSElliott Hughes max_err, 40*e1fe3e4aSElliott Hughes all_cubic=False, 41*e1fe3e4aSElliott Hughes reverse_direction=False, 42*e1fe3e4aSElliott Hughes stats=None, 43*e1fe3e4aSElliott Hughes ): 44*e1fe3e4aSElliott Hughes if reverse_direction: 45*e1fe3e4aSElliott Hughes other_pen = ReverseContourPen(other_pen) 46*e1fe3e4aSElliott Hughes super().__init__(other_pen) 47*e1fe3e4aSElliott Hughes self.all_cubic = all_cubic 48*e1fe3e4aSElliott Hughes self.max_err = max_err 49*e1fe3e4aSElliott Hughes self.stats = stats 50*e1fe3e4aSElliott Hughes 51*e1fe3e4aSElliott Hughes def _quadratics_to_curve(self, q): 52*e1fe3e4aSElliott Hughes curves = quadratic_to_curves(q, self.max_err, all_cubic=self.all_cubic) 53*e1fe3e4aSElliott Hughes if self.stats is not None: 54*e1fe3e4aSElliott Hughes for curve in curves: 55*e1fe3e4aSElliott Hughes n = str(len(curve) - 2) 56*e1fe3e4aSElliott Hughes self.stats[n] = self.stats.get(n, 0) + 1 57*e1fe3e4aSElliott Hughes for curve in curves: 58*e1fe3e4aSElliott Hughes if len(curve) == 4: 59*e1fe3e4aSElliott Hughes yield ("curveTo", curve[1:]) 60*e1fe3e4aSElliott Hughes else: 61*e1fe3e4aSElliott Hughes yield ("qCurveTo", curve[1:]) 62*e1fe3e4aSElliott Hughes 63*e1fe3e4aSElliott Hughes def filterContour(self, contour): 64*e1fe3e4aSElliott Hughes quadratics = [] 65*e1fe3e4aSElliott Hughes currentPt = None 66*e1fe3e4aSElliott Hughes newContour = [] 67*e1fe3e4aSElliott Hughes for op, args in contour: 68*e1fe3e4aSElliott Hughes if op == "qCurveTo" and ( 69*e1fe3e4aSElliott Hughes self.all_cubic or (len(args) > 2 and args[-1] is not None) 70*e1fe3e4aSElliott Hughes ): 71*e1fe3e4aSElliott Hughes if args[-1] is None: 72*e1fe3e4aSElliott Hughes raise NotImplementedError( 73*e1fe3e4aSElliott Hughes "oncurve-less contours with all_cubic not implemented" 74*e1fe3e4aSElliott Hughes ) 75*e1fe3e4aSElliott Hughes quadratics.append((currentPt,) + args) 76*e1fe3e4aSElliott Hughes else: 77*e1fe3e4aSElliott Hughes if quadratics: 78*e1fe3e4aSElliott Hughes newContour.extend(self._quadratics_to_curve(quadratics)) 79*e1fe3e4aSElliott Hughes quadratics = [] 80*e1fe3e4aSElliott Hughes newContour.append((op, args)) 81*e1fe3e4aSElliott Hughes currentPt = args[-1] if args else None 82*e1fe3e4aSElliott Hughes if quadratics: 83*e1fe3e4aSElliott Hughes newContour.extend(self._quadratics_to_curve(quadratics)) 84*e1fe3e4aSElliott Hughes 85*e1fe3e4aSElliott Hughes if not self.all_cubic: 86*e1fe3e4aSElliott Hughes # Add back implicit oncurve points 87*e1fe3e4aSElliott Hughes contour = newContour 88*e1fe3e4aSElliott Hughes newContour = [] 89*e1fe3e4aSElliott Hughes for op, args in contour: 90*e1fe3e4aSElliott Hughes if op == "qCurveTo" and newContour and newContour[-1][0] == "qCurveTo": 91*e1fe3e4aSElliott Hughes pt0 = newContour[-1][1][-2] 92*e1fe3e4aSElliott Hughes pt1 = newContour[-1][1][-1] 93*e1fe3e4aSElliott Hughes pt2 = args[0] 94*e1fe3e4aSElliott Hughes if ( 95*e1fe3e4aSElliott Hughes pt1 is not None 96*e1fe3e4aSElliott Hughes and math.isclose(pt2[0] - pt1[0], pt1[0] - pt0[0]) 97*e1fe3e4aSElliott Hughes and math.isclose(pt2[1] - pt1[1], pt1[1] - pt0[1]) 98*e1fe3e4aSElliott Hughes ): 99*e1fe3e4aSElliott Hughes newArgs = newContour[-1][1][:-1] + args 100*e1fe3e4aSElliott Hughes newContour[-1] = (op, newArgs) 101*e1fe3e4aSElliott Hughes continue 102*e1fe3e4aSElliott Hughes 103*e1fe3e4aSElliott Hughes newContour.append((op, args)) 104*e1fe3e4aSElliott Hughes 105*e1fe3e4aSElliott Hughes return newContour 106