Lelah dengan iklan? Hanya dengan IDR 833 / hari, buka konten eksklusif dan nikmati pengalaman tanpa iklan. Selengkapnya.

Script HTML PDF Toolkit: Tools Terbaik Kelola PDF dengan Mudah!

Sederhanakan pengelolaan PDF Anda dengan PDF Toolkit, aplikasi web canggih dan ramah pengguna yang dirancang untuk mempermudah pengelolaan dokumen PDF. Baik Anda perlu menambahkan watermark, menggabungkan file, mengekstrak halaman, atau mengompres PDF, alat ini memberikan hasil profesional dengan kustomisasi tingkat lanjut dan pratinjau langsung dalam antarmuka yang modern dan intuitif.

Mengapa Memilih PDF Toolkit?

1. Pengolahan PDF Serbaguna

PDF Toolkit memungkinkan Anda untuk:

  • Menambahkan Watermark: Tambahkan watermark teks atau gambar dengan kontrol penuh atas posisi, ukuran, opasitas, rotasi, dan gaya font.
  • Menggabungkan PDF: Gabungkan beberapa PDF menjadi satu dokumen yang rapi dengan mudah.
  • Mengekstrak Halaman: Pilih halaman tertentu atau rentang halaman (misalnya, 1, 3-5) untuk membuat PDF baru sesuai kebutuhan.
  • Mengompres File: Kurangi ukuran file dengan tingkat kompresi yang dapat disesuaikan (1-10) untuk memudahkan berbagi atau penyimpanan.

2. Pratinjau Watermark Langsung

Lihat perubahan Anda secara instan dengan fitur pratinjau watermark langsung. Sesuaikan pengaturan seperti teks, font, warna, opasitas, dan posisi, lalu saksikan hasilnya diperbarui secara real-time pada tampilan kanvas. Ini memastikan watermark Anda tampak sempurna sebelum diproses.

Lelah dengan iklan? Hanya dengan IDR 833 / hari, buka konten eksklusif dan nikmati pengalaman tanpa iklan. Selengkapnya.

3. Antarmuka Drag-and-Drop yang Mudah

Unggah PDF dengan mudah menggunakan antarmuka drag-and-drop atau pemilih file. Toolkit ini mendukung unggahan banyak file sekaligus, menampilkan pratinjau thumbnail dari halaman pertama setiap PDF untuk identifikasi cepat. Hapus file yang tidak diinginkan hanya dengan satu klik.

4. Template Watermark yang Dapat Disesuaikan

Hemat waktu dengan menyimpan pengaturan watermark favorit Anda sebagai template. Gunakan kembali template tersebut untuk proyek lain agar konsisten tanpa perlu memasukkan ulang detail. Pilih font seperti Helvetica, Times Roman, atau Courier, dan sesuaikan margin, offset, serta opsi tebal.

5. Desain Responsif dan Aksesibel

Dibangun dengan Tailwind CSS, PDF Toolkit menawarkan antarmuka modern yang responsif, menyesuaikan dengan perangkat apa pun—desktop, tablet, atau ponsel. Beralih antara mode terang dan gelap untuk pengalaman visual yang nyaman, dilengkapi dengan fitur aksesibilitas seperti label ARIA untuk pembaca layar.

Lelah dengan iklan? Hanya dengan IDR 833 / hari, buka konten eksklusif dan nikmati pengalaman tanpa iklan. Selengkapnya.

6. Pengolahan Cepat dengan Pelacakan Progres

Proses beberapa PDF sekaligus dan pantau progresnya dengan bilah progres dinamis dan log detail. Lihat PDF yang telah diproses dalam jendela modal, lengkap dengan tautan unduh untuk file individu atau arsip ZIP untuk unduhan massal. Toolkit ini juga menampilkan statistik perbandingan ukuran untuk menunjukkan penghematan kompresi.

Fitur Utama Sekilas

  • Watermark Tingkat Lanjut: Sesuaikan watermark teks atau gambar dengan rotasi, opasitas, dan penempatan presisi.
  • Kompresi Efisien: Seimbangkan kualitas dan ukuran file dengan skala kompresi yang fleksibel.
  • Ekstraksi Halaman: Ekstrak halaman tertentu atau rentang halaman dengan mudah.
  • Penggabungan PDF: Gabungkan beberapa PDF menjadi satu, dengan opsi watermark.
  • Pratinjau Langsung: Pratinjau watermark dan PDF secara real-time untuk hasil yang akurat.
  • Penyimpanan Template: Simpan dan gunakan kembali konfigurasi watermark untuk efisiensi.
  • Antarmuka Ramah Pengguna: Unggahan drag-and-drop, desain responsif, dan dukungan mode gelap.
  • Pelacakan Progres: Pantau proses dengan bilah progres dan log detail.
  • Unduhan Massal: Unduh semua PDF yang diproses sebagai file ZIP.

Cocok untuk Profesional dan Pengguna Umum

Baik Anda seorang profesional yang menyiapkan dokumen bermerek, pelajar yang mengatur materi belajar, atau bisnis yang mengompres file untuk email, PDF Toolkit adalah solusi ideal. Fitur canggihnya, didukung oleh pustaka seperti PDF.js dan PDF-LIB.js, menjamin performa handal tanpa mengorbankan kemudahan penggunaan.

Mulai Sekarang

Kendalikan PDF Anda dengan PDF Toolkit. Unggah file, sesuaikan pengaturan, dan proses dengan percaya diri—semuanya dari peramban Anda. Coba sekarang dan rasakan cara cerdas untuk mengelola PDF!

Lelah dengan iklan? Hanya dengan IDR 833 / hari, buka konten eksklusif dan nikmati pengalaman tanpa iklan. Selengkapnya.

Script HTML

