File Coverage

src/pdfmake_ocg.c
Criterion Covered Total %
statement 88 125 70.4
branch 39 92 42.3
condition n/a
subroutine n/a
pod n/a
total 127 217 58.5


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_ocg.c — Optional Content Groups (Layers)
3             *
4             * §8.11 Optional Content
5             */
6              
7             #include "pdfmake_ocg.h"
8             #include "pdfmake_page.h"
9             #include "pdfmake_arena.h"
10             #include
11             #include
12             #include
13              
14             /* ── Document-level OCG management ──────────────────────── */
15              
16 10           pdfmake_ocg_t *pdfmake_doc_create_ocg(pdfmake_doc_t *doc, const char *name) {
17             pdfmake_ocg_t *ocg;
18 10 50         if (!doc || !name) return NULL;
    50          
19              
20             /* Grow array if needed */
21 10 100         if (doc->ocg_count >= doc->ocg_cap) {
22 7 50         size_t new_cap = doc->ocg_cap == 0 ? 4 : doc->ocg_cap * 2;
23 7           void **new_arr = realloc(doc->ocgs, new_cap * sizeof(void *));
24 7 50         if (!new_arr) return NULL;
25 7           doc->ocgs = new_arr;
26 7           doc->ocg_cap = new_cap;
27             }
28              
29 10           ocg = pdfmake_arena_calloc(doc->arena, sizeof(pdfmake_ocg_t));
30 10 50         if (!ocg) return NULL;
31              
32 10           strncpy(ocg->name, name, sizeof(ocg->name) - 1);
33 10           ocg->visible = 1; /* default ON */
34 10           ocg->intent = PDFMAKE_OCG_INTENT_VIEW;
35 10           ocg->obj_num = 0;
36              
37             /* Generate resource name: MC0, MC1, ... */
38 10           snprintf(ocg->res_name, sizeof(ocg->res_name), "MC%zu", doc->ocg_count);
39              
40 10           doc->ocgs[doc->ocg_count++] = ocg;
41 10           return ocg;
42             }
43              
44 0           size_t pdfmake_doc_ocg_count(pdfmake_doc_t *doc) {
45 0 0         return doc ? doc->ocg_count : 0;
46             }
47              
48 0           pdfmake_ocg_t *pdfmake_doc_ocg_at(pdfmake_doc_t *doc, size_t idx) {
49 0 0         if (!doc || idx >= doc->ocg_count) return NULL;
    0          
50 0           return (pdfmake_ocg_t *)doc->ocgs[idx];
51             }
52              
53 0           pdfmake_ocg_t *pdfmake_doc_ocg_by_name(pdfmake_doc_t *doc, const char *name) {
54             size_t i;
55             pdfmake_ocg_t *ocg;
56 0 0         if (!doc || !name) return NULL;
    0          
57 0 0         for (i = 0; i < doc->ocg_count; i++) {
58 0           ocg = (pdfmake_ocg_t *)doc->ocgs[i];
59 0 0         if (strcmp(ocg->name, name) == 0) return ocg;
60             }
61 0           return NULL;
62             }
63              
64             /* ── Write OCG dictionary ───────────────────────────────── */
65              
66 10           uint32_t pdfmake_ocg_write(pdfmake_ocg_t *ocg, pdfmake_doc_t *doc) {
67             pdfmake_arena_t *arena;
68             pdfmake_obj_t dict;
69             uint32_t k;
70             pdfmake_obj_t usage;
71             uint32_t usage_key;
72             pdfmake_obj_t print_dict;
73             uint32_t ps_key;
74             uint32_t print_key;
75             pdfmake_obj_t view_dict;
76             uint32_t vs_key;
77             uint32_t view_key;
78             pdfmake_obj_t export_dict;
79             uint32_t es_key;
80             uint32_t exp_key;
81 10 50         if (!ocg || !doc) return 0;
    50          
82 10 50         if (ocg->obj_num) return ocg->obj_num; /* already written */
83              
84 10           arena = pdfmake_doc_arena(doc);
85              
86 10           dict = pdfmake_dict_new(arena);
87 10 50         if (dict.kind != PDFMAKE_DICT) return 0;
88              
89 10           k = pdfmake_arena_intern_name(arena, "Type", 4);
90 10           pdfmake_dict_set(arena, &dict, k, pdfmake_name_cstr(arena, "OCG"));
91              
92 10           k = pdfmake_arena_intern_name(arena, "Name", 4);
93 10           pdfmake_dict_set(arena, &dict, k, pdfmake_str_cstr(arena, ocg->name));
94              
95             /* Intent */
96 10           k = pdfmake_arena_intern_name(arena, "Intent", 6);
97 10           pdfmake_dict_set(arena, &dict, k, pdfmake_name_cstr(arena,
98 10 50         ocg->intent == PDFMAKE_OCG_INTENT_DESIGN ? "Design" : "View"));
99              
100             /* Usage dictionary */
101 10 50         if (ocg->has_print_state || ocg->has_view_state || ocg->has_export_state) {
    50          
    50          
102 0           usage = pdfmake_dict_new(arena);
103 0           usage_key = pdfmake_arena_intern_name(arena, "Usage", 5);
104              
105 0 0         if (ocg->has_print_state) {
106 0           print_dict = pdfmake_dict_new(arena);
107 0           ps_key = pdfmake_arena_intern_name(arena, "PrintState", 10);
108 0           pdfmake_dict_set(arena, &print_dict, ps_key,
109 0 0         pdfmake_name_cstr(arena, ocg->print_state == PDFMAKE_OCG_ON ? "ON" : "OFF"));
110 0           print_key = pdfmake_arena_intern_name(arena, "Print", 5);
111 0           pdfmake_dict_set(arena, &usage, print_key, print_dict);
112             }
113              
114 0 0         if (ocg->has_view_state) {
115 0           view_dict = pdfmake_dict_new(arena);
116 0           vs_key = pdfmake_arena_intern_name(arena, "ViewState", 9);
117 0           pdfmake_dict_set(arena, &view_dict, vs_key,
118 0 0         pdfmake_name_cstr(arena, ocg->view_state == PDFMAKE_OCG_ON ? "ON" : "OFF"));
119 0           view_key = pdfmake_arena_intern_name(arena, "View", 4);
120 0           pdfmake_dict_set(arena, &usage, view_key, view_dict);
121             }
122              
123 0 0         if (ocg->has_export_state) {
124 0           export_dict = pdfmake_dict_new(arena);
125 0           es_key = pdfmake_arena_intern_name(arena, "ExportState", 11);
126 0           pdfmake_dict_set(arena, &export_dict, es_key,
127 0 0         pdfmake_name_cstr(arena, ocg->export_state == PDFMAKE_OCG_ON ? "ON" : "OFF"));
128 0           exp_key = pdfmake_arena_intern_name(arena, "Export", 6);
129 0           pdfmake_dict_set(arena, &usage, exp_key, export_dict);
130             }
131              
132 0           pdfmake_dict_set(arena, &dict, usage_key, usage);
133             }
134              
135 10           ocg->obj_num = pdfmake_doc_add(doc, dict);
136 10           return ocg->obj_num;
137             }
138              
139             /* ── Write /OCProperties ────────────────────────────────── */
140              
141 5           pdfmake_err_t pdfmake_doc_write_ocproperties(pdfmake_doc_t *doc) {
142             pdfmake_arena_t *arena;
143             size_t i;
144             pdfmake_ocg_t *ocg;
145             pdfmake_obj_t ocgs_arr;
146             pdfmake_obj_t config;
147             uint32_t k;
148             pdfmake_obj_t off_arr;
149             int has_off;
150             pdfmake_obj_t order_arr;
151             pdfmake_obj_t ocprops;
152             pdfmake_obj_t *catalog;
153 5 50         if (!doc || doc->ocg_count == 0) return PDFMAKE_OK;
    50          
154              
155 5           arena = pdfmake_doc_arena(doc);
156              
157             /* Ensure all OCGs are written */
158 13 100         for (i = 0; i < doc->ocg_count; i++) {
159 8           ocg = (pdfmake_ocg_t *)doc->ocgs[i];
160 8 50         if (!ocg->obj_num) {
161 0 0         if (pdfmake_ocg_write(ocg, doc) == 0)
162 0           return PDFMAKE_ENOMEM;
163             }
164             }
165              
166             /* Build /OCGs array */
167 5           ocgs_arr = pdfmake_array_new(arena);
168 13 100         for (i = 0; i < doc->ocg_count; i++) {
169 8           ocg = (pdfmake_ocg_t *)doc->ocgs[i];
170 8           pdfmake_array_push(arena, &ocgs_arr, pdfmake_ref(ocg->obj_num, 0));
171             }
172              
173             /* Build default config /D */
174 5           config = pdfmake_dict_new(arena);
175              
176 5           k = pdfmake_arena_intern_name(arena, "Name", 4);
177 5           pdfmake_dict_set(arena, &config, k, pdfmake_str_cstr(arena, "Default"));
178              
179 5           k = pdfmake_arena_intern_name(arena, "BaseState", 9);
180 5           pdfmake_dict_set(arena, &config, k, pdfmake_name_cstr(arena, "ON"));
181              
182             /* Build OFF array for initially hidden layers */
183 5           off_arr = pdfmake_array_new(arena);
184 5           has_off = 0;
185 13 100         for (i = 0; i < doc->ocg_count; i++) {
186 8           ocg = (pdfmake_ocg_t *)doc->ocgs[i];
187 8 100         if (!ocg->visible) {
188 3           pdfmake_array_push(arena, &off_arr, pdfmake_ref(ocg->obj_num, 0));
189 3           has_off = 1;
190             }
191             }
192 5 100         if (has_off) {
193 3           k = pdfmake_arena_intern_name(arena, "OFF", 3);
194 3           pdfmake_dict_set(arena, &config, k, off_arr);
195             }
196              
197             /* Order array (same as OCGs array — flat order) */
198 5           order_arr = pdfmake_array_new(arena);
199 13 100         for (i = 0; i < doc->ocg_count; i++) {
200 8           ocg = (pdfmake_ocg_t *)doc->ocgs[i];
201 8           pdfmake_array_push(arena, &order_arr, pdfmake_ref(ocg->obj_num, 0));
202             }
203 5           k = pdfmake_arena_intern_name(arena, "Order", 5);
204 5           pdfmake_dict_set(arena, &config, k, order_arr);
205              
206             /* Build /OCProperties dict */
207 5           ocprops = pdfmake_dict_new(arena);
208 5           k = pdfmake_arena_intern_name(arena, "OCGs", 4);
209 5           pdfmake_dict_set(arena, &ocprops, k, ocgs_arr);
210 5           k = pdfmake_arena_intern_name(arena, "D", 1);
211 5           pdfmake_dict_set(arena, &ocprops, k, config);
212              
213             /* Add to catalog */
214 5           catalog = pdfmake_doc_get(doc, doc->root_num);
215 5 50         if (!catalog || catalog->kind != PDFMAKE_DICT) return PDFMAKE_EINVAL;
    50          
216              
217 5           k = pdfmake_arena_intern_name(arena, "OCProperties", 12);
218 5           pdfmake_dict_set(arena, catalog, k, ocprops);
219              
220 5           return PDFMAKE_OK;
221             }
222              
223             /* ── Page /Properties resource ──────────────────────────── */
224              
225 10           int pdfmake_page_add_ocg(pdfmake_page_t *page, const char *name, uint32_t ocg_obj_num) {
226             pdfmake_prop_entry_t *entry;
227 10 50         if (!page || !name || ocg_obj_num == 0) return -1;
    50          
    50          
228 10 50         if (page->prop_count >= PDFMAKE_MAX_PAGE_PROPERTIES) return -1;
229              
230 10           entry = &page->properties[page->prop_count++];
231 10           strncpy(entry->name, name, sizeof(entry->name) - 1);
232 10           entry->name[sizeof(entry->name) - 1] = '\0';
233 10           entry->prop_num = ocg_obj_num;
234              
235 10           return (int)(page->prop_count - 1);
236             }
237              
238             /* ── Content stream OCG helpers ─────────────────────────── */
239              
240 7           pdfmake_err_t pdfmake_content_begin_ocg(pdfmake_content_t *c, const char *res_name) {
241 7 50         if (!c || !res_name) return PDFMAKE_EINVAL;
    50          
242 7           return pdfmake_mc_BDC(c, "OC", res_name);
243             }
244              
245 7           pdfmake_err_t pdfmake_content_end_ocg(pdfmake_content_t *c) {
246 7 50         if (!c) return PDFMAKE_EINVAL;
247 7           return pdfmake_mc_EMC(c);
248             }