File Coverage

src/pdfmake_writer.c
Criterion Covered Total %
statement 308 364 84.6
branch 151 252 59.9
condition n/a
subroutine n/a
pod n/a
total 459 616 74.5


line stmt bran cond sub pod time code
1             /*
2             * libpdfmake — object serializer implementation.
3             *
4             * Emits PDF objects per §7.3. Locale-independent number formatting.
5             */
6              
7             #include "pdfmake_writer.h"
8             #include "pdfmake_filter.h"
9             #include "pdfmake_crypt.h"
10             #include
11             #include
12             #include
13             #include
14              
15             /*----------------------------------------------------------------------------
16             * Escape tables
17             *--------------------------------------------------------------------------*/
18              
19             /* Name escape table: 1 = must escape as #XX.
20             * Per §7.3.5: escape NUL, whitespace, delimiters (()<>[]{}/%#), and
21             * any byte outside 0x21-0x7E (printable non-space ASCII). */
22             static const uint8_t name_escape_table[256] = {
23             /* 0x00-0x0F: control chars - escape all */
24             1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
25             /* 0x10-0x1F: control chars - escape all */
26             1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
27             /* 0x20: space - escape */
28             1,
29             /* 0x21-0x22: !" - safe */
30             0,0,
31             /* 0x23: # - escape (escape char) */
32             1,
33             /* 0x24: $ - safe */
34             0,
35             /* 0x25: % - escape (comment) */
36             1,
37             /* 0x26-0x27: &' - safe */
38             0,0,
39             /* 0x28-0x29: () - escape (delimiters) */
40             1,1,
41             /* 0x2A-0x2E: *+,-. - safe */
42             0,0,0,0,0,
43             /* 0x2F: / - escape (name prefix) */
44             1,
45             /* 0x30-0x39: 0-9 - safe */
46             0,0,0,0,0,0,0,0,0,0,
47             /* 0x3A-0x3B: :; - safe */
48             0,0,
49             /* 0x3C-0x3E: <>= - escape < and > (delimiters), = is safe */
50             1,0,1,
51             /* 0x3F-0x5A: ?@A-Z - safe */
52             0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
53             /* 0x5B: [ - escape (delimiter) */
54             1,
55             /* 0x5C: \ - safe in names (only special in strings) */
56             0,
57             /* 0x5D: ] - escape (delimiter) */
58             1,
59             /* 0x5E-0x60: ^_` - safe */
60             0,0,0,
61             /* 0x61-0x7A: a-z - safe */
62             0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
63             /* 0x7B: { - escape (delimiter) */
64             1,
65             /* 0x7C: | - safe */
66             0,
67             /* 0x7D: } - escape (delimiter) */
68             1,
69             /* 0x7E: ~ - safe */
70             0,
71             /* 0x7F: DEL - escape */
72             1,
73             /* 0x80-0xFF: high bytes - escape all */
74             1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
75             1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
76             1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
77             1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
78             1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
79             1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
80             1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
81             1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
82             };
83              
84             /* Hex digits for encoding. */
85             static const char hex_upper[16] = "0123456789ABCDEF";
86              
87             /*----------------------------------------------------------------------------
88             * Number formatting (locale-independent)
89             *--------------------------------------------------------------------------*/
90              
91 21078           int pdfmake_format_int(char *buf, int64_t value) {
92             char tmp[24];
93 21078           int pos = 0;
94 21078           int neg = 0;
95             uint64_t v;
96             int len;
97              
98 21078 100         if (value < 0) {
99 83           neg = 1;
100 83           v = (uint64_t)(-(value + 1)) + 1; /* Handle INT64_MIN safely */
101             } else {
102 20995           v = (uint64_t)value;
103             }
104              
105             /* Generate digits in reverse order. */
106             do {
107 33275           tmp[pos++] = '0' + (v % 10);
108 33275           v /= 10;
109 33275 100         } while (v > 0);
110              
111 21078           len = 0;
112 21078 100         if (neg) buf[len++] = '-';
113              
114             /* Reverse the digits. */
115 54353 100         while (pos > 0) {
116 33275           buf[len++] = tmp[--pos];
117             }
118 21078           buf[len] = '\0';
119 21078           return len;
120             }
121              
122 20886           int pdfmake_format_real(char *buf, double value) {
123             int len;
124             double int_part;
125             double frac_part;
126             char tmp[24];
127             int ipos;
128             uint64_t iv;
129             int frac_digits;
130             double scaled;
131             char frac_buf[16];
132             int i;
133             int digit;
134             int j;
135              
136             /* Handle special cases. */
137 20886 50         if (isnan(value)) {
138 0           memcpy(buf, "0", 2);
139 0           return 1;
140             }
141 20886 50         if (isinf(value)) {
142 0 0         if (value > 0) {
143 0           memcpy(buf, "999999999", 10);
144 0           return 9;
145             } else {
146 0           memcpy(buf, "-999999999", 11);
147 0           return 10;
148             }
149             }
150              
151             /* If it's an integer, format as integer (no decimal point). */
152 20886 100         if (value == floor(value) && value >= -9007199254740992.0 && value <= 9007199254740992.0) {
    50          
    50          
153 14532           return pdfmake_format_int(buf, (int64_t)value);
154             }
155              
156             /* Handle negative numbers. */
157 6354           len = 0;
158 6354 50         if (value < 0) {
159 0           buf[len++] = '-';
160 0           value = -value;
161             }
162              
163             /* Format with sufficient precision for round-trip.
164             * PDF allows a lot of flexibility, but we aim for minimal representation.
165             * We try progressively more digits until round-trip succeeds. */
166              
167             /* Split into integer and fractional parts. */
168 6354           frac_part = modf(value, &int_part);
169              
170             /* Write integer part. */
171 6354 100         if (int_part == 0) {
172 461           buf[len++] = '0';
173             } else {
174 5893           ipos = 0;
175 5893           iv = (uint64_t)int_part;
176             do {
177 17444           tmp[ipos++] = '0' + (iv % 10);
178 17444           iv /= 10;
179 17444 100         } while (iv > 0);
180 23337 100         while (ipos > 0) {
181 17444           buf[len++] = tmp[--ipos];
182             }
183             }
184              
185             /* Write fractional part with minimal digits for round-trip. */
186 6354 50         if (frac_part > 0) {
187 6354           buf[len++] = '.';
188              
189             /* Generate up to 15 fractional digits. */
190 6354           frac_digits = 0;
191 6354           scaled = frac_part;
192              
193 97250 100         for (i = 0; i < 15; i++) {
194 91195           scaled *= 10.0;
195 91195           digit = (int)scaled;
196 91195 50         if (digit > 9) digit = 9; /* Clamp rounding errors */
197 91195           frac_buf[frac_digits++] = '0' + digit;
198 91195           scaled -= digit;
199              
200             /* Check if we've captured enough precision. */
201 91195 100         if (scaled < 1e-14) break;
202             }
203              
204             /* Trim trailing zeros. */
205 6968 100         while (frac_digits > 1 && frac_buf[frac_digits - 1] == '0') {
    100          
206 614           frac_digits--;
207             }
208              
209             /* Copy fractional digits. */
210 96935 100         for (j = 0; j < frac_digits; j++) {
211 90581           buf[len++] = frac_buf[j];
212             }
213             }
214              
215 6354           buf[len] = '\0';
216 6354           return len;
217             }
218              
219             /*----------------------------------------------------------------------------
220             * Per-kind writers
221             *--------------------------------------------------------------------------*/
222              
223 3           pdfmake_err_t pdfmake_write_null(pdfmake_buf_t *buf) {
224 3           return pdfmake_buf_append(buf, "null", 4);
225             }
226              
227 20           pdfmake_err_t pdfmake_write_bool(pdfmake_buf_t *buf, int value) {
228 20 100         if (value) {
229 9           return pdfmake_buf_append(buf, "true", 4);
230             } else {
231 11           return pdfmake_buf_append(buf, "false", 5);
232             }
233             }
234              
235 3300           pdfmake_err_t pdfmake_write_int(pdfmake_buf_t *buf, int64_t value) {
236             char tmp[24];
237 3300           int len = pdfmake_format_int(tmp, value);
238 3300           return pdfmake_buf_append(buf, tmp, (size_t)len);
239             }
240              
241 1233           pdfmake_err_t pdfmake_write_real(pdfmake_buf_t *buf, double value) {
242             char tmp[32];
243 1233           int len = pdfmake_format_real(tmp, value);
244 1233           return pdfmake_buf_append(buf, tmp, (size_t)len);
245             }
246              
247 9041           pdfmake_err_t pdfmake_write_name(pdfmake_buf_t *buf, const char *bytes, size_t len) {
248             pdfmake_err_t err;
249             size_t i;
250             uint8_t c;
251              
252             /* Names start with /. */
253 9041           err = pdfmake_buf_append_byte(buf, '/');
254 9041 50         if (err != PDFMAKE_OK) return err;
255              
256             /* Emit each byte, escaping as needed. */
257 64305 100         for (i = 0; i < len; i++) {
258 55264           c = (uint8_t)bytes[i];
259 55264 100         if (name_escape_table[c]) {
260             char esc[3];
261 4           esc[0] = '#';
262 4           esc[1] = hex_upper[c >> 4];
263 4           esc[2] = hex_upper[c & 0x0F];
264 4           err = pdfmake_buf_append(buf, esc, 3);
265             } else {
266 55260           err = pdfmake_buf_append_byte(buf, c);
267             }
268 55264 50         if (err != PDFMAKE_OK) return err;
269             }
270              
271 9041           return PDFMAKE_OK;
272             }
273              
274 9053           pdfmake_err_t pdfmake_write_name_id(pdfmake_buf_t *buf, pdfmake_arena_t *arena,
275             uint32_t name_id) {
276 9053           const char *bytes = pdfmake_arena_name_bytes(arena, name_id);
277 9053           size_t len = pdfmake_arena_name_len(arena, name_id);
278 9053 100         if (!bytes) return PDFMAKE_EINVAL;
279 9041           return pdfmake_write_name(buf, bytes, len);
280             }
281              
282 760           pdfmake_err_t pdfmake_write_string(pdfmake_buf_t *buf, const uint8_t *bytes, size_t len) {
283             pdfmake_err_t err;
284             size_t i;
285             uint8_t c;
286              
287 760           err = pdfmake_buf_append_byte(buf, '(');
288 760 50         if (err != PDFMAKE_OK) return err;
289              
290 11117 100         for (i = 0; i < len; i++) {
291 10357           c = bytes[i];
292 10357           switch (c) {
293 3           case '\n':
294 3           err = pdfmake_buf_append(buf, "\\n", 2);
295 3           break;
296 0           case '\r':
297 0           err = pdfmake_buf_append(buf, "\\r", 2);
298 0           break;
299 0           case '\t':
300 0           err = pdfmake_buf_append(buf, "\\t", 2);
301 0           break;
302 0           case '\b':
303 0           err = pdfmake_buf_append(buf, "\\b", 2);
304 0           break;
305 0           case '\f':
306 0           err = pdfmake_buf_append(buf, "\\f", 2);
307 0           break;
308 0           case '(':
309 0           err = pdfmake_buf_append(buf, "\\(", 2);
310 0           break;
311 0           case ')':
312 0           err = pdfmake_buf_append(buf, "\\)", 2);
313 0           break;
314 0           case '\\':
315 0           err = pdfmake_buf_append(buf, "\\\\", 2);
316 0           break;
317 10354           default:
318             /* Emit as-is (including high bytes). PDF strings are 8-bit clean. */
319 10354           err = pdfmake_buf_append_byte(buf, c);
320 10354           break;
321             }
322 10357 50         if (err != PDFMAKE_OK) return err;
323             }
324              
325 760           return pdfmake_buf_append_byte(buf, ')');
326             }
327              
328 34           pdfmake_err_t pdfmake_write_hexstring(pdfmake_buf_t *buf, const uint8_t *bytes, size_t len) {
329             pdfmake_err_t err;
330             size_t i;
331             uint8_t c;
332             char hex[2];
333              
334 34           err = pdfmake_buf_append_byte(buf, '<');
335 34 50         if (err != PDFMAKE_OK) return err;
336              
337 1590 100         for (i = 0; i < len; i++) {
338 1556           c = bytes[i];
339 1556           hex[0] = hex_upper[c >> 4];
340 1556           hex[1] = hex_upper[c & 0x0F];
341 1556           err = pdfmake_buf_append(buf, hex, 2);
342 1556 50         if (err != PDFMAKE_OK) return err;
343             }
344              
345 34           return pdfmake_buf_append_byte(buf, '>');
346             }
347              
348 860           pdfmake_err_t pdfmake_write_array(pdfmake_buf_t *buf, pdfmake_arena_t *arena,
349             const pdfmake_array_t *arr) {
350             pdfmake_err_t err;
351             uint32_t i;
352              
353 860 50         if (!arr) return PDFMAKE_EINVAL;
354              
355 860           err = pdfmake_buf_append_byte(buf, '[');
356 860 50         if (err != PDFMAKE_OK) return err;
357              
358 4920 100         for (i = 0; i < arr->len; i++) {
359 4060 100         if (i > 0) {
360 3200           err = pdfmake_buf_append_byte(buf, ' ');
361 3200 50         if (err != PDFMAKE_OK) return err;
362             }
363 4060           err = pdfmake_write_obj(buf, arena, &arr->items[i]);
364 4060 50         if (err != PDFMAKE_OK) return err;
365             }
366              
367 860           return pdfmake_buf_append_byte(buf, ']');
368             }
369              
370 2296           pdfmake_err_t pdfmake_write_dict(pdfmake_buf_t *buf, pdfmake_arena_t *arena,
371             const pdfmake_dict_t *dict) {
372             pdfmake_err_t err;
373             uint32_t order;
374             uint32_t i;
375             pdfmake_dict_entry_t *e;
376              
377 2296 50         if (!dict) return PDFMAKE_EINVAL;
378              
379 2296           err = pdfmake_buf_append(buf, "<<", 2);
380 2296 50         if (err != PDFMAKE_OK) return err;
381              
382             /* Iterate in insertion order. This is O(n²) but maintains PDF convention. */
383 8919 100         for (order = 0; order < dict->next_order; order++) {
384             /* Find entry with this order. */
385 50195 100         for (i = 0; i < dict->cap; i++) {
386 50093           e = &dict->entries[i];
387 50093 100         if (e->key != 0 && !e->deleted && e->order == order) {
    50          
    100          
388             /* Write key. */
389 6533           err = pdfmake_write_name_id(buf, arena, e->key);
390 6533 100         if (err != PDFMAKE_OK) return err;
391              
392             /* Space between key and value. */
393 6526           err = pdfmake_buf_append_byte(buf, ' ');
394 6526 50         if (err != PDFMAKE_OK) return err;
395              
396             /* Write value. */
397 6526           err = pdfmake_write_obj(buf, arena, &e->value);
398 6526 100         if (err != PDFMAKE_OK) return err;
399              
400 6521           break;
401             }
402             }
403             }
404              
405 2284           return pdfmake_buf_append(buf, ">>", 2);
406             }
407              
408 1623           pdfmake_err_t pdfmake_write_ref(pdfmake_buf_t *buf, uint32_t num, uint16_t gen) {
409             char tmp[32];
410 1623           int len = pdfmake_format_int(tmp, num);
411 1623           tmp[len++] = ' ';
412 1623           len += pdfmake_format_int(tmp + len, gen);
413 1623           tmp[len++] = ' ';
414 1623           tmp[len++] = 'R';
415 1623           return pdfmake_buf_append(buf, tmp, (size_t)len);
416             }
417              
418 339           pdfmake_err_t pdfmake_write_stream(pdfmake_buf_t *buf, pdfmake_arena_t *arena,
419             const pdfmake_stream_t *stream) {
420             pdfmake_err_t err;
421             uint32_t filter_key;
422             pdfmake_obj_t dict_obj;
423             pdfmake_obj_t *filter_val;
424             const uint8_t *output_data;
425             size_t output_len;
426 339           pdfmake_buf_t compressed = {0};
427 339           int needs_free = 0;
428             const char *filter_name;
429             uint32_t length_key;
430             pdfmake_obj_t length_obj;
431              
432 339 50         if (!stream || !stream->dict) return PDFMAKE_EINVAL;
    50          
433              
434             /* Check if /Filter is set for compression */
435 339           filter_key = pdfmake_arena_intern_name(arena, "Filter", 6);
436 339           dict_obj.kind = PDFMAKE_DICT;
437 339           dict_obj.as.dict = stream->dict;
438 339 50         filter_val = filter_key ? pdfmake_dict_get(&dict_obj, filter_key) : NULL;
439              
440 339           output_data = stream->raw;
441 339           output_len = stream->raw_len;
442              
443             /* Apply compression if /Filter is set and stream is not already filtered */
444 339 100         if (filter_val && filter_val->kind == PDFMAKE_NAME && !stream->filtered) {
    50          
    100          
445 2           filter_name = pdfmake_arena_name_bytes(arena, filter_val->as.name.id);
446              
447 2 50         if (filter_name && strcmp(filter_name, "FlateDecode") == 0 && stream->raw && stream->raw_len > 0) {
    50          
    0          
    0          
448             /* Compress with FlateDecode */
449 0 0         if (pdfmake_buf_init(&compressed) == PDFMAKE_OK) {
450 0           err = pdfmake_flate_encode(stream->raw, stream->raw_len, NULL, &compressed);
451 0 0         if (err == PDFMAKE_OK) {
452 0           output_data = compressed.data;
453 0           output_len = compressed.len;
454 0           needs_free = 1;
455             }
456             }
457             }
458             }
459              
460             /* Set /Length in dictionary */
461 339           length_key = pdfmake_arena_intern_name(arena, "Length", 6);
462 339 50         if (length_key) {
463 339           length_obj = pdfmake_int((int64_t)output_len);
464 339           pdfmake_dict_set(arena, &dict_obj, length_key, length_obj);
465             }
466              
467             /* Write the dictionary */
468 339           err = pdfmake_write_dict(buf, arena, stream->dict);
469 339 100         if (err != PDFMAKE_OK) {
470 3 50         if (needs_free) pdfmake_buf_free(&compressed);
471 3           return err;
472             }
473              
474             /* Stream framing */
475 336           err = pdfmake_buf_append(buf, "\nstream\n", 8);
476 336 50         if (err != PDFMAKE_OK) {
477 0 0         if (needs_free) pdfmake_buf_free(&compressed);
478 0           return err;
479             }
480              
481             /* Stream data */
482 336 100         if (output_data && output_len > 0) {
    50          
483 319           err = pdfmake_buf_append(buf, output_data, output_len);
484 319 50         if (err != PDFMAKE_OK) {
485 0 0         if (needs_free) pdfmake_buf_free(&compressed);
486 0           return err;
487             }
488             }
489              
490 336 50         if (needs_free) pdfmake_buf_free(&compressed);
491              
492 336           return pdfmake_buf_append(buf, "\nendstream", 10);
493             }
494              
495             /*----------------------------------------------------------------------------
496             * Object dispatcher
497             *--------------------------------------------------------------------------*/
498              
499 12301           pdfmake_err_t pdfmake_write_obj(pdfmake_buf_t *buf, pdfmake_arena_t *arena,
500             const pdfmake_obj_t *obj) {
501 12301 50         if (!buf || !obj) return PDFMAKE_EINVAL;
    50          
502              
503 12301           switch (obj->kind) {
504 3           case PDFMAKE_NULL:
505 3           return pdfmake_write_null(buf);
506              
507 20           case PDFMAKE_BOOL:
508 20           return pdfmake_write_bool(buf, (int)obj->as.i);
509              
510 3256           case PDFMAKE_INT:
511 3256           return pdfmake_write_int(buf, obj->as.i);
512              
513 1223           case PDFMAKE_REAL:
514 1223           return pdfmake_write_real(buf, obj->as.r);
515              
516 2270           case PDFMAKE_NAME:
517 2270           return pdfmake_write_name_id(buf, arena, obj->as.name.id);
518              
519 775           case PDFMAKE_STR:
520 775 100         if (obj->as.str.hex) {
521 15           return pdfmake_write_hexstring(buf, obj->as.str.bytes, obj->as.str.len);
522             } else {
523 760           return pdfmake_write_string(buf, obj->as.str.bytes, obj->as.str.len);
524             }
525              
526 860           case PDFMAKE_ARRAY:
527 860           return pdfmake_write_array(buf, arena, obj->as.arr);
528              
529 1957           case PDFMAKE_DICT:
530 1957           return pdfmake_write_dict(buf, arena, obj->as.dict);
531              
532 339           case PDFMAKE_STREAM:
533 339           return pdfmake_write_stream(buf, arena, obj->as.stream);
534              
535 1598           case PDFMAKE_REF:
536 1598           return pdfmake_write_ref(buf, obj->as.ref.num, obj->as.ref.gen);
537              
538 0           default:
539 0           return PDFMAKE_EINVAL;
540             }
541             }
542              
543             /*----------------------------------------------------------------------------
544             * Encrypted emission path — mirrors the non-encrypted helpers above but
545             * threads a crypt ctx + obj_num through recursion so every string/stream
546             * encountered inside an indirect object can be encrypted with the correct
547             * per-object key. The `skip_encrypt` flag is raised for objects that the
548             * PDF spec says must remain plaintext (the /Encrypt dict itself and its
549             * string children).
550             *--------------------------------------------------------------------------*/
551              
552             static pdfmake_err_t write_obj_enc(pdfmake_buf_t *buf, pdfmake_arena_t *arena,
553             const pdfmake_crypt_ctx_t *crypt,
554             uint32_t obj_num, int skip_encrypt,
555             const pdfmake_obj_t *obj);
556              
557 15           static pdfmake_err_t write_literal_string_bytes(pdfmake_buf_t *buf,
558             const uint8_t *bytes,
559             size_t len) {
560 15           pdfmake_err_t err = pdfmake_buf_append_byte(buf, '(');
561             size_t i;
562             uint8_t c;
563 15 50         if (err != PDFMAKE_OK) return err;
564 574 100         for (i = 0; i < len; i++) {
565 559           c = bytes[i];
566 559           switch (c) {
567 1           case '\n': err = pdfmake_buf_append(buf, "\\n", 2); break;
568 2           case '\r': err = pdfmake_buf_append(buf, "\\r", 2); break;
569 3           case '\t': err = pdfmake_buf_append(buf, "\\t", 2); break;
570 3           case '\b': err = pdfmake_buf_append(buf, "\\b", 2); break;
571 1           case '\f': err = pdfmake_buf_append(buf, "\\f", 2); break;
572 3           case '(': err = pdfmake_buf_append(buf, "\\(", 2); break;
573 3           case ')': err = pdfmake_buf_append(buf, "\\)", 2); break;
574 3           case '\\': err = pdfmake_buf_append(buf, "\\\\", 2); break;
575 540           default: err = pdfmake_buf_append_byte(buf, c); break;
576             }
577 559 50         if (err != PDFMAKE_OK) return err;
578             }
579 15           return pdfmake_buf_append_byte(buf, ')');
580             }
581              
582 34           static pdfmake_err_t write_string_enc(pdfmake_buf_t *buf,
583             const pdfmake_crypt_ctx_t *crypt,
584             uint32_t obj_num, int skip_encrypt,
585             const pdfmake_string_t *s) {
586 34           const uint8_t *bytes = s->bytes;
587 34           size_t len = s->len;
588 34           uint8_t *enc = NULL;
589 34           size_t enc_len = 0;
590             size_t cap;
591             int n;
592             pdfmake_err_t err;
593              
594 34 50         if (crypt && !skip_encrypt) {
    100          
595 15 100         cap = (crypt->R >= 4) ? (len + 32 + 16) : (len + 1);
596 15           enc = (uint8_t *)malloc(cap);
597 15 50         if (!enc) return PDFMAKE_ENOMEM;
598 15           n = pdfmake_crypt_encrypt_string(crypt, (int)obj_num, 0,
599             bytes, len, enc);
600 15 50         if (n < 0) { free(enc); return PDFMAKE_EINVAL; }
601 15           bytes = enc;
602 15           enc_len = (size_t)n;
603 15           len = enc_len;
604             }
605              
606 34 100         if (s->hex) {
607 19           err = pdfmake_write_hexstring(buf, bytes, len);
608             } else {
609 15           err = write_literal_string_bytes(buf, bytes, len);
610             }
611 34           free(enc);
612 34           return err;
613             }
614              
615 10           static pdfmake_err_t write_array_enc(pdfmake_buf_t *buf, pdfmake_arena_t *arena,
616             const pdfmake_crypt_ctx_t *crypt,
617             uint32_t obj_num, int skip_encrypt,
618             const pdfmake_array_t *arr) {
619             pdfmake_err_t err;
620             uint32_t i;
621 10 50         if (!arr) return PDFMAKE_EINVAL;
622 10           err = pdfmake_buf_append_byte(buf, '[');
623 10 50         if (err != PDFMAKE_OK) return err;
624 35 100         for (i = 0; i < arr->len; i++) {
625 25 100         if (i > 0) {
626 15           err = pdfmake_buf_append_byte(buf, ' ');
627 15 50         if (err != PDFMAKE_OK) return err;
628             }
629 25           err = write_obj_enc(buf, arena, crypt, obj_num, skip_encrypt,
630 25           &arr->items[i]);
631 25 50         if (err != PDFMAKE_OK) return err;
632             }
633 10           return pdfmake_buf_append_byte(buf, ']');
634             }
635              
636 57           static pdfmake_err_t write_dict_enc(pdfmake_buf_t *buf, pdfmake_arena_t *arena,
637             const pdfmake_crypt_ctx_t *crypt,
638             uint32_t obj_num, int skip_encrypt,
639             const pdfmake_dict_t *dict) {
640             pdfmake_err_t err;
641             uint32_t order;
642             uint32_t i;
643             pdfmake_dict_entry_t *e;
644 57 50         if (!dict) return PDFMAKE_EINVAL;
645 57           err = pdfmake_buf_append(buf, "<<", 2);
646 57 50         if (err != PDFMAKE_OK) return err;
647              
648 240 100         for (order = 0; order < dict->next_order; order++) {
649 1822 50         for (i = 0; i < dict->cap; i++) {
650 1822           e = &dict->entries[i];
651 1822 100         if (e->key != 0 && !e->deleted && e->order == order) {
    50          
    100          
652 183           err = pdfmake_write_name_id(buf, arena, e->key);
653 183 50         if (err != PDFMAKE_OK) return err;
654 183           err = pdfmake_buf_append_byte(buf, ' ');
655 183 50         if (err != PDFMAKE_OK) return err;
656 183           err = write_obj_enc(buf, arena, crypt, obj_num, skip_encrypt,
657 183           &e->value);
658 183 50         if (err != PDFMAKE_OK) return err;
659 183           break;
660             }
661             }
662             }
663 57           return pdfmake_buf_append(buf, ">>", 2);
664             }
665              
666 5           static pdfmake_err_t write_stream_enc(pdfmake_buf_t *buf,
667             pdfmake_arena_t *arena,
668             const pdfmake_crypt_ctx_t *crypt,
669             uint32_t obj_num, int skip_encrypt,
670             const pdfmake_stream_t *stream) {
671             pdfmake_err_t err;
672             uint32_t filter_key;
673             pdfmake_obj_t dict_obj;
674             pdfmake_obj_t *filter_val;
675             const uint8_t *output_data;
676             size_t output_len;
677             pdfmake_buf_t compressed;
678             int needs_free;
679             uint8_t *enc;
680             size_t enc_len;
681             uint32_t length_key;
682             pdfmake_obj_t len_obj;
683              
684 5 50         if (!stream || !stream->dict) return PDFMAKE_EINVAL;
    50          
685              
686             /* Stage 1: apply /Filter compression if requested (mirrors
687             * pdfmake_write_stream's logic). */
688 5           filter_key = pdfmake_arena_intern_name(arena, "Filter", 6);
689 5           dict_obj.kind = PDFMAKE_DICT;
690 5           dict_obj.as.dict = stream->dict;
691 5           filter_val = filter_key
692 5 50         ? pdfmake_dict_get(&dict_obj, filter_key) : NULL;
693              
694 5           output_data = stream->raw;
695 5           output_len = stream->raw_len;
696 5           memset(&compressed, 0, sizeof(compressed));
697 5           needs_free = 0;
698              
699 5 50         if (filter_val && filter_val->kind == PDFMAKE_NAME && !stream->filtered) {
    0          
    0          
700             const char *filter_name =
701 0           pdfmake_arena_name_bytes(arena, filter_val->as.name.id);
702 0 0         if (filter_name && strcmp(filter_name, "FlateDecode") == 0 &&
    0          
703 0 0         stream->raw && stream->raw_len > 0) {
    0          
704 0 0         if (pdfmake_buf_init(&compressed) == PDFMAKE_OK) {
705 0           err = pdfmake_flate_encode(stream->raw, stream->raw_len,
706             NULL, &compressed);
707 0 0         if (err == PDFMAKE_OK) {
708 0           output_data = compressed.data;
709 0           output_len = compressed.len;
710 0           needs_free = 1;
711             }
712             }
713             }
714             }
715              
716             /* Stage 2: apply stream encryption if a crypt ctx is active. */
717 5           enc = NULL;
718 5           enc_len = 0;
719 5 50         if (crypt && !skip_encrypt && output_data && output_len > 0) {
    50          
    50          
    50          
720 5 50         if (pdfmake_crypt_encrypt_stream(crypt, (int)obj_num, 0,
721             output_data, output_len,
722             &enc, &enc_len) != 0) {
723 0 0         if (needs_free) pdfmake_buf_free(&compressed);
724 0           return PDFMAKE_EINVAL;
725             }
726 5           output_data = enc;
727 5           output_len = enc_len;
728             }
729              
730             /* /Length must reflect the final on-wire size */
731 5           length_key = pdfmake_arena_intern_name(arena, "Length", 6);
732 5 50         if (length_key) {
733 5           len_obj = pdfmake_int((int64_t)output_len);
734 5           pdfmake_dict_set(arena, &dict_obj, length_key, len_obj);
735             }
736              
737             /* Emit dict with strings encrypted too (if applicable) */
738 5           err = write_dict_enc(buf, arena, crypt, obj_num, skip_encrypt, stream->dict);
739 5 50         if (err != PDFMAKE_OK) goto cleanup;
740              
741 5           err = pdfmake_buf_append(buf, "\nstream\n", 8);
742 5 50         if (err != PDFMAKE_OK) goto cleanup;
743              
744 5 50         if (output_data && output_len > 0) {
    50          
745 5           err = pdfmake_buf_append(buf, output_data, output_len);
746 5 50         if (err != PDFMAKE_OK) goto cleanup;
747             }
748              
749 5           err = pdfmake_buf_append(buf, "\nendstream", 10);
750              
751 5           cleanup:
752 5 50         if (needs_free) pdfmake_buf_free(&compressed);
753 5           free(enc);
754 5           return err;
755             }
756              
757 247           static pdfmake_err_t write_obj_enc(pdfmake_buf_t *buf, pdfmake_arena_t *arena,
758             const pdfmake_crypt_ctx_t *crypt,
759             uint32_t obj_num, int skip_encrypt,
760             const pdfmake_obj_t *obj) {
761 247 50         if (!buf || !obj) return PDFMAKE_EINVAL;
    50          
762 247           switch (obj->kind) {
763 0           case PDFMAKE_NULL: return pdfmake_write_null(buf);
764 0           case PDFMAKE_BOOL: return pdfmake_write_bool(buf, (int)obj->as.i);
765 44           case PDFMAKE_INT: return pdfmake_write_int(buf, obj->as.i);
766 10           case PDFMAKE_REAL: return pdfmake_write_real(buf, obj->as.r);
767 67           case PDFMAKE_NAME: return pdfmake_write_name_id(buf, arena,
768 67           obj->as.name.id);
769 34           case PDFMAKE_STR: return write_string_enc(buf, crypt, obj_num,
770             skip_encrypt, &obj->as.str);
771 10           case PDFMAKE_ARRAY: return write_array_enc(buf, arena, crypt, obj_num,
772 10           skip_encrypt, obj->as.arr);
773 52           case PDFMAKE_DICT: return write_dict_enc(buf, arena, crypt, obj_num,
774 52           skip_encrypt, obj->as.dict);
775 5           case PDFMAKE_STREAM: return write_stream_enc(buf, arena, crypt, obj_num,
776 5           skip_encrypt, obj->as.stream);
777 25           case PDFMAKE_REF: return pdfmake_write_ref(buf, obj->as.ref.num,
778 25           obj->as.ref.gen);
779             }
780 0           return PDFMAKE_EINVAL;
781             }
782              
783 39           pdfmake_err_t pdfmake_write_obj_encrypted(pdfmake_buf_t *buf,
784             pdfmake_arena_t *arena,
785             const pdfmake_crypt_ctx_t *crypt,
786             uint32_t obj_num,
787             int skip_encrypt,
788             const pdfmake_obj_t *obj) {
789 39           return write_obj_enc(buf, arena, crypt, obj_num, skip_encrypt, obj);
790             }