Image To PDF

Image to PDF Converter – Free Online Tool | SnapToolSet

Convert Images to PDF
in Seconds

Drag & drop your images, arrange the order, tweak settings, and export a pixel-perfect PDF — entirely in your browser.

No upload required
JPG, PNG, WEBP, GIF
Reorder & rotate
Custom page size
Zero data sent
🖼️

Drag & drop images here

or click to browse — JPG, PNG, WEBP, GIF supported

Add page numbers

Images 0

📂 Add images above to get started. You can drag to reorder them.
Pages: 0
Total size: 0 KB
Formats:
Processing…
SnapToolSet — All processing happens locally in your browser.
No files are uploaded. No data is collected. Free forever.
${img.name}
${(img.size / 1024).toFixed(1)} KB
`; card.querySelector('.img-remove').addEventListener('click', e => { e.stopPropagation(); state.images = state.images.filter(i => i.id !== img.id); renderGrid(); }); card.querySelector('.img-rotate').addEventListener('click', e => { e.stopPropagation(); img.rotation = (img.rotation + 90) % 360; renderGrid(); }); card.querySelector('.img-card-thumb').addEventListener('dblclick', () => { modalImg.src = img.dataUrl; modalImg.style.transform = `rotate(${img.rotation}deg)`; modalOverlay.classList.add('open'); }); card.addEventListener('dragstart', () => { state.dragSrc = img.id; card.classList.add('dragging'); }); card.addEventListener('dragend', () => card.classList.remove('dragging')); card.addEventListener('dragover', e => { e.preventDefault(); card.classList.add('drag-target'); }); card.addEventListener('dragleave', () => card.classList.remove('drag-target')); card.addEventListener('drop', e => { e.preventDefault(); card.classList.remove('drag-target'); if (state.dragSrc === img.id) return; const srcIdx = state.images.findIndex(i => i.id === state.dragSrc); const [moved] = state.images.splice(srcIdx, 1); state.images.splice(idx, 0, moved); renderGrid(); }); imageGrid.appendChild(card); }); } // ── Clear all ──────────────────────────────────────────────────────────────── clearAllBtn.addEventListener('click', () => { state.images = []; renderGrid(); fileInput.value = ''; }); // ── Modal ──────────────────────────────────────────────────────────────────── modalClose.addEventListener('click', () => modalOverlay.classList.remove('open')); modalOverlay.addEventListener('click', e => { if (e.target === modalOverlay) modalOverlay.classList.remove('open'); }); document.addEventListener('keydown', e => { if (e.key === 'Escape') modalOverlay.classList.remove('open'); }); // ── Progress helper ─────────────────────────────────────────────────────────── function setProgress(pct, label) { progressWrap.classList.add('active'); progressBar.style.width = pct + '%'; progressLabel.textContent = label; } function hideProgress() { progressWrap.classList.remove('active'); progressBar.style.width = '0%'; } // ── Toast ───────────────────────────────────────────────────────────────────── let toastTimer; function showToast(msg, type = 'success') { toast.textContent = msg; toast.className = `show ${type}`; clearTimeout(toastTimer); toastTimer = setTimeout(() => toast.className = '', 3200); } // ── Page sizes (mm) ─────────────────────────────────────────────────────────── const PAGE_SIZES = { a4: [210, 297], a3: [297, 420], a5: [148, 210], letter: [215.9, 279.4], legal: [215.9, 355.6], }; // ── Generate PDF ────────────────────────────────────────────────────────────── convertBtn.addEventListener('click', async () => { if (!state.images.length) return; convertBtn.disabled = true; setProgress(5, 'Initialising PDF engine…'); const pageSize = document.getElementById('pageSize').value; const orientation = document.getElementById('orientation').value; const imgFit = document.getElementById('imgFit').value; const margin = parseFloat(document.getElementById('marginSize').value); const quality = parseFloat(document.getElementById('quality').value); const addPageNums = document.getElementById('pageNumbers').checked; let filename = document.getElementById('pdfFilename').value.trim() || 'converted.pdf'; if (!filename.endsWith('.pdf')) filename += '.pdf'; try { const { jsPDF } = window.jspdf; const ori = orientation === 'landscape' ? 'l' : 'p'; const total = state.images.length; let pdf; for (let i = 0; i < total; i++) { setProgress(10 + Math.round((i / total) * 82), `Processing image ${i + 1} of ${total}…`); await new Promise(r => requestAnimationFrame(r)); const img = state.images[i]; const imgEl = await loadImage(img.dataUrl); const natW = imgEl.naturalWidth; const natH = imgEl.naturalHeight; const aspectImg = natW / natH; let pgW, pgH; if (pageSize === 'fit') { pgW = 210; pgH = Math.round(210 / aspectImg); } else { [pgW, pgH] = PAGE_SIZES[pageSize]; if (orientation === 'landscape') [pgW, pgH] = [pgH, pgW]; } if (i === 0) { pdf = new jsPDF({ orientation: pageSize === 'fit' ? 'p' : ori, unit: 'mm', format: pageSize === 'fit' ? [pgW, pgH] : (PAGE_SIZES[pageSize] ? pageSize : 'a4'), hotfixes: ['px_scaling'], }); } else { pageSize === 'fit' ? pdf.addPage([pgW, pgH], 'p') : pdf.addPage(); } const cW = pgW - margin * 2; const cH = pgH - margin * 2; let drawX = margin, drawY = margin, drawW = cW, drawH = cH; if (pageSize !== 'fit') { const aspectPage = cW / cH; if (imgFit === 'contain') { if (aspectImg > aspectPage) { drawW = cW; drawH = cW / aspectImg; drawY = margin + (cH - drawH) / 2; } else { drawH = cH; drawW = cH * aspectImg; drawX = margin + (cW - drawW) / 2; } } // fill: drawW/drawH already = cW/cH // cover: handled below } else { drawW = pgW; drawH = pgH; drawX = 0; drawY = 0; } // Draw with rotation const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const rot = img.rotation; const swapped = rot === 90 || rot === 270; canvas.width = swapped ? natH : natW; canvas.height = swapped ? natW : natH; ctx.save(); ctx.translate(canvas.width / 2, canvas.height / 2); ctx.rotate((rot * Math.PI) / 180); ctx.drawImage(imgEl, -natW / 2, -natH / 2, natW, natH); ctx.restore(); let finalDataUrl; if (imgFit === 'cover' && pageSize !== 'fit') { const cropCanvas = document.createElement('canvas'); const cropCtx = cropCanvas.getContext('2d'); const fa = cW / cH; const sa = canvas.width / canvas.height; let sx, sy, sw, sh; if (sa > fa) { sh = canvas.height; sw = sh * fa; sx = (canvas.width - sw) / 2; sy = 0; } else { sw = canvas.width; sh = sw / fa; sx = 0; sy = (canvas.height - sh) / 2; } cropCanvas.width = Math.round(sw); cropCanvas.height = Math.round(sh); cropCtx.drawImage(canvas, sx, sy, sw, sh, 0, 0, cropCanvas.width, cropCanvas.height); finalDataUrl = cropCanvas.toDataURL('image/jpeg', quality); } else { finalDataUrl = canvas.toDataURL('image/jpeg', quality); } const fmtHint = finalDataUrl.startsWith('data:image/png') ? 'PNG' : 'JPEG'; pdf.addImage(finalDataUrl, fmtHint, drawX, drawY, drawW, drawH, undefined, 'FAST'); if (addPageNums) { pdf.setFontSize(8); pdf.setTextColor(120); pdf.text(`${i + 1} / ${total}`, pgW - margin - 8, pgH - margin / 2 + 2, { align: 'right' }); } } setProgress(97, 'Saving PDF…'); await new Promise(r => setTimeout(r, 120)); pdf.save(filename); setProgress(100, 'Done!'); showToast(`✅ "${filename}" downloaded successfully!`, 'success'); setTimeout(hideProgress, 1800); } catch (err) { console.error(err); showToast('❌ PDF generation failed. Check console.', 'error'); hideProgress(); } finally { convertBtn.disabled = false; } }); // ── Load image helper ───────────────────────────────────────────────────────── function loadImage(src) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = reject; img.src = src; }); } // ── Init ───────────────────────────────────────────────────────────────────── renderGrid();

This website uses cookies.