PDF-Make

 view release on metacpan or  search on metacpan

examples/04_graphics/watermarks.pl  view on Meta::CPAN

#!/usr/bin/perl
# Feature: Watermarks
# Description: Demonstrates text watermark overlay across the document.
# Output: corpus/feature_examples/04_graphics/watermarks.pdf

use strict;
use warnings;
use lib 'lib', 'blib/lib', 'blib/arch';
use File::Path qw(make_path);
use PDF::Make::Builder;

make_path('corpus/feature_examples/04_graphics');

examples/05_forms_and_annotations/notes_and_stamps.pl  view on Meta::CPAN

#!/usr/bin/perl
# Feature: Notes and Stamps
# Description: Demonstrates text annotations (sticky notes) and stamp-like
#              overlays using boxes and text positioning.
# Output: corpus/feature_examples/05_forms_and_annotations/notes_and_stamps.pdf

use strict;
use warnings;
use lib 'lib', 'blib/lib', 'blib/arch';
use File::Path qw(make_path);
use PDF::Make::Builder;

make_path('corpus/feature_examples/05_forms_and_annotations');

my $pdf = PDF::Make::Builder->new(
    file_name => 'corpus/feature_examples/05_forms_and_annotations/notes_and_stamps',
);

$pdf->add_page(page_size => 'Letter')
    ->add_h1(text => 'Notes and Stamps');

# ── Document body ───────────────────────────────────────
$pdf->add_lines(
    'This document demonstrates annotation-style overlays.',
    'Below are examples of stamp-like elements created with boxes and positioned text.',
);

# ── "APPROVED" stamp ────────────────────────────────────
$pdf->add_stamp(text => 'APPROVED', bg_colour => '#dcfce7',
                colour => '#16a34a', size => 24, x => 72, y => 580, w => 200, h => 50);

# ── "DRAFT" stamp ───────────────────────────────────────
$pdf->add_stamp(text => 'DRAFT', bg_colour => '#fef3c7',
                colour => '#d97706', size => 24, x => 72, y => 480, w => 200, h => 50);

examples/06_document_features/redaction.pl  view on Meta::CPAN

#!/usr/bin/perl
# Feature: Redaction
# Description: Demonstrates marking regions for redaction and applying redactions.
#              Redacted areas are blacked out with optional overlay text.
# Output: corpus/feature_examples/06_document_features/redaction.pdf

use strict;
use warnings;
use lib 'lib', 'blib/lib', 'blib/arch';
use File::Path qw(make_path);
use PDF::Make::Builder;

make_path('corpus/feature_examples/06_document_features');

examples/06_document_features/redaction.pl  view on Meta::CPAN

$pdf->add_text(text => 'Employee: John Smith')
    ->add_text(text => 'SSN: 123-45-6789')
    ->add_text(text => 'Salary: $125,000')
    ->add_text(text => 'Department: Engineering');

# Mark the two sensitive lines for redaction.  Rects are [x0, y0, x1, y1]
# in PDF user-space (bottom-left origin).  Baselines for the text lines
# above are roughly y=644 (SSN) and y=630 (Salary) at 9pt Helvetica, so
# each rect spans ~13pt vertically to cover the whole glyph box.
$pdf->mark_redaction(page => 0, rect => [18, 641,  98, 654],
                     overlay_text => 'REDACTED');           # SSN line
$pdf->mark_redaction(page => 0, rect => [18, 627,  95, 640],
                     overlay_text => 'REDACTED');           # Salary line

# Burn the redactions into the content stream (drops the underlying
# text operators so extraction can't recover the bytes) and sanitize
# document metadata.
$pdf->apply_redactions->sanitize;

# Note: apply_redactions() and sanitize() burn the redaction annotations
# into the page content, permanently removing the underlying data.
# They are available but omitted here to keep the example simple.
# In production: $pdf->apply_redactions->sanitize;

examples/builder_enhanced.pl  view on Meta::CPAN

# ══════════════════════════════════════════════════════════════

$pdf->add_page()
    ->add_h1(text => 'Color & Redaction')
    ->add_h2(text => 'Color Spaces')
    ->add_text(text => 'sRGB color space registered for calibrated color output.')
    ->set_color_space('sRGB')
    ->add_h2(text => 'Redaction')
    ->add_text(text => 'Sensitive information: SSN 123-45-6789')
    ->add_text(text => 'The area above can be marked for redaction. '
                     . 'Call apply_redactions() to burn the overlay permanently.')
    ->mark_redaction(
        page          => 6,
        rect          => [36, 530, 350, 545],
        overlay_color => [0, 0, 0],
        overlay_text  => 'REDACTED',
    );

# ══════════════════════════════════════════════════════════════
# Page 8: Attachments + Watermark
# ══════════════════════════════════════════════════════════════

