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