File: /var/www/html/web-custom-tools/assets/script.js
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
const uploadZone = document.getElementById('uploadZone');
const fileInput = document.getElementById('fileInput');
const compressBtn = document.getElementById('compressBtn');
const results = document.getElementById('results');
let selectedFiles = [];
uploadZone.addEventListener('click', () => fileInput.click());
uploadZone.addEventListener('dragover', (e) => {
e.preventDefault();
uploadZone.classList.add('dragover');
});
uploadZone.addEventListener('dragleave', () => {
uploadZone.classList.remove('dragover');
});
uploadZone.addEventListener('drop', (e) => {
e.preventDefault();
uploadZone.classList.remove('dragover');
const files = Array.from(e.dataTransfer.files).filter(f =>
f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf')
);
if (files.length) {
selectedFiles = files;
updateUploadZone();
}
});
fileInput.addEventListener('change', (e) => {
selectedFiles = Array.from(e.target.files);
updateUploadZone();
});
function updateUploadZone() {
if (selectedFiles.length) {
uploadZone.innerHTML = `
<div class="upload-icon">✅</div>
<div><strong>${selectedFiles.length} PDF${selectedFiles.length > 1 ? 's' : ''} selected</strong></div>
<div style="font-size: 13px; color: #718096; margin-top: 8px;">Click to change selection</div>
`;
}
}
function formatBytes(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
}
function calculateSavings(original, compressed) {
const saved = original - compressed;
const percent = ((saved / original) * 100).toFixed(1);
return { saved, percent };
}
async function optimizePDF(file, options, progressCallback) {
const { maxPages, dpi, quality, grayscale } = options;
progressCallback(5);
const arrayBuffer = await file.arrayBuffer();
const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer });
const pdf = await loadingTask.promise;
const totalPages = pdf.numPages;
const pagesToProcess = Math.min(maxPages, totalPages);
progressCallback(10);
if (totalPages > maxPages) {
console.warn(`PDF has ${totalPages} pages, only processing first ${maxPages}`);
}
const dpiScale = dpi / 72; // 72 DPI is PDF standard
const pageImages = [];
const progressPerPage = 70 / pagesToProcess;
for (let pageNum = 1; pageNum <= pagesToProcess; pageNum++) {
const page = await pdf.getPage(pageNum);
const viewport = page.getViewport({ scale: dpiScale });
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { alpha: false });
canvas.width = Math.floor(viewport.width);
canvas.height = Math.floor(viewport.height);
await page.render({ canvasContext: ctx, viewport }).promise;
if (grayscale) {
try {
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imgData.data;
for (let i = 0; i < data.length; i += 4) {
const gray = Math.floor(0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]);
data[i] = data[i + 1] = data[i + 2] = gray;
}
ctx.putImageData(imgData, 0, 0);
} catch (err) { }
}
const blob = await new Promise(res =>
canvas.toBlob(res, 'image/jpeg', quality)
);
const arrayBuf = await blob.arrayBuffer();
pageImages.push({
arrayBuf,
width: canvas.width,
height: canvas.height
});
progressCallback(10 + Math.floor(pageNum * progressPerPage));
}
progressCallback(85);
const { PDFDocument } = PDFLib;
const newPdf = await PDFDocument.create();
for (const img of pageImages) {
const jpgImage = await newPdf.embedJpg(img.arrayBuf);
const page = newPdf.addPage([img.width, img.height]);
page.drawImage(jpgImage, { x: 0, y: 0, width: img.width, height: img.height });
}
const pdfBytes = await newPdf.save();
const outBlob = new Blob([pdfBytes], { type: 'application/pdf' });
progressCallback(100);
return {
blob: outBlob,
originalSize: file.size,
compressedSize: outBlob.size,
totalPages: totalPages,
processedPages: pagesToProcess,
truncated: totalPages > pagesToProcess
};
}
compressBtn.addEventListener('click', async () => {
if (!selectedFiles.length) {
alert('Please select at least one PDF file');
return;
}
compressBtn.disabled = true;
results.innerHTML = '';
const maxPages = parseInt(document.getElementById('maxPages').value);
const dpi = parseInt(document.getElementById('dpi').value);
const quality = parseFloat(document.getElementById('quality').value);
const grayscale = document.getElementById('grayscale').value === 'yes';
for (const file of selectedFiles) {
const fileCard = document.createElement('div');
fileCard.className = 'file-card';
fileCard.innerHTML = `
<div class="file-header">
<div class="file-name">${file.name}</div>
<div class="file-status status-processing">Processing...</div>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: 0%"></div>
</div>
<div class="file-content"></div>
`;
results.appendChild(fileCard);
const progressFill = fileCard.querySelector('.progress-fill');
const statusBadge = fileCard.querySelector('.file-status');
const contentDiv = fileCard.querySelector('.file-content');
try {
const result = await optimizePDF(file, { maxPages, dpi, quality, grayscale }, (progress) => {
progressFill.style.width = progress + '%';
});
const savings = calculateSavings(result.originalSize, result.compressedSize);
if (savings.percent < 0) {
statusBadge.textContent = 'Larger ⚠️';
statusBadge.className = 'file-status status-nochange';
} else if (savings.percent < 5) {
statusBadge.textContent = 'Minimal change';
statusBadge.className = 'file-status status-nochange';
} else {
statusBadge.textContent = 'Complete ✓';
statusBadge.className = 'file-status status-success';
}
const url = URL.createObjectURL(result.blob);
contentDiv.innerHTML = `
<div class="file-stats">
<div class="stat">
<div class="stat-label">Original</div>
<div class="stat-value">${formatBytes(result.originalSize)}</div>
</div>
<div class="stat">
<div class="stat-label">Processed</div>
<div class="stat-value">${formatBytes(result.compressedSize)}</div>
</div>
<div class="stat">
<div class="stat-label">Change</div>
<div class="stat-value ${savings.percent > 0 ? 'savings' : ''}">${savings.percent}%</div>
</div>
<div class="stat">
<div class="stat-label">Pages</div>
<div class="stat-value">${result.processedPages}/${result.totalPages}</div>
</div>
</div>
<div class="analysis-info">
${result.truncated ?
`<strong style="color: #dc2626;">⚠️ WARNING: Only ${result.processedPages} of ${result.totalPages} pages processed!</strong><br>` :
''}
<strong>Settings:</strong> ${dpi} DPI, ${(quality * 100).toFixed(0)}% quality, ${grayscale ? 'Grayscale' : 'Color'}<br>
${savings.percent < 0 ?
'<strong>Note:</strong> Rasterizing text to images increased file size. Use Ghostscript instead.' :
savings.percent < 5 ?
'<strong>Note:</strong> PDF is already well-optimized. Try professional tools for better results.' :
'<strong>Success:</strong> Some compression achieved, but professional tools will do better.'
}
</div>
<a href="${url}" download="optimized_${file.name}" class="download-btn">⬇️ Download PDF</a>
`;
} catch (err) {
console.error('Error:', err);
statusBadge.textContent = 'Error ✗';
statusBadge.className = 'file-status status-error';
contentDiv.innerHTML = `<div style="color: #dc2626; padding: 12px;">❌ ${err.message || 'Processing failed'}</div>`;
}
}
compressBtn.disabled = false;
});