$pdf->add_page()
    ->add_h1(text => 'Attachments & Watermark')
    ->add_h2(text => 'Embedded Files')
    ->add_text(text => 'This PDF has a CSV file attached. Open the attachment '

include/pdfmake_font_encoding.h  view on Meta::CPAN

/*
 * pdfmake_font_encoding.h — Resolved per-font encoding for text extraction.
 *
 * Wraps a base encoding table (WinAnsi / MacRoman / Standard / MacExpert /
 * Symbol / ZapfDingbats) with a /Differences overlay, producing a 256-entry
 * byte -> Unicode map for simple (1-byte) fonts.
 *
 * For CID / Type0 fonts this module is not used; decoding goes through
 * pdfmake_cmap instead.
 *
 * Reference: §9.6.5 (PDF 32000), §9.6.6, §D.1-D.4 (base encodings).
 */

#ifndef PDFMAKE_FONT_ENCODING_H
#define PDFMAKE_FONT_ENCODING_H

include/pdfmake_page.h  view on Meta::CPAN

/*
 * Set the page's content stream from a null-terminated string.
 * Convenience wrapper for ASCII content streams.
 */
pdfmake_err_t pdfmake_page_set_content_str(pdfmake_page_t *page,
                                            const char *content);

/*
 * Append content bytes to the page's existing content stream.  Wraps the
 * existing content in `q .. Q` and concatenates the new bytes afterwards
 * so overlay drawing starts from a clean graphics state.  When the page
 * has no prior content, this is equivalent to pdfmake_page_set_content.
 */
pdfmake_err_t pdfmake_page_append_content(pdfmake_page_t *page,
                                           const uint8_t *data,
                                           size_t len);

/*----------------------------------------------------------------------------
 * Catalog and page tree
 *--------------------------------------------------------------------------*/

include/pdfmake_redact.h  view on Meta::CPAN

#include "pdfmake_buf.h"

#ifdef __cplusplus
extern "C" {
#endif

#define PDFMAKE_MAX_REDACTIONS 256

/* Redaction options */
typedef struct {
    double overlay_color[3];     /* RGB 0-1 for fill (default black) */
    const char *overlay_text;    /* Repeated text in redaction area */
    double overlay_font_size;    /* Font size for overlay text */
} pdfmake_redact_opts_t;

/* Redaction mark */
typedef struct pdfmake_redact {
    double   rect[4];           /* [x0, y0, x1, y1] in page coords */
    double   overlay_color[3];
    char     overlay_text[128];
    double   overlay_font_size;
    int      applied;           /* 1 if already applied */
} pdfmake_redact_t;

/* ── Mark ──────────────────────────────────────────────── */

/* Mark a rectangular area for redaction on a page.
 * Content is NOT removed until apply_redactions is called. */
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);

/* Query redaction marks */
size_t pdfmake_page_redaction_count(pdfmake_page_t *page);
pdfmake_redact_t *pdfmake_page_redaction_at(pdfmake_page_t *page, size_t idx);

/* ── Apply ─────────────────────────────────────────────── */

/* Apply all redactions on a page: remove content within rects,
 * burn in overlay appearance. Content stream is rewritten. */
pdfmake_err_t pdfmake_page_apply_redactions(pdfmake_page_t *page);

/* Apply all redactions across all pages. */
pdfmake_err_t pdfmake_doc_apply_redactions(pdfmake_doc_t *doc);

/* ── Content-stream rewriter ───────────────────────────── */

/*
 * Rewrite a content stream, omitting every BT..ET block whose Tm origin
 * (the last two operands of the Tm operator inside the block) falls

include/pdfmake_watermark.h  view on Meta::CPAN

 * pdfmake_watermark.h — Watermarks and stamps.
 *
 * Provides APIs for adding watermarks and stamps to PDF pages:
 * - Text watermarks (DRAFT, CONFIDENTIAL, etc.)
 * - Image watermarks (logos, signatures)
 * - Page stamps (Bates numbering, headers/footers)
 * - Positioning (diagonal, centered, tiled, custom)
 * - Opacity control via ExtGState
 *
 * Implementation uses Form XObjects for efficiency and content stream
 * manipulation for underlay/overlay placement.
 *
 * Reference: ISO 32000-2:2020
 *   §8.4.5 Graphics State (transparency)
 *   §8.10 Form XObjects
 *   §14.11.6 Watermark Annotations
 */

#ifndef PDFMAKE_WATERMARK_H
#define PDFMAKE_WATERMARK_H

include/pdfmake_watermark.h  view on Meta::CPAN

 * Watermark options
 *--------------------------------------------------------------------------*/

typedef struct {
    pdfmake_wm_position_t position;   /* Positioning mode */
    double rotation;                   /* Rotation in degrees */
    double opacity;                    /* 0.0 (transparent) to 1.0 (opaque) */
    double scale;                      /* Scale factor (1.0 = original) */
    double x_offset;                   /* X offset from computed position */
    double y_offset;                   /* Y offset from computed position */
    int    as_overlay;                 /* 1 = on top, 0 = behind content */
    
    /* Text-specific options */
    const char *font_name;             /* Font name (e.g., "Helvetica-Bold") */
    double      font_size;             /* Font size in points */
    double      color[3];              /* RGB color (0.0-1.0 each) */
    
    /* Tile-specific options */
    double tile_spacing_x;             /* Horizontal spacing between tiles */
    double tile_spacing_y;             /* Vertical spacing between tiles */
} pdfmake_watermark_opts_t;

lib/PDF/Make/Builder.pm  view on Meta::CPAN

    my $bp = $ps->[$page_index];

    # Register the /Redact annotation for downstream tools.
    PDF::Make::Redaction->mark($bp->xs_page, %args);

    my $rect = $args{rect} or return $self;
    my ($x0, $y0, $x1, $y1) = @$rect;
    my ($w, $h) = ($x1 - $x0, $y1 - $y0);
    return $self if $w <= 0 || $h <= 0;

    my $colour = $args{overlay_colour} // $args{overlay_color} // '#000';
    my $text   = $args{overlay_text};
    my $size   = $args{overlay_font_size} // 10;

    # Remember the redaction so save-time can rewrite the content stream
    # to actually remove text that falls inside the rect, then repaint
    # the overlay on top of the filtered stream.
    my $list = $bp->redactions;
    push @$list, {
        rect         => [$x0, $y0, $x1, $y1],
        overlay_text => $text,
        overlay_size => $size,
        overlay_fill => $colour,
    };
    $bp->redactions($list);

    # Eagerly paint the opaque cover so that even a user who never calls
    # apply_redactions sees the sensitive area visually hidden.
    my $font   = $self->font;
    my ($r, $g, $b) = $font->hex_to_rgb($colour);
    my $canvas = $bp->canvas;

    $canvas->q->rg($r, $g, $b)->re($x0, $y0, $w, $h)->f->Q;

lib/PDF/Make/Builder.pm  view on Meta::CPAN

    return $self;
}

