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 )