asd
This commit is contained in:
		| @ -0,0 +1,15 @@ | ||||
| # Copyright 2016 Google Inc. All Rights Reserved. | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
|  | ||||
| from .qu2cu import * | ||||
| @ -0,0 +1,7 @@ | ||||
| import sys | ||||
|  | ||||
| from .cli import _main as main | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     sys.exit(main()) | ||||
| @ -0,0 +1,56 @@ | ||||
| """Benchmark the qu2cu algorithm performance.""" | ||||
|  | ||||
| from .qu2cu import * | ||||
| from fontTools.cu2qu import curve_to_quadratic | ||||
| import random | ||||
| import timeit | ||||
|  | ||||
| MAX_ERR = 0.5 | ||||
| NUM_CURVES = 5 | ||||
|  | ||||
|  | ||||
| def generate_curves(n): | ||||
|     points = [ | ||||
|         tuple(float(random.randint(0, 2048)) for coord in range(2)) | ||||
|         for point in range(1 + 3 * n) | ||||
|     ] | ||||
|     curves = [] | ||||
|     for i in range(n): | ||||
|         curves.append(tuple(points[i * 3 : i * 3 + 4])) | ||||
|     return curves | ||||
|  | ||||
|  | ||||
| def setup_quadratic_to_curves(): | ||||
|     curves = generate_curves(NUM_CURVES) | ||||
|     quadratics = [curve_to_quadratic(curve, MAX_ERR) for curve in curves] | ||||
|     return quadratics, MAX_ERR | ||||
|  | ||||
|  | ||||
| def run_benchmark(module, function, setup_suffix="", repeat=25, number=1): | ||||
|     setup_func = "setup_" + function | ||||
|     if setup_suffix: | ||||
|         print("%s with %s:" % (function, setup_suffix), end="") | ||||
|         setup_func += "_" + setup_suffix | ||||
|     else: | ||||
|         print("%s:" % function, end="") | ||||
|  | ||||
|     def wrapper(function, setup_func): | ||||
|         function = globals()[function] | ||||
|         setup_func = globals()[setup_func] | ||||
|  | ||||
|         def wrapped(): | ||||
|             return function(*setup_func()) | ||||
|  | ||||
|         return wrapped | ||||
|  | ||||
|     results = timeit.repeat(wrapper(function, setup_func), repeat=repeat, number=number) | ||||
|     print("\t%5.1fus" % (min(results) * 1000000.0 / number)) | ||||
|  | ||||
|  | ||||
| def main(): | ||||
|     run_benchmark("qu2cu", "quadratic_to_curves") | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     random.seed(1) | ||||
|     main() | ||||
							
								
								
									
										125
									
								
								venv/lib/python3.12/site-packages/fontTools/qu2cu/cli.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								venv/lib/python3.12/site-packages/fontTools/qu2cu/cli.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,125 @@ | ||||