sub sanitize {
    my ($self) = @_;
    _sanitize_pending $self, 1;
    return $self;
}

# Internal: filter the canvas bytes for one page through the redaction
# rewriter and re-paint overlay text.  Called from save() / to_bytes()
# when $builder->_apply_redactions_pending is set.
sub _rewrite_redacted_canvas_bytes {
    my ($self, $bp) = @_;
    my $reds = $bp->redactions;
    return $bp->canvas->to_bytes unless $reds && @$reds;

    my $raw_bytes = $bp->canvas->to_bytes;
    my @rects = map { $_->{rect} } @$reds;
    my $filtered = PDF::Make::Redaction->rewrite_stream($raw_bytes, \@rects);

    # Re-paint overlay text for each redaction on a fresh small canvas
    # so those BT..ET blocks come AFTER the filtered stream and are not
    # themselves dropped by the filter.
    my $overlay_canvas = PDF::Make::Canvas->new;
    my $font = $self->font;
    for my $r (@$reds) {
        my ($x0, $y0, $x1, $y1) = @{$r->{rect}};
        my ($w, $h) = ($x1 - $x0, $y1 - $y0);
        next if $w <= 0 || $h <= 0;

        # Black rect (re-paint since the filter kept the original, but
        # redundant paints are harmless and guard against any edge case
        # where the original rect op was adjacent to a dropped block).
        my ($br, $bg, $bb) = $font->hex_to_rgb($r->{overlay_fill} // '#000');
        $overlay_canvas->q->rg($br, $bg, $bb)->re($x0, $y0, $w, $h)->f->Q;

        my $text = $r->{overlay_text};
        next unless defined $text && length $text;

        my $size = $r->{overlay_size} || 10;
        my $res  = $font->ensure_loaded($bp->xs_page, 'normal');
        my ($tr, $tg, $tb) = $font->hex_to_rgb('#fff');
        my $tw = $font->measure_text($text) * ($size / ($font->size || 9));
        my $tx = $x0 + ($w - $tw) / 2;
        $tx = $x0 + 4 if $tw > $w - 4;
        my $ty = $y0 + ($h - $size) / 2 + 1;
        $overlay_canvas->q
                       ->BT
                       ->rg($tr, $tg, $tb)
                       ->Tf($res, $size)
                       ->Tm(1, 0, 0, 1, $tx, $ty)
                       ->Tj($text)
                       ->ET
                       ->Q;
    }

    return $filtered . $overlay_canvas->to_bytes;
}

# ── Color Management ──────────────────────────────────────

sub set_color_space {
    my ($self, $type, %args) = @_;
    my $cs;
    if ($type eq 'sRGB') {
        $cs = PDF::Make::Color->srgb;
    } elsif ($type eq 'separation') {

lib/PDF/Make/Builder.pm  view on Meta::CPAN

            $t->render($self);
        }
    }

    # Render headers/footers onto each page's canvas, then finalize
    my $all_pages = pages $self;
    my $offset = page_offset $self;
    my $rewrite = _apply_redactions_pending $self;
    for my $bp (@$all_pages) {
        if ($bp->imported) {
            # Imported pages keep their original content, but any overlay
            # drawing issued against the Builder's canvas (e.g. a box
            # around extracted text) needs to be appended so it renders
            # on top of the source graphics.
            my $overlay = $bp->canvas->to_bytes;
            if (defined $overlay && length $overlay) {
                $bp->xs_page->append_content($overlay);
            }
            next;
        }
        my $hdr = $bp->header;
        my $ftr = $bp->footer;
        my $pnum = $bp->num + $offset;

        if ($hdr) {
            $hdr->render($self, $bp, $pnum);
        }

lib/PDF/Make/Builder.pm  view on Meta::CPAN


Stop drawing on the current layer.

=head2 Redaction

=head3 mark_redaction(%args)

    $b->mark_redaction(
        page          => 0,                     # 0-based page index
        rect          => [100, 700, 300, 720],
        overlay_color => [0, 0, 0],
        overlay_text  => 'REDACTED',
    );

Mark a rectangular area for redaction.

=head3 apply_redactions()

Apply all redaction marks across all pages.

=head3 sanitize()

lib/PDF/Make/Redaction.pm  view on Meta::CPAN

    my $page = $doc->add_page(612, 792);
    $page->add_std14_font('F1', HELVETICA);

    my $c = PDF::Make::Canvas->new;
    $c->BT->Tf('F1', 12)->Td(72, 700)->Tj('SSN: 123-45-6789')->ET;
    $page->set_content($c->to_bytes);

    # Mark areas for redaction
    PDF::Make::Redaction->mark($page,
        rect          => [100, 695, 280, 712],
        overlay_color => [0, 0, 0],
        overlay_text  => 'REDACTED',
    );

    # Or use individual coordinates
    PDF::Make::Redaction->mark($page,
        x0 => 100, y0 => 650, x1 => 280, y1 => 670,
    );

    # Check count
    my $n = PDF::Make::Redaction->count($page);  # 2

    # Apply redactions (burns overlays into content)
    PDF::Make::Redaction->apply_page($page);   # single page
    PDF::Make::Redaction->apply_doc($doc);      # all pages

    # Remove metadata (author, title, etc.)
    PDF::Make::Redaction->sanitize($doc);

    $doc->to_file('redacted.pdf');

=head1 DESCRIPTION

C<PDF::Make::Redaction> provides a two-step redaction workflow following PDF
specification conventions:

=over 4

=item 1. B<Mark> - Define rectangular areas to be redacted, with optional
overlay text and color.

=item 2. B<Apply> - Burn the redaction overlays into the content stream,
permanently removing the underlying content.

=back

A separate C<sanitize> step removes document-level metadata (title, author,
subject, etc.) from the Info dictionary.

=head1 CLASS METHODS

All methods are class methods called on C<PDF::Make::Redaction>.

=head2 mark($page, %args)

    PDF::Make::Redaction->mark($page,
        rect             => [x0, y0, x1, y1],  # or use x0/y0/x1/y1
        overlay_color    => [0, 0, 0],          # RGB, default black
        overlay_text     => 'REDACTED',         # optional text overlay
        overlay_font_size => 10,                # default 10pt
    );

Mark a rectangular area on C<$page> for redaction.

=over 4

=item C<rect> - ArrayRef of [x0, y0, x1, y1] in PDF coordinates

=item C<x0, y0, x1, y1> - Alternative: specify corners individually

=item C<overlay_color> - RGB color array for the redaction box (default [0,0,0])

=item C<overlay_text> - Text to display over the redacted area

=item C<overlay_font_size> - Font size for overlay text (default 10)

=back

=head2 count($page)

    my $n = PDF::Make::Redaction->count($page);

Returns the number of redaction marks on the given page.

=head2 apply_page($page)

    PDF::Make::Redaction->apply_page($page);

Apply all redaction marks on a single page, burning overlays into the
content stream. Croaks on failure.

=head2 apply_doc($doc)

    PDF::Make::Redaction->apply_doc($doc);

Apply all redaction marks across every page in the document. Croaks on
failure.

=head2 sanitize($doc)

lib/PDF/Make/Watermark.pm  view on Meta::CPAN

=over 4

=item * C<text($text, %options)>

=item * C<image($image_obj, %options)>

=back

Common option keys include:
C<position>, C<opacity>, C<rotation>, C<scale>, C<x_offset>, C<y_offset>,
C<overlay>, C<tile_spacing_x>, C<tile_spacing_y>.

Text watermark options additionally include:
C<font>, C<size>, C<color>.

Image watermark requires:
C<width> and C<height>.

=head2 PDF::Make::Stamp

Constructors:

src/pdfmake_encoding.c  view on Meta::CPAN

    /* Apply horizontal scaling */
    width *= ts->horiz_scale;
    
    return width;
}

/*============================================================================
 * Font encoding API (Phase 2)
 *
 * Resolves a font's /Encoding to a byte->Unicode map, applying /Differences
 * overlays via the Adobe Glyph List.
 *==========================================================================*/

#include "pdfmake_font_encoding.h"
#include "pdfmake_glyphlist.h"

/* Fill a 256-entry map from a base table (0xFFFF = undefined in the
 * existing tables; translate to 0 for the new API). */
static void fill_from_table(pdfmake_font_encoding_t *enc,
                             const uint16_t *src)
{

src/pdfmake_glyphlist.c  view on Meta::CPAN

    {"slong", 0x017F},
    {"slongdotaccent", 0x1E9B},
    {"smileface", 0x263A},
    {"smonospace", 0xFF53},
    {"sofpasuqhebrew", 0x05C3},
    {"softhyphen", 0x00AD},
    {"softsigncyrillic", 0x044C},
    {"sohiragana", 0x305D},
    {"sokatakana", 0x30BD},
    {"sokatakanahalfwidth", 0xFF7F},
    {"soliduslongoverlaycmb", 0x0338},
    {"solidusshortoverlaycmb", 0x0337},
    {"sorusithai", 0x0E29},
    {"sosalathai", 0x0E28},
    {"sosothai", 0x0E0B},
    {"sosuathai", 0x0E2A},
    {"space", 0x0020},
    {"spacehackarabic", 0x0020},
    {"spade", 0x2660},
    {"spadesuitblack", 0x2660},
    {"spadesuitwhite", 0x2664},
    {"sparen", 0x24AE},

src/pdfmake_glyphlist.c  view on Meta::CPAN

    {"ssanghieuhkorean", 0x3185},
    {"ssangieungkorean", 0x3180},
    {"ssangkiyeokkorean", 0x3132},
    {"ssangnieunkorean", 0x3165},
    {"ssangpieupkorean", 0x3143},
    {"ssangsioskorean", 0x3146},
    {"ssangtikeutkorean", 0x3138},
    {"ssuperior", 0xF6F2},
    {"sterling", 0x00A3},
    {"sterlingmonospace", 0xFFE1},
    {"strokelongoverlaycmb", 0x0336},
    {"strokeshortoverlaycmb", 0x0335},
    {"subset", 0x2282},
    {"subsetnotequal", 0x228A},
    {"subsetorequal", 0x2286},
    {"succeeds", 0x227B},
    {"suchthat", 0x220B},
    {"suhiragana", 0x3059},
    {"sukatakana", 0x30B9},
    {"sukatakanahalfwidth", 0xFF7D},
    {"sukunarabic", 0x0652},
    {"summation", 0x2211},

src/pdfmake_glyphlist.c  view on Meta::CPAN

    {"tikeutaparenkorean", 0x3210},
    {"tikeutcirclekorean", 0x3262},
    {"tikeutkorean", 0x3137},
    {"tikeutparenkorean", 0x3202},
    {"tilde", 0x02DC},
    {"tildebelowcmb", 0x0330},
    {"tildecmb", 0x0303},
    {"tildecomb", 0x0303},
    {"tildedoublecmb", 0x0360},
    {"tildeoperator", 0x223C},
    {"tildeoverlaycmb", 0x0334},
    {"tildeverticalcmb", 0x033E},
    {"timescircle", 0x2297},
    {"tipehahebrew", 0x0596},
    {"tipehalefthebrew", 0x0596},
    {"tippigurmukhi", 0x0A70},
    {"titlocyrilliccmb", 0x0483},
    {"tiwnarmenian", 0x057F},
    {"tlinebelow", 0x1E6F},
    {"tmonospace", 0xFF54},
    {"toarmenian", 0x0569},

src/pdfmake_interpreter.c  view on Meta::CPAN

            pdfmake_obj_t *v = pdfmake_array_get(mat, i);
            if (!v) { m[i] = 0; continue; }
            if (v->kind == PDFMAKE_INT)       m[i] = (double)v->as.i;
            else if (v->kind == PDFMAKE_REAL) m[i] = v->as.r;
            else                              m[i] = 0;
        }
        /* CTM = form_matrix × CTM */
        pdfmake_matrix_multiply(interp->gs->ctm, m, interp->gs->ctm);
    }

    /* Swap resources: form's /Resources (if any) overlays. If the form has
     * no /Resources, keep the outer page's — matches §7.8.3 "If the form
     * XObject does not have its own Resources dictionary, the form uses
     * the page's". */
    res_k = pdfmake_arena_intern_name(interp->arena, "Resources", 9);
    form_res = pdfmake_dict_get(&stream_dict, res_k);
    if (form_res && form_res->kind == PDFMAKE_REF && interp->reader) {
        pdfmake_reader_t *rd = (pdfmake_reader_t *)interp->reader;
        if (rd->parser)
            form_res = pdfmake_parser_resolve(rd->parser, form_res->as.ref);
    }

