File Coverage

src/pdfmake_linear.c
Criterion Covered Total %
statement 423 599 70.6
branch 187 410 45.6
condition n/a
subroutine n/a
pod n/a
total 610 1009 60.4


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_linear.c — PDF Linearization (Fast Web View) implementation
3             *
4             * Implements PDF linearization per Annex F of ISO 32000-2:2020.
5             */
6              
7             #include "pdfmake_linear.h"
8             #include "pdfmake_arena.h"
9             #include "pdfmake_buf.h"
10             #include "pdfmake_writer.h"
11             #include "pdfmake_parser.h"
12             #include "pdfmake_reader.h"
13             #include
14             #include
15             #include
16              
17             /*============================================================================
18             * Internal Helpers
19             *==========================================================================*/
20              
21             /* Recursively collect objects referenced by an object */
22 36           static void collect_refs(
23             pdfmake_obj_t *obj,
24             uint32_t *refs,
25             size_t *ref_count,
26             size_t ref_cap,
27             uint8_t *visited,
28             size_t visited_size)
29             {
30 36 50         if (!obj) return;
31            
32 36           switch (obj->kind) {
33 9           case PDFMAKE_REF: {
34 9           uint32_t num = obj->as.ref.num;
35 9 50         if (num < visited_size && !visited[num]) {
    50          
36 9           visited[num] = 1;
37 9 50         if (*ref_count < ref_cap) {
38 9           refs[(*ref_count)++] = num;
39             }
40             }
41 9           break;
42             }
43 3           case PDFMAKE_ARRAY: {
44             size_t i;
45 3           pdfmake_array_t *arr = obj->as.arr;
46 15 100         for (i = 0; i < arr->len; i++) {
47 12           collect_refs(&arr->items[i], refs, ref_count, ref_cap, visited, visited_size);
48             }
49 3           break;
50             }
51 9           case PDFMAKE_DICT: {
52             size_t i;
53 9           pdfmake_dict_t *dict = obj->as.dict;
54 153 100         for (i = 0; i < dict->cap; i++) {
55 144 100         if (dict->entries[i].key == 0 || dict->entries[i].deleted) continue;
    50          
56 21           collect_refs(&dict->entries[i].value, refs, ref_count, ref_cap, visited, visited_size);
57             }
58 9           break;
59             }
60 0           case PDFMAKE_STREAM: {
61 0           pdfmake_stream_t *stream = obj->as.stream;
62 0 0         if (stream->dict) {
63             pdfmake_obj_t dict_obj;
64 0           dict_obj.kind = PDFMAKE_DICT;
65 0           dict_obj.as.dict = stream->dict;
66 0           collect_refs(&dict_obj, refs, ref_count, ref_cap, visited, visited_size);
67             }
68 0           break;
69             }
70 15           default:
71 15           break;
72             }
73             }
74              
75             /*============================================================================
76             * Linearization Detection
77             *==========================================================================*/
78              
79 9           int pdfmake_data_is_linearized(const uint8_t *data, size_t len)
80             {
81             const char *p;
82             const char *end;
83             const char *lin;
84             int obj_num;
85 9 50         if (!data || len < 100) return 0;
    100          
86            
87             /* Find first object after header */
88 4           p = (const char *)data;
89 4 50         end = p + (len < 4096 ? len : 4096); /* Check first 4KB */
90            
91             /* Skip header line */
92 36 50         while (p < end && *p != '\n') p++;
    100          
93 4 50         if (p >= end) return 0;
94 4           p++;
95            
96             /* Skip binary comment if present */
97 4 50         if (p < end && *p == '%') {
    50          
98 24 50         while (p < end && *p != '\n') p++;
    100          
99 4 50         if (p >= end) return 0;
100 4           p++;
101             }
102            
103             /* Skip whitespace */
104 4 50         while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
    50          
    50          
    50          
    50          
105            
106             /* Look for object definition */
107 4           obj_num = 0;
108 4 50         if (sscanf(p, "%d 0 obj", &obj_num) != 1) return 0;
109            
110             /* Skip to << */
111 36 50         while (p < end && *p != '<') p++;
    100          
112 4 50         if (p + 1 >= end || *(p+1) != '<') return 0;
    50          
113            
114             /* Look for /Linearized */
115 4           lin = strstr(p, "/Linearized");
116 4 100         if (!lin || lin >= end) return 0;
    50          
117            
118 3           return 1;
119             }
120              
121 7           int pdfmake_doc_is_linearized(pdfmake_doc_t *doc)
122             {
123             pdfmake_obj_t *obj;
124             pdfmake_dict_t *dict;
125             size_t i;
126 7 50         if (!doc || doc->obj_count < 1) return 0;
    100          
127            
128             /* In a linearized PDF, object 1 should be the linearization dict */
129 2           obj = pdfmake_doc_get(doc, 1);
130 2 50         if (!obj || obj->kind != PDFMAKE_DICT) return 0;
    50          
131            
132 2           dict = obj->as.dict;
133 2 50         if (!dict) return 0;
134            
135             /* Look for /Linearized key in dictionary */
136             /* This requires name table access which we check via flag pattern */
137 4 100         for (i = 0; i < dict->len; i++) {
138             /* We'd need to resolve the name ID to check if it's "Linearized" */
139             /* For now, check if doc has linearization marker set */
140             }
141            
142 2           return 0; /* Conservative default */
143             }
144              
145 0           pdfmake_err_t pdfmake_doc_linear_params(
146             pdfmake_doc_t *doc,
147             pdfmake_linear_params_t *out)
148             {
149             pdfmake_obj_t *obj;
150 0 0         if (!doc || !out) return PDFMAKE_EINVAL;
    0          
151            
152 0           memset(out, 0, sizeof(*out));
153            
154 0 0         if (!pdfmake_doc_is_linearized(doc)) {
155 0           return PDFMAKE_EINVAL;
156             }
157            
158             /* Extract parameters from linearization dictionary (object 1) */
159 0           obj = pdfmake_doc_get(doc, 1);
160 0 0         if (!obj || obj->kind != PDFMAKE_DICT) return PDFMAKE_EINVAL;
    0          
161            
162             /* Parse linearization dictionary values */
163             /* /Linearized, /L, /H, /O, /E, /N, /T */
164            
165 0           return PDFMAKE_OK;
166             }
167              
168             /*============================================================================
169             * Linearization Context
170             *==========================================================================*/
171              
172 5           pdfmake_linear_t *pdfmake_linear_new(pdfmake_doc_t *doc)
173             {
174             pdfmake_linear_t *lin;
175             size_t map_size;
176 5 50         if (!doc) return NULL;
177            
178 5           lin = calloc(1, sizeof(pdfmake_linear_t));
179 5 50         if (!lin) return NULL;
180            
181 5           lin->arena = pdfmake_arena_new();
182 5 50         if (!lin->arena) {
183 0           free(lin);
184 0           return NULL;
185             }
186            
187 5           lin->doc = doc;
188            
189             /* Allocate object map */
190 5           map_size = doc->obj_count + 1;
191 5           lin->obj_map = pdfmake_arena_alloc(lin->arena, map_size * sizeof(uint32_t));
192 5 50         if (!lin->obj_map) {
193 0           pdfmake_arena_free(lin->arena);
194 0           free(lin);
195 0           return NULL;
196             }
197 5           memset(lin->obj_map, 0, map_size * sizeof(uint32_t));
198 5           lin->obj_map_size = map_size;
199            
200             /* Allocate page objects arrays */
201 10           lin->page_objects = pdfmake_arena_alloc(lin->arena,
202 5           doc->page_count * sizeof(*lin->page_objects));
203 5 50         if (!lin->page_objects) {
204 0           pdfmake_arena_free(lin->arena);
205 0           free(lin);
206 0           return NULL;
207             }
208 5           memset(lin->page_objects, 0, doc->page_count * sizeof(*lin->page_objects));
209            
210             /* Allocate reference counts */
211 5           lin->ref_counts = pdfmake_arena_alloc(lin->arena, map_size * sizeof(uint16_t));
212 5 50         if (!lin->ref_counts) {
213 0           pdfmake_arena_free(lin->arena);
214 0           free(lin);
215 0           return NULL;
216             }
217 5           memset(lin->ref_counts, 0, map_size * sizeof(uint16_t));
218            
219 5           return lin;
220             }
221              
222 5           void pdfmake_linear_free(pdfmake_linear_t *lin)
223             {
224 5 50         if (!lin) return;
225            
226 5 50         if (lin->arena) {
227 5           pdfmake_arena_free(lin->arena);
228             }
229            
230 5           free(lin);
231             }
232              
233             /*============================================================================
234             * Page Dependency Analysis
235             *==========================================================================*/
236              
237             /* Recursively collect all objects needed to render a page */
238 3           static pdfmake_err_t collect_page_objects(
239             pdfmake_linear_t *lin,
240             size_t page_idx,
241             uint32_t start_obj)
242             {
243 3           pdfmake_doc_t *doc = lin->doc;
244             size_t cap;
245             uint32_t *objects;
246             size_t count;
247             size_t visited_size;
248             uint8_t *visited;
249             pdfmake_obj_t *page_obj;
250             size_t i;
251            
252             /* Initial capacity */
253 3           cap = 64;
254 3           objects = pdfmake_arena_alloc(lin->arena, cap * sizeof(uint32_t));
255 3 50         if (!objects) return PDFMAKE_ENOMEM;
256            
257 3           count = 0;
258            
259             /* Visited bitmap */
260 3           visited_size = doc->obj_count + 1;
261 3           visited = pdfmake_arena_alloc(lin->arena, visited_size);
262 3 50         if (!visited) return PDFMAKE_ENOMEM;
263 3           memset(visited, 0, visited_size);
264            
265             /* Start with the page object */
266 3           page_obj = pdfmake_doc_get(doc, start_obj);
267 3 50         if (!page_obj) return PDFMAKE_EINVAL;
268            
269 3           objects[count++] = start_obj;
270 3           visited[start_obj] = 1;
271            
272             /* Collect all referenced objects */
273 3           collect_refs(page_obj, objects, &count, cap, visited, visited_size);
274            
275             /* Store results */
276 3           lin->page_objects[page_idx].objects = objects;
277 3           lin->page_objects[page_idx].count = count;
278 3           lin->page_objects[page_idx].cap = cap;
279            
280             /* Update reference counts */
281 15 100         for (i = 0; i < count; i++) {
282 12           uint32_t num = objects[i];
283 12 50         if (num < lin->obj_map_size) {
284 12           lin->ref_counts[num]++;
285             }
286             }
287            
288 3           return PDFMAKE_OK;
289             }
290              
291 5           pdfmake_err_t pdfmake_linear_analyze(pdfmake_linear_t *lin)
292             {
293             pdfmake_doc_t *doc;
294             size_t i;
295             size_t p;
296             size_t shared_cap;
297             uint32_t next_num;
298 5 50         if (!lin || !lin->doc) return PDFMAKE_EINVAL;
    50          
299            
300 5           doc = lin->doc;
301            
302             /* Ensure document is finalized */
303 5 100         if (!doc->finalized) {
304 4           return PDFMAKE_EINVAL;
305             }
306            
307             /* Analyze each page's dependencies */
308 4 100         for (i = 0; i < doc->page_count; i++) {
309             pdfmake_page_t *page;
310             uint32_t page_obj_num;
311             pdfmake_err_t err;
312 3           page = doc->pages[i];
313 3 50         if (!page) continue;
314            
315             /* Get page object number - this needs page structure access */
316             /* For now, assume pages are numbered sequentially after Pages object */
317 3           page_obj_num = doc->pages_num + 1 + (uint32_t)i;
318            
319 3           err = collect_page_objects(lin, i, page_obj_num);
320 3 50         if (err != PDFMAKE_OK) return err;
321             }
322            
323             /* Identify shared objects (referenced by multiple pages) */
324 1           shared_cap = 64;
325 1           lin->shared_objects = pdfmake_arena_alloc(lin->arena, shared_cap * sizeof(uint32_t));
326 1 50         if (!lin->shared_objects) return PDFMAKE_ENOMEM;
327            
328 13 100         for (i = 1; i <= doc->obj_count; i++) {
329 12 100         if (lin->ref_counts[i] > 1) {
330 1 50         if (lin->shared_count >= shared_cap) {
331             /* Would need to reallocate */
332 0           continue;
333             }
334 1           lin->shared_objects[lin->shared_count++] = (uint32_t)i;
335             }
336             }
337 1           lin->shared_cap = shared_cap;
338            
339             /* Compute object renumbering for linearized layout */
340 1           next_num = 1;
341            
342             /* Object 1: linearization dictionary (will be created) */
343 1           next_num++;
344            
345             /* First page objects */
346 1 50         if (doc->page_count > 0) {
347 5 100         for (i = 0; i < lin->page_objects[0].count; i++) {
348 4           uint32_t old_num = lin->page_objects[0].objects[i];
349 4 50         if (old_num < lin->obj_map_size && lin->obj_map[old_num] == 0) {
    50          
350 4           lin->obj_map[old_num] = next_num++;
351             }
352             }
353             }
354            
355             /* Remaining page objects */
356 3 100         for (p = 1; p < doc->page_count; p++) {
357 10 100         for (i = 0; i < lin->page_objects[p].count; i++) {
358 8           uint32_t old_num = lin->page_objects[p].objects[i];
359             /* Skip shared objects (they come later) */
360 8 50         if (old_num < lin->obj_map_size && lin->ref_counts[old_num] > 1) {
    100          
361 2           continue;
362             }
363 6 50         if (old_num < lin->obj_map_size && lin->obj_map[old_num] == 0) {
    50          
364 6           lin->obj_map[old_num] = next_num++;
365             }
366             }
367             }
368            
369             /* Shared objects at the end */
370 2 100         for (i = 0; i < lin->shared_count; i++) {
371 1           uint32_t old_num = lin->shared_objects[i];
372 1 50         if (old_num < lin->obj_map_size && lin->obj_map[old_num] == 0) {
    50          
373 0           lin->obj_map[old_num] = next_num++;
374             }
375             }
376            
377             /* Map any remaining objects */
378 13 100         for (i = 1; i <= doc->obj_count; i++) {
379 12 100         if (lin->obj_map[i] == 0) {
380 2           lin->obj_map[i] = next_num++;
381             }
382             }
383            
384             /* Store parameters */
385 1           lin->params.page_count = doc->page_count;
386 1           lin->params.version = 1;
387            
388 1           return PDFMAKE_OK;
389             }
390              
391             /*============================================================================
392             * Hint Table Generation
393             *==========================================================================*/
394              
395 5           pdfmake_err_t pdfmake_linear_build_hints(pdfmake_linear_t *lin)
396             {
397             pdfmake_doc_t *doc;
398             size_t page_count;
399             size_t i;
400 5 50         if (!lin || !lin->doc) return PDFMAKE_EINVAL;
    50          
401            
402 5           doc = lin->doc;
403 5           page_count = doc->page_count;
404            
405             /* Allocate page hints */
406 5           lin->hints.page_hints = pdfmake_arena_alloc(lin->arena,
407             page_count * sizeof(pdfmake_page_hint_t));
408 5 50         if (!lin->hints.page_hints) return PDFMAKE_ENOMEM;
409 5           memset(lin->hints.page_hints, 0, page_count * sizeof(pdfmake_page_hint_t));
410 5           lin->hints.page_hint_count = page_count;
411            
412             /* Build page hints */
413 13 100         for (i = 0; i < page_count; i++) {
414 8           pdfmake_page_hint_t *hint = &lin->hints.page_hints[i];
415             uint16_t shared_count;
416             uint16_t idx;
417             size_t j;
418            
419 8           hint->obj_count = (uint32_t)lin->page_objects[i].count;
420 8           hint->page_length = 0; /* Will be computed during write */
421 8           hint->content_offset = 0;
422 8           hint->content_length = 0;
423            
424             /* Count shared object references for this page */
425 8           shared_count = 0;
426 20 100         for (j = 0; j < lin->page_objects[i].count; j++) {
427 12           uint32_t num = lin->page_objects[i].objects[j];
428 12 50         if (num < lin->obj_map_size && lin->ref_counts[num] > 1) {
    100          
429 3           shared_count++;
430             }
431             }
432 8           hint->shared_count = shared_count;
433            
434 8 100         if (shared_count > 0) {
435 3           hint->shared_ids = pdfmake_arena_alloc(lin->arena,
436             shared_count * sizeof(uint16_t));
437 3 50         if (!hint->shared_ids) return PDFMAKE_ENOMEM;
438            
439 3           idx = 0;
440 9 50         for (j = 0; j < lin->page_objects[i].count && idx < shared_count; j++) {
    100          
441 6           uint32_t num = lin->page_objects[i].objects[j];
442 6 50         if (num < lin->obj_map_size && lin->ref_counts[num] > 1) {
    100          
443             /* Find shared object index */
444             size_t k;
445 3 50         for (k = 0; k < lin->shared_count; k++) {
446 3 50         if (lin->shared_objects[k] == num) {
447 3           hint->shared_ids[idx++] = (uint16_t)k;
448 3           break;
449             }
450             }
451             }
452             }
453             }
454             }
455            
456             /* Allocate and build shared object hints */
457 10           lin->hints.shared_hints = pdfmake_arena_alloc(lin->arena,
458 5           lin->shared_count * sizeof(pdfmake_shared_hint_t));
459 5 50         if (!lin->hints.shared_hints && lin->shared_count > 0) return PDFMAKE_ENOMEM;
    0          
460 5           lin->hints.shared_hint_count = lin->shared_count;
461            
462 6 100         for (i = 0; i < lin->shared_count; i++) {
463 1           pdfmake_shared_hint_t *hint = &lin->hints.shared_hints[i];
464 1           uint32_t num = lin->shared_objects[i];
465            
466 1           hint->obj_num = lin->obj_map[num]; /* Use remapped number */
467 1           hint->offset = 0; /* Will be set during write */
468 1           hint->length = 0; /* Will be computed during write */
469 1           hint->ref_count = lin->ref_counts[num];
470             }
471            
472 5           lin->hints.arena = lin->arena;
473            
474 5           return PDFMAKE_OK;
475             }
476              
477             /*============================================================================
478             * Linearized Writer
479             *==========================================================================*/
480              
481             /* Write object with remapped references */
482 12           static pdfmake_err_t write_remapped_obj(
483             pdfmake_linear_t *lin,
484             pdfmake_buf_t *out,
485             pdfmake_obj_t *obj)
486             {
487             /* For now, use standard writer - full implementation would remap refs */
488 12           return pdfmake_write_obj(out, lin->arena, obj);
489             }
490              
491             /* Write linearization dictionary */
492 5           static pdfmake_err_t write_linearization_dict(
493             pdfmake_linear_t *lin,
494             pdfmake_buf_t *out,
495             size_t *dict_end)
496             {
497             /* Object header */
498 5           pdfmake_buf_appendf(out, "1 0 obj\n");
499            
500             /* Linearization dictionary - values will be fixed up later */
501 5           pdfmake_buf_appendf(out, "<<\n");
502 5           pdfmake_buf_appendf(out, " /Linearized 1\n");
503 5           pdfmake_buf_appendf(out, " /L %10zu\n", (size_t)0); /* File length - placeholder */
504 5           pdfmake_buf_appendf(out, " /H [ %10zu %10zu ]\n", (size_t)0, (size_t)0); /* Hint offset/length */
505 5           pdfmake_buf_appendf(out, " /O %u\n", lin->params.first_page_obj);
506 5           pdfmake_buf_appendf(out, " /E %10zu\n", (size_t)0); /* First page end */
507 5           pdfmake_buf_appendf(out, " /N %zu\n", lin->params.page_count);
508 5           pdfmake_buf_appendf(out, " /T %10zu\n", (size_t)0); /* Main xref offset */
509 5           pdfmake_buf_appendf(out, ">>\n");
510 5           pdfmake_buf_appendf(out, "endobj\n\n");
511            
512 5           *dict_end = pdfmake_buf_len(out);
513            
514 5           return PDFMAKE_OK;
515             }
516              
517             /* Write partial xref for first page */
518 5           static pdfmake_err_t write_first_page_xref(
519             pdfmake_linear_t *lin,
520             pdfmake_buf_t *out)
521             {
522 5           lin->first_page_xref_pos = pdfmake_buf_len(out);
523            
524             /* Write partial xref covering first page objects */
525 5           pdfmake_buf_appendf(out, "xref\n");
526            
527             /* For now, just a placeholder - real impl tracks object positions */
528 5           pdfmake_buf_appendf(out, "0 1\n");
529 5           pdfmake_buf_appendf(out, "0000000000 65535 f \n");
530            
531             /* Trailer for partial xref */
532 5           pdfmake_buf_appendf(out, "trailer\n");
533 5           pdfmake_buf_appendf(out, "<<\n");
534 5           pdfmake_buf_appendf(out, " /Size %zu\n", lin->doc->obj_count + 2);
535 5           pdfmake_buf_appendf(out, " /Prev %10zu\n", (size_t)0); /* Will point to main xref */
536 5           pdfmake_buf_appendf(out, " /Root %u 0 R\n", lin->doc->root_num);
537 5 100         if (lin->doc->info_num) {
538 2           pdfmake_buf_appendf(out, " /Info %u 0 R\n", lin->doc->info_num);
539             }
540 5           pdfmake_buf_appendf(out, ">>\n");
541 5           pdfmake_buf_appendf(out, "startxref\n");
542 5           pdfmake_buf_appendf(out, "0\n"); /* Placeholder */
543 5           pdfmake_buf_appendf(out, "%%%%EOF\n\n");
544            
545 5           return PDFMAKE_OK;
546             }
547              
548             /* Write hint stream */
549 5           static pdfmake_err_t write_hint_stream(
550             pdfmake_linear_t *lin,
551             pdfmake_buf_t *out)
552             {
553             pdfmake_buf_t hint_data;
554             pdfmake_err_t err;
555             uint32_t hint_obj_num;
556 5           lin->hint_stream_pos = pdfmake_buf_len(out);
557            
558             /* Build hint stream data */
559 5           pdfmake_buf_init(&hint_data);
560            
561 5           err = pdfmake_build_hint_stream(lin->arena, &lin->hints, &hint_data);
562 5 50         if (err != PDFMAKE_OK) {
563 0           pdfmake_buf_free(&hint_data);
564 0           return err;
565             }
566            
567             /* Write as stream object */
568 5           hint_obj_num = 2; /* Hint stream is typically object 2 */
569 5           pdfmake_buf_appendf(out, "%u 0 obj\n", hint_obj_num);
570 5           pdfmake_buf_appendf(out, "<<\n");
571 5           pdfmake_buf_appendf(out, " /Type /XRef\n");
572 5           pdfmake_buf_appendf(out, " /Length %zu\n", pdfmake_buf_len(&hint_data));
573 5           pdfmake_buf_appendf(out, " /S %zu\n", lin->hints.page_hint_count); /* Shared obj hint offset */
574 5           pdfmake_buf_appendf(out, ">>\n");
575 5           pdfmake_buf_appendf(out, "stream\n");
576 5           pdfmake_buf_append(out, pdfmake_buf_data(&hint_data), pdfmake_buf_len(&hint_data));
577 5           pdfmake_buf_appendf(out, "\nendstream\n");
578 5           pdfmake_buf_appendf(out, "endobj\n\n");
579            
580 5           lin->params.hint_offset = lin->hint_stream_pos;
581 5           lin->params.hint_length = pdfmake_buf_len(out) - lin->hint_stream_pos;
582            
583 5           pdfmake_buf_free(&hint_data);
584            
585 5           return PDFMAKE_OK;
586             }
587              
588 5           pdfmake_err_t pdfmake_linear_write(pdfmake_linear_t *lin, pdfmake_buf_t *out)
589             {
590             pdfmake_doc_t *doc;
591             size_t lin_dict_end;
592             pdfmake_err_t err;
593             size_t i;
594             size_t p;
595             int id_i;
596             pdfmake_obj_t *catalog;
597 5 50         if (!lin || !lin->doc || !out) return PDFMAKE_EINVAL;
    50          
    50          
598            
599 5           doc = lin->doc;
600            
601             /* 1. Write header */
602 5           pdfmake_buf_appendf(out, "%%PDF-2.0\n");
603 5           pdfmake_buf_appendf(out, "%%\xE2\xE3\xCF\xD3\n"); /* Binary comment */
604            
605             /* 2. Write linearization dictionary (object 1) */
606 5           err = write_linearization_dict(lin, out, &lin_dict_end);
607 5 50         if (err != PDFMAKE_OK) return err;
608            
609             /* 3. Write first-page cross-reference section */
610 5           err = write_first_page_xref(lin, out);
611 5 50         if (err != PDFMAKE_OK) return err;
612            
613             /* 4. Write document catalog and first-page objects */
614             /* Write catalog */
615 5           catalog = pdfmake_doc_get(doc, doc->root_num);
616 5 100         if (catalog) {
617 1           pdfmake_buf_appendf(out, "%u 0 obj\n", doc->root_num);
618 1           write_remapped_obj(lin, out, catalog);
619 1           pdfmake_buf_appendf(out, "\nendobj\n\n");
620             }
621            
622             /* Write first page objects */
623 5 50         if (doc->page_count > 0 && lin->page_objects[0].count > 0) {
    100          
624 5 100         for (i = 0; i < lin->page_objects[0].count; i++) {
625 4           uint32_t num = lin->page_objects[0].objects[i];
626 4           pdfmake_obj_t *obj = pdfmake_doc_get(doc, num);
627 4 50         if (obj && num != doc->root_num) {
    50          
628 4           uint32_t new_num = lin->obj_map[num];
629 4           pdfmake_buf_appendf(out, "%u 0 obj\n", new_num);
630 4           write_remapped_obj(lin, out, obj);
631 4           pdfmake_buf_appendf(out, "\nendobj\n\n");
632             }
633             }
634             }
635            
636 5           lin->params.first_page_end = pdfmake_buf_len(out);
637 5           lin->params.first_page_obj = doc->root_num; /* Simplified */
638            
639             /* 5. Write hint stream */
640 5           err = write_hint_stream(lin, out);
641 5 50         if (err != PDFMAKE_OK) return err;
642            
643             /* 6. Write remaining pages (2..N) */
644 8 100         for (p = 1; p < doc->page_count; p++) {
645 11 100         for (i = 0; i < lin->page_objects[p].count; i++) {
646 8           uint32_t num = lin->page_objects[p].objects[i];
647             pdfmake_obj_t *obj;
648             /* Skip if already written or shared */
649 8 100         if (lin->ref_counts[num] > 1) continue;
650            
651 6           obj = pdfmake_doc_get(doc, num);
652 6 50         if (obj) {
653 6           uint32_t new_num = lin->obj_map[num];
654 6           pdfmake_buf_appendf(out, "%u 0 obj\n", new_num);
655 6           write_remapped_obj(lin, out, obj);
656 6           pdfmake_buf_appendf(out, "\nendobj\n\n");
657             }
658             }
659             }
660            
661             /* 7. Write shared objects */
662 6 100         for (i = 0; i < lin->shared_count; i++) {
663 1           uint32_t num = lin->shared_objects[i];
664 1           pdfmake_obj_t *obj = pdfmake_doc_get(doc, num);
665 1 50         if (obj) {
666 1           uint32_t new_num = lin->obj_map[num];
667 1           pdfmake_buf_appendf(out, "%u 0 obj\n", new_num);
668 1           write_remapped_obj(lin, out, obj);
669 1           pdfmake_buf_appendf(out, "\nendobj\n\n");
670             }
671             }
672            
673             /* 8. Write main cross-reference table */
674 5           lin->main_xref_pos = pdfmake_buf_len(out);
675 5           lin->params.main_xref_offset = lin->main_xref_pos;
676            
677 5           pdfmake_buf_appendf(out, "xref\n");
678 5           pdfmake_buf_appendf(out, "0 %zu\n", doc->obj_count + 2);
679 5           pdfmake_buf_appendf(out, "0000000000 65535 f \n");
680            
681             /* Write xref entries - simplified, real impl tracks all positions */
682 27 100         for (i = 1; i <= doc->obj_count + 1; i++) {
683 22           pdfmake_buf_appendf(out, "%010zu 00000 n \n", (size_t)0);
684             }
685            
686             /* Trailer */
687 5           pdfmake_buf_appendf(out, "trailer\n");
688 5           pdfmake_buf_appendf(out, "<<\n");
689 5           pdfmake_buf_appendf(out, " /Size %zu\n", doc->obj_count + 2);
690 5           pdfmake_buf_appendf(out, " /Root %u 0 R\n", doc->root_num);
691 5 100         if (doc->info_num) {
692 2           pdfmake_buf_appendf(out, " /Info %u 0 R\n", doc->info_num);
693             }
694 5 100         if (doc->id_set) {
695 1           pdfmake_buf_appendf(out, " /ID [<");
696 17 100         for (id_i = 0; id_i < 16; id_i++) {
697 16           pdfmake_buf_appendf(out, "%02X", doc->id1[id_i]);
698             }
699 1           pdfmake_buf_appendf(out, "> <");
700 17 100         for (id_i = 0; id_i < 16; id_i++) {
701 16           pdfmake_buf_appendf(out, "%02X", doc->id2[id_i]);
702             }
703 1           pdfmake_buf_appendf(out, ">]\n");
704             }
705 5           pdfmake_buf_appendf(out, ">>\n");
706 5           pdfmake_buf_appendf(out, "startxref\n");
707 5           pdfmake_buf_appendf(out, "%zu\n", lin->main_xref_pos);
708 5           pdfmake_buf_appendf(out, "%%%%EOF\n");
709            
710             /* Update file length in linearization dictionary */
711 5           lin->params.file_length = pdfmake_buf_len(out);
712            
713             /* TODO: Fix up placeholder values in linearization dictionary */
714             /* This requires going back and patching specific byte offsets */
715            
716 5           return PDFMAKE_OK;
717             }
718              
719             /*============================================================================
720             * High-Level API
721             *==========================================================================*/
722              
723 4           pdfmake_err_t pdfmake_doc_linearize(pdfmake_doc_t *doc)
724             {
725             /* This is a no-op marker for now */
726             /* Real linearization happens during write */
727             (void)doc;
728 4           return PDFMAKE_OK;
729             }
730              
731 2           pdfmake_err_t pdfmake_doc_write_linearized(pdfmake_doc_t *doc, pdfmake_buf_t *out)
732             {
733             pdfmake_linear_t *lin;
734             pdfmake_err_t err;
735 2 50         if (!doc || !out) return PDFMAKE_EINVAL;
    50          
736            
737             /* Ensure finalized */
738 2 50         if (!doc->finalized) {
739 2           return PDFMAKE_EINVAL;
740             }
741            
742             /* Create linearization context */
743 0           lin = pdfmake_linear_new(doc);
744 0 0         if (!lin) return PDFMAKE_ENOMEM;
745            
746             /* Analyze document structure */
747 0           err = pdfmake_linear_analyze(lin);
748 0 0         if (err != PDFMAKE_OK) {
749 0           pdfmake_linear_free(lin);
750 0           return err;
751             }
752            
753             /* Build hint tables */
754 0           err = pdfmake_linear_build_hints(lin);
755 0 0         if (err != PDFMAKE_OK) {
756 0           pdfmake_linear_free(lin);
757 0           return err;
758             }
759            
760             /* Write linearized output */
761 0           err = pdfmake_linear_write(lin, out);
762            
763 0           pdfmake_linear_free(lin);
764            
765 0           return err;
766             }
767              
768 2           pdfmake_err_t pdfmake_doc_write_linearized_to_path(
769             pdfmake_doc_t *doc,
770             const char *path)
771             {
772             pdfmake_buf_t buf;
773             pdfmake_err_t err;
774             FILE *fp;
775             size_t written;
776 2 50         if (!doc || !path) return PDFMAKE_EINVAL;
    50          
777            
778 2           pdfmake_buf_init(&buf);
779            
780 2           err = pdfmake_doc_write_linearized(doc, &buf);
781 2 50         if (err != PDFMAKE_OK) {
782 2           pdfmake_buf_free(&buf);
783 2           return err;
784             }
785            
786 0           fp = fopen(path, "wb");
787 0 0         if (!fp) {
788 0           pdfmake_buf_free(&buf);
789 0           return PDFMAKE_EIO;
790             }
791            
792 0           written = fwrite(pdfmake_buf_data(&buf), 1, pdfmake_buf_len(&buf), fp);
793 0           fclose(fp);
794            
795 0           pdfmake_buf_free(&buf);
796            
797 0 0         if (written != pdfmake_buf_len(&buf)) {
798 0           return PDFMAKE_EIO;
799             }
800            
801 0           return PDFMAKE_OK;
802             }
803              
804             /*============================================================================
805             * Streaming Reader
806             *==========================================================================*/
807              
808 0           pdfmake_stream_reader_t *pdfmake_stream_reader_new(
809             pdfmake_fetch_fn fetch,
810             void *ctx)
811             {
812             pdfmake_stream_reader_t *reader;
813 0 0         if (!fetch) return NULL;
814            
815 0           reader = calloc(1, sizeof(pdfmake_stream_reader_t));
816 0 0         if (!reader) return NULL;
817            
818 0           reader->fetch = fetch;
819 0           reader->fetch_ctx = ctx;
820            
821 0           reader->arena = pdfmake_arena_new();
822 0 0         if (!reader->arena) {
823 0           free(reader);
824 0           return NULL;
825             }
826            
827 0           return reader;
828             }
829              
830 0           void pdfmake_stream_reader_free(pdfmake_stream_reader_t *reader)
831             {
832 0 0         if (!reader) return;
833            
834 0 0         if (reader->arena) {
835 0           pdfmake_arena_free(reader->arena);
836             }
837 0 0         if (reader->doc) {
838 0           pdfmake_doc_free(reader->doc);
839             }
840 0 0         if (reader->page_loaded) {
841 0           free(reader->page_loaded);
842             }
843 0 0         if (reader->header_data) {
844 0           free(reader->header_data);
845             }
846            
847 0           free(reader);
848             }
849              
850 0           pdfmake_err_t pdfmake_stream_reader_read_header(pdfmake_stream_reader_t *reader)
851             {
852             size_t header_size;
853             ssize_t bytes;
854             const char *n_ptr;
855             const char *l_ptr;
856             size_t bitmap_size;
857 0 0         if (!reader) return PDFMAKE_EINVAL;
858            
859             /* Fetch first 4KB to get linearization dict */
860 0           header_size = 4096;
861 0           reader->header_data = malloc(header_size);
862 0 0         if (!reader->header_data) return PDFMAKE_ENOMEM;
863            
864 0           bytes = reader->fetch(reader->fetch_ctx, 0, header_size, reader->header_data);
865 0 0         if (bytes < 0) {
866 0           free(reader->header_data);
867 0           reader->header_data = NULL;
868 0           return PDFMAKE_EIO;
869             }
870 0           reader->header_len = (size_t)bytes;
871            
872             /* Check if linearized */
873 0           reader->is_linearized = pdfmake_data_is_linearized(reader->header_data, reader->header_len);
874            
875 0 0         if (reader->is_linearized) {
876             /* Parse linearization dictionary */
877             /* Extract /L, /H, /O, /E, /N, /T values */
878            
879             /* For now, try to extract page count from /N */
880 0           n_ptr = strstr((char*)reader->header_data, "/N ");
881 0 0         if (n_ptr) {
882 0           reader->params.page_count = (size_t)atoi(n_ptr + 3);
883             }
884            
885             /* Extract file length from /L */
886 0           l_ptr = strstr((char*)reader->header_data, "/L ");
887 0 0         if (l_ptr) {
888 0           reader->params.file_length = (size_t)atol(l_ptr + 3);
889             }
890            
891             /* Allocate page loaded bitmap */
892 0 0         if (reader->params.page_count > 0) {
893 0           bitmap_size = (reader->params.page_count + 7) / 8;
894 0           reader->page_loaded = calloc(1, bitmap_size);
895 0 0         if (!reader->page_loaded) return PDFMAKE_ENOMEM;
896            
897             /* Mark first page as loaded (it's in the header) */
898 0           reader->page_loaded[0] |= 1;
899             }
900             }
901            
902 0           return PDFMAKE_OK;
903             }
904              
905 0           pdfmake_err_t pdfmake_stream_reader_load_hints(pdfmake_stream_reader_t *reader)
906             {
907             const char *h_ptr;
908             uint8_t *hint_data;
909             ssize_t bytes;
910             pdfmake_err_t err;
911 0 0         if (!reader || !reader->is_linearized) return PDFMAKE_EINVAL;
    0          
912 0 0         if (reader->hints_loaded) return PDFMAKE_OK;
913            
914             /* Fetch hint stream */
915 0 0         if (reader->params.hint_offset == 0 || reader->params.hint_length == 0) {
    0          
916             /* Try to parse from header data */
917 0           h_ptr = strstr((char*)reader->header_data, "/H [");
918 0 0         if (h_ptr) {
919 0           sscanf(h_ptr + 4, "%zu %zu",
920             &reader->params.hint_offset,
921             &reader->params.hint_length);
922             }
923             }
924            
925 0 0         if (reader->params.hint_offset == 0 || reader->params.hint_length == 0) {
    0          
926 0           return PDFMAKE_EINVAL;
927             }
928            
929 0           hint_data = malloc(reader->params.hint_length);
930 0 0         if (!hint_data) return PDFMAKE_ENOMEM;
931            
932 0           bytes = reader->fetch(reader->fetch_ctx,
933             reader->params.hint_offset,
934             reader->params.hint_length,
935             hint_data);
936            
937 0 0         if (bytes < 0 || (size_t)bytes < reader->params.hint_length) {
    0          
938 0           free(hint_data);
939 0           return PDFMAKE_EIO;
940             }
941            
942             /* Parse hint stream */
943 0           err = pdfmake_parse_hint_stream(
944             reader->arena,
945             hint_data,
946             reader->params.hint_length,
947             reader->params.page_count,
948             &reader->hints);
949            
950 0           free(hint_data);
951            
952 0 0         if (err == PDFMAKE_OK) {
953 0           reader->hints_loaded = 1;
954             }
955            
956 0           return err;
957             }
958              
959 0           int pdfmake_stream_reader_page_available(
960             pdfmake_stream_reader_t *reader,
961             int page_num)
962             {
963             size_t byte_idx;
964             uint8_t bit_mask;
965 0 0         if (!reader || !reader->page_loaded) return 0;
    0          
966 0 0         if (page_num < 0 || (size_t)page_num >= reader->params.page_count) return 0;
    0          
967            
968 0           byte_idx = page_num / 8;
969 0           bit_mask = 1 << (page_num % 8);
970            
971 0           return (reader->page_loaded[byte_idx] & bit_mask) != 0;
972             }
973              
974 0           pdfmake_err_t pdfmake_stream_reader_read_page(
975             pdfmake_stream_reader_t *reader,
976             int page_num)
977             {
978             size_t offset;
979             size_t length;
980             pdfmake_err_t err;
981             uint8_t *page_data;
982             ssize_t bytes;
983             size_t byte_idx;
984             uint8_t bit_mask;
985 0 0         if (!reader) return PDFMAKE_EINVAL;
986 0 0         if (page_num < 0 || (size_t)page_num >= reader->params.page_count) {
    0          
987 0           return PDFMAKE_EINVAL;
988             }
989            
990             /* Already loaded? */
991 0 0         if (pdfmake_stream_reader_page_available(reader, page_num)) {
992 0           return PDFMAKE_OK;
993             }
994            
995             /* Need hints to know where to fetch */
996 0 0         if (!reader->hints_loaded) {
997 0           err = pdfmake_stream_reader_load_hints(reader);
998 0 0         if (err != PDFMAKE_OK) return err;
999             }
1000            
1001             /* Get page byte range */
1002 0           err = pdfmake_stream_reader_page_range(reader, page_num, &offset, &length);
1003 0 0         if (err != PDFMAKE_OK) return err;
1004            
1005             /* Fetch page data */
1006 0           page_data = malloc(length);
1007 0 0         if (!page_data) return PDFMAKE_ENOMEM;
1008            
1009 0           bytes = reader->fetch(reader->fetch_ctx, offset, length, page_data);
1010 0 0         if (bytes < 0 || (size_t)bytes < length) {
    0          
1011 0           free(page_data);
1012 0           return PDFMAKE_EIO;
1013             }
1014            
1015             /* Parse page objects into document */
1016             /* (Simplified - real impl would parse and merge objects) */
1017            
1018             /* Mark page as loaded */
1019 0           byte_idx = page_num / 8;
1020 0           bit_mask = 1 << (page_num % 8);
1021 0           reader->page_loaded[byte_idx] |= bit_mask;
1022            
1023 0           free(page_data);
1024            
1025 0           return PDFMAKE_OK;
1026             }
1027              
1028 0           pdfmake_err_t pdfmake_stream_reader_page_range(
1029             pdfmake_stream_reader_t *reader,
1030             int page_num,
1031             size_t *offset,
1032             size_t *length)
1033             {
1034             pdfmake_page_hint_t *hint;
1035             size_t page_offset;
1036             int i;
1037 0 0         if (!reader || !offset || !length) return PDFMAKE_EINVAL;
    0          
    0          
1038 0 0         if (!reader->hints_loaded) return PDFMAKE_EINVAL;
1039 0 0         if (page_num < 0 || (size_t)page_num >= reader->hints.page_hint_count) {
    0          
1040 0           return PDFMAKE_EINVAL;
1041             }
1042            
1043             /* Calculate from hint tables */
1044 0           hint = &reader->hints.page_hints[page_num];
1045            
1046             /* Compute offset by summing previous page lengths */
1047 0           page_offset = reader->hints.first_page_offset;
1048 0 0         for (i = 0; i < page_num; i++) {
1049 0           page_offset += reader->hints.page_hints[i].page_length;
1050             }
1051            
1052 0           *offset = page_offset;
1053 0           *length = hint->page_length;
1054            
1055 0           return PDFMAKE_OK;
1056             }
1057              
1058 0           size_t pdfmake_stream_reader_page_count(pdfmake_stream_reader_t *reader)
1059             {
1060 0 0         if (!reader) return 0;
1061 0           return reader->params.page_count;
1062             }
1063              
1064 0           pdfmake_doc_t *pdfmake_stream_reader_doc(pdfmake_stream_reader_t *reader)
1065             {
1066 0 0         if (!reader) return NULL;
1067 0           return reader->doc;
1068             }
1069              
1070             /*============================================================================
1071             * Hint Table Parsing and Building
1072             *==========================================================================*/
1073              
1074 0           pdfmake_err_t pdfmake_parse_hint_stream(
1075             pdfmake_arena_t *arena,
1076             const uint8_t *data,
1077             size_t len,
1078             size_t page_count,
1079             pdfmake_hint_tables_t *out)
1080             {
1081 0 0         if (!arena || !data || !out) return PDFMAKE_EINVAL;
    0          
    0          
1082            
1083 0           memset(out, 0, sizeof(*out));
1084 0           out->arena = arena;
1085            
1086             /* Hint stream format (§F.4):
1087             * - Page offset hint table header
1088             * - Page offset hint table data
1089             * - Shared objects hint table header (at offset /S in hint stream dict)
1090             * - Shared objects hint table data
1091             */
1092            
1093             /* Allocate page hints */
1094 0           out->page_hints = pdfmake_arena_alloc(arena, page_count * sizeof(pdfmake_page_hint_t));
1095 0 0         if (!out->page_hints) return PDFMAKE_ENOMEM;
1096 0           memset(out->page_hints, 0, page_count * sizeof(pdfmake_page_hint_t));
1097 0           out->page_hint_count = page_count;
1098            
1099             /* Parse page offset hint table header (§F.4.2) */
1100 0 0         if (len < 36) return PDFMAKE_EINVAL; /* Minimum header size */
1101            
1102             /* Header fields are 32-bit big-endian values */
1103             /* Item 1: Min obj count per page (4 bytes) */
1104             /* Item 2: First page location (4 bytes) */
1105             /* Item 3: Bits for obj count delta (2 bytes) */
1106             /* ... and more */
1107            
1108             /* For now, simplified parsing - just extract basic structure */
1109            
1110 0           return PDFMAKE_OK;
1111             }
1112              
1113 5           pdfmake_err_t pdfmake_build_hint_stream(
1114             pdfmake_arena_t *arena,
1115             const pdfmake_hint_tables_t *hints,
1116             pdfmake_buf_t *out)
1117             {
1118             uint32_t min_obj_count;
1119             size_t i;
1120             uint8_t header[40];
1121             size_t hdr_len;
1122             uint32_t first_loc;
1123             size_t min_len;
1124 5 50         if (!arena || !hints || !out) return PDFMAKE_EINVAL;
    50          
    50          
1125            
1126             /* Build page offset hint table (§F.4.2) */
1127            
1128             /* Header */
1129             /* Item 1: Minimum object count per page */
1130 5           min_obj_count = UINT32_MAX;
1131 13 100         for (i = 0; i < hints->page_hint_count; i++) {
1132 8 100         if (hints->page_hints[i].obj_count < min_obj_count) {
1133 5           min_obj_count = hints->page_hints[i].obj_count;
1134             }
1135             }
1136 5 50         if (min_obj_count == UINT32_MAX) min_obj_count = 0;
1137            
1138             /* Write header in big-endian */
1139 5           hdr_len = 0;
1140            
1141             /* Item 1: Min obj count (4 bytes) */
1142 5           header[hdr_len++] = (min_obj_count >> 24) & 0xFF;
1143 5           header[hdr_len++] = (min_obj_count >> 16) & 0xFF;
1144 5           header[hdr_len++] = (min_obj_count >> 8) & 0xFF;
1145 5           header[hdr_len++] = min_obj_count & 0xFF;
1146            
1147             /* Item 2: First page object location (4 bytes) */
1148 5           first_loc = (uint32_t)hints->first_page_offset;
1149 5           header[hdr_len++] = (first_loc >> 24) & 0xFF;
1150 5           header[hdr_len++] = (first_loc >> 16) & 0xFF;
1151 5           header[hdr_len++] = (first_loc >> 8) & 0xFF;
1152 5           header[hdr_len++] = first_loc & 0xFF;
1153            
1154             /* Item 3: Bits for obj count delta (2 bytes) */
1155 5           header[hdr_len++] = 0;
1156 5           header[hdr_len++] = 16; /* 16 bits */
1157            
1158             /* Item 4: Min page length (4 bytes) */
1159 5           min_len = SIZE_MAX;
1160 13 100         for (i = 0; i < hints->page_hint_count; i++) {
1161 8 100         if (hints->page_hints[i].page_length < min_len) {
1162 5           min_len = hints->page_hints[i].page_length;
1163             }
1164             }
1165 5 50         if (min_len == SIZE_MAX) min_len = 0;
1166 5           header[hdr_len++] = (min_len >> 24) & 0xFF;
1167 5           header[hdr_len++] = (min_len >> 16) & 0xFF;
1168 5           header[hdr_len++] = (min_len >> 8) & 0xFF;
1169 5           header[hdr_len++] = min_len & 0xFF;
1170            
1171             /* Item 5: Bits for page length delta (2 bytes) */
1172 5           header[hdr_len++] = 0;
1173 5           header[hdr_len++] = 32; /* 32 bits */
1174            
1175             /* Item 6: Min content stream offset (4 bytes) - 0 */
1176 5           header[hdr_len++] = 0;
1177 5           header[hdr_len++] = 0;
1178 5           header[hdr_len++] = 0;
1179 5           header[hdr_len++] = 0;
1180            
1181             /* Item 7: Bits for content offset delta (2 bytes) */
1182 5           header[hdr_len++] = 0;
1183 5           header[hdr_len++] = 0;
1184            
1185             /* Item 8: Min content length (4 bytes) - 0 */
1186 5           header[hdr_len++] = 0;
1187 5           header[hdr_len++] = 0;
1188 5           header[hdr_len++] = 0;
1189 5           header[hdr_len++] = 0;
1190            
1191             /* Item 9: Bits for content length delta (2 bytes) */
1192 5           header[hdr_len++] = 0;
1193 5           header[hdr_len++] = 0;
1194            
1195             /* Item 10: Bits for shared obj refs (2 bytes) */
1196 5           header[hdr_len++] = 0;
1197 5           header[hdr_len++] = 16;
1198            
1199             /* Item 11: Bits for shared obj identifier (2 bytes) */
1200 5           header[hdr_len++] = 0;
1201 5           header[hdr_len++] = 16;
1202            
1203             /* Item 12: Bits for numerator (2 bytes) */
1204 5           header[hdr_len++] = 0;
1205 5           header[hdr_len++] = 0;
1206            
1207             /* Item 13: Denominator (2 bytes) */
1208 5           header[hdr_len++] = 0;
1209 5           header[hdr_len++] = 1;
1210            
1211 5           pdfmake_buf_append(out, header, hdr_len);
1212            
1213             /* Write per-page data */
1214 13 100         for (i = 0; i < hints->page_hint_count; i++) {
1215 8           pdfmake_page_hint_t *hint = &hints->page_hints[i];
1216             uint16_t obj_delta;
1217             uint32_t len_delta;
1218             uint16_t j;
1219            
1220             /* Object count delta (16 bits) */
1221 8           obj_delta = hint->obj_count - min_obj_count;
1222 8           pdfmake_buf_append_byte(out, (obj_delta >> 8) & 0xFF);
1223 8           pdfmake_buf_append_byte(out, obj_delta & 0xFF);
1224            
1225             /* Page length delta (32 bits) */
1226 8           len_delta = (uint32_t)(hint->page_length - min_len);
1227 8           pdfmake_buf_append_byte(out, (len_delta >> 24) & 0xFF);
1228 8           pdfmake_buf_append_byte(out, (len_delta >> 16) & 0xFF);
1229 8           pdfmake_buf_append_byte(out, (len_delta >> 8) & 0xFF);
1230 8           pdfmake_buf_append_byte(out, len_delta & 0xFF);
1231            
1232             /* Shared object count (16 bits) */
1233 8           pdfmake_buf_append_byte(out, (hint->shared_count >> 8) & 0xFF);
1234 8           pdfmake_buf_append_byte(out, hint->shared_count & 0xFF);
1235            
1236             /* Shared object identifiers */
1237 11 100         for (j = 0; j < hint->shared_count; j++) {
1238 3 50         uint16_t id = hint->shared_ids ? hint->shared_ids[j] : 0;
1239 3           pdfmake_buf_append_byte(out, (id >> 8) & 0xFF);
1240 3           pdfmake_buf_append_byte(out, id & 0xFF);
1241             }
1242             }
1243            
1244             /* Build shared objects hint table (§F.4.3) */
1245 5 100         if (hints->shared_hint_count > 0) {
1246             uint32_t first_shared;
1247             uint32_t first_offset;
1248             uint32_t count;
1249             /* Header */
1250             /* Item 1: First shared object number (4 bytes) */
1251 1           first_shared = hints->shared_hints[0].obj_num;
1252 1           pdfmake_buf_append_byte(out, (first_shared >> 24) & 0xFF);
1253 1           pdfmake_buf_append_byte(out, (first_shared >> 16) & 0xFF);
1254 1           pdfmake_buf_append_byte(out, (first_shared >> 8) & 0xFF);
1255 1           pdfmake_buf_append_byte(out, first_shared & 0xFF);
1256            
1257             /* Item 2: First shared object offset (4 bytes) */
1258 1           first_offset = (uint32_t)hints->shared_hints[0].offset;
1259 1           pdfmake_buf_append_byte(out, (first_offset >> 24) & 0xFF);
1260 1           pdfmake_buf_append_byte(out, (first_offset >> 16) & 0xFF);
1261 1           pdfmake_buf_append_byte(out, (first_offset >> 8) & 0xFF);
1262 1           pdfmake_buf_append_byte(out, first_offset & 0xFF);
1263            
1264             /* Item 3: Number of first page shared objects (4 bytes) */
1265 1           pdfmake_buf_append_byte(out, 0);
1266 1           pdfmake_buf_append_byte(out, 0);
1267 1           pdfmake_buf_append_byte(out, 0);
1268 1           pdfmake_buf_append_byte(out, 0);
1269            
1270             /* Item 4: Number of shared objects (4 bytes) */
1271 1           count = (uint32_t)hints->shared_hint_count;
1272 1           pdfmake_buf_append_byte(out, (count >> 24) & 0xFF);
1273 1           pdfmake_buf_append_byte(out, (count >> 16) & 0xFF);
1274 1           pdfmake_buf_append_byte(out, (count >> 8) & 0xFF);
1275 1           pdfmake_buf_append_byte(out, count & 0xFF);
1276            
1277             /* Item 5: Bits for object length delta (2 bytes) */
1278 1           pdfmake_buf_append_byte(out, 0);
1279 1           pdfmake_buf_append_byte(out, 32);
1280            
1281             /* Min shared object length (4 bytes) */
1282 1           pdfmake_buf_append_byte(out, 0);
1283 1           pdfmake_buf_append_byte(out, 0);
1284 1           pdfmake_buf_append_byte(out, 0);
1285 1           pdfmake_buf_append_byte(out, 0);
1286            
1287             /* Per-object data */
1288 2 100         for (i = 0; i < hints->shared_hint_count; i++) {
1289 1           pdfmake_shared_hint_t *hint = &hints->shared_hints[i];
1290            
1291             /* Length delta (32 bits) */
1292 1           uint32_t len = (uint32_t)hint->length;
1293 1           pdfmake_buf_append_byte(out, (len >> 24) & 0xFF);
1294 1           pdfmake_buf_append_byte(out, (len >> 16) & 0xFF);
1295 1           pdfmake_buf_append_byte(out, (len >> 8) & 0xFF);
1296 1           pdfmake_buf_append_byte(out, len & 0xFF);
1297             }
1298             }
1299            
1300 5           return PDFMAKE_OK;
1301             }