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