File Coverage

src/pdfmake_render_fill.c
Criterion Covered Total %
statement 0 214 0.0
branch 0 130 0.0
condition n/a
subroutine n/a
pod n/a
total 0 344 0.0


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_render_fill.c - Scanline fill algorithm
3             *
4             * Implements path filling using the active edge table scanline algorithm.
5             * Supports both non-zero winding number and even-odd fill rules.
6             */
7              
8             #include "pdfmake_render.h"
9             #include
10             #include
11             #include
12             #include
13              
14             #define MIN(a, b) ((a) < (b) ? (a) : (b))
15             #define MAX(a, b) ((a) > (b) ? (a) : (b))
16             #define CLAMP(x, lo, hi) ((x) < (lo) ? (lo) : ((x) > (hi) ? (hi) : (x)))
17              
18             /*
19             * Edge structure for scanline algorithm
20             */
21             typedef struct fill_edge {
22             int y_min; /* Top scanline (integer) */
23             int y_max; /* Bottom scanline (integer) */
24             double x; /* Current x intersection */
25             double dx; /* Change in x per scanline (1/slope) */
26             int dir; /* Direction: +1 going up, -1 going down */
27             struct fill_edge *next;
28             } fill_edge_t;
29              
30             /*
31             * Create edge from two points
32             */
33 0           static fill_edge_t *create_edge(double x0, double y0, double x1, double y1) {
34             fill_edge_t *edge;
35             /* Skip horizontal edges */
36 0 0         if (fabs(y1 - y0) < 0.5) {
37 0           return NULL;
38             }
39              
40 0           edge = malloc(sizeof(fill_edge_t));
41 0 0         if (!edge) {
42 0           return NULL;
43             }
44            
45             /* Ensure y0 < y1 (edge goes from top to bottom) */
46 0 0         if (y0 > y1) {
47 0           double tmp = x0; x0 = x1; x1 = tmp;
48 0           tmp = y0; y0 = y1; y1 = tmp;
49 0           edge->dir = 1; /* Original edge went up */
50             } else {
51 0           edge->dir = -1; /* Original edge went down */
52             }
53            
54 0           edge->y_min = (int)ceil(y0);
55 0           edge->y_max = (int)floor(y1);
56            
57 0 0         if (edge->y_min > edge->y_max) {
58 0           free(edge);
59 0           return NULL;
60             }
61            
62 0           edge->dx = (x1 - x0) / (y1 - y0);
63 0           edge->x = x0 + edge->dx * (edge->y_min - y0);
64 0           edge->next = NULL;
65            
66 0           return edge;
67             }
68              
69             /*
70             * Build edge table from flattened path
71             */
72 0           static fill_edge_t **build_edge_table(pdfmake_path_t *path, int y_min, int y_max, int *edge_count) {
73 0           int height = y_max - y_min + 1;
74 0           fill_edge_t **et = calloc(height, sizeof(fill_edge_t *));
75             pdfmake_point_t current;
76             pdfmake_point_t subpath_start;
77             int has_current;
78             size_t i;
79             pdfmake_path_seg_t *seg;
80             fill_edge_t *edge;
81             int idx;
82 0 0         if (!et) {
83 0           return NULL;
84             }
85            
86 0           *edge_count = 0;
87            
88 0           current.x = current.y = 0;
89 0           subpath_start.x = subpath_start.y = 0;
90 0           has_current = 0;
91              
92 0 0         for (i = 0; i < path->seg_count; i++) {
93 0           seg = &path->segs[i];
94            
95 0           switch (seg->op) {
96 0           case PDFMAKE_PATH_MOVE:
97 0           current = seg->pts[0];
98 0           subpath_start = current;
99 0           has_current = 1;
100 0           break;
101            
102 0           case PDFMAKE_PATH_LINE:
103 0 0         if (has_current) {
104 0           edge = create_edge(
105             current.x, current.y,
106             seg->pts[0].x, seg->pts[0].y);
107            
108 0 0         if (edge && edge->y_min >= y_min && edge->y_min <= y_max) {
    0          
    0          
109 0           idx = edge->y_min - y_min;
110 0           edge->next = et[idx];
111 0           et[idx] = edge;
112 0           (*edge_count)++;
113 0 0         } else if (edge) {
114 0           free(edge);
115             }
116             }
117 0           current = seg->pts[0];
118 0           break;
119            
120 0           case PDFMAKE_PATH_CLOSE:
121 0 0         if (has_current) {
122 0           edge = create_edge(
123             current.x, current.y,
124             subpath_start.x, subpath_start.y);
125            
126 0 0         if (edge && edge->y_min >= y_min && edge->y_min <= y_max) {
    0          
    0          
127 0           idx = edge->y_min - y_min;
128 0           edge->next = et[idx];
129 0           et[idx] = edge;
130 0           (*edge_count)++;
131 0 0         } else if (edge) {
132 0           free(edge);
133             }
134             }
135 0           current = subpath_start;
136 0           break;
137            
138 0           case PDFMAKE_PATH_CURVE:
139             /* Curves should be flattened before fill */
140 0           break;
141             }
142             }
143            
144 0           return et;
145             }
146              
147             /*
148             * Insert edge into active edge list (sorted by x)
149             */
150 0           static void insert_edge_sorted(fill_edge_t **ael, fill_edge_t *edge) {
151             fill_edge_t *curr;
152 0           edge->next = NULL;
153            
154 0 0         if (*ael == NULL || edge->x < (*ael)->x) {
    0          
155 0           edge->next = *ael;
156 0           *ael = edge;
157 0           return;
158             }
159            
160 0           curr = *ael;
161 0 0         while (curr->next && curr->next->x < edge->x) {
    0          
162 0           curr = curr->next;
163             }
164 0           edge->next = curr->next;
165 0           curr->next = edge;
166             }
167              
168             /*
169             * Sort active edge list by x
170             */
171 0           static void sort_ael(fill_edge_t **ael) {
172             fill_edge_t *sorted;
173             fill_edge_t *curr;
174             fill_edge_t *next;
175 0 0         if (!*ael || !(*ael)->next) {
    0          
176 0           return;
177             }
178            
179             /* Simple insertion sort (edges are usually nearly sorted) */
180 0           sorted = NULL;
181 0           curr = *ael;
182            
183 0 0         while (curr) {
184 0           next = curr->next;
185 0           insert_edge_sorted(&sorted, curr);
186 0           curr = next;
187             }
188            
189 0           *ael = sorted;
190             }
191              
192             /*
193             * Fill horizontal span
194             */
195 0           static void fill_span(pdfmake_render_ctx_t *ctx, int y, int x0, int x1, uint32_t color) {
196             uint32_t *row;
197             int x;
198 0 0         if (y < 0 || y >= ctx->height) {
    0          
199 0           return;
200             }
201            
202 0 0         x0 = CLAMP(x0, 0, ctx->width);
203 0 0         x1 = CLAMP(x1, 0, ctx->width);
204            
205 0           row = ctx->pixels + y * ctx->width;
206            
207 0 0         for (x = x0; x < x1; x++) {
208 0 0         if (ctx->has_clip && ctx->clip_mask) {
    0          
209 0 0         if (ctx->clip_mask[y * ctx->width + x] == 0) {
210 0           continue; /* Clipped out */
211             }
212             }
213 0           row[x] = pdfmake_color_blend(row[x], color);
214             }
215             }
216              
217             /*
218             * Fill path with non-zero winding rule
219             */
220 0           static void fill_nonzero(pdfmake_render_ctx_t *ctx, pdfmake_path_t *path, uint32_t color) {
221             double x_min, y_min, x_max, y_max;
222             int iy_min;
223             int iy_max;
224             int edge_count;
225             fill_edge_t **et;
226             fill_edge_t *ael;
227             int y;
228             int idx;
229             fill_edge_t *edge;
230             int winding;
231             fill_edge_t *curr;
232             int span_start;
233             int span_end;
234             fill_edge_t *prev;
235             fill_edge_t *next;
236 0 0         if (pdfmake_path_get_bounds(path, &x_min, &y_min, &x_max, &y_max) != PDFMAKE_RENDER_OK) {
237 0           return;
238             }
239              
240 0           iy_min = MAX((int)floor(y_min), 0);
241 0           iy_max = MIN((int)ceil(y_max), ctx->height - 1);
242            
243 0 0         if (iy_min > iy_max) {
244 0           return;
245             }
246            
247 0           et = build_edge_table(path, iy_min, iy_max, &edge_count);
248 0 0         if (!et) {
249 0           return;
250             }
251            
252 0           ael = NULL; /* Active edge list */
253            
254 0 0         for (y = iy_min; y <= iy_max; y++) {
255 0           idx = y - iy_min;
256            
257             /* Add edges starting at this scanline */
258 0 0         while (et[idx]) {
259 0           edge = et[idx];
260 0           et[idx] = edge->next;
261 0           insert_edge_sorted(&ael, edge);
262             }
263            
264             /* Sort AEL by x */
265 0           sort_ael(&ael);
266            
267             /* Fill spans using non-zero winding rule */
268 0           winding = 0;
269 0           curr = ael;
270 0           span_start = -1;
271            
272 0 0         while (curr) {
273 0 0         if (winding == 0 && curr->dir != 0) {
    0          
274 0           span_start = (int)floor(curr->x);
275             }
276 0           winding += curr->dir;
277 0 0         if (winding == 0 && span_start >= 0) {
    0          
278 0           span_end = (int)ceil(curr->x);
279 0           fill_span(ctx, y, span_start, span_end, color);
280 0           span_start = -1;
281             }
282 0           curr = curr->next;
283             }
284            
285             /* Remove edges ending at this scanline and update x */
286 0           prev = NULL;
287 0           curr = ael;
288 0 0         while (curr) {
289 0           next = curr->next;
290 0 0         if (y >= curr->y_max) {
291             /* Remove edge */
292 0 0         if (prev) {
293 0           prev->next = next;
294             } else {
295 0           ael = next;
296             }
297 0           free(curr);
298             } else {
299             /* Update x for next scanline */
300 0           curr->x += curr->dx;
301 0           prev = curr;
302             }
303 0           curr = next;
304             }
305             }
306            
307             /* Free edge table */
308 0           free(et);
309            
310             /* Free remaining AEL edges */
311 0 0         while (ael) {
312 0           fill_edge_t *next = ael->next;
313 0           free(ael);
314 0           ael = next;
315             }
316             }
317              
318             /*
319             * Fill path with even-odd rule
320             */
321 0           static void fill_evenodd(pdfmake_render_ctx_t *ctx, pdfmake_path_t *path, uint32_t color) {
322             double x_min, y_min, x_max, y_max;
323             int iy_min;
324             int iy_max;
325             int edge_count;
326             fill_edge_t **et;
327             fill_edge_t *ael;
328             int y;
329             int idx;
330             fill_edge_t *edge;
331             int parity;
332             fill_edge_t *curr;
333             int span_start;
334             int span_end;
335             fill_edge_t *prev;
336             fill_edge_t *next;
337 0 0         if (pdfmake_path_get_bounds(path, &x_min, &y_min, &x_max, &y_max) != PDFMAKE_RENDER_OK) {
338 0           return;
339             }
340              
341 0           iy_min = MAX((int)floor(y_min), 0);
342 0           iy_max = MIN((int)ceil(y_max), ctx->height - 1);
343            
344 0 0         if (iy_min > iy_max) {
345 0           return;
346             }
347            
348 0           et = build_edge_table(path, iy_min, iy_max, &edge_count);
349 0 0         if (!et) {
350 0           return;
351             }
352            
353 0           ael = NULL;
354            
355 0 0         for (y = iy_min; y <= iy_max; y++) {
356 0           idx = y - iy_min;
357            
358             /* Add edges starting at this scanline */
359 0 0         while (et[idx]) {
360 0           edge = et[idx];
361 0           et[idx] = edge->next;
362 0           insert_edge_sorted(&ael, edge);
363             }
364            
365 0           sort_ael(&ael);
366            
367             /* Fill spans using even-odd rule */
368 0           parity = 0;
369 0           curr = ael;
370 0           span_start = -1;
371            
372 0 0         while (curr) {
373 0 0         if (parity == 0) {
374 0           span_start = (int)floor(curr->x);
375             }
376 0           parity = 1 - parity;
377 0 0         if (parity == 0 && span_start >= 0) {
    0          
378 0           span_end = (int)ceil(curr->x);
379 0           fill_span(ctx, y, span_start, span_end, color);
380 0           span_start = -1;
381             }
382 0           curr = curr->next;
383             }
384            
385             /* Remove edges ending at this scanline and update x */
386 0           prev = NULL;
387 0           curr = ael;
388 0 0         while (curr) {
389 0           next = curr->next;
390 0 0         if (y >= curr->y_max) {
391 0 0         if (prev) {
392 0           prev->next = next;
393             } else {
394 0           ael = next;
395             }
396 0           free(curr);
397             } else {
398 0           curr->x += curr->dx;
399 0           prev = curr;
400             }
401 0           curr = next;
402             }
403             }
404            
405 0           free(et);
406            
407 0 0         while (ael) {
408 0           fill_edge_t *next = ael->next;
409 0           free(ael);
410 0           ael = next;
411             }
412             }
413              
414             /*
415             * Fill path with specified rule
416             */
417 0           pdfmake_render_err_t pdfmake_fill_path(
418             pdfmake_render_ctx_t *ctx,
419             pdfmake_path_t *path,
420             pdfmake_fill_rule_t rule)
421             {
422             pdfmake_path_t *flat;
423             uint32_t color;
424 0 0         if (!ctx || !path) {
    0          
425 0           return PDFMAKE_RENDER_ERR_NULL;
426             }
427            
428 0 0         if (pdfmake_path_is_empty(path)) {
429 0           return PDFMAKE_RENDER_ERR_EMPTY_PATH;
430             }
431            
432             /* Flatten curves */
433 0           flat = pdfmake_path_flatten(path, ctx->flatness);
434 0 0         if (!flat) {
435 0           return PDFMAKE_RENDER_ERR_MEMORY;
436             }
437              
438 0           color = pdfmake_color_pack(ctx->fill_color);
439            
440 0 0         if (rule == PDFMAKE_FILL_EVENODD) {
441 0           fill_evenodd(ctx, flat, color);
442             } else {
443 0           fill_nonzero(ctx, flat, color);
444             }
445            
446 0           pdfmake_path_destroy(flat);
447 0           return PDFMAKE_RENDER_OK;
448             }
449              
450             /*
451             * Fill current path
452             */
453 0           pdfmake_render_err_t pdfmake_render_fill(pdfmake_render_ctx_t *ctx) {
454             pdfmake_render_err_t err;
455 0 0         if (!ctx) {
456 0           return PDFMAKE_RENDER_ERR_NULL;
457             }
458              
459 0           err = pdfmake_fill_path(ctx, ctx->path, ctx->fill_rule);
460            
461             /* Clear path after fill */
462 0           pdfmake_path_clear(ctx->path);
463            
464 0           return err;
465             }
466              
467             /*
468             * Fill and preserve path
469             */
470 0           pdfmake_render_err_t pdfmake_render_fill_preserve(pdfmake_render_ctx_t *ctx) {
471 0 0         if (!ctx) {
472 0           return PDFMAKE_RENDER_ERR_NULL;
473             }
474            
475 0           return pdfmake_fill_path(ctx, ctx->path, ctx->fill_rule);
476             }