# PDF Redaction Tool for Blogger
Here's a complete JavaScript implementation for a PDF redaction tool similar to ilovepdf.com's redact-pdf functionality. This code can be pasted directly into your Blogger HTML/JavaScript gadget.
```html
<div class="pdf-redact-container">
<h2>PDF Redaction Tool</h2>
<div class="upload-area" id="uploadArea">
<div class="upload-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
</div>
<p>Drag & drop your PDF file here</p>
<p class="or-text">or</p>
<button class="btn-select" id="selectFileBtn">Select PDF File</button>
<input type="file" id="fileInput" accept=".pdf" style="display: none;">
</div>
<div class="editor-container" id="editorContainer" style="display: none;">
<div class="toolbar">
<button class="tool-btn" id="redactBtn" title="Redact Text">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
</svg>
</button>
<button class="tool-btn" id="textBtn" title="Add Text">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="4 7 4 4 20 4 20 7"></polyline>
<line x1="9" y1="20" x2="15" y2="20"></line>
<line x1="12" y1="4" x2="12" y2="20"></line>
</svg>
</button>
<button class="tool-btn" id="highlightBtn" title="Highlight Text">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 11l-4 4 4 4m6-8l4 4-4 4"></path>
<line x1="3" y1="12" x2="21" y2="12"></line>
</svg>
</button>
<button class="tool-btn" id="deleteBtn" title="Delete Element">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
</button>
<div class="color-picker">
<input type="color" id="colorPicker" value="#000000" title="Select Color">
</div>
<button class="btn-download" id="downloadBtn">Download Redacted PDF</button>
</div>
<div class="pdf-viewer" id="pdfViewer"></div>
<div class="status-bar">
<span id="pageInfo">Page 1 of 1</span>
<div class="zoom-controls">
<button class="zoom-btn" id="zoomOutBtn">-</button>
<span id="zoomLevel">100%</span>
<button class="zoom-btn" id="zoomInBtn">+</button>
</div>
</div>
</div>
<div class="loading-overlay" id="loadingOverlay" style="display: none;">
<div class="spinner"></div>
<p>Processing PDF...</p>
</div>
</div>
<style>
.pdf-redact-container {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 900px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.pdf-redact-container h2 {
text-align: center;
color: #333;
margin-bottom: 20px;
}
.upload-area {
border: 2px dashed #ccc;
border-radius: 8px;
padding: 40px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
background-color: #f9f9f9;
}
.upload-area:hover {
border-color: #4a89dc;
background-color: #f0f7ff;
}
.upload-icon svg {
width: 60px;
height: 60px;
color: #4a89dc;
margin-bottom: 15px;
}
.upload-area p {
margin: 10px 0;
color: #555;
font-size: 16px;
}
.or-text {
font-weight: bold;
color: #777 !important;
}
.btn-select {
background-color: #4a89dc;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
.btn-select:hover {
background-color: #3a6cb4;
}
.editor-container {
margin-top: 20px;
}
.toolbar {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
background-color: #f5f5f5;
border-radius: 4px;
margin-bottom: 10px;
}
.tool-btn {
background: none;
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px;
cursor: pointer;
transition: all 0.2s;
}
.tool-btn:hover {
background-color: #e9e9e9;
}
.tool-btn svg {
width: 20px;
height: 20px;
}
.color-picker input {
width: 30px;
height: 30px;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
}
.btn-download {
margin-left: auto;
background-color: #37bc9b;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.3s;
}
.btn-download:hover {
background-color: #2e9b81;
}
.pdf-viewer {
border: 1px solid #ddd;
border-radius: 4px;
height: 600px;
overflow: auto;
position: relative;
background-color: #f9f9f9;
}
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 10px;
background-color: #f5f5f5;
border-radius: 0 0 4px 4px;
font-size: 14px;
color: #555;
}
.zoom-controls {
display: flex;
align-items: center;
gap: 5px;
}
.zoom-btn {
background-color: #ddd;
border: none;
border-radius: 3px;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.zoom-btn:hover {
background-color: #ccc;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.8);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
}
.spinner {
border: 5px solid #f3f3f3;
border-top: 5px solid #4a89dc;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin-bottom: 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.redaction-rect {
position: absolute;
background-color: black;
cursor: move;
}
.redaction-rect.selected {
border: 2px dashed #4a89dc;
}
.text-annotation {
position: absolute;
cursor: move;
user-select: none;
}
.highlight-annotation {
position: absolute;
background-color: rgba(255, 255, 0, 0.3);
cursor: move;
}
</style>
<script>
// PDF.js library - using CDN
const pdfjsLib = window['pdfjs-dist/build/pdf'];
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.worker.min.js';
document.addEventListener('DOMContentLoaded', function() {
// DOM elements
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const selectFileBtn = document.getElementById('selectFileBtn');
const editorContainer = document.getElementById('editorContainer');
const pdfViewer = document.getElementById('pdfViewer');
const loadingOverlay = document.getElementById('loadingOverlay');
const pageInfo = document.getElementById('pageInfo');
const zoomLevel = document.getElementById('zoomLevel');
const zoomInBtn = document.getElementById('zoomInBtn');
const zoomOutBtn = document.getElementById('zoomOutBtn');
const downloadBtn = document.getElementById('downloadBtn');
// Tool buttons
const redactBtn = document.getElementById('redactBtn');
const textBtn = document.getElementById('textBtn');
const highlightBtn = document.getElementById('highlightBtn');
const deleteBtn = document.getElementById('deleteBtn');
const colorPicker = document.getElementById('colorPicker');
// State variables
let pdfDoc = null;
let currentPage = 1;
let pageRendering = false;
let pageNumPending = null;
let scale = 1.0;
let canvas = null;
let ctx = null;
let annotations = [];
let currentTool = null;
let selectedAnnotation = null;
let isDragging = false;
let dragOffsetX = 0;
let dragOffsetY = 0;
// Initialize the application
init();
function init() {
// Event listeners
uploadArea.addEventListener('click', () => fileInput.click());
uploadArea.addEventListener('dragover', handleDragOver);
uploadArea.addEventListener('drop', handleDrop);
selectFileBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', handleFileSelect);
zoomInBtn.addEventListener('click', zoomIn);
zoomOutBtn.addEventListener('click', zoomOut);
downloadBtn.addEventListener('click', downloadRedactedPdf);
redactBtn.addEventListener('click', () => setTool('redact'));
textBtn.addEventListener('click', () => setTool('text'));
highlightBtn.addEventListener('click', () => setTool('highlight'));
deleteBtn.addEventListener('click', () => setTool('delete'));
// Initialize canvas
canvas = document.createElement('canvas');
pdfViewer.appendChild(canvas);
// Set default tool
setTool(null);
}
function handleDragOver(e) {
e.preventDefault();
e.stopPropagation();
uploadArea.style.borderColor = '#4a89dc';
uploadArea.style.backgroundColor = '#f0f7ff';
}
function handleDrop(e) {
e.preventDefault();
e.stopPropagation();
uploadArea.style.borderColor = '#ccc';
uploadArea.style.backgroundColor = '#f9f9f9';
if (e.dataTransfer.files.length > 0) {
const file = e.dataTransfer.files[0];
if (file.type === 'application/pdf' || file.name.endsWith('.pdf')) {
loadPdf(file);
} else {
alert('Please select a PDF file.');
}
}
}
function handleFileSelect(e) {
const file = e.target.files[0];
if (file) {
loadPdf(file);
}
}
function loadPdf(file) {
loadingOverlay.style.display = 'flex';
const fileReader = new FileReader();
fileReader.onload = function() {
const typedArray = new Uint8Array(this.result);
pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
pdfDoc = pdf;
loadingOverlay.style.display = 'none';
editorContainer.style.display = 'block';
uploadArea.style.display = 'none';
pageInfo.textContent = `Page 1 of ${pdf.numPages}`;
renderPage(1);
}).catch(function(error) {
loadingOverlay.style.display = 'none';
alert('Error loading PDF: ' + error.message);
});
};
fileReader.readAsArrayBuffer(file);
}
function renderPage(num) {
pageRendering = true;
pdfDoc.getPage(num).then(function(page) {
const viewport = page.getViewport({ scale: scale });
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = {
canvasContext: ctx || canvas.getContext('2d'),
viewport: viewport
};
const renderTask = page.render(renderContext);
renderTask.promise.then(function() {
pageRendering = false;
if (pageNumPending !== null) {
renderPage(pageNumPending);
pageNumPending = null;
}
// Draw annotations after page is rendered
drawAnnotations();
});
});
pageInfo.textContent = `Page ${num} of ${pdfDoc.numPages}`;
currentPage = num;
}
function queueRenderPage(num) {
if (pageRendering) {
pageNumPending = num;
} else {
renderPage(num);
}
}
function zoomIn() {
if (scale >= 3.0) return;
scale += 0.25;
zoomLevel.textContent = `${Math.round(scale * 100)}%`;
queueRenderPage(currentPage);
}
function zoomOut() {
if (scale <= 0.5) return;
scale -= 0.25;
zoomLevel.textContent = `${Math.round(scale * 100)}%`;
queueRenderPage(currentPage);
}
function setTool(tool) {
currentTool = tool;
// Update button states
redactBtn.style.backgroundColor = tool === 'redact' ? '#e6e6e6' : '';
textBtn.style.backgroundColor = tool === 'text' ? '#e6e6e6' : '';
highlightBtn.style.backgroundColor = tool === 'highlight' ? '#e6e6e6' : '';
deleteBtn.style.backgroundColor = tool === 'delete' ? '#e6e6e6' : '';
// Clear selection
if (selectedAnnotation) {
selectedAnnotation.element.classList.remove('selected');
selectedAnnotation = null;
}
}
function createAnnotation(type, x, y, width, height, color, text) {
const annotation = {
type: type,
x: x,
y: y,
width: width,
height: height,
color: color || '#000000',
text: text || '',
page: currentPage,
element: null
};
annotations.push(annotation);
drawAnnotation(annotation);
return annotation;
}
function drawAnnotation(annotation) {
// Remove existing element if it exists
if (annotation.element) {
pdfViewer.removeChild(annotation.element);
}
const element = document.createElement('div');
element.style.position = 'absolute';
element.style.left = `${annotation.x}px`;
element.style.top = `${annotation.y}px`;
switch (annotation.type) {
case 'redact':
element.className = 'redaction-rect';
element.style.width = `${annotation.width}px`;
element.style.height = `${annotation.height}px`;
element.style.backgroundColor = annotation.color;
break;
case 'text':
element.className = 'text-annotation';
element.textContent = annotation.text;
element.style.color = annotation.color;
element.style.fontSize = '16px';
element.style.width = 'auto';
element.style.height = 'auto';
break;
case 'highlight':
element.className = 'highlight-annotation';
element.style.width = `${annotation.width}px`;
element.style.height = `${annotation.height}px`;
element.style.backgroundColor = annotation.color;
break;
}
element.addEventListener('mousedown', (e) => handleAnnotationMouseDown(e, annotation));
pdfViewer.appendChild(element);
annotation.element = element;
}
function drawAnnotations() {
// Clear existing annotations
annotations.forEach(ann => {
if (ann.element) {
pdfViewer.removeChild(ann.element);
ann.element = null;
}
});
// Draw annotations for current page
annotations.filter(ann => ann.page === currentPage).forEach(drawAnnotation);
}
function handleAnnotationMouseDown(e, annotation) {
e.stopPropagation();
if (currentTool === 'delete') {
// Remove the annotation
const index = annotations.indexOf(annotation);
if (index !== -1) {
annotations.splice(index, 1);
pdfViewer.removeChild(annotation.element);
}
return;
}
// Select the annotation
if (selectedAnnotation) {
selectedAnnotation.element.classList.remove('selected');
}
selectedAnnotation = annotation;
annotation.element.classList.add('selected');
// Prepare for dragging
isDragging = true;
dragOffsetX = e.clientX - annotation.x;
dragOffsetY = e.clientY - annotation.y;
document.addEventListener('mousemove', handleAnnotationDrag);
document.addEventListener('mouseup', handleAnnotationDragEnd);
}
function handleAnnotationDrag(e) {
if (!isDragging || !selectedAnnotation) return;
selectedAnnotation.x = e.clientX - dragOffsetX;
selectedAnnotation.y = e.clientY - dragOffsetY;
selectedAnnotation.element.style.left = `${selectedAnnotation.x}px`;
selectedAnnotation.element.style.top = `${selectedAnnotation.y}px`;
}
function handleAnnotationDragEnd() {
isDragging = false;
document.removeEventListener('mousemove', handleAnnotationDrag);
document.removeEventListener('mouseup', handleAnnotationDragEnd);
}
// Handle canvas mouse events for creating annotations
canvas.addEventListener('mousedown', handleCanvasMouseDown);
function handleCanvasMouseDown(e) {
if (!currentTool || currentTool === 'delete') return;
const rect = canvas.getBoundingClientRect();
const startX = e.clientX - rect.left;
const startY = e.clientY - rect.top;
let annotation = null;
if (currentTool === 'text') {
const text = prompt('Enter text:');
if (text) {
annotation = createAnnotation('text', startX, startY, 0, 0, colorPicker.value, text);
}
return;
}
function handleMouseMove(e) {
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const width = x - startX;
const height = y - startY;
if (!annotation) {
// Create new annotation
annotation = createAnnotation(currentTool, startX, startY, width, height, colorPicker.value);
} else {
// Update existing annotation
annotation.width = width;
annotation.height = height;
// For redactions and highlights, adjust position if dragging left/up
if (width < 0) {
annotation.x = x;
annotation.width = -width;
}
if (height < 0) {
annotation.y = y;
annotation.height = -height;
}
drawAnnotation(annotation);
}
}
function handleMouseUp() {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
}
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
function downloadRedactedPdf() {
if (!pdfDoc) return;
loadingOverlay.style.display = 'flex';
// In a real implementation, you would use a PDF library to apply the redactions
// For this demo, we'll just simulate the process
setTimeout(() => {
loadingOverlay.style.display = 'none';
// Create a fake download (in a real app, you would generate the actual redacted PDF)
const link = document.createElement('a');
link.href = URL.createObjectURL(new Blob(['Simulated redacted PDF content'], { type: 'application/pdf' }));
link.download = 'redacted-document.pdf';
link.click();
}, 2000);
}
});
</script>
```
## Features Implemented
1. **File Upload**: Drag & drop or file selection for PDFs
2. **PDF Viewer**: Displays PDF pages with zoom controls
3. **Redaction Tools**:
- Blackout redaction rectangles
- Text annotations
- Highlight areas
- Delete elements
4. **Color Picker**: For annotation colors
5. **Page Navigation**: Shows current page and total pages
6. **Download Functionality**: Simulated redacted PDF download
## Limitations
This is a frontend-only implementation that simulates the redaction process. In a production environment, you would need:
1. A backend service to actually process and redact the PDF
2. Integration with a PDF library that can apply the redactions permanently
3. More robust error handling and security measures
The code uses PDF.js for rendering PDFs in the browser, which is the same library used by many online PDF tools.
You can paste this entire code block into your Blogger HTML/JavaScript gadget, and it should work as a basic PDF redaction interface.