| import os | ||||
| import argparse | ||||
| import logging | ||||
| from fontTools.misc.cliTools import makeOutputFileName | ||||
| from fontTools.ttLib import TTFont | ||||
| from fontTools.pens.qu2cuPen import Qu2CuPen | ||||
| from fontTools.pens.ttGlyphPen import TTGlyphPen | ||||
| import fontTools | ||||
|  | ||||
|  | ||||
| logger = logging.getLogger("fontTools.qu2cu") | ||||
|  | ||||
|  | ||||
| def _font_to_cubic(input_path, output_path=None, **kwargs): | ||||
|     font = TTFont(input_path) | ||||
|     logger.info("Converting curves for %s", input_path) | ||||
|  | ||||
|     stats = {} if kwargs["dump_stats"] else None | ||||
|     qu2cu_kwargs = { | ||||
|         "stats": stats, | ||||
|         "max_err": kwargs["max_err_em"] * font["head"].unitsPerEm, | ||||
|         "all_cubic": kwargs["all_cubic"], | ||||
|     } | ||||
|  | ||||
|     assert "gvar" not in font, "Cannot convert variable font" | ||||
|     glyphSet = font.getGlyphSet() | ||||
|     glyphOrder = font.getGlyphOrder() | ||||
|     glyf = font["glyf"] | ||||
|     for glyphName in glyphOrder: | ||||
|         glyph = glyphSet[glyphName] | ||||
|         ttpen = TTGlyphPen(glyphSet) | ||||
|         pen = Qu2CuPen(ttpen, **qu2cu_kwargs) | ||||
|         glyph.draw(pen) | ||||
|         glyf[glyphName] = ttpen.glyph(dropImpliedOnCurves=True) | ||||
|  | ||||
|     font["head"].glyphDataFormat = 1 | ||||
|  | ||||
|     if kwargs["dump_stats"]: | ||||
|         logger.info("Stats: %s", stats) | ||||
|  | ||||
|     logger.info("Saving %s", output_path) | ||||
|     font.save(output_path) | ||||
|  | ||||
|  | ||||
| def _main(args=None): | ||||
|     """Convert an OpenType font from quadratic to cubic curves""" | ||||
|     parser = argparse.ArgumentParser(prog="qu2cu") | ||||
|     parser.add_argument("--version", action="version", version=fontTools.__version__) | ||||
|     parser.add_argument( | ||||
|         "infiles", | ||||
|         nargs="+", | ||||
|         metavar="INPUT", | ||||
|         help="one or more input TTF source file(s).", | ||||
|     ) | ||||
|     parser.add_argument("-v", "--verbose", action="count", default=0) | ||||
|     parser.add_argument( | ||||
|         "-e", | ||||
|         "--conversion-error", | ||||
|         type=float, | ||||
|         metavar="ERROR", | ||||
|         default=0.001, | ||||
|         help="maxiumum approximation error measured in EM (default: 0.001)", | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "-c", | ||||
|         "--all-cubic", | ||||
|         default=False, | ||||
|         action="store_true", | ||||
|         help="whether to only use cubic curves", | ||||
|     ) | ||||
|  | ||||
|     output_parser = parser.add_mutually_exclusive_group() | ||||
|     output_parser.add_argument( | ||||
|         "-o", | ||||
|         "--output-file", | ||||
|         default=None, | ||||
|         metavar="OUTPUT", | ||||
|         help=("output filename for the converted TTF."), | ||||
|     ) | ||||
|     output_parser.add_argument( | ||||
|         "-d", | ||||
|         "--output-dir", | ||||
|         default=None, | ||||
|         metavar="DIRECTORY", | ||||
|         help="output directory where to save converted TTFs", | ||||
|     ) | ||||
|  | ||||
|     options = parser.parse_args(args) | ||||
|  | ||||
|     if not options.verbose: | ||||
|         level = "WARNING" | ||||
|     elif options.verbose == 1: | ||||
|         level = "INFO" | ||||
|     else: | ||||
|         level = "DEBUG" | ||||
|     logging.basicConfig(level=level) | ||||
|  | ||||
|     if len(options.infiles) > 1 and options.output_file: | ||||
|         parser.error("-o/--output-file can't be used with multile inputs") | ||||
|  | ||||
|     if options.output_dir: | ||||
|         output_dir = options.output_dir | ||||
|         if not os.path.exists(output_dir): | ||||
|             os.mkdir(output_dir) | ||||
|         elif not os.path.isdir(output_dir): | ||||
|             parser.error("'%s' is not a directory" % output_dir) | ||||
|         output_paths = [ | ||||
|             os.path.join(output_dir, os.path.basename(p)) for p in options.infiles | ||||
|         ] | ||||
|     elif options.output_file: | ||||
|         output_paths = [options.output_file] | ||||
|     else: | ||||
|         output_paths = [ | ||||
|             makeOutputFileName(p, overWrite=True, suffix=".cubic") | ||||
|             for p in options.infiles | ||||
|         ] | ||||
|  | ||||
|     kwargs = dict( | ||||
|         dump_stats=options.verbose > 0, | ||||
|         max_err_em=options.conversion_error, | ||||
|         all_cubic=options.all_cubic, | ||||
|     ) | ||||
|  | ||||
|     for input_path, output_path in zip(options.infiles, output_paths): | ||||
|         _font_to_cubic(input_path, output_path, **kwargs) | ||||
							
								
								
									
										16365
									
								
								venv/lib/python3.12/site-packages/fontTools/qu2cu/qu2cu.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16365
									
								
								venv/lib/python3.12/site-packages/fontTools/qu2cu/qu2cu.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										408
									
								
								venv/lib/python3.12/site-packages/fontTools/qu2cu/qu2cu.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										408
									
								
								venv/lib/python3.12/site-packages/fontTools/qu2cu/qu2cu.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,408 @@ | ||||
