File Coverage

src/pdfmake_import.c
Criterion Covered Total %
statement 140 191 73.3
branch 52 116 44.8
condition n/a
subroutine n/a
pod n/a
total 192 307 62.5


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_import.c — Cross-document object/page import.
3             *
4             * See include/pdfmake_import.h for the public API and design notes.
5             */
6              
7             #include "pdfmake_import.h"
8             #include "pdfmake_arena.h"
9             #include "pdfmake_page.h"
10             #include "pdfmake_buf.h"
11             #include
12             #include
13             #include
14              
15             /*============================================================================
16             * Import context
17             *==========================================================================*/
18              
19             struct pdfmake_import_ctx {
20             pdfmake_reader_t *src_reader;
21             pdfmake_parser_t *src_parser;
22             pdfmake_arena_t *src_arena;
23             pdfmake_doc_t *dst;
24             pdfmake_arena_t *dst_arena;
25              
26             /* Remap table: src_num -> dst_num (0 means not yet imported).
27             * Sized at src_parser->xref_size. */
28             uint32_t *remap;
29             size_t remap_size;
30             };
31              
32 24           pdfmake_import_ctx_t *pdfmake_import_ctx_new(pdfmake_reader_t *src_reader,
33             pdfmake_doc_t *dst) {
34             pdfmake_import_ctx_t *ctx;
35              
36 24 50         if (!src_reader || !dst) return NULL;
    50          
37 24 50         if (!src_reader->parser) return NULL;
38              
39 24           ctx = calloc(1, sizeof(*ctx));
40 24 50         if (!ctx) return NULL;
41              
42 24           ctx->src_reader = src_reader;
43 24           ctx->src_parser = src_reader->parser;
44 24           ctx->src_arena = src_reader->parser->doc->arena;
45 24           ctx->dst = dst;
46 24           ctx->dst_arena = pdfmake_doc_arena(dst);
47              
48 24           ctx->remap_size = ctx->src_parser->xref_size + 1;
49 24           ctx->remap = calloc(ctx->remap_size, sizeof(uint32_t));
50 24 50         if (!ctx->remap) {
51 0           free(ctx);
52 0           return NULL;
53             }
54              
55 24           return ctx;
56             }
57              
58 24           void pdfmake_import_ctx_free(pdfmake_import_ctx_t *ctx) {
59 24 50         if (!ctx) return;
60 24           free(ctx->remap);
61 24           free(ctx);
62             }
63              
64             /*============================================================================
65             * Object deep-copy (walks composite graph)
66             *==========================================================================*/
67              
68             static pdfmake_obj_t import_obj(pdfmake_import_ctx_t *ctx, pdfmake_obj_t src);
69              
70             /* Forward decl — streams reached via a ref need the src object number so
71             * we can decrypt their bytes through the reader. */
72             static pdfmake_obj_t import_stream_with_decrypt(pdfmake_import_ctx_t *ctx,
73             pdfmake_obj_t src,
74             uint32_t src_num);
75              
76             /* Import an indirect src_num → dst_num. Handles cycles via remap slot.
77             * Returns 0 on failure. */
78 259           static uint32_t import_ref_num(pdfmake_import_ctx_t *ctx, uint32_t src_num) {
79             pdfmake_ref_t r;
80             pdfmake_obj_t *src_obj;
81             uint32_t dst_num;
82             pdfmake_obj_t dst_obj;
83              
84 259 50         if (src_num == 0 || src_num >= ctx->remap_size) return 0;
    50          
85 259 100         if (ctx->remap[src_num] != 0) return ctx->remap[src_num];
86              
87 159           r.num = src_num;
88 159           r.gen = 0;
89 159           src_obj = pdfmake_parser_resolve(ctx->src_parser, r);
90 159 50         if (!src_obj) return 0;
91              
92             /* Reserve the dst slot before recursing so cycles terminate. */
93 159           dst_num = pdfmake_doc_add(ctx->dst, pdfmake_null());
94 159 50         if (dst_num == 0) return 0;
95 159           ctx->remap[src_num] = dst_num;
96              
97             /* Streams carry the encryption state of the source file — decrypt them
98             * through the reader before copying so the destination stays readable
99             * when the source was encrypted. Non-stream objects (names, numbers,
100             * plain dicts) aren't per-object encrypted and import cleanly. */
101 159 100         if (src_obj->kind == PDFMAKE_STREAM) {
102 51           dst_obj = import_stream_with_decrypt(ctx, *src_obj, src_num);
103             } else {
104 108           dst_obj = import_obj(ctx, *src_obj);
105             }
106              
107             /* Overwrite the reserved slot with the real imported object. */
108 159           ctx->dst->objects[dst_num - 1].obj = dst_obj;
109              
110 159           return dst_num;
111             }
112              
113 437           static pdfmake_obj_t import_name(pdfmake_import_ctx_t *ctx, pdfmake_obj_t src) {
114             const char *bytes;
115             size_t len;
116             uint32_t new_id;
117             pdfmake_obj_t out;
118              
119 437           bytes = pdfmake_arena_name_bytes(ctx->src_arena, src.as.name.id);
120 437           len = pdfmake_arena_name_len (ctx->src_arena, src.as.name.id);
121 437 50         if (!bytes) return pdfmake_null();
122 437           new_id = pdfmake_arena_intern_name(ctx->dst_arena, bytes, len);
123 437           out.kind = PDFMAKE_NAME;
124 437           out.as.name.id = new_id;
125 437           return out;
126             }
127              
128 0           static pdfmake_obj_t import_string(pdfmake_import_ctx_t *ctx, pdfmake_obj_t src) {
129             void *copy;
130             pdfmake_obj_t out;
131              
132 0           copy = pdfmake_arena_memdup(ctx->dst_arena, src.as.str.bytes, src.as.str.len);
133 0 0         if (!copy && src.as.str.len > 0) return pdfmake_null();
    0          
134 0           out.kind = PDFMAKE_STR;
135 0           out.as.str.bytes = (const uint8_t *)copy;
136 0           out.as.str.len = src.as.str.len;
137 0           out.as.str.hex = src.as.str.hex;
138 0           return out;
139             }
140              
141 80           static pdfmake_obj_t import_array(pdfmake_import_ctx_t *ctx, pdfmake_obj_t src) {
142             pdfmake_obj_t out;
143             uint32_t i;
144             pdfmake_obj_t elem;
145              
146 80           out = pdfmake_array_new(ctx->dst_arena);
147 80 50         if (out.kind != PDFMAKE_ARRAY || !src.as.arr) return out;
    50          
148 1693 100         for (i = 0; i < src.as.arr->len; i++) {
149 1613           elem = import_obj(ctx, src.as.arr->items[i]);
150 1613 50         if (!pdfmake_array_push(ctx->dst_arena, &out, elem)) break;
151             }
152 80           return out;
153             }
154              
155 292           static pdfmake_obj_t import_dict(pdfmake_import_ctx_t *ctx, pdfmake_obj_t src) {
156             pdfmake_obj_t out;
157             pdfmake_dict_iter_t it;
158             const char *kb;
159             size_t kl;
160             uint32_t new_key;
161             pdfmake_obj_t new_val;
162              
163 292           out = pdfmake_dict_new(ctx->dst_arena);
164 292 50         if (out.kind != PDFMAKE_DICT) return out;
165              
166 292           pdfmake_dict_iter_init(&it, &src);
167 1352 100         while (pdfmake_dict_iter_next(&it)) {
168 1060           kb = pdfmake_arena_name_bytes(ctx->src_arena, it.current_key);
169 1060           kl = pdfmake_arena_name_len (ctx->src_arena, it.current_key);
170 1060 50         if (!kb) continue;
171 1060           new_key = pdfmake_arena_intern_name(ctx->dst_arena, kb, kl);
172 1060 50         if (new_key == 0) continue;
173 1060           new_val = import_obj(ctx, *it.current_value);
174 1060           pdfmake_dict_set(ctx->dst_arena, &out, new_key, new_val);
175             }
176 292           return out;
177             }
178              
179             /* Copy a stream verbatim without touching encryption state. Used for
180             * streams that weren't reached through a ref (no src_num available).
181             * Bytes already passed through the parser → in raw form (possibly
182             * encrypted). The caller is responsible for pairing this with an
183             * unencrypted destination or ensuring the source wasn't encrypted. */
184 0           static pdfmake_obj_t import_stream(pdfmake_import_ctx_t *ctx, pdfmake_obj_t src) {
185             pdfmake_obj_t out;
186             pdfmake_obj_t src_dict_obj;
187             pdfmake_obj_t new_dict_obj;
188             void *copy;
189              
190 0           out = pdfmake_stream_new(ctx->dst_arena);
191 0 0         if (out.kind != PDFMAKE_STREAM || !src.as.stream) return out;
    0          
192              
193 0           src_dict_obj.kind = PDFMAKE_DICT;
194 0           src_dict_obj.as.dict = src.as.stream->dict;
195 0           new_dict_obj = import_dict(ctx, src_dict_obj);
196 0           out.as.stream->dict = new_dict_obj.as.dict;
197              
198 0 0         if (src.as.stream->raw && src.as.stream->raw_len > 0) {
    0          
199 0           copy = pdfmake_arena_memdup(ctx->dst_arena,
200 0           src.as.stream->raw,
201 0           src.as.stream->raw_len);
202 0           out.as.stream->raw = (const uint8_t *)copy;
203 0           out.as.stream->raw_len = src.as.stream->raw_len;
204             }
205 0           out.as.stream->filtered = 1;
206              
207 0           return out;
208             }
209              
210             /* Import a stream by src object number. When the source document is
211             * encrypted we fetch fully decoded (decrypt + decompress) bytes through
212             * the reader and store them raw in the destination — dropping /Filter,
213             * /DecodeParms, and /Length from the dict so the writer emits the
214             * plain-text bytes with a fresh /Length it will compute at write time.
215             *
216             * The trade-off is file-size: stored uncompressed rather than re-applying
217             * /Filter. For a typical encrypted document this is fine; callers who
218             * care can FlateDecode the whole output by emitting with compression
219             * enabled on the destination. */
220 51           static pdfmake_obj_t import_stream_with_decrypt(pdfmake_import_ctx_t *ctx,
221             pdfmake_obj_t src,
222             uint32_t src_num)
223             {
224             pdfmake_buf_t decoded;
225             pdfmake_err_t err;
226             pdfmake_obj_t out;
227             pdfmake_obj_t src_dict_obj;
228             pdfmake_obj_t new_dict_obj;
229             uint32_t filter_key;
230             uint32_t parms_key;
231             uint32_t length_key;
232             pdfmake_obj_t dict_wrapper;
233             void *copy;
234              
235             /* Fast path: no encryption → plain verbatim copy. */
236 51 50         if (!ctx->src_reader ||
237 51 50         !ctx->src_reader->crypt ||
238 51 50         !ctx->src_reader->authenticated) {
239 0           return import_stream(ctx, src);
240             }
241              
242             /* Fetch decoded bytes through the reader (decrypt + filter chain). */
243 51           pdfmake_buf_init(&decoded);
244 51           err = pdfmake_reader_resolve_stream(
245             ctx->src_reader, src_num, 0, &decoded);
246              
247             /* On failure fall back to verbatim copy so the document still imports
248             * (readers may show the image as broken, but the page structure is
249             * preserved). */
250 51 50         if (err != PDFMAKE_OK) {
251 0           pdfmake_buf_free(&decoded);
252 0           return import_stream(ctx, src);
253             }
254              
255             /* Build the destination stream: deep-copy the dict, strip /Filter +
256             * /DecodeParms + /Length, then store the decoded bytes with
257             * filtered=1 so the writer emits them verbatim (and writes a fresh
258             * /Length). */
259 51           out = pdfmake_stream_new(ctx->dst_arena);
260 51 50         if (out.kind != PDFMAKE_STREAM) {
261 0           pdfmake_buf_free(&decoded);
262 0           return out;
263             }
264              
265 51           src_dict_obj.kind = PDFMAKE_DICT;
266 51           src_dict_obj.as.dict = src.as.stream->dict;
267 51           new_dict_obj = import_dict(ctx, src_dict_obj);
268 51           out.as.stream->dict = new_dict_obj.as.dict;
269              
270             /* Drop filter-related entries so readers don't try to decode the
271             * already-plain bytes. */
272 51           filter_key = pdfmake_arena_intern_name(ctx->dst_arena, "Filter", 6);
273 51           parms_key = pdfmake_arena_intern_name(ctx->dst_arena, "DecodeParms", 11);
274 51           length_key = pdfmake_arena_intern_name(ctx->dst_arena, "Length", 6);
275 51           dict_wrapper.kind = PDFMAKE_DICT;
276 51           dict_wrapper.as.dict = out.as.stream->dict;
277 51           pdfmake_dict_del(&dict_wrapper, filter_key);
278 51           pdfmake_dict_del(&dict_wrapper, parms_key);
279 51           pdfmake_dict_del(&dict_wrapper, length_key);
280              
281             /* Copy decoded bytes into the destination arena. */
282 51 50         if (decoded.len > 0) {
283 51           copy = pdfmake_arena_memdup(ctx->dst_arena,
284 51           decoded.data, decoded.len);
285 51           out.as.stream->raw = (const uint8_t *)copy;
286 51           out.as.stream->raw_len = decoded.len;
287             }
288 51           out.as.stream->filtered = 1;
289 51           pdfmake_buf_free(&decoded);
290 51           return out;
291             }
292              
293 2825           static pdfmake_obj_t import_obj(pdfmake_import_ctx_t *ctx, pdfmake_obj_t src) {
294 2825           switch (src.kind) {
295 0           case PDFMAKE_NULL: return pdfmake_null();
296 5           case PDFMAKE_BOOL: return pdfmake_bool((int)src.as.i);
297 1798           case PDFMAKE_INT: return pdfmake_int(src.as.i);
298 5           case PDFMAKE_REAL: return pdfmake_real(src.as.r);
299 437           case PDFMAKE_NAME: return import_name(ctx, src);
300 0           case PDFMAKE_STR: return import_string(ctx, src);
301 80           case PDFMAKE_ARRAY: return import_array(ctx, src);
302 241           case PDFMAKE_DICT: return import_dict(ctx, src);
303 0           case PDFMAKE_STREAM:return import_stream(ctx, src);
304 259           case PDFMAKE_REF: {
305 259           uint32_t dst_num = import_ref_num(ctx, src.as.ref.num);
306 259 50         if (dst_num == 0) return pdfmake_null();
307 259           return pdfmake_ref(dst_num, 0);
308             }
309             }
310 0           return pdfmake_null();
311             }
312              
313 0           uint32_t pdfmake_import_object(pdfmake_import_ctx_t *ctx, uint32_t src_num) {
314 0 0         if (!ctx) return 0;
315 0           return import_ref_num(ctx, src_num);
316             }
317              
318             /*============================================================================
319             * Page import
320             *==========================================================================*/
321              
322             /* Resolve an obj through a single ref indirection, in the parser arena. */
323 88           static pdfmake_obj_t *parser_resolve(pdfmake_parser_t *parser,
324             pdfmake_obj_t *obj) {
325 88 50         if (!obj) return NULL;
326 88 100         if (obj->kind == PDFMAKE_REF) {
327 18           return pdfmake_parser_resolve(parser, obj->as.ref);
328             }
329 70           return obj;
330             }
331              
332             /* Find a page's /Resources dict by walking up /Parent until one is found.
333             * All name lookups happen in the parser's arena so ids stay consistent
334             * for downstream import_obj calls. Returns a dict obj (in parser arena)
335             * or NULL if none found. */
336 44           static pdfmake_obj_t *find_inherited_resources(pdfmake_import_ctx_t *ctx,
337             pdfmake_obj_t *page_dict) {
338 44           pdfmake_arena_t *pa = ctx->src_arena;
339 44           uint32_t res_key = pdfmake_arena_intern_name(pa, "Resources", 9);
340 44           uint32_t parent_key = pdfmake_arena_intern_name(pa, "Parent", 6);
341             pdfmake_obj_t *current;
342             int depth;
343             pdfmake_obj_t *res;
344             pdfmake_obj_t *parent;
345              
346 44           current = parser_resolve(ctx->src_parser, page_dict);
347 44           depth = 0;
348 44 50         while (current && current->kind == PDFMAKE_DICT && depth < 32) {
    50          
    50          
349 44           res = pdfmake_dict_get(current, res_key);
350 44           res = parser_resolve(ctx->src_parser, res);
351 44 50         if (res && res->kind == PDFMAKE_DICT) return res;
    50          
352              
353 0           parent = pdfmake_dict_get(current, parent_key);
354 0 0         if (!parent) break;
355 0           current = parser_resolve(ctx->src_parser, parent);
356 0           depth++;
357             }
358 0           return NULL;
359             }
360              
361 44           pdfmake_page_t *pdfmake_doc_import_page(pdfmake_import_ctx_t *ctx,
362             size_t src_page_index) {
363             pdfmake_reader_page_t *rp;
364             double mbox[4];
365             double width;
366             double height;
367             pdfmake_page_t *dst_page;
368             pdfmake_buf_t content;
369             pdfmake_err_t cerr;
370             pdfmake_obj_t *resources;
371             pdfmake_obj_t dst_res;
372              
373 44 50         if (!ctx) return NULL;
374              
375 44           rp = pdfmake_reader_page_at(ctx->src_reader, src_page_index);
376 44 50         if (!rp) return NULL;
377              
378             /* Dimensions from media box */
379 44 50         if (pdfmake_reader_page_media_box(ctx->src_reader, rp, mbox) != PDFMAKE_OK) {
380 0           return NULL;
381             }
382 44           width = mbox[2] - mbox[0];
383 44           height = mbox[3] - mbox[1];
384 44 50         if (width <= 0 || height <= 0) return NULL;
    50          
385              
386             /* Append a fresh page to dst */
387 44           dst_page = pdfmake_doc_add_page(ctx->dst, width, height);
388 44 50         if (!dst_page) return NULL;
389              
390 44           dst_page->rotation = pdfmake_reader_page_rotation(ctx->src_reader, rp);
391              
392             /* Content stream (decompressed bytes from reader, stored uncompressed
393             * in dst so the writer emits them as-is without /Filter). */
394 44 50         if (pdfmake_buf_init(&content) == PDFMAKE_OK) {
395 44           cerr = pdfmake_reader_page_content_bytes(
396             ctx->src_reader, rp, &content);
397 44 50         if (cerr == PDFMAKE_OK && content.len > 0) {
    50          
398 44           pdfmake_page_set_content(dst_page, content.data, content.len);
399             }
400 44           pdfmake_buf_free(&content);
401             }
402              
403             /* Resolve /Resources using our own /Parent walk in the parser arena
404             * (the reader's merged dict mixes reader-arena and parser-arena name
405             * ids, which would corrupt key lookups during deep-copy). Child
406             * nearest-ancestor wins; entry-level merging across ancestors is not
407             * yet implemented. */
408 44           resources = find_inherited_resources(ctx, rp->page_dict);
409 44 50         if (resources && resources->kind == PDFMAKE_DICT) {
    50          
410 44           dst_res = import_obj(ctx, *resources);
411 44 50         if (dst_res.kind == PDFMAKE_DICT) {
412 44           dst_page->imported_resources = dst_res.as.dict;
413             }
414             }
415              
416 44           ctx->dst->finalized = 0;
417 44           return dst_page;
418             }
419              
420 0           size_t pdfmake_doc_import_all_pages(pdfmake_import_ctx_t *ctx) {
421             size_t total;
422             size_t imported;
423             size_t i;
424              
425 0 0         if (!ctx) return 0;
426 0           total = pdfmake_reader_page_count(ctx->src_reader);
427 0           imported = 0;
428 0 0         for (i = 0; i < total; i++) {
429 0 0         if (!pdfmake_doc_import_page(ctx, i)) break;
430 0           imported++;
431             }
432 0           return imported;
433             }