src/pdfmake_page.c  view on Meta::CPAN


    /* Use the stream's raw bytes directly.  Pages created via
     * pdfmake_page_set_content always store plain (unfiltered) content,
     * and pdfmake_doc_import_page feeds pre-decoded bytes in through the
     * same path — so the raw pointer is the content we need, regardless
     * of how the page was built. */
    stm = existing->as.stream;
    old_bytes = stm->raw;
    old_len   = stm->raw_len;

    /* Build `<<existing>>\nq\n<<overlay>>\nQ\n` so overlay starts from a
     * clean graphics state but the existing content is preserved. */
    cap = old_len + 1 + 2 + 1 + len + 1 + 2 + 1;
    combined = pdfmake_arena_alloc(arena, cap);
    if (!combined) return PDFMAKE_ENOMEM;

    off = 0;
    memcpy(combined + off, old_bytes, old_len); off += old_len;
    if (old_len == 0 || old_bytes[old_len - 1] != '\n') combined[off++] = '\n';
    combined[off++] = 'q'; combined[off++] = '\n';
    memcpy(combined + off, data, len); off += len;

src/pdfmake_redact.c  view on Meta::CPAN

    }

    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;
}

src/pdfmake_redact.c  view on Meta::CPAN

            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;
            }

src/pdfmake_redact.c  view on Meta::CPAN

                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);

