Redaction
Redaction removes content for good: the text, images and drawings under a mark are deleted from the file, not just covered up. It happens in two steps, as in Acrobat: first people mark what should go, and review it; then they apply the marks, and the content is gone.
Register withRedaction() next to withInteraction() and withAnnotation(), and use the
redact tool: select text to mark it, or drag to mark an area:
import { Component, inject } from '@angular/core';
import { EpdfInteraction } from '@embedpdf/angular/interaction';
import { EpdfRedaction } from '@embedpdf/angular/redaction';
@Component({
selector: 'app-redact-bar',
template: `
<button (click)="interaction.activateTool('redact')">Mark for redaction</button>
<button
[disabled]="!redaction.pendingCount() || redaction.applying()"
(click)="redaction.applyAll()"
>
Redact {{ redaction.pendingCount() }} marks
</button>
`,
})
export class RedactBar {
protected readonly interaction = inject(EpdfInteraction);
protected readonly redaction = inject(EpdfRedaction);
}Loading live preview…
import {
ChangeDetectionStrategy,
Component,
computed,
effect,
inject,
input,
ViewEncapsulation,
} from '@angular/core';
import { EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { EpdfInteraction, withInteraction } from '@embedpdf/angular/interaction';
import { EpdfSelectionLayer, withSelection } from '@embedpdf/angular/selection';
import { EpdfAnnotation, EpdfAnnotationLayer, withAnnotation } from '@embedpdf/angular/annotation';
import { EpdfRedaction, withRedaction } from '@embedpdf/angular/redaction';
import { localEngine } from '@embedpdf/engine';
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};
// The author's name on the cover, in page coordinates.
const AUTHOR = { x: 100, y: 508, width: 172, height: 50 };
/** Next to `<epdf-stage #stage="epdfStage">`: `<demo-redact-bar [stage]="stage" />`. */
@Component({
selector: 'demo-redact-bar',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="toolbar">
<button
type="button"
class="button"
[attr.aria-pressed]="marking()"
(click)="interaction.activateTool(marking() ? 'pointer' : 'redact')"
>
Mark for redaction
</button>
<button
type="button"
class="button danger"
[disabled]="!redaction.pendingCount() || redaction.applying()"
(click)="redaction.applyAll()"
>
Redact {{ redaction.pendingCount() }}
{{ redaction.pendingCount() === 1 ? 'mark' : 'marks' }}
</button>
<span class="spacer"></span>
<output class="readout">
{{ marking() ? 'Select text, or drag over an area' : 'Click a mark to move it' }}
</output>
</div>
`,
})
export class RedactBar {
readonly stage = input.required<EpdfStage>();
protected readonly interaction = inject(EpdfInteraction);
protected readonly redaction = inject(EpdfRedaction);
private readonly annotation = inject(EpdfAnnotation);
protected readonly marking = computed(() => this.interaction.activeToolId() === 'redact');
private started = false;
constructor() {
// On load: the author's name marked, and the redact tool on.
effect(() => {
if (this.annotation.status() !== 'ready' || this.started) return;
this.started = true;
const stage = this.stage();
void this.redaction.markArea(0, AUTHOR).then(() => stage.reveal(0, { rect: AUTHOR }));
this.interaction.activateTool('redact');
});
}
}
// The selection plugin lets the redact tool mark the text you select.
@Component({
selector: 'demo-root',
imports: [
EpdfDocumentGate,
EpdfStage,
EpdfPageTemplate,
EpdfRenderLayer,
EpdfSelectionLayer,
EpdfAnnotationLayer,
RedactBar,
],
providers: [
provideEmbedPdf(
{ engine: () => localEngine(), initialDocuments: [{ source: ebook }] },
withStage(),
withRender(),
withInteraction(),
withSelection(),
withAnnotation(),
withRedaction(),
),
],
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrl: './basic.css',
encapsulation: ViewEncapsulation.None,
template: `
<ng-container *epdfDocumentGate="let document; fallback: loading">
<demo-redact-bar [stage]="stage" />
<epdf-stage #stage="epdfStage" class="stage">
<ng-template epdfPage>
<epdf-render-layer />
<epdf-selection-layer />
<epdf-annotation-layer />
</ng-template>
</epdf-stage>
</ng-container>
<ng-template #loading><p class="loading">Loading…</p></ng-template>
`,
})
export class App {}A mark is an annotation, so until it’s applied, people can move it, resize it, delete it, and see it in other PDF apps. Applying it is what removes the content.
Mark from code#
await redaction.markSelection(); // the selected text
await redaction.markArea(page.ref, { x: 72, y: 100, width: 200, height: 40 });
await redaction.markPage(page.ref); // the whole page
await redaction.markMatches({ text: 'Jane Doe' }); // every match in the document
await redaction.markMatches({ text: '\\d{3}-\\d{2}-\\d{4}', regex: true }); // like a social security numbermarkMatches takes the same query as search, and resolves with { marks }, one per
match.
Loading live preview…
import {
ChangeDetectionStrategy,
Component,
computed,
effect,
inject,
input,
signal,
ViewEncapsulation,
} from '@angular/core';
import { EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { withInteraction } from '@embedpdf/angular/interaction';
import { EpdfSelection, EpdfSelectionLayer, withSelection } from '@embedpdf/angular/selection';
import { withSearch } from '@embedpdf/angular/search';
import { EpdfAnnotation, EpdfAnnotationLayer, withAnnotation } from '@embedpdf/angular/annotation';
import { EpdfRedaction, withRedaction } from '@embedpdf/angular/redaction';
import { localEngine } from '@embedpdf/engine';
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};
/** Next to `<epdf-stage #stage="epdfStage">`: `<demo-mark-from-code [stage]="stage" />`. */
@Component({
selector: 'demo-mark-from-code',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="toolbar">
<form class="query" (submit)="$event.preventDefault(); markMatches()">
<input
#field
class="field"
type="search"
aria-label="Text to mark"
[value]="text()"
(input)="text.set(field.value)"
/>
<button type="submit" class="button" [disabled]="!text().trim()">Every match</button>
</form>
<button
type="button"
class="button"
[disabled]="!selection.hasSelection()"
title="Select some text on the page first"
(click)="redaction.markSelection()"
>
The selected text
</button>
<button type="button" class="button" (click)="markThisPage()">This page</button>
<button
type="button"
class="button"
[disabled]="!redaction.pendingCount()"
(click)="redaction.clearPending()"
>
Remove all
</button>
<span class="spacer"></span>
<output class="readout">{{ marks() }}</output>
</div>
`,
})
export class MarkFromCode {
readonly stage = input.required<EpdfStage>();
protected readonly redaction = inject(EpdfRedaction);
protected readonly selection = inject(EpdfSelection);
private readonly annotation = inject(EpdfAnnotation);
protected readonly text = signal('EmbedPDF');
protected readonly marks = computed(() => {
const count = this.redaction.pendingCount();
return `${count} ${count === 1 ? 'mark' : 'marks'}`;
});
private started = false;
constructor() {
// On load: every "EmbedPDF" in the document.
effect(() => {
if (this.annotation.status() !== 'ready' || this.started) return;
this.started = true;
void this.redaction.markMatches({ text: 'EmbedPDF' });
});
}
protected markMatches() {
const text = this.text().trim();
if (text) void this.redaction.markMatches({ text });
}
protected markThisPage() {
void this.redaction.markPage(this.stage().currentPageIndex());
}
}
// Marking matches runs a search; marking the selected text needs a selection.
@Component({
selector: 'demo-root',
imports: [
EpdfDocumentGate,
EpdfStage,
EpdfPageTemplate,
EpdfRenderLayer,
EpdfSelectionLayer,
EpdfAnnotationLayer,
MarkFromCode,
],
providers: [
provideEmbedPdf(
{ engine: () => localEngine(), initialDocuments: [{ source: ebook }] },
withStage(),
withRender(),
withInteraction(),
withSelection(),
withSearch(),
withAnnotation(),
withRedaction(),
),
],
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrl: './from-code.css',
encapsulation: ViewEncapsulation.None,
template: `
<ng-container *epdfDocumentGate="let document; fallback: loading">
<demo-mark-from-code [stage]="stage" />
<epdf-stage #stage="epdfStage" class="stage">
<ng-template epdfPage>
<epdf-render-layer />
<epdf-selection-layer />
<epdf-annotation-layer />
</ng-template>
</epdf-stage>
</ng-container>
<ng-template #loading><p class="loading">Loading…</p></ng-template>
`,
})
export class App {}Review the marks#
redaction.pending() lists the marks that haven’t been applied yet, for a review panel:
import { Component, inject, input } from '@angular/core';
import { annotationKey } from '@embedpdf/angular/annotation';
import { EpdfRedaction } from '@embedpdf/angular/redaction';
import type { EpdfStage } from '@embedpdf/angular/stage';
/** Next to `<epdf-stage #stage="epdfStage">`: `<app-pending-redactions [stage]="stage" />`. */
@Component({
selector: 'app-pending-redactions',
template: `
<ul>
@for (mark of redaction.pending(); track annotationKey(mark.ref)) {
<li>
<button (click)="stage().reveal(mark.page, { rect: mark.bounds })">
Page {{ mark.pageIndex + 1 }}: {{ mark.kind === 'text' ? 'text' : 'area' }}
</button>
<button (click)="redaction.unmark([mark.ref])">Remove</button>
</li>
}
</ul>
`,
})
export class PendingRedactions {
readonly stage = input.required<EpdfStage>();
protected readonly redaction = inject(EpdfRedaction);
protected readonly annotationKey = annotationKey;
}redaction.clearPending() removes every mark. redaction.pendingOn(page.ref) lists one page’s.
Loading live preview…
import {
ChangeDetectionStrategy,
Component,
computed,
effect,
inject,
input,
ViewEncapsulation,
} from '@angular/core';
import { EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { withInteraction } from '@embedpdf/angular/interaction';
import { withSearch } from '@embedpdf/angular/search';
import {
annotationKey,
EpdfAnnotation,
EpdfAnnotationLayer,
withAnnotation,
} from '@embedpdf/angular/annotation';
import { EpdfRedaction, withRedaction } from '@embedpdf/angular/redaction';
import { localEngine } from '@embedpdf/engine';
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};
/** Next to `<epdf-stage #stage="epdfStage">`: `<div demoPendingMarks [stage]="stage"></div>`. */
@Component({
selector: 'div[demoPendingMarks]',
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'panel' },
template: `
<div class="panel-head">
<span class="readout">{{ count() }}</span>
<button
type="button"
class="button"
[disabled]="!redaction.pendingCount()"
(click)="redaction.clearPending()"
>
Remove all
</button>
</div>
<ul class="marks">
@for (mark of redaction.pending(); track key(mark.ref)) {
<li class="mark">
<button
type="button"
class="mark-go"
(click)="stage().reveal(mark.page, { rect: mark.bounds })"
>
<span class="mark-page">Page {{ mark.pageIndex + 1 }}</span>
<span class="mark-kind">{{ mark.kind === 'text' ? 'Text' : 'Area' }}</span>
</button>
<button
type="button"
class="button"
[disabled]="!redaction.canUnmark(mark.ref)"
(click)="redaction.unmark([mark.ref])"
>
Remove
</button>
</li>
}
</ul>
`,
})
export class PendingMarks {
readonly stage = input.required<EpdfStage>();
// pending(): the marks not applied yet, in page order.
protected readonly redaction = inject(EpdfRedaction);
protected readonly key = annotationKey;
protected readonly count = computed(() => {
const count = this.redaction.pendingCount();
return `${count} ${count === 1 ? 'mark' : 'marks'}`;
});
}
@Component({
selector: 'demo-root',
imports: [
EpdfDocumentGate,
EpdfStage,
EpdfPageTemplate,
EpdfRenderLayer,
EpdfAnnotationLayer,
PendingMarks,
],
providers: [
provideEmbedPdf(
{ engine: () => localEngine(), initialDocuments: [{ source: ebook }] },
withStage(),
withRender(),
withInteraction(),
withSearch(),
withAnnotation(),
withRedaction(),
),
],
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrl: './review.css',
encapsulation: ViewEncapsulation.None,
template: `
<ng-container *epdfDocumentGate="let document; fallback: loading">
<div class="viewer">
<epdf-stage #stage="epdfStage" class="stage">
<ng-template epdfPage>
<epdf-render-layer />
<epdf-annotation-layer />
</ng-template>
</epdf-stage>
<div demoPendingMarks [stage]="stage"></div>
</div>
</ng-container>
<ng-template #loading><p class="loading">Loading…</p></ng-template>
`,
})
export class App {
private readonly redaction = inject(EpdfRedaction);
private readonly annotation = inject(EpdfAnnotation);
private started = false;
constructor() {
// On load: the author's name, and every "commercial" in the document.
effect(() => {
if (this.annotation.status() !== 'ready' || this.started) return;
this.started = true;
void this.redaction
.markArea(0, { x: 100, y: 508, width: 172, height: 50 })
.then(() => this.redaction.markMatches({ text: 'commercial' }));
});
}
}Apply#
const result = await redaction.applyAll(); // every mark
await redaction.apply([mark.ref]); // some marks
await redaction.applyPages([page.ref]); // the marks on some pagesThis can’t be undone. Ask first, and say what else goes with it: annotations a mark overlaps are
removed too, because they could show what was there. estimateCollateral() tells you how many:
const { count } = redaction.estimateCollateral();
if (!(await confirm(`Redact? ${count} annotations under the marks will be removed too.`))) return;
await redaction.applyAll();After applying, download the document with { mode: 'rewrite' }, so the removed content isn’t left in
an earlier revision of the file. See Saving & downloading.
Loading live preview…
import {
ChangeDetectionStrategy,
Component,
computed,
effect,
inject,
signal,
ViewEncapsulation,
} from '@angular/core';
import {
EpdfDocumentGate,
EpdfDocuments,
provideEmbedPdf,
saveFile,
} from '@embedpdf/angular/runtime';
import type { OpenInput } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { withInteraction } from '@embedpdf/angular/interaction';
import { EpdfAnnotation, EpdfAnnotationLayer, withAnnotation } from '@embedpdf/angular/annotation';
import { EpdfRedaction, withRedaction } from '@embedpdf/angular/redaction';
import { localEngine } from '@embedpdf/engine';
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};
// The cover's title, in page coordinates: the shapes drawn over it go with it.
const TITLE = { x: 100, y: 212, width: 392, height: 156 };
@Component({
selector: 'demo-apply-bar',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@if (asking()) {
<div class="toolbar confirm" role="alertdialog" aria-label="Redact for good?">
<span class="readout">{{ question() }}</span>
<span class="spacer"></span>
<button type="button" class="button" (click)="asking.set(false)">Cancel</button>
<button
type="button"
class="button danger"
[disabled]="redaction.applying()"
(click)="redactForGood()"
>
Redact for good
</button>
</div>
} @else {
<div class="toolbar">
<button
type="button"
class="button danger"
[disabled]="!redaction.pendingCount() || !redaction.canApply()"
(click)="ask()"
>
Redact…
</button>
<button
type="button"
class="button"
[disabled]="!redaction.lastResult()"
title="A fresh file, without the earlier revision that still holds the content"
(click)="download()"
>
Download
</button>
<span class="spacer"></span>
<output class="readout">
@if (redaction.lastResult(); as result) {
Gone for good, with {{ result.removedAnnotationCount }} other annotations
} @else {
{{ marks() }} waiting
}
</output>
</div>
}
`,
})
export class ApplyBar {
protected readonly redaction = inject(EpdfRedaction);
private readonly documents = inject(EpdfDocuments);
private readonly annotation = inject(EpdfAnnotation);
protected readonly asking = signal(false);
protected readonly collateral = signal(0);
protected readonly marks = computed(() => {
const count = this.redaction.pendingCount();
return `${count} ${count === 1 ? 'mark' : 'marks'}`;
});
protected readonly question = computed(() => {
const count = this.collateral();
const others =
count > 0 ? `${count} ${count === 1 ? 'annotation' : 'annotations'} under them go too. ` : '';
return `Redact ${this.marks()}? ${others}This can’t be undone.`;
});
private started = false;
constructor() {
// On load: the title marked.
effect(() => {
if (this.annotation.status() !== 'ready' || this.started) return;
this.started = true;
void this.redaction.markArea(0, TITLE);
});
}
// The other annotations applying removes too: they could show what was there.
protected ask() {
this.collateral.set(this.redaction.estimateCollateral().count);
this.asking.set(true);
}
protected redactForGood() {
void this.redaction.applyAll().finally(() => this.asking.set(false));
}
protected download() {
void this.documents
.download(undefined, { mode: 'rewrite' })
.then((bytes) => saveFile(bytes, 'redacted.pdf', 'application/pdf'));
}
}
@Component({
selector: 'demo-root',
imports: [
EpdfDocumentGate,
EpdfStage,
EpdfPageTemplate,
EpdfRenderLayer,
EpdfAnnotationLayer,
ApplyBar,
],
providers: [
provideEmbedPdf(
{ engine: () => localEngine(), initialDocuments: [{ source: ebook }] },
withStage(),
withRender(),
withInteraction(),
withAnnotation(),
withRedaction(),
),
],
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrl: './apply.css',
encapsulation: ViewEncapsulation.None,
template: `
<ng-container *epdfDocumentGate="let document; fallback: loading">
<demo-apply-bar />
<epdf-stage class="stage">
<ng-template epdfPage>
<epdf-render-layer />
<epdf-annotation-layer />
</ng-template>
</epdf-stage>
</ng-container>
<ng-template #loading><p class="loading">Loading…</p></ng-template>
`,
})
export class App {}What shows in place of the content#
By default a redacted area is black. Give it a label, such as “Classified”, on a mark:
await redaction.updateLabel(mark.ref, { overlayText: 'Classified', repeat: true });repeat fills the area with the label over and over; otherwise it shows once. The color and font of
every mark are settings.
Loading live preview…
import {
ChangeDetectionStrategy,
Component,
computed,
effect,
inject,
input,
signal,
ViewEncapsulation,
} from '@angular/core';
import { EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { withInteraction } from '@embedpdf/angular/interaction';
import { EpdfAnnotation, EpdfAnnotationLayer, withAnnotation } from '@embedpdf/angular/annotation';
import { EpdfRedaction, withRedaction } from '@embedpdf/angular/redaction';
import { localEngine } from '@embedpdf/engine';
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};
// The cover's subtitle, in page coordinates.
const SUBTITLE = { x: 100, y: 378, width: 352, height: 114 };
/** Next to `<epdf-stage #stage="epdfStage">`: `<demo-label-bar [stage]="stage" />`. */
@Component({
selector: 'demo-label-bar',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="toolbar">
<form class="label-form" (submit)="$event.preventDefault(); setLabel()">
<input
#field
class="field"
aria-label="Label"
[value]="text()"
[disabled]="!mark()"
(input)="text.set(field.value)"
/>
<button type="submit" class="button" [disabled]="!mark()">Set label</button>
</form>
<label class="label">
<input
#repeat
type="checkbox"
[checked]="mark()?.repeat ?? false"
[disabled]="!mark()"
(change)="setRepeat(repeat.checked)"
/>
Repeat
</label>
<button
type="button"
class="button danger"
[disabled]="!mark() || redaction.applying()"
(click)="redaction.applyAll()"
>
Redact
</button>
<span class="spacer"></span>
<output class="readout">
{{
redaction.lastResult() ? 'The label is part of the page now' : 'Redact to see the label'
}}
</output>
</div>
`,
})
export class LabelBar {
readonly stage = input.required<EpdfStage>();
protected readonly redaction = inject(EpdfRedaction);
private readonly annotation = inject(EpdfAnnotation);
protected readonly mark = computed(() => this.redaction.pending()[0]);
protected readonly text = signal('Classified');
private started = false;
constructor() {
// On load: the subtitle marked, with a label that fills the area.
effect(() => {
if (this.annotation.status() !== 'ready' || this.started) return;
this.started = true;
const stage = this.stage();
void this.redaction
.markArea(0, SUBTITLE)
.then(({ mark }) =>
this.redaction.updateLabel(mark.ref, { overlayText: 'Classified', repeat: true }),
)
.then(() => stage.reveal(0, { rect: SUBTITLE }));
});
}
protected setLabel() {
const mark = this.mark();
if (mark)
void this.redaction.updateLabel(mark.ref, { overlayText: this.text().trim() || null });
}
protected setRepeat(repeat: boolean) {
const mark = this.mark();
if (mark) void this.redaction.updateLabel(mark.ref, { repeat });
}
}
// What applying paints over every mark: a dark blue area, its label in white.
@Component({
selector: 'demo-root',
imports: [
EpdfDocumentGate,
EpdfStage,
EpdfPageTemplate,
EpdfRenderLayer,
EpdfAnnotationLayer,
LabelBar,
],
providers: [
provideEmbedPdf(
{ engine: () => localEngine(), initialDocuments: [{ source: ebook }] },
withStage(),
withRender(),
withInteraction(),
withAnnotation(),
withRedaction({ overlay: { fill: '#1a2748', text: { color: '#ffffff' } } }),
),
],
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrl: './label.css',
encapsulation: ViewEncapsulation.None,
template: `
<ng-container *epdfDocumentGate="let document; fallback: loading">
<demo-label-bar [stage]="stage" />
<epdf-stage #stage="epdfStage" class="stage">
<ng-template epdfPage>
<epdf-render-layer />
<epdf-annotation-layer />
</ng-template>
</epdf-stage>
</ng-container>
<ng-template #loading><p class="loading">Loading…</p></ng-template>
`,
})
export class App {}Methods#
Everything inject(EpdfRedaction) gives you.
| Method | What it does |
|---|---|
markSelection() | Marks the selected text, one mark per page, and clears the selection. Resolves { marks }. Mark from code |
markArea(page, rect), markPage(page) | Marks an area of a page, or the whole page. Resolves { mark }. |
markMatches(query, { pages? }) | Marks every match of a search, or the ones on some pages. It replaces the current search. Resolves { marks }. |
unmark(refs) | Removes marks without applying them. Review the marks |
clearPending() | Removes every mark |
updateLabel(ref, { overlayText, repeat? }) | Sets the label a mark shows once it’s applied; overlayText: null clears it. Resolves { mark }. What shows in place of the content |
listPending({ page? }) | The marks not applied yet, in page order, or one page’s. In a component, use redaction.pending(). |
getPending(ref) | One mark, or null when it isn’t a mark waiting to be applied |
estimateCollateral(refs?) | The other annotations applying would remove too, as { count, refs }, for every mark or for the ones you pass. Apply |
applyAll() | Applies every mark in the document, on pages that haven’t loaded too. Can’t be undone. Apply |
apply(refs), applyPages(pages) | Applies some marks, or the marks on some pages. Can’t be undone. |
canMark(), canUnmark(ref), canUpdateLabel(ref), canApply() | Whether marking, removing a mark, changing its label and applying are allowed. Permissions |
getSettings(), updateSettings(changes), resetSettings() | Reads the settings, changes them while the app runs, or goes back to what you registered. In a component, use inject(EpdfRedaction).settings(). Settings |
Applying resolves the result: each page’s status ('applied' or 'unchanged') and how many
other annotations were removed. Applying is all or nothing: when a page fails, no page changes and
the promise rejects. Every method that returns a promise also
takes a signal in its options, to cancel it.
State#
Every value is a signal on inject(EpdfRedaction): read it in a template or a computed(), and your view updates when it changes. The method in the last column reads the same value once.
| Field | What it is | Read once |
|---|---|---|
pendingCount() | How many marks are waiting to be applied | getPendingCount(), or getPendingCount(page) for one page |
applying() | Whether marks are being applied right now | isApplying() |
lastResult() | The result of the last apply, from this session or another, or null | getLastResult() |
The marks themselves come from redaction.pending().
Settings#
Set them when you register the plugin, and change them while the app runs with
redaction.updateSettings({ … }); redaction.resetSettings() goes back to what you registered, and
inject(EpdfRedaction).settings() gives you the current settings.
| Setting | What it does | Default |
|---|---|---|
overlay.fill | The color of a redacted area | '#000000' |
overlay.text.color | The color of a label | '#ffffff' |
overlay.text.fontFamily | The label’s font | Helvetica |
overlay.text.fontSize | The label’s size, or 0 to fit the area (a repeated label then draws at 12pt) | 0 |
Events#
import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { map } from 'rxjs';
import { EpdfRedaction } from '@embedpdf/angular/redaction';
@Component({
selector: 'app-redaction-notice',
template: `<p aria-live="polite">{{ message() }}</p>`,
})
export class RedactionNotice {
private readonly redaction = inject(EpdfRedaction);
protected readonly message = toSignal(
this.redaction.applied$.pipe(map(() => 'Redacted: the content under the marks is gone')),
{ initialValue: '' },
);
}| Event | Fires when | Carries |
|---|---|---|
applied$ | Marks were applied and the content is gone | result, origin |
pendingChanged$ | A mark was added, changed or removed | pages |
settingsChanged$ | The settings changed | settings, changed |
Permissions#
| Permission | What it allows | Check |
|---|---|---|
annotations:create | Marking | redaction.canMark() |
annotations:delete | Removing a mark before it’s applied | redaction.canUnmark(ref) |
annotations:update | Changing a mark’s label | redaction.canUpdateLabel(ref) |
doc.redact | Applying | redaction.canApply() |
A mark is an annotation, so removing it or changing its label follows the same rules as any
annotation, per mark. Applying also rewrites the pages and removes annotations, so canApply() needs
doc.pages.modify and doc.annotate.modify as well. A reviewer who may mark but not apply can
prepare redactions for someone else to approve.
Good to know#
What is removed#
The characters under a mark are removed, so they can’t be selected, searched or copied; the pixels of images inside it are erased; and the parts of lines, shapes and fills inside it are cut away. See Redaction for the details.
Where the original is#
The local engine redacts a copy of the file the browser already has, so the original was on the user’s device before it was redacted.
Your feedback goes directly to the documentation team.