<script type="text/javascript">
        var gk_isXlsx = false;
        var gk_xlsxFileLookup = {};
        var gk_fileData = {};
        function filledCell(cell) {
          return cell !== '' && cell != null;
        }
        function loadFileData(filename) {
        if (gk_isXlsx && gk_xlsxFileLookup[filename]) {
            try {
                var workbook = XLSX.read(gk_fileData[filename], { type: 'base64' });
                var firstSheetName = workbook.SheetNames[0];
                var worksheet = workbook.Sheets[firstSheetName];

                // Convert sheet to JSON to filter blank rows
                var jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1, blankrows: false, defval: '' });
                // Filter out blank rows (rows where all cells are empty, null, or undefined)
                var filteredData = jsonData.filter(row => row.some(filledCell));

                // Heuristic to find the header row by ignoring rows with fewer filled cells than the next row
                var headerRowIndex = filteredData.findIndex((row, index) =>
                  row.filter(filledCell).length >= filteredData[index + 1]?.filter(filledCell).length
                );
                // Fallback
                if (headerRowIndex === -1 || headerRowIndex > 25) {
                  headerRowIndex = 0;
                }

                // Convert filtered JSON back to CSV
                var csv = XLSX.utils.aoa_to_sheet(filteredData.slice(headerRowIndex)); // Create a new sheet from filtered array of arrays
                csv = XLSX.utils.sheet_to_csv(csv, { header: 1 });
                return csv;
            } catch (e) {
                console.error(e);
                return "";
            }
        }
        return gk_fileData[filename] || "";
        }
        </script><!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="Professional PDF Toolkit for watermarking, merging, extracting pages, and compressing PDFs with live preview and advanced customization">
    <title>PDF Toolkit</title>
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
    <style>
        body {
            font-family: 'Inter', system-ui, sans-serif;
            min-height: 100vh;
            transition: background-color 0.3s ease, color 0.3s ease;
        }
        .dark {
            background-color: #1f2937;
            color: #f9fafb;
        }
        .dark .bg-white {
            background-color: #374151;
        }
        .dark .border-gray-200 {
            border-color: #4b5563;
        }
        .dark .text-gray-600 {
            color: #d1d5db;
        }
        .dark .bg-gray-50 {
            background-color: #4b5563;
        }
        .drop-zone {
            border: 2px dashed #d1d5db;
            transition: all 0.2s ease;
        }
        .drop-zone.dragover {
            border-color: #3b82f6;
            background: rgba(59, 130, 246, 0.1);
        }
        .preview-item .loading {
            border: 3px solid #e5e7eb;
            border-top: 3px solid #3b82f6;
            border-radius: 50%;
            width: 24px;
            height: 24px;
            animation: spin 1s linear infinite;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }
        @keyframes spin {
            0% { transform: translate(-50%, -50%) rotate(0deg); }
            100% { transform: translate(-50%, -50%) rotate(360deg); }
        }
        [data-tooltip]:hover::after {
            content: attr(data-tooltip);
            position: absolute;
            background: #1f2937;
            color: white;
            padding: 0.5rem;
            border-radius: 4px;
            bottom: 100%;
            left: 50%;
            transform: translateX(-50%);
            font-size: 0.8rem;
            white-space: nowrap;
            z-index: 10;
        }
        .dark [data-tooltip]:hover::after {
            background: #4b5563;
        }
        @media (max-width: 600px) {
            .preview-item {
                width: 60px;
                height: 80px;
            }
            .pdf-item canvas {
                max-height: 40vh;
            }
        }
        @media (min-width: 601px) and (max-width: 900px) {
            .preview-item {
                width: 70px;
                height: 90px;
            }
            .pdf-item canvas {
                max-height: 45vh;
            }
        }
    </style>
