How to Redact a PDF Document With Angular and C# in the PDF Viewer?
How to Redact a PDF Document with Angular and C# in the PDF Viewer
Description:
This article demonstrates how to redact a PDF document in the Syncfusion PDF Viewer component using Angular for the frontend and C# for the backend. The process includes selecting the areas to redact in the PDF viewer, sending the data to the backend for processing, and displaying the redacted PDF in the viewer.
Solution:
The redaction process involves hiding or removing sensitive information from a document by covering it with black bars or custom annotations. We’ll implement redaction in Angular on the frontend and use C# in the backend to process and save the redacted PDF document.
Prerequisites:
Before diving into the implementation, ensure that the following steps are completed:
- Syncfusion PDF Viewer Setup: Make sure the Syncfusion PDF Viewer is installed and set up in your Angular project. Follow the Getting Started with Syncfusion PDF Viewer for Angular guide if you haven’t already.
- Basic Knowledge of Angular: Familiarity with Angular components and the basic setup of Angular projects will help you follow along with the implementation.
Code Snippets:
Angular Client
app.comopnent.ts
import { Component, ViewEncapsulation, OnInit, ViewChild } from '@angular/core';
import { PdfViewerComponent, LinkAnnotationService, BookmarkViewService, MagnificationService, ToolbarService, NavigationService, TextSelectionService, PrintService, PageChangeEventArgs, LoadEventArgs, AnnotationService, FormDesignerService, PageOrganizerService, TextSearchService, PdfViewerModule, AnnotationAddEventArgs, AnnotationRemoveEventArgs, CreateArgs, MouseEventArgs } from '@syncfusion/ej2-angular-pdfviewer';
import { ToolbarComponent, ToolbarModule, MenuModule, AppBarModule, ChangeEventArgs } from '@syncfusion/ej2-angular-navigations';
import { DialogComponent, DialogModule } from '@syncfusion/ej2-angular-popups';
import { ClickEventArgs } from '@syncfusion/ej2-buttons';
import { ButtonComponent, ButtonModule, SwitchModule } from '@syncfusion/ej2-angular-buttons';
import { ComboBoxModule } from '@syncfusion/ej2-angular-dropdowns';
import { UploaderComponent, UploaderModule } from '@syncfusion/ej2-angular-inputs';
/**
* Default PdfViewer Controller
*/
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
encapsulation: ViewEncapsulation.None,
providers: [LinkAnnotationService, BookmarkViewService, TextSearchService, TextSelectionService, MagnificationService, ToolbarService, NavigationService, TextSelectionService, PrintService, AnnotationService, FormDesignerService, PageOrganizerService],
styleUrls: ['app.component.css'],
standalone: true,
imports: [
SwitchModule,
AppBarModule,
ToolbarModule,
MenuModule,
PdfViewerModule,
ButtonModule,
DialogModule,
UploaderModule,
ComboBoxModule
],
})
export class AppComponent {
title(title: any) {
throw new Error('Method not implemented.');
}
@ViewChild('pdfviewer')
public pdfviewerControl: PdfViewerComponent | undefined;
@ViewChild('primaryToolbar')
public primaryToolbar: ToolbarComponent | undefined;
@ViewChild('secondaryToolbar')
public secondaryToolbar: ToolbarComponent | undefined;
@ViewChild('defaultButtonDownload') downloadBtn: ButtonComponent | undefined;
@ViewChild('dialogComponent')
public dialog: DialogComponent | undefined;
@ViewChild('cancelButton')
public cancelButton: ButtonComponent | undefined;
public showCloseIcon: Boolean = true;
public height = '482px';
public target = '#e-pv-redact-sb-panel';
public width = '477px';
public visible: Boolean = false;
public isModal: Boolean = true;
@ViewChild('defaultupload')
public uploadObj: UploaderComponent | undefined;
public path: Object = {
saveUrl: 'https://services.syncfusion.com/angular/production/api/FileUploader/Save',
removeUrl: 'https://services.syncfusion.com/angular/production/api/FileUploader/Remove'
};
public dropElement: HTMLElement = document.getElementsByClassName('drop-area-wrap')[0] as HTMLElement;
public allowedExtensions = '.png, .jpg, .jpeg';
public imageSrc: any;
public onFileChange(args: any): void {
var file = args.file[0].rawFile;
let imageElement = document.getElementById('imageView');
let imageElementContainer = document.getElementById('imageContainer');
let base64String: any;
var reader = new FileReader();
reader.onload = (e) => {
base64String = e.target?.result as string;
this.imageSrc = base64String;
this.customStampSource = this.imageSrc;
(imageElement as any).src = this.imageSrc;
if(imageElementContainer)
imageElementContainer.className =
'image-container e-pv-redact-sb-image-container-selected';
(imageElement as any).style.display = 'block';
// Bind click event to the image element
if(imageElement)
imageElement.addEventListener('click', this.handleImageClick.bind(this));
};
reader.readAsDataURL(file);
}
public handleImageClick() {
this.customStampSource = this.imageSrc;
if(this.dialog)
this.dialog.hide();
this.addImage();
}
public ngAfterViewInit(): void {
if(this.downloadBtn)
this.downloadBtn.element.setAttribute("aria-label", "menu");
}
public document: string = "https://cdn.syncfusion.com/content/pdf/programmatical-annotations.pdf";
public resource: string = "https://cdn.syncfusion.com/ej2/27.2.2/dist/ej2-pdfviewer-lib";
public url: string = "https://ej2services.syncfusion.com/angular/development/api/pdfviewer/Redaction";
//zoom value
public data: string[] = ['10%', '25%', '50%', '75%', '100%', '200%', '400%'];
ngOnInit(): void {
const fileUploadElement = document.getElementById('fileUpload');
if(fileUploadElement) {
fileUploadElement.addEventListener('change', this.readFile.bind(this));
}
}
public annotation: any;
public redactionCount: number = 0;
public fileName: string = "programmatical-annotations.pdf";
//Updating the number of redaction while the annotation has been added
annotationAdd = (e: AnnotationAddEventArgs): void => {
var pdfAnnotationList = new Array();
if(this.pdfviewerControl)
pdfAnnotationList = this.pdfviewerControl.annotationCollection;
var selectedAnnotationIndex = pdfAnnotationList.findIndex(item => item.annotationId == e.annotationId);
if (selectedAnnotationIndex != -1) {
this.annotation = pdfAnnotationList[selectedAnnotationIndex];
}
if (this.annotation.author == "Redaction" || this.annotation.customStampName == "Image" || this.annotation.author == "Pattern" || this.annotation.author == "Text") {
this.redactionCount = this.redactionCount + 1;
this.updateRedaction();
}
}
//Updating the number of redaction while the annotation has been removed
annotationRemove = (e: AnnotationRemoveEventArgs): void => {
if (this.annotation.author == "Redaction" || this.annotation.customStampName == "Image" || this.annotation.author == "Pattern" || this.annotation.author == "Text") {
this.redactionCount = this.redactionCount - 1;
this.updateRedaction();
}
}
//To enable the redaction button based on count
public updateRedaction(): void {
if(this.primaryToolbar)
if (this.redactionCount <= 0) {
this.primaryToolbar.items[8].disabled = true;
}
else {
this.primaryToolbar.items[8].disabled = false;
}
}
//to read the file
// tslint:disable-next-line
private readFile(args: any): void {
// tslint:disable-next-line
let upoadedFiles: any = args.target.files;
if (args.target.files[0] !== null) {
let uploadedFile: File = upoadedFiles[0];
this.fileName = upoadedFiles[0].name;
if (uploadedFile) {
let reader: FileReader = new FileReader();
reader.readAsDataURL(uploadedFile);
// tslint:disable-next-line
let proxy: any = this;
// tslint:disable-next-line
reader.onload = (e: any): void => {
let uploadedFileUrl: string = e.currentTarget.result;
proxy.pdfviewerControl.documentPath = uploadedFileUrl;
proxy.pdfviewerControl.fileName = proxy.fileName;
proxy.pdfviewerControl.downloadFileName = proxy.fileName;
};
}
}
}
//To open a file from viewer
public openDocumentClicked(e: ClickEventArgs): void {
const fileUploadElement = document.getElementById('fileUpload');
if(fileUploadElement)
fileUploadElement.click();
}
//when the image button is clicked
public imageDialog = (): void => {
if(this.dialog)
this.dialog.show();
}
//when cancel button clicked
public closeDialog(e: MouseEvent): void {
if(this.dialog)
this.dialog.hide();
}
//Method to create rectangle annotation when "Text" button is clicked
public addText(e: ClickEventArgs): void {
if(this.pdfviewerControl)
this.pdfviewerControl.rectangleSettings = {
fillColor: '#a3a2a0',
strokeColor: '#a3a2a0',
author: 'Text'
}
if(this.pdfviewerControl)
this.pdfviewerControl.annotation.setAnnotationMode('Rectangle');
}
public customStampSource: any = "";
//Adding the image to the pdf
public addImage(): void {
if(this.pdfviewerControl){
this.pdfviewerControl.stampSettings.author = "Image";
this.pdfviewerControl.customStampSettings = {
width: 200,
author: 'Image',
height: 125,
isAddToMenu: false,
enableCustomStamp: false
};
this.pdfviewerControl.customStamp = [
{
customStampName: 'Image',
customStampImageSource: this.customStampSource
},
];
}
}
//Method to create rectangle annotation when the "Pattern" button is clicked
public addPattern(e: ClickEventArgs): void {
if(this.pdfviewerControl){
this.pdfviewerControl.rectangleSettings = {
fillColor: '#dedfe0',
strokeColor: '#dedfe0',
author: 'Pattern'
}
this.pdfviewerControl.annotation.setAnnotationMode('Rectangle');
}
}
//Method to create rectangle annotation when the "Blackout" button is clicked
public addBlackout(e: ClickEventArgs): void {
if(this.pdfviewerControl){
this.pdfviewerControl.rectangleSettings = {
fillColor: '#000000',
strokeColor: '#000000',
author: 'Redaction'
}
this.pdfviewerControl.annotation.setAnnotationMode('Rectangle');
}
}
//Method to create rectangle annotation when the "Whiteout" button is clicked
public addWhiteout(e: ClickEventArgs): void {
if(this.pdfviewerControl){
this.pdfviewerControl.rectangleSettings = {
fillColor: '#ffffff',
strokeColor: '#ffffff',
author: 'Redaction'
}
this.pdfviewerControl.annotation.setAnnotationMode('Rectangle');
}
}
//To download the redacted pdf
public download(e: MouseEvent): void {
if(this.pdfviewerControl){
this.pdfviewerControl.saveAsBlob().then((blob) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = (e: ProgressEvent<FileReader>) => {
const base64String = e.target?.result;
const xhr = new XMLHttpRequest();
xhr.open('POST', this.url, true);
xhr.setRequestHeader('Content-type', 'application/json; charset=UTF-8');
const requestData = JSON.stringify({ base64String });
xhr.onload = () => {
if (xhr.status === 200) {
const responseBase64 = xhr.responseText.split('base64,')[1];
if (responseBase64) {
const blob = this.createBloburl(responseBase64, 'application/pdf');
const blobUrl = URL.createObjectURL(blob);
this.downloadDocument(blobUrl);
} else {
console.error('Invalid base64 response.');
}
} else {
console.error('Download failed:', xhr.statusText);
}
};
xhr.onerror = () => {
console.error('An error occurred during the download:', xhr.statusText);
};
xhr.send(requestData);
};
}).catch((error) => {
console.error('Error saving Blob:', error);
});
}
}
public createBloburl(base64String: string, contentType: string):Blob{
const sliceSize = 512;
const byteCharacters = atob(base64String);
const byteArrays: Uint8Array[] = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = Array.from(slice, char => char.charCodeAt(0));
byteArrays.push(new Uint8Array(byteNumbers));
}
return new Blob(byteArrays, { type: contentType });
}
public downloadDocument(blobUrl: string):void{
const anchorElement = document.createElement('a');
anchorElement.href = blobUrl;
anchorElement.target = '_parent';
if(this.pdfviewerControl){
this.pdfviewerControl.fileName = this.fileName;
const downloadFileName = this.pdfviewerControl.fileName || 'downloadedFile.pdf';
anchorElement.download = downloadFileName.endsWith('.pdf') ? downloadFileName : `${downloadFileName}.pdf`;
document.body.appendChild(anchorElement);
anchorElement.click();
document.body.removeChild(anchorElement);
URL.revokeObjectURL(blobUrl);
}
}
//Method for moving to previous page
public previousClicked(e: ClickEventArgs): void {
if(this.pdfviewerControl){
this.pdfviewerControl.navigation.goToPreviousPage();
}
}
//Method for moving to next page
public nextClicked(e: ClickEventArgs): void {
if(this.pdfviewerControl)
this.pdfviewerControl.navigation.goToNextPage();
}
//to get current page
public pageChanged(e: PageChangeEventArgs): void {
if(this.pdfviewerControl)
(document.getElementById('e-pv-redact-sb-currentPage') as HTMLSpanElement).textContent = this.pdfviewerControl.currentPageNumber.toString() + ' ';
this.updatePageNavigation();
}
public onLoad(e: CreateArgs)
{
(document.getElementById('e-pv-redact-sb-drop-area-wrap') as HTMLDivElement).style.display="flex";
(document.getElementById('e-pv-redact-sb-appbar') as HTMLDivElement).style.display="block";
}
//while loading document
public documentLoaded(e: LoadEventArgs): void {
const fileUploadElement = document.getElementById('e-pv-redact-sb-fileUpload');
if(fileUploadElement && this.pdfviewerControl){
fileUploadElement.textContent = '/ ' + this.pdfviewerControl.pageCount;
(document.getElementById('e-pv-redact-sb-currentPage') as HTMLSpanElement).textContent = this.pdfviewerControl.currentPageNumber.toString() + ' ';
this.updatePageNavigation();
this.updateRedaction();
}
}
//Zoom values changes when the percentage is selected from the dropdown
public zoom: any;
public previousZoom: any;
public zoomValueChange(e: ChangeEventArgs) {
this.zoom = (e as any).value;
this.previousZoom = (e as any).previousItemData.value;
if (this.zoom !== null || this.previousZoom !== null) {
var zoomchange = parseInt(this.zoom.replace("%", ""), 10);
if(this.pdfviewerControl)
this.pdfviewerControl.magnificationModule.zoomTo(zoomchange);
}
}
//Updating the navigation button based on the page number either "enabled" or "disabled"
private updatePageNavigation(): void {
if(this.pdfviewerControl&& this.secondaryToolbar){
if (this.pdfviewerControl.currentPageNumber === 1) {
this.secondaryToolbar.items[0].disabled=true;
this.secondaryToolbar.items[2].disabled=false;
} else if (this.pdfviewerControl.currentPageNumber === this.pdfviewerControl.pageCount) {
this.secondaryToolbar.items[0].disabled=false;
this.secondaryToolbar.items[2].disabled=true;
} else {
this.secondaryToolbar.items[0].disabled=false;
this.secondaryToolbar.items[2].disabled=false;
}
}
}
//To redact the pdf in server side using the button click event
public redaction(): void {
if (this.redactionCount > 0) {
if(this.pdfviewerControl){
this.pdfviewerControl.saveAsBlob().then((blob) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = (e: ProgressEvent<FileReader>) => {
const base64String = e.target?.result;
const xhr = new XMLHttpRequest();
xhr.open('POST', this.url, true);
xhr.setRequestHeader('Content-type', 'application/json; charset=UTF-8');
const requestData = JSON.stringify({ base64String });
xhr.onload = () => {
if (xhr.status === 200) {
if(this.pdfviewerControl)
this.pdfviewerControl.load(xhr.responseText, "");
}
else {
console.error('Redaction failed:', xhr.statusText);
}
};
xhr.onerror = function () {
console.error('An error occurred during the redaction:', xhr.statusText);
};
xhr.send(requestData);
}
});
this.redactionCount = 0;
this.updateRedaction();
}
}
}
}
app.component.html
<style>
.control-section{
margin-top: 100px;
}
</style>
<div class="control-section" id="e-pv-redact-sb-panel">
<div class="content-wrapper">
<div class="flex-container">
</div>
<!--Appbar-->
<div class="row" [style.display]="'none'" id="e-pv-redact-sb-appbar">
<div class="col-md-12">
<ejs-appbar colorMode="Primary" (created)="onLoad($event)">
<span class="regular">Redaction</span>
<div class="e-appbar-spacer"></div>
<button #defaultButtonDownload ejs-button cssClass="e-inherit"
iconCss="e-icons e-download e-btn-icon e-icon-left" (click)="download($event)">Download</button>
</ejs-appbar>
</div>
</div>
<!--primary toolbar-->
<div class="e-sample-resize-container">
<!-- Render the Toolbar Component -->
<ejs-toolbar overflowMode='Popup' cssClass="template" id="e-pv-redact-sb-toolbar" #primaryToolbar>
<e-items>
<e-item prefixIcon="e-icon e-folder" tooltipText="Open" id="pdfviewer_open" text="Open"
(click)="openDocumentClicked($event)"></e-item>
<e-item type="Separator"></e-item>
<e-item prefixIcon="e-icon e-text-annotation" tooltipText="Text" cssClass="e-pv-redact-sb-font-container"
text="Text" (click)="addText($event)"></e-item>
<e-item prefixIcon="e-icons e-image" tooltipText="Image" cssClass="e-pv-redact-sb-image-container"
text="Image" id="targetButton" (click)="imageDialog()"></e-item>
<e-item prefixIcon="e-icons e-opacity" tooltipText="Pattern" cssClass="e-pv-redact-sb-pattern-container"
text="Pattern" (click)="addPattern($event)"></e-item>
<e-item prefixIcon="e-icons black-out" tooltipText="Black out" cssClass="e-pv-redact-sb-black-out-container"
text="Blackout" (click)="addBlackout($event)"></e-item>
<e-item prefixIcon="e-icons white-out" tooltipText="White out" cssClass="e-pv-redact-sb-white-out-container"
text="Whiteout" (click)="addWhiteout($event)"></e-item>
<e-item type="Separator"></e-item>
<e-item prefixIcon="e-icons e-redact" cssClass="e-pv-redact-sb-redaction-container" text="Redact"
id="redacticon" (click)="redaction()" [disabled]="true"></e-item>
</e-items>
</ejs-toolbar>
</div>
<!--secondary toolbar-->
<div id="e-pv-redact-sb-toolbar-secondary" class="e-pv-secondary-toolbar">
<ejs-toolbar #secondaryToolbar>
<e-items>
<e-item prefixIcon="e-icons e-chevron-left" cssClas="e-pv-redact-sb-previous-container" id="previousPage"
(click)="previousClicked($event)" [disabled]="true"></e-item>
<e-item>
<ng-template #template>
<div>
<span id="e-pv-redact-sb-currentPage">1 </span>
<span id="e-pv-redact-sb-totalPage">/ 1</span>
</div>
</ng-template>
</e-item>
<e-item prefixIcon="e-icon e-chevron-right" cssClass="e-pv-redact-sb-next-container" id="nextPage"
(click)="nextClicked($event)" [disabled]="true"></e-item>
<e-item type="Separator"></e-item>
<e-item cssClass="percentage">
<ng-template #template>
<ejs-combobox id='comboelement' [dataSource]='data' value="100%" [showClearButton]='false'
(change)="zoomValueChange($event)" width="90px">
</ejs-combobox>
</ng-template>
</e-item>
</e-items>
</ejs-toolbar>
</div>
<!--pdfviewer-->
<ejs-pdfviewer #pdfviewer id='pdfViewer' [documentPath]='document' [resourceUrl]='resource'
(pageChange)='pageChanged($event)' (documentLoad)='documentLoaded($event)'
(annotationAdd)='annotationAdd($event)' (annotationRemove)='annotationRemove($event)' [enableAnnotationToolbar] ="false" [enableCommentPanel] = "false" [enableToolbar]=false
[enableNavigationToolbar]=false (created)='onLoad($event)' style="height:640px; display: block">
</ejs-pdfviewer>
<input type="file" id="fileUpload" accept=".pdf" style="display:block;visibility:hidden;width:0;height:0;">
<!--dialog box-->
<div class="control-section template">
<ejs-dialog id="e-pv-redact-sb-dialog" #dialogComponent [height]='height' [showCloseIcon]='showCloseIcon' [target]='target'
[width]='width' [visible]='visible' [isModal]='isModal'>
<ng-template #footerTemplate>
<button id="cancelButton" #cancelButton class="e-control e-btn e-primary" data-ripple="true"
(click)="closeDialog($event)">Cancel</button>
</ng-template>
<ng-template #header>
<div id="dlg-template" title="upload" class="e-icon-settings"> Upload Image </div>
</ng-template>
<div class="drop-area-wrap" id="e-pv-redact-sb-drop-area-wrap" [style.display]="'none'">
<div id='e-pv-redact-sb-defaultfileupload'>
<div class="control_wrapper">
<ejs-uploader #defaultupload id='defaultfileupload' [asyncSettings]='path' [dropArea]='dropElement'
(change)='onFileChange($event)' [allowedExtensions]='allowedExtensions' ></ejs-uploader>
<span class="e-file-drop">Or drop files here</span>
<div>(Only JPG and PNG images will be accepted)</div>
</div>
</div>
</div>
<div class="e-pv-redact-sb-image-list">
<div id='imageContainer' class="e-pv-redact-sb-image-container">
<img id='imageView' class="e-pv-redact-sb-image-source" style="display:none"/>
</div>
</div>
</ejs-dialog>
</div>
</div>
</div>
Server-Side
PdfViewerController.cs
public IActionResult Redaction([FromBody] Dictionary<string, string> jsonObject)
{
string RedactionText = "Redacted";
var finalbase64 = string.Empty;
PdfRenderer pdfviewer = new PdfRenderer(_cache);
string documentBase = pdfviewer.GetDocumentAsBase64(jsonObject);
string base64String = documentBase.Split(new string[] { "data:application/pdf;base64," }, StringSplitOptions.None)[1];
if (base64String != null || base64String != string.Empty)
{
byte[] byteArray = Convert.FromBase64String(base64String);
Console.WriteLine("redaction");
PdfLoadedDocument loadedDocument = new PdfLoadedDocument(byteArray);
foreach (PdfLoadedPage loadedPage in loadedDocument.Pages)
{
List<PdfLoadedAnnotation> removeItems = new List<PdfLoadedAnnotation>();
foreach (PdfLoadedAnnotation annotation in loadedPage.Annotations)
{
if (annotation is PdfLoadedRectangleAnnotation)
{
if (annotation.Author == "Redaction")
{
// Add the annotation to the removeItems list
removeItems.Add(annotation);
// Create a new redaction with the annotation bounds and color
PdfRedaction redaction = new PdfRedaction(annotation.Bounds, annotation.Color);
// Add the redaction to the page
loadedPage.AddRedaction(redaction);
annotation.Flatten = true;
}
if (annotation.Author == "Text")
{
// Add the annotation to the removeItems list
removeItems.Add(annotation);
// Create a new redaction with the annotation bounds and color
PdfRedaction redaction = new PdfRedaction(annotation.Bounds);
//Set the font family and font size
PdfStandardFont font = new PdfStandardFont(PdfFontFamily.Courier, 8);
//Create the appearance like repeated text in the redaction area
CreateRedactionAppearance(redaction.Appearance.Graphics, PdfTextAlignment.Left, true, new SizeF(annotation.Bounds.Width, annotation.Bounds.Height), RedactionText, font, PdfBrushes.Red);
// Add the redaction to the page
loadedPage.AddRedaction(redaction);
annotation.Flatten = true;
}
//Apply the pattern for the Redaction
if (annotation.Author == "Pattern")
{
// Add the annotation to the removeItems list
removeItems.Add(annotation);
// Create a new redaction with the annotation bounds and color
PdfRedaction redaction = new PdfRedaction(annotation.Bounds);
Syncfusion.Drawing.RectangleF rect = new Syncfusion.Drawing.RectangleF(0, 0, 8, 8);
PdfTilingBrush tillingBrush = new PdfTilingBrush(rect);
tillingBrush.Graphics.DrawRectangle(PdfBrushes.Gray, new Syncfusion.Drawing.RectangleF(0, 0, 2, 2));
tillingBrush.Graphics.DrawRectangle(PdfBrushes.White, new Syncfusion.Drawing.RectangleF(2, 0, 2, 2));
tillingBrush.Graphics.DrawRectangle(PdfBrushes.LightGray, new Syncfusion.Drawing.RectangleF(4, 0, 2, 2));
tillingBrush.Graphics.DrawRectangle(PdfBrushes.DarkGray, new Syncfusion.Drawing.RectangleF(6, 0, 2, 2));
tillingBrush.Graphics.DrawRectangle(PdfBrushes.White, new Syncfusion.Drawing.RectangleF(0, 2, 2, 2));
tillingBrush.Graphics.DrawRectangle(PdfBrushes.LightGray, new Syncfusion.Drawing.RectangleF(2, 2, 2, 2));
tillingBrush.Graphics.DrawRectangle(PdfBrushes.Black, new Syncfusion.Drawing.RectangleF(4, 2, 2, 2));
tillingBrush.Graphics.DrawRectangle(PdfBrushes.LightGray, new Syncfusion.Drawing.RectangleF(6, 2, 2, 2));
tillingBrush.Graphics.DrawRectangle(PdfBrushes.LightGray, new Syncfusion.Drawing.RectangleF(0, 4, 2, 2));
tillingBrush.Graphics.DrawRectangle(PdfBrushes.DarkGray, new Syncfusion.Drawing.RectangleF(2, 4, 2, 2));
tillingBrush.Graphics.DrawRectangle(PdfBrushes.LightGray, new Syncfusion.Drawing.RectangleF(4, 4, 2, 2));
tillingBrush.Graphics.DrawRectangle(PdfBrushes.White, new Syncfusion.Drawing.RectangleF(6, 4, 2, 2));
tillingBrush.Graphics.DrawRectangle(PdfBrushes.Black, new Syncfusion.Drawing.RectangleF(0, 6, 2, 2));
tillingBrush.Graphics.DrawRectangle(PdfBrushes.LightGray, new Syncfusion.Drawing.RectangleF(2, 6, 2, 2));
tillingBrush.Graphics.DrawRectangle(PdfBrushes.Black, new Syncfusion.Drawing.RectangleF(4, 6, 2, 2));
tillingBrush.Graphics.DrawRectangle(PdfBrushes.DarkGray, new Syncfusion.Drawing.RectangleF(6, 6, 2, 2));
rect = new Syncfusion.Drawing.RectangleF(0, 0, 16, 14);
PdfTilingBrush tillingBrushNew = new PdfTilingBrush(rect);
tillingBrushNew.Graphics.DrawRectangle(tillingBrush, rect);
//Set the pattern for the redaction area
redaction.Appearance.Graphics.DrawRectangle(tillingBrushNew, new Syncfusion.Drawing.RectangleF(0, 0, annotation.Bounds.Width, annotation.Bounds.Height));
// Add the redaction to the page
loadedPage.AddRedaction(redaction);
annotation.Flatten = true;
}
}
else if (annotation is PdfLoadedRubberStampAnnotation)
{
if (annotation.Author == "Image")
{
Stream[] images = PdfLoadedRubberStampAnnotationExtension.GetImages(annotation as PdfLoadedRubberStampAnnotation);
// Create a new redaction with the annotation bounds and color
PdfRedaction redaction = new PdfRedaction(annotation.Bounds);
images[0].Position = 0;
PdfImage image = new PdfBitmap(images[0]);
//Apply the image to redaction area
redaction.Appearance.Graphics.DrawImage(image, new Syncfusion.Drawing.RectangleF(0, 0, annotation.Bounds.Width, annotation.Bounds.Height));
// Add the redaction to the page
loadedPage.AddRedaction(redaction);
annotation.Flatten = true;
}
}
}
foreach (PdfLoadedAnnotation annotation1 in removeItems)
{
loadedPage.Annotations.Remove(annotation1);
}
}
loadedDocument.Redact();
MemoryStream stream = new MemoryStream();
loadedDocument.Save(stream);
stream.Position = 0;
loadedDocument.Close(true);
byteArray = stream.ToArray();
finalbase64 = "data:application/pdf;base64," + Convert.ToBase64String(byteArray);
stream.Dispose();
return Content(finalbase64);
//}
}
return Content("data:application/pdf;base64," + "");
}
private static void CreateRedactionAppearance(PdfGraphics graphics, PdfTextAlignment alignment, bool repeat, SizeF size, string overlayText, PdfFont font, PdfBrush textcolor)
{
float col = 0, row;
if (font == null) font = new PdfStandardFont(PdfFontFamily.Helvetica, 10);
int textAlignment = Convert.ToInt32(alignment);
float y = 0, x = 0, diff = 0;
Syncfusion.Drawing.RectangleF rect;
Syncfusion.Drawing.SizeF textsize = font.MeasureString(overlayText);
if (repeat)
{
col = size.Width / textsize.Width;
row = (float)Math.Floor(size.Height / font.Size);
diff = Math.Abs(size.Width - (float)(Math.Floor(col) * textsize.Width));
if (textAlignment == 1)
x = diff / 2;
if (textAlignment == 2)
x = diff;
for (int i = 1; i < col; i++)
{
for (int j = 0; j < row; j++)
{
rect = new Syncfusion.Drawing.RectangleF(x, y, 0, 0);
graphics.DrawString(overlayText, font, textcolor, rect);
y = y + font.Size;
}
x = x + textsize.Width;
y = 0;
}
}
else
{
diff = Math.Abs(size.Width - textsize.Width);
if (textAlignment == 1)
{
x = diff / 2;
}
if (textAlignment == 2)
{
x = diff;
}
rect = new Syncfusion.Drawing.RectangleF(x, 0, 0, 0);
graphics.DrawString(overlayText, font, textcolor, rect);
}
}
Key Features:
- Flexible Redaction Options: Redactions can be applied to text, patterns, or images, offering flexibility in handling different types of sensitive content.
- Permanent Content Removal: Once redactions are applied, they are permanent, ensuring the sensitive content is irretrievably removed.
- Custom Redaction Appearance: The appearance of the redacted areas can be customized, including adding text, patterns, or images, and adjusting font, size, and color.
Sample Link:
You can find the full sample in our GitHub repository.
Conclusion:
I hope you found this article implement PDF redaction functionality in your Angular application using the Syncfusion PDF Viewer and C# for backend processing. The Angular Client captures the redaction details, sends them to the backend, and updates the viewer with the redacted PDF once the server processes the file.
You can refer to Angular PDF feature tour page to know about its other groundbreaking feature representations and documentation, and how to quickly get started for configuration specifications. You can also explore our Angular PDF Viewer Example to understand how to create and manipulate data.
For current customers, you can check out our components from the License and Downloads page. If you are new to Syncfusion, you can try our 30-day free trial to check out our other controls.
If you have any queries or require clarifications, please let us know in the comments section below. You can also contact us through our support forums, Direct-Trac, or feedback portal. We are always happy to assist you!