File Coverage

src/pdfmake_render_clip.c
Criterion Covered Total %
statement 0 239 0.0
branch 0 158 0.0
condition n/a
subroutine n/a
pod n/a
total 0 397 0.0


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_render_clip.c - Clipping path support
3             *
4             * Implements clipping by rendering paths to an alpha mask buffer
5             * that is applied during fill/stroke operations.
6             */
7              
8             #include "pdfmake_render.h"
9             #include
10             #include
11             #include
12              
13             #define MIN(a, b) ((a) < (b) ? (a) : (b))
14             #define MAX(a, b) ((a) > (b) ? (a) : (b))
15             #define CLAMP(x, lo, hi) ((x) < (lo) ? (lo) : ((x) > (hi) ? (hi) : (x)))
16              
17             /*
18             * Edge structure for clip scanline
19             */
20             typedef struct clip_edge {
21             int y_min;
22             int y_max;
23             double x;
24             double dx;
25             int dir;
26             struct clip_edge *next;
27             } clip_edge_t;
28              
29             /*
30             * Create clip edge
31             */
32 0           static clip_edge_t *create_clip_edge(double x0, double y0, double x1, double y1) {
33             clip_edge_t *edge;
34 0 0         if (fabs(y1 - y0) < 0.5) {
35 0           return NULL;
36             }
37            
38 0           edge = malloc(sizeof(clip_edge_t));
39 0 0         if (!edge) {
40 0           return NULL;
41             }
42            
43 0 0         if (y0 > y1) {
44 0           double tmp = x0; x0 = x1; x1 = tmp;
45 0           tmp = y0; y0 = y1; y1 = tmp;
46 0           edge->dir = 1;
47             } else {
48 0           edge->dir = -1;
49             }
50            
51 0           edge->y_min = (int)ceil(y0);
52 0           edge->y_max = (int)floor(y1);
53            
54 0 0         if (edge->y_min > edge->y_max) {
55 0           free(edge);
56 0           return NULL;
57             }
58            
59 0           edge->dx = (x1 - x0) / (y1 - y0);
60 0           edge->x = x0 + edge->dx * (edge->y_min - y0);
61 0           edge->next = NULL;
62            
63 0           return edge;
64             }
65              
66             /*
67             * Build edge table for clip path
68             */
69 0           static clip_edge_t **build_clip_edge_table(pdfmake_path_t *path,
70             int y_min, int y_max, int *edge_count)
71             {
72 0           int height = y_max - y_min + 1;
73 0           clip_edge_t **et = calloc(height, sizeof(clip_edge_t *));
74             pdfmake_point_t current;
75             pdfmake_point_t subpath_start;
76             int has_current;
77             size_t i;
78 0 0         if (!et) {
79 0           return NULL;
80             }
81            
82 0           *edge_count = 0;
83            
84 0           current.x = 0; current.y = 0;
85 0           subpath_start.x = 0; subpath_start.y = 0;
86 0           has_current = 0;
87            
88 0 0         for (i = 0; i < path->seg_count; i++) {
89 0           pdfmake_path_seg_t *seg = &path->segs[i];
90            
91 0           switch (seg->op) {
92 0           case PDFMAKE_PATH_MOVE:
93 0           current = seg->pts[0];
94 0           subpath_start = current;
95 0           has_current = 1;
96 0           break;
97            
98 0           case PDFMAKE_PATH_LINE:
99 0 0         if (has_current) {
100 0           clip_edge_t *edge = create_clip_edge(
101             current.x, current.y,
102             seg->pts[0].x, seg->pts[0].y);
103            
104 0 0         if (edge && edge->y_min >= y_min && edge->y_min <= y_max) {
    0          
    0          
105 0           int idx = edge->y_min - y_min;
106 0           edge->next = et[idx];
107 0           et[idx] = edge;
108 0           (*edge_count)++;
109 0 0         } else if (edge) {
110 0           free(edge);
111             }
112             }
113 0           current = seg->pts[0];
114 0           break;
115            
116 0           case PDFMAKE_PATH_CLOSE:
117 0 0         if (has_current) {
118 0           clip_edge_t *edge = create_clip_edge(
119             current.x, current.y,
120             subpath_start.x, subpath_start.y);
121            
122 0 0         if (edge && edge->y_min >= y_min && edge->y_min <= y_max) {
    0          
    0          
123 0           int idx = edge->y_min - y_min;
124 0           edge->next = et[idx];
125 0           et[idx] = edge;
126 0           (*edge_count)++;
127 0 0         } else if (edge) {
128 0           free(edge);
129             }
130             }
131 0           current = subpath_start;
132 0           break;
133            
134 0           case PDFMAKE_PATH_CURVE:
135 0           break;
136             }
137             }
138            
139 0           return et;
140             }
141              
142             /*
143             * Insert edge into active list sorted by x
144             */
145 0           static void insert_clip_edge_sorted(clip_edge_t **ael, clip_edge_t *edge) {
146             clip_edge_t *curr;
147 0           edge->next = NULL;
148            
149 0 0         if (*ael == NULL || edge->x < (*ael)->x) {
    0          
150 0           edge->next = *ael;
151 0           *ael = edge;
152 0           return;
153             }
154            
155 0           curr = *ael;
156 0 0         while (curr->next && curr->next->x < edge->x) {
    0          
157 0           curr = curr->next;
158             }
159 0           edge->next = curr->next;
160 0           curr->next = edge;
161             }
162              
163             /*
164             * Sort active edge list
165             */
166 0           static void sort_clip_ael(clip_edge_t **ael) {
167             clip_edge_t *sorted;
168             clip_edge_t *curr;
169 0 0         if (!*ael || !(*ael)->next) {
    0          
170 0           return;
171             }
172            
173 0           sorted = NULL;
174 0           curr = *ael;
175            
176 0 0         while (curr) {
177 0           clip_edge_t *next = curr->next;
178 0           insert_clip_edge_sorted(&sorted, curr);
179 0           curr = next;
180             }
181            
182 0           *ael = sorted;
183             }
184              
185             /*
186             * Fill mask span
187             */
188 0           static void fill_mask_span(uint8_t *mask, int width, int y, int x0, int x1) {
189             uint8_t *row;
190             int x;
191 0 0         x0 = CLAMP(x0, 0, width);
192 0 0         x1 = CLAMP(x1, 0, width);
193            
194 0           row = mask + y * width;
195 0 0         for (x = x0; x < x1; x++) {
196 0           row[x] = 255;
197             }
198 0           }
199              
200             /*
201             * Rasterize clip path to mask with non-zero winding rule
202             */
203 0           static void clip_nonzero(uint8_t *mask, int width, int height,
204             pdfmake_path_t *path)
205             {
206             double x_min, y_min, x_max, y_max;
207             int iy_min, iy_max;
208             int edge_count;
209             clip_edge_t **et;
210             clip_edge_t *ael;
211             int y;
212 0 0         if (pdfmake_path_get_bounds(path, &x_min, &y_min, &x_max, &y_max) != PDFMAKE_RENDER_OK) {
213 0           return;
214             }
215            
216 0           iy_min = MAX((int)floor(y_min), 0);
217 0           iy_max = MIN((int)ceil(y_max), height - 1);
218            
219 0 0         if (iy_min > iy_max) {
220 0           return;
221             }
222            
223 0           et = build_clip_edge_table(path, iy_min, iy_max, &edge_count);
224 0 0         if (!et) {
225 0           return;
226             }
227            
228 0           ael = NULL;
229            
230 0 0         for (y = iy_min; y <= iy_max; y++) {
231 0           int idx = y - iy_min;
232             int winding;
233             clip_edge_t *curr;
234             int span_start;
235             clip_edge_t *prev;
236            
237 0 0         while (et[idx]) {
238 0           clip_edge_t *edge = et[idx];
239 0           et[idx] = edge->next;
240 0           insert_clip_edge_sorted(&ael, edge);
241             }
242            
243 0           sort_clip_ael(&ael);
244            
245 0           winding = 0;
246 0           curr = ael;
247 0           span_start = -1;
248            
249 0 0         while (curr) {
250 0 0         if (winding == 0 && curr->dir != 0) {
    0          
251 0           span_start = (int)floor(curr->x);
252             }
253 0           winding += curr->dir;
254 0 0         if (winding == 0 && span_start >= 0) {
    0          
255 0           int span_end = (int)ceil(curr->x);
256 0           fill_mask_span(mask, width, y, span_start, span_end);
257 0           span_start = -1;
258             }
259 0           curr = curr->next;
260             }
261            
262 0           prev = NULL;
263 0           curr = ael;
264 0 0         while (curr) {
265 0           clip_edge_t *next = curr->next;
266 0 0         if (y >= curr->y_max) {
267 0 0         if (prev) {
268 0           prev->next = next;
269             } else {
270 0           ael = next;
271             }
272 0           free(curr);
273             } else {
274 0           curr->x += curr->dx;
275 0           prev = curr;
276             }
277 0           curr = next;
278             }
279             }
280            
281 0           free(et);
282            
283 0 0         while (ael) {
284 0           clip_edge_t *next = ael->next;
285 0           free(ael);
286 0           ael = next;
287             }
288             }
289              
290             /*
291             * Rasterize clip path to mask with even-odd rule
292             */
293 0           static void clip_evenodd(uint8_t *mask, int width, int height,
294             pdfmake_path_t *path)
295             {
296             double x_min, y_min, x_max, y_max;
297             int iy_min, iy_max;
298             int edge_count;
299             clip_edge_t **et;
300             clip_edge_t *ael;
301             int y;
302 0 0         if (pdfmake_path_get_bounds(path, &x_min, &y_min, &x_max, &y_max) != PDFMAKE_RENDER_OK) {
303 0           return;
304             }
305            
306 0           iy_min = MAX((int)floor(y_min), 0);
307 0           iy_max = MIN((int)ceil(y_max), height - 1);
308            
309 0 0         if (iy_min > iy_max) {
310 0           return;
311             }
312            
313 0           et = build_clip_edge_table(path, iy_min, iy_max, &edge_count);
314 0 0         if (!et) {
315 0           return;
316             }
317            
318 0           ael = NULL;
319            
320 0 0         for (y = iy_min; y <= iy_max; y++) {
321 0           int idx = y - iy_min;
322             int parity;
323             clip_edge_t *curr;
324             int span_start;
325             clip_edge_t *prev;
326            
327 0 0         while (et[idx]) {
328 0           clip_edge_t *edge = et[idx];
329 0           et[idx] = edge->next;
330 0           insert_clip_edge_sorted(&ael, edge);
331             }
332            
333 0           sort_clip_ael(&ael);
334            
335 0           parity = 0;
336 0           curr = ael;
337 0           span_start = -1;
338            
339 0 0         while (curr) {
340 0 0         if (parity == 0) {
341 0           span_start = (int)floor(curr->x);
342             }
343 0           parity = 1 - parity;
344 0 0         if (parity == 0 && span_start >= 0) {
    0          
345 0           int span_end = (int)ceil(curr->x);
346 0           fill_mask_span(mask, width, y, span_start, span_end);
347 0           span_start = -1;
348             }
349 0           curr = curr->next;
350             }
351            
352 0           prev = NULL;
353 0           curr = ael;
354 0 0         while (curr) {
355 0           clip_edge_t *next = curr->next;
356 0 0         if (y >= curr->y_max) {
357 0 0         if (prev) {
358 0           prev->next = next;
359             } else {
360 0           ael = next;
361             }
362 0           free(curr);
363             } else {
364 0           curr->x += curr->dx;
365 0           prev = curr;
366             }
367 0           curr = next;
368             }
369             }
370            
371 0           free(et);
372            
373 0 0         while (ael) {
374 0           clip_edge_t *next = ael->next;
375 0           free(ael);
376 0           ael = next;
377             }
378             }
379              
380             /*
381             * Clip to path with specified fill rule
382             */
383 0           pdfmake_render_err_t pdfmake_clip_path(
384             pdfmake_render_ctx_t *ctx,
385             pdfmake_path_t *path,
386             pdfmake_fill_rule_t rule)
387             {
388             pdfmake_path_t *flat;
389             size_t mask_size;
390             uint8_t *new_mask;
391 0 0         if (!ctx || !path) {
    0          
392 0           return PDFMAKE_RENDER_ERR_NULL;
393             }
394            
395 0 0         if (pdfmake_path_is_empty(path)) {
396 0           return PDFMAKE_RENDER_ERR_EMPTY_PATH;
397             }
398            
399             /* Flatten curves */
400 0           flat = pdfmake_path_flatten(path, ctx->flatness);
401 0 0         if (!flat) {
402 0           return PDFMAKE_RENDER_ERR_MEMORY;
403             }
404            
405 0           mask_size = ctx->width * ctx->height;
406            
407             /* Allocate new mask */
408 0           new_mask = calloc(mask_size, 1);
409 0 0         if (!new_mask) {
410 0           pdfmake_path_destroy(flat);
411 0           return PDFMAKE_RENDER_ERR_MEMORY;
412             }
413            
414             /* Rasterize path to mask */
415 0 0         if (rule == PDFMAKE_FILL_EVENODD) {
416 0           clip_evenodd(new_mask, ctx->width, ctx->height, flat);
417             } else {
418 0           clip_nonzero(new_mask, ctx->width, ctx->height, flat);
419             }
420            
421 0           pdfmake_path_destroy(flat);
422            
423             /* Intersect with existing clip mask if present */
424 0 0         if (ctx->has_clip && ctx->clip_mask) {
    0          
425             size_t i;
426 0 0         for (i = 0; i < mask_size; i++) {
427 0           new_mask[i] = MIN(new_mask[i], ctx->clip_mask[i]);
428             }
429 0           free(ctx->clip_mask);
430             }
431            
432 0           ctx->clip_mask = new_mask;
433 0           ctx->has_clip = 1;
434            
435 0           return PDFMAKE_RENDER_OK;
436             }
437              
438             /*
439             * Clip to current path
440             */
441 0           pdfmake_render_err_t pdfmake_render_clip(pdfmake_render_ctx_t *ctx) {
442             pdfmake_render_err_t err;
443 0 0         if (!ctx) {
444 0           return PDFMAKE_RENDER_ERR_NULL;
445             }
446            
447 0           err = pdfmake_clip_path(ctx, ctx->path, ctx->fill_rule);
448            
449             /* Clear path after clip */
450 0           pdfmake_path_clear(ctx->path);
451            
452 0           return err;
453             }
454              
455             /*
456             * Reset clip to full canvas
457             */
458 0           void pdfmake_render_reset_clip(pdfmake_render_ctx_t *ctx) {
459 0 0         if (!ctx) {
460 0           return;
461             }
462            
463 0 0         if (ctx->clip_mask) {
464 0           free(ctx->clip_mask);
465 0           ctx->clip_mask = NULL;
466             }
467 0           ctx->has_clip = 0;
468             }
469              
470             /*
471             * Check if point is inside clip region
472             */
473 0           int pdfmake_render_point_in_clip(pdfmake_render_ctx_t *ctx, int x, int y) {
474 0 0         if (!ctx) {
475 0           return 0;
476             }
477            
478 0 0         if (!ctx->has_clip || !ctx->clip_mask) {
    0          
479 0           return 1; /* No clip = everything inside */
480             }
481            
482 0 0         if (x < 0 || x >= ctx->width || y < 0 || y >= ctx->height) {
    0          
    0          
    0          
483 0           return 0;
484             }
485            
486 0           return ctx->clip_mask[y * ctx->width + x] > 0;
487             }
488              
489             /*
490             * Get clip mask value at point (0-255)
491             */
492 0           int pdfmake_render_get_clip_alpha(pdfmake_render_ctx_t *ctx, int x, int y) {
493 0 0         if (!ctx) {
494 0           return 0;
495             }
496            
497 0 0         if (!ctx->has_clip || !ctx->clip_mask) {
    0          
498 0           return 255;
499             }
500            
501 0 0         if (x < 0 || x >= ctx->width || y < 0 || y >= ctx->height) {
    0          
    0          
    0          
502 0           return 0;
503             }
504            
505 0           return ctx->clip_mask[y * ctx->width + x];
506             }