diff --git a/src/js/logic/pdf-to-webp-page.ts b/src/js/logic/pdf-to-webp-page.ts
index cdafdf7..c2c0fec 100644
--- a/src/js/logic/pdf-to-webp-page.ts
+++ b/src/js/logic/pdf-to-webp-page.ts
@@ -1,193 +1,237 @@
import { showLoader, hideLoader, showAlert } from '../ui.js';
-import { downloadFile, formatBytes, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js';
+import {
+ downloadFile,
+ formatBytes,
+ readFileAsArrayBuffer,
+ getPDFDocument,
+ getCleanPdfFilename,
+} from '../utils/helpers.js';
import { createIcons, icons } from 'lucide';
import JSZip from 'jszip';
import * as pdfjsLib from 'pdfjs-dist';
+import { PDFPageProxy } from 'pdfjs-dist';
-pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
+pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
+ 'pdfjs-dist/build/pdf.worker.min.mjs',
+ import.meta.url
+).toString();
let files: File[] = [];
const updateUI = () => {
- const fileDisplayArea = document.getElementById('file-display-area');
- const optionsPanel = document.getElementById('options-panel');
- const dropZone = document.getElementById('drop-zone');
+ const fileDisplayArea = document.getElementById('file-display-area');
+ const optionsPanel = document.getElementById('options-panel');
+ const dropZone = document.getElementById('drop-zone');
- if (!fileDisplayArea || !optionsPanel || !dropZone) return;
+ if (!fileDisplayArea || !optionsPanel || !dropZone) return;
- fileDisplayArea.innerHTML = '';
+ fileDisplayArea.innerHTML = '';
- if (files.length > 0) {
- optionsPanel.classList.remove('hidden');
+ if (files.length > 0) {
+ optionsPanel.classList.remove('hidden');
- files.forEach((file) => {
- const fileDiv = document.createElement('div');
- fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
+ files.forEach((file) => {
+ const fileDiv = document.createElement('div');
+ fileDiv.className =
+ 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
- const infoContainer = document.createElement('div');
- infoContainer.className = 'flex flex-col overflow-hidden';
+ const infoContainer = document.createElement('div');
+ infoContainer.className = 'flex flex-col overflow-hidden';
- const nameSpan = document.createElement('div');
- nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1';
- nameSpan.textContent = file.name;
+ const nameSpan = document.createElement('div');
+ nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1';
+ nameSpan.textContent = file.name;
- const metaSpan = document.createElement('div');
- metaSpan.className = 'text-xs text-gray-400';
- metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; // Initial state
+ const metaSpan = document.createElement('div');
+ metaSpan.className = 'text-xs text-gray-400';
+ metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; // Initial state
- infoContainer.append(nameSpan, metaSpan);
+ infoContainer.append(nameSpan, metaSpan);
- const removeBtn = document.createElement('button');
- removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
- removeBtn.innerHTML = '';
- removeBtn.onclick = () => {
- files = [];
- updateUI();
- };
+ const removeBtn = document.createElement('button');
+ removeBtn.className =
+ 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
+ removeBtn.innerHTML = '';
+ removeBtn.onclick = () => {
+ files = [];
+ updateUI();
+ };
- fileDiv.append(infoContainer, removeBtn);
- fileDisplayArea.appendChild(fileDiv);
+ fileDiv.append(infoContainer, removeBtn);
+ fileDisplayArea.appendChild(fileDiv);
- // Fetch page count asynchronously
- readFileAsArrayBuffer(file).then(buffer => {
- return getPDFDocument(buffer).promise;
- }).then(pdf => {
- metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} page${pdf.numPages !== 1 ? 's' : ''}`;
- }).catch(e => {
- console.warn('Error loading PDF page count:', e);
- metaSpan.textContent = formatBytes(file.size);
- });
+ // Fetch page count asynchronously
+ readFileAsArrayBuffer(file)
+ .then((buffer) => {
+ return getPDFDocument(buffer).promise;
+ })
+ .then((pdf) => {
+ metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} page${pdf.numPages !== 1 ? 's' : ''}`;
+ })
+ .catch((e) => {
+ console.warn('Error loading PDF page count:', e);
+ metaSpan.textContent = formatBytes(file.size);
});
+ });
- // Initialize icons immediately after synchronous render
- createIcons({ icons });
- } else {
- optionsPanel.classList.add('hidden');
- }
+ // Initialize icons immediately after synchronous render
+ createIcons({ icons });
+ } else {
+ optionsPanel.classList.add('hidden');
+ }
};
const resetState = () => {
- files = [];
- const fileInput = document.getElementById('file-input') as HTMLInputElement;
- if (fileInput) fileInput.value = '';
- const qualitySlider = document.getElementById('webp-quality') as HTMLInputElement;
- const qualityValue = document.getElementById('webp-quality-value');
- if (qualitySlider) qualitySlider.value = '0.85';
- if (qualityValue) qualityValue.textContent = '85%';
- updateUI();
+ files = [];
+ const fileInput = document.getElementById('file-input') as HTMLInputElement;
+ if (fileInput) fileInput.value = '';
+ const qualitySlider = document.getElementById(
+ 'webp-quality'
+ ) as HTMLInputElement;
+ const qualityValue = document.getElementById('webp-quality-value');
+ if (qualitySlider) qualitySlider.value = '0.85';
+ if (qualityValue) qualityValue.textContent = '85%';
+ updateUI();
};
async function convert() {
- if (files.length === 0) {
- showAlert('No File', 'Please upload a PDF file first.');
- return;
- }
- showLoader('Converting to WebP...');
- try {
- const pdf = await getPDFDocument(
- await readFileAsArrayBuffer(files[0])
- ).promise;
- const zip = new JSZip();
+ if (files.length === 0) {
+ showAlert('No File', 'Please upload a PDF file first.');
+ return;
+ }
+ showLoader('Converting to WebP...');
+ try {
+ const pdf = await getPDFDocument(await readFileAsArrayBuffer(files[0]))
+ .promise;
- const qualityInput = document.getElementById('webp-quality') as HTMLInputElement;
- const quality = qualityInput ? parseFloat(qualityInput.value) : 0.85;
+ const qualityInput = document.getElementById(
+ 'webp-quality'
+ ) as HTMLInputElement;
+ const quality = qualityInput ? parseFloat(qualityInput.value) : 0.85;
- for (let i = 1; i <= pdf.numPages; i++) {
- const page = await pdf.getPage(i);
- const viewport = page.getViewport({ scale: 2.0 });
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- canvas.height = viewport.height;
- canvas.width = viewport.width;
-
- await page.render({ canvasContext: context!, viewport: viewport, canvas }).promise;
-
- const blob = await new Promise((resolve) =>
- canvas.toBlob(resolve, 'image/webp', quality)
- );
- if (blob) {
- zip.file(`page_${i}.webp`, blob);
- }
+ if (pdf.numPages === 1) {
+ const page = await pdf.getPage(1);
+ const blob = await renderPage(page, quality);
+ downloadFile(blob, getCleanPdfFilename(files[0].name) + '.webp');
+ } else {
+ const zip = new JSZip();
+ for (let i = 1; i <= pdf.numPages; i++) {
+ const page = await pdf.getPage(i);
+ const blob = await renderPage(page, quality);
+ if (blob) {
+ zip.file(`page_${i}.webp`, blob);
}
+ }
- const zipBlob = await zip.generateAsync({ type: 'blob' });
- downloadFile(zipBlob, 'converted_images.zip');
- showAlert('Success', 'PDF converted to WebPs successfully!', 'success', () => {
- resetState();
- });
- } catch (e) {
- console.error(e);
- showAlert(
- 'Error',
- 'Failed to convert PDF to WebP. The file might be corrupted.'
- );
- } finally {
- hideLoader();
+ const zipBlob = await zip.generateAsync({ type: 'blob' });
+ downloadFile(zipBlob, getCleanPdfFilename(files[0].name) + '_webps.zip');
}
+
+ showAlert(
+ 'Success',
+ 'PDF converted to WebPs successfully!',
+ 'success',
+ () => {
+ resetState();
+ }
+ );
+ } catch (e) {
+ console.error(e);
+ showAlert(
+ 'Error',
+ 'Failed to convert PDF to WebP. The file might be corrupted.'
+ );
+ } finally {
+ hideLoader();
+ }
+}
+
+async function renderPage(
+ page: PDFPageProxy,
+ quality: number
+): Promise {
+ const viewport = page.getViewport({ scale: 2.0 });
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ canvas.height = viewport.height;
+ canvas.width = viewport.width;
+
+ await page.render({
+ canvasContext: context!,
+ viewport: viewport,
+ canvas,
+ }).promise;
+
+ const blob = await new Promise((resolve) =>
+ canvas.toBlob(resolve, 'image/webp', quality)
+ );
+ return blob;
}
document.addEventListener('DOMContentLoaded', () => {
- const fileInput = document.getElementById('file-input') as HTMLInputElement;
- const dropZone = document.getElementById('drop-zone');
- const processBtn = document.getElementById('process-btn');
- const backBtn = document.getElementById('back-to-tools');
- const qualitySlider = document.getElementById('webp-quality') as HTMLInputElement;
- const qualityValue = document.getElementById('webp-quality-value');
+ const fileInput = document.getElementById('file-input') as HTMLInputElement;
+ const dropZone = document.getElementById('drop-zone');
+ const processBtn = document.getElementById('process-btn');
+ const backBtn = document.getElementById('back-to-tools');
+ const qualitySlider = document.getElementById(
+ 'webp-quality'
+ ) as HTMLInputElement;
+ const qualityValue = document.getElementById('webp-quality-value');
- if (backBtn) {
- backBtn.addEventListener('click', () => {
- window.location.href = import.meta.env.BASE_URL;
- });
+ if (backBtn) {
+ backBtn.addEventListener('click', () => {
+ window.location.href = import.meta.env.BASE_URL;
+ });
+ }
+
+ if (qualitySlider && qualityValue) {
+ qualitySlider.addEventListener('input', () => {
+ qualityValue.textContent = `${Math.round(parseFloat(qualitySlider.value) * 100)}%`;
+ });
+ }
+
+ const handleFileSelect = (newFiles: FileList | null) => {
+ if (!newFiles || newFiles.length === 0) return;
+ const validFiles = Array.from(newFiles).filter(
+ (file) => file.type === 'application/pdf'
+ );
+
+ if (validFiles.length === 0) {
+ showAlert('Invalid File', 'Please upload a PDF file.');
+ return;
}
- if (qualitySlider && qualityValue) {
- qualitySlider.addEventListener('input', () => {
- qualityValue.textContent = `${Math.round(parseFloat(qualitySlider.value) * 100)}%`;
- });
- }
+ files = [validFiles[0]];
+ updateUI();
+ };
- const handleFileSelect = (newFiles: FileList | null) => {
- if (!newFiles || newFiles.length === 0) return;
- const validFiles = Array.from(newFiles).filter(
- (file) => file.type === 'application/pdf'
- );
+ if (fileInput && dropZone) {
+ fileInput.addEventListener('change', (e) => {
+ handleFileSelect((e.target as HTMLInputElement).files);
+ });
- if (validFiles.length === 0) {
- showAlert('Invalid File', 'Please upload a PDF file.');
- return;
- }
+ dropZone.addEventListener('dragover', (e) => {
+ e.preventDefault();
+ dropZone.classList.add('bg-gray-700');
+ });
- files = [validFiles[0]];
- updateUI();
- };
+ dropZone.addEventListener('dragleave', (e) => {
+ e.preventDefault();
+ dropZone.classList.remove('bg-gray-700');
+ });
- if (fileInput && dropZone) {
- fileInput.addEventListener('change', (e) => {
- handleFileSelect((e.target as HTMLInputElement).files);
- });
+ dropZone.addEventListener('drop', (e) => {
+ e.preventDefault();
+ dropZone.classList.remove('bg-gray-700');
+ handleFileSelect(e.dataTransfer?.files ?? null);
+ });
- dropZone.addEventListener('dragover', (e) => {
- e.preventDefault();
- dropZone.classList.add('bg-gray-700');
- });
+ fileInput.addEventListener('click', () => {
+ fileInput.value = '';
+ });
+ }
- dropZone.addEventListener('dragleave', (e) => {
- e.preventDefault();
- dropZone.classList.remove('bg-gray-700');
- });
-
- dropZone.addEventListener('drop', (e) => {
- e.preventDefault();
- dropZone.classList.remove('bg-gray-700');
- handleFileSelect(e.dataTransfer?.files ?? null);
- });
-
- fileInput.addEventListener('click', () => {
- fileInput.value = '';
- });
- }
-
- if (processBtn) {
- processBtn.addEventListener('click', convert);
- }
+ if (processBtn) {
+ processBtn.addEventListener('click', convert);
+ }
});
diff --git a/src/pages/pdf-to-webp.html b/src/pages/pdf-to-webp.html
index 253d1f6..0501482 100644
--- a/src/pages/pdf-to-webp.html
+++ b/src/pages/pdf-to-webp.html
@@ -187,7 +187,7 @@