src/pdfmake_textract.c  view on Meta::CPAN

        pdfmake_font_widths_init(&rf->widths);
        return;
    }

    if (rf->is_cid) {
        pdfmake_font_widths_from_cid(r->arena, rf->font_dict, &rf->widths);
    } else {
        pdfmake_font_widths_from_simple(r->arena, rf->font_dict, &rf->widths);
    }

    /* Phase 6: overlay with TTF metrics from /FontFile2 if present.
     * For simple fonts this requires the resolved /Encoding first; callers
     * arrange the sequence in resolve_font(). */
    if (r->reader) {
        const uint32_t *byte_to_uni = NULL;
        if (!rf->is_cid && rf->encoding_resolved) {
            byte_to_uni = rf->encoding.map;
        }
        pdfmake_font_widths_enhance_with_ttf(
            r->arena,
            (struct pdfmake_reader *)r->reader,

src/pdfmake_watermark.c  view on Meta::CPAN

{
    if (!opts) return;
    
    memset(opts, 0, sizeof(*opts));
    opts->position = PDFMAKE_WM_POS_CENTER;
    opts->rotation = 0.0;
    opts->opacity = 0.3;
    opts->scale = 1.0;
    opts->x_offset = 0.0;
    opts->y_offset = 0.0;
    opts->as_overlay = 0;  /* Behind content by default */
    
    opts->font_name = "Helvetica-Bold";
    opts->font_size = 72.0;
    opts->color[0] = 0.7;  /* Light gray */
    opts->color[1] = 0.7;
    opts->color[2] = 0.7;
    
    opts->tile_spacing_x = 150.0;
    opts->tile_spacing_y = 150.0;
}

src/pdfmake_watermark.c  view on Meta::CPAN

        pdfmake_page_add_image(page, img_name, wm->data.image.image_obj);
    }
    
    /* Generate watermark content */
    pdfmake_buf_init(&buf);
    
    generate_watermark_content(wm, page, &buf, 
                               gs_name[0] ? gs_name : NULL,
                               font_res_name, img_name);
    
    /* Merge watermark with existing page content (overlay/underlay). */
    wm_data = pdfmake_buf_data(&buf);
    wm_len = pdfmake_buf_len(&buf);

    if (page->has_content && page->contents_num) {
        old_obj = pdfmake_doc_get(doc, page->contents_num);
        if (old_obj && old_obj->kind == PDFMAKE_STREAM && old_obj->as.stream) {
            old_data = old_obj->as.stream->raw;
            old_len = old_obj->as.stream->raw_len;
        }
    }

    if (!old_data || old_len == 0) {
        pdfmake_page_set_content(page, wm_data, wm_len);
    } else {
        pdfmake_buf_init(&merged);

        if (wm->opts.as_overlay) {
            pdfmake_buf_append(&merged, old_data, old_len);
            pdfmake_buf_append_byte(&merged, '\n');
            pdfmake_buf_append(&merged, wm_data, wm_len);
        } else {
            pdfmake_buf_append(&merged, wm_data, wm_len);
            pdfmake_buf_append_byte(&merged, '\n');
            pdfmake_buf_append(&merged, old_data, old_len);
        }

        pdfmake_page_set_content(page, pdfmake_buf_data(&merged), pdfmake_buf_len(&merged));

t/24-redaction.t  view on Meta::CPAN

$c->BT->Tf('F1', 12)->Tm(1,0,0,1, 72, 660)->Tj('Public info here')->ET;
$page->set_content($c->to_bytes);

# ── Mark redactions ──────────────────────────────────────

is(PDF::Make::Redaction->count($page), 0, 'no redactions initially');

# Redaction with rect array
PDF::Make::Redaction->mark($page,
    rect          => [72, 695, 300, 712],
    overlay_color => [0, 0, 0],
    overlay_text  => 'REDACTED',
);
is(PDF::Make::Redaction->count($page), 1, 'one redaction after first mark');

# Redaction with individual coordinates
PDF::Make::Redaction->mark($page,
    x0 => 72, y0 => 675, x1 => 250, y1 => 692,
    overlay_color => [1, 0, 0],
    overlay_text  => '[REMOVED]',
);
is(PDF::Make::Redaction->count($page), 2, 'two redactions after second mark');

# Redaction with no overlay (black fill only)
PDF::Make::Redaction->mark($page,
    rect => [72, 655, 200, 672],
);
is(PDF::Make::Redaction->count($page), 3, 'three redactions');

# Redaction with custom font size
PDF::Make::Redaction->mark($page,
    rect               => [300, 695, 500, 712],
    overlay_text       => 'CLASSIFIED',
    overlay_font_size  => 8,
);
is(PDF::Make::Redaction->count($page), 4, 'four redactions');

# ── Sanitize metadata ───────────────────────────────────

# Before sanitize
my $pre_bytes = $doc->to_bytes;
like($pre_bytes, qr/Secret Author/, 'author present before sanitize');

# Sanitize

t/73-import-page.t  view on Meta::CPAN

    my $out = tmpnam() . '.pdf';
    END { unlink $out if $out && -f $out }

    my $b = PDF::Make::Builder->open_existing($fixture, file_name => $out);

    # Target the second imported page (1-based)
    $b->open_page(2);
    $b->add_note(rect => [55, 664, 174, 682], text => 'Sample Data File');
    $b->save;

    ok(-f $out, 'overlay output exists');
    ok(-s $out > $orig_size * 0.5,
       'overlay output size reasonable');

    # Verify the annotation landed on page 1 (0-based in raw PDF)
    open my $fh, '<:raw', $out or die;
    my $bytes = do { local $/; <$fh> };
    close $fh;
    like($bytes, qr/Sample Data File/, 'annotation content present in PDF');
    like($bytes, qr{/Subtype\s*/Text}, 'Text annotation emitted');
}

# ── Merge multiple files ────────────────────────────────

t/73-watermarks.t  view on Meta::CPAN


# Test 13: Invalid position
{
    eval { PDF::Make::Watermark->text('TEST', position => 'invalid') };
    like($@, qr/Unknown position/, 'Invalid position rejected');
}

# Test 14: Overlay option
{
    my $wm1 = PDF::Make::Watermark->text('TEST');
    is($wm1->overlay, 0, 'Default is underlay');
    
    my $wm2 = PDF::Make::Watermark->text('TEST', overlay => 1);
    is($wm2->overlay, 1, 'Can set overlay');
}

# Test 15: Tile spacing
{
    my $wm = PDF::Make::Watermark->text('TEST',
        position => 'tile',
        tile_spacing_x => 200,
        tile_spacing_y => 250,
    );
    is($wm->tile_spacing_x, 200, 'Custom tile spacing X');

t/80-redact-rewrite.t  view on Meta::CPAN

    my $f = tmpnam() . '.pdf';
    my $b = PDF::Make::Builder->new(file_name => $f);
    $b->add_page(page_size => 'Letter')
      ->add_text(text => 'Employee: John Smith')
      ->add_text(text => 'SSN: 123-45-6789')
      ->add_text(text => 'Salary: $125,000')
      ->add_text(text => 'Department: Engineering');

    # Inspect positions to pick rects that land on SSN/Salary baselines.
    $b->mark_redaction(page => 0, rect => [18, 748, 98, 761],
                       overlay_text => 'REDACTED');  # SSN baseline ≈ y 749
    $b->mark_redaction(page => 0, rect => [18, 734, 95, 747],
                       overlay_text => 'REDACTED');  # Salary baseline ≈ y 735

    $b->apply_redactions if $opts{apply};
    $b->sanitize         if $opts{sanitize};
    $b->save;
    return $f;
}

# ── Without apply_redactions: cover only, data still recoverable ────
{
    my $f = build();
    open my $fh, '<:raw', $f or die $!;
    my $bytes = do { local $/; <$fh> };
    ok(index($bytes, '123-45-6789') >= 0,
       'cover-only: SSN still present in raw file bytes');
    ok(index($bytes, '$125,000') >= 0,
       'cover-only: Salary still present in raw file bytes');
    ok(index($bytes, 'REDACTED') >= 0,
       'cover-only: REDACTED overlay present in raw file bytes');
    unlink $f;
}

# ── With apply_redactions: underlying text is gone ──────────────────
{
    my $f = build(apply => 1);
    my $b = PDF::Make::Builder->new(file_name => tmpnam() . '.pdf');
    my $res = $b->extract_structured($f, page => 0);
    my $text = join(' ', map { $_->{text} } $res->text_positions);

    unlike($text, qr/123-45-6789/, 'apply_redactions: SSN removed');
    unlike($text, qr/\$125,000/,    'apply_redactions: Salary removed');
    like($text,   qr/REDACTED/,    'apply_redactions: REDACTED overlay still present');
    like($text,   qr/Employee/,    'apply_redactions: non-redacted content preserved');
    like($text,   qr/Department/,  'apply_redactions: non-redacted content preserved');

    # Also check the raw PDF bytes - sensitive text should not be there
    open my $fh, '<:raw', $f or die $!;
    my $bytes = do { local $/; <$fh> };
    ok(index($bytes, '123-45-6789') < 0, 'apply_redactions: SSN absent from file bytes');
    ok(index($bytes, '$125,000')     < 0, 'apply_redactions: Salary absent from file bytes');

    unlink $f;

t/99-leaks.t  view on Meta::CPAN

    $doc->title('Secret Title');
    $doc->author('Secret Author');
    my $page = $doc->add_page(612, 792);
    $page->add_std14_font('F1', HELVETICA);
    my $c = PDF::Make::Canvas->new;
    $c->BT->Tf('F1', 12)->Td(72, 700)->Tj('SSN: 123-45-6789')->ET;
    $page->set_content($c->to_bytes);
    for my $i (1..5) {
        PDF::Make::Redaction->mark($page,
            rect => [72, 695 - $i*20, 300, 712 - $i*20],
            overlay_color => [0, 0, 0],
            overlay_text => 'REDACTED',
        );
    }
    my $cnt = PDF::Make::Redaction->count($page);
    PDF::Make::Redaction->sanitize($doc);
    my $bytes = $doc->to_bytes;
    undef $doc;
} 'Redaction mark/count/sanitize (5x)';

# ── 23: Color space create/convert/destroy ────────────

t/fixtures/feature_examples/05_forms_and_annotations/notes_and_stamps.pdf  view on Meta::CPAN

BT
0 0 0 rg
/F_Helvetica_normal 50 Tf
1 0 0 1 20 722 Tm
(Notes and Stamps) Tj
ET
BT
0 0 0 rg
/F_Helvetica_normal 9 Tf
1 0 0 1 20 688 Tm
(This document demonstrates annotation-style overlays.) Tj
ET
BT
0 0 0 rg
/F_Helvetica_normal 9 Tf
1 0 0 1 20 674 Tm
(Below are examples of stamp-like elements created with boxes and positioned text.) Tj
ET
q
0.862745098039215 0.988235294117647 0.905882352941176 rg
72 580 200 50 re

xs/redact.xs  view on Meta::CPAN

    char *class
    pdfmake_page_t *page
    PREINIT:
        double x0 = 0, y0 = 0, x1 = 0, y1 = 0;
        pdfmake_redact_opts_t opts;
        pdfmake_redact_t *r;
        int i;
    CODE:
        PERL_UNUSED_VAR(class);
        memset(&opts, 0, sizeof(opts));
        opts.overlay_font_size = 10;

        for (i = 2; i < items - 1; i += 2) {
            const char *key = SvPV_nolen(ST(i));
            SV *val = ST(i + 1);
            if (strEQ(key, "x0")) x0 = SvNV(val);
            else if (strEQ(key, "y0")) y0 = SvNV(val);
            else if (strEQ(key, "x1")) x1 = SvNV(val);
            else if (strEQ(key, "y1")) y1 = SvNV(val);
            else if (strEQ(key, "rect") && SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVAV) {
                AV *av = (AV *)SvRV(val);
                SV **e;
                if ((e = av_fetch(av, 0, 0))) x0 = SvNV(*e);
                if ((e = av_fetch(av, 1, 0))) y0 = SvNV(*e);
                if ((e = av_fetch(av, 2, 0))) x1 = SvNV(*e);
                if ((e = av_fetch(av, 3, 0))) y1 = SvNV(*e);
            }
            else if (strEQ(key, "overlay_color") && SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVAV) {
                AV *av = (AV *)SvRV(val);
                SV **e;
                if ((e = av_fetch(av, 0, 0))) opts.overlay_color[0] = SvNV(*e);
                if ((e = av_fetch(av, 1, 0))) opts.overlay_color[1] = SvNV(*e);
                if ((e = av_fetch(av, 2, 0))) opts.overlay_color[2] = SvNV(*e);
            }
            else if (strEQ(key, "overlay_text")) opts.overlay_text = SvPV_nolen(val);
            else if (strEQ(key, "overlay_font_size")) opts.overlay_font_size = SvNV(val);
        }

        r = pdfmake_page_mark_redaction(page, x0, y0, x1, y1, &opts);
        if (!r)
            croak("PDF::Make::Redaction: mark failed");

void
apply_page(class, page)
    char *class
    pdfmake_page_t *page

xs/watermark.xs  view on Meta::CPAN

            }
            else if (strEQ(key, "scale")) {
                opts.scale = SvNV(val);
            }
            else if (strEQ(key, "x_offset")) {
                opts.x_offset = SvNV(val);
            }
            else if (strEQ(key, "y_offset")) {
                opts.y_offset = SvNV(val);
            }
            else if (strEQ(key, "overlay")) {
                opts.as_overlay = SvIV(val);
            }
            else if (strEQ(key, "font")) {
                opts.font_name = SvPV_nolen(val);
            }
            else if (strEQ(key, "size")) {
                opts.font_size = SvNV(val);
            }
            else if (strEQ(key, "color")) {
                if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVAV) {
                    AV *av = (AV*)SvRV(val);

xs/watermark.xs  view on Meta::CPAN

            }
            else if (strEQ(key, "scale")) {
                opts.scale = SvNV(val);
            }
            else if (strEQ(key, "x_offset")) {
                opts.x_offset = SvNV(val);
            }
            else if (strEQ(key, "y_offset")) {
                opts.y_offset = SvNV(val);
            }
            else if (strEQ(key, "overlay")) {
                opts.as_overlay = SvIV(val);
            }
            else if (strEQ(key, "tile_spacing_x")) {
                opts.tile_spacing_x = SvNV(val);
            }
            else if (strEQ(key, "tile_spacing_y")) {
                opts.tile_spacing_y = SvNV(val);
            }
        }

        if (!have_width)

