File Coverage

src/pdfmake_render_bezier.c
Criterion Covered Total %
statement 0 148 0.0
branch 0 46 0.0
condition n/a
subroutine n/a
pod n/a
total 0 194 0.0


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_render_bezier.c - Bezier curve flattening
3             *
4             * Converts cubic Bezier curves to line segments using adaptive subdivision.
5             * The flatness tolerance controls the maximum deviation from the true curve.
6             */
7              
8             #include "pdfmake_render.h"
9             #include
10             #include
11              
12             /*
13             * Calculate squared distance from point to line segment
14             */
15 0           static double point_line_distance_sq(
16             pdfmake_point_t p,
17             pdfmake_point_t a,
18             pdfmake_point_t b)
19             {
20 0           double dx = b.x - a.x;
21 0           double dy = b.y - a.y;
22 0           double len_sq = dx * dx + dy * dy;
23             double t, cx, cy;
24              
25 0 0         if (len_sq < 1e-10) {
26             /* Line segment is a point */
27 0           dx = p.x - a.x;
28 0           dy = p.y - a.y;
29 0           return dx * dx + dy * dy;
30             }
31              
32             /* Project point onto line */
33 0           t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / len_sq;
34              
35             /* Clamp t to [0, 1] */
36 0 0         if (t < 0) t = 0;
37 0 0         if (t > 1) t = 1;
38              
39             /* Calculate closest point on segment */
40 0           cx = a.x + t * dx;
41 0           cy = a.y + t * dy;
42              
43 0           dx = p.x - cx;
44 0           dy = p.y - cy;
45 0           return dx * dx + dy * dy;
46             }
47              
48             /*
49             * Check if curve is flat enough
50             * Returns 1 if flat, 0 if needs subdivision
51             */
52 0           static int is_flat_enough(
53             pdfmake_point_t p0, pdfmake_point_t p1,
54             pdfmake_point_t p2, pdfmake_point_t p3,
55             double tolerance)
56             {
57 0           double tolerance_sq = tolerance * tolerance;
58            
59             /* Check distance of control points from chord */
60 0           double d1_sq = point_line_distance_sq(p1, p0, p3);
61 0           double d2_sq = point_line_distance_sq(p2, p0, p3);
62            
63 0 0         return (d1_sq <= tolerance_sq && d2_sq <= tolerance_sq);
    0          
64             }
65              
66             /*
67             * Subdivide cubic Bezier at t=0.5
68             */
69 0           static void subdivide_bezier(
70             pdfmake_point_t p0, pdfmake_point_t p1,
71             pdfmake_point_t p2, pdfmake_point_t p3,
72             pdfmake_point_t *left, /* 4 points for left half */
73             pdfmake_point_t *right) /* 4 points for right half */
74             {
75             /* De Casteljau's algorithm at t=0.5 */
76             pdfmake_point_t q0, q1, q2;
77             pdfmake_point_t r0, r1;
78             pdfmake_point_t s;
79            
80             /* First level */
81 0           q0.x = (p0.x + p1.x) * 0.5;
82 0           q0.y = (p0.y + p1.y) * 0.5;
83 0           q1.x = (p1.x + p2.x) * 0.5;
84 0           q1.y = (p1.y + p2.y) * 0.5;
85 0           q2.x = (p2.x + p3.x) * 0.5;
86 0           q2.y = (p2.y + p3.y) * 0.5;
87            
88             /* Second level */
89 0           r0.x = (q0.x + q1.x) * 0.5;
90 0           r0.y = (q0.y + q1.y) * 0.5;
91 0           r1.x = (q1.x + q2.x) * 0.5;
92 0           r1.y = (q1.y + q2.y) * 0.5;
93            
94             /* Third level - midpoint */
95 0           s.x = (r0.x + r1.x) * 0.5;
96 0           s.y = (r0.y + r1.y) * 0.5;
97            
98             /* Left half: p0, q0, r0, s */
99 0           left[0] = p0;
100 0           left[1] = q0;
101 0           left[2] = r0;
102 0           left[3] = s;
103            
104             /* Right half: s, r1, q2, p3 */
105 0           right[0] = s;
106 0           right[1] = r1;
107 0           right[2] = q2;
108 0           right[3] = p3;
109 0           }
110              
111             /*
112             * Recursive flattening helper
113             */
114 0           static pdfmake_render_err_t flatten_recursive(
115             pdfmake_point_t p0, pdfmake_point_t p1,
116             pdfmake_point_t p2, pdfmake_point_t p3,
117             double tolerance,
118             pdfmake_path_t *out,
119             int depth)
120             {
121             pdfmake_point_t left[4], right[4];
122             pdfmake_render_err_t err;
123              
124             /* Prevent infinite recursion */
125 0 0         if (depth > 20) {
126 0           return pdfmake_path_line_to(out, p3.x, p3.y);
127             }
128              
129 0 0         if (is_flat_enough(p0, p1, p2, p3, tolerance)) {
130             /* Curve is flat enough, output line to endpoint */
131 0           return pdfmake_path_line_to(out, p3.x, p3.y);
132             }
133              
134             /* Subdivide and recurse */
135 0           subdivide_bezier(p0, p1, p2, p3, left, right);
136              
137 0           err = flatten_recursive(left[0], left[1], left[2], left[3],
138             tolerance, out, depth + 1);
139 0 0         if (err != PDFMAKE_RENDER_OK) {
140 0           return err;
141             }
142              
143 0           err = flatten_recursive(right[0], right[1], right[2], right[3],
144             tolerance, out, depth + 1);
145 0           return err;
146             }
147              
148             /*
149             * Flatten cubic Bezier to line segments
150             */
151 0           pdfmake_render_err_t pdfmake_bezier_flatten(
152             pdfmake_point_t p0, pdfmake_point_t p1,
153             pdfmake_point_t p2, pdfmake_point_t p3,
154             double tolerance,
155             pdfmake_path_t *out)
156             {
157 0 0         if (!out) {
158 0           return PDFMAKE_RENDER_ERR_NULL;
159             }
160            
161 0 0         if (tolerance <= 0) {
162 0           tolerance = 0.5; /* Default tolerance */
163             }
164            
165             /* First point should already be set by caller (move_to) */
166 0           return flatten_recursive(p0, p1, p2, p3, tolerance, out, 0);
167             }
168              
169             /*
170             * Flatten entire path (convert curves to lines)
171             */
172 0           pdfmake_path_t *pdfmake_path_flatten(pdfmake_path_t *path, double tolerance) {
173             pdfmake_path_t *flat;
174 0           pdfmake_point_t current = {0, 0};
175 0           pdfmake_point_t subpath_start = {0, 0};
176 0           int has_current = 0;
177             size_t i;
178              
179 0 0         if (!path) {
180 0           return NULL;
181             }
182              
183 0           flat = pdfmake_path_create();
184 0 0         if (!flat) {
185 0           return NULL;
186             }
187              
188 0 0         if (tolerance <= 0) {
189 0           tolerance = 0.5;
190             }
191              
192 0 0         for (i = 0; i < path->seg_count; i++) {
193 0           pdfmake_path_seg_t *seg = &path->segs[i];
194             pdfmake_render_err_t err;
195            
196 0           switch (seg->op) {
197 0           case PDFMAKE_PATH_MOVE:
198 0           err = pdfmake_path_move_to(flat, seg->pts[0].x, seg->pts[0].y);
199 0 0         if (err != PDFMAKE_RENDER_OK) {
200 0           pdfmake_path_destroy(flat);
201 0           return NULL;
202             }
203 0           current = seg->pts[0];
204 0           subpath_start = current;
205 0           has_current = 1;
206 0           break;
207            
208 0           case PDFMAKE_PATH_LINE:
209 0 0         if (!has_current) {
210 0           pdfmake_path_move_to(flat, seg->pts[0].x, seg->pts[0].y);
211 0           current = seg->pts[0];
212 0           has_current = 1;
213             } else {
214 0           err = pdfmake_path_line_to(flat, seg->pts[0].x, seg->pts[0].y);
215 0 0         if (err != PDFMAKE_RENDER_OK) {
216 0           pdfmake_path_destroy(flat);
217 0           return NULL;
218             }
219 0           current = seg->pts[0];
220             }
221 0           break;
222            
223 0           case PDFMAKE_PATH_CURVE:
224 0 0         if (!has_current) {
225 0           pdfmake_path_move_to(flat, seg->pts[0].x, seg->pts[0].y);
226 0           current = seg->pts[0];
227 0           has_current = 1;
228             }
229            
230             /* Flatten the curve */
231 0           err = pdfmake_bezier_flatten(
232             current, seg->pts[0], seg->pts[1], seg->pts[2],
233             tolerance, flat);
234 0 0         if (err != PDFMAKE_RENDER_OK) {
235 0           pdfmake_path_destroy(flat);
236 0           return NULL;
237             }
238 0           current = seg->pts[2];
239 0           break;
240            
241 0           case PDFMAKE_PATH_CLOSE:
242 0           err = pdfmake_path_close(flat);
243 0 0         if (err != PDFMAKE_RENDER_OK) {
244 0           pdfmake_path_destroy(flat);
245 0           return NULL;
246             }
247 0           current = subpath_start;
248 0           break;
249             }
250             }
251            
252 0           return flat;
253             }
254              
255             /*
256             * Evaluate cubic Bezier at parameter t
257             */
258 0           pdfmake_point_t pdfmake_bezier_eval(
259             pdfmake_point_t p0, pdfmake_point_t p1,
260             pdfmake_point_t p2, pdfmake_point_t p3,
261             double t)
262             {
263 0           double t2 = t * t;
264 0           double t3 = t2 * t;
265 0           double mt = 1 - t;
266 0           double mt2 = mt * mt;
267 0           double mt3 = mt2 * mt;
268            
269             pdfmake_point_t result;
270 0           result.x = mt3 * p0.x + 3 * mt2 * t * p1.x + 3 * mt * t2 * p2.x + t3 * p3.x;
271 0           result.y = mt3 * p0.y + 3 * mt2 * t * p1.y + 3 * mt * t2 * p2.y + t3 * p3.y;
272            
273 0           return result;
274             }
275              
276             /*
277             * Calculate tangent at parameter t
278             */
279 0           pdfmake_point_t pdfmake_bezier_tangent(
280             pdfmake_point_t p0, pdfmake_point_t p1,
281             pdfmake_point_t p2, pdfmake_point_t p3,
282             double t)
283             {
284 0           double t2 = t * t;
285 0           double mt = 1 - t;
286 0           double mt2 = mt * mt;
287            
288             /* Derivative of cubic Bezier */
289             pdfmake_point_t result;
290 0           result.x = 3 * mt2 * (p1.x - p0.x) + 6 * mt * t * (p2.x - p1.x) + 3 * t2 * (p3.x - p2.x);
291 0           result.y = 3 * mt2 * (p1.y - p0.y) + 6 * mt * t * (p2.y - p1.y) + 3 * t2 * (p3.y - p2.y);
292            
293 0           return result;
294             }
295              
296             /*
297             * Calculate approximate arc length of cubic Bezier
298             * Uses chord length as approximation (good for flat curves)
299             */
300 0           double pdfmake_bezier_length(
301             pdfmake_point_t p0, pdfmake_point_t p1,
302             pdfmake_point_t p2, pdfmake_point_t p3,
303             double tolerance)
304             {
305             pdfmake_path_t *flat;
306 0           double length = 0;
307 0           pdfmake_point_t prev = p0;
308             size_t i;
309              
310             /* Simple approach: flatten and sum segment lengths */
311 0           flat = pdfmake_path_create();
312 0 0         if (!flat) {
313             /* Fallback: chord length */
314 0           double dx = p3.x - p0.x;
315 0           double dy = p3.y - p0.y;
316 0           return sqrt(dx * dx + dy * dy);
317             }
318              
319 0           pdfmake_path_move_to(flat, p0.x, p0.y);
320 0           pdfmake_bezier_flatten(p0, p1, p2, p3, tolerance, flat);
321              
322 0 0         for (i = 1; i < flat->seg_count; i++) {
323 0           pdfmake_path_seg_t *seg = &flat->segs[i];
324 0 0         if (seg->op == PDFMAKE_PATH_LINE) {
325 0           double dx = seg->pts[0].x - prev.x;
326 0           double dy = seg->pts[0].y - prev.y;
327 0           length += sqrt(dx * dx + dy * dy);
328 0           prev = seg->pts[0];
329             }
330             }
331              
332 0           pdfmake_path_destroy(flat);
333 0           return length;
334             }