Skip to content

Commit 60c982a

Browse files
authored
Merge pull request #426 from embedpdf/feature/redact-annotation
Feature/redact annotation
2 parents 57a8431 + d9d844b commit 60c982a

146 files changed

Lines changed: 6068 additions & 402 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@embedpdf/core': patch
3+
---
4+
5+
Fixed AutoMount component to render utilities inside wrapper context. Utilities registered via `addUtility()` now have access to context provided by wrappers (React, Vue, Svelte), enabling plugins to share context between wrappers and utilities.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@embedpdf/engines': minor
3+
---
4+
5+
Added redaction annotation engine methods:
6+
7+
- Added `applyRedaction()` to apply a single REDACT annotation, removing content and flattening the overlay
8+
- Added `applyAllRedactions()` to apply all REDACT annotations on a page
9+
- Added `flattenAnnotation()` to flatten any annotation's appearance to page content
10+
- Added `readPdfRedactAnno()` for reading REDACT annotations with all properties
11+
- Added `addRedactContent()` for creating REDACT annotations with QuadPoints, colors, and overlay text
12+
- Added overlay text getter/setter methods for REDACT annotations
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@embedpdf/models': minor
3+
---
4+
5+
Added support for REDACT annotation type with full read/write capabilities:
6+
7+
- Added `PdfRedactAnnoObject` interface for redact annotations with properties for overlay text, colors, and font settings
8+
- Added `PdfAnnotationColorType.OverlayColor` enum value for redaction overlay color
9+
- Added `PdfRedactAnnoObject` to `PdfSupportedAnnoObject` union type
10+
- Added new engine interface methods: `applyRedaction`, `applyAllRedactions`, `flattenAnnotation`
11+
- Added corresponding methods to `IPdfiumExecutor` interface
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@embedpdf/pdfium': minor
3+
---
4+
5+
Added PDFium functions for redaction annotation support:
6+
7+
- Added `EPDFAnnot_ApplyRedaction` to apply a single redaction annotation
8+
- Added `EPDFAnnot_Flatten` to flatten an annotation's appearance to page content
9+
- Added `EPDFPage_ApplyRedactions` to apply all redactions on a page
10+
- Added `EPDFAnnot_GetOverlayText` and `EPDFAnnot_SetOverlayText` for overlay text
11+
- Added `EPDFAnnot_GetOverlayTextRepeat` and `EPDFAnnot_SetOverlayTextRepeat` for text repeat setting
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@embedpdf/plugin-annotation': minor
3+
---
4+
5+
Added annotation renderer registry and enhanced annotation capabilities:
6+
7+
- Added `purgeAnnotation()` method to remove annotations from state without calling the PDF engine
8+
- Added annotation renderer registry allowing external plugins to register custom annotation renderers
9+
- Added `useRegisterRenderers()` hook and `AnnotationRendererProvider` context for renderer registration
10+
- Changed interaction properties (`isDraggable`, `isResizable`, `lockAspectRatio`) to support dynamic functions based on annotation
11+
- Added `AnnotationCommandMetadata` interface for history command filtering
12+
- Added `isRedact()` helper function for type-checking redact annotations
13+
- Framework exports now include `AnnotationPluginPackage` with `AnnotationRendererProvider` wrapper
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@embedpdf/plugin-commands': minor
3+
---
4+
5+
Added `logger` to command action context, enabling commands to log debug information through the plugin's logger instance.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@embedpdf/plugin-history': minor
3+
---
4+
5+
Added history purging by command metadata:
6+
7+
- Added `purgeByMetadata()` method to remove history entries matching a predicate on command metadata
8+
- Added generic `metadata` field to `Command` interface for attaching identifiable data to commands
9+
- Enables permanent operations (like redaction commits) to clean up related undo/redo history
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@embedpdf/plugin-redaction': minor
3+
---
4+
5+
Added annotation-based redaction mode for integrated redaction workflow:
6+
7+
- Added `useAnnotationMode` config option to use REDACT annotations as pending redactions
8+
- Added unified `RedactionMode.Redact` mode supporting both text selection and area marquee
9+
- Added `redactTool` annotation tool for integration with annotation plugin
10+
- Added `RedactHighlight` and `RedactArea` components for rendering redact annotations
11+
- Added automatic renderer registration via framework-specific `RedactionPluginPackage`
12+
- Added `source`, `markColor`, `redactionColor`, and `text` properties to `RedactionItem` type
13+
- Pending redactions now sync with annotation plugin state in annotation mode
14+
- Added `enableRedact()`, `toggleRedact()`, `isRedactActive()`, `endRedact()` methods
15+
- Removed deprecated `startRedaction()` and `endRedaction()` methods from scope API
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@embedpdf/snippet': minor
3+
---
4+
5+
Added redaction management features:
6+
7+
- Added `RedactionSidebar` component for viewing and managing pending redactions
8+
- Added `annotation:apply-redaction` command to apply the selected redaction annotation
9+
- Added `redaction:redact` command for unified redact mode (text + area)
10+
- Added `panel:toggle-redaction` command for toggling the redaction sidebar
11+
- Added redaction panel configuration to UI schema
12+
- Added REDACT annotation type support in annotation sidebar
13+
- Added `redactCombined` and `redactionSidebar` icons
14+
- Added translations for redaction panel, overlay text, and redaction states
15+
- Updated redaction toolbar to use unified redact mode
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
<script lang="ts">
2+
import { Viewport } from '@embedpdf/plugin-viewport/svelte';
3+
import { Scroller, type RenderPageProps } from '@embedpdf/plugin-scroll/svelte';
4+
import { RenderLayer } from '@embedpdf/plugin-render/svelte';
5+
import { PagePointerProvider } from '@embedpdf/plugin-interaction-manager/svelte';
6+
import { SelectionLayer } from '@embedpdf/plugin-selection/svelte';
7+
import { AnnotationLayer, useAnnotation } from '@embedpdf/plugin-annotation/svelte';
8+
import { RedactionLayer, RedactionMode, useRedaction } from '@embedpdf/plugin-redaction/svelte';
9+
import { PdfAnnotationSubtype, type PdfRedactAnnoObject } from '@embedpdf/models';
10+
import { AlertCircle, Loader2, Check, Crosshair, Trash2 } from 'lucide-svelte';
11+
12+
// Color options for redaction annotations
13+
const REDACTION_COLORS = [
14+
{ name: 'Red', value: '#E44234' },
15+
{ name: 'Blue', value: '#2563EB' },
16+
{ name: 'Green', value: '#16A34A' },
17+
{ name: 'Purple', value: '#9333EA' },
18+
];
19+
20+
interface Props {
21+
documentId: string;
22+
}
23+
24+
let { documentId }: Props = $props();
25+
26+
const redaction = useRedaction(() => documentId);
27+
const annotation = useAnnotation(() => documentId);
28+
let isCommitting = $state(false);
29+
30+
// Get selected REDACT annotation from state
31+
const selectedRedact = $derived(() => {
32+
if (!annotation.state.selectedUid) return null;
33+
const tracked = annotation.state.byUid[annotation.state.selectedUid];
34+
if (!tracked) return null;
35+
if (tracked.object.type !== PdfAnnotationSubtype.REDACT) return null;
36+
return tracked.object as PdfRedactAnnoObject;
37+
});
38+
39+
const selectedAnnotationId = $derived(selectedRedact()?.id ?? null);
40+
const selectedColor = $derived(selectedRedact()?.strokeColor ?? null);
41+
const isRedactActive = $derived(redaction.state.activeType === RedactionMode.Redact);
42+
43+
const handleApplyAll = () => {
44+
if (!redaction.provides || isCommitting) return;
45+
isCommitting = true;
46+
redaction.provides.commitAllPending().wait(
47+
() => {
48+
isCommitting = false;
49+
},
50+
() => {
51+
isCommitting = false;
52+
},
53+
);
54+
};
55+
56+
const handleColorChange = (color: string) => {
57+
const selected = selectedRedact();
58+
if (!annotation.provides || !selected) return;
59+
60+
annotation.provides.updateAnnotation(selected.pageIndex, selected.id, {
61+
strokeColor: color,
62+
color: color,
63+
});
64+
};
65+
</script>
66+
67+
<div
68+
class="overflow-hidden rounded-lg border border-gray-300 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-900"
69+
style="user-select: none"
70+
>
71+
<!-- Toolbar -->
72+
<div
73+
class="flex flex-wrap items-center gap-3 border-b border-gray-300 bg-gray-100 px-3 py-2 dark:border-gray-700 dark:bg-gray-800"
74+
>
75+
<span class="text-xs font-medium uppercase tracking-wide text-gray-600 dark:text-gray-300">
76+
Annotation Mode
77+
</span>
78+
<div class="h-4 w-px bg-gray-300 dark:bg-gray-600"></div>
79+
80+
<!-- Unified redact mode button -->
81+
<button
82+
type="button"
83+
onclick={() => redaction.provides?.toggleRedact()}
84+
class={[
85+
'inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium shadow-sm transition-all',
86+
isRedactActive
87+
? 'bg-blue-500 text-white ring-1 ring-blue-600'
88+
: 'bg-white text-gray-600 ring-1 ring-gray-300 hover:bg-gray-50 hover:text-gray-900 dark:bg-gray-700 dark:text-gray-300 dark:ring-gray-600 dark:hover:bg-gray-600 dark:hover:text-gray-100',
89+
].join(' ')}
90+
>
91+
<Crosshair size={14} />
92+
<span class="hidden sm:inline">Redact</span>
93+
</button>
94+
95+
<!-- Color picker - visible when annotation is selected -->
96+
{#if selectedAnnotationId}
97+
<div class="h-4 w-px bg-gray-300 dark:bg-gray-600"></div>
98+
<div class="flex items-center gap-1.5">
99+
<span class="text-xs text-gray-500 dark:text-gray-400">Color:</span>
100+
{#each REDACTION_COLORS as c}
101+
<button
102+
type="button"
103+
onclick={() => handleColorChange(c.value)}
104+
class={[
105+
'h-5 w-5 rounded-full transition-all',
106+
selectedColor === c.value
107+
? 'ring-2 ring-offset-1 ring-gray-800 dark:ring-gray-200'
108+
: 'hover:scale-110',
109+
].join(' ')}
110+
style:background-color={c.value}
111+
title={c.name}
112+
></button>
113+
{/each}
114+
</div>
115+
{/if}
116+
117+
<div class="h-4 w-px bg-gray-300 dark:bg-gray-600"></div>
118+
119+
<!-- Pending count and apply -->
120+
<div class="flex items-center gap-2">
121+
{#if redaction.state.pendingCount > 0}
122+
<span
123+
class="inline-flex items-center gap-1.5 text-xs font-medium text-amber-600 dark:text-amber-400"
124+
>
125+
<AlertCircle size={14} />
126+
{redaction.state.pendingCount} pending
127+
</span>
128+
{:else}
129+
<span class="text-xs text-gray-500 dark:text-gray-400">No marks pending</span>
130+
{/if}
131+
<button
132+
type="button"
133+
onclick={handleApplyAll}
134+
disabled={redaction.state.pendingCount === 0 || isCommitting}
135+
class="inline-flex items-center gap-1.5 rounded-md bg-red-500 px-2.5 py-1.5 text-xs font-medium text-white shadow-sm ring-1 ring-red-600 transition-all hover:bg-red-600 disabled:cursor-not-allowed disabled:opacity-50"
136+
>
137+
{#if isCommitting}
138+
<Loader2 size={14} class="animate-spin" />
139+
{:else}
140+
<Check size={14} />
141+
{/if}
142+
{isCommitting ? 'Applying...' : 'Apply All'}
143+
</button>
144+
</div>
145+
146+
<!-- Hint text -->
147+
{#if isRedactActive}
148+
<span class="hidden animate-pulse text-xs text-blue-600 lg:inline dark:text-blue-400">
149+
Select text or draw a rectangle to mark for redaction
150+
</span>
151+
{/if}
152+
</div>
153+
154+
<!-- PDF Viewer Area -->
155+
<div class="relative h-[450px] sm:h-[550px]">
156+
{#snippet renderPage(page: RenderPageProps)}
157+
<PagePointerProvider {documentId} pageIndex={page.pageIndex}>
158+
<RenderLayer
159+
{documentId}
160+
pageIndex={page.pageIndex}
161+
scale={1}
162+
class="pointer-events-none"
163+
/>
164+
<SelectionLayer {documentId} pageIndex={page.pageIndex} />
165+
<!-- AnnotationLayer renders REDACT annotations -->
166+
<AnnotationLayer {documentId} pageIndex={page.pageIndex}>
167+
{#snippet selectionMenuSnippet(menuProps)}
168+
{#if menuProps.selected}
169+
<span style={menuProps.menuWrapperProps.style} use:menuProps.menuWrapperProps.action>
170+
<div
171+
class="flex gap-1 rounded-lg border border-gray-300 bg-white p-1 shadow-lg dark:border-gray-600 dark:bg-gray-800"
172+
style:position="absolute"
173+
style:top="{menuProps.rect.size.height + 8}px"
174+
style:left="0"
175+
style:pointer-events="auto"
176+
>
177+
<button
178+
type="button"
179+
onclick={() =>
180+
annotation.provides?.deleteAnnotation(
181+
menuProps.context.pageIndex,
182+
menuProps.context.annotation.object.id,
183+
)}
184+
class="inline-flex items-center gap-1.5 rounded-md bg-white px-2.5 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-gray-300 transition-colors hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-200 dark:ring-gray-600 dark:hover:bg-gray-600"
185+
>
186+
<Trash2 size={12} />
187+
Delete
188+
</button>
189+
</div>
190+
</span>
191+
{/if}
192+
{/snippet}
193+
</AnnotationLayer>
194+
<!-- RedactionLayer renders marquee/selection UI -->
195+
<RedactionLayer {documentId} pageIndex={page.pageIndex} />
196+
</PagePointerProvider>
197+
{/snippet}
198+
<Viewport {documentId} class="absolute inset-0 bg-gray-200 dark:bg-gray-800">
199+
<Scroller {documentId} {renderPage} />
200+
</Viewport>
201+
</div>
202+
</div>

0 commit comments

Comments
 (0)