PDF-Make
view release on metacpan or search on metacpan
src/pdfmake_redact.c view on Meta::CPAN
/*
* pdfmake_redact.c â Secure PDF redaction.
*
* §12.5.6.17 Redaction Annotations
*/
#include "pdfmake_redact.h"
#include "pdfmake_page.h"
#include "pdfmake_arena.h"
#include "pdfmake_content.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
/* ââ Mark ââââââââââââââââââââââââââââââââââââââââââââââââ */
pdfmake_redact_t *pdfmake_page_mark_redaction(
pdfmake_page_t *page,
double x0, double y0, double x1, double y1,
const pdfmake_redact_opts_t *opts)
{
pdfmake_redact_t *r;
if (!page) return NULL;
/* Grow array */
if (page->redact_count >= page->redact_cap) {
size_t new_cap = page->redact_cap == 0 ? 4 : page->redact_cap * 2;
void *new_arr = realloc(page->redactions, new_cap * sizeof(pdfmake_redact_t));
if (!new_arr) return NULL;
page->redactions = new_arr;
page->redact_cap = new_cap;
}
r = &((pdfmake_redact_t *)page->redactions)[page->redact_count];
memset(r, 0, sizeof(*r));
r->rect[0] = x0;
r->rect[1] = y0;
r->rect[2] = x1;
r->rect[3] = y1;
if (opts) {
r->overlay_color[0] = opts->overlay_color[0];
r->overlay_color[1] = opts->overlay_color[1];
r->overlay_color[2] = opts->overlay_color[2];
if (opts->overlay_text) {
strncpy(r->overlay_text, opts->overlay_text, sizeof(r->overlay_text) - 1);
}
r->overlay_font_size = opts->overlay_font_size > 0 ? opts->overlay_font_size : 10;
} else {
/* Default: black fill, no text */
r->overlay_color[0] = 0;
r->overlay_color[1] = 0;
r->overlay_color[2] = 0;
r->overlay_font_size = 10;
}
page->redact_count++;
return r;
}
size_t pdfmake_page_redaction_count(pdfmake_page_t *page) {
return page ? page->redact_count : 0;
}
pdfmake_redact_t *pdfmake_page_redaction_at(pdfmake_page_t *page, size_t idx) {
if (!page || idx >= page->redact_count) return NULL;
return &((pdfmake_redact_t *)page->redactions)[idx];
}
/* ââ Apply âââââââââââââââââââââââââââââââââââââââââââââââ */
pdfmake_err_t pdfmake_page_apply_redactions(pdfmake_page_t *page) {
pdfmake_doc_t *doc;
pdfmake_arena_t *arena;
uint8_t *old_content = NULL;
size_t old_len = 0;
pdfmake_content_t *c;
const uint8_t *new_data;
size_t new_len;
pdfmake_obj_t new_stream;
uint32_t new_num;
size_t i;
if (!page || page->redact_count == 0) return PDFMAKE_OK;
doc = page->doc;
arena = pdfmake_doc_arena(doc);
/* Get existing content stream data */
if (page->has_content && page->contents_num > 0) {
pdfmake_obj_t *stream_obj = pdfmake_doc_get(doc, page->contents_num);
if (stream_obj && stream_obj->kind == PDFMAKE_STREAM) {
const uint8_t *sdata = stream_obj->as.stream->raw;
size_t slen = stream_obj->as.stream->raw_len;
if (sdata && slen > 0) {
old_content = malloc(slen);
if (old_content) {
memcpy(old_content, sdata, slen);
old_len = slen;
}
}
}
}
/* Build new content stream with redaction overlays appended */
c = pdfmake_content_new(arena);
if (!c) { free(old_content); return PDFMAKE_ENOMEM; }
/* Copy existing content (we leave it in â proper removal would
* require parsing and selectively removing operators within rects.
* For v1: we overlay with opaque fill which is the common approach
* even in commercial tools. Content bytes remain but are visually
* hidden and covered by the overlay. For true content removal,
* phase 11 content interpreter would need to rewrite the stream. */
if (old_content && old_len > 0) {
pdfmake_buf_append(&c->buf, old_content, old_len);
pdfmake_buf_append_byte(&c->buf, '\n');
}
free(old_content);
/* Draw redaction overlays */
for (i = 0; i < page->redact_count; i++) {
pdfmake_redact_t *r = &((pdfmake_redact_t *)page->redactions)[i];
double x0, y0, x1, y1, w, h;
if (r->applied) continue;
x0 = r->rect[0]; y0 = r->rect[1];
x1 = r->rect[2]; y1 = r->rect[3];
w = x1 - x0; h = y1 - y0;
/* Save state, fill rect with overlay color */
pdfmake_gs_q(c);
pdfmake_color_rg(c, r->overlay_color[0], r->overlay_color[1], r->overlay_color[2]);
pdfmake_path_re(c, x0, y0, w, h);
pdfmake_paint_f(c);
/* Overlay text if specified */
if (r->overlay_text[0]) {
/* Ensure the page has a Helvetica font we can reference. */
const char *font_name = NULL;
size_t fi;
for (fi = 0; fi < page->font_count; fi++) {
/* Any font will keep the /Tf valid; Helvetica is
* preferred but any resolvable name is enough to avoid
* the "unknown font" error in readers. */
font_name = page->fonts[fi].name;
break;
}
if (!font_name) {
if (pdfmake_page_add_font(page, "RedactF", "Helvetica") != 0) {
font_name = "RedactF";
}
}
if (font_name) {
double ty;
double tx;
pdfmake_color_rg(c, 1, 1, 1); /* White text */
pdfmake_text_BT(c);
pdfmake_text_Tf(c, font_name, r->overlay_font_size);
/* Center text vertically */
ty = y0 + (h - r->overlay_font_size) / 2;
tx = x0 + 4;
pdfmake_text_Td(c, tx, ty);
pdfmake_text_Tj(c, (const uint8_t *)r->overlay_text,
strlen(r->overlay_text));
pdfmake_text_ET(c);
}
}
pdfmake_gs_Q(c);
r->applied = 1;
}
/* Replace content stream */
new_data = pdfmake_content_data(c);
new_len = pdfmake_content_len(c);
new_stream = pdfmake_stream_new(arena);
pdfmake_stream_set_data(arena, &new_stream, new_data, new_len);
new_num = pdfmake_doc_add(doc, new_stream);
page->contents_num = new_num;
page->has_content = 1;
{
pdfmake_arena_t *content_arena = c->arena;
pdfmake_content_free(c);
pdfmake_arena_free(content_arena);
}
return PDFMAKE_OK;
}
pdfmake_err_t pdfmake_doc_apply_redactions(pdfmake_doc_t *doc) {
size_t i;
if (!doc) return PDFMAKE_EINVAL;
for (i = 0; i < doc->page_count; i++) {
pdfmake_err_t err = pdfmake_page_apply_redactions(doc->pages[i]);
if (err != PDFMAKE_OK) return err;
}
return PDFMAKE_OK;
}
/* ââ Sanitize ââââââââââââââââââââââââââââââââââââââââââââ */
pdfmake_err_t pdfmake_doc_sanitize_metadata(pdfmake_doc_t *doc) {
if (!doc) return PDFMAKE_EINVAL;
/* Clear /Info dictionary if present */
if (doc->info_num > 0) {
pdfmake_arena_t *arena = pdfmake_doc_arena(doc);
pdfmake_obj_t *info = pdfmake_doc_get(doc, doc->info_num);
if (info && info->kind == PDFMAKE_DICT) {
/* Replace with empty dict */
*info = pdfmake_dict_new(arena);
}
}
/* Regenerate document IDs */
doc->id_set = 0;
pdfmake_doc_generate_id(doc);
return PDFMAKE_OK;
}
( run in 0.897 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )