File Coverage

src/pdfmake_redact.c
Criterion Covered Total %
statement 35 111 31.5
branch 15 72 20.8
condition n/a
subroutine n/a
pod n/a
total 50 183 27.3


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_redact.c — Secure PDF redaction.
3             *
4             * §12.5.6.17 Redaction Annotations
5             */
6              
7             #include "pdfmake_redact.h"
8             #include "pdfmake_page.h"
9             #include "pdfmake_arena.h"
10             #include "pdfmake_content.h"
11             #include
12             #include
13             #include
14              
15             /* ── Mark ──────────────────────────────────────────────── */
16              
17 22           pdfmake_redact_t *pdfmake_page_mark_redaction(
18             pdfmake_page_t *page,
19             double x0, double y0, double x1, double y1,
20             const pdfmake_redact_opts_t *opts)
21             {
22             pdfmake_redact_t *r;
23              
24 22 50         if (!page) return NULL;
25              
26             /* Grow array */
27 22 100         if (page->redact_count >= page->redact_cap) {
28 11 100         size_t new_cap = page->redact_cap == 0 ? 4 : page->redact_cap * 2;
29 11           void *new_arr = realloc(page->redactions, new_cap * sizeof(pdfmake_redact_t));
30 11 50         if (!new_arr) return NULL;
31 11           page->redactions = new_arr;
32 11           page->redact_cap = new_cap;
33             }
34              
35 22           r = &((pdfmake_redact_t *)page->redactions)[page->redact_count];
36 22           memset(r, 0, sizeof(*r));
37 22           r->rect[0] = x0;
38 22           r->rect[1] = y0;
39 22           r->rect[2] = x1;
40 22           r->rect[3] = y1;
41              
42 22 50         if (opts) {
43 22           r->overlay_color[0] = opts->overlay_color[0];
44 22           r->overlay_color[1] = opts->overlay_color[1];
45 22           r->overlay_color[2] = opts->overlay_color[2];
46 22 100         if (opts->overlay_text) {
47 17           strncpy(r->overlay_text, opts->overlay_text, sizeof(r->overlay_text) - 1);
48             }
49 22 50         r->overlay_font_size = opts->overlay_font_size > 0 ? opts->overlay_font_size : 10;
50             } else {
51             /* Default: black fill, no text */
52 0           r->overlay_color[0] = 0;
53 0           r->overlay_color[1] = 0;
54 0           r->overlay_color[2] = 0;
55 0           r->overlay_font_size = 10;
56             }
57              
58 22           page->redact_count++;
59 22           return r;
60             }
61              
62 9           size_t pdfmake_page_redaction_count(pdfmake_page_t *page) {
63 9 50         return page ? page->redact_count : 0;
64             }
65              
66 0           pdfmake_redact_t *pdfmake_page_redaction_at(pdfmake_page_t *page, size_t idx) {
67 0 0         if (!page || idx >= page->redact_count) return NULL;
    0          
68 0           return &((pdfmake_redact_t *)page->redactions)[idx];
69             }
70              
71             /* ── Apply ─────────────────────────────────────────────── */
72              
73 0           pdfmake_err_t pdfmake_page_apply_redactions(pdfmake_page_t *page) {
74             pdfmake_doc_t *doc;
75             pdfmake_arena_t *arena;
76 0           uint8_t *old_content = NULL;
77 0           size_t old_len = 0;
78             pdfmake_content_t *c;
79             const uint8_t *new_data;
80             size_t new_len;
81             pdfmake_obj_t new_stream;
82             uint32_t new_num;
83             size_t i;
84              
85 0 0         if (!page || page->redact_count == 0) return PDFMAKE_OK;
    0          
86              
87 0           doc = page->doc;
88 0           arena = pdfmake_doc_arena(doc);
89              
90             /* Get existing content stream data */
91 0 0         if (page->has_content && page->contents_num > 0) {
    0          
92 0           pdfmake_obj_t *stream_obj = pdfmake_doc_get(doc, page->contents_num);
93 0 0         if (stream_obj && stream_obj->kind == PDFMAKE_STREAM) {
    0          
94 0           const uint8_t *sdata = stream_obj->as.stream->raw;
95 0           size_t slen = stream_obj->as.stream->raw_len;
96 0 0         if (sdata && slen > 0) {
    0          
97 0           old_content = malloc(slen);
98 0 0         if (old_content) {
99 0           memcpy(old_content, sdata, slen);
100 0           old_len = slen;
101             }
102             }
103             }
104             }
105              
106             /* Build new content stream with redaction overlays appended */
107 0           c = pdfmake_content_new(arena);
108 0 0         if (!c) { free(old_content); return PDFMAKE_ENOMEM; }
109              
110             /* Copy existing content (we leave it in — proper removal would
111             * require parsing and selectively removing operators within rects.
112             * For v1: we overlay with opaque fill which is the common approach
113             * even in commercial tools. Content bytes remain but are visually
114             * hidden and covered by the overlay. For true content removal,
115             * phase 11 content interpreter would need to rewrite the stream. */
116 0 0         if (old_content && old_len > 0) {
    0          
117 0           pdfmake_buf_append(&c->buf, old_content, old_len);
118 0           pdfmake_buf_append_byte(&c->buf, '\n');
119             }
120 0           free(old_content);
121              
122             /* Draw redaction overlays */
123 0 0         for (i = 0; i < page->redact_count; i++) {
124 0           pdfmake_redact_t *r = &((pdfmake_redact_t *)page->redactions)[i];
125             double x0, y0, x1, y1, w, h;
126              
127 0 0         if (r->applied) continue;
128              
129 0           x0 = r->rect[0]; y0 = r->rect[1];
130 0           x1 = r->rect[2]; y1 = r->rect[3];
131 0           w = x1 - x0; h = y1 - y0;
132              
133             /* Save state, fill rect with overlay color */
134 0           pdfmake_gs_q(c);
135 0           pdfmake_color_rg(c, r->overlay_color[0], r->overlay_color[1], r->overlay_color[2]);
136 0           pdfmake_path_re(c, x0, y0, w, h);
137 0           pdfmake_paint_f(c);
138              
139             /* Overlay text if specified */
140 0 0         if (r->overlay_text[0]) {
141             /* Ensure the page has a Helvetica font we can reference. */
142 0           const char *font_name = NULL;
143             size_t fi;
144 0 0         for (fi = 0; fi < page->font_count; fi++) {
145             /* Any font will keep the /Tf valid; Helvetica is
146             * preferred but any resolvable name is enough to avoid
147             * the "unknown font" error in readers. */
148 0           font_name = page->fonts[fi].name;
149 0           break;
150             }
151 0 0         if (!font_name) {
152 0 0         if (pdfmake_page_add_font(page, "RedactF", "Helvetica") != 0) {
153 0           font_name = "RedactF";
154             }
155             }
156              
157 0 0         if (font_name) {
158             double ty;
159             double tx;
160 0           pdfmake_color_rg(c, 1, 1, 1); /* White text */
161 0           pdfmake_text_BT(c);
162 0           pdfmake_text_Tf(c, font_name, r->overlay_font_size);
163              
164             /* Center text vertically */
165 0           ty = y0 + (h - r->overlay_font_size) / 2;
166 0           tx = x0 + 4;
167 0           pdfmake_text_Td(c, tx, ty);
168 0           pdfmake_text_Tj(c, (const uint8_t *)r->overlay_text,
169 0           strlen(r->overlay_text));
170 0           pdfmake_text_ET(c);
171             }
172             }
173              
174 0           pdfmake_gs_Q(c);
175 0           r->applied = 1;
176             }
177              
178             /* Replace content stream */
179 0           new_data = pdfmake_content_data(c);
180 0           new_len = pdfmake_content_len(c);
181              
182 0           new_stream = pdfmake_stream_new(arena);
183 0           pdfmake_stream_set_data(arena, &new_stream, new_data, new_len);
184 0           new_num = pdfmake_doc_add(doc, new_stream);
185              
186 0           page->contents_num = new_num;
187 0           page->has_content = 1;
188              
189             {
190 0           pdfmake_arena_t *content_arena = c->arena;
191 0           pdfmake_content_free(c);
192 0           pdfmake_arena_free(content_arena);
193             }
194              
195 0           return PDFMAKE_OK;
196             }
197              
198 0           pdfmake_err_t pdfmake_doc_apply_redactions(pdfmake_doc_t *doc) {
199             size_t i;
200 0 0         if (!doc) return PDFMAKE_EINVAL;
201 0 0         for (i = 0; i < doc->page_count; i++) {
202 0           pdfmake_err_t err = pdfmake_page_apply_redactions(doc->pages[i]);
203 0 0         if (err != PDFMAKE_OK) return err;
204             }
205 0           return PDFMAKE_OK;
206             }
207              
208             /* ── Sanitize ──────────────────────────────────────────── */
209              
210 5           pdfmake_err_t pdfmake_doc_sanitize_metadata(pdfmake_doc_t *doc) {
211 5 50         if (!doc) return PDFMAKE_EINVAL;
212              
213             /* Clear /Info dictionary if present */
214 5 50         if (doc->info_num > 0) {
215 5           pdfmake_arena_t *arena = pdfmake_doc_arena(doc);
216 5           pdfmake_obj_t *info = pdfmake_doc_get(doc, doc->info_num);
217 5 50         if (info && info->kind == PDFMAKE_DICT) {
    50          
218             /* Replace with empty dict */
219 5           *info = pdfmake_dict_new(arena);
220             }
221             }
222              
223             /* Regenerate document IDs */
224 5           doc->id_set = 0;
225 5           pdfmake_doc_generate_id(doc);
226              
227 5           return PDFMAKE_OK;
228             }