// ES6 Compliant Syntax
// GitHub Copilot v1, Claude Sonnet 4 (Preview), October 5, 2025
// File: showcase-demos/redaction/index.js
import WebViewer from '@pdftron/webviewer';
const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
// Global variables to track state
let redactionDemoFile = "https://apryse.s3.amazonaws.com/public/files/samples/sales-invoice-with-credit-cards.pdf";
const searchResults = []; // Store search results globally for access in other functions
// Function to initialize and load the Redaction Tool
function initializeWebViewer() {
const element = document.getElementById('viewer');
if (!element) {
console.error('Viewer div not found.');
return;
}
WebViewer({
path: '/lib',
initialDoc: redactionDemoFile,
licenseKey: licenseKey,
enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
enableRedaction: true, // Enable redaction feature
backendType: WebViewer.BackendTypes.WASM, //required for redaction https://community.apryse.com/t/pdfworkererror-related-to-exclusive-lock-in-recursivesharedmutex-cpp-on-emscripten-platform/10059
fullAPI: true, // Required to use the PDFNet API
loadAsPDF: true,
disableElements: ['searchPanel', 'searchButton'], // Disable built-in search to prevent focus errors
}, element).then(instance => {
const { documentViewer } = instance.Core;
documentViewer.addEventListener('documentLoaded', () => {
instance.UI.openElements(['redactionPanel']);
instance.UI.disableElements(disabledElements);
instance.UI.addSearchListener(searchListener); //Handle search events to capture results for redaction
});
// UI Section
createUIElements();
});
}
// Function to apply redactions based on search results
async function applyRedactions() {
const { documentViewer } = window.WebViewer.getInstance().Core;
const annotationManager = documentViewer.getAnnotationManager();
const annotations = await formatAnnotations(searchResults);
console.log('Global results', searchResults);
//Accessing the annotation manager to add and draw annotations
annotationManager.addAnnotations(annotations);
annotationManager.drawAnnotationsFromList(annotations);
// Apply redactions
annotationManager.applyRedactions();
// Clear search results and the searchResults array after applying redactions
documentViewer.clearSearchResults();
searchResults.length = 0;
}
// Search Listener, captures search results and adds redaction annotations
// Only add it once to avoid multiple triggers
const searchListener = (searchPattern, options, results) => {
const { UI } = window.WebViewer.getInstance();
addAnnotationsUsingSearchResult(results);
if (results.length > 0) {
UI.openElements(['redactionPanel']);
}
else
UI.closeElements(['redactionPanel']);
console.log('Search complete: ', searchPattern, options, results);
};
// Function to perform search and add redaction annotations
function search(searchtext, searchOptions) {
const { documentViewer } = window.WebViewer.getInstance().Core;
const { UI } = window.WebViewer.getInstance();
const annotationManagerObj = documentViewer.getAnnotationManager();
const annotationList = annotationManagerObj.getAnnotationsList();
annotationManagerObj.deleteAnnotations(annotationList);
UI.searchTextFull(searchtext, searchOptions); // Perform the search with given options
}
// Function to format search results into redaction annotations
async function formatAnnotations(results) {
const { documentViewer, Annotations } = window.WebViewer.getInstance().Core;
const annotationManager = documentViewer.getAnnotationManager();
const redactionList = annotationManager
.getAnnotationsList()
.filter((annot) => annot instanceof Annotations.RedactionAnnotation);
return await results.flatMap((r) => {
const annotation = new Annotations.RedactionAnnotation();
annotation.PageNumber = r.page_num;
annotation.Quads = r.quads.map((quad) => quad.getPoints());
annotation.StrokeColor = new Annotations.Color(0, 255, 0);
annotation.setContents(r.result_str);
annotation.Author = 'Guest';
annotation.setCustomData(
'trn-annot-preview',
documentViewer.getSelectedText(annotation.PageNumber)
);
if (redactionList.some((r) => r.getContents() === annotation.getContents())) {
return [];
}
return [annotation];
});
}
// Function to add annotations using search results
// This function is called from the search listener
async function addAnnotationsUsingSearchResult(results) {
const { documentViewer } = window.WebViewer.getInstance().Core;
const annotationManager = documentViewer.getAnnotationManager();
//Keep results in global variable to access later if needed
searchResults.push(...results);
console.log('results', results);
const annotations = await formatAnnotations(results);
annotationManager.addAnnotations(annotations);
annotationManager.drawAnnotationsFromList(annotations);
};
// Search options for redaction
// You can modify these options or add more as needed
const searchOptions = {
caseSensitive: true, // match case
wholeWord: true, // match whole words only
wildcard: false, // allow using '*' as a wildcard value
regex: false, // string is treated as a regular expression
searchUp: false, // search from the end of the document upwards
ambientString: true, // return ambient string as part of the result
};
// Sample redaction search patterns using regex
// You can modify or add more patterns as needed
// WebViewer implements its own pattern similar to these below, here we define our own for the redaction demo
const redactionSearchSamples = [
{
label: 'Phone Numbers',
value: '\\b(?:\\+?1[-\\s]?)?(?:\\(?[0-9]{3}\\)?[-\\s]?)[0-9]{3}[-\\s]?[0-9]{4}\\b',
},
{
label: 'Emails',
value: '\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}\\b',
},
{
label: 'Credit Card Numbers',
value: '\\b(?:\\d[ -]*?){13,16}\\b',
},
];
//UI Elements to disable
const disabledElements = [
'toolbarGroup-Shapes',
'toolbarGroup-View',
'toolbarGroup-Insert',
'toolbarGroup-Annotate',
'toolbarGroup-FillAndSign',
'toolbarGroup-Forms',
'toolbarGroup-Edit',
'toolbarGroup-Measure',
];
// UI Elements
// ui-elements.js
// Function to create and initialize UI elements
function createUIElements() {
// Create a container for all controls (label, dropdown, and buttons)
// Dynamically load ui-elements.js if not already loaded
if (!window.SidePanel) {
const script = document.createElement('script');
script.src = '/showcase-demos/redaction/ui-elements.js';
script.onload = () => {
UIElements.init('viewer', searchResults);
UIElements.handleException(); //Add handling of Reacts focus error on this JavaScript sample.
};
document.head.appendChild(script);
}
}
//Make functions accessible globally
window.redactionSearchSamples = redactionSearchSamples;
window.searchOptions = searchOptions;
window.applyRedactions = applyRedactions;
window.search = search;
// Initialize the WebViewer
initializeWebViewer();