Coverage Report

Created: 2026-06-09 06:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 */