</head>
<body class="bg-gray-100">
    <div class="container max-w-3xl mx-auto p-4 sm:p-6">
        <div class="flex justify-between items-center mb-6">
            <h1 class="text-2xl sm:text-3xl font-bold text-gray-800 dark:text-white">PDF Toolkit</h1>
            <button onclick="toggleTheme()" class="p-2 bg-gray-200 dark:bg-gray-600 rounded-full" data-tooltip="Toggle dark/light mode">
                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
                </svg>
            </button>
        </div>
        <div class="bg-white dark:bg-gray-700 rounded-lg shadow-lg p-4 sm:p-6">
            <div class="drop-zone rounded-lg p-6 text-center" id="drop-zone">
                <p class="text-gray-600 dark:text-gray-300">Drag and drop PDFs or click to select</p>
                <input type="file" id="pdf-input" accept="application/pdf" multiple hidden aria-describedby="drop-zone-desc">
                <span id="drop-zone-desc" class="sr-only">Upload PDFs by dragging and dropping or clicking to select</span>
            </div>
            <div class="file-selection-info text-sm text-gray-600 dark:text-gray-300 text-center mt-2" id="file-selection-info" aria-live="polite"></div>
            <div class="file-preview-gallery flex flex-wrap gap-2 mt-4 justify-center" id="file-preview-gallery"></div>
            <div class="controls mt-6 space-y-4">
                <details class="bg-gray-50 dark:bg-gray-600 rounded-lg p-4">
                    <summary class="cursor-pointer font-semibold text-gray-800 dark:text-white">PDF Settings</summary>
                    <div class="mt-4 space-y-4">
                        <div>
                            <label for="compression-level" class="block text-sm font-medium">Compression Level (1-10):</label>
                            <input type="range" id="compression-level" min="1" max="10" value="5" class="w-full h-2 bg-gray-200 rounded-lg cursor-pointer">
                            <span class="text-sm text-gray-600 dark:text-gray-300" id="compression-value">5</span>
                        </div>
                        <div>
                            <label for="pages" class="block text-sm font-medium">Pages to Extract (e.g., 1,3-5):</label>
                            <input type="text" id="pages" placeholder="All pages" class="w-full p-2 border border-gray-200 dark:border-gray-600 rounded-lg">
                        </div>
                        <div>
                            <label for="action" class="block text-sm font-medium">Action:</label>
                            <select id="action" class="w-full p-2 border border-gray-200 dark:border-gray-600 rounded-lg">
                                <option value="compress">Compress</option>
                                <option value="merge">Merge</option>
                                <option value="extract">Extract Pages</option>
                            </select>
                        </div>
                    </div>
                </details>
                <details class="bg-gray-50 dark:bg-gray-600 rounded-lg p-4">
                    <summary class="cursor-pointer font-semibold text-gray-800 dark:text-white">Watermark Settings</summary>
                    <div class="mt-4 space-y-4">
                        <div>
                            <label for="watermark-text" class="block text-sm font-medium">Watermark Text (optional):</label>
                            <input type="text" id="watermark-text" placeholder="Enter watermark text" class="w-full p-2 border border-gray-200 dark:border-gray-600 rounded-lg">
                        </div>
                        <div>
                            <label for="watermark-image" class="block text-sm font-medium">Watermark Image (optional):</label>
                            <input type="file" id="watermark-image" accept="image/png,image/jpeg" class="w-full p-2 border border-gray-200 dark:border-gray-600 rounded-lg">
                        </div>
                        <div>
                            <label for="watermark-font" class="block text-sm font-medium">Watermark Font:</label>
                            <select id="watermark-font" class="w-full p-2 border border-gray-200 dark:border-gray-600 rounded-lg">
                                <option value="Helvetica">Helvetica</option>
                                <option value="TimesRoman">Times Roman</option>
                                <option value="Courier">Courier</option>
                            </select>
                        </div>
                        <div>
                            <label for="watermark-color" class="block text-sm font-medium">Watermark Color:</label>
                            <input type="color" id="watermark-color" value="#808080" class="w-full h-10 border border-gray-200 dark:border-gray-600 rounded-lg">
                        </div>
                        <div>
                            <label for="watermark-bold" class="flex items-center">
                                <input type="checkbox" id="watermark-bold" class="mr-2">
                                <span class="text-sm font-medium">Bold Watermark</span>
                            </label>
                        </div>
                        <div>
                            <label for="watermark-size" class="block text-sm font-medium">Watermark Size (%):</label>
                            <input type="number" id="watermark-size" min="10" max="200" value="100" placeholder="100" class="w-full p-2 border border-gray-200 dark:border-gray-600 rounded-lg">
                        </div>
                        <div>
                            <label for="watermark-opacity" class="block text-sm font-medium">Watermark Opacity (%):</label>
                            <input type="range" id="watermark-opacity" min="0" max="100" value="50" class="w-full h-2 bg-gray-200 rounded-lg cursor-pointer">
                            <span class="text-sm text-gray-600 dark:text-gray-300" id="watermark-opacity-value">50</span>
                        </div>
                        <div>
                            <label for="watermark-rotation" class="block text-sm font-medium">Watermark Rotation (degrees, 0-360):</label>
                            <input type="number" id="watermark-rotation" min="0" max="360" value="0" placeholder="0" class="w-full p-2 border border-gray-200 dark:border-gray-600 rounded-lg">
                        </div>
                        <div>
                            <label for="watermark-position" class="block text-sm font-medium">Watermark Position:</label>
                            <select id="watermark-position" class="w-full p-2 border border-gray-200 dark:border-gray-600 rounded-lg">
                                <option value="center">Center</option>
                                <option value="top-left">Top-Left</option>
                                <option value="top-right">Top-Right</option>
                                <option value="bottom-left">Bottom-Left</option>
                                <option value="bottom-right">Bottom-Right</option>
                            </select>
                        </div>
                        <div class="grid grid-cols-2 gap-4">
                            <div>
                                <label for="watermark-margin-top" class="block text-sm font-medium">Top Margin (pt):</label>
                                <input type="number" id="watermark-margin-top" min="0" max="200" value="20" class="w-full p-2 border border-gray-200 dark:border-gray-600 rounded-lg">
                            </div>
                            <div>
                                <label for="watermark-margin-bottom" class="block text-sm font-medium">Bottom Margin (pt):</label>
                                <input type="number" id="watermark-margin-bottom" min="0" max="200" value="20" class="w-full p-2 border border-gray-200 dark:border-gray-600 rounded-lg">
                            </div>
                            <div>
                                <label for="watermark-margin-left" class="block text-sm font-medium">Left Margin (pt):</label>
                                <input type="number" id="watermark-margin-left" min="0" max="200" value="20" class="w-full p-2 border border-gray-200 dark:border-gray-600 rounded-lg">
                            </div>
                            <div>
                                <label for="watermark-margin-right" class="block text-sm font-medium">Right Margin (pt):</label>
                                <input type="number" id="watermark-margin-right" min="0" max="200" value="20" class="w-full p-2 border border-gray-200 dark:border-gray-600 rounded-lg">
                            </div>
                        </div>
                        <div class="grid grid-cols-2 gap-4">
                            <div>
                                <label for="watermark-offset-x" class="block text-sm font-medium">X Offset (pt, -100 to 100):</label>
                                <input type="number" id="watermark-offset-x" min="-100" max="100" value="0" class="w-full p-2 border border-gray-200 dark:border-gray-600 rounded-lg">
                            </div>
                            <div>
                                <label for="watermark-offset-y" class="block text-sm font-medium">Y Offset (pt, -100 to 100):</label>
                                <input type="number" id="watermark-offset-y" min="-100" max="100" value="0" class="w-full p-2 border border-gray-200 dark:border-gray-600 rounded-lg">
                            </div>
                        </div>
                        <div>
                            <label class="block text-sm font-medium">Watermark Preview:</label>
                            <canvas id="watermark-preview" class="w-full max-w-xs mx-auto border border-gray-200 dark:border-gray-600 rounded-lg" style="max-height: 200px;"></canvas>
                        </div>
                        <div class="flex space-x-2">
                            <button onclick="saveTemplate()" class="p-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700" data-tooltip="Save watermark settings">Save Template</button>
                            <select id="template-select" class="p-2 border border-gray-200 dark:border-gray-600 rounded-lg">
                                <option value="">Select Template</option>
                            </select>
                        </div>
                    </div>
                </details>
                <div class="flex flex-wrap gap-2">
                    <button onclick="processPDFs()" class="p-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700" data-tooltip="Process selected PDFs">Process PDFs</button>
                    <button onclick="resetForm()" class="p-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600" data-tooltip="Reset all settings">Reset</button>
                    <button onclick="openPreviewModal()" class="p-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 hidden" id="preview-button" data-tooltip="View processed PDFs">View PDFs</button>
                </div>
            </div>
            <div class="mt-4 text-sm text-gray-600 dark:text-gray-300" id="stats" aria-live="polite">
                <div class="w-full h-2 bg-gray-200 dark:bg-gray-600 rounded-full overflow-hidden" id="progress-container" style="display: none;">
                    <div class="h-full bg-blue-600 transition-all duration-300" id="progress-bar"></div>
                </div>
                <div class="mt-2" id="progress-log"></div>
            </div>
            <div class="text-red-500 text-sm mt-2 text-center" id="error-message" role="alert"></div>
        </div>
        <div class="modal fixed inset-0 bg-black bg-opacity-90 hidden flex items-center justify-center" id="preview-modal">
            <div class="modal-content bg-white dark:bg-gray-700 rounded-lg p-4 sm:p-6 max-w-4xl w-full max-h-[90vh] overflow-y-auto">
                <div id="pdf-preview"></div>
                <div class="modal-footer sticky bottom-0 bg-white dark:bg-gray-700 p-4 border-t border-gray-200 dark:border-gray-600 text-center" id="modal-footer"></div>
            </div>
            <button onclick="closePreviewModal()" class="absolute top-4 right-4 p-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600" data-tooltip="Close preview">Close</button>
        </div>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf-lib/1.17.1/pdf-lib.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.min.js"></script>
    <script>
        pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js';

        // Theme toggle
        function toggleTheme() {
            document.body.classList.toggle('dark');
            localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
            updateLivePreview(); // Refresh preview for dark mode
        }
        if (localStorage.getItem('theme') === 'dark') {
            document.body.classList.add('dark');
        }

        // File handling
        const dropZone = document.getElementById('drop-zone');
        const pdfInput = document.getElementById('pdf-input');
        const fileSelectionInfo = document.getElementById('file-selection-info');
        const filePreviewGallery = document.getElementById('file-preview-gallery');

        function updateFileSelectionInfo() {
            const files = pdfInput.files;
            if (files.length === 0) {
                fileSelectionInfo.textContent = '';
                filePreviewGallery.innerHTML = '';
            } else if (files.length === 1) {
                fileSelectionInfo.textContent = `Selected: ${files[0].name}`;
            } else {
                fileSelectionInfo.textContent = `${files.length} PDFs selected`;
            }
            updateFilePreviewGallery();
        }

        async function updateFilePreviewGallery() {
            filePreviewGallery.innerHTML = '';
            const files = Array.from(pdfInput.files);
            for (let index = 0; index < files.length; index++) {
                const file = files[index];
                const reader = new FileReader();
                reader.onload = async (e) => {
                    try {
                        const pdf = await pdfjsLib.getDocument({ data: e.target.result }).promise;
                        const page = await pdf.getPage(1);
                        const viewport = page.getViewport({ scale: 0.2 });
                        const canvas = document.createElement('canvas');
                        canvas.width = viewport.width;
                        canvas.height = viewport.height;
                        const context = canvas.getContext('2d');
                        await page.render({ canvasContext: context, viewport }).promise;

                        const previewItem = document.createElement('div');
                        previewItem.className = 'preview-item relative rounded-lg border border-gray-200 dark:border-gray-600 overflow-hidden';
                        previewItem.innerHTML = `
                            <canvas></canvas>
                            <button class="remove-file absolute top-1 right-1 bg-red-500 text-white rounded-full w-5 h-5 flex items-center justify-center text-xs hover:bg-red-600" aria-label="Remove ${file.name}" data-index="${index}">×</button>
                        `;
                        previewItem.querySelector('canvas').getContext('2d').drawImage(canvas, 0, 0);
                        filePreviewGallery.appendChild(previewItem);
                        pdf.destroy();
                    } catch (e) {
                        const previewItem = document.createElement('div');
                        previewItem.className = 'preview-item relative rounded-lg border border-gray-200 dark:border-gray-600 overflow-hidden';
                        previewItem.innerHTML = `
                            <span class="text-xs text-gray-600 dark:text-gray-300 p-2">${file.name}</span>
                            <button class="remove-file absolute top-1 right-1 bg-red-500 text-white rounded-full w-5 h-5 flex items-center justify-center text-xs hover:bg-red-600" aria-label="Remove ${file.name}" data-index="${index}">×</button>
                        `;
                        filePreviewGallery.appendChild(previewItem);
                    }
                };
                reader.readAsArrayBuffer(file);
            }
        }

        filePreviewGallery.addEventListener('click', (e) => {
            if (e.target.classList.contains('remove-file')) {
                const index = parseInt(e.target.dataset.index);
                const files = Array.from(pdfInput.files);
                const newFiles = files.filter((_, i) => i !== index);
                const dataTransfer = new DataTransfer();
                newFiles.forEach(file => dataTransfer.items.add(file));
                pdfInput.files = dataTransfer.files;
                updateFileSelectionInfo();
            }
        });

        dropZone.addEventListener('click', () => pdfInput.click());
        dropZone.addEventListener('dragover', (e) => {
            e.preventDefault();
            dropZone.classList.add('dragover');
        });
        dropZone.addEventListener('dragleave', () => {
            dropZone.classList.remove('dragover');
        });
        dropZone.addEventListener('drop', (e) => {
            e.preventDefault();
            dropZone.classList.remove('dragover');
            pdfInput.files = e.dataTransfer.files;
            updateFileSelectionInfo();
        });
        pdfInput.addEventListener('change', updateFileSelectionInfo);

        // Live watermark preview
        async function updateLivePreview() {
            const canvas = document.getElementById('watermark-preview');
            if (!canvas) return;
            const context = canvas.getContext('2d');
            const watermarkText = document.getElementById('watermark-text').value;
            const watermarkImage = document.getElementById('watermark-image').files[0];
            const watermarkSize = parseInt(document.getElementById('watermark-size').value) || 100;
            const watermarkOpacity = parseInt(document.getElementById('watermark-opacity').value) / 100 || 0.5;
            const watermarkRotation = parseInt(document.getElementById('watermark-rotation').value) || 0;
            const watermarkPosition = document.getElementById('watermark-position').value;
            const watermarkFont = document.getElementById('watermark-font').value;
            const watermarkColor = document.getElementById('watermark-color').value;
            const watermarkBold = document.getElementById('watermark-bold').checked;
            const marginTop = parseInt(document.getElementById('watermark-margin-top').value) || 20;
            const marginBottom = parseInt(document.getElementById('watermark-margin-bottom').value) || 20;
            const marginLeft = parseInt(document.getElementById('watermark-margin-left').value) || 20;
            const marginRight = parseInt(document.getElementById('watermark-margin-right').value) || 20;
            const offsetX = parseInt(document.getElementById('watermark-offset-x').value) || 0;
            const offsetY = parseInt(document.getElementById('watermark-offset-y').value) || 0;

            try {
                const pdfDoc = await PDFLib.PDFDocument.create();
                const page = pdfDoc.addPage([595, 842]);
                const { width, height } = page.getSize();
                const scaleFactor = Math.max(10, Math.min(200, watermarkSize)) / 100;

                if (watermarkText) {
                    const defaultTextSize = Math.min(width, height) * 0.05;
                    const textSize = defaultTextSize * scaleFactor;
                    const textWidth = textSize * watermarkText.length * 0.6;
                    const textHeight = textSize;
                    const textPosition = getWatermarkPosition(watermarkPosition, width, height, textWidth, textHeight, marginTop, marginBottom, marginLeft, marginRight, offsetX, offsetY);
                    page.drawText(watermarkText, {
                        x: textPosition.x,
                        y: textPosition.y,
                        size: textSize,
                        font: await pdfDoc.embedFont(PDFLib.StandardFonts[watermarkBold ? `${watermarkFont}Bold` : watermarkFont] || PDFLib.StandardFonts.Helvetica),
                        color: PDFLib.rgb(
                            parseInt(watermarkColor.slice(1, 3), 16) / 255,
                            parseInt(watermarkColor.slice(3, 5), 16) / 255,
                            parseInt(watermarkColor.slice(5, 7), 16) / 255
                        ),
                        opacity: watermarkOpacity,
                        rotate: PDFLib.degrees(watermarkRotation),
                        textAlign: watermarkPosition === 'center' ? 'center' : 'left',
                        textBaseline: watermarkPosition === 'center' ? 'middle' : 'top'
                    });
                }
                if (watermarkImage) {
                    const imgArrayBuffer = await watermarkImage.arrayBuffer();
                    const img = watermarkImage.type === 'image/png' ?
                        await pdfDoc.embedPng(imgArrayBuffer) :
                        await pdfDoc.embedJpg(imgArrayBuffer);
                    const defaultImgWidth = 80;
                    const defaultImgHeight = 80;
                    const imgWidth = defaultImgWidth * scaleFactor;
                    const imgHeight = defaultImgHeight * scaleFactor;
                    const imgPosition = getWatermarkPosition(watermarkPosition, width, height, imgWidth, imgHeight, marginTop, marginBottom, marginLeft, marginRight, offsetX, offsetY);
                    page.drawImage(img, {
                        x: imgPosition.x,
                        y: imgPosition.y,
                        width: imgWidth,
                        height: imgHeight,
                        opacity: watermarkOpacity,
                        rotate: PDFLib.degrees(watermarkRotation)
                    });
                }

                const pdfBytes = await pdfDoc.save();
                const pdf = await pdfjsLib.getDocument(pdfBytes).promise;
                const pdfPage = await pdf.getPage(1);
                const viewport = pdfPage.getViewport({ scale: 0.5 });
                canvas.width = viewport.width;
                canvas.height = viewport.height;
                await pdfPage.render({ canvasContext: context, viewport }).promise;
                pdf.destroy();
            } catch (e) {
                context.clearRect(0, 0, canvas.width, canvas.height);
                context.fillStyle = document.body.classList.contains('dark') ? '#d1d5db' : '#4b5563';
                context.fillText('Preview unavailable', 10, 50);
            }
        }

        ['watermark-text', 'watermark-image', 'watermark-size', 'watermark-opacity', 'watermark-rotation', 'watermark-position', 'watermark-font', 'watermark-color', 'watermark-bold', 'watermark-margin-top', 'watermark-margin-bottom', 'watermark-margin-left', 'watermark-margin-right', 'watermark-offset-x', 'watermark-offset-y'].forEach(id => {
            const input = document.getElementById(id);
            if (input) {
                input.addEventListener(id === 'watermark-image' ? 'change' : 'input', updateLivePreview);
            }
        });
        updateLivePreview();

        // Template handling
        function saveTemplate() {
            const template = {
                text: document.getElementById('watermark-text').value,
                size: document.getElementById('watermark-size').value,
                opacity: document.getElementById('watermark-opacity').value,
                rotation: document.getElementById('watermark-rotation').value,
                position: document.getElementById('watermark-position').value,
                font: document.getElementById('watermark-font').value,
                color: document.getElementById('watermark-color').value,
                bold: document.getElementById('watermark-bold').checked,
                marginTop: document.getElementById('watermark-margin-top').value,
                marginBottom: document.getElementById('watermark-margin-bottom').value,
                marginLeft: document.getElementById('watermark-margin-left').value,
                marginRight: document.getElementById('watermark-margin-right').value,
                offsetX: document.getElementById('watermark-offset-x').value,
                offsetY: document.getElementById('watermark-offset-y').value
            };
            const templates = JSON.parse(localStorage.getItem('watermarkTemplates') || '[]');
            templates.push(template);
            localStorage.setItem('watermarkTemplates', JSON.stringify(templates));
            updateTemplateDropdown();
        }

        function updateTemplateDropdown() {
            const select = document.getElementById('template-select');
            const templates = JSON.parse(localStorage.getItem('watermarkTemplates') || '[]');
            select.innerHTML = '<option value="">Select Template</option>' +
                templates.map((t, i) => `<option value="${i}">${t.text || 'Template ' + (i + 1)}</option>`).join('');
        }

        document.getElementById('template-select').addEventListener('change', (e) => {
            const templates = JSON.parse(localStorage.getItem('watermarkTemplates') || '[]');
            const template = templates[e.target.value];
            if (template) {
                document.getElementById('watermark-text').value = template.text;
                document.getElementById('watermark-size').value = template.size;
                document.getElementById('watermark-opacity').value = template.opacity;
                document.getElementById('watermark-opacity-value').textContent = template.opacity;
                document.getElementById('watermark-rotation').value = template.rotation;
                document.getElementById('watermark-position').value = template.position;
                document.getElementById('watermark-font').value = template.font;
                document.getElementById('watermark-color').value = template.color;
                document.getElementById('watermark-bold').checked = template.bold;
                document.getElementById('watermark-margin-top').value = template.marginTop;
                document.getElementById('watermark-margin-bottom').value = template.marginBottom;
                document.getElementById('watermark-margin-left').value = template.marginLeft;
                document.getElementById('watermark-margin-right').value = template.marginRight;
                document.getElementById('watermark-offset-x').value = template.offsetX;
                document.getElementById('watermark-offset-y').value = template.offsetY;
                updateLivePreview();
            }
        });
        updateTemplateDropdown();

        // Input updates
        document.getElementById('compression-level').addEventListener('input', () => {
            document.getElementById('compression-value').textContent = document.getElementById('compression-level').value;
        });
        document.getElementById('watermark-opacity').addEventListener('input', () => {
            document.getElementById('watermark-opacity-value').textContent = document.getElementById('watermark-opacity').value;
        });

        // Modal handling
        function openPreviewModal() {
            const modal = document.getElementById('preview-modal');
            if (modal) {
                modal.style.display = 'flex';
                document.getElementById('pdf-preview').focus();
            }
        }

        function closePreviewModal() {
            const modal = document.getElementById('preview-modal');
            if (modal) {
                modal.style.display = 'none';
            }
        }

        // Reset form
        function resetForm() {
            pdfInput.value = '';
            document.getElementById('compression-level').value = 5;
            document.getElementById('compression-value').textContent = '5';
            document.getElementById('watermark-text').value = '';
            document.getElementById('watermark-image').value = '';
            document.getElementById('watermark-size').value = '100';
            document.getElementById('watermark-opacity').value = '50';
            document.getElementById('watermark-opacity-value').textContent = '50';
            document.getElementById('watermark-rotation').value = '0';
            document.getElementById('watermark-position').value = 'center';
            document.getElementById('watermark-font').value = 'Helvetica';
            document.getElementById('watermark-color').value = '#808080';
            document.getElementById('watermark-bold').checked = false;
            document.getElementById('watermark-margin-top').value = '20';
            document.getElementById('watermark-margin-bottom').value = '20';
            document.getElementById('watermark-margin-left').value = '20';
            document.getElementById('watermark-margin-right').value = '20';
            document.getElementById('watermark-offset-x').value = '0';
            document.getElementById('watermark-offset-y').value = '0';
            document.getElementById('pages').value = '';
            document.getElementById('action').value = 'compress';
            document.getElementById('pdf-preview').innerHTML = '<div class="modal-footer" id="modal-footer"></div>';
            document.getElementById('error-message').innerHTML = '';
            document.getElementById('stats').innerHTML = '<div class="w-full h-2 bg-gray-200 dark:bg-gray-600 rounded-full overflow-hidden" id="progress-container" style="display: none;"><div class="h-full bg-blue-600 transition-all duration-300" id="progress-bar"></div></div><div class="mt-2" id="progress-log"></div>';
            document.getElementById('preview-button').classList.add('hidden');
            fileSelectionInfo.textContent = '';
            filePreviewGallery.innerHTML = '';
            closePreviewModal();
            updateLivePreview();
        }

        // Watermark position helper
        function getWatermarkPosition(position, width, height, contentWidth, contentHeight, marginTop, marginBottom, marginLeft, marginRight, offsetX, offsetY) {
            let x, y;
            switch (position) {
                case 'top-left':
                    x = marginLeft;
                    y = height - contentHeight - marginTop;
                    break;
                case 'top-right':
                    x = width - contentWidth - marginRight;
                    y = height - contentHeight - marginTop;
                    break;
                case 'bottom-left':
                    x = marginLeft;
                    y = marginBottom;
                    break;
                case 'bottom-right':
                    x = width - contentWidth - marginRight;
                    y = marginBottom;
                    break;
                case 'center':
                default:
                    x = (width - contentWidth) / 2;
                    y = (height - contentHeight) / 2;
                    break;
            }
            return { x: x + offsetX, y: y + offsetY };
        }

        // Main processing function
        async function processPDFs() {
            const files = Array.from(pdfInput.files);
            const compressionLevel = parseInt(document.getElementById('compression-level').value);
            const watermarkText = document.getElementById('watermark-text').value;
            const watermarkImage = document.getElementById('watermark-image').files[0];
            const watermarkSize = parseInt(document.getElementById('watermark-size').value) || 100;
            const watermarkOpacity = parseInt(document.getElementById('watermark-opacity').value) / 100 || 0.5;
            const watermarkRotation = parseInt(document.getElementById('watermark-rotation').value) || 0;
            const watermarkPosition = document.getElementById('watermark-position').value;
            const watermarkFont = document.getElementById('watermark-font').value;
            const watermarkColor = document.getElementById('watermark-color').value;
            const watermarkBold = document.getElementById('watermark-bold').checked;
            const marginTop = parseInt(document.getElementById('watermark-margin-top').value) || 20;
            const marginBottom = parseInt(document.getElementById('watermark-margin-bottom').value) || 20;
            const marginLeft = parseInt(document.getElementById('watermark-margin-left').value) || 20;
            const marginRight = parseInt(document.getElementById('watermark-margin-right').value) || 20;
            const offsetX = parseInt(document.getElementById('watermark-offset-x').value) || 0;
            const offsetY = parseInt(document.getElementById('watermark-offset-y').value) || 0;
            const pagesInput = document.getElementById('pages').value;
            const action = document.getElementById('action').value;
            const previewDiv = document.getElementById('pdf-preview');
            const errorDiv = document.getElementById('error-message');
            const statsDiv = document.getElementById('stats');
            const progressContainer = document.getElementById('progress-container');
            const progressBar = document.getElementById('progress-bar');
            const progressLog = document.getElementById('progress-log');
            const processButton = document.querySelector('button[onclick="processPDFs()"]');
            const previewButton = document.getElementById('preview-button');
            const modalFooter = document.getElementById('modal-footer');

            // Validation
            errorDiv.innerHTML = '';
            if (files.length === 0) {
                errorDiv.textContent = 'Please select at least one PDF.';
                return;
            }
            if (watermarkImage && watermarkImage.size > 5 * 1024 * 1024) {
                errorDiv.textContent = 'Watermark image too large. Maximum size is 5MB.';
                return;
            }
            if (watermarkSize < 10 || watermarkSize > 200) {
                errorDiv.textContent = 'Watermark size must be between 10% and 200%.';
                return;
            }
            if (watermarkRotation < 0 || watermarkRotation > 360) {
                errorDiv.textContent = 'Watermark rotation must be between 0 and 360 degrees.';
                return;
            }
            if (marginTop < 0 || marginTop > 200 || marginBottom < 0 || marginBottom > 200 || marginLeft < 0 || marginLeft > 200 || marginRight < 0 || marginRight > 200) {
                errorDiv.textContent = 'Margins must be between 0 and 200 points.';
                return;
            }
            if (offsetX < -100 || offsetX > 100 || offsetY < -100 || offsetY > 100) {
                errorDiv.textContent = 'Offsets must be between -100 and 100 points.';
                return;
            }
            if (pagesInput && !/^\d+(-\d+)?(?:,\d+(-\d+)?)*$/.test(pagesInput)) {
                errorDiv.textContent = 'Invalid page range format. Use e.g., 1,3-5.';
                return;
            }

            // Initialize UI
            previewDiv.innerHTML = '<div class="modal-footer" id="modal-footer"></div>';
            modalFooter.innerHTML = '';
            statsDiv.innerHTML = `<div class="w-full h-2 bg-gray-200 dark:bg-gray-600 rounded-full overflow-hidden" id="progress-container"><div class="h-full bg-blue-600 transition-all duration-300" id="progress-bar"></div></div><div class="mt-2" id="progress-log">Processing 0/${files.length} PDF(s)...</div>`;
            progressContainer.style.display = 'block';
            progressBar.style.width = '0%';
            processButton.disabled = true;
            previewButton.classList.add('hidden');
            openPreviewModal();

            const processedPDFs = [];
            let totalOriginalSize = 0;
            let totalProcessedSize = 0;
            let processedCount = 0;

            const parsePages = (input) => {
                if (!input) return null;
                const pages = [];
                input.split(',').forEach(range => {
                    if (range.includes('-')) {
                        const [start, end] = range.split('-').map(Number);
                        for (let i = start; i <= end; i++) pages.push(i - 1);
                    } else {
                        pages.push(parseInt(range) - 1);
                    }
                });
                return pages;
            };

            for (let index = 0; index < files.length; index++) {
                const file = files[index];
                if (file.type !== 'application/pdf') {
                    errorDiv.innerHTML += `<p>Invalid file type: ${file.name}. Please use PDF.</p>`;
                    progressLog.innerHTML += `<p>Skipped ${file.name}: Invalid file type</p>`;
                    continue;
                }

                totalOriginalSize += file.size;
                progressLog.innerHTML += `<p>Processing ${file.name}...</p>`;

                const previewItem = Array.from(filePreviewGallery.children).find(item =>
                    item.querySelector('.remove-file')?.dataset.index === index.toString()
                );
                if (previewItem) {
                    previewItem.innerHTML += '<div class="loading"></div>';
                }

                const itemDiv = document.createElement('div');
                itemDiv.className = 'pdf-item bg-gray-50 dark:bg-gray-600 rounded-lg p-4 flex flex-col items-center gap-2';
                itemDiv.innerHTML = '<div class="loading mx-auto"></div>';
                previewDiv.appendChild(itemDiv);

                try {
                    const arrayBuffer = await file.arrayBuffer();
                    const pdfDoc = await PDFLib.PDFDocument.load(arrayBuffer);
                    let outputDoc;

                    if (action === 'merge' && files.length > 1) {
                        continue;
                    } else if (action === 'extract') {
                        outputDoc = await PDFLib.PDFDocument.create();
                        const pages = parsePages(pagesInput) || Array.from({ length: pdfDoc.getPageCount() }, (_, i) => i);
                        if (pages.some(p => p < 0 || p >= pdfDoc.getPageCount())) {
                            throw new Error('Invalid page range');
                        }
                        const copiedPages = await outputDoc.copyPages(pdfDoc, pages);
                        copiedPages.forEach(page => outputDoc.addPage(page));
                    } else {
                        outputDoc = pdfDoc;
                    }

                    if (watermarkText || watermarkImage) {
                        const pages = outputDoc.getPages();
                        for (const page of pages) {
                            const { width, height } = page.getSize();
                            const scaleFactor = Math.max(10, Math.min(200, watermarkSize)) / 100;
                            if (watermarkText) {
                                const defaultTextSize = Math.min(width, height) * 0.05;
                                const textSize = defaultTextSize * scaleFactor;
                                const textWidth = textSize * watermarkText.length * 0.6;
                                const textHeight = textSize;
                                const textPosition = getWatermarkPosition(watermarkPosition, width, height, textWidth, textHeight, marginTop, marginBottom, marginLeft, marginRight, offsetX, offsetY);
                                page.drawText(watermarkText, {
                                    x: textPosition.x,
                                    y: textPosition.y,
                                    size: textSize,
                                    font: await outputDoc.embedFont(PDFLib.StandardFonts[watermarkBold ? `${watermarkFont}Bold` : watermarkFont] || PDFLib.StandardFonts.Helvetica),
                                    color: PDFLib.rgb(
                                        parseInt(watermarkColor.slice(1, 3), 16) / 255,
                                        parseInt(watermarkColor.slice(3, 5), 16) / 255,
                                        parseInt(watermarkColor.slice(5, 7), 16) / 255
                                    ),
                                    opacity: watermarkOpacity,
                                    rotate: PDFLib.degrees(watermarkRotation),
                                    textAlign: watermarkPosition === 'center' ? 'center' : 'left',
                                    textBaseline: watermarkPosition === 'center' ? 'middle' : 'top'
                                });
                            }
                            if (watermarkImage) {
                                const imgArrayBuffer = await watermarkImage.arrayBuffer();
                                const img = watermarkImage.type === 'image/png' ?
                                    await outputDoc.embedPng(imgArrayBuffer) :
                                    await outputDoc.embedJpg(imgArrayBuffer);
                                const defaultImgWidth = 80;
                                const defaultImgHeight = 80;
                                const imgWidth = defaultImgWidth * scaleFactor;
                                const imgHeight = defaultImgHeight * scaleFactor;
                                const imgPosition = getWatermarkPosition(watermarkPosition, width, height, imgWidth, imgHeight, marginTop, marginBottom, marginLeft, marginRight, offsetX, offsetY);
                                page.drawImage(img, {
                                    x: imgPosition.x,
                                    y: imgPosition.y,
                                    width: imgWidth,
                                    height: imgHeight,
                                    opacity: watermarkOpacity,
                                    rotate: PDFLib.degrees(watermarkRotation)
                                });
                            }
                        }
                    }

                    const pdfBytes = await outputDoc.save();
                    const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' });
                    const pdfUrl = URL.createObjectURL(pdfBlob);

                    const pdf = await pdfjsLib.getDocument(pdfUrl).promise;
                    const page = await pdf.getPage(1);
                    const viewport = page.getViewport({ scale: 0.5 });
                    const canvas = document.createElement('canvas');
                    canvas.width = viewport.width;
                    canvas.height = viewport.height;
                    const context = canvas.getContext('2d');
                    await page.render({ canvasContext: context, viewport }).promise;

                    const infoDiv = document.createElement('div');
                    infoDiv.className = 'text-sm text-gray-600 dark:text-gray-300';
                    const originalSize = (file.size / 1024).toFixed(2);
                    const processedSize = (pdfBytes.length / 1024).toFixed(2);
                    infoDiv.textContent = `Original: ${originalSize} KB | Processed: ${processedSize} KB`;

                    const downloadLink = document.createElement('a');
                    downloadLink.href = pdfUrl;
                    downloadLink.download = `processed_${file.name}`;
                    downloadLink.className = 'p-2 bg-green-600 text-white rounded-lg hover:bg-green-700';
                    downloadLink.textContent = 'Download';
                    downloadLink.setAttribute('aria-label', `Download processed ${file.name}`);

                    itemDiv.innerHTML = '';
                    itemDiv.appendChild(canvas);
                    itemDiv.appendChild(infoDiv);
                    itemDiv.appendChild(downloadLink);

                    if (previewItem) {
                        previewItem.querySelector('.loading')?.remove();
                    }

                    totalProcessedSize += pdfBytes.length;
                    processedPDFs.push({ url: pdfUrl, name: file.name });
                    processedCount++;
                    progressLog.innerHTML += `<p>Completed ${file.name}</p>`;
                    updateProgress();
                    updateStats();
                    updateDownloadAllButton();
                    pdf.destroy();
                } catch (e) {
                    itemDiv.remove();
                    if (previewItem) {
                        previewItem.querySelector('.loading')?.remove();
                    }
                    errorDiv.innerHTML += `<p>Error processing ${file.name}: ${e.message}</p>`;
                    progressLog.innerHTML += `<p>Error processing ${file.name}: ${e.message}</p>`;
                    processedCount++;
                    updateProgress();
                }
            }

            if (action === 'merge' && files.length > 1) {
                try {
                    progressLog.innerHTML += '<p>Merging PDFs...</p>';
                    const mergedDoc = await PDFLib.PDFDocument.create();
                    for (const file of files) {
                        const arrayBuffer = await file.arrayBuffer();
                        const pdfDoc = await PDFLib.PDFDocument.load(arrayBuffer);
                        const pages = await mergedDoc.copyPages(pdfDoc, pdfDoc.getPageIndices());
                        pages.forEach(page => mergedDoc.addPage(page));
                    }

                    if (watermarkText || watermarkImage) {
                        const pages = mergedDoc.getPages();
                        for (const page of pages) {
                            const { width, height } = page.getSize();
                            const scaleFactor = Math.max(10, Math.min(200, watermarkSize)) / 100;
                            if (watermarkText) {
                                const defaultTextSize = Math.min(width, height) * 0.05;
                                const textSize = defaultTextSize * scaleFactor;
                                const textWidth = textSize * watermarkText.length * 0.6;
                                const textHeight = textSize;
                                const textPosition = getWatermarkPosition(watermarkPosition, width, height, textWidth, textHeight, marginTop, marginBottom, marginLeft, marginRight, offsetX, offsetY);
                                page.drawText(watermarkText, {
                                    x: textPosition.x,
                                    y: textPosition.y,
                                    size: textSize,
                                    font: await mergedDoc.embedFont(PDFLib.StandardFonts[watermarkBold ? `${watermarkFont}Bold` : watermarkFont] || PDFLib.StandardFonts.Helvetica),
                                    color: PDFLib.rgb(
                                        parseInt(watermarkColor.slice(1, 3), 16) / 255,
                                        parseInt(watermarkColor.slice(3, 5), 16) / 255,
                                        parseInt(watermarkColor.slice(5, 7), 16) / 255
                                    ),
                                    opacity: watermarkOpacity,
                                    rotate: PDFLib.degrees(watermarkRotation),
                                    textAlign: watermarkPosition === 'center' ? 'center' : 'left',
                                    textBaseline: watermarkPosition === 'center' ? 'middle' : 'top'
                                });
                            }
                            if (watermarkImage) {
                                const imgArrayBuffer = await watermarkImage.arrayBuffer();
                                const img = watermarkImage.type === 'image/png' ?
                                    await mergedDoc.embedPng(imgArrayBuffer) :
                                    await mergedDoc.embedJpg(imgArrayBuffer);
                                const defaultImgWidth = 80;
                                const defaultImgHeight = 80;
                                const imgWidth = defaultImgWidth * scaleFactor;
                                const imgHeight = defaultImgHeight * scaleFactor;
                                const imgPosition = getWatermarkPosition(watermarkPosition, width, height, imgWidth, imgHeight, marginTop, marginBottom, marginLeft, marginRight, offsetX, offsetY);
                                page.drawImage(img, {
                                    x: imgPosition.x,
                                    y: imgPosition.y,
                                    width: imgWidth,
                                    height: imgHeight,
                                    opacity: watermarkOpacity,
                                    rotate: PDFLib.degrees(watermarkRotation)
                                });
                            }
                        }
                    }

                    const pdfBytes = await mergedDoc.save();
                    const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' });
                    const pdfUrl = URL.createObjectURL(pdfBlob);

                    const itemDiv = document.createElement('div');
                    itemDiv.className = 'pdf-item bg-gray-50 dark:bg-gray-600 rounded-lg p-4 flex flex-col items-center gap-2';
                    const pdf = await pdfjsLib.getDocument(pdfUrl).promise;
                    const page = await pdf.getPage(1);
                    const viewport = page.getViewport({ scale: 0.5 });
                    const canvas = document.createElement('canvas');
                    canvas.width = viewport.width;
                    canvas.height = viewport.height;
                    const context = canvas.getContext('2d');
                    await page.render({ canvasContext: context, viewport }).promise;

                    const infoDiv = document.createElement('div');
                    infoDiv.className = 'text-sm text-gray-600 dark:text-gray-300';
                    const processedSize = (pdfBytes.length / 1024).toFixed(2);
                    infoDiv.textContent = `Merged PDF: ${processedSize} KB`;

                    const downloadLink = document.createElement('a');
                    downloadLink.href = pdfUrl;
                    downloadLink.download = 'merged.pdf';
                    downloadLink.className = 'p-2 bg-green-600 text-white rounded-lg hover:bg-green-700';
                    downloadLink.textContent = 'Download Merged PDF';
                    downloadLink.setAttribute('aria-label', 'Download merged PDF');

                    itemDiv.appendChild(canvas);
                    itemDiv.appendChild(infoDiv);
                    itemDiv.appendChild(downloadLink);
                    previewDiv.appendChild(itemDiv);

                    totalProcessedSize += pdfBytes.length;
                    processedPDFs.push({ url: pdfUrl, name: 'merged.pdf' });
                    processedCount = files.length;
                    progressLog.innerHTML += '<p>Completed merging PDFs</p>';
                    updateProgress();
                    updateStats();
                    updateDownloadAllButton();
                    pdf.destroy();
                    Array.from(filePreviewGallery.children).forEach(item => item.querySelector('.loading')?.remove());
                } catch (e) {
                    errorDiv.innerHTML += `<p>Error merging PDFs: ${e.message}</p>`;
                    progressLog.innerHTML += `<p>Error merging PDFs: ${e.message}</p>`;
                    processedCount = files.length;
                    updateProgress();
                    Array.from(filePreviewGallery.children).forEach(item => item.querySelector('.loading')?.remove());
                }
            }

            function updateProgress() {
                const progress = (processedCount / (action === 'merge' && files.length > 1 ? 1 : files.length)) * 100;
                progressBar.style.width = `${progress}%`;
                if (processedCount === (action === 'merge' && files.length > 1 ? 1 : files.length)) {
                    progressContainer.style.display = 'none';
                }
            }

            function updateStats() {
                statsDiv.innerHTML = `<p>Processed ${processedPDFs.length}/${action === 'merge' && files.length > 1 ? 1 : files.length} PDF(s) | Original: ${(totalOriginalSize / 1024).toFixed(2)} KB | Processed: ${(totalProcessedSize / 1024).toFixed(2)} KB | Saved: ${((totalOriginalSize - totalProcessedSize) / 1024).toFixed(2)} KB</p><div class="w-full h-2 bg-gray-200 dark:bg-gray-600 rounded-full overflow-hidden" id="progress-container" style="display: ${progressContainer.style.display}"><div class="h-full bg-blue-600 transition-all duration-300" id="progress-bar" style="width: ${progressBar.style.width}"></div></div><div class="mt-2" id="progress-log">${progressLog.innerHTML}</div>`;
                if (processedPDFs.length > 0) {
                    previewButton.classList.remove('hidden');
                }
            }

            function updateDownloadAllButton() {
                if (processedPDFs.length > 1) {
                    const downloadAllButton = document.createElement('button');
                    downloadAllButton.className = 'p-2 bg-green-600 text-white rounded-lg hover:bg-green-700';
                    downloadAllButton.textContent = 'Download All as ZIP';
                    downloadAllButton.setAttribute('aria-label', 'Download all processed PDFs as ZIP');
                    downloadAllButton.onclick = async () => {
                        downloadAllButton.disabled = true;
                        const zip = new JSZip();
                        for (const pdf of processedPDFs) {
                            try {
                                const response = await fetch(pdf.url);
                                const blob = await response.blob();
                                zip.file(pdf.name, blob);
                            } catch (e) {
                                errorDiv.innerHTML += `<p>Failed to include ${pdf.name} in ZIP.</p>`;
                                progressLog.innerHTML += `<p>Failed to include ${pdf.name} in ZIP.</p>`;
                            }
                        }
                        try {
                            const content = await zip.generateAsync({ type: 'blob' });
                            const link = document.createElement('a');
                            link.href = URL.createObjectURL(content);
                            link.download = 'processed_pdfs.zip';
                            link.click();
                            URL.revokeObjectURL(link.href);
                        } catch (e) {
                            errorDiv.innerHTML += '<p>Failed to generate ZIP file.</p>';
                            progressLog.innerHTML += '<p>Failed to generate ZIP file.</p>';
                        }
                        downloadAllButton.disabled = false;
                    };
                    modalFooter.innerHTML = '';
                    modalFooter.appendChild(downloadAllButton);
                } else {
                    modalFooter.innerHTML = '';
                }
            }

            processButton.disabled = false;
        }
    </script>
</body>
</html>
Iklan
Lelah dengan iklan? Hanya dengan IDR 833 / hari, buka konten eksklusif dan nikmati pengalaman tanpa iklan. Selengkapnya.

Lelah dengan iklan? Hanya dengan IDR 833 / hari, buka konten eksklusif dan nikmati pengalaman tanpa iklan. Selengkapnya.

Beri komentar

Kamu yakin ingin keluar?

Ya, Keluar
Lelah dengan iklan? Hanya dengan IDR 833 / hari, buka konten eksklusif dan nikmati pengalaman tanpa iklan. Selengkapnya.
Lelah dengan iklan? Hanya dengan IDR 833 / hari, buka konten eksklusif dan nikmati pengalaman tanpa iklan. Selengkapnya.
Kami harap dukungan Anda via iklan ini membantu kami terus menyajikan konten bermanfaat.
Tunggu hingga iklan selesai untuk menyalin atau mendownload kode.
Situs ini menggunakan cookie untuk memberikan pengalaman menjelajah yang lebih baik. Dengan menggunakan situs web ini, Anda menyetujui penggunaan cookie kami.