HEX
Server: Apache/2.4.46 (Ubuntu)
System: Linux localhost 5.11.0-49-generic #55-Ubuntu SMP Wed Jan 12 17:36:34 UTC 2022 x86_64
User: root (0)
PHP: 7.4.16
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
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;
});