xs/watermark.xs  view on Meta::CPAN

            }
            else if (strEQ(key, "scale")) {
                opts.scale = SvNV(val);
            }
            else if (strEQ(key, "x_offset")) {
                opts.x_offset = SvNV(val);
            }
            else if (strEQ(key, "y_offset")) {
                opts.y_offset = SvNV(val);
            }
            else if (strEQ(key, "overlay")) {
                opts.as_overlay = SvIV(val);
            }
            else if (strEQ(key, "font")) {
                opts.font_name = SvPV_nolen(val);
            }
            else if (strEQ(key, "size")) {
                opts.font_size = SvNV(val);
            }
            else if (strEQ(key, "color")) {
                if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVAV) {
                    AV *av = (AV*)SvRV(val);

xs/watermark.xs  view on Meta::CPAN

            }
            else if (strEQ(key, "scale")) {
                opts.scale = SvNV(val);
            }
            else if (strEQ(key, "x_offset")) {
                opts.x_offset = SvNV(val);
            }
            else if (strEQ(key, "y_offset")) {
                opts.y_offset = SvNV(val);
            }
            else if (strEQ(key, "overlay")) {
                opts.as_overlay = SvIV(val);
            }
            else if (strEQ(key, "tile_spacing_x")) {
                opts.tile_spacing_x = SvNV(val);
            }
            else if (strEQ(key, "tile_spacing_y")) {
                opts.tile_spacing_y = SvNV(val);
            }
        }
        
        RETVAL = pdfmake_watermark_image(NULL, (uint32_t)image_obj, width, height, &opts);

