Download pdftoimages.html from Talha912/PDFIX: direct link, hf CLI and curl.
- Browser
- Download file 36 kB
-
https://huggingface.co/spaces/Talha912/PDFIX/resolve/main/pdftoimages.html
- Command line
-
hf download hf://spaces/Talha912/PDFIX/pdftoimages.html
-
curl -L -o pdftoimages.html https://huggingface.co/spaces/Talha912/PDFIX/resolve/main/pdftoimages.html
36 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <meta name="theme-color" content="#111827"> | |
| <title>PDFix — PDF to Images</title> | |
| <meta name="description" content="Convert PDF pages into high-quality PNG or JPG images directly in your browser."> | |
| <style> | |
| :root{--bg:#f4f7fb;--surface:#fff;--ink:#182230;--muted:#667085;--line:#e5eaf2;--brand:#5b5ce2;--brand2:#7778ff;--brand-soft:#eeeeff;--green:#12805c;--shadow:0 18px 50px rgba(20,31,56,.08);--radius:22px} | |
| *{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 10% 0%,#ededff 0,transparent 28%),radial-gradient(circle at 100% 15%,#e4f8f3 0,transparent 24%),var(--bg);color:var(--ink);font:15px/1.5 Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}button,input,select{font:inherit}button{cursor:pointer}a{color:inherit;text-decoration:none} | |
| .topbar{height:76px;display:flex;align-items:center;justify-content:space-between;padding:0 clamp(18px,5vw,72px);background:rgba(255,255,255,.82);border-bottom:1px solid rgba(229,234,242,.9);backdrop-filter:blur(18px);position:sticky;top:0;z-index:20} | |
| .brand{display:flex;align-items:center;gap:11px;font-weight:850;letter-spacing:-.7px;font-size:22px}.logo{width:39px;height:39px;border-radius:13px;background:linear-gradient(135deg,#696aff,#4946cb);display:grid;place-items:center;color:#fff;box-shadow:0 8px 18px #5b5ce244}.logo svg{width:23px}.brand em{font-style:normal;color:var(--brand)} | |
| .top-actions{display:flex;align-items:center;gap:10px}.secure{color:var(--muted);font-size:12px;display:flex;align-items:center;gap:7px}.secure span{width:8px;height:8px;background:#18a874;border-radius:50%}.nav-btn{border:1px solid var(--line);background:white;border-radius:12px;padding:10px 14px;font-weight:700} | |
| .wrap{max-width:1440px;margin:auto;padding:38px clamp(16px,4vw,58px) 64px}.crumb{font-size:13px;color:var(--muted);margin-bottom:22px}.crumb b{color:var(--brand)} | |
| .hero{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;margin-bottom:28px}.eyebrow{display:inline-flex;align-items:center;gap:8px;background:var(--brand-soft);color:var(--brand);font-size:11px;font-weight:850;letter-spacing:1.3px;text-transform:uppercase;padding:7px 11px;border-radius:99px}h1{font-size:clamp(31px,4vw,48px);letter-spacing:-1.8px;line-height:1.08;margin:15px 0 12px}.hero p{max-width:680px;color:var(--muted);font-size:16px;margin:0}.hero-badge{background:#fff;border:1px solid var(--line);border-radius:17px;padding:14px 17px;display:flex;align-items:center;gap:12px;box-shadow:0 7px 20px #26324a08;white-space:nowrap}.hero-badge .shield{width:38px;height:38px;background:#e8faf2;color:#16805d;border-radius:12px;display:grid;place-items:center;font-size:20px}.hero-badge strong{display:block;font-size:13px}.hero-badge small{display:block;color:var(--muted);font-size:11px} | |
| .layout{display:grid;grid-template-columns:minmax(0,1fr) 330px;gap:22px;align-items:start}.maincol{min-width:0}.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);overflow:hidden}.upload{padding:25px;margin-bottom:20px}.section-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:17px}.section-title{font-size:17px;font-weight:800;letter-spacing:-.35px}.step{display:inline-grid;place-items:center;width:27px;height:27px;border-radius:9px;background:var(--brand-soft);color:var(--brand);font-weight:850;font-size:12px;margin-right:9px} | |
| .dropzone{border:1.5px dashed #c7c9f8;border-radius:19px;background:linear-gradient(135deg,#f9f9ff,#f6fbff);padding:35px 20px;text-align:center;transition:.2s;position:relative}.dropzone.drag{border-color:var(--brand);background:#efefff;transform:translateY(-1px)}.upload-icon{width:61px;height:61px;margin:0 auto 13px;border-radius:19px;background:#e8e9ff;color:var(--brand);display:grid;place-items:center}.upload-icon svg{width:31px;height:31px}.dropzone h2{font-size:18px;margin:0 0 6px;letter-spacing:-.4px}.dropzone p{font-size:13px;color:var(--muted);margin:0 0 17px}.primary,.secondary,.danger{border:0;border-radius:12px;padding:11px 16px;font-weight:800;transition:transform .15s,filter .15s}.primary{color:#fff;background:linear-gradient(135deg,var(--brand),#4948c9);box-shadow:0 8px 17px #5b5ce22e}.primary:hover,.secondary:hover,.danger:hover{filter:brightness(.98);transform:translateY(-1px)}.secondary{background:#fff;border:1px solid var(--line);color:#344054}.danger{background:#fff0f0;color:#b42318;border:1px solid #ffdddd}.micro{color:var(--muted);font-size:11px;margin-top:13px}.micro b{color:#475467}.file-input{display:none} | |
| .file-summary{display:none;margin-top:17px;padding:13px 14px;background:#f8fafc;border:1px solid var(--line);border-radius:14px;align-items:center;justify-content:space-between;gap:10px}.file-summary.show{display:flex}.file-meta{display:flex;align-items:center;gap:11px;min-width:0}.pdf-icon{width:40px;height:44px;background:#fff0f0;border:1px solid #ffd5d5;border-radius:10px;color:#d92d20;display:grid;place-items:center;font-size:11px;font-weight:900;flex-shrink:0}.file-name{font-weight:800;overflow-wrap:anywhere;font-size:13px}.file-sub{font-size:11px;color:var(--muted)}.status-pill{font-size:11px;font-weight:800;background:#e8faf2;color:#137c59;padding:6px 9px;border-radius:99px;white-space:nowrap} | |
| .workspace{padding:24px}.workspace .section-head{margin-bottom:16px}.count{font-size:12px;color:var(--muted);background:#f2f4f7;padding:6px 9px;border-radius:99px;font-weight:750}.empty{padding:38px 15px;text-align:center;color:var(--muted);border:1px dashed var(--line);border-radius:16px;background:#fbfcfe}.empty .empty-icon{font-size:31px;margin-bottom:8px}.empty strong{display:block;color:#344054;margin-bottom:3px}.empty span{font-size:12px} | |
| .preview-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:14px}.page-tile{border:1px solid var(--line);border-radius:15px;overflow:hidden;background:#fff;min-width:0;transition:.18s;position:relative}.page-tile:hover{box-shadow:0 8px 22px #202b460f;transform:translateY(-2px)}.thumb{height:195px;background:repeating-conic-gradient(#f5f6f8 0% 25%,#fff 0% 50%) 50%/18px 18px;display:grid;place-items:center;overflow:hidden;position:relative}.thumb canvas{max-width:100%;max-height:100%;object-fit:contain;box-shadow:0 2px 12px #1c29400e}.page-label{padding:10px 11px;display:flex;justify-content:space-between;align-items:center;gap:5px}.page-label strong{font-size:12px}.page-label span{font-size:10px;color:var(--muted)}.tile-check{position:absolute;top:9px;left:9px;width:19px;height:19px;accent-color:var(--brand);z-index:2}.tile-menu{position:absolute;top:7px;right:7px;border:1px solid #e4e7ec;background:#ffffffed;border-radius:8px;width:29px;height:29px;font-weight:900;z-index:2}.tile-menu:focus{outline:2px solid var(--brand)} | |
| .side{display:grid;gap:18px}.settings{padding:23px;position:sticky;top:96px}.settings h2{font-size:17px;margin:0 0 4px;letter-spacing:-.4px}.settings-intro{font-size:12px;color:var(--muted);margin:0 0 20px}.field{margin:0 0 17px}.field label,.field-label{display:flex;justify-content:space-between;align-items:center;font-size:12px;font-weight:800;color:#344054;margin-bottom:7px}.field small{font-size:10px;color:var(--muted);font-weight:500}.field select,.field input[type="number"],.field input[type="text"]{width:100%;padding:10px 11px;border:1px solid #dfe4ec;border-radius:10px;background:#fff;color:var(--ink);outline:none}.field select:focus,.field input:focus{border-color:#9b9cff;box-shadow:0 0 0 3px #5b5ce21c}.segmented{display:grid;grid-template-columns:repeat(2,1fr);gap:5px;padding:4px;background:#f2f4f7;border-radius:12px}.segmented button{border:0;background:transparent;border-radius:9px;padding:8px 5px;font-size:12px;font-weight:800;color:#667085}.segmented button.active{background:white;color:var(--brand);box-shadow:0 2px 5px #1d29390c}.range-row{display:flex;align-items:center;gap:10px}.range-row input{width:100%;accent-color:var(--brand)}.range-value{font-size:12px;font-weight:850;min-width:42px;text-align:right}.check-row{display:flex;align-items:flex-start;gap:9px;font-size:12px;color:#475467;margin:13px 0}.check-row input{accent-color:var(--brand);margin-top:3px}.check-row strong{display:block;color:#344054;font-size:12px}.hint{font-size:10px;color:var(--muted);margin:5px 0 0}.advanced-toggle{width:100%;border:0;background:#f7f8ff;color:var(--brand);border-radius:11px;padding:10px;font-weight:800;font-size:12px;display:flex;justify-content:space-between;align-items:center}.advanced{display:none;padding-top:17px}.advanced.open{display:block}.convert-btn{width:100%;padding:14px 12px;margin-top:3px;font-size:14px}.convert-btn:disabled{opacity:.45;cursor:not-allowed;transform:none;box-shadow:none}.privacy{display:flex;gap:10px;padding:15px;background:#edfaf4;border:1px solid #d2f1e3;border-radius:15px;color:#137b59;font-size:11px}.privacy .lock{font-size:17px}.privacy strong{display:block;font-size:12px;margin-bottom:2px}.privacy p{margin:0;color:#347b64} | |
| .progress-wrap{display:none;margin-top:17px}.progress-wrap.show{display:block}.progress-top{display:flex;justify-content:space-between;font-size:11px;font-weight:800;color:#475467;margin-bottom:7px}.progress{height:7px;background:#eaecf0;border-radius:99px;overflow:hidden}.progress-bar{height:100%;width:0;background:linear-gradient(90deg,#5b5ce2,#31b78a);transition:width .18s}.notice{display:none;padding:12px 14px;margin-top:14px;border-radius:12px;font-size:12px}.notice.show{display:block}.notice.error{background:#fff0f0;color:#b42318;border:1px solid #ffd9d9}.notice.success{background:#eafaf2;color:#137c59;border:1px solid #c9f0df} | |
| .results{display:none;margin-top:20px;padding:20px}.results.show{display:block}.results-head{display:flex;justify-content:space-between;gap:15px;align-items:center;margin-bottom:13px}.results-head h3{margin:0;font-size:17px}.results-head p{margin:3px 0 0;font-size:12px;color:var(--muted)}.result-actions{display:flex;gap:8px;flex-wrap:wrap}.result-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px}.result-item{display:flex;align-items:center;gap:10px;padding:11px;border:1px solid var(--line);border-radius:13px;min-width:0}.result-preview{width:42px;height:48px;background:#f2f4f7;border-radius:7px;display:grid;place-items:center;overflow:hidden;flex-shrink:0}.result-preview img{width:100%;height:100%;object-fit:contain}.result-info{min-width:0;flex:1}.result-info strong{display:block;font-size:11px;overflow-wrap:anywhere}.result-info small{display:block;font-size:10px;color:var(--muted)}.download-one{border:1px solid var(--line);background:#fff;border-radius:8px;padding:7px;font-weight:900;color:var(--brand)} | |
| footer{margin-top:36px;text-align:center;color:#98a2b3;font-size:11px}footer b{color:#667085} | |
| @media(max-width:1020px){.layout{grid-template-columns:1fr}.side{grid-template-columns:1fr}.settings{position:static}.hero-badge{display:none}} | |
| @media(max-width:600px){.topbar{height:65px}.secure{display:none}.wrap{padding-top:25px}.hero{display:block}.upload,.workspace,.settings{padding:18px}.dropzone{padding:28px 14px}.preview-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.thumb{height:170px}.results-head{align-items:flex-start;flex-direction:column}.result-actions{width:100%}.result-actions button{flex:1}.brand{font-size:20px}} | |
| </style> | |
| </head> | |
| <body> | |
| <header class="topbar"> | |
| <a class="brand" href="index.html" aria-label="PDFix home"><span class="logo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M7 3h7l5 5v13H7z"/><path d="M14 3v6h5M4 7v14h11M10 13h5M10 17h5"/></svg></span><span>PDF<em>ix</em></span></a> | |
| <div class="top-actions"><div class="secure"><span></span> Local processing in your browser</div><a class="nav-btn" href="index.html">← All tools</a></div> | |
| </header> | |
| <main class="wrap"> | |
| <div class="crumb"><a href="index.html">Home</a> › PDF tools › <b>PDF to Images</b></div> | |
| <section class="hero"> | |
| <div><span class="eyebrow">✦ IMAGE CONVERTER</span><h1>PDF to <span style="color:var(--brand)">Images.</span></h1><p>Turn every PDF page into a crisp PNG or JPG. Fine-tune resolution, image quality, page selection and file naming before downloading.</p></div> | |
| <div class="hero-badge"><div class="shield">✓</div><div><strong>Private by design</strong><small>Your PDF stays on your device</small></div></div> | |
| </section> | |
| <div class="layout"> | |
| <section class="maincol"> | |
| <div class="card upload"> | |
| <div class="section-head"><div class="section-title"><span class="step">1</span>Upload your PDF</div><button id="clearBtn" class="secondary" style="padding:8px 11px;font-size:12px" disabled>Clear file</button></div> | |
| <div id="dropzone" class="dropzone" tabindex="0" role="button" aria-label="Choose PDF or drop it here"> | |
| <div class="upload-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><path d="M12 16V4m0 0L7.5 8.5M12 4l4.5 4.5"/><path d="M5 14v5a1.5 1.5 0 001.5 1.5h11A1.5 1.5 0 0019 19v-5"/></svg></div> | |
| <h2>Drop your PDF here</h2><p>or choose a file from your device</p><button class="primary" id="chooseBtn" type="button">Choose PDF file</button> | |
| <div class="micro">PDF only · Max file size depends on your browser memory · <b>No server upload</b></div> | |
| </div> | |
| <input class="file-input" id="fileInput" type="file" accept="application/pdf,.pdf"> | |
| <div id="fileSummary" class="file-summary"><div class="file-meta"><div class="pdf-icon">PDF</div><div style="min-width:0"><div class="file-name" id="fileName">document.pdf</div><div class="file-sub" id="fileSub">0 pages · 0 MB</div></div></div><span class="status-pill" id="fileStatus">Ready</span></div> | |
| <div id="loadProgress" class="progress-wrap"><div class="progress-top"><span id="loadLabel">Reading PDF…</span><span id="loadPercent">0%</span></div><div class="progress"><div class="progress-bar" id="loadBar"></div></div></div> | |
| <div id="notice" class="notice"></div> | |
| </div> | |
| <div class="card workspace"> | |
| <div class="section-head"><div><div class="section-title"><span class="step">2</span>Choose pages</div><div style="font-size:12px;color:var(--muted);margin:4px 0 0 36px">Select individual pages or export the full document.</div></div><span class="count" id="pageCount">0 pages</span></div> | |
| <div id="pageControls" style="display:none;display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px"> | |
| <button id="selectAllBtn" class="secondary" style="font-size:11px;padding:8px 10px">Select all</button> | |
| <button id="selectNoneBtn" class="secondary" style="font-size:11px;padding:8px 10px">Select none</button> | |
| <div style="display:flex;align-items:center;gap:7px;margin-left:auto"><label for="pageRange" style="font-size:11px;font-weight:800;color:#475467">Page range</label><input id="pageRange" type="text" placeholder="1-3, 5" style="width:115px;padding:8px;border:1px solid var(--line);border-radius:9px;font-size:11px"><button id="applyRangeBtn" class="secondary" style="font-size:11px;padding:8px 10px">Apply</button></div> | |
| </div> | |
| <div id="emptyState" class="empty"><div class="empty-icon">▧</div><strong>Your page previews will appear here</strong><span>Upload a PDF to view and select pages for conversion.</span></div> | |
| <div id="previewGrid" class="preview-grid"></div> | |
| </div> | |
| <div class="card results" id="results"> | |
| <div class="results-head"><div><h3>✨ Your images are ready</h3><p id="resultSummary">Conversion completed successfully.</p></div><div class="result-actions"><button id="downloadZipBtn" class="primary">↓ Download all as ZIP</button><button id="downloadEachBtn" class="secondary">Download individually</button></div></div> | |
| <div id="resultList" class="result-list"></div> | |
| </div> | |
| </section> | |
| <aside class="side"> | |
| <div class="card settings"> | |
| <h2>Export settings</h2><p class="settings-intro">Customize the output to match your needs.</p> | |
| <div class="field"><label>Image format</label><div class="segmented" id="formatSwitch"><button data-format="png" class="active" type="button">PNG <span style="font-weight:500">· Lossless</span></button><button data-format="jpeg" type="button">JPG <span style="font-weight:500">· Smaller</span></button></div></div> | |
| <div class="field"><label for="resolution">Resolution (DPI) <small>Higher = sharper + larger</small></label><select id="resolution"><option value="72">72 DPI — Web / preview</option><option value="96">96 DPI — Standard screen</option><option value="150" selected>150 DPI — High quality</option><option value="200">200 DPI — Print</option><option value="300">300 DPI — Professional</option><option value="400">400 DPI — Ultra detail</option></select></div> | |
| <div class="field" id="qualityField"><label for="quality">JPG quality <span class="range-value" id="qualityValue">90%</span></label><div class="range-row"><input id="quality" type="range" min="40" max="100" value="90"><span class="range-value" id="qualityValue2">90%</span></div><div class="hint">Only applies to JPG output.</div></div> | |
| <div class="field"><label for="scaleMode">Sizing mode</label><select id="scaleMode"><option value="page" selected>Preserve PDF page size</option><option value="fixed">Fixed pixel width</option></select></div> | |
| <div class="field" id="widthField" style="display:none"><label for="pixelWidth">Output width <small>pixels</small></label><input type="number" id="pixelWidth" min="300" max="12000" step="100" value="2000"><div class="hint">Height follows the page's aspect ratio.</div></div> | |
| <div class="field"><label for="background">Transparent background</label><select id="background"><option value="white" selected>White background</option><option value="transparent">Transparent (PNG only)</option></select><div class="hint" id="transHint">JPG always uses a solid background.</div></div> | |
| <div class="field"><label for="filename">File naming</label><input id="filename" type="text" value="pdfix-page" maxlength="60" placeholder="e.g. invoice-page"><div class="hint">Output: pdfix-page-001.png</div></div> | |
| <button class="advanced-toggle" id="advancedToggle" type="button"><span>⚙ Advanced options</span><span id="advancedChevron">+</span></button> | |
| <div class="advanced" id="advancedPanel"> | |
| <div class="field"><label for="pageOrder">Page order</label><select id="pageOrder"><option value="asc">Original order (ascending)</option><option value="desc">Reverse order (descending)</option></select></div> | |
| <div class="field"><label for="colorMode">Color mode</label><select id="colorMode"><option value="color">Full color</option><option value="grayscale">Grayscale</option></select><div class="hint">Grayscale is applied after rendering.</div></div> | |
| <div class="field"><label for="rotation">Rotation</label><select id="rotation"><option value="0">Keep original rotation</option><option value="90">Rotate +90°</option><option value="180">Rotate 180°</option><option value="270">Rotate +270°</option></select></div> | |
| <label class="check-row"><input type="checkbox" id="includePageNo" checked><span><strong>Include page number in filename</strong><span>Recommended for multi-page PDFs.</span></span></label> | |
| <label class="check-row"><input type="checkbox" id="zipAfter" checked><span><strong>Prepare a ZIP bundle</strong><span>Combine all converted images in one download.</span></span></label> | |
| </div> | |
| <button class="primary convert-btn" id="convertBtn" disabled>Convert to images <span>→</span></button> | |
| <div class="progress-wrap" id="convertProgress"><div class="progress-top"><span id="convertLabel">Rendering pages…</span><span id="convertPercent">0%</span></div><div class="progress"><div class="progress-bar" id="convertBar"></div></div></div> | |
| <div class="hint" style="text-align:center;margin-top:10px">Large PDFs may take a little time at high DPI.</div> | |
| </div> | |
| <div class="privacy"><div class="lock">♧</div><div><strong>Your files stay private</strong><p>PDFix processes pages locally in your browser. Your PDF isn't uploaded to our server. Libraries load from public CDNs.</p></div></div> | |
| </aside> | |
| </div> | |
| <footer>Made for a smoother PDF workflow · <b>PDFix</b> · Processing runs in your browser</footer> | |
| </main> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script> | |
| <script> | |
| (() => { | |
| 'use strict'; | |
| const $ = id => document.getElementById(id); | |
| const fileInput=$('fileInput'), dropzone=$('dropzone'), notice=$('notice'); | |
| let pdfDoc=null, pdfFile=null, format='png', selectedPages=new Set(), renderedThumbs=new Map(), converted=[]; | |
| if (window.pdfjsLib) pdfjsLib.GlobalWorkerOptions.workerSrc='https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; | |
| const bytes = n => n<1024*1024 ? (n/1024).toFixed(1)+' KB' : (n/1024/1024).toFixed(2)+' MB'; | |
| function showNotice(msg,type='error'){notice.textContent=msg;notice.className='notice show '+type;} | |
| function hideNotice(){notice.className='notice';notice.textContent='';} | |
| function setProgress(prefix,pct,label){const wrap=$(prefix+'Progress');wrap.classList.add('show');$(prefix+'Bar').style.width=pct+'%';$(prefix+'Percent').textContent=Math.round(pct)+'%';if(label)$(prefix+'Label').textContent=label;} | |
| function resetProgress(prefix){$(prefix+'Progress').classList.remove('show');$(prefix+'Bar').style.width='0%';} | |
| function safeBase(s){return (s||'pdfix-page').trim().replace(/\.pdf$/i,'').replace(/[\\/:*?"<>|]+/g,'-').replace(/\s+/g,'-').replace(/-+/g,'-').replace(/^-|-$/g,'').slice(0,60)||'pdfix-page';} | |
| function pageFilename(page,total,ext){const base=safeBase($('filename').value);if(!$('includePageNo').checked && total===1)return `${base}.${ext}`;const digits=Math.max(3,String(total).length);return `${base}-${String(page).padStart(digits,'0')}.${ext}`;} | |
| function formatBytes(n){return bytes(n);} | |
| function resetOutput(){converted.forEach(x=>{if(x.url)URL.revokeObjectURL(x.url)});converted=[];$('results').classList.remove('show');$('resultList').innerHTML='';} | |
| async function loadPDF(file){ | |
| hideNotice();if(!file)return; | |
| if(file.type!=='application/pdf'&&!file.name.toLowerCase().endsWith('.pdf')){showNotice('Please choose a valid PDF file.');return;} | |
| if(!window.pdfjsLib){showNotice('The PDF rendering library could not load. Check your internet connection and refresh this page.');return;} | |
| pdfDoc=null;pdfFile=file;renderedThumbs.clear();selectedPages.clear();resetOutput(); | |
| $('previewGrid').innerHTML='';$('emptyState').style.display='none';$('convertBtn').disabled=true;$('clearBtn').disabled=false; | |
| $('fileSummary').classList.add('show');$('fileName').textContent=file.name;$('fileSub').textContent=`${formatBytes(file.size)} · Reading document…`;$('fileStatus').textContent='Loading…'; | |
| setProgress('load',0,'Opening PDF…'); | |
| try{ | |
| const data=await file.arrayBuffer(); | |
| const task=pdfjsLib.getDocument({data, useSystemFonts:true}); | |
| task.onProgress=p=>{if(p.total)setProgress('load',Math.min(35,p.loaded/p.total*35),'Reading PDF…');}; | |
| pdfDoc=await task.promise; | |
| $('fileSub').textContent=`${pdfDoc.numPages} page${pdfDoc.numPages===1?'':'s'} · ${formatBytes(file.size)}`;$('fileStatus').textContent='Ready'; | |
| $('pageCount').textContent=`${pdfDoc.numPages} page${pdfDoc.numPages===1?'':'s'}`; | |
| $('pageControls').style.display='flex';$('emptyState').style.display='none'; | |
| selectedPages=new Set(Array.from({length:pdfDoc.numPages},(_,i)=>i+1)); | |
| $('convertBtn').disabled=false; | |
| await buildThumbnails(); | |
| resetProgress('load'); | |
| showNotice(`PDF loaded successfully. ${pdfDoc.numPages} page${pdfDoc.numPages===1?'':'s'} ready to convert.`, 'success'); | |
| }catch(e){ | |
| console.error(e);pdfDoc=null;resetProgress('load');$('emptyState').style.display='block';$('emptyState').innerHTML='<div class="empty-icon">⚠</div><strong>Could not open this PDF</strong><span>The file may be damaged, password-protected or unsupported.</span>';showNotice('Unable to read this PDF. Try another file or remove its password protection.');$('fileStatus').textContent='Error'; | |
| } | |
| } | |
| async function buildThumbnails(){ | |
| const grid=$('previewGrid');grid.innerHTML=''; | |
| for(let n=1;n<=pdfDoc.numPages;n++){ | |
| const tile=document.createElement('div');tile.className='page-tile';tile.dataset.page=n; | |
| tile.innerHTML=`<input class="tile-check" type="checkbox" aria-label="Select page ${n}" checked><button class="tile-menu" type="button" title="Preview page ${n}" aria-label="Preview page ${n}">↗</button><div class="thumb"><span style="font-size:11px;color:#98a2b3">Rendering…</span></div><div class="page-label"><strong>Page ${n}</strong><span>PDF</span></div>`; | |
| const checkbox=tile.querySelector('input');checkbox.addEventListener('change',()=>{checkbox.checked?selectedPages.add(n):selectedPages.delete(n);updateSelectionCount();}); | |
| tile.querySelector('.tile-menu').addEventListener('click',()=>openPagePreview(n)); | |
| tile.addEventListener('click',e=>{if(e.target.closest('button')||e.target.closest('input'))return;checkbox.checked=!checkbox.checked;checkbox.dispatchEvent(new Event('change'));}); | |
| grid.appendChild(tile); | |
| } | |
| for(let n=1;n<=pdfDoc.numPages;n++){ | |
| const tile=grid.querySelector(`[data-page="${n}"]`),holder=tile.querySelector('.thumb'); | |
| try{ | |
| const page=await pdfDoc.getPage(n),vp=page.getViewport({scale:.38}); | |
| const canvas=document.createElement('canvas'),ctx=canvas.getContext('2d',{alpha:false}); | |
| canvas.width=Math.ceil(vp.width);canvas.height=Math.ceil(vp.height); | |
| await page.render({canvasContext:ctx,viewport:vp,background:'#ffffff'}).promise; | |
| holder.innerHTML='';holder.appendChild(canvas);renderedThumbs.set(n,canvas); | |
| }catch(e){holder.textContent='Preview unavailable';} | |
| if(n%3===0||n===pdfDoc.numPages)setProgress('load',35+(n/pdfDoc.numPages*65),`Preparing page previews (${n}/${pdfDoc.numPages})…`); | |
| } | |
| updateSelectionCount(); | |
| } | |
| function updateSelectionCount(){const total=pdfDoc?pdfDoc.numPages:0;$('pageCount').textContent=`${selectedPages.size} of ${total} selected`;document.querySelectorAll('.page-tile').forEach(t=>{const n=+t.dataset.page;t.querySelector('input').checked=selectedPages.has(n);t.style.borderColor=selectedPages.has(n)?'#c9caff':'var(--line)';});} | |
| function openPagePreview(n){ | |
| const canvas=renderedThumbs.get(n);if(!canvas)return; | |
| const w=window.open('','_blank');if(!w){showNotice('Your browser blocked the preview window. Allow pop-ups for this page.');return;} | |
| w.document.write('<!doctype html><title>PDFix — Page '+n+'</title><style>body{margin:0;background:#20242c;display:grid;place-items:center;min-height:100vh}img{max-width:96vw;max-height:96vh;background:white;box-shadow:0 8px 40px #0008}</style><img alt="PDF page preview" src="'+canvas.toDataURL('image/png')+'">');w.document.close(); | |
| } | |
| $('chooseBtn').addEventListener('click',e=>{e.stopPropagation();fileInput.click();}); | |
| dropzone.addEventListener('click',()=>fileInput.click());dropzone.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();fileInput.click();}}); | |
| fileInput.addEventListener('change',()=>{if(fileInput.files[0])loadPDF(fileInput.files[0]);}); | |
| ['dragenter','dragover'].forEach(ev=>dropzone.addEventListener(ev,e=>{e.preventDefault();dropzone.classList.add('drag');})); | |
| ['dragleave','drop'].forEach(ev=>dropzone.addEventListener(ev,e=>{e.preventDefault();dropzone.classList.remove('drag');})); | |
| dropzone.addEventListener('drop',e=>{const f=e.dataTransfer.files[0];if(f)loadPDF(f);}); | |
| $('clearBtn').addEventListener('click',()=>{pdfDoc=null;pdfFile=null;fileInput.value='';selectedPages.clear();renderedThumbs.clear();resetOutput();$('previewGrid').innerHTML='';$('fileSummary').classList.remove('show');$('pageControls').style.display='none';$('pageCount').textContent='0 pages';$('emptyState').style.display='block';$('emptyState').innerHTML='<div class="empty-icon">▧</div><strong>Your page previews will appear here</strong><span>Upload a PDF to view and select pages for conversion.</span>';$('convertBtn').disabled=true;$('clearBtn').disabled=true;resetProgress('load');hideNotice();}); | |
| $('selectAllBtn').addEventListener('click',()=>{if(!pdfDoc)return;selectedPages=new Set(Array.from({length:pdfDoc.numPages},(_,i)=>i+1));updateSelectionCount();}); | |
| $('selectNoneBtn').addEventListener('click',()=>{selectedPages.clear();updateSelectionCount();}); | |
| $('applyRangeBtn').addEventListener('click',()=>{ | |
| if(!pdfDoc)return;const val=$('pageRange').value.trim();if(!val){showNotice('Enter a page range, for example 1-3, 5.');return;} | |
| try{const next=new Set();for(const part of val.split(',')){const p=part.trim();if(!p)continue;const m=p.match(/^(\d+)\s*(?:-\s*(\d+))?$/);if(!m)throw Error();let a=+m[1],b=m[2]?+m[2]:a;if(a<1||b<a||b>pdfDoc.numPages)throw Error();for(let i=a;i<=b;i++)next.add(i);}if(!next.size)throw Error();selectedPages=next;updateSelectionCount();hideNotice();}catch(e){showNotice(`Invalid page range. Use numbers between 1 and ${pdfDoc.numPages}, e.g. 1-3, 5.`);} | |
| }); | |
| document.querySelectorAll('#formatSwitch button').forEach(b=>b.addEventListener('click',()=>{format=b.dataset.format;document.querySelectorAll('#formatSwitch button').forEach(x=>x.classList.toggle('active',x===b));$('qualityField').style.opacity=format==='jpeg'?'1':'.45';$('background').disabled=format==='jpeg';$('transHint').textContent=format==='jpeg'?'JPG always uses a solid background.':'Transparent background works for PNG output.';})); | |
| $('quality').addEventListener('input',()=>{$('qualityValue').textContent=$('quality').value+'%';$('qualityValue2').textContent=$('quality').value+'%';}); | |
| $('scaleMode').addEventListener('change',()=>{$('widthField').style.display=$('scaleMode').value==='fixed'?'block':'none';}); | |
| $('advancedToggle').addEventListener('click',()=>{const p=$('advancedPanel');p.classList.toggle('open');$('advancedChevron').textContent=p.classList.contains('open')?'−':'+';}); | |
| $('convertBtn').addEventListener('click',convert); | |
| async function convert(){ | |
| if(!pdfDoc||!selectedPages.size)return showNotice('Select at least one page to convert.'); | |
| if(!$('zipAfter').checked&&!window.JSZip){/* individual downloads remain available */} | |
| hideNotice();resetOutput();$('convertBtn').disabled=true; | |
| const pages=[...selectedPages].sort((a,b)=>$('pageOrder').value==='asc'?a-b:b-a); | |
| const dpi=+$('resolution').value,scale=dpi/72,quality=+$('quality').value/100,ext=format==='png'?'png':'jpg'; | |
| const outBase=safeBase($('filename').value); | |
| try{ | |
| for(let i=0;i<pages.length;i++){ | |
| const n=pages[i];$('convertLabel').textContent=`Rendering page ${n} of ${pdfDoc.numPages}…`;setProgress('convert',(i/pages.length)*100); | |
| const page=await pdfDoc.getPage(n);let rotation=(page.rotate||0)+(+ $('rotation').value);rotation=((rotation%360)+360)%360; | |
| const vp=page.getViewport({scale:1,rotation}); | |
| let viewport; | |
| if($('scaleMode').value==='fixed'){const target=Math.max(300,Math.min(12000,+$('pixelWidth').value||2000));viewport=page.getViewport({scale:target/vp.width,rotation});} | |
| else viewport=page.getViewport({scale,rotation}); | |
| const maxSide=16000,maxPixels=100000000; | |
| let factor=Math.min(1,maxSide/Math.max(viewport.width,viewport.height),Math.sqrt(maxPixels/(viewport.width*viewport.height))); | |
| if(factor<1){viewport=page.getViewport({scale:( $('scaleMode').value==='fixed' ? Math.max(300,Math.min(12000,+$('pixelWidth').value||2000))/vp.width : scale)*factor,rotation});} | |
| const transparent=format==='png'&&$('background').value==='transparent'; | |
| const canvas=document.createElement('canvas');canvas.width=Math.max(1,Math.ceil(viewport.width));canvas.height=Math.max(1,Math.ceil(viewport.height)); | |
| const ctx=canvas.getContext('2d',{alpha:transparent}); | |
| if(!transparent){ctx.fillStyle='#ffffff';ctx.fillRect(0,0,canvas.width,canvas.height);} | |
| await page.render({canvasContext:ctx,viewport,background:transparent?undefined:'#ffffff'}).promise; | |
| if($('colorMode').value==='grayscale'){ | |
| const img=ctx.getImageData(0,0,canvas.width,canvas.height),d=img.data; | |
| for(let j=0;j<d.length;j+=4){const gray=.2126*d[j]+.7152*d[j+1]+.0722*d[j+2];d[j]=d[j+1]=d[j+2]=gray;} | |
| ctx.putImageData(img,0,0); | |
| } | |
| const mime=format==='png'?'image/png':'image/jpeg'; | |
| const blob=await new Promise((resolve,reject)=>canvas.toBlob(b=>b?resolve(b):reject(new Error('Could not encode image. Try a lower DPI.')),mime,quality)); | |
| const filename=pageFilename(n,pdfDoc.numPages,ext); | |
| converted.push({page:n,filename,blob,url:URL.createObjectURL(blob),size:blob.size}); | |
| canvas.width=1;canvas.height=1; | |
| setProgress('convert',((i+1)/pages.length)*100,`Rendered ${i+1} of ${pages.length} page${pages.length===1?'':'s'}…`); | |
| await new Promise(r=>setTimeout(r,0)); | |
| } | |
| renderResults(); | |
| $('resultSummary').textContent=`${converted.length} image${converted.length===1?'':'s'} · ${formatBytes(converted.reduce((a,x)=>a+x.size,0))} total · ${dpi} DPI setting`; | |
| $('results').classList.add('show');$('results').scrollIntoView({behavior:'smooth',block:'start'}); | |
| showNotice('Conversion completed successfully. Your images are ready to download.','success'); | |
| }catch(e){console.error(e);showNotice(e.message||'Conversion failed. Try a lower resolution or fewer pages.');} | |
| finally{$('convertBtn').disabled=false;} | |
| } | |
| function renderResults(){ | |
| const list=$('resultList');list.innerHTML=''; | |
| converted.forEach((item,i)=>{const row=document.createElement('div');row.className='result-item';row.innerHTML=`<div class="result-preview"><img alt="" src="${item.url}"></div><div class="result-info"><strong></strong><small></small></div><button class="download-one" title="Download image" aria-label="Download image">↓</button>`;row.querySelector('strong').textContent=item.filename;row.querySelector('small').textContent=formatBytes(item.size);row.querySelector('button').addEventListener('click',()=>downloadBlob(item.blob,item.filename));list.appendChild(row);}); | |
| $('downloadZipBtn').style.display=window.JSZip?'inline-block':'none'; | |
| } | |
| function downloadBlob(blob,name){const u=URL.createObjectURL(blob),a=document.createElement('a');a.href=u;a.download=name;document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(u),1500);} | |
| $('downloadEachBtn').addEventListener('click',async()=>{for(let i=0;i<converted.length;i++){downloadBlob(converted[i].blob,converted[i].filename);await new Promise(r=>setTimeout(r,250));}}); | |
| $('downloadZipBtn').addEventListener('click',async()=>{ | |
| if(!window.JSZip)return showNotice('ZIP library is unavailable. Use individual downloads instead.'); | |
| const b=$('downloadZipBtn');b.disabled=true;b.textContent='Preparing ZIP…'; | |
| try{const zip=new JSZip();converted.forEach(x=>zip.file(x.filename,x.blob));const blob=await zip.generateAsync({type:'blob',compression:'DEFLATE',compressionOptions:{level:4}});downloadBlob(blob,`${safeBase($('filename').value)}-images.zip`);} | |
| catch(e){showNotice('Could not create ZIP. Please download the images individually.');} | |
| finally{b.disabled=false;b.textContent='↓ Download all as ZIP';} | |
| }); | |
| })(); | |
| </script> | |
| </body> | |
| </html> |