xref: /aosp_15_r20/external/fonttools/Lib/fontTools/pens/qu2cuPen.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
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