xref: /aosp_15_r20/external/fonttools/Lib/fontTools/qu2cu/qu2cu.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughes# cython: language_level=3
2*e1fe3e4aSElliott Hughes# distutils: define_macros=CYTHON_TRACE_NOGIL=1
3*e1fe3e4aSElliott Hughes
4*e1fe3e4aSElliott Hughes# Copyright 2023 Google Inc. All Rights Reserved.
5*e1fe3e4aSElliott Hughes# Copyright 2023 Behdad Esfahbod. All Rights Reserved.
6*e1fe3e4aSElliott Hughes#
7*e1fe3e4aSElliott Hughes# Licensed under the Apache License, Version 2.0 (the "License");
8*e1fe3e4aSElliott Hughes# you may not use this file except in compliance with the License.
9*e1fe3e4aSElliott Hughes# You may obtain a copy of the License at
10*e1fe3e4aSElliott Hughes#
11*e1fe3e4aSElliott Hughes#     http://www.apache.org/licenses/LICENSE-2.0
12*e1fe3e4aSElliott Hughes#
13*e1fe3e4aSElliott Hughes# Unless required by applicable law or agreed to in writing, software
14*e1fe3e4aSElliott Hughes# distributed under the License is distributed on an "AS IS" BASIS,
15*e1fe3e4aSElliott Hughes# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16*e1fe3e4aSElliott Hughes# See the License for the specific language governing permissions and
17*e1fe3e4aSElliott Hughes# limitations under the License.
18*e1fe3e4aSElliott Hughes
19*e1fe3e4aSElliott Hughestry:
20*e1fe3e4aSElliott Hughes    import cython
21*e1fe3e4aSElliott Hughes
22*e1fe3e4aSElliott Hughes    COMPILED = cython.compiled
23*e1fe3e4aSElliott Hughesexcept (AttributeError, ImportError):
24*e1fe3e4aSElliott Hughes    # if cython not installed, use mock module with no-op decorators and types
25*e1fe3e4aSElliott Hughes    from fontTools.misc import cython
26*e1fe3e4aSElliott Hughes
27*e1fe3e4aSElliott Hughes    COMPILED = False
28*e1fe3e4aSElliott Hughes
29*e1fe3e4aSElliott Hughesfrom fontTools.misc.bezierTools import splitCubicAtTC
30*e1fe3e4aSElliott Hughesfrom collections import namedtuple
31*e1fe3e4aSElliott Hughesimport math
32*e1fe3e4aSElliott Hughesfrom typing import (
33*e1fe3e4aSElliott Hughes    List,
34*e1fe3e4aSElliott Hughes    Tuple,
35*e1fe3e4aSElliott Hughes    Union,
36*e1fe3e4aSElliott Hughes)
37*e1fe3e4aSElliott Hughes
38*e1fe3e4aSElliott Hughes
39*e1fe3e4aSElliott Hughes__all__ = ["quadratic_to_curves"]
40*e1fe3e4aSElliott Hughes
41*e1fe3e4aSElliott Hughes
42*e1fe3e4aSElliott Hughes# Copied from cu2qu
43*e1fe3e4aSElliott Hughes@cython.cfunc
44*e1fe3e4aSElliott Hughes@cython.returns(cython.int)
45*e1fe3e4aSElliott Hughes@cython.locals(
46*e1fe3e4aSElliott Hughes    tolerance=cython.double,
47*e1fe3e4aSElliott Hughes    p0=cython.complex,
48*e1fe3e4aSElliott Hughes    p1=cython.complex,
49*e1fe3e4aSElliott Hughes    p2=cython.complex,
50*e1fe3e4aSElliott Hughes    p3=cython.complex,
51*e1fe3e4aSElliott Hughes)
52*e1fe3e4aSElliott Hughes@cython.locals(mid=cython.complex, deriv3=cython.complex)
53*e1fe3e4aSElliott Hughesdef cubic_farthest_fit_inside(p0, p1, p2, p3, tolerance):
54*e1fe3e4aSElliott Hughes    """Check if a cubic Bezier lies within a given distance of the origin.
55*e1fe3e4aSElliott Hughes
56*e1fe3e4aSElliott Hughes    "Origin" means *the* origin (0,0), not the start of the curve. Note that no
57*e1fe3e4aSElliott Hughes    checks are made on the start and end positions of the curve; this function
58*e1fe3e4aSElliott Hughes    only checks the inside of the curve.
59*e1fe3e4aSElliott Hughes
60*e1fe3e4aSElliott Hughes    Args:
61*e1fe3e4aSElliott Hughes        p0 (complex): Start point of curve.
62*e1fe3e4aSElliott Hughes        p1 (complex): First handle of curve.
63*e1fe3e4aSElliott Hughes        p2 (complex): Second handle of curve.
64*e1fe3e4aSElliott Hughes        p3 (complex): End point of curve.
65*e1fe3e4aSElliott Hughes        tolerance (double): Distance from origin.
66*e1fe3e4aSElliott Hughes
67*e1fe3e4aSElliott Hughes    Returns:
68*e1fe3e4aSElliott Hughes        bool: True if the cubic Bezier ``p`` entirely lies within a distance
69*e1fe3e4aSElliott Hughes        ``tolerance`` of the origin, False otherwise.
70*e1fe3e4aSElliott Hughes    """
71*e1fe3e4aSElliott Hughes    # First check p2 then p1, as p2 has higher error early on.
72*e1fe3e4aSElliott Hughes    if abs(p2) <= tolerance and abs(p1) <= tolerance:
73*e1fe3e4aSElliott Hughes        return True
74*e1fe3e4aSElliott Hughes
75*e1fe3e4aSElliott Hughes    # Split.
76*e1fe3e4aSElliott Hughes    mid = (p0 + 3 * (p1 + p2) + p3) * 0.125
77*e1fe3e4aSElliott Hughes    if abs(mid) > tolerance:
78*e1fe3e4aSElliott Hughes        return False
79*e1fe3e4aSElliott Hughes    deriv3 = (p3 + p2 - p1 - p0) * 0.125
80*e1fe3e4aSElliott Hughes    return cubic_farthest_fit_inside(
81*e1fe3e4aSElliott Hughes        p0, (p0 + p1) * 0.5, mid - deriv3, mid, tolerance
82*e1fe3e4aSElliott Hughes    ) and cubic_farthest_fit_inside(mid, mid + deriv3, (p2 + p3) * 0.5, p3, tolerance)
83*e1fe3e4aSElliott Hughes
84*e1fe3e4aSElliott Hughes
85*e1fe3e4aSElliott Hughes@cython.locals(
86*e1fe3e4aSElliott Hughes    p0=cython.complex,
87*e1fe3e4aSElliott Hughes    p1=cython.complex,
88*e1fe3e4aSElliott Hughes    p2=cython.complex,
89*e1fe3e4aSElliott Hughes    p1_2_3=cython.complex,
90*e1fe3e4aSElliott Hughes)
91*e1fe3e4aSElliott Hughesdef elevate_quadratic(p0, p1, p2):
92*e1fe3e4aSElliott Hughes    """Given a quadratic bezier curve, return its degree-elevated cubic."""
93*e1fe3e4aSElliott Hughes
94*e1fe3e4aSElliott Hughes    # https://pomax.github.io/bezierinfo/#reordering
95*e1fe3e4aSElliott Hughes    p1_2_3 = p1 * (2 / 3)
96*e1fe3e4aSElliott Hughes    return (
97*e1fe3e4aSElliott Hughes        p0,
98*e1fe3e4aSElliott Hughes        (p0 * (1 / 3) + p1_2_3),
99*e1fe3e4aSElliott Hughes        (p2 * (1 / 3) + p1_2_3),
100*e1fe3e4aSElliott Hughes        p2,
101*e1fe3e4aSElliott Hughes    )
102*e1fe3e4aSElliott Hughes
103*e1fe3e4aSElliott Hughes
104*e1fe3e4aSElliott Hughes@cython.cfunc
105*e1fe3e4aSElliott Hughes@cython.locals(
106*e1fe3e4aSElliott Hughes    start=cython.int,
107*e1fe3e4aSElliott Hughes    n=cython.int,
108*e1fe3e4aSElliott Hughes    k=cython.int,
109*e1fe3e4aSElliott Hughes    prod_ratio=cython.double,
110*e1fe3e4aSElliott Hughes    sum_ratio=cython.double,
111*e1fe3e4aSElliott Hughes    ratio=cython.double,
112*e1fe3e4aSElliott Hughes    t=cython.double,
113*e1fe3e4aSElliott Hughes    p0=cython.complex,
114*e1fe3e4aSElliott Hughes    p1=cython.complex,
115*e1fe3e4aSElliott Hughes    p2=cython.complex,
116*e1fe3e4aSElliott Hughes    p3=cython.complex,
117*e1fe3e4aSElliott Hughes)
118*e1fe3e4aSElliott Hughesdef merge_curves(curves, start, n):
119*e1fe3e4aSElliott Hughes    """Give a cubic-Bezier spline, reconstruct one cubic-Bezier
120*e1fe3e4aSElliott Hughes    that has the same endpoints and tangents and approxmates
121*e1fe3e4aSElliott Hughes    the spline."""
122*e1fe3e4aSElliott Hughes
123*e1fe3e4aSElliott Hughes    # Reconstruct the t values of the cut segments
124*e1fe3e4aSElliott Hughes    prod_ratio = 1.0
125*e1fe3e4aSElliott Hughes    sum_ratio = 1.0
126*e1fe3e4aSElliott Hughes    ts = [1]
127*e1fe3e4aSElliott Hughes    for k in range(1, n):
128*e1fe3e4aSElliott Hughes        ck = curves[start + k]
129*e1fe3e4aSElliott Hughes        c_before = curves[start + k - 1]
130*e1fe3e4aSElliott Hughes
131*e1fe3e4aSElliott Hughes        # |t_(k+1) - t_k| / |t_k - t_(k - 1)| = ratio
132*e1fe3e4aSElliott Hughes        assert ck[0] == c_before[3]
133*e1fe3e4aSElliott Hughes        ratio = abs(ck[1] - ck[0]) / abs(c_before[3] - c_before[2])
134*e1fe3e4aSElliott Hughes
135*e1fe3e4aSElliott Hughes        prod_ratio *= ratio
136*e1fe3e4aSElliott Hughes        sum_ratio += prod_ratio
137*e1fe3e4aSElliott Hughes        ts.append(sum_ratio)
138*e1fe3e4aSElliott Hughes
139*e1fe3e4aSElliott Hughes    # (t(n) - t(n - 1)) / (t_(1) - t(0)) = prod_ratio
140*e1fe3e4aSElliott Hughes
141*e1fe3e4aSElliott Hughes    ts = [t / sum_ratio for t in ts[:-1]]
142*e1fe3e4aSElliott Hughes
143*e1fe3e4aSElliott Hughes    p0 = curves[start][0]
144*e1fe3e4aSElliott Hughes    p1 = curves[start][1]
145*e1fe3e4aSElliott Hughes    p2 = curves[start + n - 1][2]
146*e1fe3e4aSElliott Hughes    p3 = curves[start + n - 1][3]
147*e1fe3e4aSElliott Hughes
148*e1fe3e4aSElliott Hughes    # Build the curve by scaling the control-points.
149*e1fe3e4aSElliott Hughes    p1 = p0 + (p1 - p0) / (ts[0] if ts else 1)
150*e1fe3e4aSElliott Hughes    p2 = p3 + (p2 - p3) / ((1 - ts[-1]) if ts else 1)
151*e1fe3e4aSElliott Hughes
152*e1fe3e4aSElliott Hughes    curve = (p0, p1, p2, p3)
153*e1fe3e4aSElliott Hughes
154*e1fe3e4aSElliott Hughes    return curve, ts
155*e1fe3e4aSElliott Hughes
156*e1fe3e4aSElliott Hughes
157*e1fe3e4aSElliott Hughes@cython.locals(
158*e1fe3e4aSElliott Hughes    count=cython.int,
159*e1fe3e4aSElliott Hughes    num_offcurves=cython.int,
160*e1fe3e4aSElliott Hughes    i=cython.int,
161*e1fe3e4aSElliott Hughes    off1=cython.complex,
162*e1fe3e4aSElliott Hughes    off2=cython.complex,
163*e1fe3e4aSElliott Hughes    on=cython.complex,
164*e1fe3e4aSElliott Hughes)
165*e1fe3e4aSElliott Hughesdef add_implicit_on_curves(p):
166*e1fe3e4aSElliott Hughes    q = list(p)
167*e1fe3e4aSElliott Hughes    count = 0
168*e1fe3e4aSElliott Hughes    num_offcurves = len(p) - 2
169*e1fe3e4aSElliott Hughes    for i in range(1, num_offcurves):
170*e1fe3e4aSElliott Hughes        off1 = p[i]
171*e1fe3e4aSElliott Hughes        off2 = p[i + 1]
172*e1fe3e4aSElliott Hughes        on = off1 + (off2 - off1) * 0.5
173*e1fe3e4aSElliott Hughes        q.insert(i + 1 + count, on)
174*e1fe3e4aSElliott Hughes        count += 1
175*e1fe3e4aSElliott Hughes    return q
176*e1fe3e4aSElliott Hughes
177*e1fe3e4aSElliott Hughes
178*e1fe3e4aSElliott HughesPoint = Union[Tuple[float, float], complex]
179*e1fe3e4aSElliott Hughes
180*e1fe3e4aSElliott Hughes
181*e1fe3e4aSElliott Hughes@cython.locals(
182*e1fe3e4aSElliott Hughes    cost=cython.int,
183*e1fe3e4aSElliott Hughes    is_complex=cython.int,
184*e1fe3e4aSElliott Hughes)
185*e1fe3e4aSElliott Hughesdef quadratic_to_curves(
186*e1fe3e4aSElliott Hughes    quads: List[List[Point]],
187*e1fe3e4aSElliott Hughes    max_err: float = 0.5,
188*e1fe3e4aSElliott Hughes    all_cubic: bool = False,
189*e1fe3e4aSElliott Hughes) -> List[Tuple[Point, ...]]:
190*e1fe3e4aSElliott Hughes    """Converts a connecting list of quadratic splines to a list of quadratic
191*e1fe3e4aSElliott Hughes    and cubic curves.
192*e1fe3e4aSElliott Hughes
193*e1fe3e4aSElliott Hughes    A quadratic spline is specified as a list of points.  Either each point is
194*e1fe3e4aSElliott Hughes    a 2-tuple of X,Y coordinates, or each point is a complex number with
195*e1fe3e4aSElliott Hughes    real/imaginary components representing X,Y coordinates.
196*e1fe3e4aSElliott Hughes
197*e1fe3e4aSElliott Hughes    The first and last points are on-curve points and the rest are off-curve
198*e1fe3e4aSElliott Hughes    points, with an implied on-curve point in the middle between every two
199*e1fe3e4aSElliott Hughes    consequtive off-curve points.
200*e1fe3e4aSElliott Hughes
201*e1fe3e4aSElliott Hughes    Returns:
202*e1fe3e4aSElliott Hughes        The output is a list of tuples of points. Points are represented
203*e1fe3e4aSElliott Hughes        in the same format as the input, either as 2-tuples or complex numbers.
204*e1fe3e4aSElliott Hughes
205*e1fe3e4aSElliott Hughes        Each tuple is either of length three, for a quadratic curve, or four,
206*e1fe3e4aSElliott Hughes        for a cubic curve.  Each curve's last point is the same as the next
207*e1fe3e4aSElliott Hughes        curve's first point.
208*e1fe3e4aSElliott Hughes
209*e1fe3e4aSElliott Hughes    Args:
210*e1fe3e4aSElliott Hughes        quads: quadratic splines
211*e1fe3e4aSElliott Hughes
212*e1fe3e4aSElliott Hughes        max_err: absolute error tolerance; defaults to 0.5
213*e1fe3e4aSElliott Hughes
214*e1fe3e4aSElliott Hughes        all_cubic: if True, only cubic curves are generated; defaults to False
215*e1fe3e4aSElliott Hughes    """
216*e1fe3e4aSElliott Hughes    is_complex = type(quads[0][0]) is complex
217*e1fe3e4aSElliott Hughes    if not is_complex:
218*e1fe3e4aSElliott Hughes        quads = [[complex(x, y) for (x, y) in p] for p in quads]
219*e1fe3e4aSElliott Hughes
220*e1fe3e4aSElliott Hughes    q = [quads[0][0]]
221*e1fe3e4aSElliott Hughes    costs = [1]
222*e1fe3e4aSElliott Hughes    cost = 1
223*e1fe3e4aSElliott Hughes    for p in quads:
224*e1fe3e4aSElliott Hughes        assert q[-1] == p[0]
225*e1fe3e4aSElliott Hughes        for i in range(len(p) - 2):
226*e1fe3e4aSElliott Hughes            cost += 1
227*e1fe3e4aSElliott Hughes            costs.append(cost)
228*e1fe3e4aSElliott Hughes            costs.append(cost)
229*e1fe3e4aSElliott Hughes        qq = add_implicit_on_curves(p)[1:]
230*e1fe3e4aSElliott Hughes        costs.pop()
231*e1fe3e4aSElliott Hughes        q.extend(qq)
232*e1fe3e4aSElliott Hughes        cost += 1
233*e1fe3e4aSElliott Hughes        costs.append(cost)
234*e1fe3e4aSElliott Hughes
235*e1fe3e4aSElliott Hughes    curves = spline_to_curves(q, costs, max_err, all_cubic)
236*e1fe3e4aSElliott Hughes
237*e1fe3e4aSElliott Hughes    if not is_complex:
238*e1fe3e4aSElliott Hughes        curves = [tuple((c.real, c.imag) for c in curve) for curve in curves]
239*e1fe3e4aSElliott Hughes    return curves
240*e1fe3e4aSElliott Hughes
241*e1fe3e4aSElliott Hughes
242*e1fe3e4aSElliott HughesSolution = namedtuple("Solution", ["num_points", "error", "start_index", "is_cubic"])
243*e1fe3e4aSElliott Hughes
244*e1fe3e4aSElliott Hughes
245*e1fe3e4aSElliott Hughes@cython.locals(
246*e1fe3e4aSElliott Hughes    i=cython.int,
247*e1fe3e4aSElliott Hughes    j=cython.int,
248*e1fe3e4aSElliott Hughes    k=cython.int,
249*e1fe3e4aSElliott Hughes    start=cython.int,
250*e1fe3e4aSElliott Hughes    i_sol_count=cython.int,
251*e1fe3e4aSElliott Hughes    j_sol_count=cython.int,
252*e1fe3e4aSElliott Hughes    this_sol_count=cython.int,
253*e1fe3e4aSElliott Hughes    tolerance=cython.double,
254*e1fe3e4aSElliott Hughes    err=cython.double,
255*e1fe3e4aSElliott Hughes    error=cython.double,
256*e1fe3e4aSElliott Hughes    i_sol_error=cython.double,
257*e1fe3e4aSElliott Hughes    j_sol_error=cython.double,
258*e1fe3e4aSElliott Hughes    all_cubic=cython.int,
259*e1fe3e4aSElliott Hughes    is_cubic=cython.int,
260*e1fe3e4aSElliott Hughes    count=cython.int,
261*e1fe3e4aSElliott Hughes    p0=cython.complex,
262*e1fe3e4aSElliott Hughes    p1=cython.complex,
263*e1fe3e4aSElliott Hughes    p2=cython.complex,
264*e1fe3e4aSElliott Hughes    p3=cython.complex,
265*e1fe3e4aSElliott Hughes    v=cython.complex,
266*e1fe3e4aSElliott Hughes    u=cython.complex,
267*e1fe3e4aSElliott Hughes)
268*e1fe3e4aSElliott Hughesdef spline_to_curves(q, costs, tolerance=0.5, all_cubic=False):
269*e1fe3e4aSElliott Hughes    """
270*e1fe3e4aSElliott Hughes    q: quadratic spline with alternating on-curve / off-curve points.
271*e1fe3e4aSElliott Hughes
272*e1fe3e4aSElliott Hughes    costs: cumulative list of encoding cost of q in terms of number of
273*e1fe3e4aSElliott Hughes      points that need to be encoded.  Implied on-curve points do not
274*e1fe3e4aSElliott Hughes      contribute to the cost. If all points need to be encoded, then
275*e1fe3e4aSElliott Hughes      costs will be range(1, len(q)+1).
276*e1fe3e4aSElliott Hughes    """
277*e1fe3e4aSElliott Hughes
278*e1fe3e4aSElliott Hughes    assert len(q) >= 3, "quadratic spline requires at least 3 points"
279*e1fe3e4aSElliott Hughes
280*e1fe3e4aSElliott Hughes    # Elevate quadratic segments to cubic
281*e1fe3e4aSElliott Hughes    elevated_quadratics = [
282*e1fe3e4aSElliott Hughes        elevate_quadratic(*q[i : i + 3]) for i in range(0, len(q) - 2, 2)
283*e1fe3e4aSElliott Hughes    ]
284*e1fe3e4aSElliott Hughes
285*e1fe3e4aSElliott Hughes    # Find sharp corners; they have to be oncurves for sure.
286*e1fe3e4aSElliott Hughes    forced = set()
287*e1fe3e4aSElliott Hughes    for i in range(1, len(elevated_quadratics)):
288*e1fe3e4aSElliott Hughes        p0 = elevated_quadratics[i - 1][2]
289*e1fe3e4aSElliott Hughes        p1 = elevated_quadratics[i][0]
290*e1fe3e4aSElliott Hughes        p2 = elevated_quadratics[i][1]
291*e1fe3e4aSElliott Hughes        if abs(p1 - p0) + abs(p2 - p1) > tolerance + abs(p2 - p0):
292*e1fe3e4aSElliott Hughes            forced.add(i)
293*e1fe3e4aSElliott Hughes
294*e1fe3e4aSElliott Hughes    # Dynamic-Programming to find the solution with fewest number of
295*e1fe3e4aSElliott Hughes    # cubic curves, and within those the one with smallest error.
296*e1fe3e4aSElliott Hughes    sols = [Solution(0, 0, 0, False)]
297*e1fe3e4aSElliott Hughes    impossible = Solution(len(elevated_quadratics) * 3 + 1, 0, 1, False)
298*e1fe3e4aSElliott Hughes    start = 0
299*e1fe3e4aSElliott Hughes    for i in range(1, len(elevated_quadratics) + 1):
300*e1fe3e4aSElliott Hughes        best_sol = impossible
301*e1fe3e4aSElliott Hughes        for j in range(start, i):
302*e1fe3e4aSElliott Hughes            j_sol_count, j_sol_error = sols[j].num_points, sols[j].error
303*e1fe3e4aSElliott Hughes
304*e1fe3e4aSElliott Hughes            if not all_cubic:
305*e1fe3e4aSElliott Hughes                # Solution with quadratics between j:i
306*e1fe3e4aSElliott Hughes                this_count = costs[2 * i - 1] - costs[2 * j] + 1
307*e1fe3e4aSElliott Hughes                i_sol_count = j_sol_count + this_count
308*e1fe3e4aSElliott Hughes                i_sol_error = j_sol_error
309*e1fe3e4aSElliott Hughes                i_sol = Solution(i_sol_count, i_sol_error, i - j, False)
310*e1fe3e4aSElliott Hughes                if i_sol < best_sol:
311*e1fe3e4aSElliott Hughes                    best_sol = i_sol
312*e1fe3e4aSElliott Hughes
313*e1fe3e4aSElliott Hughes                if this_count <= 3:
314*e1fe3e4aSElliott Hughes                    # Can't get any better than this in the path below
315*e1fe3e4aSElliott Hughes                    continue
316*e1fe3e4aSElliott Hughes
317*e1fe3e4aSElliott Hughes            # Fit elevated_quadratics[j:i] into one cubic
318*e1fe3e4aSElliott Hughes            try:
319*e1fe3e4aSElliott Hughes                curve, ts = merge_curves(elevated_quadratics, j, i - j)
320*e1fe3e4aSElliott Hughes            except ZeroDivisionError:
321*e1fe3e4aSElliott Hughes                continue
322*e1fe3e4aSElliott Hughes
323*e1fe3e4aSElliott Hughes            # Now reconstruct the segments from the fitted curve
324*e1fe3e4aSElliott Hughes            reconstructed_iter = splitCubicAtTC(*curve, *ts)
325*e1fe3e4aSElliott Hughes            reconstructed = []
326*e1fe3e4aSElliott Hughes
327*e1fe3e4aSElliott Hughes            # Knot errors
328*e1fe3e4aSElliott Hughes            error = 0
329*e1fe3e4aSElliott Hughes            for k, reconst in enumerate(reconstructed_iter):
330*e1fe3e4aSElliott Hughes                orig = elevated_quadratics[j + k]
331*e1fe3e4aSElliott Hughes                err = abs(reconst[3] - orig[3])
332*e1fe3e4aSElliott Hughes                error = max(error, err)
333*e1fe3e4aSElliott Hughes                if error > tolerance:
334*e1fe3e4aSElliott Hughes                    break
335*e1fe3e4aSElliott Hughes                reconstructed.append(reconst)
336*e1fe3e4aSElliott Hughes            if error > tolerance:
337*e1fe3e4aSElliott Hughes                # Not feasible
338*e1fe3e4aSElliott Hughes                continue
339*e1fe3e4aSElliott Hughes
340*e1fe3e4aSElliott Hughes            # Interior errors
341*e1fe3e4aSElliott Hughes            for k, reconst in enumerate(reconstructed):
342*e1fe3e4aSElliott Hughes                orig = elevated_quadratics[j + k]
343*e1fe3e4aSElliott Hughes                p0, p1, p2, p3 = tuple(v - u for v, u in zip(reconst, orig))
344*e1fe3e4aSElliott Hughes
345*e1fe3e4aSElliott Hughes                if not cubic_farthest_fit_inside(p0, p1, p2, p3, tolerance):
346*e1fe3e4aSElliott Hughes                    error = tolerance + 1
347*e1fe3e4aSElliott Hughes                    break
348*e1fe3e4aSElliott Hughes            if error > tolerance:
349*e1fe3e4aSElliott Hughes                # Not feasible
350*e1fe3e4aSElliott Hughes                continue
351*e1fe3e4aSElliott Hughes
352*e1fe3e4aSElliott Hughes            # Save best solution
353*e1fe3e4aSElliott Hughes            i_sol_count = j_sol_count + 3
354*e1fe3e4aSElliott Hughes            i_sol_error = max(j_sol_error, error)
355*e1fe3e4aSElliott Hughes            i_sol = Solution(i_sol_count, i_sol_error, i - j, True)
356*e1fe3e4aSElliott Hughes            if i_sol < best_sol:
357*e1fe3e4aSElliott Hughes                best_sol = i_sol
358*e1fe3e4aSElliott Hughes
359*e1fe3e4aSElliott Hughes            if i_sol_count == 3:
360*e1fe3e4aSElliott Hughes                # Can't get any better than this
361*e1fe3e4aSElliott Hughes                break
362*e1fe3e4aSElliott Hughes
363*e1fe3e4aSElliott Hughes        sols.append(best_sol)
364*e1fe3e4aSElliott Hughes        if i in forced:
365*e1fe3e4aSElliott Hughes            start = i
366*e1fe3e4aSElliott Hughes
367*e1fe3e4aSElliott Hughes    # Reconstruct solution
368*e1fe3e4aSElliott Hughes    splits = []
369*e1fe3e4aSElliott Hughes    cubic = []
370*e1fe3e4aSElliott Hughes    i = len(sols) - 1
371*e1fe3e4aSElliott Hughes    while i:
372*e1fe3e4aSElliott Hughes        count, is_cubic = sols[i].start_index, sols[i].is_cubic
373*e1fe3e4aSElliott Hughes        splits.append(i)
374*e1fe3e4aSElliott Hughes        cubic.append(is_cubic)
375*e1fe3e4aSElliott Hughes        i -= count
376*e1fe3e4aSElliott Hughes    curves = []
377*e1fe3e4aSElliott Hughes    j = 0
378*e1fe3e4aSElliott Hughes    for i, is_cubic in reversed(list(zip(splits, cubic))):
379*e1fe3e4aSElliott Hughes        if is_cubic:
380*e1fe3e4aSElliott Hughes            curves.append(merge_curves(elevated_quadratics, j, i - j)[0])
381*e1fe3e4aSElliott Hughes        else:
382*e1fe3e4aSElliott Hughes            for k in range(j, i):
383*e1fe3e4aSElliott Hughes                curves.append(q[k * 2 : k * 2 + 3])
384*e1fe3e4aSElliott Hughes        j = i
385*e1fe3e4aSElliott Hughes
386*e1fe3e4aSElliott Hughes    return curves
387*e1fe3e4aSElliott Hughes
388*e1fe3e4aSElliott Hughes
389*e1fe3e4aSElliott Hughesdef main():
390*e1fe3e4aSElliott Hughes    from fontTools.cu2qu.benchmark import generate_curve
391*e1fe3e4aSElliott Hughes    from fontTools.cu2qu import curve_to_quadratic
392*e1fe3e4aSElliott Hughes
393*e1fe3e4aSElliott Hughes    tolerance = 0.05
394*e1fe3e4aSElliott Hughes    reconstruct_tolerance = tolerance * 1
395*e1fe3e4aSElliott Hughes    curve = generate_curve()
396*e1fe3e4aSElliott Hughes    quadratics = curve_to_quadratic(curve, tolerance)
397*e1fe3e4aSElliott Hughes    print(
398*e1fe3e4aSElliott Hughes        "cu2qu tolerance %g. qu2cu tolerance %g." % (tolerance, reconstruct_tolerance)
399*e1fe3e4aSElliott Hughes    )
400*e1fe3e4aSElliott Hughes    print("One random cubic turned into %d quadratics." % len(quadratics))
401*e1fe3e4aSElliott Hughes    curves = quadratic_to_curves([quadratics], reconstruct_tolerance)
402*e1fe3e4aSElliott Hughes    print("Those quadratics turned back into %d cubics. " % len(curves))
403*e1fe3e4aSElliott Hughes    print("Original curve:", curve)
404*e1fe3e4aSElliott Hughes    print("Reconstructed curve(s):", curves)
405*e1fe3e4aSElliott Hughes
406*e1fe3e4aSElliott Hughes
407*e1fe3e4aSElliott Hughesif __name__ == "__main__":
408*e1fe3e4aSElliott Hughes    main()
409