File Coverage

src/pdfmake_form.c
Criterion Covered Total %
statement 766 1033 74.1
branch 313 730 42.8
condition n/a
subroutine n/a
pod n/a
total 1079 1763 61.2


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_form.c — PDF interactive forms (AcroForms)
3             *
4             * Implementation of PDF forms per ISO 32000-2:2020 §12.7.
5             */
6              
7             #include "pdfmake_form.h"
8             #include "pdfmake_arena.h"
9             #include "pdfmake_buf.h"
10             #include "pdfmake_content.h"
11             #include
12             #include
13             #include
14              
15             /*============================================================================
16             * Internal constants
17             *==========================================================================*/
18              
19             #define FORM_MAGIC 0x464F524D /* "FORM" */
20              
21             /* Default appearance for text fields */
22             static const char *DEFAULT_DA = "/Helv 12 Tf 0 g";
23              
24             /*============================================================================
25             * Form storage (associated with doc)
26             *==========================================================================*/
27              
28             typedef struct {
29             uint32_t magic;
30             pdfmake_form_t *form;
31             } form_storage_t;
32              
33 444           static form_storage_t *get_or_create_storage(pdfmake_doc_t *doc)
34             {
35             form_storage_t *storage;
36              
37             /* Use doc->form_data to store form_storage pointer directly */
38 444 100         if (doc->form_data) {
39 246           return (form_storage_t *)doc->form_data;
40             }
41              
42 198           storage = pdfmake_arena_alloc(doc->arena, sizeof(form_storage_t));
43 198 50         if (!storage) return NULL;
44            
45 198           storage->magic = FORM_MAGIC;
46 198           storage->form = NULL;
47            
48 198           doc->form_data = storage;
49 198           return storage;
50             }
51              
52             /*============================================================================
53             * Helper functions
54             *==========================================================================*/
55              
56 88           static pdfmake_field_t *create_field(pdfmake_doc_t *doc,
57             pdfmake_field_type_t type,
58             const char *name,
59             pdfmake_rect_t rect)
60             {
61 88           pdfmake_arena_t *arena = doc->arena;
62            
63 88           pdfmake_field_t *field = pdfmake_arena_alloc(arena, sizeof(pdfmake_field_t));
64 88 50         if (!field) return NULL;
65            
66 88           memset(field, 0, sizeof(pdfmake_field_t));
67            
68 88           field->doc = doc;
69 88           field->type = type;
70 88           field->name = pdfmake_arena_strdup(arena, name);
71 88           field->full_name = pdfmake_arena_strdup(arena, name); /* Will be updated for nested fields */
72 88           field->rect = rect;
73 88           field->da = pdfmake_arena_strdup(arena, DEFAULT_DA);
74 88           field->quadding = PDFMAKE_QUADDING_LEFT;
75            
76 88           return field;
77             }
78              
79 88           static pdfmake_err_t add_field_to_form(pdfmake_form_t *form, pdfmake_field_t *field)
80             {
81 88 50         if (!form || !field) return PDFMAKE_EINVAL;
    50          
82            
83 88 100         if (form->field_count >= form->field_cap) {
84 47 100         size_t new_cap = form->field_cap ? form->field_cap * 2 : 8;
85 47           pdfmake_field_t **new_fields = pdfmake_arena_alloc(form->doc->arena,
86             new_cap * sizeof(pdfmake_field_t *));
87 47 50         if (!new_fields) return PDFMAKE_ENOMEM;
88            
89 47 100         if (form->fields) {
90 2           memcpy(new_fields, form->fields, form->field_count * sizeof(pdfmake_field_t *));
91             }
92 47           form->fields = new_fields;
93 47           form->field_cap = new_cap;
94             }
95            
96 88           form->fields[form->field_count++] = field;
97 88           return PDFMAKE_OK;
98             }
99              
100             /*============================================================================
101             * Form access
102             *==========================================================================*/
103              
104 306           pdfmake_form_t *pdfmake_doc_get_form(pdfmake_doc_t *doc)
105             {
106             form_storage_t *storage;
107              
108 306 50         if (!doc) return NULL;
109              
110 306           storage = get_or_create_storage(doc);
111 306 50         if (!storage) return NULL;
112            
113 306           return storage->form;
114             }
115              
116 138           pdfmake_form_t *pdfmake_doc_create_form(pdfmake_doc_t *doc)
117             {
118             form_storage_t *storage;
119             pdfmake_form_t *form;
120              
121 138 50         if (!doc) return NULL;
122              
123 138           storage = get_or_create_storage(doc);
124 138 50         if (!storage) return NULL;
125              
126 138 100         if (storage->form) return storage->form;
127              
128 50           form = pdfmake_arena_alloc(doc->arena, sizeof(pdfmake_form_t));
129 50 50         if (!form) return NULL;
130            
131 50           memset(form, 0, sizeof(pdfmake_form_t));
132 50           form->doc = doc;
133 50           form->da = pdfmake_arena_strdup(doc->arena, DEFAULT_DA);
134 50           form->need_appearances = 0; /* We generate appearances by default */
135            
136 50           storage->form = form;
137 50           return form;
138             }
139              
140             /*============================================================================
141             * Field iteration
142             *==========================================================================*/
143              
144 10           size_t pdfmake_form_field_count(pdfmake_form_t *form)
145             {
146 10 50         return form ? form->field_count : 0;
147             }
148              
149 12           pdfmake_field_t *pdfmake_form_field_at(pdfmake_form_t *form, size_t idx)
150             {
151 12 50         if (!form || idx >= form->field_count) return NULL;
    100          
152 9           return form->fields[idx];
153             }
154              
155 10           pdfmake_field_t *pdfmake_form_field_by_name(pdfmake_form_t *form, const char *name)
156             {
157             size_t i;
158             pdfmake_field_t *field;
159              
160 10 50         if (!form || !name) return NULL;
    50          
161              
162 20 100         for (i = 0; i < form->field_count; i++) {
163 17           field = form->fields[i];
164 17 50         if (field->full_name && strcmp(field->full_name, name) == 0) {
    100          
165 7           return field;
166             }
167 10 50         if (field->name && strcmp(field->name, name) == 0) {
    50          
168 0           return field;
169             }
170             }
171 3           return NULL;
172             }
173              
174             /*============================================================================
175             * Field builders
176             *==========================================================================*/
177              
178 49           pdfmake_field_t *pdfmake_field_text(pdfmake_doc_t *doc,
179             const char *name,
180             pdfmake_rect_t rect)
181             {
182 49           pdfmake_field_t *field = create_field(doc, PDFMAKE_FIELD_TEXT, name, rect);
183             pdfmake_form_t *form;
184              
185 49 50         if (!field) return NULL;
186              
187             /* Get or create form */
188 49           form = pdfmake_doc_create_form(doc);
189 49 50         if (form) {
190 49           add_field_to_form(form, field);
191             }
192            
193 49           return field;
194             }
195              
196 8           pdfmake_field_t *pdfmake_field_checkbox(pdfmake_doc_t *doc,
197             const char *name,
198             pdfmake_rect_t rect,
199             const char *on_value)
200             {
201 8           pdfmake_field_t *field = create_field(doc, PDFMAKE_FIELD_BUTTON, name, rect);
202             pdfmake_form_t *form;
203              
204 8 50         if (!field) return NULL;
205              
206 8 50         field->on_value = pdfmake_arena_strdup(doc->arena, on_value ? on_value : "Yes");
207              
208 8           form = pdfmake_doc_create_form(doc);
209 8 50         if (form) {
210 8           add_field_to_form(form, field);
211             }
212            
213 8           return field;
214             }
215              
216 8           pdfmake_field_t *pdfmake_field_radio_group(pdfmake_doc_t *doc,
217             const char *name)
218             {
219 8           pdfmake_rect_t empty_rect = {0, 0, 0, 0};
220 8           pdfmake_field_t *field = create_field(doc, PDFMAKE_FIELD_BUTTON, name, empty_rect);
221             pdfmake_form_t *form;
222              
223 8 50         if (!field) return NULL;
224              
225 8           field->flags |= PDFMAKE_FF_RADIO | PDFMAKE_FF_NOTOGGLETOOFF;
226              
227 8           form = pdfmake_doc_create_form(doc);
228 8 50         if (form) {
229 8           add_field_to_form(form, field);
230             }
231            
232 8           return field;
233             }
234              
235 18           pdfmake_field_t *pdfmake_field_add_radio_option(pdfmake_field_t *group,
236             pdfmake_rect_t rect,
237             const char *value)
238             {
239             pdfmake_doc_t *doc;
240             pdfmake_field_t *option;
241             size_t parent_len;
242             size_t val_len;
243             char *full;
244             pdfmake_field_t *last;
245              
246 18 50         if (!group || group->type != PDFMAKE_FIELD_BUTTON) return NULL;
    50          
247 18 50         if (!(group->flags & PDFMAKE_FF_RADIO)) return NULL;
248              
249 18           doc = group->doc;
250              
251             /* Create child field for this option */
252 18           option = pdfmake_arena_alloc(doc->arena, sizeof(pdfmake_field_t));
253 18 50         if (!option) return NULL;
254            
255 18           memset(option, 0, sizeof(pdfmake_field_t));
256 18           option->doc = doc;
257 18           option->type = PDFMAKE_FIELD_BUTTON;
258 18           option->rect = rect;
259 18           option->flags = PDFMAKE_FF_RADIO;
260 18           option->on_value = pdfmake_arena_strdup(doc->arena, value);
261 18           option->parent = group;
262              
263             /* Build full name */
264 18 50         parent_len = group->full_name ? strlen(group->full_name) : 0;
265 18 50         val_len = value ? strlen(value) : 0;
266 18           full = pdfmake_arena_alloc(doc->arena, parent_len + val_len + 2);
267 18 50         if (full) {
268 36 50         snprintf(full, parent_len + val_len + 2, "%s.%s",
269 18 50         group->full_name ? group->full_name : "", value ? value : "");
270 18           option->full_name = full;
271             }
272            
273             /* Link into group's children */
274 18 100         if (!group->first_child) {
275 7           group->first_child = option;
276             } else {
277 11           last = group->first_child;
278 17 100         while (last->next_sibling) last = last->next_sibling;
279 11           last->next_sibling = option;
280             }
281            
282 18           return option;
283             }
284              
285 11           pdfmake_field_t *pdfmake_field_choice(pdfmake_doc_t *doc,
286             const char *name,
287             pdfmake_rect_t rect,
288             int combo)
289             {
290 11           pdfmake_field_t *field = create_field(doc, PDFMAKE_FIELD_CHOICE, name, rect);
291             pdfmake_form_t *form;
292              
293 11 50         if (!field) return NULL;
294              
295 11 100         if (combo) {
296 6           field->flags |= PDFMAKE_FF_COMBO;
297             }
298              
299 11           form = pdfmake_doc_create_form(doc);
300 11 50         if (form) {
301 11           add_field_to_form(form, field);
302             }
303            
304 11           return field;
305             }
306              
307 9           pdfmake_field_t *pdfmake_field_button(pdfmake_doc_t *doc,
308             const char *name,
309             pdfmake_rect_t rect,
310             const char *caption)
311             {
312 9           pdfmake_field_t *field = create_field(doc, PDFMAKE_FIELD_BUTTON, name, rect);
313             pdfmake_form_t *form;
314              
315 9 50         if (!field) return NULL;
316              
317 9           field->flags |= PDFMAKE_FF_PUSHBUTTON;
318 9           field->value = pdfmake_arena_strdup(doc->arena, caption); /* Store caption as value */
319              
320 9           form = pdfmake_doc_create_form(doc);
321 9 50         if (form) {
322 9           add_field_to_form(form, field);
323             }
324            
325 9           return field;
326             }
327              
328 3           pdfmake_field_t *pdfmake_field_signature(pdfmake_doc_t *doc,
329             const char *name,
330             pdfmake_rect_t rect)
331             {
332 3           pdfmake_field_t *field = create_field(doc, PDFMAKE_FIELD_SIGNATURE, name, rect);
333             pdfmake_form_t *form;
334              
335 3 50         if (!field) return NULL;
336              
337 3           form = pdfmake_doc_create_form(doc);
338 3 50         if (form) {
339 3           add_field_to_form(form, field);
340             }
341            
342 3           return field;
343             }
344              
345             /*============================================================================
346             * Field properties
347             *==========================================================================*/
348              
349 7           pdfmake_field_type_t pdfmake_field_type(pdfmake_field_t *field)
350             {
351 7 50         return field ? field->type : PDFMAKE_FIELD_TEXT;
352             }
353              
354 9           const char *pdfmake_field_name(pdfmake_field_t *field)
355             {
356 9 50         return field ? field->name : NULL;
357             }
358              
359 2           const char *pdfmake_field_full_name(pdfmake_field_t *field)
360             {
361 2 50         return field ? field->full_name : NULL;
362             }
363              
364 17           const char *pdfmake_field_value(pdfmake_field_t *field)
365             {
366 17 50         return field ? field->value : NULL;
367             }
368              
369 40           pdfmake_err_t pdfmake_field_set_value(pdfmake_field_t *field, const char *value)
370             {
371 40 50         if (!field) return PDFMAKE_EINVAL;
372 40           field->value = pdfmake_arena_strdup(field->doc->arena, value);
373 40           return PDFMAKE_OK;
374             }
375              
376 2           pdfmake_err_t pdfmake_field_set_default_value(pdfmake_field_t *field, const char *value)
377             {
378 2 50         if (!field) return PDFMAKE_EINVAL;
379 2           field->default_val = pdfmake_arena_strdup(field->doc->arena, value);
380 2           return PDFMAKE_OK;
381             }
382              
383 16           uint32_t pdfmake_field_flags(pdfmake_field_t *field)
384             {
385 16 50         return field ? field->flags : 0;
386             }
387              
388 1           pdfmake_err_t pdfmake_field_set_flags(pdfmake_field_t *field, uint32_t flags)
389             {
390 1 50         if (!field) return PDFMAKE_EINVAL;
391 1           field->flags = flags;
392 1           return PDFMAKE_OK;
393             }
394              
395 19           pdfmake_err_t pdfmake_field_add_flags(pdfmake_field_t *field, uint32_t flags)
396             {
397 19 50         if (!field) return PDFMAKE_EINVAL;
398 19           field->flags |= flags;
399 19           return PDFMAKE_OK;
400             }
401              
402 8           pdfmake_err_t pdfmake_field_clear_flags(pdfmake_field_t *field, uint32_t flags)
403             {
404 8 50         if (!field) return PDFMAKE_EINVAL;
405 8           field->flags &= ~flags;
406 8           return PDFMAKE_OK;
407             }
408              
409 26           pdfmake_err_t pdfmake_field_set_da(pdfmake_field_t *field, const char *da)
410             {
411 26 50         if (!field) return PDFMAKE_EINVAL;
412 26           field->da = pdfmake_arena_strdup(field->doc->arena, da);
413 26           return PDFMAKE_OK;
414             }
415              
416 6           pdfmake_err_t pdfmake_field_set_quadding(pdfmake_field_t *field, pdfmake_quadding_t q)
417             {
418 6 50         if (!field) return PDFMAKE_EINVAL;
419 6           field->quadding = q;
420 6           return PDFMAKE_OK;
421             }
422              
423 3           pdfmake_err_t pdfmake_field_set_max_len(pdfmake_field_t *field, int max_len)
424             {
425 3 50         if (!field) return PDFMAKE_EINVAL;
426 3           field->max_len = max_len;
427 3           return PDFMAKE_OK;
428             }
429              
430             /*============================================================================
431             * Choice field options
432             *==========================================================================*/
433              
434 5           size_t pdfmake_field_option_count(pdfmake_field_t *field)
435             {
436 5 50         return field ? field->option_count : 0;
437             }
438              
439 5           const char *pdfmake_field_option_display(pdfmake_field_t *field, size_t idx)
440             {
441 5 50         if (!field || idx >= field->option_count) return NULL;
    50          
442 5           return field->options[idx].display;
443             }
444              
445 5           const char *pdfmake_field_option_export(pdfmake_field_t *field, size_t idx)
446             {
447             const char *exp;
448              
449 5 50         if (!field || idx >= field->option_count) return NULL;
    50          
450 5           exp = field->options[idx].export_val;
451 5 100         return exp ? exp : field->options[idx].display;
452             }
453              
454 32           pdfmake_err_t pdfmake_field_add_option(pdfmake_field_t *field,
455             const char *display,
456             const char *export_val)
457             {
458 32 50         if (!field || !display) return PDFMAKE_EINVAL;
    50          
459 32 50         if (field->type != PDFMAKE_FIELD_CHOICE) return PDFMAKE_EINVAL;
460            
461 32 100         if (field->option_count >= field->option_cap) {
462 10 50         size_t new_cap = field->option_cap ? field->option_cap * 2 : 8;
463 10           pdfmake_choice_opt_t *new_opts = pdfmake_arena_alloc(field->doc->arena,
464             new_cap * sizeof(pdfmake_choice_opt_t));
465 10 50         if (!new_opts) return PDFMAKE_ENOMEM;
466            
467 10 50         if (field->options) {
468 0           memcpy(new_opts, field->options, field->option_count * sizeof(pdfmake_choice_opt_t));
469             }
470 10           field->options = new_opts;
471 10           field->option_cap = new_cap;
472             }
473            
474 32           field->options[field->option_count].display = pdfmake_arena_strdup(field->doc->arena, display);
475 32           field->options[field->option_count].export_val = export_val ?
476 32 100         pdfmake_arena_strdup(field->doc->arena, export_val) : NULL;
477 32           field->option_count++;
478            
479 32           return PDFMAKE_OK;
480             }
481              
482             /*============================================================================
483             * Field-page association
484             *==========================================================================*/
485              
486 70           pdfmake_err_t pdfmake_page_add_field(pdfmake_page_t *page, pdfmake_field_t *field)
487             {
488 70 50         if (!page || !field) return PDFMAKE_EINVAL;
    50          
489 70           field->page = page;
490 70           return PDFMAKE_OK;
491             }
492              
493             /*============================================================================
494             * Appearance generation
495             *==========================================================================*/
496              
497             /* Helper: create an appearance stream with BBox */
498 49           static pdfmake_obj_t create_appearance_stream(pdfmake_doc_t *doc,
499             pdfmake_buf_t *buf,
500             double width, double height)
501             {
502 49           pdfmake_arena_t *arena = doc->arena;
503             uint32_t bbox_key;
504             uint32_t type_key;
505             uint32_t subtype_key;
506             pdfmake_obj_t bbox;
507             pdfmake_obj_t stream_dict;
508             uint32_t res_key;
509             uint32_t font_key;
510             pdfmake_obj_t res_dict;
511             pdfmake_obj_t font_dict;
512             pdfmake_obj_t helv_font;
513             uint32_t bt_key;
514             uint32_t st_key;
515             uint32_t helv_num;
516             uint32_t helv_key;
517            
518 49           pdfmake_obj_t stream = pdfmake_stream_new(arena);
519 49 50         if (stream.kind != PDFMAKE_STREAM) return stream;
520            
521 49           pdfmake_stream_set_data(arena, &stream, buf->data, buf->len);
522            
523             /* Add BBox, Type, Subtype to stream dict */
524 49           bbox_key = pdfmake_arena_intern_name(arena, "BBox", 4);
525 49           type_key = pdfmake_arena_intern_name(arena, "Type", 4);
526 49           subtype_key = pdfmake_arena_intern_name(arena, "Subtype", 7);
527            
528 49           bbox = pdfmake_array_new(arena);
529 49           pdfmake_array_push(arena, &bbox, pdfmake_real(0));
530 49           pdfmake_array_push(arena, &bbox, pdfmake_real(0));
531 49           pdfmake_array_push(arena, &bbox, pdfmake_real(width));
532 49           pdfmake_array_push(arena, &bbox, pdfmake_real(height));
533            
534 49           stream_dict.kind = PDFMAKE_DICT;
535 49           stream_dict.as.dict = stream.as.stream->dict;
536            
537 49           pdfmake_dict_set(arena, &stream_dict, bbox_key, bbox);
538 49           pdfmake_dict_set(arena, &stream_dict, type_key,
539             pdfmake_name(arena, "XObject", 7));
540 49           pdfmake_dict_set(arena, &stream_dict, subtype_key,
541             pdfmake_name(arena, "Form", 4));
542              
543             /* /Resources with /Font /Helv for appearance text rendering */
544 49           res_key = pdfmake_arena_intern_name(arena, "Resources", 9);
545 49           font_key = pdfmake_arena_intern_name(arena, "Font", 4);
546 49           res_dict = pdfmake_dict_new(arena);
547 49           font_dict = pdfmake_dict_new(arena);
548 49           helv_font = pdfmake_dict_new(arena);
549              
550 49           bt_key = pdfmake_arena_intern_name(arena, "BaseFont", 8);
551 49           st_key = pdfmake_arena_intern_name(arena, "Subtype", 7);
552 49           pdfmake_dict_set(arena, &helv_font, type_key,
553             pdfmake_name(arena, "Font", 4));
554 49           pdfmake_dict_set(arena, &helv_font, st_key,
555             pdfmake_name(arena, "Type1", 5));
556 49           pdfmake_dict_set(arena, &helv_font, bt_key,
557             pdfmake_name(arena, "Helvetica", 9));
558              
559 49           helv_num = pdfmake_doc_add(doc, helv_font);
560 49           helv_key = pdfmake_arena_intern_name(arena, "Helv", 4);
561 49           pdfmake_dict_set(arena, &font_dict, helv_key,
562             pdfmake_ref(helv_num, 0));
563 49           pdfmake_dict_set(arena, &res_dict, font_key, font_dict);
564 49           pdfmake_dict_set(arena, &stream_dict, res_key, res_dict);
565            
566 49           return stream;
567             }
568              
569             /* Generate appearance stream for text field */
570 27           static pdfmake_err_t generate_text_appearance(pdfmake_doc_t *doc,
571             pdfmake_field_t *field,
572             pdfmake_obj_t *ap_dict)
573             {
574 27           pdfmake_arena_t *arena = doc->arena;
575 27           double width = field->rect.x2 - field->rect.x1;
576 27           double height = field->rect.y2 - field->rect.y1;
577             pdfmake_buf_t buf;
578             double x;
579             double y;
580             const char *p;
581             pdfmake_obj_t stream;
582             uint32_t stream_num;
583             uint32_t n_key;
584            
585             /* Build appearance stream content */
586 27           pdfmake_buf_init(&buf);
587            
588             /* Background and border */
589 27           pdfmake_buf_appendf(&buf, "q\n");
590 27           pdfmake_buf_appendf(&buf, "1 1 1 rg\n"); /* White background */
591 27           pdfmake_buf_appendf(&buf, "0 0 %.2f %.2f re f\n", width, height);
592            
593             /* Text */
594 27 100         if (field->value && field->value[0]) {
    50          
595 15           pdfmake_buf_appendf(&buf, "BT\n");
596 15 50         pdfmake_buf_appendf(&buf, "%s\n", field->da ? field->da : DEFAULT_DA);
597            
598             /* Position text with margin */
599 15           x = 2;
600 15           y = (height - 12) / 2 + 2; /* Rough vertical center for 12pt */
601            
602             /* Apply quadding */
603 15 50         if (field->quadding == PDFMAKE_QUADDING_CENTER) {
604             /* Approximate center (would need font metrics for accurate) */
605 0           x = width / 2 - strlen(field->value) * 3;
606 15 50         } else if (field->quadding == PDFMAKE_QUADDING_RIGHT) {
607 0           x = width - 2 - strlen(field->value) * 6;
608             }
609            
610 15           pdfmake_buf_appendf(&buf, "%.2f %.2f Td\n", x, y);
611 15           pdfmake_buf_append_cstr(&buf, "(");
612            
613             /* Escape parentheses in value */
614 165 100         for (p = field->value; *p; p++) {
615 150 50         if (*p == '(' || *p == ')' || *p == '\\') {
    50          
    50          
616 0           pdfmake_buf_append_byte(&buf, '\\');
617             }
618 150           pdfmake_buf_append_byte(&buf, *p);
619             }
620            
621 15           pdfmake_buf_appendf(&buf, ") Tj\n");
622 15           pdfmake_buf_appendf(&buf, "ET\n");
623             }
624            
625 27           pdfmake_buf_appendf(&buf, "Q\n");
626            
627             /* Create stream object using helper */
628 27           stream = create_appearance_stream(doc, &buf, width, height);
629 27           stream_num = pdfmake_doc_add(doc, stream);
630            
631             /* Set /N (normal) appearance */
632 27           n_key = pdfmake_arena_intern_name(arena, "N", 1);
633 27           pdfmake_dict_set(arena, ap_dict, n_key, pdfmake_ref(stream_num, 0));
634            
635 27           pdfmake_buf_free(&buf);
636 27           return PDFMAKE_OK;
637             }
638              
639             /* Generate appearance stream for checkbox */
640 8           static pdfmake_err_t generate_checkbox_appearance(pdfmake_doc_t *doc,
641             pdfmake_field_t *field,
642             pdfmake_obj_t *ap_dict)
643             {
644 8           pdfmake_arena_t *arena = doc->arena;
645 8           double width = field->rect.x2 - field->rect.x1;
646 8           double height = field->rect.y2 - field->rect.y1;
647 8 50         double size = (width < height ? width : height) - 2;
648             uint32_t n_key;
649            
650             /* Create /N dict with /Yes and /Off appearances */
651 8           pdfmake_obj_t n_dict = pdfmake_dict_new(arena);
652            
653             /* /Off appearance - empty box */
654             {
655             pdfmake_buf_t buf;
656             pdfmake_obj_t stream;
657             uint32_t off_num;
658             uint32_t off_key;
659              
660 8           pdfmake_buf_init(&buf);
661 8           pdfmake_buf_appendf(&buf, "q\n");
662 8           pdfmake_buf_appendf(&buf, "1 1 1 rg\n");
663 8           pdfmake_buf_appendf(&buf, "0 0 %.2f %.2f re f\n", width, height);
664 8           pdfmake_buf_appendf(&buf, "0 0 0 RG\n");
665 8           pdfmake_buf_appendf(&buf, "0.5 w\n");
666 8           pdfmake_buf_appendf(&buf, "1 1 %.2f %.2f re s\n", width - 2, height - 2);
667 8           pdfmake_buf_appendf(&buf, "Q\n");
668            
669 8           stream = create_appearance_stream(doc, &buf, width, height);
670 8           off_num = pdfmake_doc_add(doc, stream);
671            
672 8           off_key = pdfmake_arena_intern_name(arena, "Off", 3);
673 8           pdfmake_dict_set(arena, &n_dict, off_key, pdfmake_ref(off_num, 0));
674            
675 8           pdfmake_buf_free(&buf);
676             }
677            
678             /* /Yes appearance - box with checkmark */
679             {
680             pdfmake_buf_t buf;
681             double cx;
682             double cy;
683             pdfmake_obj_t stream;
684             uint32_t yes_num;
685             const char *on_name;
686             uint32_t yes_key;
687              
688 8           pdfmake_buf_init(&buf);
689 8           pdfmake_buf_appendf(&buf, "q\n");
690 8           pdfmake_buf_appendf(&buf, "1 1 1 rg\n");
691 8           pdfmake_buf_appendf(&buf, "0 0 %.2f %.2f re f\n", width, height);
692 8           pdfmake_buf_appendf(&buf, "0 0 0 RG\n");
693 8           pdfmake_buf_appendf(&buf, "0.5 w\n");
694 8           pdfmake_buf_appendf(&buf, "1 1 %.2f %.2f re s\n", width - 2, height - 2);
695            
696             /* Draw checkmark */
697 8           cx = width / 2;
698 8           cy = height / 2;
699 8           pdfmake_buf_appendf(&buf, "1 w\n");
700 8           pdfmake_buf_appendf(&buf, "%.2f %.2f m\n", cx - size * 0.3, cy);
701 8           pdfmake_buf_appendf(&buf, "%.2f %.2f l\n", cx - size * 0.1, cy - size * 0.25);
702 8           pdfmake_buf_appendf(&buf, "%.2f %.2f l\n", cx + size * 0.35, cy + size * 0.3);
703 8           pdfmake_buf_appendf(&buf, "S\n");
704 8           pdfmake_buf_appendf(&buf, "Q\n");
705            
706 8           stream = create_appearance_stream(doc, &buf, width, height);
707 8           yes_num = pdfmake_doc_add(doc, stream);
708            
709 8 100         on_name = field->on_value ? field->on_value : "Yes";
710 8           yes_key = pdfmake_arena_intern_name(arena, on_name, strlen(on_name));
711 8           pdfmake_dict_set(arena, &n_dict, yes_key, pdfmake_ref(yes_num, 0));
712            
713 8           pdfmake_buf_free(&buf);
714             }
715            
716 8           n_key = pdfmake_arena_intern_name(arena, "N", 1);
717 8           pdfmake_dict_set(arena, ap_dict, n_key, n_dict);
718            
719 8           return PDFMAKE_OK;
720             }
721              
722             /* Generate appearance stream for choice (dropdown/list) */
723 7           static pdfmake_err_t generate_choice_appearance(pdfmake_doc_t *doc,
724             pdfmake_field_t *field,
725             pdfmake_obj_t *ap_dict)
726             {
727             /* Similar to text field but shows selected value */
728 7           return generate_text_appearance(doc, field, ap_dict);
729             }
730              
731             /* Generate appearance stream for pushbutton */
732 6           static pdfmake_err_t generate_button_appearance(pdfmake_doc_t *doc,
733             pdfmake_field_t *field,
734             pdfmake_obj_t *ap_dict)
735             {
736 6           pdfmake_arena_t *arena = doc->arena;
737 6           double width = field->rect.x2 - field->rect.x1;
738 6           double height = field->rect.y2 - field->rect.y1;
739             pdfmake_buf_t buf;
740             double x;
741             double y;
742             const char *p;
743             pdfmake_obj_t stream;
744             uint32_t stream_num;
745             uint32_t n_key;
746              
747 6           pdfmake_buf_init(&buf);
748            
749             /* 3D button appearance */
750 6           pdfmake_buf_appendf(&buf, "q\n");
751            
752             /* Button face */
753 6           pdfmake_buf_appendf(&buf, "0.8 0.8 0.8 rg\n");
754 6           pdfmake_buf_appendf(&buf, "0 0 %.2f %.2f re f\n", width, height);
755            
756             /* 3D border — use S (stroke) not s (close-and-stroke) to avoid diagonal */
757 6           pdfmake_buf_appendf(&buf, "1 1 1 RG 1 w\n");
758 6           pdfmake_buf_appendf(&buf, "0 0 m %.2f 0 l %.2f %.2f l S\n", width, width, height);
759 6           pdfmake_buf_appendf(&buf, "0.4 0.4 0.4 RG\n");
760 6           pdfmake_buf_appendf(&buf, "0 0 m 0 %.2f l %.2f %.2f l S\n", height, width, height);
761            
762             /* Caption text */
763 6 50         if (field->value && field->value[0]) {
    50          
764 6           pdfmake_buf_appendf(&buf, "BT\n");
765 6           pdfmake_buf_appendf(&buf, "/Helv 10 Tf 0 g\n");
766            
767 6           x = width / 2 - strlen(field->value) * 2.5;
768 6           y = height / 2 - 4;
769            
770 6           pdfmake_buf_appendf(&buf, "%.2f %.2f Td (", x, y);
771 43 100         for (p = field->value; *p; p++) {
772 37 50         if (*p == '(' || *p == ')' || *p == '\\') {
    50          
    50          
773 0           pdfmake_buf_append_byte(&buf, '\\');
774             }
775 37           pdfmake_buf_append_byte(&buf, *p);
776             }
777 6           pdfmake_buf_appendf(&buf, ") Tj\nET\n");
778             }
779            
780 6           pdfmake_buf_appendf(&buf, "Q\n");
781            
782 6           stream = create_appearance_stream(doc, &buf, width, height);
783 6           stream_num = pdfmake_doc_add(doc, stream);
784            
785 6           n_key = pdfmake_arena_intern_name(arena, "N", 1);
786 6           pdfmake_dict_set(arena, ap_dict, n_key, pdfmake_ref(stream_num, 0));
787            
788 6           pdfmake_buf_free(&buf);
789 6           return PDFMAKE_OK;
790             }
791              
792 1           pdfmake_err_t pdfmake_field_generate_appearance(pdfmake_field_t *field)
793             {
794             pdfmake_doc_t *doc;
795             pdfmake_arena_t *arena;
796             pdfmake_obj_t ap_dict;
797             pdfmake_err_t err;
798              
799 1 50         if (!field || !field->doc) return PDFMAKE_EINVAL;
    50          
800              
801 1           doc = field->doc;
802 1           arena = doc->arena;
803            
804             /* Create /AP (appearance) dictionary */
805 1           ap_dict = pdfmake_dict_new(arena);
806              
807 1           err = PDFMAKE_OK;
808            
809 1           switch (field->type) {
810 1           case PDFMAKE_FIELD_TEXT:
811 1           err = generate_text_appearance(doc, field, &ap_dict);
812 1           break;
813            
814 0           case PDFMAKE_FIELD_BUTTON:
815 0 0         if (field->flags & PDFMAKE_FF_PUSHBUTTON) {
816 0           err = generate_button_appearance(doc, field, &ap_dict);
817             } else {
818 0           err = generate_checkbox_appearance(doc, field, &ap_dict);
819             }
820 0           break;
821            
822 0           case PDFMAKE_FIELD_CHOICE:
823 0           err = generate_choice_appearance(doc, field, &ap_dict);
824 0           break;
825            
826 0           case PDFMAKE_FIELD_SIGNATURE:
827             /* Signature fields typically have no appearance until signed */
828 0           break;
829             }
830            
831             /* Store appearance dict for use during finalization */
832             /* (Will be attached to widget annotation) */
833            
834 1           return err;
835             }
836              
837 10           pdfmake_err_t pdfmake_form_set_need_appearances(pdfmake_form_t *form, int need)
838             {
839 10 50         if (!form) return PDFMAKE_EINVAL;
840 10           form->need_appearances = need;
841 10           return PDFMAKE_OK;
842             }
843              
844             /*============================================================================
845             * Form finalization
846             *==========================================================================*/
847              
848 41           static pdfmake_err_t finalize_field(pdfmake_doc_t *doc, pdfmake_field_t *field)
849             {
850 41           pdfmake_arena_t *arena = doc->arena;
851             pdfmake_obj_t field_dict;
852             uint32_t ft_key;
853             const char *ft_val;
854             uint32_t type_key;
855             uint32_t subtype_key;
856             uint32_t rect_key;
857             pdfmake_obj_t rect_arr;
858             uint32_t border_key;
859             pdfmake_obj_t border_arr;
860             uint32_t f_key;
861             pdfmake_obj_t ap_dict;
862             uint32_t ap_key;
863             size_t i;
864            
865             /* Create field dictionary */
866 41           field_dict = pdfmake_dict_new(arena);
867            
868             /* /FT - field type */
869 41           ft_key = pdfmake_arena_intern_name(arena, "FT", 2);
870 41           switch (field->type) {
871 19           case PDFMAKE_FIELD_TEXT: ft_val = "Tx"; break;
872 14           case PDFMAKE_FIELD_BUTTON: ft_val = "Btn"; break;
873 7           case PDFMAKE_FIELD_CHOICE: ft_val = "Ch"; break;
874 1           case PDFMAKE_FIELD_SIGNATURE: ft_val = "Sig"; break;
875 0           default: ft_val = "Tx"; break;
876             }
877 41           pdfmake_dict_set(arena, &field_dict, ft_key, pdfmake_name_cstr(arena, ft_val));
878            
879             /* /T - partial field name */
880 41 50         if (field->name) {
881             uint32_t t_key;
882              
883 41           t_key = pdfmake_arena_intern_name(arena, "T", 1);
884 41           pdfmake_dict_set(arena, &field_dict, t_key,
885 41           pdfmake_str(arena, field->name,
886 41           strlen(field->name)));
887             }
888            
889             /* /V - value */
890 41 100         if (field->value) {
891             uint32_t v_key;
892              
893 23           v_key = pdfmake_arena_intern_name(arena, "V", 1);
894 23 100         if (field->type == PDFMAKE_FIELD_BUTTON && !(field->flags & PDFMAKE_FF_PUSHBUTTON)) {
    100          
895             /* Checkbox/radio: value is a name */
896 3           pdfmake_dict_set(arena, &field_dict, v_key,
897 3           pdfmake_name_cstr(arena, field->value));
898             } else {
899 20           pdfmake_dict_set(arena, &field_dict, v_key,
900 20           pdfmake_str(arena, field->value,
901 20           strlen(field->value)));
902             }
903             }
904            
905             /* /DV - default value */
906 41 100         if (field->default_val) {
907             uint32_t dv_key;
908              
909 2           dv_key = pdfmake_arena_intern_name(arena, "DV", 2);
910 2           pdfmake_dict_set(arena, &field_dict, dv_key,
911 2           pdfmake_str(arena, field->default_val,
912 2           strlen(field->default_val)));
913             }
914            
915             /* /Ff - field flags */
916 41 100         if (field->flags) {
917             uint32_t ff_key;
918              
919 22           ff_key = pdfmake_arena_intern_name(arena, "Ff", 2);
920 22           pdfmake_dict_set(arena, &field_dict, ff_key, pdfmake_int(field->flags));
921             }
922            
923             /* /DA - default appearance */
924 41 50         if (field->da) {
925             uint32_t da_key;
926              
927 41           da_key = pdfmake_arena_intern_name(arena, "DA", 2);
928 41           pdfmake_dict_set(arena, &field_dict, da_key,
929 41           pdfmake_str(arena, field->da,
930 41           strlen(field->da)));
931             }
932            
933             /* /Q - quadding */
934 41 50         if (field->quadding != PDFMAKE_QUADDING_LEFT) {
935             uint32_t q_key;
936              
937 0           q_key = pdfmake_arena_intern_name(arena, "Q", 1);
938 0           pdfmake_dict_set(arena, &field_dict, q_key, pdfmake_int(field->quadding));
939             }
940            
941             /* /MaxLen for text fields */
942 41 100         if (field->type == PDFMAKE_FIELD_TEXT && field->max_len > 0) {
    100          
943             uint32_t maxlen_key;
944              
945 1           maxlen_key = pdfmake_arena_intern_name(arena, "MaxLen", 6);
946 1           pdfmake_dict_set(arena, &field_dict, maxlen_key, pdfmake_int(field->max_len));
947             }
948            
949             /* /Opt for choice fields */
950 41 100         if (field->type == PDFMAKE_FIELD_CHOICE && field->option_count > 0) {
    50          
951             uint32_t opt_key;
952             pdfmake_obj_t opt_arr;
953              
954 7           opt_key = pdfmake_arena_intern_name(arena, "Opt", 3);
955 7           opt_arr = pdfmake_array_new(arena);
956              
957 30 100         for (i = 0; i < field->option_count; i++) {
958 23 100         if (field->options[i].export_val) {
959             /* [export_val, display] pair */
960             pdfmake_obj_t pair;
961              
962 14           pair = pdfmake_array_new(arena);
963 14           pdfmake_array_push(arena, &pair,
964 14           pdfmake_str_cstr(arena, field->options[i].export_val));
965 14           pdfmake_array_push(arena, &pair,
966 14           pdfmake_str_cstr(arena, field->options[i].display));
967 14           pdfmake_array_push(arena, &opt_arr, pair);
968             } else {
969 9           pdfmake_array_push(arena, &opt_arr,
970 9           pdfmake_str_cstr(arena, field->options[i].display));
971             }
972             }
973 7           pdfmake_dict_set(arena, &field_dict, opt_key, opt_arr);
974             }
975            
976             /* Widget annotation (merged with field for simple fields) */
977             /* /Type /Annot, /Subtype /Widget */
978 41           type_key = pdfmake_arena_intern_name(arena, "Type", 4);
979 41           subtype_key = pdfmake_arena_intern_name(arena, "Subtype", 7);
980 41           pdfmake_dict_set(arena, &field_dict, type_key, pdfmake_name_cstr(arena, "Annot"));
981 41           pdfmake_dict_set(arena, &field_dict, subtype_key, pdfmake_name_cstr(arena, "Widget"));
982            
983             /* /Rect */
984 41           rect_key = pdfmake_arena_intern_name(arena, "Rect", 4);
985 41           rect_arr = pdfmake_array_new(arena);
986 41           pdfmake_array_push(arena, &rect_arr, pdfmake_real(field->rect.x1));
987 41           pdfmake_array_push(arena, &rect_arr, pdfmake_real(field->rect.y1));
988 41           pdfmake_array_push(arena, &rect_arr, pdfmake_real(field->rect.x2));
989 41           pdfmake_array_push(arena, &rect_arr, pdfmake_real(field->rect.y2));
990 41           pdfmake_dict_set(arena, &field_dict, rect_key, rect_arr);
991              
992             /* /Border [0 0 1] — thin border for interactive feedback */
993 41           border_key = pdfmake_arena_intern_name(arena, "Border", 6);
994 41           border_arr = pdfmake_array_new(arena);
995 41           pdfmake_array_push(arena, &border_arr, pdfmake_int(0));
996 41           pdfmake_array_push(arena, &border_arr, pdfmake_int(0));
997 41           pdfmake_array_push(arena, &border_arr, pdfmake_int(1));
998 41           pdfmake_dict_set(arena, &field_dict, border_key, border_arr);
999              
1000             /* /MK — appearance characteristics (border color for text/choice fields) */
1001 41 100         if (field->type == PDFMAKE_FIELD_TEXT || field->type == PDFMAKE_FIELD_CHOICE) {
    100          
1002             uint32_t mk_key;
1003             pdfmake_obj_t mk;
1004             /* /BC [0 0 0] — border color black */
1005             uint32_t bc_key;
1006             pdfmake_obj_t bc;
1007             /* /BG [1 1 1] — background color white */
1008             uint32_t bg_key;
1009             pdfmake_obj_t bg;
1010              
1011 26           mk_key = pdfmake_arena_intern_name(arena, "MK", 2);
1012 26           mk = pdfmake_dict_new(arena);
1013 26           bc_key = pdfmake_arena_intern_name(arena, "BC", 2);
1014 26           bc = pdfmake_array_new(arena);
1015 26           bg_key = pdfmake_arena_intern_name(arena, "BG", 2);
1016 26           bg = pdfmake_array_new(arena);
1017              
1018 26           pdfmake_array_push(arena, &bc, pdfmake_real(0));
1019 26           pdfmake_array_push(arena, &bc, pdfmake_real(0));
1020 26           pdfmake_array_push(arena, &bc, pdfmake_real(0));
1021 26           pdfmake_dict_set(arena, &mk, bc_key, bc);
1022 26           pdfmake_array_push(arena, &bg, pdfmake_real(1));
1023 26           pdfmake_array_push(arena, &bg, pdfmake_real(1));
1024 26           pdfmake_array_push(arena, &bg, pdfmake_real(1));
1025 26           pdfmake_dict_set(arena, &mk, bg_key, bg);
1026 26           pdfmake_dict_set(arena, &field_dict, mk_key, mk);
1027             }
1028              
1029             /* /F 4 — Print flag (annotation should print) */
1030 41           f_key = pdfmake_arena_intern_name(arena, "F", 1);
1031 41           pdfmake_dict_set(arena, &field_dict, f_key, pdfmake_int(4));
1032              
1033             /* Generate and attach appearance */
1034 41           ap_dict = pdfmake_dict_new(arena);
1035            
1036 41           switch (field->type) {
1037 19           case PDFMAKE_FIELD_TEXT:
1038 19           generate_text_appearance(doc, field, &ap_dict);
1039 19           break;
1040 14           case PDFMAKE_FIELD_BUTTON:
1041 14 100         if (field->flags & PDFMAKE_FF_PUSHBUTTON) {
1042 6           generate_button_appearance(doc, field, &ap_dict);
1043             } else {
1044 8           generate_checkbox_appearance(doc, field, &ap_dict);
1045             }
1046 14           break;
1047 7           case PDFMAKE_FIELD_CHOICE:
1048 7           generate_choice_appearance(doc, field, &ap_dict);
1049 7           break;
1050 1           case PDFMAKE_FIELD_SIGNATURE:
1051             /* No appearance for unsigned signature */
1052 1           break;
1053             }
1054            
1055 41           ap_key = pdfmake_arena_intern_name(arena, "AP", 2);
1056 41           pdfmake_dict_set(arena, &field_dict, ap_key, ap_dict);
1057            
1058             /* /AS - appearance state for checkboxes */
1059 41 100         if (field->type == PDFMAKE_FIELD_BUTTON && !(field->flags & PDFMAKE_FF_PUSHBUTTON)) {
    100          
1060             uint32_t as_key;
1061             const char *state;
1062              
1063 8           as_key = pdfmake_arena_intern_name(arena, "AS", 2);
1064 3 100         state = (field->value && field->on_value &&
1065 2 50         strcmp(field->value, field->on_value) == 0)
1066 11 100         ? field->on_value : "Off";
1067 8           pdfmake_dict_set(arena, &field_dict, as_key, pdfmake_name_cstr(arena, state));
1068             }
1069            
1070             /* /P - page reference (if field is on a page) */
1071 41 100         if (field->page && field->page->page_num) {
    50          
1072             uint32_t p_key;
1073              
1074 0           p_key = pdfmake_arena_intern_name(arena, "P", 1);
1075 0           pdfmake_dict_set(arena, &field_dict, p_key,
1076 0           pdfmake_ref(field->page->page_num, 0));
1077             }
1078            
1079             /* /A - button action */
1080 41 100         if (field->action_uri) {
1081             pdfmake_obj_t action;
1082             uint32_t s_key;
1083             uint32_t uri_key;
1084             uint32_t a_key;
1085              
1086 2           action = pdfmake_dict_new(arena);
1087 2           s_key = pdfmake_arena_intern_name(arena, "S", 1);
1088 2           pdfmake_dict_set(arena, &action, s_key,
1089             pdfmake_name_cstr(arena, "URI"));
1090 2           uri_key = pdfmake_arena_intern_name(arena, "URI", 3);
1091 2           pdfmake_dict_set(arena, &action, uri_key,
1092 2           pdfmake_str_cstr(arena, field->action_uri));
1093 2           a_key = pdfmake_arena_intern_name(arena, "A", 1);
1094 2           pdfmake_dict_set(arena, &field_dict, a_key, action);
1095 39 50         } else if (field->action_url) {
1096             pdfmake_obj_t action;
1097             uint32_t s_key;
1098             uint32_t f_key2;
1099             uint32_t flags_key;
1100             uint32_t a_key;
1101              
1102 0           action = pdfmake_dict_new(arena);
1103 0           s_key = pdfmake_arena_intern_name(arena, "S", 1);
1104 0           pdfmake_dict_set(arena, &action, s_key,
1105             pdfmake_name_cstr(arena, "SubmitForm"));
1106 0           f_key2 = pdfmake_arena_intern_name(arena, "F", 1);
1107 0           pdfmake_dict_set(arena, &action, f_key2,
1108 0           pdfmake_str_cstr(arena, field->action_url));
1109             /* Flags: 0 = FDF, 4 = HTML, 8 = XFDF */
1110 0           flags_key = pdfmake_arena_intern_name(arena, "Flags", 5);
1111 0           pdfmake_dict_set(arena, &action, flags_key, pdfmake_int(4));
1112 0           a_key = pdfmake_arena_intern_name(arena, "A", 1);
1113 0           pdfmake_dict_set(arena, &field_dict, a_key, action);
1114 39 100         } else if (field->action_reset) {
1115             pdfmake_obj_t action;
1116             uint32_t s_key;
1117             uint32_t a_key;
1118              
1119 1           action = pdfmake_dict_new(arena);
1120 1           s_key = pdfmake_arena_intern_name(arena, "S", 1);
1121 1           pdfmake_dict_set(arena, &action, s_key,
1122             pdfmake_name_cstr(arena, "ResetForm"));
1123 1           a_key = pdfmake_arena_intern_name(arena, "A", 1);
1124 1           pdfmake_dict_set(arena, &field_dict, a_key, action);
1125 38 50         } else if (field->action_js) {
1126             pdfmake_obj_t action;
1127             uint32_t s_key;
1128             uint32_t js_key;
1129             uint32_t a_key;
1130              
1131 0           action = pdfmake_dict_new(arena);
1132 0           s_key = pdfmake_arena_intern_name(arena, "S", 1);
1133 0           pdfmake_dict_set(arena, &action, s_key,
1134             pdfmake_name_cstr(arena, "JavaScript"));
1135 0           js_key = pdfmake_arena_intern_name(arena, "JS", 2);
1136 0           pdfmake_dict_set(arena, &action, js_key,
1137 0           pdfmake_str_cstr(arena, field->action_js));
1138 0           a_key = pdfmake_arena_intern_name(arena, "A", 1);
1139 0           pdfmake_dict_set(arena, &field_dict, a_key, action);
1140             }
1141              
1142             /* Add field dictionary as indirect object */
1143 41           field->field_num = pdfmake_doc_add(doc, field_dict);
1144 41           field->widget_num = field->field_num; /* Merged field+widget */
1145            
1146             /* Add to page's /Annots array if we have a page */
1147 41 100         if (field->page) {
1148 40           pdfmake_page_add_annot(field->page, field->widget_num);
1149             }
1150            
1151 41           return PDFMAKE_OK;
1152             }
1153              
1154 14           pdfmake_err_t pdfmake_form_finalize(pdfmake_form_t *form)
1155             {
1156             pdfmake_doc_t *doc;
1157             pdfmake_arena_t *arena;
1158             int live_fields;
1159             size_t i;
1160             pdfmake_field_t *f;
1161             pdfmake_err_t err;
1162             pdfmake_obj_t acroform;
1163             uint32_t fields_key;
1164             pdfmake_obj_t fields_arr;
1165             pdfmake_obj_t dr;
1166             pdfmake_obj_t font_dict;
1167             pdfmake_obj_t helv;
1168             uint32_t type_key;
1169             uint32_t subtype_key;
1170             uint32_t basefont_key;
1171             uint32_t helv_num;
1172             uint32_t helv_key;
1173             uint32_t font_key;
1174             uint32_t dr_key;
1175              
1176 14 50         if (!form || !form->doc) return PDFMAKE_EINVAL;
    50          
1177 14 50         if (form->form_num) return PDFMAKE_OK; /* Already finalized */
1178              
1179 14           doc = form->doc;
1180 14           arena = doc->arena;
1181            
1182             /* Finalize non-flattened fields */
1183 14           live_fields = 0;
1184 58 100         for (i = 0; i < form->field_count; i++) {
1185 44           f = form->fields[i];
1186 44 100         if (f->flattened) continue;
1187 41           err = finalize_field(doc, f);
1188 41 50         if (err != PDFMAKE_OK) return err;
1189 41 50         if (f->field_num) live_fields++;
1190             }
1191              
1192             /* If all fields were flattened, don't emit AcroForm at all */
1193 14 100         if (live_fields == 0) return PDFMAKE_OK;
1194              
1195             /* Create AcroForm dictionary */
1196 11           acroform = pdfmake_dict_new(arena);
1197            
1198             /* /Fields array */
1199 11           fields_key = pdfmake_arena_intern_name(arena, "Fields", 6);
1200 11           fields_arr = pdfmake_array_new(arena);
1201            
1202 52 100         for (i = 0; i < form->field_count; i++) {
1203 41 50         if (form->fields[i]->field_num) {
1204 41           pdfmake_array_push(arena, &fields_arr,
1205 41           pdfmake_ref(form->fields[i]->field_num, 0));
1206             }
1207             }
1208 11           pdfmake_dict_set(arena, &acroform, fields_key, fields_arr);
1209            
1210             /* /DA - default appearance */
1211 11 50         if (form->da) {
1212             uint32_t da_key;
1213              
1214 11           da_key = pdfmake_arena_intern_name(arena, "DA", 2);
1215 11           pdfmake_dict_set(arena, &acroform, da_key,
1216 11           pdfmake_str(arena, form->da,
1217 11           strlen(form->da)));
1218             }
1219            
1220             /* /NeedAppearances */
1221 11 100         if (form->need_appearances) {
1222             uint32_t na_key;
1223              
1224 4           na_key = pdfmake_arena_intern_name(arena, "NeedAppearances", 15);
1225 4           pdfmake_dict_set(arena, &acroform, na_key, pdfmake_bool(1));
1226             }
1227            
1228             /* /DR - default resources (font for Helv) */
1229 11           dr = pdfmake_dict_new(arena);
1230 11           font_dict = pdfmake_dict_new(arena);
1231            
1232             /* /Helv -> Helvetica */
1233 11           helv = pdfmake_dict_new(arena);
1234 11           type_key = pdfmake_arena_intern_name(arena, "Type", 4);
1235 11           subtype_key = pdfmake_arena_intern_name(arena, "Subtype", 7);
1236 11           basefont_key = pdfmake_arena_intern_name(arena, "BaseFont", 8);
1237            
1238 11           pdfmake_dict_set(arena, &helv, type_key, pdfmake_name_cstr(arena, "Font"));
1239 11           pdfmake_dict_set(arena, &helv, subtype_key, pdfmake_name_cstr(arena, "Type1"));
1240 11           pdfmake_dict_set(arena, &helv, basefont_key, pdfmake_name_cstr(arena, "Helvetica"));
1241            
1242 11           helv_num = pdfmake_doc_add(doc, helv);
1243            
1244 11           helv_key = pdfmake_arena_intern_name(arena, "Helv", 4);
1245 11           pdfmake_dict_set(arena, &font_dict, helv_key, pdfmake_ref(helv_num, 0));
1246            
1247 11           font_key = pdfmake_arena_intern_name(arena, "Font", 4);
1248 11           pdfmake_dict_set(arena, &dr, font_key, font_dict);
1249            
1250 11           dr_key = pdfmake_arena_intern_name(arena, "DR", 2);
1251 11           pdfmake_dict_set(arena, &acroform, dr_key, dr);
1252            
1253             /* Add AcroForm to document */
1254 11           form->form_num = pdfmake_doc_add(doc, acroform);
1255            
1256 11           return PDFMAKE_OK;
1257             }
1258              
1259             /*============================================================================
1260             * Flatten
1261             *==========================================================================*/
1262              
1263 5           pdfmake_err_t pdfmake_field_flatten(pdfmake_field_t *field)
1264             {
1265             pdfmake_doc_t *doc;
1266             pdfmake_arena_t *arena;
1267             pdfmake_page_t *page;
1268             double x;
1269             double y;
1270             double width;
1271             double height;
1272             uint8_t *old_content;
1273             size_t old_len;
1274             pdfmake_obj_t *stream_obj;
1275             const uint8_t *sdata;
1276             size_t slen;
1277             pdfmake_content_t *c;
1278             pdfmake_buf_t *buf;
1279             double tx;
1280             double ty;
1281             const char *p;
1282             double size;
1283             const char *on_val;
1284             double cx;
1285             double cy;
1286             const uint8_t *new_data;
1287             size_t new_len;
1288             pdfmake_obj_t new_stream;
1289             uint32_t new_num;
1290              
1291 5 50         if (!field) return PDFMAKE_EINVAL;
1292 5 50         if (!field->page) return PDFMAKE_EINVAL; /* Field must be on a page */
1293              
1294 5           doc = field->doc;
1295 5           arena = doc->arena;
1296 5           page = field->page;
1297            
1298             /* Generate field value content for the page */
1299 5           x = field->rect.x1;
1300 5           y = field->rect.y1;
1301 5           width = field->rect.x2 - field->rect.x1;
1302 5           height = field->rect.y2 - field->rect.y1;
1303            
1304             /* Get existing content stream data */
1305 5           old_content = NULL;
1306 5           old_len = 0;
1307              
1308 5 100         if (page->has_content && page->contents_num > 0) {
    50          
1309 3           stream_obj = pdfmake_doc_get(doc, page->contents_num);
1310 3 50         if (stream_obj && stream_obj->kind == PDFMAKE_STREAM) {
    50          
1311 3           sdata = stream_obj->as.stream->raw;
1312 3           slen = stream_obj->as.stream->raw_len;
1313 3 100         if (sdata && slen > 0) {
    50          
1314 1           old_content = malloc(slen);
1315 1 50         if (old_content) {
1316 1           memcpy(old_content, sdata, slen);
1317 1           old_len = slen;
1318             }
1319             }
1320             }
1321             }
1322              
1323             /* Build new content stream with flattened field appended */
1324 5           c = pdfmake_content_new(arena);
1325 5 50         if (!c) { free(old_content); return PDFMAKE_ENOMEM; }
1326              
1327             /* Copy existing content */
1328 5 100         if (old_content && old_len > 0) {
    50          
1329 1           pdfmake_buf_append(&c->buf, old_content, old_len);
1330 1           pdfmake_buf_append_byte(&c->buf, '\n');
1331             }
1332 5           free(old_content);
1333              
1334 5           buf = &c->buf;
1335            
1336             /* Save graphics state */
1337 5           pdfmake_buf_appendf(buf, "q\n");
1338            
1339             /* Translate to field position */
1340 5           pdfmake_buf_appendf(buf, "1 0 0 1 %.4f %.4f cm\n", x, y);
1341            
1342             /* Clip to field rectangle */
1343 5           pdfmake_buf_appendf(buf, "0 0 %.4f %.4f re W n\n", width, height);
1344            
1345             /* Render based on field type */
1346 5           switch (field->type) {
1347 4           case PDFMAKE_FIELD_TEXT:
1348             case PDFMAKE_FIELD_CHOICE:
1349             /* White background */
1350 4           pdfmake_buf_appendf(buf, "1 1 1 rg 0 0 %.4f %.4f re f\n", width, height);
1351            
1352             /* Text value */
1353 4 100         if (field->value && field->value[0]) {
    50          
1354 3           pdfmake_buf_appendf(buf, "BT\n");
1355 3 50         pdfmake_buf_appendf(buf, "%s\n", field->da ? field->da : DEFAULT_DA);
1356            
1357 3           tx = 2;
1358 3           ty = (height - 12) / 2 + 2;
1359            
1360 3 50         if (field->quadding == PDFMAKE_QUADDING_CENTER) {
1361 0           tx = width / 2 - strlen(field->value) * 3;
1362 3 50         } else if (field->quadding == PDFMAKE_QUADDING_RIGHT) {
1363 0           tx = width - 2 - strlen(field->value) * 6;
1364             }
1365            
1366 3           pdfmake_buf_appendf(buf, "%.4f %.4f Td\n", tx, ty);
1367 3           pdfmake_buf_appendf(buf, "(");
1368 32 100         for (p = field->value; *p; p++) {
1369 29 50         if (*p == '(' || *p == ')' || *p == '\\') {
    50          
    50          
1370 0           pdfmake_buf_append_byte(buf, '\\');
1371             }
1372 29           pdfmake_buf_append_byte(buf, *p);
1373             }
1374 3           pdfmake_buf_appendf(buf, ") Tj\n");
1375 3           pdfmake_buf_appendf(buf, "ET\n");
1376             }
1377 4           break;
1378            
1379 1           case PDFMAKE_FIELD_BUTTON:
1380 1 50         if (field->flags & PDFMAKE_FF_PUSHBUTTON) {
1381             /* Pushbutton appearance */
1382 0           pdfmake_buf_appendf(buf, "0.8 0.8 0.8 rg 0 0 %.4f %.4f re f\n", width, height);
1383 0           pdfmake_buf_appendf(buf, "1 1 1 RG 1 w 0 0 m %.4f 0 l %.4f %.4f l s\n",
1384             width, width, height);
1385 0           pdfmake_buf_appendf(buf, "0.4 0.4 0.4 RG 0 0 m 0 %.4f l %.4f %.4f l s\n",
1386             height, width, height);
1387            
1388 0 0         if (field->value && field->value[0]) {
    0          
1389 0           pdfmake_buf_appendf(buf, "BT /Helv 10 Tf 0 g\n");
1390 0           tx = width / 2 - strlen(field->value) * 2.5;
1391 0           ty = height / 2 - 4;
1392 0           pdfmake_buf_appendf(buf, "%.4f %.4f Td (", tx, ty);
1393 0 0         for (p = field->value; *p; p++) {
1394 0 0         if (*p == '(' || *p == ')' || *p == '\\') {
    0          
    0          
1395 0           pdfmake_buf_append_byte(buf, '\\');
1396             }
1397 0           pdfmake_buf_append_byte(buf, *p);
1398             }
1399 0           pdfmake_buf_appendf(buf, ") Tj ET\n");
1400             }
1401             } else {
1402             /* Checkbox/radio appearance */
1403 1 50         size = (width < height ? width : height) - 2;
1404            
1405             /* Box */
1406 1           pdfmake_buf_appendf(buf, "1 1 1 rg 0 0 %.4f %.4f re f\n", width, height);
1407 1           pdfmake_buf_appendf(buf, "0 0 0 RG 0.5 w 1 1 %.4f %.4f re s\n",
1408             width - 2, height - 2);
1409            
1410             /* Check if selected */
1411 1 50         on_val = field->on_value ? field->on_value : "Yes";
1412 1 50         if (field->value && strcmp(field->value, "Off") != 0 &&
    0          
1413 0 0         strcmp(field->value, on_val) == 0) {
1414             /* Draw checkmark */
1415 0           cx = width / 2;
1416 0           cy = height / 2;
1417 0           pdfmake_buf_appendf(buf, "1 w\n");
1418 0           pdfmake_buf_appendf(buf, "%.4f %.4f m\n", cx - size * 0.3, cy);
1419 0           pdfmake_buf_appendf(buf, "%.4f %.4f l\n", cx - size * 0.1, cy - size * 0.25);
1420 0           pdfmake_buf_appendf(buf, "%.4f %.4f l S\n", cx + size * 0.35, cy + size * 0.3);
1421             }
1422             }
1423 1           break;
1424            
1425 0           case PDFMAKE_FIELD_SIGNATURE:
1426             /* Signature fields: just render placeholder */
1427 0           pdfmake_buf_appendf(buf, "0.9 0.9 0.9 rg 0 0 %.4f %.4f re f\n", width, height);
1428 0           pdfmake_buf_appendf(buf, "0 0 0 RG 0.5 w 0 0 %.4f %.4f re s\n", width, height);
1429 0           break;
1430             }
1431            
1432             /* Restore graphics state */
1433 5           pdfmake_buf_appendf(buf, "Q\n");
1434              
1435             /* Replace content stream */
1436 5           new_data = pdfmake_content_data(c);
1437 5           new_len = pdfmake_content_len(c);
1438              
1439 5           new_stream = pdfmake_stream_new(arena);
1440 5           pdfmake_stream_set_data(arena, &new_stream, new_data, new_len);
1441 5           new_num = pdfmake_doc_add(doc, new_stream);
1442              
1443 5           page->contents_num = new_num;
1444 5           page->has_content = 1;
1445              
1446 5           pdfmake_content_free(c);
1447            
1448             /* Mark field as flattened */
1449 5           field->widget_num = 0;
1450 5           field->field_num = 0;
1451 5           field->flattened = 1;
1452            
1453 5           return PDFMAKE_OK;
1454             }
1455              
1456 3           pdfmake_err_t pdfmake_form_flatten(pdfmake_form_t *form)
1457             {
1458             size_t i;
1459             pdfmake_err_t err;
1460              
1461 3 50         if (!form) return PDFMAKE_EINVAL;
1462              
1463 7 100         for (i = 0; i < form->field_count; i++) {
1464 4           err = pdfmake_field_flatten(form->fields[i]);
1465 4 50         if (err != PDFMAKE_OK) return err;
1466             }
1467            
1468 3           return PDFMAKE_OK;
1469             }
1470              
1471             /*============================================================================
1472             * Form data export/import
1473             *==========================================================================*/
1474              
1475 3           pdfmake_err_t pdfmake_form_export_fdf(pdfmake_form_t *form, pdfmake_buf_t *out)
1476             {
1477             size_t i;
1478             pdfmake_field_t *field;
1479             const char *p;
1480              
1481 3 50         if (!form || !out) return PDFMAKE_EINVAL;
    50          
1482            
1483             /* FDF header */
1484 3           pdfmake_buf_appendf(out, "%%FDF-1.2\n");
1485 3           pdfmake_buf_appendf(out, "1 0 obj\n");
1486 3           pdfmake_buf_appendf(out, "<<\n");
1487 3           pdfmake_buf_appendf(out, "/FDF <<\n");
1488 3           pdfmake_buf_appendf(out, "/Fields [\n");
1489            
1490             /* Field values */
1491 9 100         for (i = 0; i < form->field_count; i++) {
1492 6           field = form->fields[i];
1493 6 50         if (!field->full_name) continue;
1494 6 100         if (field->flags & PDFMAKE_FF_NOEXPORT) continue; /* Skip noexport fields */
1495            
1496 5           pdfmake_buf_appendf(out, "<< /T (%s)", field->full_name);
1497            
1498 5 50         if (field->value) {
1499 5 100         if (field->type == PDFMAKE_FIELD_BUTTON &&
1500 1 50         !(field->flags & PDFMAKE_FF_PUSHBUTTON)) {
1501 0           pdfmake_buf_appendf(out, " /V /%s", field->value);
1502             } else {
1503 5           pdfmake_buf_appendf(out, " /V (");
1504 46 100         for (p = field->value; *p; p++) {
1505 41 50         if (*p == '(' || *p == ')' || *p == '\\') {
    50          
    50          
1506 0           pdfmake_buf_append_byte(out, '\\');
1507             }
1508 41           pdfmake_buf_append_byte(out, *p);
1509             }
1510 5           pdfmake_buf_appendf(out, ")");
1511             }
1512             }
1513            
1514 5           pdfmake_buf_appendf(out, " >>\n");
1515             }
1516            
1517 3           pdfmake_buf_appendf(out, "]\n");
1518 3           pdfmake_buf_appendf(out, ">>\n");
1519 3           pdfmake_buf_appendf(out, ">>\n");
1520 3           pdfmake_buf_appendf(out, "endobj\n");
1521 3           pdfmake_buf_appendf(out, "trailer\n");
1522 3           pdfmake_buf_appendf(out, "<< /Root 1 0 R >>\n");
1523 3           pdfmake_buf_appendf(out, "%%%%EOF\n");
1524            
1525 3           return PDFMAKE_OK;
1526             }
1527              
1528 3           pdfmake_err_t pdfmake_form_export_xfdf(pdfmake_form_t *form, pdfmake_buf_t *out)
1529             {
1530             size_t i;
1531             pdfmake_field_t *field;
1532              
1533 3 50         if (!form || !out) return PDFMAKE_EINVAL;
    50          
1534            
1535             /* XFDF header */
1536 3           pdfmake_buf_appendf(out, "\n");
1537 3           pdfmake_buf_appendf(out, "\n");
1538 3           pdfmake_buf_appendf(out, "\n");
1539            
1540             /* Field values */
1541 9 100         for (i = 0; i < form->field_count; i++) {
1542 6           field = form->fields[i];
1543 6 50         if (!field->full_name) continue;
1544 6 100         if (field->flags & PDFMAKE_FF_NOEXPORT) continue; /* Skip noexport fields */
1545            
1546 5           pdfmake_buf_appendf(out, "\n", field->full_name);
1547 5 50         if (field->value) {
1548 5           pdfmake_buf_appendf(out, "%s\n", field->value);
1549             }
1550 5           pdfmake_buf_appendf(out, "\n");
1551             }
1552            
1553 3           pdfmake_buf_appendf(out, "\n");
1554 3           pdfmake_buf_appendf(out, "\n");
1555            
1556 3           return PDFMAKE_OK;
1557             }
1558              
1559 1           pdfmake_err_t pdfmake_form_import_fdf(pdfmake_form_t *form,
1560             const uint8_t *data, size_t len)
1561             {
1562             char *buf;
1563             const char *p;
1564             const char *end;
1565             const char *t_pos;
1566             const char *v_pos;
1567             const char *next_field;
1568             char field_name[256];
1569             char value[1024];
1570             size_t i;
1571             int escape;
1572             int paren_depth;
1573             pdfmake_field_t *field;
1574              
1575 1 50         if (!form || !data || len == 0) return PDFMAKE_EINVAL;
    50          
    50          
1576            
1577             /* Copy to null-terminated buffer for strstr */
1578 1           buf = malloc(len + 1);
1579 1 50         if (!buf) return PDFMAKE_ENOMEM;
1580 1           memcpy(buf, data, len);
1581 1           buf[len] = '\0';
1582            
1583             /* Simple FDF parser - looks for /T (field name) and /V (value) pairs */
1584 1           p = buf;
1585 1           end = buf + len;
1586            
1587 3 50         while (p < end) {
1588 3           field_name[0] = '\0';
1589 3           value[0] = '\0';
1590              
1591             /* Find /T (field name) */
1592 3           t_pos = strstr(p, "/T ");
1593 3 100         if (!t_pos || t_pos >= end) break;
    50          
1594            
1595 2           t_pos += 3; /* Skip "/T " */
1596            
1597             /* Skip whitespace */
1598 2 50         while (t_pos < end && (*t_pos == ' ' || *t_pos == '\n' || *t_pos == '\r'))
    50          
    50          
    50          
1599 0           t_pos++;
1600            
1601 2 50         if (t_pos >= end) break;
1602            
1603             /* Extract field name (expecting string literal or name) */
1604 2 50         if (*t_pos == '(') {
1605             /* String literal */
1606 2           i = 0;
1607 2           escape = 0;
1608              
1609 2           t_pos++;
1610 19 50         while (t_pos < end && i < sizeof(field_name) - 1) {
    50          
1611 19 50         if (escape) {
1612 0           field_name[i++] = *t_pos;
1613 0           escape = 0;
1614 19 50         } else if (*t_pos == '\\') {
1615 0           escape = 1;
1616 19 100         } else if (*t_pos == ')') {
1617 2           break;
1618             } else {
1619 17           field_name[i++] = *t_pos;
1620             }
1621 17           t_pos++;
1622             }
1623 2           field_name[i] = '\0';
1624 0 0         } else if (*t_pos == '/') {
1625             /* Name object */
1626 0           i = 0;
1627              
1628 0           t_pos++;
1629 0 0         while (t_pos < end && i < sizeof(field_name) - 1 &&
1630 0 0         *t_pos != ' ' && *t_pos != '/' && *t_pos != '>' &&
    0          
    0          
1631 0 0         *t_pos != '\n' && *t_pos != '\r') {
    0          
    0          
1632 0           field_name[i++] = *t_pos++;
1633             }
1634 0           field_name[i] = '\0';
1635             }
1636            
1637 2 50         if (!field_name[0]) {
1638 0           p = t_pos;
1639 0           continue;
1640             }
1641            
1642             /* Look for /V (value) after the field name */
1643 2           v_pos = strstr(t_pos, "/V ");
1644 2 50         if (!v_pos || v_pos >= end) {
    50          
1645 0           p = t_pos;
1646 0           continue;
1647             }
1648            
1649             /* Make sure we haven't gone past the next field definition */
1650 2           next_field = strstr(t_pos, "/T ");
1651 2 100         if (next_field && next_field < v_pos) {
    50          
1652 0           p = t_pos;
1653 0           continue;
1654             }
1655            
1656 2           v_pos += 3; /* Skip "/V " */
1657            
1658             /* Skip whitespace */
1659 2 50         while (v_pos < end && (*v_pos == ' ' || *v_pos == '\n' || *v_pos == '\r'))
    50          
    50          
    50          
1660 0           v_pos++;
1661            
1662 2 50         if (v_pos >= end) {
1663 0           p = t_pos;
1664 0           continue;
1665             }
1666            
1667             /* Extract value */
1668 2 50         if (*v_pos == '(') {
1669             /* String literal */
1670 2           i = 0;
1671 2           escape = 0;
1672 2           paren_depth = 1;
1673              
1674 2           v_pos++;
1675 11 50         while (v_pos < end && i < sizeof(value) - 1 && paren_depth > 0) {
    50          
    100          
1676 9 50         if (escape) {
1677 0           value[i++] = *v_pos;
1678 0           escape = 0;
1679 9 50         } else if (*v_pos == '\\') {
1680 0           escape = 1;
1681 9 50         } else if (*v_pos == '(') {
1682 0           paren_depth++;
1683 0           value[i++] = *v_pos;
1684 9 100         } else if (*v_pos == ')') {
1685 2           paren_depth--;
1686 2 50         if (paren_depth > 0) value[i++] = *v_pos;
1687             } else {
1688 7           value[i++] = *v_pos;
1689             }
1690 9           v_pos++;
1691             }
1692 2           value[i] = '\0';
1693 0 0         } else if (*v_pos == '/') {
1694             /* Name object (for checkbox/radio) */
1695 0           i = 0;
1696              
1697 0           v_pos++;
1698 0 0         while (v_pos < end && i < sizeof(value) - 1 &&
1699 0 0         *v_pos != ' ' && *v_pos != '/' && *v_pos != '>' &&
    0          
    0          
1700 0 0         *v_pos != '\n' && *v_pos != '\r') {
    0          
    0          
1701 0           value[i++] = *v_pos++;
1702             }
1703 0           value[i] = '\0';
1704             }
1705            
1706             /* Find the field and set its value */
1707 2           field = pdfmake_form_field_by_name(form, field_name);
1708 2 50         if (field) {
1709 2           pdfmake_field_set_value(field, value);
1710             }
1711            
1712 2           p = v_pos;
1713             }
1714            
1715 1           free(buf);
1716 1           return PDFMAKE_OK;
1717             }
1718              
1719 1           pdfmake_err_t pdfmake_form_import_xfdf(pdfmake_form_t *form,
1720             const uint8_t *data, size_t len)
1721             {
1722             char *buf;
1723             const char *p;
1724             const char *end;
1725             const char *field_tag;
1726             const char *name_attr;
1727             char field_name[256];
1728             size_t i;
1729             const char *value_tag;
1730             const char *field_end;
1731             const char *value_end;
1732             char value[4096];
1733             const char *vp;
1734             int code;
1735             pdfmake_field_t *field;
1736              
1737 1 50         if (!form || !data || len == 0) return PDFMAKE_EINVAL;
    50          
    50          
1738            
1739             /* Copy to null-terminated buffer for strstr */
1740 1           buf = malloc(len + 1);
1741 1 50         if (!buf) return PDFMAKE_ENOMEM;
1742 1           memcpy(buf, data, len);
1743 1           buf[len] = '\0';
1744            
1745             /* Simple XFDF parser - looks for ... */
1746 1           p = buf;
1747 1           end = buf + len;
1748            
1749 3 50         while (p < end) {
1750 3           field_name[0] = '\0';
1751              
1752             /* Find
1753 3           field_tag = strstr(p, "
1754 3 100         if (!field_tag || field_tag >= end) break;
    50          
1755            
1756 2           field_tag += 7; /* Skip "
1757            
1758             /* Find name attribute */
1759 2           name_attr = strstr(field_tag, "name=\"");
1760 2 50         if (!name_attr || name_attr >= end) {
    50          
1761 0           p = field_tag;
1762 0           continue;
1763             }
1764            
1765 2           name_attr += 6; /* Skip 'name="' */
1766            
1767             /* Extract field name */
1768 2           i = 0;
1769 14 50         while (name_attr < end && *name_attr != '"' && i < sizeof(field_name) - 1) {
    100          
    50          
1770             /* Handle XML entities */
1771 12 50         if (*name_attr == '&') {
1772 0 0         if (strncmp(name_attr, "&", 5) == 0) {
1773 0           field_name[i++] = '&';
1774 0           name_attr += 5;
1775 0 0         } else if (strncmp(name_attr, "<", 4) == 0) {
1776 0           field_name[i++] = '<';
1777 0           name_attr += 4;
1778 0 0         } else if (strncmp(name_attr, ">", 4) == 0) {
1779 0           field_name[i++] = '>';
1780 0           name_attr += 4;
1781 0 0         } else if (strncmp(name_attr, """, 6) == 0) {
1782 0           field_name[i++] = '"';
1783 0           name_attr += 6;
1784 0 0         } else if (strncmp(name_attr, "'", 6) == 0) {
1785 0           field_name[i++] = '\'';
1786 0           name_attr += 6;
1787             } else {
1788 0           field_name[i++] = *name_attr++;
1789             }
1790             } else {
1791 12           field_name[i++] = *name_attr++;
1792             }
1793             }
1794 2           field_name[i] = '\0';
1795            
1796 2 50         if (!field_name[0]) {
1797 0           p = name_attr;
1798 0           continue;
1799             }
1800            
1801             /* Find tag */
1802 2           value_tag = strstr(name_attr, "");
1803 2 50         if (!value_tag || value_tag >= end) {
    50          
1804             /* Check if field is empty (self-closing or no value tag) */
1805 0           field_end = strstr(name_attr, "");
1806 0 0         if (field_end && field_end < end) {
    0          
1807 0           p = field_end + 8;
1808             } else {
1809 0           p = name_attr;
1810             }
1811 0           continue;
1812             }
1813            
1814 2           value_tag += 7; /* Skip "" */
1815            
1816             /* Find */
1817 2           value_end = strstr(value_tag, "");
1818 2 50         if (!value_end || value_end >= end) {
    50          
1819 0           p = value_tag;
1820 0           continue;
1821             }
1822            
1823             /* Extract value with XML entity decoding */
1824 2           value[0] = '\0';
1825 2           i = 0;
1826 2           vp = value_tag;
1827 27 100         while (vp < value_end && i < sizeof(value) - 1) {
    50          
1828 25 100         if (*vp == '&') {
1829 1 50         if (strncmp(vp, "&", 5) == 0) {
1830 1           value[i++] = '&';
1831 1           vp += 5;
1832 0 0         } else if (strncmp(vp, "<", 4) == 0) {
1833 0           value[i++] = '<';
1834 0           vp += 4;
1835 0 0         } else if (strncmp(vp, ">", 4) == 0) {
1836 0           value[i++] = '>';
1837 0           vp += 4;
1838 0 0         } else if (strncmp(vp, """, 6) == 0) {
1839 0           value[i++] = '"';
1840 0           vp += 6;
1841 0 0         } else if (strncmp(vp, "'", 6) == 0) {
1842 0           value[i++] = '\'';
1843 0           vp += 6;
1844 0 0         } else if (strncmp(vp, "&#", 2) == 0) {
1845             /* Numeric entity */
1846 0           vp += 2;
1847 0           code = 0;
1848 0 0         if (*vp == 'x' || *vp == 'X') {
    0          
1849 0           vp++;
1850 0 0         while (vp < value_end && *vp != ';') {
    0          
1851 0 0         if (*vp >= '0' && *vp <= '9')
    0          
1852 0           code = code * 16 + (*vp - '0');
1853 0 0         else if (*vp >= 'a' && *vp <= 'f')
    0          
1854 0           code = code * 16 + (*vp - 'a' + 10);
1855 0 0         else if (*vp >= 'A' && *vp <= 'F')
    0          
1856 0           code = code * 16 + (*vp - 'A' + 10);
1857 0           vp++;
1858             }
1859             } else {
1860 0 0         while (vp < value_end && *vp != ';') {
    0          
1861 0 0         if (*vp >= '0' && *vp <= '9')
    0          
1862 0           code = code * 10 + (*vp - '0');
1863 0           vp++;
1864             }
1865             }
1866 0 0         if (code > 0 && code < 128) value[i++] = (char)code;
    0          
1867 0 0         if (vp < value_end && *vp == ';') vp++;
    0          
1868             } else {
1869 0           value[i++] = *vp++;
1870             }
1871             } else {
1872 24           value[i++] = *vp++;
1873             }
1874             }
1875 2           value[i] = '\0';
1876            
1877             /* Find the field and set its value */
1878 2           field = pdfmake_form_field_by_name(form, field_name);
1879 2 50         if (field) {
1880 2           pdfmake_field_set_value(field, value);
1881             }
1882            
1883 2           p = value_end + 8; /* Skip "" */
1884             }
1885            
1886 1           free(buf);
1887 1           return PDFMAKE_OK;
1888             }
1889              
1890             /*============================================================================
1891             * Reading forms from existing PDFs
1892             *==========================================================================*/
1893              
1894             /* Helper: get dict value by name string */
1895 0           static pdfmake_obj_t *get_dict_entry(pdfmake_arena_t *arena, pdfmake_dict_t *dict,
1896             const char *key_name)
1897             {
1898             uint32_t key_id;
1899             size_t i;
1900              
1901 0 0         if (!dict || !key_name) return NULL;
    0          
1902              
1903 0           key_id = pdfmake_arena_intern_name(arena, key_name, strlen(key_name));
1904              
1905 0 0         for (i = 0; i < dict->cap; i++) {
1906 0 0         if (dict->entries[i].key == key_id && !dict->entries[i].deleted) {
    0          
1907 0           return &dict->entries[i].value;
1908             }
1909             }
1910 0           return NULL;
1911             }
1912              
1913             /* Helper: extract string value from obj */
1914 0           static const char *get_string_value(pdfmake_obj_t *obj, char *buf, size_t buf_len)
1915             {
1916 0 0         if (!obj || !buf) return NULL;
    0          
1917            
1918 0 0         if (obj->kind == PDFMAKE_STR) {
1919 0           size_t copy_len = obj->as.str.len < buf_len - 1 ? obj->as.str.len : buf_len - 1;
1920 0           memcpy(buf, obj->as.str.bytes, copy_len);
1921 0           buf[copy_len] = '\0';
1922 0           return buf;
1923 0 0         } else if (obj->kind == PDFMAKE_NAME) {
1924             /* Would need to look up name in arena - simplified for now */
1925 0           return NULL;
1926             }
1927 0           return NULL;
1928             }
1929              
1930             /* Helper: resolve indirect reference */
1931 0           static pdfmake_obj_t *resolve_ref(pdfmake_doc_t *doc, pdfmake_obj_t *obj)
1932             {
1933 0 0         if (!obj) return NULL;
1934 0 0         if (obj->kind == PDFMAKE_REF) {
1935 0           return pdfmake_doc_get(doc, obj->as.ref.num);
1936             }
1937 0           return obj;
1938             }
1939              
1940             /* Parse a single field from field dictionary */
1941 0           static pdfmake_err_t parse_field_dict(pdfmake_form_t *form, pdfmake_obj_t *field_obj,
1942             pdfmake_field_t *parent)
1943             {
1944             pdfmake_doc_t *doc;
1945             pdfmake_arena_t *arena;
1946             pdfmake_dict_t *dict;
1947             pdfmake_field_type_t type;
1948             pdfmake_obj_t *ft_obj;
1949 0           char name_buf[256] = "";
1950             pdfmake_obj_t *t_obj;
1951 0           pdfmake_rect_t rect = {0, 0, 100, 20};
1952             pdfmake_obj_t *rect_obj;
1953             pdfmake_array_t *arr;
1954             pdfmake_field_t *field;
1955             pdfmake_obj_t *v_obj;
1956             pdfmake_obj_t *ff_obj;
1957             pdfmake_obj_t *da_obj;
1958             pdfmake_obj_t *q_obj;
1959             pdfmake_obj_t *kids_obj;
1960             size_t i;
1961              
1962 0 0         if (!form || !field_obj) return PDFMAKE_EINVAL;
    0          
1963              
1964 0           doc = form->doc;
1965 0           arena = doc->arena;
1966            
1967             /* Resolve if indirect reference */
1968 0           field_obj = resolve_ref(doc, field_obj);
1969 0 0         if (!field_obj || field_obj->kind != PDFMAKE_DICT) return PDFMAKE_EINVAL;
    0          
1970            
1971 0           dict = field_obj->as.dict;
1972            
1973             /* Get field type /FT */
1974 0           type = PDFMAKE_FIELD_TEXT; /* Default */
1975 0           ft_obj = get_dict_entry(arena, dict, "FT");
1976 0 0         if (ft_obj && ft_obj->kind == PDFMAKE_NAME) {
1977             /* Check name - this is simplified, would need name table lookup */
1978             }
1979            
1980             /* Get field name /T */
1981 0           t_obj = get_dict_entry(arena, dict, "T");
1982 0           get_string_value(t_obj, name_buf, sizeof(name_buf));
1983            
1984             /* Get field rect (from widget annotation or /Rect) */
1985 0           rect_obj = get_dict_entry(arena, dict, "Rect");
1986 0 0         if (rect_obj && rect_obj->kind == PDFMAKE_ARRAY && rect_obj->as.arr->len >= 4) {
    0          
    0          
1987 0           arr = rect_obj->as.arr;
1988 0 0         if (arr->items[0].kind == PDFMAKE_REAL || arr->items[0].kind == PDFMAKE_INT)
    0          
1989 0 0         rect.x1 = arr->items[0].kind == PDFMAKE_REAL ? arr->items[0].as.r : arr->items[0].as.i;
1990 0 0         if (arr->items[1].kind == PDFMAKE_REAL || arr->items[1].kind == PDFMAKE_INT)
    0          
1991 0 0         rect.y1 = arr->items[1].kind == PDFMAKE_REAL ? arr->items[1].as.r : arr->items[1].as.i;
1992 0 0         if (arr->items[2].kind == PDFMAKE_REAL || arr->items[2].kind == PDFMAKE_INT)
    0          
1993 0 0         rect.x2 = arr->items[2].kind == PDFMAKE_REAL ? arr->items[2].as.r : arr->items[2].as.i;
1994 0 0         if (arr->items[3].kind == PDFMAKE_REAL || arr->items[3].kind == PDFMAKE_INT)
    0          
1995 0 0         rect.y2 = arr->items[3].kind == PDFMAKE_REAL ? arr->items[3].as.r : arr->items[3].as.i;
1996             }
1997            
1998             /* Create field structure */
1999 0           field = create_field(doc, type, name_buf, rect);
2000 0 0         if (!field) return PDFMAKE_ENOMEM;
2001            
2002             /* Set parent */
2003 0           field->parent = parent;
2004            
2005             /* Get value /V */
2006 0           v_obj = get_dict_entry(arena, dict, "V");
2007 0 0         if (v_obj) {
2008 0           char value_buf[4096] = "";
2009 0 0         if (get_string_value(v_obj, value_buf, sizeof(value_buf))) {
2010 0           field->value = pdfmake_arena_strdup(arena, value_buf);
2011             }
2012             }
2013            
2014             /* Get flags /Ff */
2015 0           ff_obj = get_dict_entry(arena, dict, "Ff");
2016 0 0         if (ff_obj && ff_obj->kind == PDFMAKE_INT) {
    0          
2017 0           field->flags = (uint32_t)ff_obj->as.i;
2018             }
2019            
2020             /* Get default appearance /DA */
2021 0           da_obj = get_dict_entry(arena, dict, "DA");
2022 0 0         if (da_obj) {
2023 0           char da_buf[256] = "";
2024 0 0         if (get_string_value(da_obj, da_buf, sizeof(da_buf))) {
2025 0           field->da = pdfmake_arena_strdup(arena, da_buf);
2026             }
2027             }
2028            
2029             /* Get quadding /Q */
2030 0           q_obj = get_dict_entry(arena, dict, "Q");
2031 0 0         if (q_obj && q_obj->kind == PDFMAKE_INT) {
    0          
2032 0           field->quadding = (pdfmake_quadding_t)q_obj->as.i;
2033             }
2034            
2035             /* Add to form */
2036 0           add_field_to_form(form, field);
2037            
2038             /* Process children /Kids */
2039 0           kids_obj = get_dict_entry(arena, dict, "Kids");
2040 0 0         if (kids_obj) {
2041 0           kids_obj = resolve_ref(doc, kids_obj);
2042 0 0         if (kids_obj && kids_obj->kind == PDFMAKE_ARRAY) {
    0          
2043 0 0         for (i = 0; i < kids_obj->as.arr->len; i++) {
2044 0           parse_field_dict(form, &kids_obj->as.arr->items[i], field);
2045             }
2046             }
2047             }
2048            
2049 0           return PDFMAKE_OK;
2050             }
2051              
2052 0           pdfmake_err_t pdfmake_form_parse(pdfmake_form_t *form)
2053             {
2054             pdfmake_doc_t *doc;
2055             pdfmake_arena_t *arena;
2056             pdfmake_obj_t *catalog;
2057             pdfmake_obj_t *acroform_obj;
2058             pdfmake_dict_t *acroform;
2059             pdfmake_obj_t *da_obj;
2060             pdfmake_obj_t *na_obj;
2061             pdfmake_obj_t *sf_obj;
2062             pdfmake_obj_t *fields_obj;
2063             size_t i;
2064             pdfmake_err_t err;
2065              
2066 0 0         if (!form) return PDFMAKE_EINVAL;
2067              
2068 0           doc = form->doc;
2069 0           arena = doc->arena;
2070            
2071             /* Get document catalog */
2072 0 0         if (doc->root_num == 0) return PDFMAKE_EINVAL;
2073            
2074 0           catalog = pdfmake_doc_get(doc, doc->root_num);
2075 0 0         if (!catalog || catalog->kind != PDFMAKE_DICT) return PDFMAKE_EINVAL;
    0          
2076            
2077             /* Get /AcroForm dictionary */
2078 0           acroform_obj = get_dict_entry(arena, catalog->as.dict, "AcroForm");
2079 0 0         if (!acroform_obj) return PDFMAKE_OK; /* No form - not an error */
2080            
2081 0           acroform_obj = resolve_ref(doc, acroform_obj);
2082 0 0         if (!acroform_obj || acroform_obj->kind != PDFMAKE_DICT) return PDFMAKE_EINVAL;
    0          
2083            
2084 0           acroform = acroform_obj->as.dict;
2085            
2086             /* Get form-wide DA */
2087 0           da_obj = get_dict_entry(arena, acroform, "DA");
2088 0 0         if (da_obj) {
2089 0           char da_buf[256] = "";
2090 0 0         if (get_string_value(da_obj, da_buf, sizeof(da_buf))) {
2091 0           form->da = pdfmake_arena_strdup(arena, da_buf);
2092             }
2093             }
2094            
2095             /* Get NeedAppearances */
2096 0           na_obj = get_dict_entry(arena, acroform, "NeedAppearances");
2097 0 0         if (na_obj && na_obj->kind == PDFMAKE_BOOL) {
    0          
2098 0           form->need_appearances = na_obj->as.b;
2099             }
2100            
2101             /* Get SigFlags */
2102 0           sf_obj = get_dict_entry(arena, acroform, "SigFlags");
2103 0 0         if (sf_obj && sf_obj->kind == PDFMAKE_INT) {
    0          
2104 0           form->sig_flags = (int)sf_obj->as.i;
2105             }
2106            
2107             /* Parse /Fields array */
2108 0           fields_obj = get_dict_entry(arena, acroform, "Fields");
2109 0 0         if (!fields_obj) return PDFMAKE_OK; /* No fields */
2110            
2111 0           fields_obj = resolve_ref(doc, fields_obj);
2112 0 0         if (!fields_obj || fields_obj->kind != PDFMAKE_ARRAY) return PDFMAKE_EINVAL;
    0          
2113            
2114             /* Parse each top-level field */
2115 0 0         for (i = 0; i < fields_obj->as.arr->len; i++) {
2116 0           err = parse_field_dict(form, &fields_obj->as.arr->items[i], NULL);
2117 0 0         if (err != PDFMAKE_OK) return err;
2118             }
2119            
2120 0           return PDFMAKE_OK;
2121             }