File Coverage

src/pdfmake_edit.c
Criterion Covered Total %
statement 65 109 59.6
branch 32 70 45.7
condition n/a
subroutine n/a
pod n/a
total 97 179 54.1


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_edit.c — Page-level editing operations
3             */
4              
5             #include "pdfmake_edit.h"
6             #include "pdfmake_arena.h"
7             #include "pdfmake_writer.h"
8             #include
9             #include
10              
11             /*============================================================================
12             * Page editing operations
13             *==========================================================================*/
14              
15 2           pdfmake_err_t pdfmake_doc_insert_page(pdfmake_doc_t *doc, size_t idx,
16             pdfmake_page_t *page) {
17 2 50         if (!doc || !page) return PDFMAKE_EINVAL;
    50          
18 2 50         if (idx > doc->page_count) return PDFMAKE_EINVAL;
19            
20             /* Grow array if needed */
21 2 50         if (doc->page_count >= doc->page_cap) {
22 0 0         size_t new_cap = doc->page_cap == 0 ? 4 : doc->page_cap * 2;
23 0           pdfmake_page_t **new_pages = realloc(doc->pages,
24             new_cap * sizeof(pdfmake_page_t *));
25 0 0         if (!new_pages) return PDFMAKE_ENOMEM;
26 0           doc->pages = new_pages;
27 0           doc->page_cap = new_cap;
28             }
29            
30             /* Shift pages to make room */
31 2 100         if (idx < doc->page_count) {
32 1           memmove(&doc->pages[idx + 1], &doc->pages[idx],
33 1           (doc->page_count - idx) * sizeof(pdfmake_page_t *));
34             }
35            
36 2           doc->pages[idx] = page;
37 2           doc->page_count++;
38            
39             /* Document needs re-finalization */
40 2           doc->finalized = 0;
41            
42 2           return PDFMAKE_OK;
43             }
44              
45 3           pdfmake_err_t pdfmake_doc_remove_page(pdfmake_doc_t *doc, size_t idx) {
46 3 50         if (!doc) return PDFMAKE_EINVAL;
47 3 100         if (idx >= doc->page_count) return PDFMAKE_EINVAL;
48            
49             /* The page is arena-allocated, so no need to free */
50            
51             /* Shift remaining pages left */
52 2 100         if (idx < doc->page_count - 1) {
53 1           memmove(&doc->pages[idx], &doc->pages[idx + 1],
54 1           (doc->page_count - idx - 1) * sizeof(pdfmake_page_t *));
55             }
56            
57 2           doc->page_count--;
58 2           doc->finalized = 0;
59            
60 2           return PDFMAKE_OK;
61             }
62              
63 2           pdfmake_err_t pdfmake_doc_move_page(pdfmake_doc_t *doc, size_t from_idx,
64             size_t to_idx) {
65             pdfmake_page_t *page;
66              
67 2 50         if (!doc) return PDFMAKE_EINVAL;
68 2 100         if (from_idx >= doc->page_count) return PDFMAKE_EINVAL;
69 1 50         if (to_idx >= doc->page_count) return PDFMAKE_EINVAL;
70 1 50         if (from_idx == to_idx) return PDFMAKE_OK;
71              
72 1           page = doc->pages[from_idx];
73            
74 1 50         if (from_idx < to_idx) {
75             /* Moving forward: shift elements left */
76 0           memmove(&doc->pages[from_idx], &doc->pages[from_idx + 1],
77 0           (to_idx - from_idx) * sizeof(pdfmake_page_t *));
78             } else {
79             /* Moving backward: shift elements right */
80 1           memmove(&doc->pages[to_idx + 1], &doc->pages[to_idx],
81 1           (from_idx - to_idx) * sizeof(pdfmake_page_t *));
82             }
83            
84 1           doc->pages[to_idx] = page;
85 1           doc->finalized = 0;
86            
87 1           return PDFMAKE_OK;
88             }
89              
90 4           pdfmake_err_t pdfmake_doc_rotate_page(pdfmake_doc_t *doc, size_t idx,
91             pdfmake_rotation_t degrees) {
92             pdfmake_page_t *page;
93             int current_rotation;
94             int new_rotation;
95              
96 4 50         if (!doc) return PDFMAKE_EINVAL;
97 4 50         if (idx >= doc->page_count) return PDFMAKE_EINVAL;
98              
99             /* Validate rotation value */
100 4 50         if (degrees != PDFMAKE_ROTATE_0 &&
    100          
101 2 100         degrees != PDFMAKE_ROTATE_90 &&
102 1 50         degrees != PDFMAKE_ROTATE_180 &&
103             degrees != PDFMAKE_ROTATE_270) {
104 0           return PDFMAKE_EINVAL;
105             }
106              
107 4           page = doc->pages[idx];
108 4 50         if (!page) return PDFMAKE_EINVAL;
109              
110             /* Store rotation in the page structure.
111             * This is applied when building the page dict at finalize time. */
112 4           current_rotation = page->rotation;
113 4           new_rotation = (current_rotation + (int)degrees) % 360;
114 4 50         if (new_rotation < 0) new_rotation += 360;
115            
116 4           page->rotation = new_rotation;
117 4           doc->finalized = 0;
118            
119 4           return PDFMAKE_OK;
120             }
121              
122 3           pdfmake_err_t pdfmake_doc_duplicate_page(pdfmake_doc_t *doc, size_t idx) {
123             pdfmake_page_t *src_page;
124             pdfmake_page_t *new_page;
125              
126 3 50         if (!doc) return PDFMAKE_EINVAL;
127 3 100         if (idx >= doc->page_count) return PDFMAKE_EINVAL;
128              
129 2           src_page = doc->pages[idx];
130 2 50         if (!src_page) return PDFMAKE_EINVAL;
131              
132             /* Create new page structure */
133 2           new_page = pdfmake_arena_calloc(doc->arena, sizeof(pdfmake_page_t));
134 2 50         if (!new_page) return PDFMAKE_ENOMEM;
135            
136 2           new_page->doc = doc;
137 2           new_page->width = src_page->width;
138 2           new_page->height = src_page->height;
139 2           new_page->rotation = src_page->rotation;
140            
141             /* Copy font resources (shared - same object numbers) */
142 2           memcpy(new_page->fonts, src_page->fonts, sizeof(src_page->fonts));
143 2           new_page->font_count = src_page->font_count;
144            
145             /* Copy image resources (shared) */
146 2           memcpy(new_page->images, src_page->images, sizeof(src_page->images));
147 2           new_page->image_count = src_page->image_count;
148            
149             /* Share content stream reference - the /Contents will point to same stream */
150 2           new_page->contents_num = src_page->contents_num;
151 2           new_page->has_content = src_page->has_content;
152            
153             /* page_num will be assigned during finalize */
154 2           new_page->page_num = 0;
155            
156             /* Insert the new page after the source */
157 2           return pdfmake_doc_insert_page(doc, idx + 1, new_page);
158             }
159              
160             /*============================================================================
161             * Document merge
162             *==========================================================================*/
163              
164 0           pdfmake_err_t pdfmake_doc_merge(pdfmake_doc_t *dst, pdfmake_doc_t *src) {
165             size_t i;
166              
167 0 0         if (!dst || !src) return PDFMAKE_EINVAL;
    0          
168 0 0         if (src->page_count == 0) return PDFMAKE_OK; /* Nothing to merge */
169            
170             /* For now, merge simply copies page structures from src to dst.
171             * This works for pre-finalize documents where pages just hold
172             * dimensions and resource references.
173             *
174             * A full implementation would handle:
175             * - Cloning indirect objects with renumbered refs
176             * - Merging resource pools (fonts, images)
177             * - Handling finalized documents
178             */
179            
180 0 0         for (i = 0; i < src->page_count; i++) {
181 0           pdfmake_page_t *src_page = src->pages[i];
182             pdfmake_page_t *new_page;
183             pdfmake_err_t err;
184              
185             /* Create new page with same dimensions */
186 0           new_page = pdfmake_arena_calloc(dst->arena,
187             sizeof(pdfmake_page_t));
188 0 0         if (!new_page) return PDFMAKE_ENOMEM;
189              
190 0           new_page->doc = dst;
191 0           new_page->width = src_page->width;
192 0           new_page->height = src_page->height;
193 0           new_page->page_num = 0; /* Will be assigned at finalize */
194              
195             /* Note: For a complete merge we would need to:
196             * 1. Clone font objects from src to dst
197             * 2. Clone content streams from src to dst
198             * 3. Update references accordingly
199             *
200             * For now, pages are added without content/resources
201             * A proper implementation requires object cloning infrastructure.
202             */
203              
204 0           new_page->font_count = 0;
205 0           new_page->image_count = 0;
206 0           new_page->has_content = 0;
207 0           new_page->contents_num = 0;
208              
209             /* Insert into dst */
210 0           err = pdfmake_doc_insert_page(dst, dst->page_count, new_page);
211 0 0         if (err != PDFMAKE_OK) return err;
212             }
213            
214 0           dst->finalized = 0;
215            
216 0           return PDFMAKE_OK;
217             }
218              
219             /*============================================================================
220             * Save operations
221             *==========================================================================*/
222              
223 0           pdfmake_err_t pdfmake_doc_save(pdfmake_doc_t *doc, pdfmake_buf_t *out,
224             pdfmake_save_mode_t mode) {
225 0 0         if (!doc || !out) return PDFMAKE_EINVAL;
    0          
226            
227 0           switch (mode) {
228 0           case PDFMAKE_SAVE_FULL:
229 0           return pdfmake_doc_write(doc, out);
230            
231 0           case PDFMAKE_SAVE_INCREMENTAL:
232             /* TODO: Implement incremental save */
233 0           return PDFMAKE_EINVAL;
234            
235 0           case PDFMAKE_SAVE_COMPACT:
236             /* TODO: Implement object stream packing */
237 0           return pdfmake_doc_write(doc, out);
238            
239 0           default:
240 0           return PDFMAKE_EINVAL;
241             }
242             }
243              
244 0           pdfmake_err_t pdfmake_doc_set_original(pdfmake_doc_t *doc,
245             const uint8_t *data, size_t len) {
246             /* TODO: Store original bytes for incremental updates */
247             (void)doc;
248             (void)data;
249             (void)len;
250 0           return PDFMAKE_OK;
251             }
252              
253 0           void pdfmake_doc_mark_modified(pdfmake_doc_t *doc, uint32_t obj_num) {
254             /* TODO: Track modified objects for incremental updates */
255             (void)doc;
256             (void)obj_num;
257 0           }
258              
259 0           void pdfmake_doc_set_encrypt_hook(pdfmake_doc_t *doc,
260             pdfmake_encrypt_hook_t hook,
261             void *ctx) {
262             /* Placeholder for Phase 16 encryption */
263             (void)doc;
264             (void)hook;
265             (void)ctx;
266 0           }