Download protectpdf.html from Talha912/PDFIX: direct link, hf CLI and curl.
- Browser
- Download file 30 kB
-
https://huggingface.co/spaces/Talha912/PDFIX/resolve/main/protectpdf.html
- Command line
-
hf download hf://spaces/Talha912/PDFIX/protectpdf.html
-
curl -L -o protectpdf.html https://huggingface.co/spaces/Talha912/PDFIX/resolve/main/protectpdf.html
30 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="#10152b"> | |
| <title>Protect PDF — PDFix</title> | |
| <meta name="description" content="Protect PDF files with password encryption and configurable permissions in your browser."> | |
| <link rel="preconnect" href="https://cdnjs.cloudflare.com"> | |
| <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf-lib/1.17.1/pdf-lib.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@pdfsmaller/pdf-encrypt@1.2.0/dist/pdf-encrypt.umd.js"></script> | |
| <style> | |
| :root{--bg:#f5f7fc;--surface:#fff;--ink:#171c35;--muted:#7c849f;--line:#e6eaf3;--primary:#6558ed;--primary2:#877bff;--mint:#22b89a;--danger:#e45469;--shadow:0 18px 55px rgba(33,43,89,.08);--radius:22px} | |
| *{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 12% 0%,#eceaff 0,transparent 30%),radial-gradient(circle at 90% 10%,#e5faf6 0,transparent 25%),var(--bg);font-family:"DM Sans",sans-serif;color:var(--ink);min-height:100vh}button,input,select{font:inherit}button{cursor:pointer}.shell{max-width:1220px;margin:auto;padding:24px 26px 60px}.topbar{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-bottom:44px}.brand{display:flex;align-items:center;gap:12px;text-decoration:none;color:inherit}.brandmark{width:44px;height:44px;border-radius:15px;background:linear-gradient(135deg,#786bff,#5145d7);display:grid;place-items:center;color:white;box-shadow:0 8px 20px #6658ed44}.brandmark svg{width:24px}.brandname{font:800 22px Manrope,sans-serif;letter-spacing:-.8px}.brandname span{color:var(--primary)}.secure-pill{display:flex;align-items:center;gap:8px;background:#fff;border:1px solid var(--line);padding:10px 14px;border-radius:99px;color:#56607b;font-size:12px;font-weight:700}.secure-pill i{width:8px;height:8px;border-radius:50%;background:var(--mint);box-shadow:0 0 0 4px #22b89a1b}.hero{text-align:center;margin:0 auto 34px;max-width:760px}.eyebrow{display:inline-flex;align-items:center;gap:8px;color:var(--primary);font-size:11px;font-weight:800;letter-spacing:1.5px;text-transform:uppercase;background:#eeecff;border:1px solid #e0dcff;border-radius:99px;padding:8px 12px}.hero h1{font:800 clamp(34px,5vw,55px)/1.08 Manrope,sans-serif;letter-spacing:-2.5px;margin:18px 0 14px}.hero h1 em{font-style:normal;background:linear-gradient(90deg,#6558ed,#a16aff);-webkit-background-clip:text;background-clip:text;color:transparent}.hero p{margin:0 auto;max-width:600px;color:var(--muted);font-size:15px;line-height:1.75}.workspace{display:grid;grid-template-columns:minmax(0,1.45fr) minmax(320px,.85fr);gap:22px;align-items:start}.panel{background:var(--surface);border:1px solid #e8ebf4;border-radius:var(--radius);box-shadow:var(--shadow);overflow:hidden}.panel-head{padding:23px 25px 18px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;gap:14px}.panel-title{font:800 16px Manrope,sans-serif;letter-spacing:-.3px}.panel-sub{font-size:12px;color:var(--muted);margin-top:5px}.step{font-size:11px;font-weight:800;color:var(--primary);background:#f0efff;padding:8px 11px;border-radius:10px;white-space:nowrap}.panel-body{padding:24px}.dropzone{border:1.5px dashed #c9c9ee;border-radius:18px;background:linear-gradient(135deg,#fafaff,#f5fffc);min-height:240px;display:flex;align-items:center;justify-content:center;text-align:center;padding:26px;transition:.2s}.dropzone.drag{border-color:var(--primary);background:#f0efff;transform:scale(.995)}.drop-inner{max-width:380px}.upload-icon{width:58px;height:58px;margin:0 auto 15px;display:grid;place-items:center;border-radius:18px;color:var(--primary);background:#ebe9ff}.upload-icon svg{width:28px}.dropzone h3{margin:0 0 8px;font:800 16px Manrope,sans-serif}.dropzone p{margin:0 0 17px;color:var(--muted);font-size:12px;line-height:1.65}.btn{border:0;border-radius:12px;padding:12px 17px;font-weight:800;font-size:12px;transition:transform .15s,box-shadow .15s}.btn:hover{transform:translateY(-1px)}.btn-primary{color:#fff;background:linear-gradient(135deg,#7467ff,#5749dd);box-shadow:0 9px 20px #6558ed30}.btn-light{background:#f3f4fa;color:#535c79;border:1px solid var(--line)}.file-card{display:none;align-items:center;gap:14px;padding:15px;border:1px solid var(--line);border-radius:15px;background:#fbfcff;margin-top:16px}.file-icon{width:44px;height:48px;display:grid;place-items:center;border-radius:11px;background:#fff0f1;color:#e45265;flex:none}.file-icon svg{width:22px}.file-info{min-width:0;flex:1}.file-name{font-weight:800;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.file-meta{color:var(--muted);font-size:11px;margin-top:5px}.remove-btn{border:0;background:#f5f6fb;color:#8a91a9;width:32px;height:32px;border-radius:10px;font-size:19px}.section-label{font:800 12px Manrope,sans-serif;margin:24px 0 13px;display:flex;align-items:center;justify-content:space-between;gap:12px}.section-label small{font:500 11px "DM Sans";color:var(--muted)}.field{margin-bottom:15px}.field label{display:flex;justify-content:space-between;gap:10px;font-size:12px;font-weight:700;margin-bottom:8px;color:#424a68}.field input[type=password],.field input[type=text],.field input[type=number],.field select{width:100%;padding:13px 14px;border:1px solid #e1e5f0;border-radius:11px;outline:none;color:var(--ink);background:#fff;transition:.2s}.field input:focus,.field select:focus{border-color:#8d82ff;box-shadow:0 0 0 3px #6558ed12}.input-wrap{position:relative}.input-wrap input{padding-right:48px!important}.eye{position:absolute;right:7px;top:7px;border:0;background:#f4f5fb;border-radius:8px;width:34px;height:34px;color:#69718d}.strength{display:flex;gap:5px;margin-top:9px}.strength span{height:4px;border-radius:5px;background:#e7eaf3;flex:1}.strength span.on.weak{background:#e45b6d}.strength span.on.medium{background:#efb84c}.strength span.on.strong{background:#20b99a}.strength-copy{font-size:10px;color:var(--muted);margin-top:6px}.checkline{display:flex;align-items:flex-start;gap:10px;padding:12px 13px;border:1px solid var(--line);border-radius:12px;margin:10px 0;background:#fcfcff}.checkline input{accent-color:var(--primary);margin-top:2px}.checkline strong{display:block;font-size:12px}.checkline small{display:block;color:var(--muted);font-size:11px;line-height:1.5;margin-top:4px}.permissions{display:grid;grid-template-columns:1fr 1fr;gap:10px}.permission{display:flex;align-items:flex-start;gap:9px;padding:12px;border:1px solid var(--line);border-radius:12px;background:#fff}.permission input{accent-color:var(--primary);margin-top:2px}.permission strong{font-size:11px;display:block}.permission small{display:block;font-size:10px;color:var(--muted);margin-top:4px;line-height:1.4}.info-note{font-size:11px;color:#6c7290;line-height:1.65;background:#f5f6ff;border:1px solid #e8e8ff;border-radius:12px;padding:12px 14px;margin-top:14px}.info-note b{color:#4f49b8}.side-panel .panel-body{padding:22px}.preview-card{border:1px solid var(--line);border-radius:17px;background:linear-gradient(150deg,#fbfbff,#f5f7ff);padding:22px;text-align:center}.lock-visual{width:82px;height:88px;margin:4px auto 16px;position:relative;display:grid;place-items:center}.lock-shackle{position:absolute;top:0;width:40px;height:42px;border:6px solid #786bff;border-bottom:0;border-radius:22px 22px 0 0}.lock-body{position:absolute;bottom:0;width:67px;height:52px;border-radius:14px;background:linear-gradient(135deg,#7d70ff,#5345d8);box-shadow:0 10px 18px #6558ed35;display:grid;place-items:center;color:white}.lock-body svg{width:24px}.preview-card h3{font:800 15px Manrope,sans-serif;margin:0 0 8px}.preview-card p{color:var(--muted);font-size:11px;line-height:1.65;margin:0}.summary{margin-top:17px}.summary-row{display:flex;justify-content:space-between;gap:15px;padding:12px 0;border-bottom:1px solid #edf0f6;font-size:11px}.summary-row:last-child{border-bottom:0}.summary-row span{color:var(--muted)}.summary-row strong{text-align:right;overflow-wrap:anywhere}.secure-status{display:flex;align-items:center;gap:10px;padding:12px;border-radius:12px;background:#eafaf5;color:#16896f;font-size:11px;font-weight:800;margin-top:16px}.secure-status svg{width:18px;flex:none}.actions{margin-top:19px}.actions .btn{width:100%;padding:15px}.actions .btn:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none}.actions .subtext{text-align:center;color:var(--muted);font-size:10px;line-height:1.6;margin-top:10px}.progress-wrap{display:none;margin-top:16px}.progress-head{display:flex;justify-content:space-between;font-size:11px;color:#59617e;margin-bottom:8px}.progress{height:6px;background:#eceefa;border-radius:99px;overflow:hidden}.progress span{display:block;width:0;height:100%;background:linear-gradient(90deg,#6558ed,#24bea1);transition:width .2s}.result{display:none;margin-top:15px;padding:14px;border:1px solid #cdeee3;background:#f1fcf8;border-radius:13px}.result strong{font-size:12px;color:#16866e}.result p{font-size:11px;color:#5c7180;line-height:1.6;margin:6px 0 12px}.result .btn{width:100%}.trust-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:23px}.trust{display:flex;align-items:center;gap:12px;padding:17px;background:#ffffffad;border:1px solid #e8ebf4;border-radius:16px}.trust-icon{width:38px;height:38px;border-radius:12px;display:grid;place-items:center;background:#efedff;color:var(--primary);flex:none}.trust-icon svg{width:19px}.trust strong{display:block;font-size:11px}.trust span{display:block;font-size:10px;color:var(--muted);margin-top:4px;line-height:1.4}.footer{text-align:center;color:#9299b0;font-size:10px;margin-top:35px}.toast{position:fixed;left:50%;bottom:25px;transform:translate(-50%,20px);opacity:0;pointer-events:none;z-index:10;background:#202542;color:#fff;padding:13px 18px;border-radius:12px;box-shadow:0 15px 35px #171c3533;font-size:12px;transition:.25s;max-width:calc(100vw - 30px)}.toast.show{opacity:1;transform:translate(-50%,0)}.smallprint{font-size:10px;color:var(--muted);line-height:1.65;margin-top:10px} | |
| @media(max-width:900px){.workspace{grid-template-columns:1fr}.side-panel{order:2}.trust-grid{grid-template-columns:1fr 1fr}}@media(max-width:560px){.shell{padding:16px 13px 40px}.topbar{margin-bottom:30px}.secure-pill{font-size:0;padding:11px}.secure-pill i{margin:0}.hero h1{letter-spacing:-1.5px}.panel-head{padding:19px}.panel-body{padding:17px}.permissions{grid-template-columns:1fr}.trust-grid{grid-template-columns:1fr}.dropzone{min-height:220px}.panel{border-radius:18px}} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="shell"> | |
| <header class="topbar"> | |
| <a class="brand" href="index.html" aria-label="PDFix home"> | |
| <span class="brandmark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 3h7l5 5v13H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z"/><path d="M14 3v6h5M9 14h6M9 17h6"/></svg></span> | |
| <span class="brandname">PDF<span>ix</span></span> | |
| </a> | |
| <div class="secure-pill"><i></i> LOCAL BROWSER PROCESSING</div> | |
| </header> | |
| <section class="hero"> | |
| <span class="eyebrow">✦ PDF SECURITY STUDIO</span> | |
| <h1>Protect your PDF.<br><em>Keep it private.</em></h1> | |
| <p>Add an opening password and set document permissions before sharing. Real AES-256 PDF encryption runs locally in your browser using WebAssembly; your document is not uploaded to a PDFix server.</p> | |
| </section> | |
| <main class="workspace"> | |
| <section class="panel"> | |
| <div class="panel-head"><div><div class="panel-title">Secure your document</div><div class="panel-sub">Upload a PDF, choose a password, and export a protected copy.</div></div><span class="step">STEP 01 / 03</span></div> | |
| <div class="panel-body"> | |
| <div class="dropzone" id="dropzone"> | |
| <div class="drop-inner"> | |
| <div class="upload-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M7 3h7l5 5v13H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z"/><path d="M14 3v5h5M12 17V10m-3 3 3-3 3 3"/></svg></div> | |
| <h3>Drop your PDF right here</h3> | |
| <p>or select a file from your device. For best results, use an unencrypted PDF.</p> | |
| <button class="btn btn-primary" id="browseBtn" type="button">Choose PDF file</button> | |
| <input id="fileInput" type="file" accept="application/pdf,.pdf" hidden> | |
| </div> | |
| </div> | |
| <div class="file-card" id="fileCard"> | |
| <div class="file-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M7 3h7l5 5v13H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z"/><path d="M14 3v5h5M9 14h6M9 17h6"/></svg></div> | |
| <div class="file-info"><div class="file-name" id="fileName">document.pdf</div><div class="file-meta" id="fileMeta">PDF · 0 MB</div></div> | |
| <button class="remove-btn" id="removeFile" type="button" title="Remove file">×</button> | |
| </div> | |
| <div class="section-label">01 — Opening password <small>Required</small></div> | |
| <div class="field"> | |
| <label for="password">Set a password to open this PDF</label> | |
| <div class="input-wrap"><input id="password" type="password" autocomplete="new-password" placeholder="Use a long, unique password"><button type="button" class="eye" data-toggle="password" aria-label="Show password">◉</button></div> | |
| <div class="strength" id="strength"><span></span><span></span><span></span><span></span></div> | |
| <div class="strength-copy" id="strengthCopy">Use at least 12 characters, including a mix of character types.</div> | |
| </div> | |
| <div class="field"> | |
| <label for="confirmPassword">Confirm password</label> | |
| <div class="input-wrap"><input id="confirmPassword" type="password" autocomplete="new-password" placeholder="Enter the same password again"><button type="button" class="eye" data-toggle="confirmPassword" aria-label="Show password">◉</button></div> | |
| </div> | |
| <label class="checkline"><input id="showPassword" type="checkbox"><span><strong>Show password while typing</strong><small>Turn this off before sharing your screen or device.</small></span></label> | |
| <div class="section-label">02 — Document permissions <small>Optional</small></div> | |
| <p class="smallprint" style="margin-top:-5px;margin-bottom:12px">These settings are written as PDF permission flags. Support varies across PDF viewers; they are not a substitute for an opening password.</p> | |
| <div class="permissions"> | |
| <label class="permission"><input type="checkbox" id="allowPrint" checked><span><strong>Allow printing</strong><small>Permit printing in compatible viewers</small></span></label> | |
| <label class="permission"><input type="checkbox" id="allowCopy" checked><span><strong>Allow copying</strong><small>Permit text and content extraction</small></span></label> | |
| <label class="permission"><input type="checkbox" id="allowModify" checked><span><strong>Allow editing</strong><small>Permit document changes</small></span></label> | |
| <label class="permission"><input type="checkbox" id="allowAnnotate" checked><span><strong>Allow annotations</strong><small>Permit comments and form interactions</small></span></label> | |
| </div> | |
| <div class="field" style="margin-top:18px"> | |
| <label for="printQuality">Printing quality</label> | |
| <select id="printQuality"><option value="high">High resolution</option><option value="low">Low resolution</option></select> | |
| </div> | |
| <div class="section-label">03 — Export settings <small>Optional</small></div> | |
| <div class="field"><label for="outputName">Output file name</label><input id="outputName" type="text" value="protected-document.pdf" maxlength="120"></div> | |
| <label class="checkline"><input id="downloadAfter" type="checkbox" checked><span><strong>Download automatically when ready</strong><small>Save the protected copy to your device.</small></span></label> | |
| <div class="info-note"><b>Security note:</b> Use a unique password and share it through a separate channel. If you forget the password, PDFix cannot recover it. The tool verifies that the output is marked encrypted, but always test the downloaded PDF in a trusted reader before relying on it for sensitive documents.</div> | |
| </div> | |
| </section> | |
| <aside class="panel side-panel"> | |
| <div class="panel-head"><div><div class="panel-title">Security overview</div><div class="panel-sub">Review your protection settings.</div></div><span class="step">LIVE</span></div> | |
| <div class="panel-body"> | |
| <div class="preview-card"> | |
| <div class="lock-visual"><div class="lock-shackle"></div><div class="lock-body"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="10" width="14" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3M12 14v3"/></svg></div></div> | |
| <h3 id="previewTitle">Protection is ready to configure</h3> | |
| <p id="previewCopy">Add an opening password to make access to your PDF private.</p> | |
| </div> | |
| <div class="summary"> | |
| <div class="summary-row"><span>Selected file</span><strong id="sumFile">Not selected</strong></div> | |
| <div class="summary-row"><span>Opening password</span><strong id="sumPassword">Not set</strong></div> | |
| <div class="summary-row"><span>Printing</span><strong id="sumPrint">Allowed</strong></div> | |
| <div class="summary-row"><span>Copying</span><strong id="sumCopy">Allowed</strong></div> | |
| <div class="summary-row"><span>Editing</span><strong id="sumEdit">Allowed</strong></div> | |
| <div class="summary-row"><span>Annotations</span><strong id="sumAnnotate">Allowed</strong></div> | |
| <div class="summary-row"><span>Output</span><strong id="sumOutput">protected-document.pdf</strong></div> | |
| </div> | |
| <div class="secure-status"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-11V5l-8-3-8 3v6c0 7 8 11 8 11Z"/><path d="m9 12 2 2 4-4"/></svg><span id="statusText">Your document stays on this device during processing.</span></div> | |
| <div class="actions"><button class="btn btn-primary" id="protectBtn" type="button" disabled>🔒 Protect & download PDF</button><div class="subtext">The original file is not modified. A new protected copy will be created.</div></div> | |
| <div class="progress-wrap" id="progressWrap"><div class="progress-head"><span id="progressText">Preparing secure PDF…</span><strong id="progressPct">0%</strong></div><div class="progress"><span id="progressBar"></span></div></div> | |
| <div class="result" id="result"><strong>✓ Protected PDF is ready</strong><p id="resultText">Your file has been processed.</p><button class="btn btn-primary" id="downloadBtn" type="button">Download protected PDF</button></div> | |
| </div> | |
| </aside> | |
| </main> | |
| <section class="trust-grid"> | |
| <div class="trust"><div class="trust-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="10" width="16" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></div><div><strong>Password protection</strong><span>Add a password required to open the output PDF.</span></div></div> | |
| <div class="trust"><div class="trust-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 5h16M4 12h16M4 19h16"/><path d="m8 2-4 3 4 3"/></svg></div><div><strong>Permission controls</strong><span>Set printing, copying, editing and annotation flags.</span></div></div> | |
| <div class="trust"><div class="trust-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-11V5l-8-3-8 3v6c0 7 8 11 8 11Z"/><path d="m9 12 2 2 4-4"/></svg></div><div><strong>Browser-side workflow</strong><span>Your PDF is handled locally by this page's JavaScript.</span></div></div> | |
| </section> | |
| <footer class="footer">PDFix · Practical PDF tools, designed for your workflow.</footer> | |
| </div> | |
| <div class="toast" id="toast" role="status" aria-live="polite"></div> | |
| <script> | |
| (() => { | |
| const $ = id => document.getElementById(id); | |
| let selectedFile = null, outputBlob = null, outputUrl = null, toastTimer = null; | |
| const fields = ['password','confirmPassword','outputName','allowPrint','allowCopy','allowModify','allowAnnotate','printQuality','downloadAfter']; | |
| function toast(message){ $('toast').textContent=message; $('toast').classList.add('show'); clearTimeout(toastTimer); toastTimer=setTimeout(()=>$('toast').classList.remove('show'),3000); } | |
| function bytes(n){ if(n<1024) return n+' B'; if(n<1024*1024) return (n/1024).toFixed(1)+' KB'; return (n/1024/1024).toFixed(2)+' MB'; } | |
| function safeName(name){ let v=(name||'protected-document.pdf').trim().replace(/[\\/:*?"<>|]/g,'-').replace(/\.pdf$/i,''); return (v||'protected-document')+'.pdf'; } | |
| function setProgress(p,msg){ $('progressWrap').style.display='block'; $('progressBar').style.width=p+'%'; $('progressPct').textContent=p+'%'; $('progressText').textContent=msg; } | |
| function setFile(file){ | |
| if(!file) return; | |
| if(!/\.pdf$/i.test(file.name) && file.type!=='application/pdf'){toast('Please choose a PDF file.');return;} | |
| if(file.size===0){toast('This file is empty. Choose another PDF.');return;} | |
| selectedFile=file; outputBlob=null; | |
| $('dropzone').style.display='none'; $('fileCard').style.display='flex'; | |
| $('fileName').textContent=file.name; $('fileMeta').textContent='PDF · '+bytes(file.size); | |
| $('sumFile').textContent=file.name; $('protectBtn').disabled=false; $('result').style.display='none'; $('progressWrap').style.display='none'; | |
| $('statusText').textContent='Ready to protect locally in this browser.'; | |
| updateSummary(); | |
| } | |
| function clearFile(){ | |
| selectedFile=null;outputBlob=null;if(outputUrl){URL.revokeObjectURL(outputUrl);outputUrl=null;} | |
| $('fileInput').value='';$('dropzone').style.display='flex';$('fileCard').style.display='none';$('sumFile').textContent='Not selected';$('protectBtn').disabled=true;$('result').style.display='none';$('progressWrap').style.display='none';updateSummary(); | |
| } | |
| function passwordScore(p){ | |
| if(!p) return 0; let score=0; | |
| if(p.length>=8)score++; if(p.length>=12)score++; if(/[a-z]/.test(p)&&/[A-Z]/.test(p))score++; if(/\d/.test(p))score++; if(/[^A-Za-z0-9]/.test(p))score++; | |
| return Math.min(4,score); | |
| } | |
| function updateStrength(){ | |
| const p=$('password').value, score=passwordScore(p), segments=[...$('strength').children]; | |
| segments.forEach((s,i)=>{s.className='';if(i<score)s.classList.add('on',score<=1?'weak':score<=2?'medium':'strong');}); | |
| $('strengthCopy').textContent=!p?'Use at least 12 characters, including a mix of character types.':score<=1?'Weak password — add length and variety.':score<=2?'Fair password — consider a longer passphrase.':score===3?'Good password — longer is better.':'Strong password pattern. Avoid reusing this password.'; | |
| $('sumPassword').textContent=p?(score>=3?'Set · good pattern':'Set · improve strength'):'Not set'; | |
| $('previewTitle').textContent=p?'Password protection configured':'Protection is ready to configure'; | |
| $('previewCopy').textContent=p?'An opening password will be applied to the exported PDF.':'Add an opening password to make access to your PDF private.'; | |
| updateSummary(); | |
| } | |
| function updateSummary(){ | |
| $('sumPrint').textContent=$('allowPrint').checked?'Allowed':'Restricted'; | |
| $('sumCopy').textContent=$('allowCopy').checked?'Allowed':'Restricted'; | |
| $('sumEdit').textContent=$('allowModify').checked?'Allowed':'Restricted'; | |
| $('sumAnnotate').textContent=$('allowAnnotate').checked?'Allowed':'Restricted'; | |
| $('sumOutput').textContent=safeName($('outputName').value); | |
| $('protectBtn').disabled=!(selectedFile && window.PDFEncrypt && typeof window.PDFEncrypt.encryptPDF === 'function'); | |
| } | |
| $('browseBtn').addEventListener('click',()=>$('fileInput').click()); | |
| $('fileInput').addEventListener('change',e=>setFile(e.target.files[0])); | |
| $('removeFile').addEventListener('click',clearFile); | |
| const dz=$('dropzone'); | |
| dz.addEventListener('dragover',e=>{e.preventDefault();dz.classList.add('drag');}); | |
| dz.addEventListener('dragleave',()=>dz.classList.remove('drag')); | |
| dz.addEventListener('drop',e=>{e.preventDefault();dz.classList.remove('drag');setFile(e.dataTransfer.files[0]);}); | |
| document.querySelectorAll('[data-toggle]').forEach(btn=>btn.addEventListener('click',()=>{const input=$(btn.dataset.toggle);input.type=input.type==='password'?'text':'password';btn.textContent=input.type==='password'?'◉':'◎';})); | |
| $('showPassword').addEventListener('change',e=>{const t=e.target.checked?'text':'password';$('password').type=t;$('confirmPassword').type=t;}); | |
| $('password').addEventListener('input',updateStrength); | |
| fields.slice(2).forEach(id=>$(id).addEventListener('input',updateSummary)); | |
| ['allowPrint','allowCopy','allowModify','allowAnnotate','printQuality','outputName'].forEach(id=>$(id).addEventListener('change',updateSummary)); | |
| $('confirmPassword').addEventListener('input',()=>{if($('confirmPassword').value && $('confirmPassword').value!==$('password').value){$('confirmPassword').setCustomValidity('Passwords do not match.');}else $('confirmPassword').setCustomValidity('');}); | |
| async function protect(){ | |
| if(!selectedFile){toast('Choose a PDF first.');return;} | |
| const password=$('password').value, confirm=$('confirmPassword').value; | |
| if(password.length<8){toast('Use a password of at least 8 characters.');$('password').focus();return;} | |
| if(password!==confirm){toast('Passwords do not match.');$('confirmPassword').focus();return;} | |
| if(!window.PDFEncrypt || typeof window.PDFEncrypt.encryptPDF !== 'function'){ | |
| toast('The encryption library did not load. Check your internet connection, then refresh this page.'); | |
| $('statusText').textContent='Encryption library unavailable. Check internet and refresh.'; | |
| return; | |
| } | |
| $('protectBtn').disabled=true;$('result').style.display='none';setProgress(8,'Reading your PDF…'); | |
| try{ | |
| setProgress(22,'Loading PDF encryption engine…'); | |
| const inputBytes = new Uint8Array(await selectedFile.arrayBuffer()); | |
| setProgress(42,'Applying AES-256 password encryption…'); | |
| const ownerPassword = Array.from(crypto.getRandomValues(new Uint8Array(24)), b => b.toString(16).padStart(2,'0')).join(''); | |
| const print = !$('allowPrint').checked ? 'none' : ($('printQuality').value === 'low' ? 'low' : 'full'); | |
| const modify = $('allowModify').checked ? 'all' : ($('allowAnnotate').checked ? 'annotate' : 'none'); | |
| const encryptedBytes = await window.PDFEncrypt.encryptPDF(inputBytes, password, { | |
| algorithm: 'AES-256', | |
| ownerPassword, | |
| allowPrinting: $('allowPrint').checked, | |
| allowHighQualityPrint: $('allowPrint').checked && $('printQuality').value === 'high', | |
| allowCopying: $('allowCopy').checked, | |
| allowModifying: $('allowModify').checked, | |
| allowAnnotating: $('allowAnnotate').checked, | |
| allowFillingForms: $('allowAnnotate').checked, | |
| allowExtraction: $('allowCopy').checked, | |
| allowAssembly: $('allowModify').checked | |
| }); | |
| setProgress(82,'Checking encrypted output…'); | |
| if(!encryptedBytes || encryptedBytes.length<10) throw new Error('The encryption engine returned an empty output file.'); | |
| // The qpdf WebAssembly lock operation returns the encrypted PDF bytes. | |
| // Do not block download on a separate metadata-inspection call: that check | |
| // can fail independently even when encryption itself succeeded. | |
| outputBlob=new Blob([encryptedBytes],{type:'application/pdf'}); | |
| if(outputUrl)URL.revokeObjectURL(outputUrl); | |
| outputUrl=URL.createObjectURL(outputBlob); | |
| setProgress(100,'AES-256 protected PDF ready'); | |
| $('result').style.display='block'; | |
| $('resultText').textContent=`${safeName($('outputName').value)} · ${bytes(outputBlob.size)}. AES-256 encryption applied. Open the downloaded file in a PDF reader to confirm it prompts for your password.`; | |
| $('statusText').textContent='AES-256 encrypted copy generated in this browser.'; | |
| $('protectBtn').disabled=false; | |
| if($('downloadAfter').checked) download(); | |
| toast('Password-protected PDF is ready.'); | |
| }catch(err){ | |
| console.error(err); | |
| $('statusText').textContent='Could not complete protection.'; | |
| toast(err.message||'Unable to protect this PDF. Try another file.'); | |
| $('progressWrap').style.display='none'; | |
| }finally{$('protectBtn').disabled=!(selectedFile && window.PDFEncrypt && typeof window.PDFEncrypt.encryptPDF === 'function');} | |
| } | |
| function download(){ | |
| if(!outputBlob){toast('Protect the PDF first.');return;} | |
| // Create a fresh object URL at click time for more reliable downloads across browsers. | |
| const url=URL.createObjectURL(outputBlob); | |
| const a=document.createElement('a'); | |
| a.href=url; | |
| a.download=safeName($('outputName').value); | |
| a.style.display='none'; | |
| document.body.appendChild(a); | |
| a.click(); | |
| a.remove(); | |
| setTimeout(()=>URL.revokeObjectURL(url),60000); | |
| } | |
| $('protectBtn').addEventListener('click',protect); | |
| $('downloadBtn').addEventListener('click',download); | |
| $('outputName').addEventListener('input',updateSummary); | |
| updateSummary(); | |
| $('statusText').textContent = (window.PDFEncrypt && typeof window.PDFEncrypt.encryptPDF === 'function') | |
| ? 'AES-256 encryption engine ready. Your PDF stays on this device.' | |
| : 'Encryption library unavailable. Check internet and refresh.'; | |
| if (!(window.PDFEncrypt && typeof window.PDFEncrypt.encryptPDF === 'function')) { | |
| $('protectBtn').disabled = true; | |
| } | |
| })(); | |
| </script> | |
| </body> | |
| </html> |