| /* | |
| * Copyright (C) 2026 Behdad Esfahbod | |
| * | |
| * This is part of HarfBuzz, a text shaping library. | |
| * | |
| * Cubic-to-quadratic Bézier conversion. | |
| * Ported from fonttools cu2qu: | |
| * https://github.com/fonttools/fonttools/blob/main/Lib/fontTools/cu2qu/cu2qu.py | |
| * | |
| * Permission is hereby granted, without written agreement and without | |
| * license or royalty fees, to use, copy, modify, and distribute this | |
| * software and its documentation for any purpose, provided that the | |
| * above copyright notice and the following two paragraphs appear in | |
| * all copies of this software. | |
| * | |
| * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR | |
| * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES | |
| * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN | |
| * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH | |
| * DAMAGE. | |
| * | |
| * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, | |
| * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
| * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS | |
| * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO | |
| * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | |
| * | |
| * Author(s): Behdad Esfahbod | |
| */ | |
| #ifndef HB_GPU_CU2QU_HH | |
| #define HB_GPU_CU2QU_HH | |
| #include "hb-gpu-draw.hh" | |
| #include <cmath> | |
| /* Maximum approximation error in font units. | |
| * 0.5 is well below one unit in any reasonable coordinate system. */ | |
| #ifndef HB_GPU_CU2QU_TOLERANCE | |
| #define HB_GPU_CU2QU_TOLERANCE 0.5 | |
| #endif | |
| /* Maximum subdivision depth. Each level halves the parameter interval; | |
| * at depth 10 the sub-curve spans 1/1024 of the original, so the | |
| * quadratic error (O(dt^4)) is reduced by a factor of ~10^12. */ | |
| #define HB_GPU_CU2QU_MAX_DEPTH 10 | |
| struct hb_gpu_cu2qu_point_t | |
| { | |
| double x, y; | |
| }; | |
| static inline hb_gpu_cu2qu_point_t | |
| hb_gpu_cu2qu_lerp (hb_gpu_cu2qu_point_t a, hb_gpu_cu2qu_point_t b, double t) | |
| { | |
| return {a.x + (b.x - a.x) * t, | |
| a.y + (b.y - a.y) * t}; | |
| } | |
| /* | |
| * Check whether a cubic error curve stays within `tolerance` of the origin. | |
| */ | |
| static bool | |
| hb_gpu_cubic_farthest_fit_inside (hb_gpu_cu2qu_point_t p0, | |
| hb_gpu_cu2qu_point_t p1, | |
| hb_gpu_cu2qu_point_t p2, | |
| hb_gpu_cu2qu_point_t p3, | |
| double tolerance, unsigned depth) | |
| { | |
| if (hypot (p1.x, p1.y) <= tolerance && | |
| hypot (p2.x, p2.y) <= tolerance) | |
| return true; | |
| if (depth >= 8) | |
| return false; | |
| hb_gpu_cu2qu_point_t mid = {(p0.x + 3 * (p1.x + p2.x) + p3.x) * 0.125, | |
| (p0.y + 3 * (p1.y + p2.y) + p3.y) * 0.125}; | |
| if (hypot (mid.x, mid.y) > tolerance) | |
| return false; | |
| hb_gpu_cu2qu_point_t d3 = {(p3.x + p2.x - p1.x - p0.x) * 0.125, | |
| (p3.y + p2.y - p1.y - p0.y) * 0.125}; | |
| return hb_gpu_cubic_farthest_fit_inside ( | |
| p0, | |
| hb_gpu_cu2qu_lerp (p0, p1, 0.5), | |
| {mid.x - d3.x, mid.y - d3.y}, | |
| mid, | |
| tolerance, depth + 1 | |
| ) && hb_gpu_cubic_farthest_fit_inside ( | |
| mid, | |
| {mid.x + d3.x, mid.y + d3.y}, | |
| hb_gpu_cu2qu_lerp (p2, p3, 0.5), | |
| p3, | |
| tolerance, depth + 1 | |
| ); | |
| } | |
| /* | |
| * Try to approximate a cubic Bézier with a single quadratic. | |
| */ | |
| static bool | |
| hb_gpu_cubic_approx_quadratic (hb_gpu_cu2qu_point_t c0, | |
| hb_gpu_cu2qu_point_t c1, | |
| hb_gpu_cu2qu_point_t c2, | |
| hb_gpu_cu2qu_point_t c3, | |
| double tolerance, | |
| hb_gpu_cu2qu_point_t *q1) | |
| { | |
| double ax = c1.x - c0.x, ay = c1.y - c0.y; | |
| double dx = c3.x - c2.x, dy = c3.y - c2.y; | |
| double px = -ay, py = ax; | |
| double denom = px * dx + py * dy; | |
| if (fabs (denom) < 1e-12) | |
| return false; | |
| double h = (px * (c0.x - c2.x) + py * (c0.y - c2.y)) / denom; | |
| q1->x = c2.x + dx * h; | |
| q1->y = c2.y + dy * h; | |
| hb_gpu_cu2qu_point_t err1 = {c0.x + (q1->x - c0.x) * (2.0 / 3.0) - c1.x, | |
| c0.y + (q1->y - c0.y) * (2.0 / 3.0) - c1.y}; | |
| hb_gpu_cu2qu_point_t err2 = {c3.x + (q1->x - c3.x) * (2.0 / 3.0) - c2.x, | |
| c3.y + (q1->y - c3.y) * (2.0 / 3.0) - c2.y}; | |
| return hb_gpu_cubic_farthest_fit_inside ({0, 0}, err1, err2, {0, 0}, tolerance, 0); | |
| } | |
| /* | |
| * Split a cubic at t = 0.5 (de Casteljau). | |
| */ | |
| static void | |
| hb_gpu_split_cubic_half (hb_gpu_cu2qu_point_t c0, | |
| hb_gpu_cu2qu_point_t c1, | |
| hb_gpu_cu2qu_point_t c2, | |
| hb_gpu_cu2qu_point_t c3, | |
| hb_gpu_cu2qu_point_t out[7]) | |
| { | |
| hb_gpu_cu2qu_point_t m01 = hb_gpu_cu2qu_lerp (c0, c1, 0.5); | |
| hb_gpu_cu2qu_point_t m12 = hb_gpu_cu2qu_lerp (c1, c2, 0.5); | |
| hb_gpu_cu2qu_point_t m23 = hb_gpu_cu2qu_lerp (c2, c3, 0.5); | |
| hb_gpu_cu2qu_point_t m012 = hb_gpu_cu2qu_lerp (m01, m12, 0.5); | |
| hb_gpu_cu2qu_point_t m123 = hb_gpu_cu2qu_lerp (m12, m23, 0.5); | |
| hb_gpu_cu2qu_point_t mid = hb_gpu_cu2qu_lerp (m012, m123, 0.5); | |
| out[0] = c0; out[1] = m01; out[2] = m012; | |
| out[3] = mid; | |
| out[4] = m123; out[5] = m23; out[6] = c3; | |
| } | |
| /* | |
| * Recursively convert a cubic into quadratic segments. | |
| */ | |
| static void | |
| hb_gpu_cubic_to_quadratics (hb_gpu_draw_t *g, | |
| hb_gpu_cu2qu_point_t c0, | |
| hb_gpu_cu2qu_point_t c1, | |
| hb_gpu_cu2qu_point_t c2, | |
| hb_gpu_cu2qu_point_t c3, | |
| double tolerance, unsigned depth) | |
| { | |
| hb_gpu_cu2qu_point_t q1; | |
| if (hb_gpu_cubic_approx_quadratic (c0, c1, c2, c3, tolerance, &q1)) | |
| { | |
| g->acc_conic_to (q1.x, q1.y, c3.x, c3.y); | |
| return; | |
| } | |
| if (depth >= HB_GPU_CU2QU_MAX_DEPTH) | |
| { | |
| g->acc_line_to (c3.x, c3.y); | |
| return; | |
| } | |
| hb_gpu_cu2qu_point_t pts[7]; | |
| hb_gpu_split_cubic_half (c0, c1, c2, c3, pts); | |
| hb_gpu_cubic_to_quadratics (g, pts[0], pts[1], pts[2], pts[3], tolerance, depth + 1); | |
| hb_gpu_cubic_to_quadratics (g, pts[3], pts[4], pts[5], pts[6], tolerance, depth + 1); | |
| } | |
| #endif /* HB_GPU_CU2QU_HH */ |