File Coverage

src/pdfmake_interpreter.c
Criterion Covered Total %
statement 590 1075 54.8
branch 181 408 44.3
condition n/a
subroutine n/a
pod n/a
total 771 1483 51.9


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_interpreter.c - Content stream interpreter implementation
3             *
4             * Reference: PDF 32000-1:2008
5             * - §8.4 Graphics state
6             * - §9.3 Text state parameters
7             * - §9.4 Text objects
8             * - Annex A Operators
9             */
10              
11             #include "pdfmake_interpreter.h"
12             #include "pdfmake_arena.h"
13             #include "pdfmake_tokenizer.h"
14             #include "pdfmake_reader.h"
15             #include "pdfmake_parser.h"
16             #include "pdfmake_buf.h"
17             #include
18             #include
19             #include
20             #include
21              
22             /* Forward declaration — defined later in this file and used by interpret_form. */
23             static pdfmake_err_t parse_content_stream(pdfmake_interp_t *interp,
24             const uint8_t *buf, size_t len);
25              
26             /*============================================================================
27             * Constants
28             *==========================================================================*/
29              
30             #define OPERAND_STACK_INIT 32
31             #define PATH_INIT_CAP 64
32             #define MC_STACK_INIT 16
33              
34             /*============================================================================
35             * Matrix operations
36             *==========================================================================*/
37              
38 7424           void pdfmake_matrix_identity(double m[6]) {
39 7424           m[0] = 1.0; m[1] = 0.0;
40 7424           m[2] = 0.0; m[3] = 1.0;
41 7424           m[4] = 0.0; m[5] = 0.0;
42 7424           }
43              
44 845           void pdfmake_matrix_copy(double dst[6], const double src[6]) {
45 845           memcpy(dst, src, 6 * sizeof(double));
46 845           }
47              
48             /*
49             * Matrix multiplication for 3x3 affine matrices stored as [a b c d e f]:
50             * | a b 0 | | a' b' 0 | | aa'+cb' ab'+db' 0 |
51             * | c d 0 | × | c' d' 0 | = | ac'+cd' bc'+dd' 0 |
52             * | e f 1 | | e' f' 1 | | ae'+cf'+e' be'+df'+f' 1 |
53             */
54 6641           void pdfmake_matrix_multiply(double result[6], const double a[6], const double b[6]) {
55             double r[6];
56 6641           r[0] = a[0] * b[0] + a[1] * b[2];
57 6641           r[1] = a[0] * b[1] + a[1] * b[3];
58 6641           r[2] = a[2] * b[0] + a[3] * b[2];
59 6641           r[3] = a[2] * b[1] + a[3] * b[3];
60 6641           r[4] = a[4] * b[0] + a[5] * b[2] + b[4];
61 6641           r[5] = a[4] * b[1] + a[5] * b[3] + b[5];
62 6641           memcpy(result, r, 6 * sizeof(double));
63 6641           }
64              
65 19           void pdfmake_matrix_concat(double m[6], const double other[6]) {
66 19           pdfmake_matrix_multiply(m, m, other);
67 19           }
68              
69 6618           void pdfmake_matrix_translate(double m[6], double tx, double ty) {
70 6618           pdfmake_matrix_identity(m);
71 6618           m[4] = tx;
72 6618           m[5] = ty;
73 6618           }
74              
75 0           void pdfmake_matrix_scale(double m[6], double sx, double sy) {
76 0           pdfmake_matrix_identity(m);
77 0           m[0] = sx;
78 0           m[3] = sy;
79 0           }
80              
81 0           void pdfmake_matrix_rotate(double m[6], double angle) {
82 0           double c = cos(angle);
83 0           double s = sin(angle);
84 0           m[0] = c; m[1] = s;
85 0           m[2] = -s; m[3] = c;
86 0           m[4] = 0; m[5] = 0;
87 0           }
88              
89 0           int pdfmake_matrix_invert(double result[6], const double m[6]) {
90             double det;
91             double inv_det;
92 0           det = m[0] * m[3] - m[1] * m[2];
93 0 0         if (fabs(det) < 1e-10) return -1; /* Singular */
94            
95 0           inv_det = 1.0 / det;
96 0           result[0] = m[3] * inv_det;
97 0           result[1] = -m[1] * inv_det;
98 0           result[2] = -m[2] * inv_det;
99 0           result[3] = m[0] * inv_det;
100 0           result[4] = (m[2] * m[5] - m[3] * m[4]) * inv_det;
101 0           result[5] = (m[1] * m[4] - m[0] * m[5]) * inv_det;
102 0           return 0;
103             }
104              
105 0           void pdfmake_matrix_transform_point(const double m[6], double *x, double *y) {
106 0           double px = *x, py = *y;
107 0           *x = m[0] * px + m[2] * py + m[4];
108 0           *y = m[1] * px + m[3] * py + m[5];
109 0           }
110              
111             /*============================================================================
112             * Graphics state helpers
113             *==========================================================================*/
114              
115 162           static void gstate_init(pdfmake_gstate_t *gs) {
116 162           memset(gs, 0, sizeof(*gs));
117            
118             /* CTM = identity */
119 162           pdfmake_matrix_identity(gs->ctm);
120            
121             /* Line state defaults (§8.4.3) */
122 162           gs->line_width = 1.0;
123 162           gs->line_cap = PDFMAKE_CAP_BUTT;
124 162           gs->line_join = PDFMAKE_JOIN_MITER;
125 162           gs->miter_limit = 10.0;
126 162           gs->dash_array = NULL;
127 162           gs->dash_count = 0;
128 162           gs->dash_phase = 0.0;
129            
130             /* Color defaults - black */
131 162           gs->stroke_color.space = PDFMAKE_CS_GRAY;
132 162           gs->stroke_color.components[0] = 0.0;
133 162           gs->stroke_color.n_components = 1;
134 162           gs->fill_color.space = PDFMAKE_CS_GRAY;
135 162           gs->fill_color.components[0] = 0.0;
136 162           gs->fill_color.n_components = 1;
137            
138             /* Text state defaults (§9.3) */
139 162           gs->char_space = 0.0;
140 162           gs->word_space = 0.0;
141 162           gs->h_scale = 100.0; /* 100% */
142 162           gs->leading = 0.0;
143 162           gs->font_size = 0.0;
144 162           gs->render_mode = PDFMAKE_RENDER_FILL;
145 162           gs->rise = 0.0;
146 162           gs->font_name = 0;
147 162           gs->font = NULL;
148            
149             /* Text matrices = identity */
150 162           pdfmake_matrix_identity(gs->text_matrix);
151 162           pdfmake_matrix_identity(gs->text_line_matrix);
152            
153             /* Other */
154 162           gs->clip_depth = 0;
155 162           gs->flatness = 1.0;
156 162           gs->rendering_intent = 0;
157 162           }
158              
159 42           static void gstate_copy(pdfmake_gstate_t *dst, const pdfmake_gstate_t *src) {
160             /* Copy everything except dash array */
161 42           double *old_dash = dst->dash_array;
162 42           memcpy(dst, src, sizeof(*dst));
163            
164             /* Deep copy dash array */
165 42 50         if (src->dash_array && src->dash_count > 0) {
    0          
166 0           dst->dash_array = malloc(src->dash_count * sizeof(double));
167 0 0         if (dst->dash_array) {
168 0           memcpy(dst->dash_array, src->dash_array,
169 0           src->dash_count * sizeof(double));
170             }
171             } else {
172 42           dst->dash_array = NULL;
173             }
174            
175             /* Free old dash array if it was allocated */
176 42           free(old_dash);
177 42           }
178              
179 162           static void gstate_cleanup(pdfmake_gstate_t *gs) {
180 162           free(gs->dash_array);
181 162           gs->dash_array = NULL;
182 162           }
183              
184             /*============================================================================
185             * Interpreter lifecycle
186             *==========================================================================*/
187              
188 60           pdfmake_interp_t *pdfmake_interp_new(pdfmake_arena_t *arena) {
189 60           pdfmake_interp_t *interp = calloc(1, sizeof(*interp));
190 60 50         if (!interp) return NULL;
191            
192 60           interp->arena = arena;
193            
194             /* Allocate graphics state stack */
195 60           interp->stack_cap = PDFMAKE_GSTATE_STACK_MAX;
196 60           interp->stack = calloc(interp->stack_cap, sizeof(pdfmake_gstate_t));
197 60 50         if (!interp->stack) {
198 0           free(interp);
199 0           return NULL;
200             }
201            
202             /* Initialize first state on stack */
203 60           interp->stack_size = 1;
204 60           gstate_init(&interp->stack[0]);
205 60           interp->gs = &interp->stack[0];
206            
207             /* Allocate operand stack */
208 60           interp->op_cap = OPERAND_STACK_INIT;
209 60           interp->operands = calloc(interp->op_cap, sizeof(pdfmake_obj_t));
210 60 50         if (!interp->operands) {
211 0           free(interp->stack);
212 0           free(interp);
213 0           return NULL;
214             }
215            
216             /* Allocate path buffer */
217 60           interp->path_cap = PATH_INIT_CAP;
218 60           interp->path = calloc(interp->path_cap, sizeof(pdfmake_path_segment_t));
219 60 50         if (!interp->path) {
220 0           free(interp->operands);
221 0           free(interp->stack);
222 0           free(interp);
223 0           return NULL;
224             }
225            
226             /* Allocate marked content stack */
227 60           interp->mc_cap = MC_STACK_INIT;
228 60           interp->mc_stack = calloc(interp->mc_cap, sizeof(uint32_t));
229 60 50         if (!interp->mc_stack) {
230 0           free(interp->path);
231 0           free(interp->operands);
232 0           free(interp->stack);
233 0           free(interp);
234 0           return NULL;
235             }
236            
237 60           return interp;
238             }
239              
240 60           void pdfmake_interp_free(pdfmake_interp_t *interp) {
241             size_t i;
242 60 50         if (!interp) return;
243            
244             /* Clean up graphics states */
245 120 100         for (i = 0; i < interp->stack_size; i++) {
246 60           gstate_cleanup(&interp->stack[i]);
247             }
248            
249 60           free(interp->stack);
250 60           free(interp->operands);
251 60           free(interp->path);
252 60           free(interp->mc_stack);
253 60           free(interp);
254             }
255              
256 60           void pdfmake_interp_set_resources(pdfmake_interp_t *interp, pdfmake_obj_t *resources) {
257 60 50         if (interp) {
258 60           interp->resources = resources;
259             }
260 60           }
261              
262 60           void pdfmake_interp_set_visitor(pdfmake_interp_t *interp, const pdfmake_visitor_t *visitor) {
263 60 50         if (interp) {
264 60           interp->visitor = visitor;
265             }
266 60           }
267              
268 60           void pdfmake_interp_set_reader(pdfmake_interp_t *interp, void *reader) {
269 60 50         if (interp) interp->reader = reader;
270 60           }
271              
272 60           void pdfmake_interp_reset(pdfmake_interp_t *interp) {
273             size_t i;
274 60 50         if (!interp) return;
275            
276             /* Clean up all graphics states */
277 120 100         for (i = 0; i < interp->stack_size; i++) {
278 60           gstate_cleanup(&interp->stack[i]);
279             }
280            
281             /* Reset to single identity state */
282 60           interp->stack_size = 1;
283 60           gstate_init(&interp->stack[0]);
284 60           interp->gs = &interp->stack[0];
285            
286             /* Clear other state */
287 60           interp->in_text_object = 0;
288 60           interp->path_size = 0;
289 60           interp->cur_x = interp->cur_y = 0;
290 60           interp->have_cur_point = 0;
291 60           interp->mc_depth = 0;
292 60           interp->op_count = 0;
293            
294             /* Clear error */
295 60           interp->last_err = PDFMAKE_OK;
296 60           interp->errmsg[0] = '\0';
297 60           interp->erroffset = 0;
298             }
299              
300 0           const char *pdfmake_interp_errmsg(pdfmake_interp_t *interp) {
301 0 0         return interp ? interp->errmsg : "";
302             }
303              
304 0           size_t pdfmake_interp_erroffset(pdfmake_interp_t *interp) {
305 0 0         return interp ? interp->erroffset : 0;
306             }
307              
308 0           const pdfmake_gstate_t *pdfmake_interp_gstate(pdfmake_interp_t *interp) {
309 0 0         return interp ? interp->gs : NULL;
310             }
311              
312 0           int pdfmake_interp_in_text_object(pdfmake_interp_t *interp) {
313 0 0         return interp ? interp->in_text_object : 0;
314             }
315              
316 0           int pdfmake_interp_get_current_point(pdfmake_interp_t *interp, double *x, double *y) {
317 0 0         if (!interp || !interp->have_cur_point) return 0;
    0          
318 0 0         if (x) *x = interp->cur_x;
319 0 0         if (y) *y = interp->cur_y;
320 0           return 1;
321             }
322              
323             /*============================================================================
324             * Error handling
325             *==========================================================================*/
326              
327 0           static void set_error(pdfmake_interp_t *interp, pdfmake_err_t err,
328             size_t offset, const char *msg) {
329 0           interp->last_err = err;
330 0           interp->erroffset = offset;
331 0 0         if (msg) {
332 0           strncpy(interp->errmsg, msg, sizeof(interp->errmsg) - 1);
333 0           interp->errmsg[sizeof(interp->errmsg) - 1] = '\0';
334             }
335 0           }
336              
337             /*============================================================================
338             * Operand stack operations
339             *==========================================================================*/
340              
341 6072           static int push_operand(pdfmake_interp_t *interp, pdfmake_obj_t obj) {
342 6072 50         if (interp->op_count >= interp->op_cap) {
343 0           size_t new_cap = interp->op_cap * 2;
344 0           pdfmake_obj_t *new_ops = realloc(interp->operands,
345             new_cap * sizeof(pdfmake_obj_t));
346 0 0         if (!new_ops) return 0;
347 0           interp->operands = new_ops;
348 0           interp->op_cap = new_cap;
349             }
350 6072           interp->operands[interp->op_count++] = obj;
351 6072           return 1;
352             }
353              
354 6072           static pdfmake_obj_t pop_operand(pdfmake_interp_t *interp) {
355 6072 50         if (interp->op_count == 0) {
356 0           return pdfmake_null();
357             }
358 6072           return interp->operands[--interp->op_count];
359             }
360              
361 0           static void clear_operands(pdfmake_interp_t *interp) {
362 0           interp->op_count = 0;
363 0           }
364              
365             /* Helper to get numeric operand */
366 7838           static double get_number(pdfmake_obj_t obj) {
367 7838 100         if (obj.kind == PDFMAKE_INT) return (double)obj.as.i;
368 5151 50         if (obj.kind == PDFMAKE_REAL) return obj.as.r;
369 0           return 0.0;
370             }
371              
372             /*============================================================================
373             * Graphics state stack operations (q/Q)
374             *==========================================================================*/
375              
376 42           static pdfmake_err_t gstate_push(pdfmake_interp_t *interp) {
377             pdfmake_gstate_t *new_gs;
378 42 50         if (interp->stack_size >= PDFMAKE_GSTATE_STACK_MAX) {
379 0           set_error(interp, PDFMAKE_ESTACK_OVER, 0,
380             "Graphics state stack overflow");
381 0           return PDFMAKE_ESTACK_OVER;
382             }
383            
384             /* Deep copy current state to new top */
385 42           new_gs = &interp->stack[interp->stack_size];
386 42           gstate_init(new_gs); /* Initialize to clear any garbage */
387 42           gstate_copy(new_gs, interp->gs);
388            
389 42           interp->stack_size++;
390 42           interp->gs = new_gs;
391            
392 42           return PDFMAKE_OK;
393             }
394              
395 42           static pdfmake_err_t gstate_pop(pdfmake_interp_t *interp) {
396 42 50         if (interp->stack_size <= 1) {
397 0           set_error(interp, PDFMAKE_ESTACK_UNDER, 0,
398             "Graphics state stack underflow");
399 0           return PDFMAKE_ESTACK_UNDER;
400             }
401            
402             /* Clean up current state */
403 42           gstate_cleanup(interp->gs);
404            
405 42           interp->stack_size--;
406 42           interp->gs = &interp->stack[interp->stack_size - 1];
407            
408 42           return PDFMAKE_OK;
409             }
410              
411             /*============================================================================
412             * Path operations
413             *==========================================================================*/
414              
415 127           static int path_ensure_cap(pdfmake_interp_t *interp, size_t need) {
416             size_t new_cap;
417             pdfmake_path_segment_t *new_path;
418 127 50         if (interp->path_size + need > interp->path_cap) {
419 0           new_cap = interp->path_cap * 2;
420 0 0         while (new_cap < interp->path_size + need) new_cap *= 2;
421 0           new_path = realloc(interp->path,
422             new_cap * sizeof(pdfmake_path_segment_t));
423 0 0         if (!new_path) return 0;
424 0           interp->path = new_path;
425 0           interp->path_cap = new_cap;
426             }
427 127           return 1;
428             }
429              
430 127           static void path_add_segment(pdfmake_interp_t *interp, pdfmake_path_segment_t seg) {
431 127 50         if (path_ensure_cap(interp, 1)) {
432 127           interp->path[interp->path_size++] = seg;
433             }
434 127           }
435              
436 127           static void path_clear(pdfmake_interp_t *interp) {
437 127           interp->path_size = 0;
438 127           interp->have_cur_point = 0;
439 127           }
440              
441             /*============================================================================
442             * Resource lookup
443             *==========================================================================*/
444              
445 380           static pdfmake_obj_t *lookup_resource(pdfmake_interp_t *interp,
446             const char *category,
447             uint32_t name_id) {
448             uint32_t cat_id;
449             pdfmake_obj_t *cat_dict;
450 380 50         if (!interp->resources || interp->resources->kind != PDFMAKE_DICT) {
    50          
451 0           return NULL;
452             }
453              
454             /* Get category dict */
455 380           cat_id = pdfmake_arena_intern_name(interp->arena, category, strlen(category));
456 380           cat_dict = pdfmake_dict_get(interp->resources, cat_id);
457 380 50         if (!cat_dict || cat_dict->kind != PDFMAKE_DICT) {
    50          
458 0           return NULL;
459             }
460              
461             /* Get resource by name */
462 380           return pdfmake_dict_get(cat_dict, name_id);
463             }
464              
465             /*============================================================================
466             * Text operators
467             *==========================================================================*/
468              
469             /* BT - Begin text object */
470 160           static pdfmake_err_t op_BT(pdfmake_interp_t *interp) {
471 160 50         if (interp->in_text_object) {
472 0           set_error(interp, PDFMAKE_ETEXTOBJ, 0, "Nested BT");
473 0           return PDFMAKE_ETEXTOBJ;
474             }
475 160           interp->in_text_object = 1;
476            
477             /* Reset text matrices to identity */
478 160           pdfmake_matrix_identity(interp->gs->text_matrix);
479 160           pdfmake_matrix_identity(interp->gs->text_line_matrix);
480            
481 160           return PDFMAKE_OK;
482             }
483              
484             /* ET - End text object */
485 160           static pdfmake_err_t op_ET(pdfmake_interp_t *interp) {
486 160 50         if (!interp->in_text_object) {
487 0           set_error(interp, PDFMAKE_ETEXTOBJ, 0, "ET without BT");
488 0           return PDFMAKE_ETEXTOBJ;
489             }
490 160           interp->in_text_object = 0;
491 160           return PDFMAKE_OK;
492             }
493              
494             /* Tc - Set character spacing */
495 290           static pdfmake_err_t op_Tc(pdfmake_interp_t *interp) {
496 290           pdfmake_obj_t charSpace = pop_operand(interp);
497 290           interp->gs->char_space = get_number(charSpace);
498 290           return PDFMAKE_OK;
499             }
500              
501             /* Tw - Set word spacing */
502 256           static pdfmake_err_t op_Tw(pdfmake_interp_t *interp) {
503 256           pdfmake_obj_t wordSpace = pop_operand(interp);
504 256           interp->gs->word_space = get_number(wordSpace);
505 256           return PDFMAKE_OK;
506             }
507              
508             /* Tz - Set horizontal scaling */
509 0           static pdfmake_err_t op_Tz(pdfmake_interp_t *interp) {
510 0           pdfmake_obj_t scale = pop_operand(interp);
511 0           interp->gs->h_scale = get_number(scale);
512 0           return PDFMAKE_OK;
513             }
514              
515             /* TL - Set text leading */
516 1           static pdfmake_err_t op_TL(pdfmake_interp_t *interp) {
517 1           pdfmake_obj_t leading = pop_operand(interp);
518 1           interp->gs->leading = get_number(leading);
519 1           return PDFMAKE_OK;
520             }
521              
522             /* Tf - Set text font and size */
523 366           static pdfmake_err_t op_Tf(pdfmake_interp_t *interp) {
524 366           pdfmake_obj_t size = pop_operand(interp);
525 366           pdfmake_obj_t font = pop_operand(interp);
526            
527 366           interp->gs->font_size = get_number(size);
528            
529 366 50         if (font.kind == PDFMAKE_NAME) {
530 366           interp->gs->font_name = font.as.name.id;
531             /* Look up font in resources */
532 366           interp->gs->font = lookup_resource(interp, "Font", font.as.name.id);
533             }
534            
535 366           return PDFMAKE_OK;
536             }
537              
538             /* Tr - Set text rendering mode */
539 6           static pdfmake_err_t op_Tr(pdfmake_interp_t *interp) {
540 6           pdfmake_obj_t render = pop_operand(interp);
541 6           int mode = (int)get_number(render);
542 6 50         if (mode >= 0 && mode <= 7) {
    50          
543 6           interp->gs->render_mode = mode;
544             }
545 6           return PDFMAKE_OK;
546             }
547              
548             /* Ts - Set text rise */
549 0           static pdfmake_err_t op_Ts(pdfmake_interp_t *interp) {
550 0           pdfmake_obj_t rise = pop_operand(interp);
551 0           interp->gs->rise = get_number(rise);
552 0           return PDFMAKE_OK;
553             }
554              
555             /* Td - Move text position */
556 37           static pdfmake_err_t op_Td(pdfmake_interp_t *interp) {
557 37           pdfmake_obj_t ty = pop_operand(interp);
558 37           pdfmake_obj_t tx = pop_operand(interp);
559            
560             double m[6];
561 37           pdfmake_matrix_translate(m, get_number(tx), get_number(ty));
562 37           pdfmake_matrix_multiply(interp->gs->text_line_matrix,
563 37           m, interp->gs->text_line_matrix);
564 37           pdfmake_matrix_copy(interp->gs->text_matrix, interp->gs->text_line_matrix);
565            
566 37           return PDFMAKE_OK;
567             }
568              
569             /* TD - Move text position and set leading */
570 381           static pdfmake_err_t op_TD(pdfmake_interp_t *interp) {
571 381           pdfmake_obj_t ty = pop_operand(interp);
572 381           pdfmake_obj_t tx = pop_operand(interp);
573             double m[6];
574            
575 381           interp->gs->leading = -get_number(ty);
576            
577 381           pdfmake_matrix_translate(m, get_number(tx), get_number(ty));
578 381           pdfmake_matrix_multiply(interp->gs->text_line_matrix,
579 381           m, interp->gs->text_line_matrix);
580 381           pdfmake_matrix_copy(interp->gs->text_matrix, interp->gs->text_line_matrix);
581            
582 381           return PDFMAKE_OK;
583             }
584              
585             /* Tm - Set text matrix */
586 349           static pdfmake_err_t op_Tm(pdfmake_interp_t *interp) {
587 349           pdfmake_obj_t f = pop_operand(interp);
588 349           pdfmake_obj_t e = pop_operand(interp);
589 349           pdfmake_obj_t d = pop_operand(interp);
590 349           pdfmake_obj_t c = pop_operand(interp);
591 349           pdfmake_obj_t b = pop_operand(interp);
592 349           pdfmake_obj_t a = pop_operand(interp);
593            
594 349           interp->gs->text_matrix[0] = get_number(a);
595 349           interp->gs->text_matrix[1] = get_number(b);
596 349           interp->gs->text_matrix[2] = get_number(c);
597 349           interp->gs->text_matrix[3] = get_number(d);
598 349           interp->gs->text_matrix[4] = get_number(e);
599 349           interp->gs->text_matrix[5] = get_number(f);
600            
601 349           pdfmake_matrix_copy(interp->gs->text_line_matrix, interp->gs->text_matrix);
602            
603 349           return PDFMAKE_OK;
604             }
605              
606             /* T* - Move to start of next line */
607 78           static pdfmake_err_t op_Tstar(pdfmake_interp_t *interp) {
608             double m[6];
609 78           pdfmake_matrix_translate(m, 0, -interp->gs->leading);
610 78           pdfmake_matrix_multiply(interp->gs->text_line_matrix,
611 78           m, interp->gs->text_line_matrix);
612 78           pdfmake_matrix_copy(interp->gs->text_matrix, interp->gs->text_line_matrix);
613 78           return PDFMAKE_OK;
614             }
615              
616             /* Helper: advance text matrix by string width */
617 3484           static void advance_text_matrix(pdfmake_interp_t *interp,
618             const uint8_t *bytes, size_t len) {
619             double width;
620             size_t char_count;
621             size_t space_count;
622             size_t i;
623             double avg_glyph_width;
624             int vertical;
625             double m[6];
626              
627 3484           width = 0;
628              
629             /* Phase 8: if the visitor can compute the real text-space advance
630             * (using resolved font widths), use that. This keeps text_matrix in
631             * sync with the visitor's glyph positions across Tj/TJ calls, which
632             * is essential for accurate inter-run gap detection. */
633 3484 50         if (interp->visitor && interp->visitor->get_string_advance) {
    50          
634 3484           width = interp->visitor->get_string_advance(
635 3484           interp->visitor->ctx, interp->gs, bytes, len);
636             }
637              
638 3484 50         if (width <= 0) {
639             /* Fallback: 0.6-em placeholder per character, plus char/word space */
640 0           char_count = len;
641 0           space_count = 0;
642 0 0         for (i = 0; i < len; i++) {
643 0 0         if (bytes[i] == ' ') space_count++;
644             }
645 0           avg_glyph_width = 0.6 * interp->gs->font_size;
646 0           width = char_count * avg_glyph_width
647 0           + char_count * interp->gs->char_space
648 0           + space_count * interp->gs->word_space;
649             }
650              
651 3484           width *= interp->gs->h_scale / 100.0;
652              
653             /* Phase 14: vertical writing advances along -y, not +x. */
654 3484           vertical = 0;
655 3484 50         if (interp->visitor && interp->visitor->is_vertical_writing) {
    50          
656 3484           vertical = interp->visitor->is_vertical_writing(
657 3484           interp->visitor->ctx, interp->gs);
658             }
659              
660 3484 100         if (vertical) {
661 1           pdfmake_matrix_translate(m, 0, -width);
662             } else {
663 3483           pdfmake_matrix_translate(m, width, 0);
664             }
665 3484           pdfmake_matrix_multiply(interp->gs->text_matrix,
666 3484           m, interp->gs->text_matrix);
667 3484           }
668              
669             /* Tj - Show text string */
670             /* Convert ASCII hex-digit byte to its 0-15 value, or -1 if not a hex digit. */
671 124           static int hex_val(uint8_t c) {
672 124 50         if (c >= '0' && c <= '9') return c - '0';
    100          
673 16 50         if (c >= 'a' && c <= 'f') return c - 'a' + 10;
    50          
674 0 0         if (c >= 'A' && c <= 'F') return c - 'A' + 10;
    0          
675 0           return -1;
676             }
677              
678             /* If the string is a hex string (`` form), decode it into a fresh
679             * arena-backed byte array. Whitespace is skipped; odd trailing nibble is
680             * padded with 0 per PDF spec.
681             * Returns 1 if decoded (caller uses *out_bytes), 0 if not a hex string. */
682 6           static int maybe_decode_hex_string(pdfmake_arena_t *arena,
683             const pdfmake_obj_t *str,
684             const uint8_t **out_bytes,
685             size_t *out_len)
686             {
687             const uint8_t *in;
688             size_t in_len;
689             uint8_t *out;
690             size_t written;
691             int nibble;
692             size_t i;
693              
694 6 50         if (!str || str->kind != PDFMAKE_STR || !str->as.str.hex) return 0;
    50          
    50          
695              
696 6           in = str->as.str.bytes;
697 6           in_len = str->as.str.len;
698              
699             /* Max output is ceil(in_len / 2) — safe upper bound */
700 6           out = pdfmake_arena_alloc(arena, (in_len / 2) + 1);
701 6 50         if (!out) return 0;
702              
703 6           written = 0;
704 6           nibble = -1;
705 130 100         for (i = 0; i < in_len; i++) {
706 124           int v = hex_val(in[i]);
707 124 50         if (v < 0) continue; /* skip whitespace, ignore garbage */
708 124 100         if (nibble < 0) {
709 62           nibble = v;
710             } else {
711 62           out[written++] = (uint8_t)((nibble << 4) | v);
712 62           nibble = -1;
713             }
714             }
715 6 50         if (nibble >= 0) out[written++] = (uint8_t)(nibble << 4);
716              
717 6           *out_bytes = out;
718 6           *out_len = written;
719 6           return 1;
720             }
721              
722 437           static pdfmake_err_t op_Tj(pdfmake_interp_t *interp) {
723             pdfmake_obj_t str;
724             const uint8_t *bytes;
725             size_t len;
726              
727 437 50         if (!interp->in_text_object) {
728             /* Lenient: just skip */
729 0           clear_operands(interp);
730 0           return PDFMAKE_OK;
731             }
732              
733 437           str = pop_operand(interp);
734              
735 437           bytes = NULL;
736 437           len = 0;
737              
738 437 50         if (str.kind == PDFMAKE_STR) {
739 437 100         if (str.as.str.hex) {
740             /* Decode → raw bytes */
741 1 50         if (!maybe_decode_hex_string(interp->arena, &str, &bytes, &len)) {
742 0           bytes = str.as.str.bytes;
743 0           len = str.as.str.len;
744             }
745             } else {
746 436           bytes = str.as.str.bytes;
747 436           len = str.as.str.len;
748             }
749             }
750            
751             /* Fire visitor callback */
752 437 50         if (interp->visitor && interp->visitor->on_text_show && bytes) {
    50          
    50          
753 437           interp->visitor->on_text_show(interp->visitor->ctx,
754 437           interp->gs, bytes, len);
755             }
756            
757             /* Advance text matrix */
758 437 50         if (bytes && len > 0) {
    50          
759 437           advance_text_matrix(interp, bytes, len);
760             }
761            
762 437           return PDFMAKE_OK;
763             }
764              
765             /* TJ - Show text with positioning */
766 409           static pdfmake_err_t op_TJ(pdfmake_interp_t *interp) {
767             pdfmake_obj_t arr;
768             size_t arr_len;
769             size_t i;
770              
771 409 50         if (!interp->in_text_object) {
772 0           clear_operands(interp);
773 0           return PDFMAKE_OK;
774             }
775            
776 409           arr = pop_operand(interp);
777            
778 409 50         if (arr.kind != PDFMAKE_ARRAY) {
779 0           return PDFMAKE_OK;
780             }
781            
782 409           arr_len = pdfmake_array_len(&arr);
783            
784 6094 100         for (i = 0; i < arr_len; i++) {
785 5685           pdfmake_obj_t *elem = pdfmake_array_get(&arr, i);
786 5685 50         if (!elem) continue;
787            
788 5685 100         if (elem->kind == PDFMAKE_STR) {
789             /* String element - show text. Decode hex strings first. */
790             const uint8_t *bytes;
791             size_t len;
792 3047 100         if (elem->as.str.hex) {
793 5 50         if (!maybe_decode_hex_string(interp->arena, elem, &bytes, &len)) {
794 0           bytes = elem->as.str.bytes;
795 0           len = elem->as.str.len;
796             }
797             } else {
798 3042           bytes = elem->as.str.bytes;
799 3042           len = elem->as.str.len;
800             }
801              
802 3047 50         if (interp->visitor && interp->visitor->on_text_show) {
    50          
803 3047           interp->visitor->on_text_show(interp->visitor->ctx,
804 3047           interp->gs, bytes, len);
805             }
806              
807 3047           advance_text_matrix(interp, bytes, len);
808            
809 2638 100         } else if (elem->kind == PDFMAKE_INT || elem->kind == PDFMAKE_REAL) {
    50          
810             /* Numeric element - adjust position.
811             * Value is in thousandths of a text space unit.
812             * Positive moves left (decreases position). */
813 2638           double adj = get_number(*elem);
814 2638           double tx = -adj * interp->gs->font_size / 1000.0
815 2638           * interp->gs->h_scale / 100.0;
816            
817             double m[6];
818 2638           pdfmake_matrix_translate(m, tx, 0);
819 2638           pdfmake_matrix_multiply(interp->gs->text_matrix,
820 2638           m, interp->gs->text_matrix);
821             }
822             }
823            
824 409           return PDFMAKE_OK;
825             }
826              
827             /* ' - Move to next line and show text */
828 0           static pdfmake_err_t op_quote(pdfmake_interp_t *interp) {
829 0           op_Tstar(interp);
830 0           return op_Tj(interp);
831             }
832              
833             /* " - Set spacing, move to next line, show text */
834 0           static pdfmake_err_t op_dquote(pdfmake_interp_t *interp) {
835 0           pdfmake_obj_t str = pop_operand(interp);
836 0           pdfmake_obj_t ac = pop_operand(interp);
837 0           pdfmake_obj_t aw = pop_operand(interp);
838            
839 0           interp->gs->word_space = get_number(aw);
840 0           interp->gs->char_space = get_number(ac);
841            
842 0           push_operand(interp, str);
843 0           op_Tstar(interp);
844 0           return op_Tj(interp);
845             }
846              
847             /*============================================================================
848             * Graphics state operators
849             *==========================================================================*/
850              
851             /* q - Save graphics state */
852 38           static pdfmake_err_t op_q(pdfmake_interp_t *interp) {
853 38           return gstate_push(interp);
854             }
855              
856             /* Q - Restore graphics state */
857 38           static pdfmake_err_t op_Q(pdfmake_interp_t *interp) {
858 38           return gstate_pop(interp);
859             }
860              
861             /* cm - Concatenate matrix */
862 19           static pdfmake_err_t op_cm(pdfmake_interp_t *interp) {
863 19           pdfmake_obj_t f = pop_operand(interp);
864 19           pdfmake_obj_t e = pop_operand(interp);
865 19           pdfmake_obj_t d = pop_operand(interp);
866 19           pdfmake_obj_t c = pop_operand(interp);
867 19           pdfmake_obj_t b = pop_operand(interp);
868 19           pdfmake_obj_t a = pop_operand(interp);
869            
870             double m[6];
871 19           m[0] = get_number(a);
872 19           m[1] = get_number(b);
873 19           m[2] = get_number(c);
874 19           m[3] = get_number(d);
875 19           m[4] = get_number(e);
876 19           m[5] = get_number(f);
877            
878 19           pdfmake_matrix_concat(interp->gs->ctm, m);
879            
880 19           return PDFMAKE_OK;
881             }
882              
883             /* w - Set line width */
884 0           static pdfmake_err_t op_w(pdfmake_interp_t *interp) {
885 0           pdfmake_obj_t width = pop_operand(interp);
886 0           interp->gs->line_width = get_number(width);
887 0           return PDFMAKE_OK;
888             }
889              
890             /* J - Set line cap */
891 0           static pdfmake_err_t op_J(pdfmake_interp_t *interp) {
892 0           pdfmake_obj_t cap = pop_operand(interp);
893 0           int c = (int)get_number(cap);
894 0 0         if (c >= 0 && c <= 2) {
    0          
895 0           interp->gs->line_cap = c;
896             }
897 0           return PDFMAKE_OK;
898             }
899              
900             /* j - Set line join */
901 0           static pdfmake_err_t op_j(pdfmake_interp_t *interp) {
902 0           pdfmake_obj_t join = pop_operand(interp);
903 0           int j = (int)get_number(join);
904 0 0         if (j >= 0 && j <= 2) {
    0          
905 0           interp->gs->line_join = j;
906             }
907 0           return PDFMAKE_OK;
908             }
909              
910             /* M - Set miter limit */
911 0           static pdfmake_err_t op_M(pdfmake_interp_t *interp) {
912 0           pdfmake_obj_t ml = pop_operand(interp);
913 0           interp->gs->miter_limit = get_number(ml);
914 0           return PDFMAKE_OK;
915             }
916              
917             /* d - Set dash pattern */
918 0           static pdfmake_err_t op_d(pdfmake_interp_t *interp) {
919 0           pdfmake_obj_t phase = pop_operand(interp);
920 0           pdfmake_obj_t array = pop_operand(interp);
921             size_t i;
922            
923             /* Free old dash array */
924 0           free(interp->gs->dash_array);
925 0           interp->gs->dash_array = NULL;
926 0           interp->gs->dash_count = 0;
927 0           interp->gs->dash_phase = get_number(phase);
928            
929 0 0         if (array.kind == PDFMAKE_ARRAY) {
930 0           size_t n = pdfmake_array_len(&array);
931 0 0         if (n > 0) {
932 0           interp->gs->dash_array = malloc(n * sizeof(double));
933 0 0         if (interp->gs->dash_array) {
934 0           interp->gs->dash_count = n;
935 0 0         for (i = 0; i < n; i++) {
936 0           pdfmake_obj_t *v = pdfmake_array_get(&array, i);
937 0 0         interp->gs->dash_array[i] = v ? get_number(*v) : 0;
938             }
939             }
940             }
941             }
942            
943 0           return PDFMAKE_OK;
944             }
945              
946             /* i - Set flatness */
947 3           static pdfmake_err_t op_i(pdfmake_interp_t *interp) {
948 3           pdfmake_obj_t flat = pop_operand(interp);
949 3           interp->gs->flatness = get_number(flat);
950 3           return PDFMAKE_OK;
951             }
952              
953             /* gs - Set graphics state from ExtGState dict */
954 14           static pdfmake_err_t op_gs(pdfmake_interp_t *interp) {
955 14           pdfmake_obj_t name = pop_operand(interp);
956             pdfmake_obj_t *dict;
957            
958 14 50         if (name.kind != PDFMAKE_NAME) {
959 0           return PDFMAKE_OK;
960             }
961            
962             /* Look up in ExtGState resources */
963 14           dict = lookup_resource(interp, "ExtGState", name.as.name.id);
964 14 50         if (!dict || dict->kind != PDFMAKE_DICT) {
    50          
965 14           return PDFMAKE_OK;
966             }
967            
968             /* Apply relevant settings from dict */
969             /* This is simplified - full implementation would handle all ExtGState keys */
970            
971 0           return PDFMAKE_OK;
972             }
973              
974             /*============================================================================
975             * Path construction operators
976             *==========================================================================*/
977              
978             /* m - moveto */
979 0           static pdfmake_err_t op_m(pdfmake_interp_t *interp) {
980 0           pdfmake_obj_t y = pop_operand(interp);
981 0           pdfmake_obj_t x = pop_operand(interp);
982            
983 0           pdfmake_path_segment_t seg = {0};
984 0           seg.op = PDFMAKE_PATH_MOVE;
985 0           seg.x1 = get_number(x);
986 0           seg.y1 = get_number(y);
987            
988 0           path_add_segment(interp, seg);
989 0           interp->cur_x = seg.x1;
990 0           interp->cur_y = seg.y1;
991 0           interp->have_cur_point = 1;
992            
993 0           return PDFMAKE_OK;
994             }
995              
996             /* l - lineto */
997 0           static pdfmake_err_t op_l(pdfmake_interp_t *interp) {
998 0           pdfmake_obj_t y = pop_operand(interp);
999 0           pdfmake_obj_t x = pop_operand(interp);
1000            
1001 0           pdfmake_path_segment_t seg = {0};
1002 0           seg.op = PDFMAKE_PATH_LINE;
1003 0           seg.x1 = get_number(x);
1004 0           seg.y1 = get_number(y);
1005            
1006 0           path_add_segment(interp, seg);
1007 0           interp->cur_x = seg.x1;
1008 0           interp->cur_y = seg.y1;
1009            
1010 0           return PDFMAKE_OK;
1011             }
1012              
1013             /* c - curveto (cubic Bézier) */
1014 0           static pdfmake_err_t op_c(pdfmake_interp_t *interp) {
1015 0           pdfmake_obj_t y3 = pop_operand(interp);
1016 0           pdfmake_obj_t x3 = pop_operand(interp);
1017 0           pdfmake_obj_t y2 = pop_operand(interp);
1018 0           pdfmake_obj_t x2 = pop_operand(interp);
1019 0           pdfmake_obj_t y1 = pop_operand(interp);
1020 0           pdfmake_obj_t x1 = pop_operand(interp);
1021            
1022 0           pdfmake_path_segment_t seg = {0};
1023 0           seg.op = PDFMAKE_PATH_CURVE;
1024 0           seg.x1 = get_number(x1);
1025 0           seg.y1 = get_number(y1);
1026 0           seg.x2 = get_number(x2);
1027 0           seg.y2 = get_number(y2);
1028 0           seg.x3 = get_number(x3);
1029 0           seg.y3 = get_number(y3);
1030            
1031 0           path_add_segment(interp, seg);
1032 0           interp->cur_x = seg.x3;
1033 0           interp->cur_y = seg.y3;
1034            
1035 0           return PDFMAKE_OK;
1036             }
1037              
1038             /* v - curveto (initial point replicated) */
1039 0           static pdfmake_err_t op_v(pdfmake_interp_t *interp) {
1040 0           pdfmake_obj_t y3 = pop_operand(interp);
1041 0           pdfmake_obj_t x3 = pop_operand(interp);
1042 0           pdfmake_obj_t y2 = pop_operand(interp);
1043 0           pdfmake_obj_t x2 = pop_operand(interp);
1044            
1045 0           pdfmake_path_segment_t seg = {0};
1046 0           seg.op = PDFMAKE_PATH_CURVE_V;
1047 0           seg.x1 = interp->cur_x; /* First control point = current point */
1048 0           seg.y1 = interp->cur_y;
1049 0           seg.x2 = get_number(x2);
1050 0           seg.y2 = get_number(y2);
1051 0           seg.x3 = get_number(x3);
1052 0           seg.y3 = get_number(y3);
1053            
1054 0           path_add_segment(interp, seg);
1055 0           interp->cur_x = seg.x3;
1056 0           interp->cur_y = seg.y3;
1057            
1058 0           return PDFMAKE_OK;
1059             }
1060              
1061             /* y - curveto (final point replicated) */
1062 0           static pdfmake_err_t op_y(pdfmake_interp_t *interp) {
1063 0           pdfmake_obj_t y3 = pop_operand(interp);
1064 0           pdfmake_obj_t x3 = pop_operand(interp);
1065 0           pdfmake_obj_t y1 = pop_operand(interp);
1066 0           pdfmake_obj_t x1 = pop_operand(interp);
1067            
1068 0           pdfmake_path_segment_t seg = {0};
1069 0           seg.op = PDFMAKE_PATH_CURVE_Y;
1070 0           seg.x1 = get_number(x1);
1071 0           seg.y1 = get_number(y1);
1072 0           seg.x2 = get_number(x3); /* Second control point = endpoint */
1073 0           seg.y2 = get_number(y3);
1074 0           seg.x3 = get_number(x3);
1075 0           seg.y3 = get_number(y3);
1076            
1077 0           path_add_segment(interp, seg);
1078 0           interp->cur_x = seg.x3;
1079 0           interp->cur_y = seg.y3;
1080            
1081 0           return PDFMAKE_OK;
1082             }
1083              
1084             /* h - closepath */
1085 0           static pdfmake_err_t op_h(pdfmake_interp_t *interp) {
1086 0           pdfmake_path_segment_t seg = {0};
1087 0           seg.op = PDFMAKE_PATH_CLOSE;
1088 0           path_add_segment(interp, seg);
1089 0           return PDFMAKE_OK;
1090             }
1091              
1092             /* re - rectangle */
1093 127           static pdfmake_err_t op_re(pdfmake_interp_t *interp) {
1094 127           pdfmake_obj_t height = pop_operand(interp);
1095 127           pdfmake_obj_t width = pop_operand(interp);
1096 127           pdfmake_obj_t y = pop_operand(interp);
1097 127           pdfmake_obj_t x = pop_operand(interp);
1098            
1099 127           pdfmake_path_segment_t seg = {0};
1100 127           seg.op = PDFMAKE_PATH_RECT;
1101 127           seg.x1 = get_number(x);
1102 127           seg.y1 = get_number(y);
1103 127           seg.width = get_number(width);
1104 127           seg.height = get_number(height);
1105            
1106 127           path_add_segment(interp, seg);
1107 127           interp->cur_x = seg.x1;
1108 127           interp->cur_y = seg.y1;
1109 127           interp->have_cur_point = 1;
1110            
1111 127           return PDFMAKE_OK;
1112             }
1113              
1114             /*============================================================================
1115             * Path painting operators
1116             *==========================================================================*/
1117              
1118 127           static void fire_path_callback(pdfmake_interp_t *interp,
1119             int stroke, int fill, int even_odd) {
1120 127 50         if (interp->visitor && interp->visitor->on_path && interp->path_size > 0) {
    50          
    0          
1121 0           interp->visitor->on_path(interp->visitor->ctx,
1122 0           interp->gs,
1123 0           interp->path,
1124             interp->path_size,
1125             stroke, fill, even_odd);
1126             }
1127 127           path_clear(interp);
1128 127           }
1129              
1130             /* S - Stroke path */
1131 0           static pdfmake_err_t op_S(pdfmake_interp_t *interp) {
1132 0           fire_path_callback(interp, 1, 0, 0);
1133 0           return PDFMAKE_OK;
1134             }
1135              
1136             /* s - Close and stroke path */
1137 0           static pdfmake_err_t op_s(pdfmake_interp_t *interp) {
1138 0           op_h(interp);
1139 0           fire_path_callback(interp, 1, 0, 0);
1140 0           return PDFMAKE_OK;
1141             }
1142              
1143             /* f / F - Fill path (nonzero winding) */
1144 127           static pdfmake_err_t op_f(pdfmake_interp_t *interp) {
1145 127           fire_path_callback(interp, 0, 1, 0);
1146 127           return PDFMAKE_OK;
1147             }
1148              
1149             /* f* - Fill path (even-odd rule) */
1150 0           static pdfmake_err_t op_fstar(pdfmake_interp_t *interp) {
1151 0           fire_path_callback(interp, 0, 1, 1);
1152 0           return PDFMAKE_OK;
1153             }
1154              
1155             /* B - Fill and stroke path (nonzero winding) */
1156 0           static pdfmake_err_t op_B(pdfmake_interp_t *interp) {
1157 0           fire_path_callback(interp, 1, 1, 0);
1158 0           return PDFMAKE_OK;
1159             }
1160              
1161             /* B* - Fill and stroke path (even-odd rule) */
1162 0           static pdfmake_err_t op_Bstar(pdfmake_interp_t *interp) {
1163 0           fire_path_callback(interp, 1, 1, 1);
1164 0           return PDFMAKE_OK;
1165             }
1166              
1167             /* b - Close, fill, and stroke path (nonzero winding) */
1168 0           static pdfmake_err_t op_b(pdfmake_interp_t *interp) {
1169 0           op_h(interp);
1170 0           fire_path_callback(interp, 1, 1, 0);
1171 0           return PDFMAKE_OK;
1172             }
1173              
1174             /* b* - Close, fill, and stroke path (even-odd rule) */
1175 0           static pdfmake_err_t op_bstar(pdfmake_interp_t *interp) {
1176 0           op_h(interp);
1177 0           fire_path_callback(interp, 1, 1, 1);
1178 0           return PDFMAKE_OK;
1179             }
1180              
1181             /* n - End path without filling or stroking */
1182 0           static pdfmake_err_t op_n(pdfmake_interp_t *interp) {
1183 0           path_clear(interp);
1184 0           return PDFMAKE_OK;
1185             }
1186              
1187             /*============================================================================
1188             * Clipping path operators
1189             *==========================================================================*/
1190              
1191             /* W - Set clipping path (nonzero) */
1192 0           static pdfmake_err_t op_W(pdfmake_interp_t *interp) {
1193 0           interp->gs->clip_depth++;
1194 0           return PDFMAKE_OK;
1195             }
1196              
1197             /* W* - Set clipping path (even-odd) */
1198 0           static pdfmake_err_t op_Wstar(pdfmake_interp_t *interp) {
1199 0           interp->gs->clip_depth++;
1200 0           return PDFMAKE_OK;
1201             }
1202              
1203             /*============================================================================
1204             * Color operators (simplified)
1205             *==========================================================================*/
1206              
1207             /* g - Set gray fill color */
1208 0           static pdfmake_err_t op_g(pdfmake_interp_t *interp) {
1209 0           pdfmake_obj_t gray = pop_operand(interp);
1210 0           interp->gs->fill_color.space = PDFMAKE_CS_GRAY;
1211 0           interp->gs->fill_color.components[0] = get_number(gray);
1212 0           interp->gs->fill_color.n_components = 1;
1213 0           return PDFMAKE_OK;
1214             }
1215              
1216             /* G - Set gray stroke color */
1217 0           static pdfmake_err_t op_G(pdfmake_interp_t *interp) {
1218 0           pdfmake_obj_t gray = pop_operand(interp);
1219 0           interp->gs->stroke_color.space = PDFMAKE_CS_GRAY;
1220 0           interp->gs->stroke_color.components[0] = get_number(gray);
1221 0           interp->gs->stroke_color.n_components = 1;
1222 0           return PDFMAKE_OK;
1223             }
1224              
1225             /* rg - Set RGB fill color */
1226 115           static pdfmake_err_t op_rg(pdfmake_interp_t *interp) {
1227 115           pdfmake_obj_t b = pop_operand(interp);
1228 115           pdfmake_obj_t g = pop_operand(interp);
1229 115           pdfmake_obj_t r = pop_operand(interp);
1230 115           interp->gs->fill_color.space = PDFMAKE_CS_RGB;
1231 115           interp->gs->fill_color.components[0] = get_number(r);
1232 115           interp->gs->fill_color.components[1] = get_number(g);
1233 115           interp->gs->fill_color.components[2] = get_number(b);
1234 115           interp->gs->fill_color.n_components = 3;
1235 115           return PDFMAKE_OK;
1236             }
1237              
1238             /* RG - Set RGB stroke color */
1239 0           static pdfmake_err_t op_RG(pdfmake_interp_t *interp) {
1240 0           pdfmake_obj_t b = pop_operand(interp);
1241 0           pdfmake_obj_t g = pop_operand(interp);
1242 0           pdfmake_obj_t r = pop_operand(interp);
1243 0           interp->gs->stroke_color.space = PDFMAKE_CS_RGB;
1244 0           interp->gs->stroke_color.components[0] = get_number(r);
1245 0           interp->gs->stroke_color.components[1] = get_number(g);
1246 0           interp->gs->stroke_color.components[2] = get_number(b);
1247 0           interp->gs->stroke_color.n_components = 3;
1248 0           return PDFMAKE_OK;
1249             }
1250              
1251             /* k - Set CMYK fill color */
1252 0           static pdfmake_err_t op_k(pdfmake_interp_t *interp) {
1253 0           pdfmake_obj_t kk = pop_operand(interp);
1254 0           pdfmake_obj_t y = pop_operand(interp);
1255 0           pdfmake_obj_t m = pop_operand(interp);
1256 0           pdfmake_obj_t c = pop_operand(interp);
1257 0           interp->gs->fill_color.space = PDFMAKE_CS_CMYK;
1258 0           interp->gs->fill_color.components[0] = get_number(c);
1259 0           interp->gs->fill_color.components[1] = get_number(m);
1260 0           interp->gs->fill_color.components[2] = get_number(y);
1261 0           interp->gs->fill_color.components[3] = get_number(kk);
1262 0           interp->gs->fill_color.n_components = 4;
1263 0           return PDFMAKE_OK;
1264             }
1265              
1266             /* K - Set CMYK stroke color */
1267 0           static pdfmake_err_t op_K(pdfmake_interp_t *interp) {
1268 0           pdfmake_obj_t kk = pop_operand(interp);
1269 0           pdfmake_obj_t y = pop_operand(interp);
1270 0           pdfmake_obj_t m = pop_operand(interp);
1271 0           pdfmake_obj_t c = pop_operand(interp);
1272 0           interp->gs->stroke_color.space = PDFMAKE_CS_CMYK;
1273 0           interp->gs->stroke_color.components[0] = get_number(c);
1274 0           interp->gs->stroke_color.components[1] = get_number(m);
1275 0           interp->gs->stroke_color.components[2] = get_number(y);
1276 0           interp->gs->stroke_color.components[3] = get_number(kk);
1277 0           interp->gs->stroke_color.n_components = 4;
1278 0           return PDFMAKE_OK;
1279             }
1280              
1281             /*============================================================================
1282             * XObject operators
1283             *==========================================================================*/
1284              
1285             /* Look up XObject in resources, returning (obj, num, gen).
1286             * If the resource is stored by reference, num/gen are filled; otherwise 0. */
1287 21           static pdfmake_obj_t *lookup_xobject_with_ref(pdfmake_interp_t *interp,
1288             uint32_t name_id,
1289             uint32_t *out_num,
1290             uint16_t *out_gen) {
1291             uint32_t cat_id;
1292             pdfmake_obj_t *cat;
1293             pdfmake_obj_t *entry;
1294              
1295 21           *out_num = 0;
1296 21           *out_gen = 0;
1297 21 50         if (!interp->resources || interp->resources->kind != PDFMAKE_DICT)
    50          
1298 0           return NULL;
1299              
1300 21           cat_id = pdfmake_arena_intern_name(interp->arena, "XObject", 7);
1301 21           cat = pdfmake_dict_get(interp->resources, cat_id);
1302 21 50         if (!cat) return NULL;
1303             /* Follow indirect ref */
1304 21 50         if (cat->kind == PDFMAKE_REF && interp->reader) {
    0          
1305 0           pdfmake_reader_t *rd = (pdfmake_reader_t *)interp->reader;
1306 0 0         if (rd->parser) {
1307 0           cat = pdfmake_parser_resolve(rd->parser, cat->as.ref);
1308             }
1309             }
1310 21 50         if (!cat || cat->kind != PDFMAKE_DICT) return NULL;
    50          
1311              
1312 21           entry = pdfmake_dict_get(cat, name_id);
1313 21 50         if (!entry) return NULL;
1314 21 50         if (entry->kind == PDFMAKE_REF) {
1315 21           *out_num = entry->as.ref.num;
1316 21           *out_gen = entry->as.ref.gen;
1317             /* Resolve to concrete object via the reader's parser */
1318 21 50         if (interp->reader) {
1319 21           pdfmake_reader_t *rd = (pdfmake_reader_t *)interp->reader;
1320 21 50         if (rd->parser)
1321 21           return pdfmake_parser_resolve(rd->parser, entry->as.ref);
1322             }
1323 0           return NULL;
1324             }
1325 0           return entry;
1326             }
1327              
1328             /* Interpret a Form XObject's content stream with the form's Matrix + Resources
1329             * pushed onto the current state. Depth-guarded to prevent pathological cycles. */
1330 4           static pdfmake_err_t interpret_form(pdfmake_interp_t *interp,
1331             pdfmake_obj_t *form,
1332             uint32_t form_num,
1333             uint16_t form_gen) {
1334             pdfmake_buf_t content;
1335             pdfmake_err_t err;
1336             pdfmake_obj_t *saved_resources;
1337             int saved_in_text;
1338             pdfmake_obj_t stream_dict;
1339             uint32_t matrix_k;
1340             pdfmake_obj_t *mat;
1341             uint32_t res_k;
1342             pdfmake_obj_t *form_res;
1343             int i;
1344              
1345 4 50         if (!form || form->kind != PDFMAKE_STREAM) return PDFMAKE_OK;
    50          
1346              
1347 4 50         if (interp->form_depth >= 32) {
1348             /* Cycle guard — spec doesn't allow cycles; silently stop. */
1349 0           return PDFMAKE_OK;
1350             }
1351              
1352             /* Decode the form's content stream (decrypt + FlateDecode). */
1353 4 50         if (pdfmake_buf_init(&content) != PDFMAKE_OK) return PDFMAKE_ENOMEM;
1354              
1355 4           err = PDFMAKE_OK;
1356 4 50         if (interp->reader && form_num > 0) {
    50          
1357 4           err = pdfmake_reader_resolve_stream(
1358 4           (pdfmake_reader_t *)interp->reader, form_num, form_gen, &content);
1359             } else {
1360             /* No reader attached: use parser-level decode without decryption.
1361             * Will likely fail for encrypted PDFs but works for simple ones. */
1362 0           err = PDFMAKE_EINVAL;
1363             }
1364              
1365 4 50         if (err != PDFMAKE_OK || pdfmake_buf_len(&content) == 0) {
    50          
1366 0           pdfmake_buf_free(&content);
1367 0           return PDFMAKE_OK;
1368             }
1369              
1370             /* Save state: push gstate, stash current resources. */
1371 4           gstate_push(interp);
1372 4           saved_resources = interp->resources;
1373 4           saved_in_text = interp->in_text_object;
1374 4           interp->in_text_object = 0;
1375              
1376             /* Apply form's /Matrix to CTM (default = identity). */
1377 4           stream_dict.kind = PDFMAKE_DICT;
1378 4           stream_dict.as.dict = form->as.stream->dict;
1379              
1380 4           matrix_k = pdfmake_arena_intern_name(interp->arena, "Matrix", 6);
1381 4           mat = pdfmake_dict_get(&stream_dict, matrix_k);
1382 4 50         if (mat && mat->kind == PDFMAKE_ARRAY && pdfmake_array_len(mat) == 6) {
    50          
    50          
1383             double m[6];
1384 28 100         for (i = 0; i < 6; i++) {
1385 24           pdfmake_obj_t *v = pdfmake_array_get(mat, i);
1386 24 50         if (!v) { m[i] = 0; continue; }
1387 24 50         if (v->kind == PDFMAKE_INT) m[i] = (double)v->as.i;
1388 0 0         else if (v->kind == PDFMAKE_REAL) m[i] = v->as.r;
1389 0           else m[i] = 0;
1390             }
1391             /* CTM = form_matrix × CTM */
1392 4           pdfmake_matrix_multiply(interp->gs->ctm, m, interp->gs->ctm);
1393             }
1394              
1395             /* Swap resources: form's /Resources (if any) overlays. If the form has
1396             * no /Resources, keep the outer page's — matches §7.8.3 "If the form
1397             * XObject does not have its own Resources dictionary, the form uses
1398             * the page's". */
1399 4           res_k = pdfmake_arena_intern_name(interp->arena, "Resources", 9);
1400 4           form_res = pdfmake_dict_get(&stream_dict, res_k);
1401 4 50         if (form_res && form_res->kind == PDFMAKE_REF && interp->reader) {
    100          
    50          
1402 2           pdfmake_reader_t *rd = (pdfmake_reader_t *)interp->reader;
1403 2 50         if (rd->parser)
1404 2           form_res = pdfmake_parser_resolve(rd->parser, form_res->as.ref);
1405             }
1406 4 50         if (form_res && form_res->kind == PDFMAKE_DICT) {
    50          
1407 4           interp->resources = form_res;
1408             }
1409              
1410             /* Recurse with the already-initialized interpreter */
1411 4           interp->form_depth++;
1412 4           (void)parse_content_stream(interp,
1413             pdfmake_buf_data(&content),
1414             pdfmake_buf_len(&content));
1415 4           interp->form_depth--;
1416              
1417             /* Restore */
1418 4           interp->in_text_object = saved_in_text;
1419 4           interp->resources = saved_resources;
1420 4           gstate_pop(interp);
1421              
1422 4           pdfmake_buf_free(&content);
1423 4           return PDFMAKE_OK;
1424             }
1425              
1426             /* Do - Paint XObject */
1427 21           static pdfmake_err_t op_Do(pdfmake_interp_t *interp) {
1428 21           pdfmake_obj_t name = pop_operand(interp);
1429             uint32_t xobj_num;
1430             uint16_t xobj_gen;
1431             pdfmake_obj_t *xobj;
1432              
1433 21 50         if (name.kind != PDFMAKE_NAME) {
1434 0           return PDFMAKE_OK;
1435             }
1436              
1437 21           xobj_num = 0;
1438 21           xobj_gen = 0;
1439 21           xobj = lookup_xobject_with_ref(interp, name.as.name.id,
1440             &xobj_num, &xobj_gen);
1441 21 50         if (!xobj) {
1442 0           return PDFMAKE_OK;
1443             }
1444              
1445             /* Check subtype */
1446 21 50         if (xobj->kind == PDFMAKE_STREAM) {
1447             /* Get /Subtype from stream dict */
1448 21           uint32_t subtype_id = pdfmake_arena_intern_name(interp->arena, "Subtype", 7);
1449             pdfmake_obj_t stream_dict;
1450             pdfmake_obj_t *subtype;
1451 21           stream_dict.kind = PDFMAKE_DICT;
1452 21           stream_dict.as.dict = xobj->as.stream->dict;
1453 21           subtype = pdfmake_dict_get(&stream_dict, subtype_id);
1454              
1455 21 50         if (subtype && subtype->kind == PDFMAKE_NAME) {
    50          
1456 21           const char *subtype_str = pdfmake_arena_name_bytes(interp->arena,
1457             subtype->as.name.id);
1458 21 50         if (subtype_str) {
1459 21 100         if (strcmp(subtype_str, "Image") == 0) {
1460             /* Image XObject */
1461 17 50         if (interp->visitor && interp->visitor->on_image) {
    50          
1462 0           interp->visitor->on_image(interp->visitor->ctx,
1463 0           interp->gs,
1464             name.as.name.id, xobj);
1465             }
1466 4 50         } else if (strcmp(subtype_str, "Form") == 0) {
1467             /* Form XObject - recursive interpretation */
1468 4 50         if (interp->visitor && interp->visitor->on_form_begin) {
    50          
1469 0           interp->visitor->on_form_begin(interp->visitor->ctx,
1470 0           interp->gs,
1471             name.as.name.id, xobj);
1472             }
1473              
1474 4           interpret_form(interp, xobj, xobj_num, xobj_gen);
1475              
1476 4 50         if (interp->visitor && interp->visitor->on_form_end) {
    50          
1477 0           interp->visitor->on_form_end(interp->visitor->ctx,
1478 0           interp->gs,
1479             name.as.name.id);
1480             }
1481             }
1482             }
1483             }
1484             }
1485              
1486 21           return PDFMAKE_OK;
1487             }
1488              
1489             /*============================================================================
1490             * Marked content operators
1491             *==========================================================================*/
1492              
1493 3           static int mc_push(pdfmake_interp_t *interp, uint32_t tag) {
1494 3 50         if (interp->mc_depth >= interp->mc_cap) {
1495 0           size_t new_cap = interp->mc_cap * 2;
1496 0           uint32_t *new_stack = realloc(interp->mc_stack,
1497             new_cap * sizeof(uint32_t));
1498 0 0         if (!new_stack) return 0;
1499 0           interp->mc_stack = new_stack;
1500 0           interp->mc_cap = new_cap;
1501             }
1502 3           interp->mc_stack[interp->mc_depth++] = tag;
1503 3           return 1;
1504             }
1505              
1506             /* BMC - Begin marked content */
1507 0           static pdfmake_err_t op_BMC(pdfmake_interp_t *interp) {
1508 0           pdfmake_obj_t tag = pop_operand(interp);
1509            
1510 0           uint32_t tag_id = 0;
1511 0 0         if (tag.kind == PDFMAKE_NAME) {
1512 0           tag_id = tag.as.name.id;
1513             }
1514            
1515 0           mc_push(interp, tag_id);
1516            
1517 0 0         if (interp->visitor && interp->visitor->on_marked_content_begin) {
    0          
1518 0           interp->visitor->on_marked_content_begin(interp->visitor->ctx,
1519 0           interp->gs, tag_id, NULL);
1520             }
1521            
1522 0           return PDFMAKE_OK;
1523             }
1524              
1525             /* BDC - Begin marked content with properties */
1526 3           static pdfmake_err_t op_BDC(pdfmake_interp_t *interp) {
1527 3           pdfmake_obj_t props = pop_operand(interp);
1528 3           pdfmake_obj_t tag = pop_operand(interp);
1529             uint32_t tag_id;
1530             pdfmake_obj_t *props_ptr;
1531            
1532 3           tag_id = 0;
1533 3 50         if (tag.kind == PDFMAKE_NAME) {
1534 3           tag_id = tag.as.name.id;
1535             }
1536            
1537 3           props_ptr = NULL;
1538 3 50         if (props.kind == PDFMAKE_DICT) {
1539 3           props_ptr = &props;
1540 0 0         } else if (props.kind == PDFMAKE_NAME) {
1541             /* Look up in Properties resource */
1542 0           props_ptr = lookup_resource(interp, "Properties", props.as.name.id);
1543             }
1544            
1545 3           mc_push(interp, tag_id);
1546            
1547 3 50         if (interp->visitor && interp->visitor->on_marked_content_begin) {
    50          
1548 3           interp->visitor->on_marked_content_begin(interp->visitor->ctx,
1549 3           interp->gs, tag_id, props_ptr);
1550             }
1551            
1552 3           return PDFMAKE_OK;
1553             }
1554              
1555             /* EMC - End marked content */
1556 3           static pdfmake_err_t op_EMC(pdfmake_interp_t *interp) {
1557 3 50         if (interp->mc_depth > 0) {
1558 3           interp->mc_depth--;
1559             }
1560            
1561 3 50         if (interp->visitor && interp->visitor->on_marked_content_end) {
    50          
1562 3           interp->visitor->on_marked_content_end(interp->visitor->ctx, interp->gs);
1563             }
1564            
1565 3           return PDFMAKE_OK;
1566             }
1567              
1568             /* MP - Marked content point */
1569 0           static pdfmake_err_t op_MP(pdfmake_interp_t *interp) {
1570 0           pop_operand(interp); /* tag */
1571 0           return PDFMAKE_OK;
1572             }
1573              
1574             /* DP - Marked content point with properties */
1575 0           static pdfmake_err_t op_DP(pdfmake_interp_t *interp) {
1576 0           pop_operand(interp); /* properties */
1577 0           pop_operand(interp); /* tag */
1578 0           return PDFMAKE_OK;
1579             }
1580              
1581             /*============================================================================
1582             * Compatibility operators
1583             *==========================================================================*/
1584              
1585             /* BX - Begin compatibility section */
1586 0           static pdfmake_err_t op_BX(pdfmake_interp_t *interp) {
1587             (void)interp;
1588 0           return PDFMAKE_OK;
1589             }
1590              
1591             /* EX - End compatibility section */
1592 0           static pdfmake_err_t op_EX(pdfmake_interp_t *interp) {
1593             (void)interp;
1594 0           return PDFMAKE_OK;
1595             }
1596              
1597             /*============================================================================
1598             * Color space operators (simplified - just consume operands)
1599             *==========================================================================*/
1600              
1601             /* cs - Set fill color space */
1602 0           static pdfmake_err_t op_cs(pdfmake_interp_t *interp) {
1603 0           pop_operand(interp);
1604 0           return PDFMAKE_OK;
1605             }
1606              
1607             /* CS - Set stroke color space */
1608 0           static pdfmake_err_t op_CS(pdfmake_interp_t *interp) {
1609 0           pop_operand(interp);
1610 0           return PDFMAKE_OK;
1611             }
1612              
1613             /* sc/scn - Set fill color (arbitrary color space) */
1614 0           static pdfmake_err_t op_sc(pdfmake_interp_t *interp) {
1615             /* Just consume operands for now */
1616 0           clear_operands(interp);
1617 0           return PDFMAKE_OK;
1618             }
1619              
1620             /* SC/SCN - Set stroke color (arbitrary color space) */
1621 0           static pdfmake_err_t op_SC(pdfmake_interp_t *interp) {
1622 0           clear_operands(interp);
1623 0           return PDFMAKE_OK;
1624             }
1625              
1626             /*============================================================================
1627             * Rendering intent
1628             *==========================================================================*/
1629              
1630             /* ri - Set rendering intent */
1631 0           static pdfmake_err_t op_ri(pdfmake_interp_t *interp) {
1632 0           pdfmake_obj_t intent = pop_operand(interp);
1633 0 0         if (intent.kind == PDFMAKE_NAME) {
1634 0           interp->gs->rendering_intent = intent.as.name.id;
1635             }
1636 0           return PDFMAKE_OK;
1637             }
1638              
1639             /*============================================================================
1640             * Inline image operators (simplified)
1641             *==========================================================================*/
1642              
1643             /* BI - Begin inline image */
1644 0           static pdfmake_err_t op_BI(pdfmake_interp_t *interp) {
1645             /* Inline images are handled specially during tokenization */
1646             (void)interp;
1647 0           return PDFMAKE_OK;
1648             }
1649              
1650             /*============================================================================
1651             * Shading operator
1652             *==========================================================================*/
1653              
1654             /* sh - Paint shading */
1655 0           static pdfmake_err_t op_sh(pdfmake_interp_t *interp) {
1656 0           pop_operand(interp);
1657 0           return PDFMAKE_OK;
1658             }
1659              
1660             /*============================================================================
1661             * Operator dispatch table
1662             *==========================================================================*/
1663              
1664             typedef pdfmake_err_t (*op_handler_t)(pdfmake_interp_t *interp);
1665              
1666             typedef struct {
1667             const char *name;
1668             op_handler_t handler;
1669             } op_entry_t;
1670              
1671             static const op_entry_t op_table[] = {
1672             /* Graphics state */
1673             {"q", op_q},
1674             {"Q", op_Q},
1675             {"cm", op_cm},
1676             {"w", op_w},
1677             {"J", op_J},
1678             {"j", op_j},
1679             {"M", op_M},
1680             {"d", op_d},
1681             {"ri", op_ri},
1682             {"i", op_i},
1683             {"gs", op_gs},
1684            
1685             /* Text state */
1686             {"Tc", op_Tc},
1687             {"Tw", op_Tw},
1688             {"Tz", op_Tz},
1689             {"TL", op_TL},
1690             {"Tf", op_Tf},
1691             {"Tr", op_Tr},
1692             {"Ts", op_Ts},
1693            
1694             /* Text positioning */
1695             {"Td", op_Td},
1696             {"TD", op_TD},
1697             {"Tm", op_Tm},
1698             {"T*", op_Tstar},
1699            
1700             /* Text showing */
1701             {"Tj", op_Tj},
1702             {"TJ", op_TJ},
1703             {"'", op_quote},
1704             {"\"", op_dquote},
1705            
1706             /* Text object */
1707             {"BT", op_BT},
1708             {"ET", op_ET},
1709            
1710             /* Path construction */
1711             {"m", op_m},
1712             {"l", op_l},
1713             {"c", op_c},
1714             {"v", op_v},
1715             {"y", op_y},
1716             {"h", op_h},
1717             {"re", op_re},
1718            
1719             /* Path painting */
1720             {"S", op_S},
1721             {"s", op_s},
1722             {"f", op_f},
1723             {"F", op_f}, /* F is same as f */
1724             {"f*", op_fstar},
1725             {"B", op_B},
1726             {"B*", op_Bstar},
1727             {"b", op_b},
1728             {"b*", op_bstar},
1729             {"n", op_n},
1730            
1731             /* Clipping */
1732             {"W", op_W},
1733             {"W*", op_Wstar},
1734            
1735             /* Color */
1736             {"g", op_g},
1737             {"G", op_G},
1738             {"rg", op_rg},
1739             {"RG", op_RG},
1740             {"k", op_k},
1741             {"K", op_K},
1742             {"cs", op_cs},
1743             {"CS", op_CS},
1744             {"sc", op_sc},
1745             {"SC", op_SC},
1746             {"scn", op_sc},
1747             {"SCN", op_SC},
1748            
1749             /* XObject */
1750             {"Do", op_Do},
1751            
1752             /* Marked content */
1753             {"BMC", op_BMC},
1754             {"BDC", op_BDC},
1755             {"EMC", op_EMC},
1756             {"MP", op_MP},
1757             {"DP", op_DP},
1758            
1759             /* Compatibility */
1760             {"BX", op_BX},
1761             {"EX", op_EX},
1762            
1763             /* Inline image */
1764             {"BI", op_BI},
1765            
1766             /* Shading */
1767             {"sh", op_sh},
1768            
1769             {NULL, NULL}
1770             };
1771              
1772 3438           static op_handler_t find_operator(const char *name, size_t len) {
1773             const op_entry_t *e;
1774 75827 50         for (e = op_table; e->name; e++) {
1775 75827 100         if (strlen(e->name) == len && memcmp(e->name, name, len) == 0) {
    100          
1776 3438           return e->handler;
1777             }
1778             }
1779 0           return NULL;
1780             }
1781              
1782             /*============================================================================
1783             * Content stream tokenizer (simplified)
1784             *==========================================================================*/
1785              
1786             /* Build a pdfmake_obj_t from a token (numbers, names, strings, booleans,
1787             * null). Returns 1 if populated, 0 if the token isn't a primitive value
1788             * (arrays/dicts are handled inline by the callers because they recurse via
1789             * the tokenizer). */
1790 3           static int token_to_primitive(const uint8_t *bytes,
1791             pdfmake_tok_t t,
1792             pdfmake_obj_t *out)
1793             {
1794 3           switch (t.kind) {
1795 3           case PDFMAKE_TOK_INT:
1796 3           *out = pdfmake_int(t.payload.int_val);
1797 3           return 1;
1798 0           case PDFMAKE_TOK_REAL:
1799 0           *out = pdfmake_real(t.payload.real_val);
1800 0           return 1;
1801 0           case PDFMAKE_TOK_LSTR:
1802 0           out->kind = PDFMAKE_STR;
1803 0           out->as.str.bytes = bytes + t.offset + 1;
1804 0           out->as.str.len = t.length - 2;
1805 0           out->as.str.hex = 0;
1806 0           return 1;
1807 0           case PDFMAKE_TOK_HSTR:
1808 0           out->kind = PDFMAKE_STR;
1809 0           out->as.str.bytes = bytes + t.offset + 1;
1810 0           out->as.str.len = t.length - 2;
1811 0           out->as.str.hex = 1;
1812 0           return 1;
1813 0           case PDFMAKE_TOK_KW_TRUE: *out = pdfmake_bool(1); return 1;
1814 0           case PDFMAKE_TOK_KW_FALSE: *out = pdfmake_bool(0); return 1;
1815 0           case PDFMAKE_TOK_KW_NULL: *out = pdfmake_null(); return 1;
1816 0           default: return 0;
1817             }
1818             }
1819              
1820             /* Parse an inline dictionary starting just after the '<<' token.
1821             * Handles nested dicts and arrays so BDC property dicts like
1822             * `/P << /MCID 7 /Lang (en) >>` are usable by visitors. */
1823             static pdfmake_obj_t parse_inline_dict(pdfmake_interp_t *interp,
1824             const uint8_t *bytes,
1825             pdfmake_tokenizer_t *tok);
1826              
1827 0           static pdfmake_obj_t parse_inline_array(pdfmake_interp_t *interp,
1828             const uint8_t *bytes,
1829             pdfmake_tokenizer_t *tok)
1830             {
1831             pdfmake_obj_t arr;
1832             pdfmake_tok_t t;
1833             pdfmake_obj_t v;
1834              
1835 0           arr = pdfmake_array_new(interp->arena);
1836             while (1) {
1837 0           t = pdfmake_tok_next_significant(tok);
1838 0 0         if (t.kind == PDFMAKE_TOK_EOF || t.kind == PDFMAKE_TOK_ARR_CLOSE) break;
    0          
1839 0 0         if (t.kind == PDFMAKE_TOK_ARR_OPEN) {
1840 0           v = parse_inline_array(interp, bytes, tok);
1841 0 0         } else if (t.kind == PDFMAKE_TOK_DICT_OPEN) {
1842 0           v = parse_inline_dict(interp, bytes, tok);
1843 0 0         } else if (t.kind == PDFMAKE_TOK_NAME) {
1844 0           const char *name_str = (const char *)(bytes + t.offset + 1);
1845 0           uint32_t id = pdfmake_arena_intern_name(
1846 0           interp->arena, name_str, t.length - 1);
1847 0           v.kind = PDFMAKE_NAME;
1848 0           v.as.name.id = id;
1849 0 0         } else if (!token_to_primitive(bytes, t, &v)) {
1850 0           continue;
1851             }
1852 0           pdfmake_array_push(interp->arena, &arr, v);
1853             }
1854 0           return arr;
1855             }
1856              
1857 3           static pdfmake_obj_t parse_inline_dict(pdfmake_interp_t *interp,
1858             const uint8_t *bytes,
1859             pdfmake_tokenizer_t *tok)
1860             {
1861             pdfmake_obj_t dict;
1862             pdfmake_tok_t kt;
1863             pdfmake_tok_t vt;
1864             const char *name_str;
1865             uint32_t key;
1866             pdfmake_obj_t v;
1867              
1868 3           dict = pdfmake_dict_new(interp->arena);
1869             while (1) {
1870 6           kt = pdfmake_tok_next_significant(tok);
1871 6 50         if (kt.kind == PDFMAKE_TOK_EOF || kt.kind == PDFMAKE_TOK_DICT_CLOSE) break;
    100          
1872 3 50         if (kt.kind != PDFMAKE_TOK_NAME) continue; /* malformed — skip */
1873 3           name_str = (const char *)(bytes + kt.offset + 1);
1874 3           key = pdfmake_arena_intern_name(
1875 3           interp->arena, name_str, kt.length - 1);
1876              
1877 3           vt = pdfmake_tok_next_significant(tok);
1878 3 50         if (vt.kind == PDFMAKE_TOK_EOF || vt.kind == PDFMAKE_TOK_DICT_CLOSE) break;
    50          
1879 3 50         if (vt.kind == PDFMAKE_TOK_DICT_OPEN) {
1880 0           v = parse_inline_dict(interp, bytes, tok);
1881 3 50         } else if (vt.kind == PDFMAKE_TOK_ARR_OPEN) {
1882 0           v = parse_inline_array(interp, bytes, tok);
1883 3 50         } else if (vt.kind == PDFMAKE_TOK_NAME) {
1884 0           const char *vstr = (const char *)(bytes + vt.offset + 1);
1885 0           uint32_t vid = pdfmake_arena_intern_name(
1886 0           interp->arena, vstr, vt.length - 1);
1887 0           v.kind = PDFMAKE_NAME;
1888 0           v.as.name.id = vid;
1889 3 50         } else if (!token_to_primitive(bytes, vt, &v)) {
1890 0           continue; /* unknown — drop the key/value pair */
1891             }
1892 3           pdfmake_dict_set(interp->arena, &dict, key, v);
1893             }
1894 3           return dict;
1895             }
1896              
1897             /* Simple content stream parser using the existing tokenizer */
1898 64           static pdfmake_err_t parse_content_stream(pdfmake_interp_t *interp,
1899             const uint8_t *bytes,
1900             size_t len) {
1901             pdfmake_tokenizer_t tok;
1902 64           pdfmake_tokenizer_init(&tok, bytes, len);
1903            
1904 9510           while (1) {
1905 9574           pdfmake_tok_t t = pdfmake_tok_next_significant(&tok);
1906            
1907 9574 100         if (t.kind == PDFMAKE_TOK_EOF) {
1908 64           break;
1909             }
1910            
1911 9510           switch (t.kind) {
1912 2517           case PDFMAKE_TOK_INT: {
1913 2517           pdfmake_obj_t obj = pdfmake_int(t.payload.int_val);
1914 2517 50         if (!push_operand(interp, obj)) {
1915 0           set_error(interp, PDFMAKE_ENOMEM, t.offset, "Operand stack overflow");
1916 0           return PDFMAKE_ENOMEM;
1917             }
1918 2517           break;
1919             }
1920            
1921 2302           case PDFMAKE_TOK_REAL: {
1922 2302           pdfmake_obj_t obj = pdfmake_real(t.payload.real_val);
1923 2302 50         if (!push_operand(interp, obj)) {
1924 0           set_error(interp, PDFMAKE_ENOMEM, t.offset, "Operand stack overflow");
1925 0           return PDFMAKE_ENOMEM;
1926             }
1927 2302           break;
1928             }
1929            
1930 404           case PDFMAKE_TOK_NAME: {
1931             /* Intern the name */
1932 404           const char *name_str = (const char *)(bytes + t.offset + 1); /* Skip / */
1933 404           uint32_t name_id = pdfmake_arena_intern_name(interp->arena,
1934 404           name_str, t.length - 1);
1935             pdfmake_obj_t obj;
1936 404           obj.kind = PDFMAKE_NAME;
1937 404           obj.as.name.id = name_id;
1938 404 50         if (!push_operand(interp, obj)) {
1939 0           set_error(interp, PDFMAKE_ENOMEM, t.offset, "Operand stack overflow");
1940 0           return PDFMAKE_ENOMEM;
1941             }
1942 404           break;
1943             }
1944            
1945 436           case PDFMAKE_TOK_LSTR: {
1946             pdfmake_obj_t obj;
1947 436           obj.kind = PDFMAKE_STR;
1948 436           obj.as.str.bytes = bytes + t.offset + 1; /* Skip ( */
1949 436           obj.as.str.len = t.length - 2; /* Exclude parens */
1950 436           obj.as.str.hex = 0;
1951 436 50         if (!push_operand(interp, obj)) {
1952 0           set_error(interp, PDFMAKE_ENOMEM, t.offset, "Operand stack overflow");
1953 0           return PDFMAKE_ENOMEM;
1954             }
1955 436           break;
1956             }
1957            
1958 1           case PDFMAKE_TOK_HSTR: {
1959             pdfmake_obj_t obj;
1960 1           obj.kind = PDFMAKE_STR;
1961 1           obj.as.str.bytes = bytes + t.offset + 1; /* Skip < */
1962 1           obj.as.str.len = t.length - 2; /* Exclude angle brackets */
1963 1           obj.as.str.hex = 1;
1964 1 50         if (!push_operand(interp, obj)) {
1965 0           set_error(interp, PDFMAKE_ENOMEM, t.offset, "Operand stack overflow");
1966 0           return PDFMAKE_ENOMEM;
1967             }
1968 1           break;
1969             }
1970            
1971 409           case PDFMAKE_TOK_ARR_OPEN: {
1972             /* Parse array - simple recursive handling */
1973             /* For now, use a temporary array */
1974 409           pdfmake_obj_t arr = pdfmake_array_new(interp->arena);
1975 409           int depth = 1;
1976 6503 100         while (depth > 0) {
1977 6094           pdfmake_tok_t at = pdfmake_tok_next_significant(&tok);
1978 6094 50         if (at.kind == PDFMAKE_TOK_EOF) {
1979 0           break;
1980             }
1981 6094 50         if (at.kind == PDFMAKE_TOK_ARR_OPEN) {
1982 0           depth++;
1983             /* Nested arrays not fully handled */
1984 6094 100         } else if (at.kind == PDFMAKE_TOK_ARR_CLOSE) {
1985 409           depth--;
1986 5685 100         } else if (at.kind == PDFMAKE_TOK_INT) {
1987 150           pdfmake_obj_t elem = pdfmake_int(at.payload.int_val);
1988 150           pdfmake_array_push(interp->arena, &arr, elem);
1989 5535 100         } else if (at.kind == PDFMAKE_TOK_REAL) {
1990 2488           pdfmake_obj_t elem = pdfmake_real(at.payload.real_val);
1991 2488           pdfmake_array_push(interp->arena, &arr, elem);
1992 3047 100         } else if (at.kind == PDFMAKE_TOK_LSTR) {
1993             pdfmake_obj_t elem;
1994 3042           elem.kind = PDFMAKE_STR;
1995 3042           elem.as.str.bytes = bytes + at.offset + 1;
1996 3042           elem.as.str.len = at.length - 2;
1997 3042           elem.as.str.hex = 0;
1998 3042           pdfmake_array_push(interp->arena, &arr, elem);
1999 5 50         } else if (at.kind == PDFMAKE_TOK_HSTR) {
2000             pdfmake_obj_t elem;
2001 5           elem.kind = PDFMAKE_STR;
2002 5           elem.as.str.bytes = bytes + at.offset + 1;
2003 5           elem.as.str.len = at.length - 2;
2004 5           elem.as.str.hex = 1;
2005 5           pdfmake_array_push(interp->arena, &arr, elem);
2006             }
2007             }
2008 409 50         if (!push_operand(interp, arr)) {
2009 0           set_error(interp, PDFMAKE_ENOMEM, t.offset, "Operand stack overflow");
2010 0           return PDFMAKE_ENOMEM;
2011             }
2012 409           break;
2013             }
2014            
2015 3           case PDFMAKE_TOK_DICT_OPEN: {
2016 3           pdfmake_obj_t dict = parse_inline_dict(interp, bytes, &tok);
2017 3 50         if (!push_operand(interp, dict)) {
2018 0           set_error(interp, PDFMAKE_ENOMEM, t.offset, "Operand stack overflow");
2019 0           return PDFMAKE_ENOMEM;
2020             }
2021 3           break;
2022             }
2023            
2024 0           case PDFMAKE_TOK_KW_TRUE: {
2025 0           pdfmake_obj_t obj = pdfmake_bool(1);
2026 0 0         if (!push_operand(interp, obj)) {
2027 0           set_error(interp, PDFMAKE_ENOMEM, t.offset, "Operand stack overflow");
2028 0           return PDFMAKE_ENOMEM;
2029             }
2030 0           break;
2031             }
2032            
2033 0           case PDFMAKE_TOK_KW_FALSE: {
2034 0           pdfmake_obj_t obj = pdfmake_bool(0);
2035 0 0         if (!push_operand(interp, obj)) {
2036 0           set_error(interp, PDFMAKE_ENOMEM, t.offset, "Operand stack overflow");
2037 0           return PDFMAKE_ENOMEM;
2038             }
2039 0           break;
2040             }
2041            
2042 0           case PDFMAKE_TOK_KW_NULL: {
2043 0           pdfmake_obj_t obj = pdfmake_null();
2044 0 0         if (!push_operand(interp, obj)) {
2045 0           set_error(interp, PDFMAKE_ENOMEM, t.offset, "Operand stack overflow");
2046 0           return PDFMAKE_ENOMEM;
2047             }
2048 0           break;
2049             }
2050            
2051 3438           case PDFMAKE_TOK_ERROR:
2052             default: {
2053             /* Try to interpret as operator */
2054 3438           const char *op_str = (const char *)(bytes + t.offset);
2055 3438           op_handler_t handler = find_operator(op_str, t.length);
2056            
2057 3438 50         if (handler) {
2058 3438           pdfmake_err_t err = handler(interp);
2059 3438 50         if (err != PDFMAKE_OK) {
2060 0           return err;
2061             }
2062             } else {
2063             /* Unknown operator - skip */
2064             }
2065 3438           break;
2066             }
2067             }
2068             }
2069            
2070 64           return PDFMAKE_OK;
2071             }
2072              
2073             /*============================================================================
2074             * Main interpret function
2075             *==========================================================================*/
2076              
2077 60           pdfmake_err_t pdfmake_interpret(pdfmake_interp_t *interp,
2078             const uint8_t *bytes,
2079             size_t len) {
2080 60 50         if (!interp || !bytes) {
    50          
2081 0           return PDFMAKE_EINVAL;
2082             }
2083            
2084 60           pdfmake_interp_reset(interp);
2085            
2086 60           return parse_content_stream(interp, bytes, len);
2087             }