xs/watermark.xs  view on Meta::CPAN


double
y_offset(self)
    pdfmake_watermark_t *self
    CODE:
        RETVAL = self->opts.y_offset;
    OUTPUT:
        RETVAL

int
overlay(self)
    pdfmake_watermark_t *self
    CODE:
        RETVAL = self->opts.as_overlay;
    OUTPUT:
        RETVAL

const char *
font(self)
    pdfmake_watermark_t *self
    CODE:
        RETVAL = self->opts.font_name ? self->opts.font_name : "Helvetica-Bold";
    OUTPUT:
        RETVAL

xs/watermark.xs  view on Meta::CPAN


BOOT:
{
    HV *stash = gv_stashpv("PDF::Make::Watermark", GV_ADD);
    PDFMAKE_REGISTER_GETTER(stash, "position",       pdfmake_watermark_t, opts.position,       PDFMAKE_FIELD_INT);
    PDFMAKE_REGISTER_GETTER(stash, "opacity",        pdfmake_watermark_t, opts.opacity,        PDFMAKE_FIELD_DOUBLE);
    PDFMAKE_REGISTER_GETTER(stash, "rotation",       pdfmake_watermark_t, opts.rotation,       PDFMAKE_FIELD_DOUBLE);
    PDFMAKE_REGISTER_GETTER(stash, "scale",          pdfmake_watermark_t, opts.scale,          PDFMAKE_FIELD_DOUBLE);
    PDFMAKE_REGISTER_GETTER(stash, "x_offset",       pdfmake_watermark_t, opts.x_offset,       PDFMAKE_FIELD_DOUBLE);
    PDFMAKE_REGISTER_GETTER(stash, "y_offset",       pdfmake_watermark_t, opts.y_offset,       PDFMAKE_FIELD_DOUBLE);
    PDFMAKE_REGISTER_GETTER(stash, "overlay",        pdfmake_watermark_t, opts.as_overlay,     PDFMAKE_FIELD_INT);
    PDFMAKE_REGISTER_GETTER(stash, "font_size",      pdfmake_watermark_t, opts.font_size,      PDFMAKE_FIELD_DOUBLE);
    PDFMAKE_REGISTER_GETTER(stash, "tile_spacing_x", pdfmake_watermark_t, opts.tile_spacing_x, PDFMAKE_FIELD_DOUBLE);
    PDFMAKE_REGISTER_GETTER(stash, "tile_spacing_y", pdfmake_watermark_t, opts.tile_spacing_y, PDFMAKE_FIELD_DOUBLE);
}



( run in 1.982 second using v1.01-cache-2.11-cpan-7fcb06a456a )