File Coverage

src/pdfmake_tag.c
Criterion Covered Total %
statement 104 179 58.1
branch 53 128 41.4
condition n/a
subroutine n/a
pod n/a
total 157 307 51.1


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_tag.c — Tagged PDF / Logical Structure
3             *
4             * §14.6 Marked Content, §14.7 Logical Structure, §14.8 Tagged PDF
5             */
6              
7             #include "pdfmake_tag.h"
8             #include "pdfmake_page.h"
9             #include "pdfmake_arena.h"
10             #include
11             #include
12             #include
13              
14             /* ── Type names ────────────────────────────────────────── */
15              
16             static const char *_struct_type_names[] = {
17             "Document", "Part", "Art", "Sect", "Div",
18             "BlockQuote", "Caption", "TOC", "TOCI",
19             "P", "H", "H1", "H2", "H3", "H4", "H5", "H6",
20             "L", "LI", "Lbl", "LBody",
21             "Table", "TR", "TH", "TD", "THead", "TBody", "TFoot",
22             "Span", "Quote", "Note", "Reference", "Code", "Link", "Annot",
23             "Figure", "Formula", "Form",
24             };
25              
26 11           const char *pdfmake_struct_type_name(pdfmake_struct_type_t type) {
27 11 50         if (type < 0 || type >= PDFMAKE_TAG_COUNT) return "Div";
28 11           return _struct_type_names[type];
29             }
30              
31 5           int pdfmake_struct_type_lookup(const char *name) {
32             int i;
33 5 50         if (!name) return -1;
34 80 50         for (i = 0; i < PDFMAKE_TAG_COUNT; i++) {
35 80 100         if (strcmp(name, _struct_type_names[i]) == 0) return i;
36             }
37 0           return -1;
38             }
39              
40             /* ── Document-level ────────────────────────────────────── */
41              
42 167           pdfmake_struct_tree_t *pdfmake_doc_create_struct_tree(pdfmake_doc_t *doc) {
43             pdfmake_struct_tree_t *tree;
44              
45 167 50         if (!doc) return NULL;
46              
47             /* Reuse if already exists on this document */
48 167 100         if (doc->struct_tree) return (pdfmake_struct_tree_t *)doc->struct_tree;
49              
50 165           tree = calloc(1, sizeof(*tree));
51 165 50         if (!tree) return NULL;
52              
53             /* Create root Document element */
54 165           tree->root = calloc(1, sizeof(pdfmake_struct_elem_t));
55 165 50         if (!tree->root) { free(tree); return NULL; }
56 165           tree->root->type = PDFMAKE_TAG_DOCUMENT;
57              
58 165           doc->struct_tree = tree;
59 165           return tree;
60             }
61              
62 0           pdfmake_struct_tree_t *pdfmake_doc_struct_tree(pdfmake_doc_t *doc) {
63 0 0         return doc ? (pdfmake_struct_tree_t *)doc->struct_tree : NULL;
64             }
65              
66             /* ── Structure elements ────────────────────────────────── */
67              
68 5           pdfmake_struct_elem_t *pdfmake_struct_elem_create(
69             pdfmake_doc_t *doc,
70             pdfmake_struct_type_t type,
71             pdfmake_struct_elem_t *parent)
72             {
73             pdfmake_struct_elem_t *elem;
74             size_t new_cap;
75             pdfmake_struct_elem_t **new_arr;
76              
77             (void)doc;
78 5           elem = calloc(1, sizeof(*elem));
79 5 50         if (!elem) return NULL;
80              
81 5           elem->type = type;
82 5           elem->parent = parent;
83              
84             /* Add to parent's children */
85 5 50         if (parent) {
86 5 100         if (parent->child_count >= parent->child_cap) {
87 2 50         new_cap = parent->child_cap == 0 ? 8 : parent->child_cap * 2;
88 2           new_arr = realloc(parent->children,
89             new_cap * sizeof(pdfmake_struct_elem_t *));
90 2 50         if (!new_arr) { free(elem); return NULL; }
91 2           parent->children = new_arr;
92 2           parent->child_cap = new_cap;
93             }
94 5           parent->children[parent->child_count++] = elem;
95             }
96              
97 5           return elem;
98             }
99              
100 0           pdfmake_struct_elem_t *pdfmake_struct_elem_create_custom(
101             pdfmake_doc_t *doc,
102             const char *custom_type,
103             pdfmake_struct_elem_t *parent)
104             {
105 0           pdfmake_struct_elem_t *elem = pdfmake_struct_elem_create(doc, PDFMAKE_TAG_DIV, parent);
106 0 0         if (elem && custom_type) {
    0          
107 0           strncpy(elem->custom_type, custom_type, sizeof(elem->custom_type) - 1);
108             }
109 0           return elem;
110             }
111              
112 1           pdfmake_err_t pdfmake_struct_elem_set_alt_text(pdfmake_struct_elem_t *elem, const char *alt) {
113 1 50         if (!elem || !alt) return PDFMAKE_EINVAL;
    50          
114 1           strncpy(elem->alt_text, alt, sizeof(elem->alt_text) - 1);
115 1           return PDFMAKE_OK;
116             }
117              
118 0           pdfmake_err_t pdfmake_struct_elem_set_actual_text(pdfmake_struct_elem_t *elem, const char *text) {
119 0 0         if (!elem || !text) return PDFMAKE_EINVAL;
    0          
120 0           strncpy(elem->actual_text, text, sizeof(elem->actual_text) - 1);
121 0           return PDFMAKE_OK;
122             }
123              
124 1           pdfmake_err_t pdfmake_struct_elem_set_lang(pdfmake_struct_elem_t *elem, const char *lang) {
125 1 50         if (!elem || !lang) return PDFMAKE_EINVAL;
    50          
126 1           strncpy(elem->lang, lang, sizeof(elem->lang) - 1);
127 1           return PDFMAKE_OK;
128             }
129              
130 0           size_t pdfmake_struct_elem_child_count(pdfmake_struct_elem_t *elem) {
131 0 0         return elem ? elem->child_count : 0;
132             }
133              
134 0           pdfmake_struct_elem_t *pdfmake_struct_elem_child_at(pdfmake_struct_elem_t *elem, size_t idx) {
135 0 0         if (!elem || idx >= elem->child_count) return NULL;
    0          
136 0           return elem->children[idx];
137             }
138              
139 0           pdfmake_err_t pdfmake_struct_elem_add_mcr(
140             pdfmake_struct_elem_t *elem,
141             uint32_t page_obj_num,
142             int mcid)
143             {
144             size_t new_cap;
145             pdfmake_mcr_t *new_arr;
146              
147 0 0         if (!elem) return PDFMAKE_EINVAL;
148 0 0         if (elem->mcr_count >= elem->mcr_cap) {
149 0 0         new_cap = elem->mcr_cap == 0 ? 4 : elem->mcr_cap * 2;
150 0           new_arr = realloc(elem->content_refs, new_cap * sizeof(pdfmake_mcr_t));
151 0 0         if (!new_arr) return PDFMAKE_ENOMEM;
152 0           elem->content_refs = new_arr;
153 0           elem->mcr_cap = new_cap;
154             }
155 0           elem->content_refs[elem->mcr_count].page_obj_num = page_obj_num;
156 0           elem->content_refs[elem->mcr_count].mcid = mcid;
157 0           elem->mcr_count++;
158 0           return PDFMAKE_OK;
159             }
160              
161             /* ── Content stream ────────────────────────────────────── */
162              
163 0           pdfmake_err_t pdfmake_content_begin_tag(
164             pdfmake_content_t *c,
165             pdfmake_struct_type_t type,
166             int mcid)
167             {
168             pdfmake_buf_t *buf;
169             const char *tag;
170             char props[64];
171              
172 0 0         if (!c) return PDFMAKE_EINVAL;
173 0           buf = &c->buf;
174 0           tag = pdfmake_struct_type_name(type);
175              
176             /* Emit: /Tag <> BDC */
177 0           snprintf(props, sizeof(props), "<>", mcid);
178              
179 0           pdfmake_buf_append_byte(buf, '/');
180 0           pdfmake_buf_append_cstr(buf, tag);
181 0           pdfmake_buf_append_byte(buf, ' ');
182 0           pdfmake_buf_append_cstr(buf, props);
183 0           pdfmake_buf_append_cstr(buf, " BDC\n");
184              
185 0           return PDFMAKE_OK;
186             }
187              
188 0           pdfmake_err_t pdfmake_content_end_tag(pdfmake_content_t *c) {
189 0 0         if (!c) return PDFMAKE_EINVAL;
190 0           return pdfmake_mc_EMC(c);
191             }
192              
193             /* ── Role mapping ──────────────────────────────────────── */
194              
195 0           pdfmake_err_t pdfmake_struct_tree_map_role(
196             pdfmake_struct_tree_t *tree,
197             const char *custom,
198             pdfmake_struct_type_t standard)
199             {
200             size_t new_cap;
201             void *new_arr;
202              
203 0 0         if (!tree || !custom) return PDFMAKE_EINVAL;
    0          
204 0 0         if (tree->role_map_count >= tree->role_map_cap) {
205 0 0         new_cap = tree->role_map_cap == 0 ? 4 : tree->role_map_cap * 2;
206 0           new_arr = realloc(tree->role_map, new_cap * sizeof(tree->role_map[0]));
207 0 0         if (!new_arr) return PDFMAKE_ENOMEM;
208 0           tree->role_map = new_arr;
209 0           tree->role_map_cap = new_cap;
210             }
211 0           strncpy(tree->role_map[tree->role_map_count].custom, custom, 63);
212 0           tree->role_map[tree->role_map_count].standard = standard;
213 0           tree->role_map_count++;
214 0           return PDFMAKE_OK;
215             }
216              
217             /* ── Writing ───────────────────────────────────────────── */
218              
219             /* Write a single structure element recursively */
220 7           static uint32_t _write_struct_elem(pdfmake_struct_elem_t *elem,
221             pdfmake_doc_t *doc,
222             uint32_t parent_num)
223             {
224             pdfmake_arena_t *arena;
225             uint32_t k;
226             pdfmake_obj_t dict;
227             const char *type_name;
228             pdfmake_obj_t kids;
229             size_t i_mcr;
230             size_t i_child;
231             pdfmake_obj_t mcr;
232             uint32_t mk;
233             uint32_t child_num;
234             pdfmake_obj_t *elem_obj;
235              
236 7 50         if (!elem || !doc) return 0;
    50          
237 7           arena = pdfmake_doc_arena(doc);
238              
239 7           dict = pdfmake_dict_new(arena);
240 7           k = pdfmake_arena_intern_name(arena, "Type", 4);
241 7           pdfmake_dict_set(arena, &dict, k, pdfmake_name_cstr(arena, "StructElem"));
242              
243             /* /S — structure type */
244 7           k = pdfmake_arena_intern_name(arena, "S", 1);
245 14           type_name = elem->custom_type[0] ? elem->custom_type
246 7 50         : pdfmake_struct_type_name(elem->type);
247 7           pdfmake_dict_set(arena, &dict, k, pdfmake_name_cstr(arena, type_name));
248              
249             /* /P — parent reference */
250 7 100         if (parent_num > 0) {
251 5           k = pdfmake_arena_intern_name(arena, "P", 1);
252 5           pdfmake_dict_set(arena, &dict, k, pdfmake_ref(parent_num, 0));
253             }
254              
255             /* /Alt, /ActualText, /Lang */
256 7 100         if (elem->alt_text[0]) {
257 1           k = pdfmake_arena_intern_name(arena, "Alt", 3);
258 1           pdfmake_dict_set(arena, &dict, k, pdfmake_str_cstr(arena, elem->alt_text));
259             }
260 7 50         if (elem->actual_text[0]) {
261 0           k = pdfmake_arena_intern_name(arena, "ActualText", 10);
262 0           pdfmake_dict_set(arena, &dict, k, pdfmake_str_cstr(arena, elem->actual_text));
263             }
264 7 100         if (elem->lang[0]) {
265 1           k = pdfmake_arena_intern_name(arena, "Lang", 4);
266 1           pdfmake_dict_set(arena, &dict, k, pdfmake_str_cstr(arena, elem->lang));
267             }
268              
269             /* Add as indirect object to get our number */
270 7           elem->obj_num = pdfmake_doc_add(doc, dict);
271 7 50         if (elem->obj_num == 0) return 0;
272              
273             /* /K — children array (MCRs + child elements) */
274 7 50         if (elem->mcr_count > 0 || elem->child_count > 0) {
    100          
275 2           kids = pdfmake_array_new(arena);
276              
277             /* Marked content references */
278 2 50         for (i_mcr = 0; i_mcr < elem->mcr_count; i_mcr++) {
279 0           mcr = pdfmake_dict_new(arena);
280 0           mk = pdfmake_arena_intern_name(arena, "Type", 4);
281 0           pdfmake_dict_set(arena, &mcr, mk, pdfmake_name_cstr(arena, "MCR"));
282 0           mk = pdfmake_arena_intern_name(arena, "Pg", 2);
283 0           pdfmake_dict_set(arena, &mcr, mk,
284 0           pdfmake_ref(elem->content_refs[i_mcr].page_obj_num, 0));
285 0           mk = pdfmake_arena_intern_name(arena, "MCID", 4);
286 0           pdfmake_dict_set(arena, &mcr, mk,
287 0           pdfmake_int(elem->content_refs[i_mcr].mcid));
288 0           pdfmake_array_push(arena, &kids, mcr);
289             }
290              
291             /* Child elements */
292 7 100         for (i_child = 0; i_child < elem->child_count; i_child++) {
293 5           child_num = _write_struct_elem(elem->children[i_child], doc, elem->obj_num);
294 5 50         if (child_num > 0) {
295 5           pdfmake_array_push(arena, &kids, pdfmake_ref(child_num, 0));
296             }
297             }
298              
299             /* Update the element dict with /K */
300 2           elem_obj = pdfmake_doc_get(doc, elem->obj_num);
301 2 50         if (elem_obj && elem_obj->kind == PDFMAKE_DICT) {
    50          
302 2           k = pdfmake_arena_intern_name(arena, "K", 1);
303 2           pdfmake_dict_set(arena, elem_obj, k, kids);
304             }
305             }
306              
307 7           return elem->obj_num;
308             }
309              
310 163           pdfmake_err_t pdfmake_doc_write_struct_tree(pdfmake_doc_t *doc) {
311             pdfmake_struct_tree_t *tree;
312             pdfmake_arena_t *arena;
313             uint32_t k;
314             uint32_t root_elem_num;
315             pdfmake_obj_t tree_dict;
316             pdfmake_obj_t rm;
317             size_t i;
318             uint32_t rk;
319             pdfmake_obj_t *root_obj;
320             pdfmake_obj_t *catalog;
321             pdfmake_obj_t mark_info;
322             uint32_t marked_key;
323              
324 163 50         if (!doc) return PDFMAKE_EINVAL;
325              
326 163           tree = pdfmake_doc_create_struct_tree(doc);
327 163 50         if (!tree || !tree->root) return PDFMAKE_OK; /* No tree */
    50          
328 163 100         if (tree->root->child_count == 0 && tree->root->mcr_count == 0)
    50          
329 161           return PDFMAKE_OK; /* Empty tree */
330              
331 2           arena = pdfmake_doc_arena(doc);
332              
333             /* Write all elements recursively */
334 2           root_elem_num = _write_struct_elem(tree->root, doc, 0);
335 2 50         if (root_elem_num == 0) return PDFMAKE_ENOMEM;
336              
337             /* Build /StructTreeRoot */
338 2           tree_dict = pdfmake_dict_new(arena);
339 2           k = pdfmake_arena_intern_name(arena, "Type", 4);
340 2           pdfmake_dict_set(arena, &tree_dict, k, pdfmake_name_cstr(arena, "StructTreeRoot"));
341              
342 2           k = pdfmake_arena_intern_name(arena, "K", 1);
343 2           pdfmake_dict_set(arena, &tree_dict, k, pdfmake_ref(root_elem_num, 0));
344              
345             /* Role mapping */
346 2 50         if (tree->role_map_count > 0) {
347 0           rm = pdfmake_dict_new(arena);
348 0 0         for (i = 0; i < tree->role_map_count; i++) {
349 0           rk = pdfmake_arena_intern_name(arena,
350 0           tree->role_map[i].custom, strlen(tree->role_map[i].custom));
351 0           pdfmake_dict_set(arena, &rm, rk,
352 0           pdfmake_name_cstr(arena, pdfmake_struct_type_name(tree->role_map[i].standard)));
353             }
354 0           k = pdfmake_arena_intern_name(arena, "RoleMap", 7);
355 0           pdfmake_dict_set(arena, &tree_dict, k, rm);
356             }
357              
358 2           tree->obj_num = pdfmake_doc_add(doc, tree_dict);
359 2 50         if (tree->obj_num == 0) return PDFMAKE_ENOMEM;
360              
361             /* Update root element's /P to point to tree root */
362 2           root_obj = pdfmake_doc_get(doc, root_elem_num);
363 2 50         if (root_obj && root_obj->kind == PDFMAKE_DICT) {
    50          
364 2           k = pdfmake_arena_intern_name(arena, "P", 1);
365 2           pdfmake_dict_set(arena, root_obj, k, pdfmake_ref(tree->obj_num, 0));
366             }
367              
368             /* Add /StructTreeRoot to catalog */
369 2           catalog = pdfmake_doc_get(doc, doc->root_num);
370 2 50         if (catalog && catalog->kind == PDFMAKE_DICT) {
    50          
371 2           k = pdfmake_arena_intern_name(arena, "StructTreeRoot", 14);
372 2           pdfmake_dict_set(arena, catalog, k, pdfmake_ref(tree->obj_num, 0));
373              
374             /* Mark as tagged: /MarkInfo << /Marked true >> */
375 2           mark_info = pdfmake_dict_new(arena);
376 2           marked_key = pdfmake_arena_intern_name(arena, "Marked", 6);
377 2           pdfmake_dict_set(arena, &mark_info, marked_key, pdfmake_bool(1));
378 2           k = pdfmake_arena_intern_name(arena, "MarkInfo", 8);
379 2           pdfmake_dict_set(arena, catalog, k, mark_info);
380             }
381              
382 2           return PDFMAKE_OK;
383             }