| # cython: language_level=3 | ||||
| # distutils: define_macros=CYTHON_TRACE_NOGIL=1 | ||||
|  | ||||
| # Copyright 2023 Google Inc. All Rights Reserved. | ||||
| # Copyright 2023 Behdad Esfahbod. All Rights Reserved. | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
|  | ||||
| try: | ||||
|     import cython | ||||
|  | ||||
|     COMPILED = cython.compiled | ||||
| except (AttributeError, ImportError): | ||||
|     # if cython not installed, use mock module with no-op decorators and types | ||||
|     from fontTools.misc import cython | ||||
|  | ||||
|     COMPILED = False | ||||
|  | ||||
| from fontTools.misc.bezierTools import splitCubicAtTC | ||||
| from collections import namedtuple | ||||
| import math | ||||
| from typing import ( | ||||
|     List, | ||||
|     Tuple, | ||||
|     Union, | ||||
| ) | ||||
|  | ||||
|  | ||||
| __all__ = ["quadratic_to_curves"] | ||||
|  | ||||
|  | ||||
| # Copied from cu2qu | ||||
| @cython.cfunc | ||||
| @cython.returns(cython.int) | ||||
| @cython.locals( | ||||
|     tolerance=cython.double, | ||||
|     p0=cython.complex, | ||||
|     p1=cython.complex, | ||||
|     p2=cython.complex, | ||||
|     p3=cython.complex, | ||||
| ) | ||||
| @cython.locals(mid=cython.complex, deriv3=cython.complex) | ||||
| def cubic_farthest_fit_inside(p0, p1, p2, p3, tolerance): | ||||
|     """Check if a cubic Bezier lies within a given distance of the origin. | ||||
|  | ||||
|     "Origin" means *the* origin (0,0), not the start of the curve. Note that no | ||||
|     checks are made on the start and end positions of the curve; this function | ||||
|     only checks the inside of the curve. | ||||
|  | ||||
|     Args: | ||||
|         p0 (complex): Start point of curve. | ||||
|         p1 (complex): First handle of curve. | ||||
|         p2 (complex): Second handle of curve. | ||||
|         p3 (complex): End point of curve. | ||||
|         tolerance (double): Distance from origin. | ||||
|  | ||||
|     Returns: | ||||
|         bool: True if the cubic Bezier ``p`` entirely lies within a distance | ||||
|         ``tolerance`` of the origin, False otherwise. | ||||
|     """ | ||||
|     # First check p2 then p1, as p2 has higher error early on. | ||||
|     if abs(p2) <= tolerance and abs(p1) <= tolerance: | ||||
|         return True | ||||
|  | ||||
|     # Split. | ||||
|     mid = (p0 + 3 * (p1 + p2) + p3) * 0.125 | ||||
|     if abs(mid) > tolerance: | ||||
|         return False | ||||
|     deriv3 = (p3 + p2 - p1 - p0) * 0.125 | ||||
|     return cubic_farthest_fit_inside( | ||||
|         p0, (p0 + p1) * 0.5, mid - deriv3, mid, tolerance | ||||
|     ) and cubic_farthest_fit_inside(mid, mid + deriv3, (p2 + p3) * 0.5, p3, tolerance) | ||||
|  | ||||
|  | ||||
| @cython.locals( | ||||
|     p0=cython.complex, | ||||
|     p1=cython.complex, | ||||
|     p2=cython.complex, | ||||
|     p1_2_3=cython.complex, | ||||
| ) | ||||
| def elevate_quadratic(p0, p1, p2): | ||||
|     """Given a quadratic bezier curve, return its degree-elevated cubic.""" | ||||
|  | ||||
|     # https://pomax.github.io/bezierinfo/#reordering | ||||
|     p1_2_3 = p1 * (2 / 3) | ||||
|     return ( | ||||
|         p0, | ||||
|         (p0 * (1 / 3) + p1_2_3), | ||||
|         (p2 * (1 / 3) + p1_2_3), | ||||
|         p2, | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @cython.cfunc | ||||
| @cython.locals( | ||||
|     start=cython.int, | ||||
|     n=cython.int, | ||||
|     k=cython.int, | ||||
|     prod_ratio=cython.double, | ||||
|     sum_ratio=cython.double, | ||||
|     ratio=cython.double, | ||||
|     t=cython.double, | ||||
|     p0=cython.complex, | ||||
|     p1=cython.complex, | ||||
|     p2=cython.complex, | ||||
|     p3=cython.complex, | ||||
| ) | ||||
| def merge_curves(curves, start, n): | ||||
|     """Give a cubic-Bezier spline, reconstruct one cubic-Bezier | ||||
|     that has the same endpoints and tangents and approxmates | ||||
|     the spline.""" | ||||
|  | ||||
|     # Reconstruct the t values of the cut segments | ||||
|     prod_ratio = 1.0 | ||||
|     sum_ratio = 1.0 | ||||
|     ts = [1] | ||||
|     for k in range(1, n): | ||||
|         ck = curves[start + k] | ||||
|         c_before = curves[start + k - 1] | ||||
|  | ||||
|         # |t_(k+1) - t_k| / |t_k - t_(k - 1)| = ratio | ||||
|         assert ck[0] == c_before[3] | ||||
|         ratio = abs(ck[1] - ck[0]) / abs(c_before[3] - c_before[2]) | ||||
|  | ||||
|         prod_ratio *= ratio | ||||
|         sum_ratio += prod_ratio | ||||
|         ts.append(sum_ratio) | ||||
|  | ||||
|     # (t(n) - t(n - 1)) / (t_(1) - t(0)) = prod_ratio | ||||
|  | ||||
|     ts = [t / sum_ratio for t in ts[:-1]] | ||||
|  | ||||
|     p0 = curves[start][0] | ||||
|     p1 = curves[start][1] | ||||
|     p2 = curves[start + n - 1][2] | ||||
|     p3 = curves[start + n - 1][3] | ||||
|  | ||||
|     # Build the curve by scaling the control-points. | ||||
|     p1 = p0 + (p1 - p0) / (ts[0] if ts else 1) | ||||
|     p2 = p3 + (p2 - p3) / ((1 - ts[-1]) if ts else 1) | ||||
|  | ||||
|     curve = (p0, p1, p2, p3) | ||||
|  | ||||
|     return curve, ts | ||||
|  | ||||
|  | ||||
| @cython.locals( | ||||
|     count=cython.int, | ||||
|     num_offcurves=cython.int, | ||||
|     i=cython.int, | ||||
|     off1=cython.complex, | ||||
|     off2=cython.complex, | ||||
|     on=cython.complex, | ||||
| ) | ||||
| def add_implicit_on_curves(p): | ||||
|     q = list(p) | ||||
|     count = 0 | ||||
|     num_offcurves = len(p) - 2 | ||||
|     for i in range(1, num_offcurves): | ||||
|         off1 = p[i] | ||||
|         off2 = p[i + 1] | ||||
|         on = off1 + (off2 - off1) * 0.5 | ||||
|         q.insert(i + 1 + count, on) | ||||
|         count += 1 | ||||
|     return q | ||||
|  | ||||
|  | ||||
| Point = Union[Tuple[float, float], complex] | ||||
|  | ||||
|  | ||||
| @cython.locals( | ||||
|     cost=cython.int, | ||||
|     is_complex=cython.int, | ||||
| ) | ||||
| def quadratic_to_curves( | ||||
|     quads: List[List[Point]], | ||||
|     max_err: float = 0.5, | ||||
|     all_cubic: bool = False, | ||||
| ) -> List[Tuple[Point, ...]]: | ||||
|     """Converts a connecting list of quadratic splines to a list of quadratic | ||||
|     and cubic curves. | ||||
|  | ||||
|     A quadratic spline is specified as a list of points.  Either each point is | ||||
|     a 2-tuple of X,Y coordinates, or each point is a complex number with | ||||
|     real/imaginary components representing X,Y coordinates. | ||||
|  | ||||
|     The first and last points are on-curve points and the rest are off-curve | ||||
|     points, with an implied on-curve point in the middle between every two | ||||
|     consequtive off-curve points. | ||||
|  | ||||
|     Returns: | ||||
|         The output is a list of tuples of points. Points are represented | ||||
|         in the same format as the input, either as 2-tuples or complex numbers. | ||||
|  | ||||
|         Each tuple is either of length three, for a quadratic curve, or four, | ||||
|         for a cubic curve.  Each curve's last point is the same as the next | ||||
|         curve's first point. | ||||
|  | ||||
|     Args: | ||||
|         quads: quadratic splines | ||||
|  | ||||
|         max_err: absolute error tolerance; defaults to 0.5 | ||||
|  | ||||
|         all_cubic: if True, only cubic curves are generated; defaults to False | ||||
|     """ | ||||
|     is_complex = type(quads[0][0]) is complex | ||||
|     if not is_complex: | ||||
|         quads = [[complex(x, y) for (x, y) in p] for p in quads] | ||||
|  | ||||
|     q = [quads[0][0]] | ||||
|     costs = [1] | ||||
|     cost = 1 | ||||
|     for p in quads: | ||||
|         assert q[-1] == p[0] | ||||
|         for i in range(len(p) - 2): | ||||
|             cost += 1 | ||||
|             costs.append(cost) | ||||
|             costs.append(cost) | ||||
|         qq = add_implicit_on_curves(p)[1:] | ||||
|         costs.pop() | ||||
|         q.extend(qq) | ||||
|         cost += 1 | ||||
|         costs.append(cost) | ||||
|  | ||||
|     curves = spline_to_curves(q, costs, max_err, all_cubic) | ||||
|  | ||||
|     if not is_complex: | ||||
|         curves = [tuple((c.real, c.imag) for c in curve) for curve in curves] | ||||
|     return curves | ||||
|  | ||||
|  | ||||
| Solution = namedtuple("Solution", ["num_points", "error", "start_index", "is_cubic"]) | ||||
|  | ||||
|  | ||||
| @cython.locals( | ||||
|     i=cython.int, | ||||
|     j=cython.int, | ||||
|     k=cython.int, | ||||
|     start=cython.int, | ||||
|     i_sol_count=cython.int, | ||||
|     j_sol_count=cython.int, | ||||
|     this_sol_count=cython.int, | ||||
|     tolerance=cython.double, | ||||
|     err=cython.double, | ||||
|     error=cython.double, | ||||
|     i_sol_error=cython.double, | ||||
|     j_sol_error=cython.double, | ||||
|     all_cubic=cython.int, | ||||
|     is_cubic=cython.int, | ||||
|     count=cython.int, | ||||
|     p0=cython.complex, | ||||
|     p1=cython.complex, | ||||
|     p2=cython.complex, | ||||
|     p3=cython.complex, | ||||
|     v=cython.complex, | ||||
|     u=cython.complex, | ||||
| ) | ||||
| def spline_to_curves(q, costs, tolerance=0.5, all_cubic=False): | ||||
|     """ | ||||
|     q: quadratic spline with alternating on-curve / off-curve points. | ||||
|  | ||||
|     costs: cumulative list of encoding cost of q in terms of number of | ||||
|       points that need to be encoded.  Implied on-curve points do not | ||||
|       contribute to the cost. If all points need to be encoded, then | ||||
|       costs will be range(1, len(q)+1). | ||||
|     """ | ||||
|  | ||||
|     assert len(q) >= 3, "quadratic spline requires at least 3 points" | ||||
|  | ||||
|     # Elevate quadratic segments to cubic | ||||
|     elevated_quadratics = [ | ||||
|         elevate_quadratic(*q[i : i + 3]) for i in range(0, len(q) - 2, 2) | ||||
|     ] | ||||
|  | ||||
|     # Find sharp corners; they have to be oncurves for sure. | ||||
|     forced = set() | ||||
|     for i in range(1, len(elevated_quadratics)): | ||||
|         p0 = elevated_quadratics[i - 1][2] | ||||
|         p1 = elevated_quadratics[i][0] | ||||
|         p2 = elevated_quadratics[i][1] | ||||
|         if abs(p1 - p0) + abs(p2 - p1) > tolerance + abs(p2 - p0): | ||||
|             forced.add(i) | ||||
|  | ||||
|     # Dynamic-Programming to find the solution with fewest number of | ||||
|     # cubic curves, and within those the one with smallest error. | ||||
|     sols = [Solution(0, 0, 0, False)] | ||||
|     impossible = Solution(len(elevated_quadratics) * 3 + 1, 0, 1, False) | ||||
|     start = 0 | ||||
|     for i in range(1, len(elevated_quadratics) + 1): | ||||
|         best_sol = impossible | ||||
|         for j in range(start, i): | ||||
|             j_sol_count, j_sol_error = sols[j].num_points, sols[j].error | ||||
|  | ||||
|             if not all_cubic: | ||||
|                 # Solution with quadratics between j:i | ||||
|                 this_count = costs[2 * i - 1] - costs[2 * j] + 1 | ||||
|                 i_sol_count = j_sol_count + this_count | ||||
|                 i_sol_error = j_sol_error | ||||
|                 i_sol = Solution(i_sol_count, i_sol_error, i - j, False) | ||||
|                 if i_sol < best_sol: | ||||
|                     best_sol = i_sol | ||||
|  | ||||
|                 if this_count <= 3: | ||||
|                     # Can't get any better than this in the path below | ||||
|                     continue | ||||
|  | ||||
|             # Fit elevated_quadratics[j:i] into one cubic | ||||
|             try: | ||||
|                 curve, ts = merge_curves(elevated_quadratics, j, i - j) | ||||
|             except ZeroDivisionError: | ||||
|                 continue | ||||
|  | ||||
|             # Now reconstruct the segments from the fitted curve | ||||
|             reconstructed_iter = splitCubicAtTC(*curve, *ts) | ||||
|             reconstructed = [] | ||||
|  | ||||
|             # Knot errors | ||||
|             error = 0 | ||||
|             for k, reconst in enumerate(reconstructed_iter): | ||||
|                 orig = elevated_quadratics[j + k] | ||||
|                 err = abs(reconst[3] - orig[3]) | ||||
|                 error = max(error, err) | ||||
|                 if error > tolerance: | ||||
|                     break | ||||
|                 reconstructed.append(reconst) | ||||
|             if error > tolerance: | ||||
|                 # Not feasible | ||||
|                 continue | ||||
|  | ||||
|             # Interior errors | ||||
|             for k, reconst in enumerate(reconstructed): | ||||
|                 orig = elevated_quadratics[j + k] | ||||
|                 p0, p1, p2, p3 = tuple(v - u for v, u in zip(reconst, orig)) | ||||
|  | ||||
|                 if not cubic_farthest_fit_inside(p0, p1, p2, p3, tolerance): | ||||
|                     error = tolerance + 1 | ||||
|                     break | ||||
|             if error > tolerance: | ||||
|                 # Not feasible | ||||
|                 continue | ||||
|  | ||||
|             # Save best solution | ||||
|             i_sol_count = j_sol_count + 3 | ||||
|             i_sol_error = max(j_sol_error, error) | ||||
|             i_sol = Solution(i_sol_count, i_sol_error, i - j, True) | ||||
|             if i_sol < best_sol: | ||||
|                 best_sol = i_sol | ||||
|  | ||||
|             if i_sol_count == 3: | ||||
|                 # Can't get any better than this | ||||
|                 break | ||||
|  | ||||
|         sols.append(best_sol) | ||||
|         if i in forced: | ||||
|             start = i | ||||
|  | ||||
|     # Reconstruct solution | ||||
|     splits = [] | ||||
|     cubic = [] | ||||
|     i = len(sols) - 1 | ||||
|     while i: | ||||
|         count, is_cubic = sols[i].start_index, sols[i].is_cubic | ||||
|         splits.append(i) | ||||
|         cubic.append(is_cubic) | ||||
|         i -= count | ||||
|     curves = [] | ||||
|     j = 0 | ||||
|     for i, is_cubic in reversed(list(zip(splits, cubic))): | ||||
|         if is_cubic: | ||||
|             curves.append(merge_curves(elevated_quadratics, j, i - j)[0]) | ||||
|         else: | ||||
|             for k in range(j, i): | ||||
|                 curves.append(q[k * 2 : k * 2 + 3]) | ||||
|         j = i | ||||
|  | ||||
|     return curves | ||||
|  | ||||
|  | ||||
| def main(): | ||||
|     from fontTools.cu2qu.benchmark import generate_curve | ||||
|     from fontTools.cu2qu import curve_to_quadratic | ||||
|  | ||||
|     tolerance = 0.05 | ||||
|     reconstruct_tolerance = tolerance * 1 | ||||
|     curve = generate_curve() | ||||
|     quadratics = curve_to_quadratic(curve, tolerance) | ||||
|     print( | ||||
|         "cu2qu tolerance %g. qu2cu tolerance %g." % (tolerance, reconstruct_tolerance) | ||||
|     ) | ||||
|     print("One random cubic turned into %d quadratics." % len(quadratics)) | ||||
|     curves = quadratic_to_curves([quadratics], reconstruct_tolerance) | ||||
|     print("Those quadratics turned back into %d cubics. " % len(curves)) | ||||
|     print("Original curve:", curve) | ||||
|     print("Reconstructed curve(s):", curves) | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
		Reference in New Issue
	
	Block a user