Voyn Software
← Back to Tools

📄 PDF Converters

Private & local: Files never leave your browser. Convert PDFs into .docx or page images (PNG/JPEG). Supports page ranges, DPI, and ZIP export.

PDF ➜ Images

Click to choose or drop a PDF here
0%
No file selected.
Voyn Software
← Back to Tools
${html}
`); idoc.close(); // wait for layout, fonts, images await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))); if (idoc.fonts && idoc.fonts.ready) { try { await idoc.fonts.ready; } catch(_){} } await waitForImages(idoc.body); // 3) Pagination + capture with html2canvas const pageWmm = 210, pageHmm = 297, marginMm = 10; const contentWmm = pageWmm - marginMm*2; const contentHmm = pageHmm - marginMm*2; const pxPerMm = idoc.documentElement.clientWidth / 210; const pageWpx = pageWmm * pxPerMm; const pageHpx = pageHmm * pxPerMm; const contentHpx = contentHmm * pxPerMm; const source = idoc.querySelector('.docx-html'); const staging = idoc.createElement('div'); staging.style.width = source.style.width || '210mm'; staging.style.boxSizing = 'border-box'; idoc.body.appendChild(staging); const queue = Array.from(source.childNodes); source.innerHTML = ''; const pdf = new JSPDFCtor({ unit: 'mm', format: 'a4', orientation: 'p' }); let pageCount = 0; async function renderPage() { const canvas = await window.html2canvas(staging, { backgroundColor: '#ffffff', scale: 2, useCORS: true, windowWidth: pageWpx, windowHeight: contentHpx }); const imgWmm = contentWmm; const imgHmm = (canvas.height / canvas.width) * imgWmm; if (pageCount > 0) pdf.addPage(); pdf.addImage(canvas.toDataURL('image/jpeg', 0.95), 'JPEG', marginMm, marginMm, imgWmm, imgHmm); staging.innerHTML = ''; pageCount++; setProgress(0.1 + 0.9 * (pageCount / Math.max(queue.length, 1))); await new Promise(r => setTimeout(r)); } while (queue.length) { if (staging.childNodes.length === 0) { staging.appendChild(queue.shift()); await new Promise(r => requestAnimationFrame(r)); } while (queue.length && staging.scrollHeight <= contentHpx) { staging.appendChild(queue.shift()); if (staging.childNodes.length % 20 === 0) { await new Promise(r => requestAnimationFrame(r)); } } await renderPage(); } setStatus('Saving PDF…'); pdf.save('document.pdf'); setProgress(1); setStatus('Done ✓', 'ok'); } catch (err) { showError(err); } } // ---------- Image → PDF ---------- const img2pdfDrop = document.getElementById('img2pdfDrop'); const docx2pdfDrop = document.getElementById('docx2pdfDrop'); const img2pdfFiles = document.getElementById('img2pdfFiles'); const img2pdfList = document.getElementById('img2pdfList'); const img2pdfPageSize = document.getElementById('img2pdfPageSize'); const img2pdfFit = document.getElementById('img2pdfFit'); const img2pdfMargin = document.getElementById('img2pdfMargin'); const btnImgToPdf = document.getElementById('btnImgToPdf'); let imgQueue = []; function describeImages(){ if(!imgQueue.length){ img2pdfList.textContent = 'No images selected.'; return; } const names = imgQueue.map(f=>f.name).join(', '); img2pdfList.textContent = `${imgQueue.length} image(s): ${names}`; } function addImages(files){ for(const f of files){ if(!f.type.startsWith('image/')) continue; imgQueue.push(f); } describeImages(); } img2pdfDrop.addEventListener('click', ()=> img2pdfFiles.click()); img2pdfDrop.addEventListener('dragenter', e=>{ e.preventDefault(); img2pdfDrop.classList.add('drag'); }); img2pdfDrop.addEventListener('dragover', e=>{ e.preventDefault(); img2pdfDrop.classList.add('drag'); }); img2pdfDrop.addEventListener('dragleave', e=>{ e.preventDefault(); img2pdfDrop.classList.remove('drag'); }); img2pdfDrop.addEventListener('drop', e=>{ e.preventDefault(); img2pdfDrop.classList.remove('drag'); addImages(e.dataTransfer.files||[]); }); img2pdfFiles.addEventListener('change', e=>{ addImages(e.target.files||[]); e.target.value=''; }); const docx2pdfFile = document.getElementById('docx2pdfFile'); function pickDocx(file){ if (!file || (!file.name.toLowerCase().endsWith('.docx') && file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')) { setStatus('Please choose a .docx file.','err'); return; } file.arrayBuffer() .then(ab => { docxAB = ab; setStatus('Loaded DOCX: ' + file.name); }) .catch(showError); } // Click to open file picker docx2pdfDrop.addEventListener('click', () => docx2pdfFile.click()); // Drag & drop UX and prevention (so the browser doesn't navigate away) ['dragenter','dragover'].forEach(evt => { docx2pdfDrop.addEventListener(evt, e => { e.preventDefault(); e.stopPropagation(); docx2pdfDrop.classList.add('drag'); }); }); ['dragleave','drop'].forEach(evt => { docx2pdfDrop.addEventListener(evt, e => { e.preventDefault(); e.stopPropagation(); docx2pdfDrop.classList.remove('drag'); }); }); // Handle drop docx2pdfDrop.addEventListener('drop', e => { const f = e.dataTransfer?.files?.[0]; if (f) pickDocx(f); }); // Handle file input docx2pdfFile.addEventListener('change', e => { const f = e.target.files?.[0]; if (f) pickDocx(f); e.target.value = ''; // allow re-selecting same file }); // Convert button document.getElementById('btnDocxToPdf').addEventListener('click', convertDocxToPdf); function mmToPt(mm){ return mm * 72 / 25.4; } function pageSizePoints(code){ switch(code){ case 'a4p': return { w: 595.28, h: 841.89 }; case 'a4l': return { w: 841.89, h: 595.28 }; case 'letterp': return { w: 612, h: 792 }; case 'letterl': return { w: 792, h: 612 }; default: return null; } } async function imgToArrayBuffer(imgFile){ const ab = await imgFile.arrayBuffer(); return { ab, type: imgFile.type }; } async function convertImagesToPdf(){ try{ if(!imgQueue.length){ setStatus('Please add images first.','err'); return; } setStatus('Building PDF…'); setProgress(0); const { PDFDocument } = PDFLib; const pdfDoc = await PDFDocument.create(); const marginPt = mmToPt(parseFloat(img2pdfMargin.value)||0); const targetSize = pageSizePoints(img2pdfPageSize.value); const fitMode = img2pdfFit.value; for(let i=0;isetTimeout(r)); } const pdfBytesOut = await pdfDoc.save(); saveAs(new Blob([pdfBytesOut], { type: 'application/pdf' }), 'images.pdf'); setStatus('Done ✓', 'ok'); }catch(err){ showError(err); } } btnImgToPdf.addEventListener('click', convertImagesToPdf); // ---------- PDF → Images (ZIP) ---------- async function convertToImages(){ try { if(!pdfBytes){ setStatus('Please choose a PDF first.','err'); return; } setStatus('Parsing PDF…'); setProgress(0); logEl.textContent=''; preview.innerHTML=''; const pdf = await loadPDFFresh(); const total = pdf.numPages; const pages = parseRange(rangeImgEl.value, total); log('Pages: '+pages.join(', ')); const dpi = Math.max(72, Math.min(600, parseInt(dpiEl.value)||144)); const fmt = (fmtEl.value||'png').toLowerCase(); const mime = fmt === 'jpeg' ? 'image/jpeg' : 'image/png'; const zip = new JSZip(); for(let idx=0; idxsetTimeout(r)); } setStatus('Packaging ZIP…'); const zipBlob = await zip.generateAsync({type:'blob'}); saveAs(zipBlob, 'pdf-pages.zip'); setStatus('Done ✓', 'ok'); } catch (err) { showError(err); } } // ---------- Quick preview ---------- async function quickPreview(){ if(!pdfBytes){ setStatus('Please choose a PDF first.','err'); return; } setStatus('Rendering preview…'); setProgress(0); preview.innerHTML=''; const pdf = await loadPDFFresh(); const maxPrev = Math.min(4, pdf.numPages); for(let p=1;p<=maxPrev;p++){ const page = await pdf.getPage(p); const { viewport } = getScaleForDPI(page, 120); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d', { willReadFrequently: true }); canvas.width = Math.ceil(viewport.width); canvas.height = Math.ceil(viewport.height); await page.render({ canvasContext: ctx, viewport }).promise; const url = canvas.toDataURL('image/png'); const div = document.createElement('div'); div.className='thumb'; const img = document.createElement('img'); img.src=url; div.appendChild(img); const cap = document.createElement('div'); cap.className='small'; cap.style.padding='6px 8px'; cap.textContent='Page '+p; div.appendChild(cap); preview.appendChild(div); setProgress(p/maxPrev); await new Promise(r=>setTimeout(r)); } setStatus('Preview ready', 'ok'); } // ---------- PDF ➜ Word (text) ---------- async function convertToDocx(){ try { if(!pdfBytes){ setStatus('Please choose a PDF first.','err'); return; } await ensureDocxLoaded(); if (!window.docx) throw new Error('docx library not loaded'); setStatus('Extracting text…'); setProgress(0); logEl.textContent=''; const pdf = await loadPDFFresh(); const total = pdf.numPages; const pages = parseRange(rangeDocxEl.value, total); const { Document, Packer, Paragraph, HeadingLevel, TextRun, PageBreak } = window.docx; const paragraphs = []; for(let i=0;i { if(lineRuns.length){ paragraphs.push(new Paragraph({ children: lineRuns })); lineRuns = []; } }; for(const item of content.items){ const str = item.str || ''; if(!str) continue; const y = item.transform ? item.transform[5] : null; if(currentLineY === null) currentLineY = y; const sameLine = y !== null && Math.abs(y - currentLineY) < 2.5; if(!sameLine){ flushLine(); currentLineY = y; } lineRuns.push(new TextRun({ text: str })); } flushLine(); if(keepLayoutEl.value === 'paged' && i < pages.length-1){ paragraphs.push(new Paragraph({ children: [new PageBreak()] })); } setProgress((i+1)/pages.length); await new Promise(r=>setTimeout(r)); } const doc = new Document({ sections: [{ properties: {}, children: paragraphs }] }); setStatus('Building .docx…'); const blob = await Packer.toBlob(doc); saveAs(blob, 'document.docx'); setStatus('Done ✓', 'ok'); } catch (err) { showError(err); } } // ---------- PDF ➜ Word (exact images) ---------- async function convertToDocxExact(){ try { if(!pdfAB && !pdfBytes){ setStatus('Please choose a PDF first.','err'); return; } await ensureDocxLoaded(); if (!window.docx) throw new Error('docx library not loaded'); const { Document, Packer, Paragraph, PageBreak, ImageRun } = window.docx; setStatus('Rendering pages…'); setProgress(0); logEl.textContent=''; const pdf = await loadPDFFresh(); const total = pdf.numPages; const pages = parseRange(rangeDocxEl.value, total); const DPI_FOR_WORD = 144; const paras = []; for(let i=0;isetTimeout(r)); } const doc = new Document({ sections: [{ properties: {}, children: paras }] }); setStatus('Building .docx…'); const blobOut = await Packer.toBlob(doc); saveAs(blobOut, 'document.docx'); setStatus('Done ✓', 'ok'); } catch (err) { showError(err); } } // ------- Events ------- const dropPdf2Img = document.getElementById('dropPdf2Img'); const filePdf2Img = document.getElementById('filePdf2Img'); if (dropPdf2Img && filePdf2Img) { dropPdf2Img.addEventListener('click', ()=> filePdf2Img.click()); ['dragenter','dragover'].forEach(ev=> dropPdf2Img.addEventListener(ev, e=>{ e.preventDefault(); dropPdf2Img.classList.add('drag'); })); ['dragleave','drop'].forEach(ev=> dropPdf2Img.addEventListener(ev, e=>{ e.preventDefault(); dropPdf2Img.classList.remove('drag'); })); dropPdf2Img.addEventListener('drop', e=>{ const f=e.dataTransfer.files?.[0]; if(f) fileChosen(f); }); filePdf2Img.addEventListener('change', e=>{ const f=e.target.files?.[0]; if(f) fileChosen(f); e.target.value=''; }); } const dropPdf2Docx = document.getElementById('dropPdf2Docx'); const filePdf2Docx = document.getElementById('filePdf2Docx'); if (dropPdf2Docx && filePdf2Docx) { dropPdf2Docx.addEventListener('click', ()=> filePdf2Docx.click()); ['dragenter','dragover'].forEach(ev=> dropPdf2Docx.addEventListener(ev, e=>{ e.preventDefault(); dropPdf2Docx.classList.add('drag'); })); ['dragleave','drop'].forEach(ev=> dropPdf2Docx.addEventListener(ev, e=>{ e.preventDefault(); dropPdf2Docx.classList.remove('drag'); })); dropPdf2Docx.addEventListener('drop', e=>{ const f=e.dataTransfer.files?.[0]; if(f) fileChosen(f); }); filePdf2Docx.addEventListener('change', e=>{ const f=e.target.files?.[0]; if(f) fileChosen(f); e.target.value=''; }); } btnImages.addEventListener('click', ()=>{ convertToImages().catch(err=>{ console.error(err); setStatus('Failed: '+err.message,'err'); }); }); btnPreview.addEventListener('click', ()=>{ quickPreview().catch(err=>{ console.error(err); setStatus('Failed: '+err.message,'err'); }); }); btnDocx.addEventListener('click', ()=>{ const mode = (keepLayoutEl && keepLayoutEl.value) || 'simple'; const task = (mode === 'exact') ? convertToDocxExact() : convertToDocx(); task.catch(err=>{ console.error(err); setStatus('Failed: '+err.message,'err'); showError(err); }); }); // Tabs const tabBtns = document.querySelectorAll('[data-tab]'); const panels = ['panel-pdf2img', 'panel-img2pdf', 'panel-pdf2docx', 'panel-docx2pdf']; tabBtns.forEach(btn => { btn.addEventListener('click', () => { const target = btn.getAttribute('data-tab'); panels.forEach(id => { document.getElementById(id).style.display = (id === target) ? '' : 'none'; }); tabBtns.forEach(b => b.classList.add('ghost')); btn.classList.remove('ghost'); }); }); document.querySelector('[data-tab="panel-pdf2img"]').click();