/src/harfbuzz/src/hb-gpu-cu2qu.hh
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2026 Behdad Esfahbod |
3 | | * |
4 | | * This is part of HarfBuzz, a text shaping library. |
5 | | * |
6 | | * Cubic-to-quadratic Bézier conversion. |
7 | | * Ported from fonttools cu2qu: |
8 | | * https://github.com/fonttools/fonttools/blob/main/Lib/fontTools/cu2qu/cu2qu.py |
9 | | * |
10 | | * Permission is hereby granted, without written agreement and without |
11 | | * license or royalty fees, to use, copy, modify, and distribute this |
12 | | * software and its documentation for any purpose, provided that the |
13 | | * above copyright notice and the following two paragraphs appear in |
14 | | * all copies of this software. |
15 | | * |
16 | | * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR |
17 | | * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES |
18 | | * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN |
19 | | * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH |
20 | | * DAMAGE. |
21 | | * |
22 | | * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, |
23 | | * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
24 | | * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS |
25 | | * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO |
26 | | * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
27 | | * |
28 | | * Author(s): Behdad Esfahbod |
29 | | */ |
30 | | |
31 | | #ifndef HB_GPU_CU2QU_HH |
32 | | #define HB_GPU_CU2QU_HH |
33 | | |
34 | | #include "hb-gpu-draw.hh" |
35 | | |
36 | | #include <cmath> |
37 | | |
38 | | |
39 | | /* Maximum approximation error in font units. |
40 | | * 0.5 is well below one unit in any reasonable coordinate system. */ |
41 | | #ifndef HB_GPU_CU2QU_TOLERANCE |
42 | 44.3M | #define HB_GPU_CU2QU_TOLERANCE 0.5 |
43 | | #endif |
44 | | |
45 | | /* Maximum subdivision depth. Each level halves the parameter interval; |
46 | | * at depth 10 the sub-curve spans 1/1024 of the original, so the |
47 | | * quadratic error (O(dt^4)) is reduced by a factor of ~10^12. */ |
48 | 39.6M | #define HB_GPU_CU2QU_MAX_DEPTH 10 |
49 | | |
50 | | |
51 | | struct hb_gpu_cu2qu_point_t |
52 | | { |
53 | | double x, y; |
54 | | }; |
55 | | |
56 | | static inline hb_gpu_cu2qu_point_t |
57 | | hb_gpu_cu2qu_lerp (hb_gpu_cu2qu_point_t a, hb_gpu_cu2qu_point_t b, double t) |
58 | 193M | { |
59 | 193M | return {a.x + (b.x - a.x) * t, |
60 | 193M | a.y + (b.y - a.y) * t}; |
61 | 193M | } |
62 | | |
63 | | |
64 | | /* |
65 | | * Check whether a cubic error curve stays within `tolerance` of the origin. |
66 | | */ |
67 | | static bool |
68 | | hb_gpu_cubic_farthest_fit_inside (hb_gpu_cu2qu_point_t p0, |
69 | | hb_gpu_cu2qu_point_t p1, |
70 | | hb_gpu_cu2qu_point_t p2, |
71 | | hb_gpu_cu2qu_point_t p3, |
72 | | double tolerance, unsigned depth) |
73 | 66.0M | { |
74 | 66.0M | if (hypot (p1.x, p1.y) <= tolerance && |
75 | 34.1M | hypot (p2.x, p2.y) <= tolerance) |
76 | 31.7M | return true; |
77 | | |
78 | 34.3M | if (depth >= 8) |
79 | 991k | return false; |
80 | | |
81 | 33.3M | hb_gpu_cu2qu_point_t mid = {(p0.x + 3 * (p1.x + p2.x) + p3.x) * 0.125, |
82 | 33.3M | (p0.y + 3 * (p1.y + p2.y) + p3.y) * 0.125}; |
83 | 33.3M | if (hypot (mid.x, mid.y) > tolerance) |
84 | 19.6M | return false; |
85 | | |
86 | 13.7M | hb_gpu_cu2qu_point_t d3 = {(p3.x + p2.x - p1.x - p0.x) * 0.125, |
87 | 13.7M | (p3.y + p2.y - p1.y - p0.y) * 0.125}; |
88 | | |
89 | 13.7M | return hb_gpu_cubic_farthest_fit_inside ( |
90 | 13.7M | p0, |
91 | 13.7M | hb_gpu_cu2qu_lerp (p0, p1, 0.5), |
92 | 13.7M | {mid.x - d3.x, mid.y - d3.y}, |
93 | 13.7M | mid, |
94 | 13.7M | tolerance, depth + 1 |
95 | 13.7M | ) && hb_gpu_cubic_farthest_fit_inside ( |
96 | 5.59M | mid, |
97 | 5.59M | {mid.x + d3.x, mid.y + d3.y}, |
98 | 5.59M | hb_gpu_cu2qu_lerp (p2, p3, 0.5), |
99 | 5.59M | p3, |
100 | 5.59M | tolerance, depth + 1 |
101 | 5.59M | ); |
102 | 33.3M | } |
103 | | |
104 | | |
105 | | /* |
106 | | * Try to approximate a cubic Bézier with a single quadratic. |
107 | | */ |
108 | | static bool |
109 | | hb_gpu_cubic_approx_quadratic (hb_gpu_cu2qu_point_t c0, |
110 | | hb_gpu_cu2qu_point_t c1, |
111 | | hb_gpu_cu2qu_point_t c2, |
112 | | hb_gpu_cu2qu_point_t c3, |
113 | | double tolerance, |
114 | | hb_gpu_cu2qu_point_t *q1) |
115 | 65.7M | { |
116 | 65.7M | double ax = c1.x - c0.x, ay = c1.y - c0.y; |
117 | 65.7M | double dx = c3.x - c2.x, dy = c3.y - c2.y; |
118 | | |
119 | 65.7M | double px = -ay, py = ax; |
120 | | |
121 | 65.7M | double denom = px * dx + py * dy; |
122 | 65.7M | if (fabs (denom) < 1e-12) |
123 | 19.0M | return false; |
124 | | |
125 | 46.7M | double h = (px * (c0.x - c2.x) + py * (c0.y - c2.y)) / denom; |
126 | | |
127 | 46.7M | q1->x = c2.x + dx * h; |
128 | 46.7M | q1->y = c2.y + dy * h; |
129 | | |
130 | 46.7M | hb_gpu_cu2qu_point_t err1 = {c0.x + (q1->x - c0.x) * (2.0 / 3.0) - c1.x, |
131 | 46.7M | c0.y + (q1->y - c0.y) * (2.0 / 3.0) - c1.y}; |
132 | 46.7M | hb_gpu_cu2qu_point_t err2 = {c3.x + (q1->x - c3.x) * (2.0 / 3.0) - c2.x, |
133 | 46.7M | c3.y + (q1->y - c3.y) * (2.0 / 3.0) - c2.y}; |
134 | | |
135 | 46.7M | return hb_gpu_cubic_farthest_fit_inside ({0, 0}, err1, err2, {0, 0}, tolerance, 0); |
136 | 65.7M | } |
137 | | |
138 | | |
139 | | /* |
140 | | * Split a cubic at t = 0.5 (de Casteljau). |
141 | | */ |
142 | | static void |
143 | | hb_gpu_split_cubic_half (hb_gpu_cu2qu_point_t c0, |
144 | | hb_gpu_cu2qu_point_t c1, |
145 | | hb_gpu_cu2qu_point_t c2, |
146 | | hb_gpu_cu2qu_point_t c3, |
147 | | hb_gpu_cu2qu_point_t out[7]) |
148 | 29.0M | { |
149 | 29.0M | hb_gpu_cu2qu_point_t m01 = hb_gpu_cu2qu_lerp (c0, c1, 0.5); |
150 | 29.0M | hb_gpu_cu2qu_point_t m12 = hb_gpu_cu2qu_lerp (c1, c2, 0.5); |
151 | 29.0M | hb_gpu_cu2qu_point_t m23 = hb_gpu_cu2qu_lerp (c2, c3, 0.5); |
152 | 29.0M | hb_gpu_cu2qu_point_t m012 = hb_gpu_cu2qu_lerp (m01, m12, 0.5); |
153 | 29.0M | hb_gpu_cu2qu_point_t m123 = hb_gpu_cu2qu_lerp (m12, m23, 0.5); |
154 | 29.0M | hb_gpu_cu2qu_point_t mid = hb_gpu_cu2qu_lerp (m012, m123, 0.5); |
155 | | |
156 | 29.0M | out[0] = c0; out[1] = m01; out[2] = m012; |
157 | 29.0M | out[3] = mid; |
158 | 29.0M | out[4] = m123; out[5] = m23; out[6] = c3; |
159 | 29.0M | } |
160 | | |
161 | | |
162 | | /* |
163 | | * Recursively convert a cubic into quadratic segments. |
164 | | */ |
165 | | static void |
166 | | hb_gpu_cubic_to_quadratics (hb_gpu_draw_t *g, |
167 | | hb_gpu_cu2qu_point_t c0, |
168 | | hb_gpu_cu2qu_point_t c1, |
169 | | hb_gpu_cu2qu_point_t c2, |
170 | | hb_gpu_cu2qu_point_t c3, |
171 | | double tolerance, unsigned depth) |
172 | 65.7M | { |
173 | 65.7M | hb_gpu_cu2qu_point_t q1; |
174 | 65.7M | if (hb_gpu_cubic_approx_quadratic (c0, c1, c2, c3, tolerance, &q1)) |
175 | 26.1M | { |
176 | 26.1M | g->acc_conic_to (q1.x, q1.y, c3.x, c3.y); |
177 | 26.1M | return; |
178 | 26.1M | } |
179 | | |
180 | 39.6M | if (depth >= HB_GPU_CU2QU_MAX_DEPTH) |
181 | 10.6M | { |
182 | 10.6M | g->acc_line_to (c3.x, c3.y); |
183 | 10.6M | return; |
184 | 10.6M | } |
185 | | |
186 | 29.0M | hb_gpu_cu2qu_point_t pts[7]; |
187 | 29.0M | hb_gpu_split_cubic_half (c0, c1, c2, c3, pts); |
188 | 29.0M | hb_gpu_cubic_to_quadratics (g, pts[0], pts[1], pts[2], pts[3], tolerance, depth + 1); |
189 | 29.0M | hb_gpu_cubic_to_quadratics (g, pts[3], pts[4], pts[5], pts[6], tolerance, depth + 1); |
190 | 29.0M | } |
191 | | |
192 | | |
193 | | #endif /* HB_GPU_CU2QU_HH */ |