PDF Editor Pro
Properties
Select an element to edit its properties
×
Add Signature
×
Document Security
:root {
--primary-color: #4285f4;
--secondary-color: #34a853;
--danger-color: #ea4335;
--warning-color: #fbbc05;
--dark-color: #202124;
--light-color: #f8f9fa;
--sidebar-width: 250px;
--properties-width: 300px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #f5f5f5;
color: var(--dark-color);
overflow-x: hidden;
}
.app-container {
display: flex;
flex-direction: column;
height: 100vh;
width: 100vw;
}
.app-header {
background-color: var(--primary-color);
color: white;
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.app-header h1 {
font-size: 1.5rem;
font-weight: 500;
}
.toolbar {
display: flex;
gap: 10px;
}
.toolbar button {
background-color: rgba(255, 255, 255, 0.2);
border: none;
color: white;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
transition: background-color 0.3s;
}
.toolbar button:hover {
background-color: rgba(255, 255, 255, 0.3);
}
.dropdown {
position: relative;
display: inline-block;
}
.dropbtn {
background-color: rgba(255, 255, 255, 0.2);
border: none;
color: white;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
}
.dropdown-content {
display: none;
position: absolute;
background-color: white;
min-width: 200px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
z-index: 1;
border-radius: 4px;
overflow: hidden;
}
.dropdown-content a {
color: var(--dark-color);
padding: 10px 15px;
text-decoration: none;
display: flex;
align-items: center;
gap: 10px;
transition: background-color 0.2s;
}
.dropdown-content a:hover {
background-color: #f1f1f1;
}
.dropdown:hover .dropdown-content {
display: block;
}
.main-content {
display: flex;
flex: 1;
overflow: hidden;
}
.sidebar {
width: var(--sidebar-width);
background-color: white;
border-right: 1px solid #e0e0e0;
overflow-y: auto;
padding: 10px;
}
.page-thumbnails {
display: flex;
flex-direction: column;
gap: 10px;
}
.page-thumbnail {
border: 1px solid #e0e0e0;
padding: 5px;
cursor: pointer;
transition: border-color 0.2s;
}
.page-thumbnail:hover {
border-color: var(--primary-color);
}
.page-thumbnail.active {
border-color: var(--primary-color);
background-color: rgba(66, 133, 244, 0.1);
}
.page-thumbnail img {
width: 100%;
height: auto;
display: block;
}
.pdf-viewer-container {
flex: 1;
overflow: auto;
position: relative;
background-color: #525659;
display: flex;
justify-content: center;
align-items: center;
}
#pdf-canvas {
max-width: 100%;
max-height: 100%;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
.pdf-annotations {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.annotation {
position: absolute;
pointer-events: auto;
}
.annotation-text {
background-color: rgba(255, 255, 255, 0.9);
padding: 2px 5px;
border-radius: 3px;
border: 1px solid #ccc;
}
.annotation-highlight {
background-color: rgba(255, 255, 0, 0.3);
}
.annotation-comment {
background-color: rgba(255, 255, 255, 0.9);
border: 1px solid #ccc;
border-radius: 4px;
padding: 5px;
width: 200px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.annotation-comment::after {
content: '';
position: absolute;
bottom: -10px;
left: 10px;
border-width: 10px 10px 0;
border-style: solid;
border-color: #fff transparent transparent;
}
.annotation-stamp {
opacity: 0.8;
}
.properties-panel {
width: var(--properties-width);
background-color: white;
border-left: 1px solid #e0e0e0;
overflow-y: auto;
padding: 15px;
}
.properties-panel h3 {
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #e0e0e0;
}
.modal {
display: none;
position: fixed;
z-index: 100;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
overflow: auto;
}
.modal-content {
background-color: white;
margin: 5% auto;
padding: 20px;
border-radius: 5px;
width: 80%;
max-width: 600px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
position: relative;
}
.close {
position: absolute;
right: 20px;
top: 15px;
font-size: 24px;
font-weight: bold;
cursor: pointer;
color: #aaa;
}
.close:hover {
color: var(--dark-color);
}
#text-content {
width: 100%;
height: 150px;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 4px;
resize: vertical;
}
.text-properties {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 15px;
}
.text-properties label {
display: flex;
flex-direction: column;
gap: 5px;
}
.text-properties select, .text-properties input {
padding: 5px;
border: 1px solid #ddd;
border-radius: 4px;
}
#image-preview {
margin: 15px 0;
text-align: center;
}
#image-preview img {
max-width: 100%;
max-height: 300px;
border: 1px dashed #ccc;
padding: 5px;
}
#signature-pad {
border: 1px solid #ddd;
border-radius: 4px;
margin: 15px 0;
background-color: white;
}
.signature-actions {
display: flex;
justify-content: space-between;
}
.security-options {
display: flex;
flex-direction: column;
gap: 15px;
margin: 20px 0;
}
.security-options label {
display: flex;
align-items: center;
gap: 10px;
}
.loading-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
z-index: 1000;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
}
.loading-spinner {
border: 5px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top: 5px solid white;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin-bottom: 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Responsive styles */
@media (max-width: 1200px) {
.properties-panel {
display: none;
}
}
@media (max-width: 768px) {
.sidebar {
display: none;
}
.toolbar {
flex-wrap: wrap;
justify-content: center;
}
.modal-content {
width: 95%;
margin: 10% auto;
}
}
// PDF.js worker path
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.worker.min.js';
// Global variables
let pdfDoc = null;
let currentPage = 1;
let scale = 1.5;
let pdfLibDoc = null;
let annotations = [];
let selectedAnnotation = null;
let isDrawing = false;
let startX, startY;
let currentTool = null;
let signatures = [];
let stamps = [];
// DOM elements
const canvas = document.getElementById('pdf-canvas');
const ctx = canvas.getContext('2d');
const pageThumbnails = document.getElementById('page-thumbnails');
const pdfAnnotations = document.getElementById('pdf-annotations');
const fileInput = document.getElementById('file-input');
const propertiesContent = document.getElementById('properties-content');
// Modals
const textModal = document.getElementById('text-modal');
const imageModal = document.getElementById('image-modal');
const signatureModal = document.getElementById('signature-modal');
const securityModal = document.getElementById('security-modal');
// Buttons
document.getElementById('new-pdf').addEventListener('click', createNewPDF);
document.getElementById('open-pdf').addEventListener('click', () => fileInput.click());
document.getElementById('save-pdf').addEventListener('click', savePDF);
document.getElementById('export-pdf').addEventListener('click', exportPDF);
document.getElementById('cloud-save').addEventListener('click', saveToCloud);
document.getElementById('security').addEventListener('click', () => securityModal.style.display = 'block');
// Tool buttons
document.getElementById('add-text').addEventListener('click', () => {
currentTool = 'text';
textModal.style.display = 'block';
});
document.getElementById('add-image').addEventListener('click', () => {
currentTool = 'image';
imageModal.style.display = 'block';
});
document.getElementById('highlight').addEventListener('click', () => currentTool = 'highlight');
document.getElementById('add-comment').addEventListener('click', () => currentTool = 'comment');
document.getElementById('add-signature').addEventListener('click', () => {
currentTool = 'signature';
signatureModal.style.display = 'block';
initSignaturePad();
});
document.getElementById('add-stamp').addEventListener('click', () => currentTool = 'stamp');
document.getElementById('manage-pages').addEventListener('click', managePages);
// Modal close buttons
document.querySelectorAll('.close').forEach(btn => {
btn.addEventListener('click', function() {
this.closest('.modal').style.display = 'none';
});
});
// Window click to close modals
window.addEventListener('click', (e) => {
if (e.target.classList.contains('modal')) {
e.target.style.display = 'none';
}
});
// Text modal
document.getElementById('insert-text').addEventListener('click', insertText);
// Image modal
document.getElementById('image-input').addEventListener('change', previewImage);
document.getElementById('insert-image').addEventListener('click', insertImage);
// Signature modal
document.getElementById('clear-signature').addEventListener('click', clearSignature);
document.getElementById('save-signature').addEventListener('click', saveSignature);
// Security modal
document.getElementById('password-protect').addEventListener('change', function() {
document.getElementById('password-fields').style.display = this.checked ? 'block' : 'none';
});
document.getElementById('apply-security').addEventListener('click', applySecurity);
// File input handler
fileInput.addEventListener('change', async (e) => {
if (e.target.files.length === 0) return;
showLoading();
const file = e.target.files[0];
try {
// Load with PDF.js for rendering
const arrayBuffer = await file.arrayBuffer();
pdfDoc = await pdfjsLib.getDocument(arrayBuffer).promise;
// Load with PDF-Lib for editing
pdfLibDoc = await PDFLib.PDFDocument.load(arrayBuffer);
renderPage();
generateThumbnails();
} catch (err) {
console.error('Error loading PDF:', err);
alert('Error loading PDF. Please try another file.');
} finally {
hideLoading();
}
});
// Render PDF page
async function renderPage() {
if (!pdfDoc) return;
showLoading();
try {
const page = await pdfDoc.getPage(currentPage);
const viewport = page.getViewport({ scale });
canvas.height = viewport.height;
canvas.width = viewport.width;
await page.render({
canvasContext: ctx,
viewport
}).promise;
renderAnnotations();
} catch (err) {
console.error('Error rendering page:', err);
} finally {
hideLoading();
}
}
// Generate page thumbnails
async function generateThumbnails() {
if (!pdfDoc) return;
pageThumbnails.innerHTML = '';
for (let i = 1; i <= pdfDoc.numPages; i++) {
const thumbnail = document.createElement('div');
thumbnail.className = 'page-thumbnail' + (i === currentPage ? ' active' : '');
thumbnail.innerHTML = `
Page ${i}
`;
thumbnail.addEventListener('click', () => {
currentPage = i;
renderPage();
document.querySelectorAll('.page-thumbnail').forEach(el => el.classList.remove('active'));
thumbnail.classList.add('active');
});
// Generate thumbnail image (simplified - in production you'd render actual thumbnails)
pageThumbnails.appendChild(thumbnail);
}
}
// Create new PDF
async function createNewPDF() {
showLoading();
try {
pdfLibDoc = await PDFLib.PDFDocument.create();
const page = pdfLibDoc.addPage([550, 750]);
// Convert to array buffer for PDF.js
const pdfBytes = await pdfLibDoc.save();
pdfDoc = await pdfjsLib.getDocument(pdfBytes).promise;
currentPage = 1;
annotations = [];
renderPage();
generateThumbnails();
} catch (err) {
console.error('Error creating new PDF:', err);
} finally {
hideLoading();
}
}
// Save PDF
async function savePDF() {
if (!pdfLibDoc) return;
showLoading();
try {
// Update PDF-Lib document with annotations
await updatePdfWithAnnotations();
const pdfBytes = await pdfLibDoc.save();
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
saveAs(blob, 'edited-document.pdf');
} catch (err) {
console.error('Error saving PDF:', err);
alert('Error saving PDF. Please try again.');
} finally {
hideLoading();
}
}
// Export PDF in different formats
async function exportPDF() {
if (!pdfLibDoc) return;
// In a real app, you would implement export to Word, images, etc.
// This is simplified to just save as PDF
savePDF();
}
// Save to cloud
async function saveToCloud() {
if (!pdfLibDoc) return;
showLoading();
try {
await updatePdfWithAnnotations();
const pdfBytes = await pdfLibDoc.save();
// In a real app, you would integrate with Google Drive, Dropbox, etc.
// This is just a simulation
setTimeout(() => {
hideLoading();
alert('PDF saved to cloud successfully!');
}, 1500);
} catch (err) {
console.error('Error saving to cloud:', err);
alert('Error saving to cloud. Please try again.');
hideLoading();
}
}
// Update PDF-Lib document with annotations
async function updatePdfWithAnnotations() {
if (!pdfLibDoc || annotations.length === 0) return;
const pages = pdfLibDoc.getPages();
const currentPdfPage = pages[currentPage - 1];
// Process annotations for current page
const pageAnnotations = annotations.filter(ann => ann.page === currentPage);
for (const ann of pageAnnotations) {
switch (ann.type) {
case 'text':
currentPdfPage.drawText(ann.text, {
x: ann.x,
y: ann.y,
size: ann.size || 12,
font: await pdfLibDoc.embedFont(ann.font || 'Helvetica'),
color: PDFLib.rgb(ann.color?.r || 0, ann.color?.g || 0, ann.color?.b || 0),
});
break;
case 'image':
if (ann.imageBytes) {
const image = await pdfLibDoc.embedPng(ann.imageBytes);
currentPdfPage.drawImage(image, {
x: ann.x,
y: ann.y,
width: ann.width,
height: ann.height,
});
}
break;
// Handle other annotation types...
}
}
}
// Insert text annotation
function insertText() {
const text = document.getElementById('text-content').value;
if (!text) return;
const font = document.getElementById('text-font').value;
const size = parseInt(document.getElementById('text-size').value);
const color = hexToRgb(document.getElementById('text-color').value);
annotations.push({
type: 'text',
text,
font,
size,
color,
x: 50, // Default position
y: 50, // Default position
page: currentPage
});
renderAnnotations();
textModal.style.display = 'none';
document.getElementById('text-content').value = '';
}
// Preview image before insertion
function previewImage() {
const file = document.getElementById('image-input').files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('image-preview').innerHTML = `

`;
};
reader.readAsDataURL(file);
}
// Insert image annotation
async function insertImage() {
const file = document.getElementById('image-input').files[0];
if (!file) return;
showLoading();
try {
const imageBytes = await file.arrayBuffer();
const img = new Image();
img.src = URL.createObjectURL(file);
await new Promise((resolve) => {
img.onload = resolve;
});
// Calculate dimensions to fit (simplified)
const maxWidth = 200;
const maxHeight = 200;
let width = img.width;
let height = img.height;
if (width > maxWidth) {
height = (maxWidth / width) * height;
width = maxWidth;
}
if (height > maxHeight) {
width = (maxHeight / height) * width;
height = maxHeight;
}
annotations.push({
type: 'image',
imageBytes,
width,
height,
x: 50, // Default position
y: 50, // Default position
page: currentPage
});
renderAnnotations();
imageModal.style.display = 'none';
document.getElementById('image-preview').innerHTML = '';
document.getElementById('image-input').value = '';
} catch (err) {
console.error('Error inserting image:', err);
} finally {
hideLoading();
}
}
// Initialize signature pad
function initSignaturePad() {
const signatureCanvas = document.getElementById('signature-pad');
const signatureCtx = signatureCanvas.getContext('2d');
let isDrawing = false;
let lastX = 0;
let lastY = 0;
signatureCanvas.addEventListener('mousedown', (e) => {
isDrawing = true;
[lastX, lastY] = [e.offsetX, e.offsetY];
});
signatureCanvas.addEventListener('mousemove', (e) => {
if (!isDrawing) return;
signatureCtx.beginPath();
signatureCtx.moveTo(lastX, lastY);
signatureCtx.lineTo(e.offsetX, e.offsetY);
signatureCtx.strokeStyle = '#000';
signatureCtx.lineWidth = 2;
signatureCtx.stroke();
[lastX, lastY] = [e.offsetX, e.offsetY];
});
signatureCanvas.addEventListener('mouseup', () => isDrawing = false);
signatureCanvas.addEventListener('mouseout', () => isDrawing = false);
// Touch support
signatureCanvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const touch = e.touches[0];
const rect = signatureCanvas.getBoundingClientRect();
isDrawing = true;
[lastX, lastY] = [touch.clientX - rect.left, touch.clientY - rect.top];
});
signatureCanvas.addEventListener('touchmove', (e) => {
e.preventDefault();
if (!isDrawing) return;
const touch = e.touches[0];
const rect = signatureCanvas.getBoundingClientRect();
const x = touch.clientX - rect.left;
const y = touch.clientY - rect.top;
signatureCtx.beginPath();
signatureCtx.moveTo(lastX, lastY);
signatureCtx.lineTo(x, y);
signatureCtx.strokeStyle = '#000';
signatureCtx.lineWidth = 2;
signatureCtx.stroke();
[lastX, lastY] = [x, y];
});
signatureCanvas.addEventListener('touchend', () => isDrawing = false);
}
// Clear signature
function clearSignature() {
const signatureCanvas = document.getElementById('signature-pad');
const signatureCtx = signatureCanvas.getContext('2d');
signatureCtx.clearRect(0, 0, signatureCanvas.width, signatureCanvas.height);
}
// Save signature
function saveSignature() {
const signatureCanvas = document.getElementById('signature-pad');
const dataURL = signatureCanvas.toDataURL('image/png');
signatures.push(dataURL);
// Add as annotation
annotations.push({
type: 'signature',
imageData: dataURL,
x: 50,
y: 50,
width: 150,
height: 75,
page: currentPage
});
renderAnnotations();
signatureModal.style.display = 'none';
clearSignature();
}
// Apply security settings
function applySecurity() {
const passwordProtect = document.getElementById('password-protect').checked;
const password = document.getElementById('pdf-password').value;
const confirmPassword = document.getElementById('pdf-confirm-password').value;
const restrictEditing = document.getElementById('restrict-editing').checked;
const restrictPrinting = document.getElementById('restrict-printing').checked;
if (passwordProtect && password !== confirmPassword) {
alert('Passwords do not match!');
return;
}
// In a real app, you would apply these security settings to the PDF
// This is just a simulation
alert('Security settings applied successfully!');
securityModal.style.display = 'none';
}
// Manage pages (simplified)
function managePages() {
alert('Page management would allow you to add, delete, reorder, and rotate pages in the PDF.');
}
// Render annotations
function renderAnnotations() {
pdfAnnotations.innerHTML = '';
const pageAnnotations = annotations.filter(ann => ann.page === currentPage);
const canvasRect = canvas.getBoundingClientRect();
for (const ann of pageAnnotations) {
const annotationEl = document.createElement('div');
annotationEl.className = `annotation annotation-${ann.type}`;
// Position annotation relative to PDF canvas
annotationEl.style.left = `${ann.x}px`;
annotationEl.style.top = `${canvasRect.height - ann.y}px`;
switch (ann.type) {
case 'text':
annotationEl.classList.add('annotation-text');
annotationEl.textContent = ann.text;
annotationEl.style.fontSize = `${ann.size}px`;
annotationEl.style.color = `rgb(${ann.color?.r || 0}, ${ann.color?.g || 0}, ${ann.color?.b || 0})`;
annotationEl.style.fontFamily = ann.font || 'Helvetica';
break;
case 'image':
annotationEl.classList.add('annotation-image');
annotationEl.innerHTML = `

`;
break;
case 'highlight':
annotationEl.classList.add('annotation-highlight');
annotationEl.style.width = `${ann.width}px`;
annotationEl.style.height = `${ann.height}px`;
break;
case 'comment':
annotationEl.classList.add('annotation-comment');
annotationEl.textContent = ann.text || 'Comment';
break;
case 'signature':
annotationEl.classList.add('annotation-stamp');
annotationEl.innerHTML = `

`;
break;
case 'stamp':
annotationEl.classList.add('annotation-stamp');
annotationEl.innerHTML = `

`;
break;
}
// Make annotation draggable
makeDraggable(annotationEl, ann);
pdfAnnotations.appendChild(annotationEl);
}
}
// Make annotations draggable
function makeDraggable(element, annotation) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
element.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// Get the mouse cursor position at startup
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// Calculate the new cursor position
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// Set the element's new position
const newTop = element.offsetTop - pos2;
const newLeft = element.offsetLeft - pos1;
element.style.top = newTop + 'px';
element.style.left = newLeft + 'px';
// Update annotation coordinates
const canvasRect = canvas.getBoundingClientRect();
annotation.x = newLeft;
annotation.y = canvasRect.height - newTop;
}
function closeDragElement() {
// Stop moving when mouse button is released
document.onmouseup = null;
document.onmousemove = null;
}
}
// Helper function to convert hex to RGB
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : { r: 0, g: 0, b: 0 };
}
// Show loading overlay
function showLoading() {
document.getElementById('loading-overlay').style.display = 'flex';
}
// Hide loading overlay
function hideLoading() {
document.getElementById('loading-overlay').style.display = 'none';
}
// Initialize with a blank PDF
createNewPDF();
// PDF.js worker path
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.worker.min.js';
// Global variables
let pdfDoc = null;
let currentPage = 1;
let scale = 1.5;
let pdfLibDoc = null;
let annotations = [];
let selectedAnnotation = null;
let isDrawing = false;
let startX, startY;
let currentTool = null;
let signatures = [];
let stamps = [];
// DOM elements
const canvas = document.getElementById('pdf-canvas');
const ctx = canvas.getContext('2d');
const pageThumbnails = document.getElementById('page-thumbnails');
const pdfAnnotations = document.getElementById('pdf-annotations');
const fileInput = document.getElementById('file-input');
const propertiesContent = document.getElementById('properties-content');
// Modals
const textModal = document.getElementById('text-modal');
const imageModal = document.getElementById('image-modal');
const signatureModal = document.getElementById('signature-modal');
const securityModal = document.getElementById('security-modal');
// Buttons
document.getElementById('new-pdf').addEventListener('click', createNewPDF);
document.getElementById('open-pdf').addEventListener('click', () => fileInput.click());
document.getElementById('save-pdf').addEventListener('click', savePDF);
document.getElementById('export-pdf').addEventListener('click', exportPDF);
document.getElementById('cloud-save').addEventListener('click', saveToCloud);
document.getElementById('security').addEventListener('click', () => securityModal.style.display = 'block');
// Tool buttons
document.getElementById('add-text').addEventListener('click', () => {
currentTool = 'text';
textModal.style.display = 'block';
});
document.getElementById('add-image').addEventListener('click', () => {
currentTool = 'image';
imageModal.style.display = 'block';
});
document.getElementById('highlight').addEventListener('click', () => currentTool = 'highlight');
document.getElementById('add-comment').addEventListener('click', () => currentTool = 'comment');
document.getElementById('add-signature').addEventListener('click', () => {
currentTool = 'signature';
signatureModal.style.display = 'block';
initSignaturePad();
});
document.getElementById('add-stamp').addEventListener('click', () => currentTool = 'stamp');
document.getElementById('manage-pages').addEventListener('click', managePages);
// Modal close buttons
document.querySelectorAll('.close').forEach(btn => {
btn.addEventListener('click', function() {
this.closest('.modal').style.display = 'none';
});
});
// Window click to close modals
window.addEventListener('click', (e) => {
if (e.target.classList.contains('modal')) {
e.target.style.display = 'none';
}
});
// Text modal
document.getElementById('insert-text').addEventListener('click', insertText);
// Image modal
document.getElementById('image-input').addEventListener('change', previewImage);
document.getElementById('insert-image').addEventListener('click', insertImage);
// Signature modal
document.getElementById('clear-signature').addEventListener('click', clearSignature);
document.getElementById('save-signature').addEventListener('click', saveSignature);
// Security modal
document.getElementById('password-protect').addEventListener('change', function() {
document.getElementById('password-fields').style.display = this.checked ? 'block' : 'none';
});
document.getElementById('apply-security').addEventListener('click', applySecurity);
// File input handler
fileInput.addEventListener('change', async (e) => {
if (e.target.files.length === 0) return;
showLoading();
const file = e.target.files[0];
try {
// Load with PDF.js for rendering
const arrayBuffer = await file.arrayBuffer();
pdfDoc = await pdfjsLib.getDocument(arrayBuffer).promise;
// Load with PDF-Lib for editing
pdfLibDoc = await PDFLib.PDFDocument.load(arrayBuffer);
renderPage();
generateThumbnails();
} catch (err) {
console.error('Error loading PDF:', err);
alert('Error loading PDF. Please try another file.');
} finally {
hideLoading();
}
});
// Render PDF page
async function renderPage() {
if (!pdfDoc) return;
showLoading();
try {
const page = await pdfDoc.getPage(currentPage);
const viewport = page.getViewport({ scale });
canvas.height = viewport.height;
canvas.width = viewport.width;
await page.render({
canvasContext: ctx,
viewport
}).promise;
renderAnnotations();
} catch (err) {
console.error('Error rendering page:', err);
} finally {
hideLoading();
}
}
// Generate page thumbnails
async function generateThumbnails() {
if (!pdfDoc) return;
pageThumbnails.innerHTML = '';
for (let i = 1; i <= pdfDoc.numPages; i++) {
const thumbnail = document.createElement('div');
thumbnail.className = 'page-thumbnail' + (i === currentPage ? ' active' : '');
thumbnail.innerHTML = `
Page ${i}
`;
thumbnail.addEventListener('click', () => {
currentPage = i;
renderPage();
document.querySelectorAll('.page-thumbnail').forEach(el => el.classList.remove('active'));
thumbnail.classList.add('active');
});
// Generate thumbnail image (simplified - in production you'd render actual thumbnails)
pageThumbnails.appendChild(thumbnail);
}
}
// Create new PDF
async function createNewPDF() {
showLoading();
try {
pdfLibDoc = await PDFLib.PDFDocument.create();
const page = pdfLibDoc.addPage([550, 750]);
// Convert to array buffer for PDF.js
const pdfBytes = await pdfLibDoc.save();
pdfDoc = await pdfjsLib.getDocument(pdfBytes).promise;
currentPage = 1;
annotations = [];
renderPage();
generateThumbnails();
} catch (err) {
console.error('Error creating new PDF:', err);
} finally {
hideLoading();
}
}
// Save PDF
async function savePDF() {
if (!pdfLibDoc) return;
showLoading();
try {
// Update PDF-Lib document with annotations
await updatePdfWithAnnotations();
const pdfBytes = await pdfLibDoc.save();
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
saveAs(blob, 'edited-document.pdf');
} catch (err) {
console.error('Error saving PDF:', err);
alert('Error saving PDF. Please try again.');
} finally {
hideLoading();
}
}
// Export PDF in different formats
async function exportPDF() {
if (!pdfLibDoc) return;
// In a real app, you would implement export to Word, images, etc.
// This is simplified to just save as PDF
savePDF();
}
// Save to cloud
async function saveToCloud() {
if (!pdfLibDoc) return;
showLoading();
try {
await updatePdfWithAnnotations();
const pdfBytes = await pdfLibDoc.save();
// In a real app, you would integrate with Google Drive, Dropbox, etc.
// This is just a simulation
setTimeout(() => {
hideLoading();
alert('PDF saved to cloud successfully!');
}, 1500);
} catch (err) {
console.error('Error saving to cloud:', err);
alert('Error saving to cloud. Please try again.');
hideLoading();
}
}
// Update PDF-Lib document with annotations
async function updatePdfWithAnnotations() {
if (!pdfLibDoc || annotations.length === 0) return;
const pages = pdfLibDoc.getPages();
const currentPdfPage = pages[currentPage - 1];
// Process annotations for current page
const pageAnnotations = annotations.filter(ann => ann.page === currentPage);
for (const ann of pageAnnotations) {
switch (ann.type) {
case 'text':
currentPdfPage.drawText(ann.text, {
x: ann.x,
y: ann.y,
size: ann.size || 12,
font: await pdfLibDoc.embedFont(ann.font || 'Helvetica'),
color: PDFLib.rgb(ann.color?.r || 0, ann.color?.g || 0, ann.color?.b || 0),
});
break;
case 'image':
if (ann.imageBytes) {
const image = await pdfLibDoc.embedPng(ann.imageBytes);
currentPdfPage.drawImage(image, {
x: ann.x,
y: ann.y,
width: ann.width,
height: ann.height,
});
}
break;
// Handle other annotation types...
}
}
}
// Insert text annotation
function insertText() {
const text = document.getElementById('text-content').value;
if (!text) return;
const font = document.getElementById('text-font').value;
const size = parseInt(document.getElementById('text-size').value);
const color = hexToRgb(document.getElementById('text-color').value);
annotations.push({
type: 'text',
text,
font,
size,
color,
x: 50, // Default position
y: 50, // Default position
page: currentPage
});
renderAnnotations();
textModal.style.display = 'none';
document.getElementById('text-content').value = '';
}
// Preview image before insertion
function previewImage() {
const file = document.getElementById('image-input').files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('image-preview').innerHTML = `

`;
};
reader.readAsDataURL(file);
}
// Insert image annotation
async function insertImage() {
const file = document.getElementById('image-input').files[0];
if (!file) return;
showLoading();
try {
const imageBytes = await file.arrayBuffer();
const img = new Image();
img.src = URL.createObjectURL(file);
await new Promise((resolve) => {
img.onload = resolve;
});
// Calculate dimensions to fit (simplified)
const maxWidth = 200;
const maxHeight = 200;
let width = img.width;
let height = img.height;
if (width > maxWidth) {
height = (maxWidth / width) * height;
width = maxWidth;
}
if (height > maxHeight) {
width = (maxHeight / height) * width;
height = maxHeight;
}
annotations.push({
type: 'image',
imageBytes,
width,
height,
x: 50, // Default position
y: 50, // Default position
page: currentPage
});
renderAnnotations();
imageModal.style.display = 'none';
document.getElementById('image-preview').innerHTML = '';
document.getElementById('image-input').value = '';
} catch (err) {
console.error('Error inserting image:', err);
} finally {
hideLoading();
}
}
// Initialize signature pad
function initSignaturePad() {
const signatureCanvas = document.getElementById('signature-pad');
const signatureCtx = signatureCanvas.getContext('2d');
let isDrawing = false;
let lastX = 0;
let lastY = 0;
signatureCanvas.addEventListener('mousedown', (e) => {
isDrawing = true;
[lastX, lastY] = [e.offsetX, e.offsetY];
});
signatureCanvas.addEventListener('mousemove', (e) => {
if (!isDrawing) return;
signatureCtx.beginPath();
signatureCtx.moveTo(lastX, lastY);
signatureCtx.lineTo(e.offsetX, e.offsetY);
signatureCtx.strokeStyle = '#000';
signatureCtx.lineWidth = 2;
signatureCtx.stroke();
[lastX, lastY] = [e.offsetX, e.offsetY];
});
signatureCanvas.addEventListener('mouseup', () => isDrawing = false);
signatureCanvas.addEventListener('mouseout', () => isDrawing = false);
// Touch support
signatureCanvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const touch = e.touches[0];
const rect = signatureCanvas.getBoundingClientRect();
isDrawing = true;
[lastX, lastY] = [touch.clientX - rect.left, touch.clientY - rect.top];
});
signatureCanvas.addEventListener('touchmove', (e) => {
e.preventDefault();
if (!isDrawing) return;
const touch = e.touches[0];
const rect = signatureCanvas.getBoundingClientRect();
const x = touch.clientX - rect.left;
const y = touch.clientY - rect.top;
signatureCtx.beginPath();
signatureCtx.moveTo(lastX, lastY);
signatureCtx.lineTo(x, y);
signatureCtx.strokeStyle = '#000';
signatureCtx.lineWidth = 2;
signatureCtx.stroke();
[lastX, lastY] = [x, y];
});
signatureCanvas.addEventListener('touchend', () => isDrawing = false);
}
// Clear signature
function clearSignature() {
const signatureCanvas = document.getElementById('signature-pad');
const signatureCtx = signatureCanvas.getContext('2d');
signatureCtx.clearRect(0, 0, signatureCanvas.width, signatureCanvas.height);
}
// Save signature
function saveSignature() {
const signatureCanvas = document.getElementById('signature-pad');
const dataURL = signatureCanvas.toDataURL('image/png');
signatures.push(dataURL);
// Add as annotation
annotations.push({
type: 'signature',
imageData: dataURL,
x: 50,
y: 50,
width: 150,
height: 75,
page: currentPage
});
renderAnnotations();
signatureModal.style.display = 'none';
clearSignature();
}
// Apply security settings
function applySecurity() {
const passwordProtect = document.getElementById('password-protect').checked;
const password = document.getElementById('pdf-password').value;
const confirmPassword = document.getElementById('pdf-confirm-password').value;
const restrictEditing = document.getElementById('restrict-editing').checked;
const restrictPrinting = document.getElementById('restrict-printing').checked;
if (passwordProtect && password !== confirmPassword) {
alert('Passwords do not match!');
return;
}
// In a real app, you would apply these security settings to the PDF
// This is just a simulation
alert('Security settings applied successfully!');
securityModal.style.display = 'none';
}
// Manage pages (simplified)
function managePages() {
alert('Page management would allow you to add, delete, reorder, and rotate pages in the PDF.');
}
// Render annotations
function renderAnnotations() {
pdfAnnotations.innerHTML = '';
const pageAnnotations = annotations.filter(ann => ann.page === currentPage);
const canvasRect = canvas.getBoundingClientRect();
for (const ann of pageAnnotations) {
const annotationEl = document.createElement('div');
annotationEl.className = `annotation annotation-${ann.type}`;
// Position annotation relative to PDF canvas
annotationEl.style.left = `${ann.x}px`;
annotationEl.style.top = `${canvasRect.height - ann.y}px`;
switch (ann.type) {
case 'text':
annotationEl.classList.add('annotation-text');
annotationEl.textContent = ann.text;
annotationEl.style.fontSize = `${ann.size}px`;
annotationEl.style.color = `rgb(${ann.color?.r || 0}, ${ann.color?.g || 0}, ${ann.color?.b || 0})`;
annotationEl.style.fontFamily = ann.font || 'Helvetica';
break;
case 'image':
annotationEl.classList.add('annotation-image');
annotationEl.innerHTML = `

`;
break;
case 'highlight':
annotationEl.classList.add('annotation-highlight');
annotationEl.style.width = `${ann.width}px`;
annotationEl.style.height = `${ann.height}px`;
break;
case 'comment':
annotationEl.classList.add('annotation-comment');
annotationEl.textContent = ann.text || 'Comment';
break;
case 'signature':
annotationEl.classList.add('annotation-stamp');
annotationEl.innerHTML = `

`;
break;
case 'stamp':
annotationEl.classList.add('annotation-stamp');
annotationEl.innerHTML = `

`;
break;
}
// Make annotation draggable
makeDraggable(annotationEl, ann);
pdfAnnotations.appendChild(annotationEl);
}
}
// Make annotations draggable
function makeDraggable(element, annotation) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
element.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// Get the mouse cursor position at startup
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// Calculate the new cursor position
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// Set the element's new position
const newTop = element.offsetTop - pos2;
const newLeft = element.offsetLeft - pos1;
element.style.top = newTop + 'px';
element.style.left = newLeft + 'px';
// Update annotation coordinates
const canvasRect = canvas.getBoundingClientRect();
annotation.x = newLeft;
annotation.y = canvasRect.height - newTop;
}
function closeDragElement() {
// Stop moving when mouse button is released
document.onmouseup = null;
document.onmousemove = null;
}
}
// Helper function to convert hex to RGB
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : { r: 0, g: 0, b: 0 };
}
// Show loading overlay
function showLoading() {
document.getElementById('loading-overlay').style.display = 'flex';
}
// Hide loading overlay
function hideLoading() {
document.getElementById('loading-overlay').style.display = 'none';
}
// Initialize with a blank PDF
createNewPDF();
No comments: