{"id":1575,"date":"2026-03-27T09:33:39","date_gmt":"2026-03-27T09:33:39","guid":{"rendered":"https:\/\/primetoolhub.com\/?page_id=1575"},"modified":"2026-08-09T09:18:39","modified_gmt":"2026-08-09T09:18:39","slug":"free-offline-pdf-reader-and-editor","status":"publish","type":"page","link":"https:\/\/schoolict.net\/tools\/free-offline-pdf-reader-and-editor\/","title":{"rendered":"Free Offline PDF Reader and  Editor &#8211; Annotate &amp; Convert"},"content":{"rendered":"<div class=\"pth-hero-section\">\n  <div class=\"pth-hero-content\">\n    <h2>Complete Offline PDF Suite<\/h2>\n    <p>Looking for a Free Offline PDF Reader and Editor? View, draw, annotate, add images, extract text, and edit your PDF documents instantly with 100% privacy.<\/p>\n    <div id=\"pth-toc-placeholder\"><\/div>\n    <p><\/p>\n  <\/div>\n\n  <div class=\"pth-hero-image\">\n    <img data-no-lazy=\"1\"\n         src=\"https:\/\/schoolict.net\/tools\/wp-content\/uploads\/2026\/05\/Free-Offline-PDF-Reader-and-Editor-800x447.jpeg\"\n         width=\"800\"\n         height=\"447\"\n         alt=\"Offline PDF Suite\"\n         fetchpriority=\"high\"\n         loading=\"eager\"\n         decoding=\"async\"\n         style=\"width:100%; height:auto; display:block;\">\n  <\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><h2>Table of Contents<\/h2><nav><ul><li><a href=\"#\ud83d\udfe2-what-this-offline-pdf-editor-actually-does\">\ud83d\udfe2 What This Offline PDF Editor Actually Does<\/a><\/li><li><a href=\"#\ud83d\udd34-the-redaction-difference-worth-knowing\">\ud83d\udd34 The Redaction Difference Worth Knowing<\/a><ul><li><a href=\"#attempt-one-white-out\">Attempt one: white-out<\/a><\/li><li><a href=\"#attempt-two-redaction\">Attempt two: redaction<\/a><\/li><\/ul><\/li><li><a href=\"#\ud83d\udfe1-three-jobs-this-handles-well\">\ud83d\udfe1 Three Jobs This Handles Well<\/a><ul><li><a href=\"#marking-student-work\">Marking student work<\/a><\/li><li><a href=\"#returning-a-signed-form\">Returning a signed form<\/a><\/li><li><a href=\"#sharing-a-document-with-private-details-removed\">Sharing a document with private details removed<\/a><\/li><\/ul><\/li><li><a href=\"#\ud83d\udfe2-a-sensible-working-order\">\ud83d\udfe2 A Sensible Working Order<\/a><\/li><li><a href=\"#\ud83d\udd34-common-mistakes-and-real-limits\">\ud83d\udd34 Common Mistakes and Real Limits<\/a><\/li><li><a href=\"#\ud83d\udfe1-where-this-sits-next-to-other-tools\">\ud83d\udfe1 Where This Sits Next to Other Tools<\/a><\/li><\/ul><\/nav><\/div>\n\n\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/2.16.105\/pdf.min.js\" data-no-optimize=\"1\" data-no-minify=\"1\" data-cfasync=\"false\"><\/script>\r\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf-lib\/1.17.1\/pdf-lib.min.js\" data-no-optimize=\"1\" data-no-minify=\"1\" data-cfasync=\"false\"><\/script>\r\n\r\n<style>\r\n\/* STRICTLY SCOPED CSS FOR PRIMETOOLHUB - PDF STUDIO PRO *\/\r\n#pth-pdfeditor-wrapper {\r\n    --ora: #ea580c; --blu: #2563eb; --grn: #047857; --red: #dc2626; --amb: #b45309; --pur: #7c3aed;\r\n    --bg1: #ffffff; --bg2: #f8fafc; --bg3: #f1f5f9;\r\n    --bd1: #cbd5e1; --bd2: #e2e8f0;\r\n    --tx1: #0f172a; --tx2: #475569;\r\n    font-family: 'Inter', system-ui, sans-serif;\r\n    max-width: 1400px; margin: 0 auto; box-sizing: border-box;\r\n    background: var(--bg3); padding: 20px; border-radius: 16px; color: var(--tx1); opacity: 1;\r\n}\r\n#pth-pdfeditor-wrapper * { box-sizing: border-box; min-width: 0; }\r\n\r\n#pth-pdfeditor-wrapper .pth-intro-card { background: var(--bg1); border: 1px solid var(--bd2); border-radius: 12px; padding: 22px; margin-bottom: 18px; }\r\n#pth-pdfeditor-wrapper .pth-tool-title { font-size: 27px; font-weight: 800; color: var(--blu); margin: 0 0 10px 0; line-height: 1.25; }\r\n#pth-pdfeditor-wrapper .pth-intro-card p { font-size: 14px; color: var(--tx2); margin: 0; line-height: 1.65; }\r\n#pth-pdfeditor-wrapper .pth-badges { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }\r\n#pth-pdfeditor-wrapper .pth-badge-top { font-size: 12px; font-weight: 600; padding: 4px 12px; border-radius: 20px; }\r\n#pth-pdfeditor-wrapper .pth-version-badge { background: #eff6ff; color: #1d4ed8; border: 1px solid #bfdbfe; }\r\n#pth-pdfeditor-wrapper .pth-status-offline { background: #ecfdf5; color: #065f46; border: 1px solid #a7f3d0; }\r\n#pth-pdfeditor-wrapper .pth-badge-ora { background: #fff7ed; color: #9a3412; border: 1px solid #fed7aa; }\r\n\r\n#pth-pdfeditor-wrapper .pth-actions-bar { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }\r\n#pth-pdfeditor-wrapper .layout { display: grid; grid-template-columns: 1.8fr 1fr; gap: 16px; align-items: start; }\r\n#pth-pdfeditor-wrapper .layout.fw { grid-template-columns: 1fr; }\r\n#pth-pdfeditor-wrapper .layout.fw .sidebar { display: none; }\r\n\r\n#pth-pdfeditor-wrapper .card { background: var(--bg1); border: 1px solid var(--bd2); border-radius: 12px; overflow: hidden; }\r\n#pth-pdfeditor-wrapper .card-pad { padding: 14px; }\r\n#pth-pdfeditor-wrapper .hdr { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; border-bottom: 1px solid var(--bd2); background: var(--bg2); flex-wrap: wrap; gap: 8px; }\r\n#pth-pdfeditor-wrapper .hdr-title { font-size: 15px; font-weight: 700; color: var(--tx1); margin: 0; }\r\n#pth-pdfeditor-wrapper label { font-size: 12px; color: var(--tx2); font-weight: 600; }\r\n\r\n#pth-pdfeditor-wrapper .btn { padding: 8px 14px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; border: 1px solid var(--bd1); transition: background .15s; display: inline-flex; align-items: center; gap: 6px; font-family: inherit; background: var(--bg1); color: var(--tx1); }\r\n#pth-pdfeditor-wrapper .btn:hover { background: var(--bg2); }\r\n#pth-pdfeditor-wrapper .btn:disabled { opacity: 1; color: #94a3b8; cursor: not-allowed; background: var(--bg3); }\r\n#pth-pdfeditor-wrapper .btn-primary { background: var(--blu); color: #fff; border-color: var(--blu); }\r\n#pth-pdfeditor-wrapper .btn-primary:hover { background: #1d4ed8; }\r\n#pth-pdfeditor-wrapper .btn-grn { background: var(--grn); color: #fff; border-color: var(--grn); }\r\n#pth-pdfeditor-wrapper .btn-grn:hover { background: #065f46; }\r\n#pth-pdfeditor-wrapper .btn-red { background: var(--red); color: #fff; border-color: var(--red); }\r\n#pth-pdfeditor-wrapper .btn-red:hover { background: #b91c1c; }\r\n#pth-pdfeditor-wrapper .btn-sm { padding: 5px 10px; font-size: 12px; }\r\n\r\n#pth-pdfeditor-wrapper .drop { border: 2px dashed var(--bd1); border-radius: 12px; background: var(--bg2); padding: 46px 20px; text-align: center; cursor: pointer; }\r\n#pth-pdfeditor-wrapper .drop.over { border-color: var(--blu); background: #eff6ff; }\r\n#pth-pdfeditor-wrapper .drop .big { font-size: 34px; margin-bottom: 8px; }\r\n#pth-pdfeditor-wrapper .drop .t1 { font-size: 15px; font-weight: 700; color: var(--tx1); margin-bottom: 4px; }\r\n#pth-pdfeditor-wrapper .drop .t2 { font-size: 12px; color: var(--tx2); }\r\n\r\n#pth-pdfeditor-wrapper .tbar { display: flex; align-items: center; gap: 6px; padding: 8px 12px; background: var(--bg2); border-bottom: 1px solid var(--bd2); flex-wrap: wrap; }\r\n#pth-pdfeditor-wrapper .tsep { width: 1px; height: 20px; background: var(--bd1); }\r\n#pth-pdfeditor-wrapper .tool-btn { padding: 6px 10px; border-radius: 7px; border: 1px solid var(--bd1); background: var(--bg1); color: var(--tx1); font-size: 12px; font-weight: 600; cursor: pointer; font-family: inherit; }\r\n#pth-pdfeditor-wrapper .tool-btn:hover { background: var(--bg3); }\r\n#pth-pdfeditor-wrapper .tool-btn.on { background: var(--blu); color: #fff; border-color: var(--blu); }\r\n#pth-pdfeditor-wrapper .pginfo { font-size: 12px; font-weight: 700; color: var(--tx2); padding: 0 4px; }\r\n\r\n#pth-pdfeditor-wrapper .stage { position: relative; background: #64748b; padding: 16px; overflow: auto; max-height: 74vh; min-height: 420px; text-align: center; }\r\n#pth-pdfeditor-wrapper .canvas-box { position: relative; display: inline-block; box-shadow: 0 4px 18px rgba(0,0,0,.28); background: #fff; }\r\n#pth-pdfeditor-wrapper .canvas-box canvas { display: block; }\r\n#pth-pdfeditor-wrapper #pth-pdf-overlay { position: absolute; top: 0; left: 0; cursor: crosshair; }\r\n\r\n#pth-pdfeditor-wrapper .tabs { display: flex; gap: 4px; padding: 8px 8px 0; background: var(--bg2); border-bottom: 1px solid var(--bd2); flex-wrap: wrap; }\r\n#pth-pdfeditor-wrapper .tab { padding: 8px 11px; border-radius: 8px 8px 0 0; border: 1px solid transparent; background: transparent; color: var(--tx2); font-size: 12px; font-weight: 700; cursor: pointer; font-family: inherit; }\r\n#pth-pdfeditor-wrapper .tab:hover { background: var(--bg3); }\r\n#pth-pdfeditor-wrapper .tab.on { background: var(--bg1); color: var(--blu); border-color: var(--bd2); border-bottom-color: var(--bg1); margin-bottom: -1px; }\r\n#pth-pdfeditor-wrapper .panel { display: none; padding: 14px; }\r\n#pth-pdfeditor-wrapper .panel.on { display: block; }\r\n#pth-pdfeditor-wrapper .panel h4 { font-size: 12px; font-weight: 800; color: var(--tx2); margin: 0 0 8px; text-transform: uppercase; letter-spacing: .5px; }\r\n#pth-pdfeditor-wrapper .panel .row { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }\r\n#pth-pdfeditor-wrapper .panel .fld { margin-bottom: 12px; }\r\n#pth-pdfeditor-wrapper .panel .fld label { display: block; margin-bottom: 4px; }\r\n#pth-pdfeditor-wrapper input[type=\"text\"], #pth-pdfeditor-wrapper input[type=\"number\"], #pth-pdfeditor-wrapper select, #pth-pdfeditor-wrapper textarea {\r\n  width: 100%; padding: 7px 9px; border: 1px solid var(--bd1); border-radius: 7px; font-size: 13px; font-family: inherit; color: var(--tx1); background: var(--bg1);\r\n}\r\n#pth-pdfeditor-wrapper input[type=\"color\"] { width: 42px; height: 32px; padding: 2px; border: 1px solid var(--bd1); border-radius: 7px; background: var(--bg1); cursor: pointer; }\r\n#pth-pdfeditor-wrapper textarea { resize: vertical; min-height: 68px; }\r\n\r\n#pth-pdfeditor-wrapper .thumbs { display: grid; grid-template-columns: repeat(2, 1fr); gap: 9px; max-height: 430px; overflow-y: auto; }\r\n#pth-pdfeditor-wrapper .thumb { border: 2px solid var(--bd2); border-radius: 8px; padding: 5px; background: var(--bg2); cursor: pointer; position: relative; }\r\n#pth-pdfeditor-wrapper .thumb.cur { border-color: var(--blu); }\r\n#pth-pdfeditor-wrapper .thumb.drag { opacity: 1; border-style: dashed; border-color: var(--ora); }\r\n#pth-pdfeditor-wrapper .thumb canvas { width: 100%; height: auto; display: block; border-radius: 3px; background: #fff; }\r\n#pth-pdfeditor-wrapper .thumb .tn { font-size: 11px; font-weight: 700; color: var(--tx2); text-align: center; padding-top: 4px; }\r\n#pth-pdfeditor-wrapper .thumb .tacts { display: flex; gap: 3px; justify-content: center; padding-top: 4px; }\r\n#pth-pdfeditor-wrapper .thumb .tacts button { border: 1px solid var(--bd1); background: var(--bg1); border-radius: 5px; font-size: 11px; padding: 2px 6px; cursor: pointer; font-family: inherit; color: var(--tx1); }\r\n\r\n#pth-pdfeditor-wrapper .alist { max-height: 220px; overflow-y: auto; border: 1px solid var(--bd2); border-radius: 8px; }\r\n#pth-pdfeditor-wrapper .aitem { display: flex; align-items: center; gap: 8px; padding: 7px 9px; border-bottom: 1px solid var(--bd2); font-size: 12px; }\r\n#pth-pdfeditor-wrapper .aitem:last-child { border-bottom: none; }\r\n#pth-pdfeditor-wrapper .aitem .adot { width: 11px; height: 11px; border-radius: 3px; flex-shrink: 0; border: 1px solid rgba(0,0,0,.15); }\r\n#pth-pdfeditor-wrapper .aitem .atx { flex: 1; color: var(--tx1); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\r\n#pth-pdfeditor-wrapper .aitem .apg { color: var(--tx2); font-weight: 700; font-size: 11px; }\r\n#pth-pdfeditor-wrapper .aitem button { border: none; background: transparent; color: var(--red); cursor: pointer; font-size: 14px; font-weight: 800; padding: 0 3px; }\r\n#pth-pdfeditor-wrapper .empty { font-size: 12px; color: var(--tx2); text-align: center; padding: 18px 10px; }\r\n\r\n#pth-pdfeditor-wrapper .out { background: var(--bg2); border: 1px solid var(--bd2); border-radius: 8px; padding: 11px; font-size: 12px; color: var(--tx1); max-height: 250px; overflow: auto; white-space: pre-wrap; word-break: break-word; min-height: 90px; line-height: 1.6; }\r\n#pth-pdfeditor-wrapper .kv { display: flex; justify-content: space-between; gap: 10px; padding: 6px 0; border-bottom: 1px solid var(--bd2); font-size: 12px; }\r\n#pth-pdfeditor-wrapper .kv:last-child { border-bottom: none; }\r\n#pth-pdfeditor-wrapper .kv span:first-child { color: var(--tx2); font-weight: 600; }\r\n#pth-pdfeditor-wrapper .kv span:last-child { color: var(--tx1); font-weight: 700; text-align: right; word-break: break-word; }\r\n\r\n#pth-pdfeditor-wrapper .sigpad { border: 2px dashed var(--bd1); border-radius: 8px; background: var(--bg2); }\r\n#pth-pdfeditor-wrapper .sigpad canvas { display: block; width: 100%; height: auto; cursor: crosshair; touch-action: none; }\r\n\r\n#pth-pdfeditor-wrapper .note { background: #fffbeb; border: 1px solid #fde68a; border-radius: 8px; padding: 10px 12px; font-size: 12px; color: #78350f; line-height: 1.6; margin-bottom: 12px; }\r\n#pth-pdfeditor-wrapper .fieldrow { border: 1px solid var(--bd2); border-radius: 8px; padding: 9px; margin-bottom: 8px; background: var(--bg2); }\r\n#pth-pdfeditor-wrapper .fieldrow .fn { font-size: 11px; font-weight: 800; color: var(--tx2); margin-bottom: 5px; word-break: break-word; }\r\n\r\n#pth-pdfeditor-wrapper #pth-toast { position: fixed; bottom: 20px; right: 20px; background: #0f172a; color: #fff; padding: 12px 18px; border-radius: 10px; font-size: 13px; font-weight: 600; z-index: 999999; display: none; max-width: 330px; line-height: 1.5; }\r\n#pth-pdfeditor-wrapper #pth-toast.show { display: block; }\r\n#pth-pdfeditor-wrapper #pth-toast.err { background: #991b1b; }\r\n#pth-pdfeditor-wrapper #pth-toast.ok { background: #065f46; }\r\n\r\n@media (max-width: 900px) {\r\n  #pth-pdfeditor-wrapper { padding: 14px; }\r\n  #pth-pdfeditor-wrapper .layout { grid-template-columns: 1fr; }\r\n  #pth-pdfeditor-wrapper .stage { max-height: 58vh; }\r\n}\r\n<\/style>\r\n\r\n<div id=\"pth-pdfeditor-wrapper\">\r\n\r\n  <div class=\"pth-intro-card\">\r\n    <h2 class=\"pth-tool-title\">PDF Studio Pro - Read, Annotate, Sign &amp; Redact<\/h2>\r\n    <div class=\"pth-badges\">\r\n      <span class=\"pth-badge-top pth-version-badge\">v6.0<\/span>\r\n      <span class=\"pth-badge-top pth-status-offline\">100% Offline<\/span>\r\n      <span class=\"pth-badge-top pth-badge-ora\">Zero Server Uploads<\/span>\r\n    <\/div>\r\n    <p>Open a PDF and work on it directly in this page. Mark up text, reorder pages, fill and sign forms, pull out the text and images, permanently black out private details, then save a fresh copy. Your file is read by your own browser and never sent anywhere.<\/p>\r\n  <\/div>\r\n\r\n  <div class=\"pth-actions-bar\">\r\n    <button type=\"button\" class=\"btn\" id=\"pth-sample-btn\">Load Sample<\/button>\r\n    <button type=\"button\" class=\"btn\" id=\"pth-fw-btn\">Full Width<\/button>\r\n    <button type=\"button\" class=\"btn\" id=\"pth-clear-btn\">Clear<\/button>\r\n    <button type=\"button\" class=\"btn btn-grn\" id=\"pth-save-btn\" disabled>Save PDF<\/button>\r\n  <\/div>\r\n\r\n  <div class=\"layout\" id=\"pth-layout\">\r\n\r\n    <div class=\"card\">\r\n      <div class=\"hdr\">\r\n        <h3 class=\"hdr-title\">PDF Workspace<\/h3>\r\n        <div>\r\n          <input type=\"file\" id=\"pth-file-input\" accept=\"application\/pdf\" style=\"display:none\" aria-label=\"Choose a PDF file\">\r\n          <button type=\"button\" class=\"btn btn-sm\" id=\"pth-open-btn\">Open PDF<\/button>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div class=\"card-pad\" id=\"pth-drop-wrap\">\r\n        <div class=\"drop\" id=\"pth-drop\" role=\"button\" tabindex=\"0\" aria-label=\"Click or drop a PDF file to open\">\r\n          <div class=\"big\" aria-hidden=\"true\">&#128196;<\/div>\r\n          <div class=\"t1\">Click or drag &amp; drop a PDF here<\/div>\r\n          <div class=\"t2\">Everything runs in your browser - the file never leaves your device<\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div id=\"pth-viewer\" style=\"display:none\">\r\n        <div class=\"tbar\">\r\n          <button type=\"button\" class=\"tool-btn\" id=\"pth-prev\">Prev<\/button>\r\n          <span class=\"pginfo\" id=\"pth-pginfo\">Page 0 \/ 0<\/span>\r\n          <button type=\"button\" class=\"tool-btn\" id=\"pth-next\">Next<\/button>\r\n          <span class=\"tsep\" aria-hidden=\"true\"><\/span>\r\n          <button type=\"button\" class=\"tool-btn\" id=\"pth-zout\">Zoom -<\/button>\r\n          <button type=\"button\" class=\"tool-btn\" id=\"pth-zin\">Zoom +<\/button>\r\n          <button type=\"button\" class=\"tool-btn\" id=\"pth-fit\">Fit<\/button>\r\n          <button type=\"button\" class=\"tool-btn\" id=\"pth-rot\">Rotate View<\/button>\r\n          <span class=\"tsep\" aria-hidden=\"true\"><\/span>\r\n          <button type=\"button\" class=\"tool-btn\" id=\"pth-undo\">Undo<\/button>\r\n          <span class=\"pginfo\" id=\"pth-toolinfo\">Tool: Select<\/span>\r\n        <\/div>\r\n        <div class=\"stage\" id=\"pth-stage\">\r\n          <div class=\"canvas-box\" id=\"pth-canvas-box\">\r\n            <canvas id=\"pth-pdf-canvas\" width=\"820\" height=\"1060\"><\/canvas>\r\n            <canvas id=\"pth-pdf-overlay\" width=\"820\" height=\"1060\"><\/canvas>\r\n          <\/div>\r\n        <\/div>\r\n      <\/div>\r\n    <\/div>\r\n\r\n    <div class=\"card sidebar\">\r\n      <div class=\"tabs\" role=\"tablist\">\r\n        <button type=\"button\" class=\"tab on\" data-tab=\"mark\" role=\"tab\">Annotate<\/button>\r\n        <button type=\"button\" class=\"tab\" data-tab=\"pages\" role=\"tab\">Pages<\/button>\r\n        <button type=\"button\" class=\"tab\" data-tab=\"extract\" role=\"tab\">Extract<\/button>\r\n        <button type=\"button\" class=\"tab\" data-tab=\"sign\" role=\"tab\">Sign<\/button>\r\n        <button type=\"button\" class=\"tab\" data-tab=\"secure\" role=\"tab\">Secure<\/button>\r\n      <\/div>\r\n\r\n      <div class=\"panel on\" data-panel=\"mark\">\r\n        <h4>Markup Tools<\/h4>\r\n        <div class=\"row\">\r\n          <button type=\"button\" class=\"tool-btn on\" data-tool=\"select\">Select<\/button>\r\n          <button type=\"button\" class=\"tool-btn\" data-tool=\"ink\">Draw<\/button>\r\n          <button type=\"button\" class=\"tool-btn\" data-tool=\"highlight\">Highlight<\/button>\r\n          <button type=\"button\" class=\"tool-btn\" data-tool=\"underline\">Underline<\/button>\r\n          <button type=\"button\" class=\"tool-btn\" data-tool=\"strike\">Strikeout<\/button>\r\n          <button type=\"button\" class=\"tool-btn\" data-tool=\"rect\">Box<\/button>\r\n          <button type=\"button\" class=\"tool-btn\" data-tool=\"text\">Text<\/button>\r\n          <button type=\"button\" class=\"tool-btn\" data-tool=\"note\">Sticky Note<\/button>\r\n          <button type=\"button\" class=\"tool-btn\" data-tool=\"whiteout\">White-out<\/button>\r\n        <\/div>\r\n        <div class=\"fld\">\r\n          <label for=\"pth-color\">Colour<\/label>\r\n          <input type=\"color\" id=\"pth-color\" value=\"#dc2626\">\r\n        <\/div>\r\n        <div class=\"fld\">\r\n          <label for=\"pth-size\">Line width \/ font size<\/label>\r\n          <input type=\"number\" id=\"pth-size\" value=\"3\" min=\"1\" max=\"48\">\r\n        <\/div>\r\n        <div class=\"fld\" id=\"pth-textfld\" style=\"display:none\">\r\n          <label for=\"pth-textval\">Text to place<\/label>\r\n          <input type=\"text\" id=\"pth-textval\" placeholder=\"Type text, then click the page\">\r\n        <\/div>\r\n        <h4>Annotations On This Page<\/h4>\r\n        <div class=\"alist\" id=\"pth-alist\"><div class=\"empty\">No annotations yet.<\/div><\/div>\r\n        <div class=\"row\" style=\"margin-top:10px\">\r\n          <button type=\"button\" class=\"btn btn-sm\" id=\"pth-clear-page\">Clear This Page<\/button>\r\n          <button type=\"button\" class=\"btn btn-sm btn-red\" id=\"pth-clear-all\">Clear All<\/button>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div class=\"panel\" data-panel=\"pages\">\r\n        <h4>Page Organizer<\/h4>\r\n        <div class=\"note\">Drag a thumbnail onto another to change page order. Rotation and deletion here are saved into the exported PDF. To combine or split separate files, use the PDF Merge &amp; Split Studio.<\/div>\r\n        <div class=\"row\">\r\n          <button type=\"button\" class=\"btn btn-sm\" id=\"pth-add-blank\">Add Blank Page<\/button>\r\n          <button type=\"button\" class=\"btn btn-sm\" id=\"pth-reset-pages\">Reset Order<\/button>\r\n        <\/div>\r\n        <div class=\"thumbs\" id=\"pth-thumbs\"><div class=\"empty\">Open a PDF to see pages.<\/div><\/div>\r\n      <\/div>\r\n\r\n      <div class=\"panel\" data-panel=\"extract\">\r\n        <h4>Text<\/h4>\r\n        <div class=\"row\">\r\n          <button type=\"button\" class=\"btn btn-sm\" id=\"pth-x-page\">This Page<\/button>\r\n          <button type=\"button\" class=\"btn btn-sm\" id=\"pth-x-all\">Whole Document<\/button>\r\n          <button type=\"button\" class=\"btn btn-sm\" id=\"pth-x-dl\" disabled>Download .txt<\/button>\r\n        <\/div>\r\n        <div class=\"fld\">\r\n          <label for=\"pth-x-search\">Search inside extracted text<\/label>\r\n          <input type=\"text\" id=\"pth-x-search\" placeholder=\"Type to filter lines\">\r\n        <\/div>\r\n        <div class=\"out\" id=\"pth-x-out\">Extracted text will appear here.<\/div>\r\n        <h4 style=\"margin-top:14px\">Images &amp; Page Export<\/h4>\r\n        <div class=\"row\">\r\n          <button type=\"button\" class=\"btn btn-sm\" id=\"pth-x-img\">Extract Images<\/button>\r\n          <button type=\"button\" class=\"btn btn-sm\" id=\"pth-x-png\">Save Page as PNG<\/button>\r\n        <\/div>\r\n        <div class=\"out\" id=\"pth-img-out\" style=\"min-height:50px\">Image results will appear here.<\/div>\r\n      <\/div>\r\n\r\n      <div class=\"panel\" data-panel=\"sign\">\r\n        <h4>Signature<\/h4>\r\n        <div class=\"fld\">\r\n          <label for=\"pth-sigpad-canvas\">Draw your signature<\/label>\r\n          <div class=\"sigpad\"><canvas id=\"pth-sigpad-canvas\" width=\"560\" height=\"200\"><\/canvas><\/div>\r\n        <\/div>\r\n        <div class=\"row\">\r\n          <button type=\"button\" class=\"btn btn-sm\" id=\"pth-sig-clear\">Clear Pad<\/button>\r\n          <button type=\"button\" class=\"btn btn-sm btn-primary\" id=\"pth-sig-use\">Use Signature<\/button>\r\n        <\/div>\r\n        <div class=\"fld\">\r\n          <label for=\"pth-sig-type\">Or type a signature<\/label>\r\n          <input type=\"text\" id=\"pth-sig-type\" placeholder=\"Your name\">\r\n        <\/div>\r\n        <div class=\"row\">\r\n          <button type=\"button\" class=\"btn btn-sm\" id=\"pth-sig-typed\">Use Typed Signature<\/button>\r\n        <\/div>\r\n        <h4>Quick Stamps<\/h4>\r\n        <div class=\"row\">\r\n          <button type=\"button\" class=\"btn btn-sm\" id=\"pth-stamp-date\">Today's Date<\/button>\r\n          <button type=\"button\" class=\"btn btn-sm\" id=\"pth-stamp-check\">Check Mark<\/button>\r\n        <\/div>\r\n        <h4>Form Fields<\/h4>\r\n        <div class=\"row\">\r\n          <button type=\"button\" class=\"btn btn-sm\" id=\"pth-form-scan\">Detect Fields<\/button>\r\n          <label style=\"display:flex;align-items:center;gap:5px;font-size:12px;\">\r\n            <input type=\"checkbox\" id=\"pth-form-flat\" style=\"width:auto\"> Flatten on save\r\n          <\/label>\r\n        <\/div>\r\n        <div id=\"pth-form-list\"><div class=\"empty\">Click Detect Fields to look for a fillable form.<\/div><\/div>\r\n      <\/div>\r\n\r\n      <div class=\"panel\" data-panel=\"secure\">\r\n        <h4>Permanent Redaction<\/h4>\r\n        <div class=\"note\">White-out only covers text - the words are still inside the file and can be copied out. Redaction rebuilds the marked pages as flat images, so the hidden text is genuinely gone from the saved copy.<\/div>\r\n        <div class=\"row\">\r\n          <button type=\"button\" class=\"tool-btn\" data-tool=\"redact\">Mark Redaction Area<\/button>\r\n          <button type=\"button\" class=\"btn btn-sm btn-red\" id=\"pth-redact-clear\">Clear Marks<\/button>\r\n        <\/div>\r\n        <div class=\"out\" id=\"pth-redact-out\" style=\"min-height:44px\">No redaction areas marked.<\/div>\r\n        <h4 style=\"margin-top:14px\">Document Information<\/h4>\r\n        <div class=\"row\"><button type=\"button\" class=\"btn btn-sm\" id=\"pth-info-btn\">Read Document Info<\/button><\/div>\r\n        <div id=\"pth-info-out\"><div class=\"empty\">Open a PDF and click Read Document Info.<\/div><\/div>\r\n        <h4 style=\"margin-top:14px\">Metadata Cleaner<\/h4>\r\n        <div class=\"fld\">\r\n          <label style=\"display:flex;align-items:center;gap:6px;\">\r\n            <input type=\"checkbox\" id=\"pth-scrub\" style=\"width:auto\"> Remove author, title, producer and dates when saving\r\n          <\/label>\r\n        <\/div>\r\n      <\/div>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <div id=\"pth-toast\" role=\"status\" aria-live=\"polite\"><\/div>\r\n<\/div>\r\n\r\n<script data-no-optimize=\"1\" data-no-minify=\"1\" data-cfasync=\"false\">\r\nwindow.pthPdfStudioApp = (function () {\r\n  'use strict';\r\n\r\n  var W = null, booted = false;\r\n  var S = {\r\n    bytes: null, doc: null, page: 1, total: 0, scale: 1.2, viewRot: 0,\r\n    tool: 'select', annots: [], order: [], pageRot: {}, blanks: [],\r\n    sig: null, extracted: '', fields: [], viewport: null, drawing: false,\r\n    start: null, inkPts: [], seq: 1, fitDone: false\r\n  };\r\n\r\n  function $(id) { return document.getElementById(id); }\r\n  function q(sel) { return W ? W.querySelector(sel) : null; }\r\n  function qa(sel) { return W ? W.querySelectorAll(sel) : []; }\r\n\r\n  var toastTimer = null;\r\n  function toast(msg, kind) {\r\n    var t = $('pth-toast');\r\n    if (!t) { return; }\r\n    t.className = 'show' + (kind ? ' ' + kind : '');\r\n    t.textContent = msg;\r\n    if (toastTimer) { clearTimeout(toastTimer); }\r\n    toastTimer = setTimeout(function () { t.className = ''; }, 3000);\r\n  }\r\n\r\n  function hexToRgb(hex) {\r\n    var h = String(hex).replace('#', '');\r\n    if (h.length === 3) { h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2]; }\r\n    return {\r\n      r: parseInt(h.substring(0, 2), 16) \/ 255,\r\n      g: parseInt(h.substring(2, 4), 16) \/ 255,\r\n      b: parseInt(h.substring(4, 6), 16) \/ 255\r\n    };\r\n  }\r\n\r\n  function esc(s) {\r\n    return String(s).replace(\/&\/g, '&amp;').replace(\/<\/g, '&lt;')\r\n      .replace(\/>\/g, '&gt;').replace(\/\"\/g, '&quot;');\r\n  }\r\n\r\n  \/* ---------- Page order helpers ---------- *\/\r\n  function resetOrder() {\r\n    S.order = [];\r\n    for (var i = 1; i <= S.total; i++) { S.order.push({ src: i, id: 'p' + i }); }\r\n    S.pageRot = {};\r\n    S.blanks = [];\r\n  }\r\n\r\n  function curEntry() { return S.order[S.page - 1]; }\r\n\r\n  \/* ---------- Rendering ---------- *\/\r\n  function renderPage() {\r\n    if (!S.doc || !S.order.length) { return; }\r\n    var entry = curEntry();\r\n    if (!entry) { return; }\r\n\r\n    if (entry.blank) {\r\n      var cv = $('pth-pdf-canvas');\r\n      var w = Math.round(612 * S.scale), h = Math.round(792 * S.scale);\r\n      cv.width = w; cv.height = h;\r\n      var cx = cv.getContext('2d');\r\n      cx.fillStyle = '#ffffff';\r\n      cx.fillRect(0, 0, w, h);\r\n      S.viewport = { width: w, height: h, scale: S.scale, blank: true, pw: 612, ph: 792 };\r\n      sizeOverlay(w, h);\r\n      drawOverlay();\r\n      updateInfo();\r\n      return;\r\n    }\r\n\r\n    S.doc.getPage(entry.src).then(function (pg) {\r\n      var rot = (pg.rotate + S.viewRot + (S.pageRot[entry.id] || 0)) % 360;\r\n      var vp = pg.getViewport({ scale: S.scale, rotation: rot });\r\n      var cv = $('pth-pdf-canvas');\r\n      cv.width = Math.floor(vp.width);\r\n      cv.height = Math.floor(vp.height);\r\n      var ctx = cv.getContext('2d');\r\n      ctx.fillStyle = '#ffffff';\r\n      ctx.fillRect(0, 0, cv.width, cv.height);\r\n      pg.render({ canvasContext: ctx, viewport: vp }).promise.then(function () {\r\n        S.viewport = vp;\r\n        sizeOverlay(cv.width, cv.height);\r\n        drawOverlay();\r\n        updateInfo();\r\n      });\r\n    })['catch'](function () { toast('Could not render that page.', 'err'); });\r\n  }\r\n\r\n  function sizeOverlay(w, h) {\r\n    var ov = $('pth-pdf-overlay');\r\n    ov.width = w; ov.height = h;\r\n    ov.style.width = w + 'px';\r\n    ov.style.height = h + 'px';\r\n  }\r\n\r\n  function toPdfPoint(x, y) {\r\n    if (S.viewport && S.viewport.blank) {\r\n      return [x \/ S.scale, (S.viewport.height - y) \/ S.scale];\r\n    }\r\n    return S.viewport.convertToPdfPoint(x, y);\r\n  }\r\n\r\n  function toViewPoint(x, y) {\r\n    if (S.viewport && S.viewport.blank) {\r\n      return [x * S.scale, S.viewport.height - (y * S.scale)];\r\n    }\r\n    return S.viewport.convertToViewportPoint(x, y);\r\n  }\r\n\r\n  function pageAnnots() {\r\n    var e = curEntry();\r\n    if (!e) { return []; }\r\n    var out = [], i;\r\n    for (i = 0; i < S.annots.length; i++) {\r\n      if (S.annots[i].pid === e.id) { out.push(S.annots[i]); }\r\n    }\r\n    return out;\r\n  }\r\n\r\n  function drawOverlay() {\r\n    var ov = $('pth-pdf-overlay');\r\n    var c = ov.getContext('2d');\r\n    c.clearRect(0, 0, ov.width, ov.height);\r\n    var list = pageAnnots(), i, a;\r\n    for (i = 0; i < list.length; i++) {\r\n      a = list[i];\r\n      drawOne(c, a);\r\n    }\r\n    renderAnnotList();\r\n  }\r\n\r\n  function rectOnScreen(a) {\r\n    var p1 = toViewPoint(a.x, a.y);\r\n    var p2 = toViewPoint(a.x + a.w, a.y + a.h);\r\n    return {\r\n      x: Math.min(p1[0], p2[0]), y: Math.min(p1[1], p2[1]),\r\n      w: Math.abs(p2[0] - p1[0]), h: Math.abs(p2[1] - p1[1])\r\n    };\r\n  }\r\n\r\n  function drawOne(c, a) {\r\n    var r, i, pt;\r\n    c.save();\r\n    if (a.type === 'ink') {\r\n      c.strokeStyle = a.color; c.lineWidth = a.size * S.scale;\r\n      c.lineCap = 'round'; c.lineJoin = 'round';\r\n      c.beginPath();\r\n      for (i = 0; i < a.pts.length; i++) {\r\n        pt = toViewPoint(a.pts[i][0], a.pts[i][1]);\r\n        if (i === 0) { c.moveTo(pt[0], pt[1]); } else { c.lineTo(pt[0], pt[1]); }\r\n      }\r\n      c.stroke();\r\n    } else if (a.type === 'highlight') {\r\n      r = rectOnScreen(a);\r\n      c.globalAlpha = 0.38; c.fillStyle = a.color;\r\n      c.fillRect(r.x, r.y, r.w, r.h);\r\n    } else if (a.type === 'underline' || a.type === 'strike') {\r\n      r = rectOnScreen(a);\r\n      c.strokeStyle = a.color; c.lineWidth = Math.max(1, a.size * S.scale * 0.5);\r\n      var yy = a.type === 'underline' ? r.y + r.h : r.y + r.h \/ 2;\r\n      c.beginPath(); c.moveTo(r.x, yy); c.lineTo(r.x + r.w, yy); c.stroke();\r\n    } else if (a.type === 'rect') {\r\n      r = rectOnScreen(a);\r\n      c.strokeStyle = a.color; c.lineWidth = a.size * S.scale;\r\n      c.strokeRect(r.x, r.y, r.w, r.h);\r\n    } else if (a.type === 'whiteout') {\r\n      r = rectOnScreen(a);\r\n      c.fillStyle = '#ffffff';\r\n      c.fillRect(r.x, r.y, r.w, r.h);\r\n      c.strokeStyle = '#cbd5e1'; c.lineWidth = 1;\r\n      c.strokeRect(r.x, r.y, r.w, r.h);\r\n    } else if (a.type === 'redact') {\r\n      r = rectOnScreen(a);\r\n      c.fillStyle = '#0f172a';\r\n      c.fillRect(r.x, r.y, r.w, r.h);\r\n      c.strokeStyle = '#dc2626'; c.lineWidth = 2;\r\n      c.strokeRect(r.x, r.y, r.w, r.h);\r\n    } else if (a.type === 'text') {\r\n      pt = toViewPoint(a.x, a.y);\r\n      c.fillStyle = a.color;\r\n      c.font = '600 ' + (a.size * S.scale) + 'px Inter, system-ui, sans-serif';\r\n      c.textBaseline = 'alphabetic';\r\n      c.fillText(a.text, pt[0], pt[1]);\r\n    } else if (a.type === 'note') {\r\n      pt = toViewPoint(a.x, a.y);\r\n      var sz = 20;\r\n      c.fillStyle = '#f59e0b';\r\n      c.fillRect(pt[0], pt[1] - sz, sz, sz);\r\n      c.strokeStyle = '#92400e'; c.lineWidth = 1.5;\r\n      c.strokeRect(pt[0], pt[1] - sz, sz, sz);\r\n      c.fillStyle = '#ffffff';\r\n      c.font = '700 13px Inter, system-ui, sans-serif';\r\n      c.fillText('N', pt[0] + 6, pt[1] - 5);\r\n    } else if (a.type === 'image') {\r\n      r = rectOnScreen(a);\r\n      if (a.img) { c.drawImage(a.img, r.x, r.y, r.w, r.h); }\r\n    }\r\n    c.restore();\r\n  }\r\n\r\n  \/* ---------- Annotation list ---------- *\/\r\n  function labelFor(a) {\r\n    if (a.type === 'text') { return 'Text: ' + a.text; }\r\n    if (a.type === 'note') { return 'Note: ' + a.text; }\r\n    if (a.type === 'image') { return a.label || 'Signature'; }\r\n    if (a.type === 'ink') { return 'Freehand drawing'; }\r\n    if (a.type === 'redact') { return 'Redaction area'; }\r\n    if (a.type === 'whiteout') { return 'White-out box'; }\r\n    if (a.type === 'highlight') { return 'Highlight'; }\r\n    if (a.type === 'underline') { return 'Underline'; }\r\n    if (a.type === 'strike') { return 'Strikeout'; }\r\n    return 'Box';\r\n  }\r\n\r\n  function renderAnnotList() {\r\n    var box = $('pth-alist');\r\n    if (!box) { return; }\r\n    var list = pageAnnots();\r\n    if (!list.length) {\r\n      box.innerHTML = '<div class=\"empty\">No annotations yet.<\/div>';\r\n      return;\r\n    }\r\n    var html = '', i, a;\r\n    for (i = 0; i < list.length; i++) {\r\n      a = list[i];\r\n      html += '<div class=\"aitem\">'\r\n        + '<span class=\"adot\" style=\"background:' + esc(a.color || '#0f172a') + '\"><\/span>'\r\n        + '<span class=\"atx\">' + esc(labelFor(a)) + '<\/span>'\r\n        + '<span class=\"apg\">p' + S.page + '<\/span>'\r\n        + '<button type=\"button\" data-del=\"' + a.id + '\" aria-label=\"Delete annotation\">&times;<\/button>'\r\n        + '<\/div>';\r\n    }\r\n    box.innerHTML = html;\r\n    var dels = box.querySelectorAll('button[data-del]');\r\n    for (i = 0; i < dels.length; i++) {\r\n      dels[i].addEventListener('click', function () {\r\n        var id = this.getAttribute('data-del'), k;\r\n        for (k = 0; k < S.annots.length; k++) {\r\n          if (S.annots[k].id === id) { S.annots.splice(k, 1); break; }\r\n        }\r\n        drawOverlay(); updateRedactOut();\r\n      });\r\n    }\r\n  }\r\n\r\n  function updateInfo() {\r\n    $('pth-pginfo').textContent = 'Page ' + S.page + ' \/ ' + S.order.length;\r\n    $('pth-toolinfo').textContent = 'Tool: ' + S.tool.charAt(0).toUpperCase() + S.tool.slice(1);\r\n  }\r\n\r\n  \/* ---------- Mouse interaction ---------- *\/\r\n  function relPos(e) {\r\n    var ov = $('pth-pdf-overlay');\r\n    var r = ov.getBoundingClientRect();\r\n    var cx = (e.touches && e.touches[0]) ? e.touches[0].clientX : e.clientX;\r\n    var cy = (e.touches && e.touches[0]) ? e.touches[0].clientY : e.clientY;\r\n    return [(cx - r.left) * (ov.width \/ r.width), (cy - r.top) * (ov.height \/ r.height)];\r\n  }\r\n\r\n  function onDown(e) {\r\n    if (!S.doc || S.tool === 'select') { return; }\r\n    e.preventDefault();\r\n    var p = relPos(e);\r\n    var pdfPt = toPdfPoint(p[0], p[1]);\r\n\r\n    if (S.tool === 'text') {\r\n      var val = $('pth-textval').value;\r\n      if (!val) { toast('Type the text first, then click the page.', 'err'); return; }\r\n      addAnnot({ type: 'text', x: pdfPt[0], y: pdfPt[1], text: val, color: $('pth-color').value, size: parseInt($('pth-size').value, 10) || 14 });\r\n      return;\r\n    }\r\n    if (S.tool === 'note') {\r\n      var nv = $('pth-textval').value || 'Note';\r\n      addAnnot({ type: 'note', x: pdfPt[0], y: pdfPt[1], text: nv, color: '#f59e0b', size: 12 });\r\n      return;\r\n    }\r\n    S.drawing = true;\r\n    S.start = pdfPt;\r\n    S.inkPts = [pdfPt];\r\n  }\r\n\r\n  function onMove(e) {\r\n    if (!S.drawing) { return; }\r\n    e.preventDefault();\r\n    var p = relPos(e);\r\n    var pdfPt = toPdfPoint(p[0], p[1]);\r\n    if (S.tool === 'ink') {\r\n      S.inkPts.push(pdfPt);\r\n      drawOverlay();\r\n      var c = $('pth-pdf-overlay').getContext('2d');\r\n      drawOne(c, { type: 'ink', pts: S.inkPts, color: $('pth-color').value, size: parseInt($('pth-size').value, 10) || 3 });\r\n    } else {\r\n      drawOverlay();\r\n      var cc = $('pth-pdf-overlay').getContext('2d');\r\n      drawOne(cc, previewRect(pdfPt));\r\n    }\r\n  }\r\n\r\n  function previewRect(pdfPt) {\r\n    return {\r\n      type: S.tool === 'redact' ? 'redact' : (S.tool === 'whiteout' ? 'whiteout' : (S.tool === 'highlight' ? 'highlight' : (S.tool === 'underline' ? 'underline' : (S.tool === 'strike' ? 'strike' : 'rect')))),\r\n      x: Math.min(S.start[0], pdfPt[0]), y: Math.min(S.start[1], pdfPt[1]),\r\n      w: Math.abs(pdfPt[0] - S.start[0]), h: Math.abs(pdfPt[1] - S.start[1]),\r\n      color: $('pth-color').value, size: parseInt($('pth-size').value, 10) || 3\r\n    };\r\n  }\r\n\r\n  function onUp(e) {\r\n    if (!S.drawing) { return; }\r\n    S.drawing = false;\r\n    var p = relPos(e.changedTouches ? { clientX: e.changedTouches[0].clientX, clientY: e.changedTouches[0].clientY } : e);\r\n    var pdfPt = toPdfPoint(p[0], p[1]);\r\n\r\n    if (S.tool === 'ink') {\r\n      if (S.inkPts.length > 1) {\r\n        addAnnot({ type: 'ink', pts: S.inkPts.slice(0), color: $('pth-color').value, size: parseInt($('pth-size').value, 10) || 3 });\r\n      } else { drawOverlay(); }\r\n      S.inkPts = [];\r\n      return;\r\n    }\r\n    var r = previewRect(pdfPt);\r\n    if (r.w < 2 || r.h < 2) { drawOverlay(); return; }\r\n    addAnnot(r);\r\n  }\r\n\r\n  function addAnnot(a) {\r\n    a.id = 'a' + (S.seq++);\r\n    a.pid = curEntry().id;\r\n    S.annots.push(a);\r\n    drawOverlay();\r\n    updateRedactOut();\r\n  }\r\n\r\n  \/* ---------- Loading ---------- *\/\r\n  function loadBytes(buf, name) {\r\n    S.bytes = buf.slice(0);\r\n    var task = window.pdfjsLib.getDocument({ data: buf.slice(0) });\r\n    task.promise.then(function (doc) {\r\n      S.doc = doc;\r\n      S.total = doc.numPages;\r\n      S.page = 1;\r\n      S.annots = [];\r\n      S.fitDone = false;\r\n      resetOrder();\r\n      $('pth-drop-wrap').style.display = 'none';\r\n      $('pth-viewer').style.display = 'block';\r\n      $('pth-save-btn').disabled = false;\r\n      fitWidth();\r\n      buildThumbs();\r\n      toast('Opened ' + (name || 'PDF') + ' - ' + S.total + ' pages', 'ok');\r\n    })['catch'](function () {\r\n      toast('That file could not be opened as a PDF.', 'err');\r\n    });\r\n  }\r\n\r\n  function handleFile(f) {\r\n    if (!f) { return; }\r\n    if (f.type !== 'application\/pdf' && !\/\\.pdf$\/i.test(f.name)) {\r\n      toast('Please choose a PDF file.', 'err'); return;\r\n    }\r\n    var fr = new FileReader();\r\n    fr.onload = function (ev) { loadBytes(ev.target.result, f.name); };\r\n    fr.onerror = function () { toast('Could not read that file.', 'err'); };\r\n    fr.readAsArrayBuffer(f);\r\n  }\r\n\r\n  function fitWidth() {\r\n    if (!S.doc) { return; }\r\n    var entry = curEntry();\r\n    if (!entry || entry.blank) { S.scale = 1.2; renderPage(); return; }\r\n    S.doc.getPage(entry.src).then(function (pg) {\r\n      var stage = $('pth-stage');\r\n      var avail = Math.max(260, stage.clientWidth - 44);\r\n      var base = pg.getViewport({ scale: 1 });\r\n      S.scale = Math.min(2.4, Math.max(0.4, avail \/ base.width));\r\n      renderPage();\r\n    });\r\n  }\r\n\r\n  \/* ---------- Thumbnails ---------- *\/\r\n  function buildThumbs() {\r\n    var box = $('pth-thumbs');\r\n    if (!S.doc) { box.innerHTML = '<div class=\"empty\">Open a PDF to see pages.<\/div>'; return; }\r\n    box.innerHTML = '';\r\n    var i;\r\n    for (i = 0; i < S.order.length; i++) {\r\n      (function (idx) {\r\n        var e = S.order[idx];\r\n        var d = document.createElement('div');\r\n        d.className = 'thumb' + (idx + 1 === S.page ? ' cur' : '');\r\n        d.setAttribute('draggable', 'true');\r\n        d.setAttribute('data-idx', String(idx));\r\n        var cv = document.createElement('canvas');\r\n        cv.width = 120; cv.height = 156;\r\n        d.appendChild(cv);\r\n        var nm = document.createElement('div');\r\n        nm.className = 'tn';\r\n        nm.textContent = 'Page ' + (idx + 1) + (e.blank ? ' (blank)' : '');\r\n        d.appendChild(nm);\r\n        var acts = document.createElement('div');\r\n        acts.className = 'tacts';\r\n        var rb = document.createElement('button');\r\n        rb.type = 'button'; rb.textContent = 'Rotate';\r\n        rb.setAttribute('aria-label', 'Rotate page ' + (idx + 1));\r\n        rb.addEventListener('click', function (ev) {\r\n          ev.stopPropagation();\r\n          S.pageRot[e.id] = ((S.pageRot[e.id] || 0) + 90) % 360;\r\n          if (idx + 1 === S.page) { renderPage(); }\r\n          buildThumbs();\r\n          toast('Page rotated - saved into the export.');\r\n        });\r\n        var db = document.createElement('button');\r\n        db.type = 'button'; db.textContent = 'Delete';\r\n        db.setAttribute('aria-label', 'Delete page ' + (idx + 1));\r\n        db.addEventListener('click', function (ev) {\r\n          ev.stopPropagation();\r\n          if (S.order.length <= 1) { toast('A document needs at least one page.', 'err'); return; }\r\n          S.order.splice(idx, 1);\r\n          if (S.page > S.order.length) { S.page = S.order.length; }\r\n          renderPage(); buildThumbs();\r\n          toast('Page removed.');\r\n        });\r\n        acts.appendChild(rb); acts.appendChild(db);\r\n        d.appendChild(acts);\r\n\r\n        d.addEventListener('click', function () { S.page = idx + 1; renderPage(); buildThumbs(); });\r\n        d.addEventListener('dragstart', function (ev) {\r\n          ev.dataTransfer.setData('text\/plain', String(idx));\r\n          d.classList.add('drag');\r\n        });\r\n        d.addEventListener('dragend', function () { d.classList.remove('drag'); });\r\n        d.addEventListener('dragover', function (ev) { ev.preventDefault(); });\r\n        d.addEventListener('drop', function (ev) {\r\n          ev.preventDefault();\r\n          var from = parseInt(ev.dataTransfer.getData('text\/plain'), 10);\r\n          if (isNaN(from) || from === idx) { return; }\r\n          var moved = S.order.splice(from, 1)[0];\r\n          S.order.splice(idx, 0, moved);\r\n          S.page = idx + 1;\r\n          renderPage(); buildThumbs();\r\n          toast('Page order updated.');\r\n        });\r\n\r\n        box.appendChild(d);\r\n        paintThumb(cv, e);\r\n      })(i);\r\n    }\r\n  }\r\n\r\n  function paintThumb(cv, e) {\r\n    var cx = cv.getContext('2d');\r\n    cx.fillStyle = '#ffffff';\r\n    cx.fillRect(0, 0, cv.width, cv.height);\r\n    if (e.blank) { return; }\r\n    S.doc.getPage(e.src).then(function (pg) {\r\n      var rot = (pg.rotate + (S.pageRot[e.id] || 0)) % 360;\r\n      var base = pg.getViewport({ scale: 1, rotation: rot });\r\n      var sc = Math.min(cv.width \/ base.width, cv.height \/ base.height);\r\n      var vp = pg.getViewport({ scale: sc, rotation: rot });\r\n      cv.height = Math.max(40, Math.floor(vp.height));\r\n      cx.fillStyle = '#ffffff';\r\n      cx.fillRect(0, 0, cv.width, cv.height);\r\n      pg.render({ canvasContext: cx, viewport: vp });\r\n    })['catch'](function () { });\r\n  }\r\n\r\n  \/* ---------- Extraction ---------- *\/\r\n  function extractText(all) {\r\n    if (!S.doc) { toast('Open a PDF first.', 'err'); return; }\r\n    var out = $('pth-x-out');\r\n    out.textContent = 'Reading text...';\r\n    var pages = [], i;\r\n    if (all) {\r\n      for (i = 0; i < S.order.length; i++) { if (!S.order[i].blank) { pages.push({ n: i + 1, src: S.order[i].src }); } }\r\n    } else {\r\n      var e = curEntry();\r\n      if (e.blank) { out.textContent = 'This is a blank page.'; return; }\r\n      pages.push({ n: S.page, src: e.src });\r\n    }\r\n    var texts = [];\r\n    var step = function (k) {\r\n      if (k >= pages.length) {\r\n        S.extracted = texts.join('\\n\\n');\r\n        out.textContent = S.extracted || 'No selectable text found. This PDF may be a scan of a paper document.';\r\n        $('pth-x-dl').disabled = !S.extracted;\r\n        toast('Text extracted from ' + pages.length + ' page(s).', 'ok');\r\n        return;\r\n      }\r\n      S.doc.getPage(pages[k].src).then(function (pg) {\r\n        return pg.getTextContent();\r\n      }).then(function (tc) {\r\n        var line = '', last = null, j, it;\r\n        for (j = 0; j < tc.items.length; j++) {\r\n          it = tc.items[j];\r\n          if (last !== null && Math.abs(it.transform[5] - last) > 3) { line += '\\n'; }\r\n          line += it.str;\r\n          if (it.hasEOL) { line += '\\n'; }\r\n          last = it.transform[5];\r\n        }\r\n        texts.push('--- Page ' + pages[k].n + ' ---\\n' + line.replace(\/\\n{3,}\/g, '\\n\\n').trim());\r\n        step(k + 1);\r\n      })['catch'](function () { texts.push('--- Page ' + pages[k].n + ' --- (could not read)'); step(k + 1); });\r\n    };\r\n    step(0);\r\n  }\r\n\r\n  function extractImages() {\r\n    if (!S.doc) { toast('Open a PDF first.', 'err'); return; }\r\n    var e = curEntry();\r\n    var box = $('pth-img-out');\r\n    if (e.blank) { box.textContent = 'This is a blank page.'; return; }\r\n    box.textContent = 'Scanning page for images...';\r\n    S.doc.getPage(e.src).then(function (pg) {\r\n      return pg.getOperatorList().then(function (ops) {\r\n        var names = [], i;\r\n        for (i = 0; i < ops.fnArray.length; i++) {\r\n          if (ops.fnArray[i] === window.pdfjsLib.OPS.paintImageXObject ||\r\n              ops.fnArray[i] === window.pdfjsLib.OPS.paintJpegXObject) {\r\n            names.push(ops.argsArray[i][0]);\r\n          }\r\n        }\r\n        if (!names.length) { box.textContent = 'No embedded images found on this page.'; return; }\r\n        box.innerHTML = '';\r\n        var found = 0, k;\r\n        for (k = 0; k < names.length; k++) {\r\n          (function (nm, idx) {\r\n            try {\r\n              pg.objs.get(nm, function (im) {\r\n                if (!im) { return; }\r\n                var cv = document.createElement('canvas');\r\n                cv.width = im.width; cv.height = im.height;\r\n                var cc = cv.getContext('2d');\r\n                var id = cc.createImageData(im.width, im.height);\r\n                var src = im.data, dst = id.data, p, o = 0;\r\n                if (src.length === im.width * im.height * 3) {\r\n                  for (p = 0; p < src.length; p += 3) {\r\n                    dst[o++] = src[p]; dst[o++] = src[p + 1]; dst[o++] = src[p + 2]; dst[o++] = 255;\r\n                  }\r\n                } else {\r\n                  for (p = 0; p < dst.length; p++) { dst[p] = src[p]; }\r\n                }\r\n                cc.putImageData(id, 0, 0);\r\n                found++;\r\n                var a = document.createElement('a');\r\n                a.href = cv.toDataURL('image\/png');\r\n                a.download = 'pdf-image-' + (idx + 1) + '.png';\r\n                a.textContent = 'Download image ' + (idx + 1) + ' (' + im.width + ' x ' + im.height + ')';\r\n                a.style.display = 'block';\r\n                a.style.color = '#1d4ed8';\r\n                a.style.fontWeight = '700';\r\n                a.style.padding = '4px 0';\r\n                box.appendChild(a);\r\n              });\r\n            } catch (err) { }\r\n          })(names[k], k);\r\n        }\r\n        setTimeout(function () {\r\n          if (!box.children.length) {\r\n            box.textContent = 'Images were detected but could not be decoded in the browser. Saving the page as PNG is a reliable alternative.';\r\n          }\r\n        }, 1400);\r\n      });\r\n    })['catch'](function () { box.textContent = 'Could not scan this page for images.'; });\r\n  }\r\n\r\n  function savePagePng() {\r\n    if (!S.doc) { toast('Open a PDF first.', 'err'); return; }\r\n    var merged = document.createElement('canvas');\r\n    var base = $('pth-pdf-canvas'), ov = $('pth-pdf-overlay');\r\n    merged.width = base.width; merged.height = base.height;\r\n    var c = merged.getContext('2d');\r\n    c.drawImage(base, 0, 0);\r\n    c.drawImage(ov, 0, 0);\r\n    merged.toBlob(function (b) {\r\n      dl(b, 'page-' + S.page + '.png');\r\n      toast('Page saved as PNG.', 'ok');\r\n    });\r\n  }\r\n\r\n  function dl(blob, name) {\r\n    var u = URL.createObjectURL(blob);\r\n    var a = document.createElement('a');\r\n    a.href = u; a.download = name;\r\n    document.body.appendChild(a); a.click(); document.body.removeChild(a);\r\n    setTimeout(function () { URL.revokeObjectURL(u); }, 1200);\r\n  }\r\n\r\n  \/* ---------- Signature pad ---------- *\/\r\n  var sigDraw = false, sigCtx = null, sigDirty = false;\r\n  function initSigPad() {\r\n    var cv = $('pth-sigpad-canvas');\r\n    sigCtx = cv.getContext('2d');\r\n    sigCtx.fillStyle = '#ffffff';\r\n    sigCtx.fillRect(0, 0, cv.width, cv.height);\r\n    sigCtx.strokeStyle = '#0f172a';\r\n    sigCtx.lineWidth = 3; sigCtx.lineCap = 'round'; sigCtx.lineJoin = 'round';\r\n\r\n    function pos(e) {\r\n      var r = cv.getBoundingClientRect();\r\n      var cx = (e.touches && e.touches[0]) ? e.touches[0].clientX : e.clientX;\r\n      var cy = (e.touches && e.touches[0]) ? e.touches[0].clientY : e.clientY;\r\n      return [(cx - r.left) * (cv.width \/ r.width), (cy - r.top) * (cv.height \/ r.height)];\r\n    }\r\n    function down(e) { e.preventDefault(); sigDraw = true; sigDirty = true; var p = pos(e); sigCtx.beginPath(); sigCtx.moveTo(p[0], p[1]); }\r\n    function move(e) { if (!sigDraw) { return; } e.preventDefault(); var p = pos(e); sigCtx.lineTo(p[0], p[1]); sigCtx.stroke(); }\r\n    function up() { sigDraw = false; }\r\n\r\n    cv.addEventListener('mousedown', down);\r\n    cv.addEventListener('mousemove', move);\r\n    document.addEventListener('mouseup', up);\r\n    cv.addEventListener('touchstart', down);\r\n    cv.addEventListener('touchmove', move);\r\n    cv.addEventListener('touchend', up);\r\n  }\r\n\r\n  function placeImageAnnot(dataUrl, label, wPt, hPt) {\r\n    if (!S.doc) { toast('Open a PDF first.', 'err'); return; }\r\n    var img = new Image();\r\n    img.onload = function () {\r\n      var e = curEntry();\r\n      var pw = S.viewport && S.viewport.blank ? S.viewport.pw : 0;\r\n      var px = 60, py = 90;\r\n      addAnnot({ type: 'image', x: px, y: py, w: wPt, h: hPt, dataUrl: dataUrl, img: img, label: label, color: '#0f172a' });\r\n      toast(label + ' placed - drag is not needed, use Clear to remove.', 'ok');\r\n    };\r\n    img.src = dataUrl;\r\n  }\r\n\r\n  \/* ---------- Forms ---------- *\/\r\n  function scanForm() {\r\n    if (!S.bytes) { toast('Open a PDF first.', 'err'); return; }\r\n    var box = $('pth-form-list');\r\n    box.innerHTML = '<div class=\"empty\">Looking for form fields...<\/div>';\r\n    window.PDFLib.PDFDocument.load(S.bytes.slice(0)).then(function (doc) {\r\n      var form;\r\n      try { form = doc.getForm(); } catch (e2) { box.innerHTML = '<div class=\"empty\">This PDF has no fillable form.<\/div>'; return; }\r\n      var fields = form.getFields();\r\n      if (!fields.length) { box.innerHTML = '<div class=\"empty\">This PDF has no fillable form fields.<\/div>'; return; }\r\n      S.fields = [];\r\n      box.innerHTML = '';\r\n      var i, f, nm, tp;\r\n      for (i = 0; i < fields.length; i++) {\r\n        f = fields[i];\r\n        nm = f.getName();\r\n        tp = f.constructor && f.constructor.name ? f.constructor.name : 'Field';\r\n        S.fields.push({ name: nm, type: tp });\r\n        var row = document.createElement('div');\r\n        row.className = 'fieldrow';\r\n        var lab = document.createElement('div');\r\n        lab.className = 'fn';\r\n        lab.textContent = nm;\r\n        row.appendChild(lab);\r\n        if (tp.indexOf('CheckBox') >= 0) {\r\n          var cb = document.createElement('input');\r\n          cb.type = 'checkbox';\r\n          cb.setAttribute('data-field', nm);\r\n          cb.setAttribute('aria-label', nm);\r\n          cb.style.width = 'auto';\r\n          row.appendChild(cb);\r\n        } else {\r\n          var tx = document.createElement('input');\r\n          tx.type = 'text';\r\n          tx.setAttribute('data-field', nm);\r\n          tx.setAttribute('aria-label', nm);\r\n          tx.placeholder = 'Value';\r\n          row.appendChild(tx);\r\n        }\r\n        box.appendChild(row);\r\n      }\r\n      toast(fields.length + ' form field(s) found.', 'ok');\r\n    })['catch'](function () { box.innerHTML = '<div class=\"empty\">Could not read a form from this PDF.<\/div>'; });\r\n  }\r\n\r\n  \/* ---------- Document info ---------- *\/\r\n  function readInfo() {\r\n    if (!S.bytes) { toast('Open a PDF first.', 'err'); return; }\r\n    var box = $('pth-info-out');\r\n    box.innerHTML = '<div class=\"empty\">Reading...<\/div>';\r\n    window.PDFLib.PDFDocument.load(S.bytes.slice(0)).then(function (doc) {\r\n      function safe(fn) { try { var v = fn(); return v ? String(v) : 'Not set'; } catch (e) { return 'Not set'; } }\r\n      var rows = [\r\n        ['Title', safe(function () { return doc.getTitle(); })],\r\n        ['Author', safe(function () { return doc.getAuthor(); })],\r\n        ['Subject', safe(function () { return doc.getSubject(); })],\r\n        ['Creator', safe(function () { return doc.getCreator(); })],\r\n        ['Producer', safe(function () { return doc.getProducer(); })],\r\n        ['Created', safe(function () { var d = doc.getCreationDate(); return d ? d.toDateString() : ''; })],\r\n        ['Modified', safe(function () { var d = doc.getModificationDate(); return d ? d.toDateString() : ''; })],\r\n        ['Pages in file', String(doc.getPageCount())],\r\n        ['Pages after edits', String(S.order.length)],\r\n        ['File size', (S.bytes.byteLength \/ 1024).toFixed(1) + ' KB']\r\n      ];\r\n      var html = '', i;\r\n      for (i = 0; i < rows.length; i++) {\r\n        html += '<div class=\"kv\"><span>' + esc(rows[i][0]) + '<\/span><span>' + esc(rows[i][1]) + '<\/span><\/div>';\r\n      }\r\n      box.innerHTML = html;\r\n    })['catch'](function () {\r\n      box.innerHTML = '<div class=\"empty\">This PDF is encrypted or its information could not be read.<\/div>';\r\n    });\r\n  }\r\n\r\n  function updateRedactOut() {\r\n    var box = $('pth-redact-out');\r\n    if (!box) { return; }\r\n    var n = 0, pages = {}, i;\r\n    for (i = 0; i < S.annots.length; i++) {\r\n      if (S.annots[i].type === 'redact') { n++; pages[S.annots[i].pid] = 1; }\r\n    }\r\n    if (!n) { box.textContent = 'No redaction areas marked.'; return; }\r\n    var pc = 0, k;\r\n    for (k in pages) { if (pages.hasOwnProperty(k)) { pc++; } }\r\n    box.textContent = n + ' redaction area(s) across ' + pc + ' page(s). These pages will be flattened to images when you save, so the covered text is removed for good.';\r\n  }\r\n\r\n  \/* ---------- Export ---------- *\/\r\n  function pageHasRedaction(pid) {\r\n    var i;\r\n    for (i = 0; i < S.annots.length; i++) {\r\n      if (S.annots[i].pid === pid && S.annots[i].type === 'redact') { return true; }\r\n    }\r\n    return false;\r\n  }\r\n\r\n  function rasterizePage(entry, srcDoc, outDoc) {\r\n    return srcDoc.getPage(entry.src).then(function (pg) {\r\n      var rot = (pg.rotate + (S.pageRot[entry.id] || 0)) % 360;\r\n      var vp = pg.getViewport({ scale: 2, rotation: rot });\r\n      var cv = document.createElement('canvas');\r\n      cv.width = Math.floor(vp.width); cv.height = Math.floor(vp.height);\r\n      var cx = cv.getContext('2d');\r\n      cx.fillStyle = '#ffffff'; cx.fillRect(0, 0, cv.width, cv.height);\r\n      return pg.render({ canvasContext: cx, viewport: vp }).promise.then(function () {\r\n        var i, a, r, sx = cv.width \/ vp.width, sy = cv.height \/ vp.height;\r\n        for (i = 0; i < S.annots.length; i++) {\r\n          a = S.annots[i];\r\n          if (a.pid !== entry.id) { continue; }\r\n          if (a.type === 'redact') {\r\n            var p1 = vp.convertToViewportPoint(a.x, a.y);\r\n            var p2 = vp.convertToViewportPoint(a.x + a.w, a.y + a.h);\r\n            cx.fillStyle = '#000000';\r\n            cx.fillRect(Math.min(p1[0], p2[0]) * sx, Math.min(p1[1], p2[1]) * sy,\r\n              Math.abs(p2[0] - p1[0]) * sx, Math.abs(p2[1] - p1[1]) * sy);\r\n          }\r\n        }\r\n        return new Promise(function (res) {\r\n          cv.toBlob(function (b) {\r\n            var fr = new FileReader();\r\n            fr.onload = function (ev) { res({ data: ev.target.result, w: vp.width, h: vp.height }); };\r\n            fr.readAsArrayBuffer(b);\r\n          }, 'image\/png');\r\n        });\r\n      });\r\n    });\r\n  }\r\n\r\n  function savePdf() {\r\n    if (!S.bytes) { toast('Open a PDF first.', 'err'); return; }\r\n    var btn = $('pth-save-btn');\r\n    btn.disabled = true;\r\n    btn.textContent = 'Building...';\r\n    var PDFLib = window.PDFLib;\r\n\r\n    PDFLib.PDFDocument.load(S.bytes.slice(0)).then(function (src) {\r\n      if ($('pth-form-flat').checked || S.fields.length) {\r\n        try {\r\n          var form = src.getForm();\r\n          var inputs = qa('#pth-form-list input[data-field]');\r\n          var i;\r\n          for (i = 0; i < inputs.length; i++) {\r\n            var nm = inputs[i].getAttribute('data-field');\r\n            try {\r\n              if (inputs[i].type === 'checkbox') {\r\n                var cbf = form.getCheckBox(nm);\r\n                if (inputs[i].checked) { cbf.check(); } else { cbf.uncheck(); }\r\n              } else if (inputs[i].value) {\r\n                form.getTextField(nm).setText(inputs[i].value);\r\n              }\r\n            } catch (e1) { }\r\n          }\r\n          if ($('pth-form-flat').checked) { form.flatten(); }\r\n        } catch (e2) { }\r\n      }\r\n      return PDFLib.PDFDocument.create().then(function (out) {\r\n        return out.embedFont(PDFLib.StandardFonts.Helvetica).then(function (font) {\r\n          var idx = [], i;\r\n          for (i = 0; i < S.order.length; i++) {\r\n            if (!S.order[i].blank) { idx.push(S.order[i].src - 1); }\r\n          }\r\n          return src.copyPages(src, idx).then(function (copied) {\r\n            var map = {}, c = 0;\r\n            for (i = 0; i < S.order.length; i++) {\r\n              if (!S.order[i].blank) { map[i] = copied[c++]; }\r\n            }\r\n            var step = function (k) {\r\n              if (k >= S.order.length) { return Promise.resolve(); }\r\n              var e = S.order[k];\r\n              if (e.blank) {\r\n                out.addPage([612, 792]);\r\n                return step(k + 1);\r\n              }\r\n              if (pageHasRedaction(e.id)) {\r\n                return rasterizePage(e, S.doc, out).then(function (img) {\r\n                  return out.embedPng(img.data).then(function (png) {\r\n                    var pg = out.addPage([img.w, img.h]);\r\n                    pg.drawImage(png, { x: 0, y: 0, width: img.w, height: img.h });\r\n                    return drawAnnots(pg, e, font, out, true).then(function () { return step(k + 1); });\r\n                  });\r\n                });\r\n              }\r\n              var pg2 = out.addPage(map[k]);\r\n              if (S.pageRot[e.id]) {\r\n                try { pg2.setRotation(PDFLib.degrees((pg2.getRotation().angle + S.pageRot[e.id]) % 360)); } catch (e3) { }\r\n              }\r\n              return drawAnnots(pg2, e, font, out, false).then(function () { return step(k + 1); });\r\n            };\r\n            return step(0).then(function () {\r\n              if ($('pth-scrub').checked) {\r\n                try {\r\n                  out.setTitle(''); out.setAuthor(''); out.setSubject('');\r\n                  out.setKeywords([]); out.setProducer(''); out.setCreator('');\r\n                } catch (e4) { }\r\n              }\r\n              return out.save();\r\n            });\r\n          });\r\n        });\r\n      });\r\n    }).then(function (bytes) {\r\n      dl(new Blob([bytes], { type: 'application\/pdf' }), 'pdf-studio-export.pdf');\r\n      btn.disabled = false;\r\n      btn.textContent = 'Save PDF';\r\n      toast('PDF saved with your edits baked in.', 'ok');\r\n    })['catch'](function (err) {\r\n      btn.disabled = false;\r\n      btn.textContent = 'Save PDF';\r\n      toast('Export failed. The PDF may be encrypted or password protected.', 'err');\r\n    });\r\n  }\r\n\r\n  function drawAnnots(pg, entry, font, out, rasterized) {\r\n    var PDFLib = window.PDFLib;\r\n    var list = [], i;\r\n    for (i = 0; i < S.annots.length; i++) {\r\n      if (S.annots[i].pid === entry.id) { list.push(S.annots[i]); }\r\n    }\r\n    if (!list.length) { return Promise.resolve(); }\r\n    var h = pg.getHeight();\r\n    var chain = Promise.resolve();\r\n\r\n    for (i = 0; i < list.length; i++) {\r\n      (function (a) {\r\n        chain = chain.then(function () {\r\n          var col, j;\r\n          if (a.type === 'redact') { return; }\r\n          if (rasterized && (a.type === 'whiteout' || a.type === 'highlight')) { return; }\r\n          col = hexToRgb(a.color || '#000000');\r\n          var rgb = PDFLib.rgb(col.r, col.g, col.b);\r\n\r\n          if (a.type === 'ink') {\r\n            for (j = 1; j < a.pts.length; j++) {\r\n              pg.drawLine({\r\n                start: { x: a.pts[j - 1][0], y: a.pts[j - 1][1] },\r\n                end: { x: a.pts[j][0], y: a.pts[j][1] },\r\n                thickness: a.size, color: rgb, opacity: 1\r\n              });\r\n            }\r\n          } else if (a.type === 'highlight') {\r\n            pg.drawRectangle({ x: a.x, y: a.y, width: a.w, height: a.h, color: rgb, opacity: 0.35 });\r\n          } else if (a.type === 'underline') {\r\n            pg.drawLine({ start: { x: a.x, y: a.y }, end: { x: a.x + a.w, y: a.y }, thickness: Math.max(1, a.size * 0.5), color: rgb });\r\n          } else if (a.type === 'strike') {\r\n            pg.drawLine({ start: { x: a.x, y: a.y + a.h \/ 2 }, end: { x: a.x + a.w, y: a.y + a.h \/ 2 }, thickness: Math.max(1, a.size * 0.5), color: rgb });\r\n          } else if (a.type === 'rect') {\r\n            pg.drawRectangle({ x: a.x, y: a.y, width: a.w, height: a.h, borderColor: rgb, borderWidth: a.size, opacity: 0 });\r\n          } else if (a.type === 'whiteout') {\r\n            pg.drawRectangle({ x: a.x, y: a.y, width: a.w, height: a.h, color: PDFLib.rgb(1, 1, 1) });\r\n          } else if (a.type === 'text') {\r\n            pg.drawText(String(a.text), { x: a.x, y: a.y, size: a.size, font: font, color: rgb });\r\n          } else if (a.type === 'note') {\r\n            pg.drawRectangle({ x: a.x, y: a.y, width: 18, height: 18, color: PDFLib.rgb(0.96, 0.62, 0.04) });\r\n            pg.drawText(String(a.text), { x: a.x + 22, y: a.y + 4, size: 9, font: font, color: PDFLib.rgb(0.28, 0.16, 0.02) });\r\n          } else if (a.type === 'image' && a.dataUrl) {\r\n            return out.embedPng(a.dataUrl).then(function (png) {\r\n              pg.drawImage(png, { x: a.x, y: a.y, width: a.w, height: a.h });\r\n            })['catch'](function () { });\r\n          }\r\n        });\r\n      })(list[i]);\r\n    }\r\n    return chain;\r\n  }\r\n\r\n  \/* ---------- Sample ---------- *\/\r\n  function loadSample() {\r\n    if (!window.PDFLib) { toast('PDF engine still loading, try again in a second.', 'err'); return; }\r\n    var PDFLib = window.PDFLib;\r\n    PDFLib.PDFDocument.create().then(function (doc) {\r\n      return doc.embedFont(PDFLib.StandardFonts.HelveticaBold).then(function (bold) {\r\n        return doc.embedFont(PDFLib.StandardFonts.Helvetica).then(function (reg) {\r\n          var lines = [\r\n            'This sample document is generated inside your browser so you can',\r\n            'try every tool without opening a private file.',\r\n            '',\r\n            'Things worth trying:',\r\n            '  1. Highlight a line, then underline another one.',\r\n            '  2. Open the Pages tab and drag page 2 in front of page 1.',\r\n            '  3. Use Extract to pull this text out as a .txt file.',\r\n            '  4. Draw a signature in the Sign tab and place it.',\r\n            '  5. Mark a redaction box over the account number below,',\r\n            '     save the PDF, then try to select that text again.',\r\n            '',\r\n            'Account reference: 4417-9920-3388',\r\n            'Contact: sample.person@example.com'\r\n          ];\r\n          var p1 = doc.addPage([595, 842]);\r\n          p1.drawText('PDF Studio Pro - Sample Document', { x: 56, y: 770, size: 20, font: bold, color: PDFLib.rgb(0.15, 0.23, 0.42) });\r\n          p1.drawLine({ start: { x: 56, y: 758 }, end: { x: 539, y: 758 }, thickness: 1.5, color: PDFLib.rgb(0.8, 0.84, 0.9) });\r\n          var y = 726, i;\r\n          for (i = 0; i < lines.length; i++) {\r\n            p1.drawText(lines[i], { x: 56, y: y, size: 11.5, font: reg, color: PDFLib.rgb(0.12, 0.16, 0.24) });\r\n            y -= 19;\r\n          }\r\n          var p2 = doc.addPage([595, 842]);\r\n          p2.drawText('Page Two', { x: 56, y: 770, size: 18, font: bold, color: PDFLib.rgb(0.15, 0.23, 0.42) });\r\n          p2.drawText('Use the Pages tab to move this page ahead of page one,', { x: 56, y: 730, size: 11.5, font: reg, color: PDFLib.rgb(0.12, 0.16, 0.24) });\r\n          p2.drawText('rotate it, or delete it. The change is kept when you save.', { x: 56, y: 711, size: 11.5, font: reg, color: PDFLib.rgb(0.12, 0.16, 0.24) });\r\n          return doc.save();\r\n        });\r\n      });\r\n    }).then(function (bytes) {\r\n      var ab = new ArrayBuffer(bytes.length);\r\n      var view = new Uint8Array(ab);\r\n      view.set(bytes);\r\n      loadBytes(ab, 'sample.pdf');\r\n    })['catch'](function () { toast('Could not build the sample document.', 'err'); });\r\n  }\r\n\r\n  \/* ---------- Wiring ---------- *\/\r\n  function setTool(t) {\r\n    S.tool = t;\r\n    var btns = qa('.tool-btn[data-tool]'), i;\r\n    for (i = 0; i < btns.length; i++) {\r\n      if (btns[i].getAttribute('data-tool') === t) { btns[i].classList.add('on'); }\r\n      else { btns[i].classList.remove('on'); }\r\n    }\r\n    $('pth-textfld').style.display = (t === 'text' || t === 'note') ? 'block' : 'none';\r\n    $('pth-pdf-overlay').style.cursor = (t === 'select') ? 'default' : 'crosshair';\r\n    updateInfo();\r\n  }\r\n\r\n  function bind() {\r\n    var i;\r\n\r\n    $('pth-open-btn').addEventListener('click', function () { $('pth-file-input').click(); });\r\n    $('pth-drop').addEventListener('click', function () { $('pth-file-input').click(); });\r\n    $('pth-drop').addEventListener('keydown', function (e) {\r\n      if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); $('pth-file-input').click(); }\r\n    });\r\n    $('pth-file-input').addEventListener('change', function () { handleFile(this.files[0]); this.value = ''; });\r\n\r\n    var dz = $('pth-drop');\r\n    dz.addEventListener('dragover', function (e) { e.preventDefault(); dz.classList.add('over'); });\r\n    dz.addEventListener('dragleave', function () { dz.classList.remove('over'); });\r\n    dz.addEventListener('drop', function (e) {\r\n      e.preventDefault(); dz.classList.remove('over');\r\n      if (e.dataTransfer.files && e.dataTransfer.files[0]) { handleFile(e.dataTransfer.files[0]); }\r\n    });\r\n\r\n    $('pth-sample-btn').addEventListener('click', loadSample);\r\n    $('pth-fw-btn').addEventListener('click', function () {\r\n      $('pth-layout').classList.toggle('fw');\r\n      setTimeout(function () { if (S.doc) { fitWidth(); } }, 220);\r\n    });\r\n    $('pth-clear-btn').addEventListener('click', function () {\r\n      S.doc = null; S.bytes = null; S.annots = []; S.order = []; S.total = 0; S.page = 1; S.fields = [];\r\n      $('pth-drop-wrap').style.display = 'block';\r\n      $('pth-viewer').style.display = 'none';\r\n      $('pth-save-btn').disabled = true;\r\n      $('pth-thumbs').innerHTML = '<div class=\"empty\">Open a PDF to see pages.<\/div>';\r\n      $('pth-x-out').textContent = 'Extracted text will appear here.';\r\n      $('pth-img-out').textContent = 'Image results will appear here.';\r\n      $('pth-info-out').innerHTML = '<div class=\"empty\">Open a PDF and click Read Document Info.<\/div>';\r\n      $('pth-form-list').innerHTML = '<div class=\"empty\">Click Detect Fields to look for a fillable form.<\/div>';\r\n      updateRedactOut();\r\n      toast('Workspace cleared.');\r\n    });\r\n    $('pth-save-btn').addEventListener('click', savePdf);\r\n\r\n    $('pth-prev').addEventListener('click', function () { if (S.page > 1) { S.page--; renderPage(); buildThumbs(); } });\r\n    $('pth-next').addEventListener('click', function () { if (S.page < S.order.length) { S.page++; renderPage(); buildThumbs(); } });\r\n    $('pth-zin').addEventListener('click', function () { S.scale = Math.min(4, S.scale + 0.2); renderPage(); });\r\n    $('pth-zout').addEventListener('click', function () { S.scale = Math.max(0.3, S.scale - 0.2); renderPage(); });\r\n    $('pth-fit').addEventListener('click', fitWidth);\r\n    $('pth-rot').addEventListener('click', function () { S.viewRot = (S.viewRot + 90) % 360; renderPage(); });\r\n    $('pth-undo').addEventListener('click', function () {\r\n      var list = pageAnnots();\r\n      if (!list.length) { toast('Nothing to undo on this page.'); return; }\r\n      var last = list[list.length - 1], k;\r\n      for (k = S.annots.length - 1; k >= 0; k--) {\r\n        if (S.annots[k].id === last.id) { S.annots.splice(k, 1); break; }\r\n      }\r\n      drawOverlay(); updateRedactOut();\r\n    });\r\n\r\n    var tools = qa('.tool-btn[data-tool]');\r\n    for (i = 0; i < tools.length; i++) {\r\n      tools[i].addEventListener('click', function () { setTool(this.getAttribute('data-tool')); });\r\n    }\r\n\r\n    var tabs = qa('.tab');\r\n    for (i = 0; i < tabs.length; i++) {\r\n      tabs[i].addEventListener('click', function () {\r\n        var name = this.getAttribute('data-tab'), k;\r\n        var all = qa('.tab'), pans = qa('.panel');\r\n        for (k = 0; k < all.length; k++) { all[k].classList.remove('on'); }\r\n        for (k = 0; k < pans.length; k++) { pans[k].classList.remove('on'); }\r\n        this.classList.add('on');\r\n        var p = q('.panel[data-panel=\"' + name + '\"]');\r\n        if (p) { p.classList.add('on'); }\r\n      });\r\n    }\r\n\r\n    var ov = $('pth-pdf-overlay');\r\n    ov.addEventListener('mousedown', onDown);\r\n    ov.addEventListener('mousemove', onMove);\r\n    document.addEventListener('mouseup', function (e) { if (S.drawing) { onUp(e); } });\r\n    ov.addEventListener('touchstart', onDown);\r\n    ov.addEventListener('touchmove', onMove);\r\n    ov.addEventListener('touchend', onUp);\r\n\r\n    $('pth-clear-page').addEventListener('click', function () {\r\n      var e = curEntry();\r\n      if (!e) { return; }\r\n      var kept = [], k;\r\n      for (k = 0; k < S.annots.length; k++) { if (S.annots[k].pid !== e.id) { kept.push(S.annots[k]); } }\r\n      S.annots = kept;\r\n      drawOverlay(); updateRedactOut();\r\n      toast('Annotations cleared on this page.');\r\n    });\r\n    $('pth-clear-all').addEventListener('click', function () {\r\n      S.annots = [];\r\n      drawOverlay(); updateRedactOut();\r\n      toast('All annotations cleared.');\r\n    });\r\n\r\n    $('pth-add-blank').addEventListener('click', function () {\r\n      if (!S.doc) { toast('Open a PDF first.', 'err'); return; }\r\n      S.order.splice(S.page, 0, { blank: true, id: 'b' + (S.seq++) });\r\n      buildThumbs();\r\n      toast('Blank page added after page ' + S.page + '.');\r\n    });\r\n    $('pth-reset-pages').addEventListener('click', function () {\r\n      if (!S.doc) { return; }\r\n      resetOrder(); S.page = 1; renderPage(); buildThumbs();\r\n      toast('Page order reset.');\r\n    });\r\n\r\n    $('pth-x-page').addEventListener('click', function () { extractText(false); });\r\n    $('pth-x-all').addEventListener('click', function () { extractText(true); });\r\n    $('pth-x-dl').addEventListener('click', function () {\r\n      if (!S.extracted) { return; }\r\n      dl(new Blob([S.extracted], { type: 'text\/plain' }), 'extracted-text.txt');\r\n      toast('Text file downloaded.', 'ok');\r\n    });\r\n    $('pth-x-search').addEventListener('input', function () {\r\n      var term = this.value.toLowerCase();\r\n      if (!S.extracted) { return; }\r\n      if (!term) { $('pth-x-out').textContent = S.extracted; return; }\r\n      var lines = S.extracted.split('\\n'), keep = [], k;\r\n      for (k = 0; k < lines.length; k++) {\r\n        if (lines[k].toLowerCase().indexOf(term) >= 0) { keep.push(lines[k]); }\r\n      }\r\n      $('pth-x-out').textContent = keep.length ? keep.join('\\n') : 'No lines match that search.';\r\n    });\r\n    $('pth-x-img').addEventListener('click', extractImages);\r\n    $('pth-x-png').addEventListener('click', savePagePng);\r\n\r\n    $('pth-sig-clear').addEventListener('click', function () {\r\n      var cv = $('pth-sigpad-canvas');\r\n      sigCtx.fillStyle = '#ffffff';\r\n      sigCtx.fillRect(0, 0, cv.width, cv.height);\r\n      sigDirty = false;\r\n      toast('Signature pad cleared.');\r\n    });\r\n    $('pth-sig-use').addEventListener('click', function () {\r\n      if (!sigDirty) { toast('Draw a signature first.', 'err'); return; }\r\n      placeImageAnnot($('pth-sigpad-canvas').toDataURL('image\/png'), 'Signature', 170, 61);\r\n    });\r\n    $('pth-sig-typed').addEventListener('click', function () {\r\n      var v = $('pth-sig-type').value;\r\n      if (!v) { toast('Type a name first.', 'err'); return; }\r\n      var cv = document.createElement('canvas');\r\n      cv.width = 560; cv.height = 200;\r\n      var c = cv.getContext('2d');\r\n      c.fillStyle = '#ffffff'; c.fillRect(0, 0, cv.width, cv.height);\r\n      c.fillStyle = '#0f172a';\r\n      c.font = 'italic 700 68px Georgia, serif';\r\n      c.textBaseline = 'middle';\r\n      c.fillText(v, 20, 105);\r\n      placeImageAnnot(cv.toDataURL('image\/png'), 'Typed signature', 170, 61);\r\n    });\r\n    $('pth-stamp-date').addEventListener('click', function () {\r\n      if (!S.doc) { toast('Open a PDF first.', 'err'); return; }\r\n      var d = new Date();\r\n      var txt = d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2);\r\n      addAnnot({ type: 'text', x: 60, y: 60, text: txt, color: '#0f172a', size: 12 });\r\n      toast('Date stamp added near the bottom-left of this page.');\r\n    });\r\n    $('pth-stamp-check').addEventListener('click', function () {\r\n      if (!S.doc) { toast('Open a PDF first.', 'err'); return; }\r\n      addAnnot({ type: 'text', x: 60, y: 90, text: 'X', color: '#047857', size: 18 });\r\n      toast('Check mark added near the bottom-left of this page.');\r\n    });\r\n    $('pth-form-scan').addEventListener('click', scanForm);\r\n\r\n    $('pth-redact-clear').addEventListener('click', function () {\r\n      var kept = [], k;\r\n      for (k = 0; k < S.annots.length; k++) { if (S.annots[k].type !== 'redact') { kept.push(S.annots[k]); } }\r\n      S.annots = kept;\r\n      drawOverlay(); updateRedactOut();\r\n      toast('Redaction marks removed.');\r\n    });\r\n    $('pth-info-btn').addEventListener('click', readInfo);\r\n\r\n    window.addEventListener('resize', function () {\r\n      if (S.doc && !$('pth-layout').classList.contains('fw')) { fitWidth(); }\r\n    });\r\n  }\r\n\r\n  function boot() {\r\n    if (booted) { return; }\r\n    W = document.getElementById('pth-pdfeditor-wrapper');\r\n    if (!W) { return; }\r\n    if (!window.pdfjsLib || !window.PDFLib) { return; }\r\n    booted = true;\r\n    window.pdfjsLib.GlobalWorkerOptions.workerSrc = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/2.16.105\/pdf.worker.min.js';\r\n    initSigPad();\r\n    bind();\r\n    setTool('select');\r\n  }\r\n\r\n  boot();\r\n  if (document.readyState === 'loading') {\r\n    document.addEventListener('DOMContentLoaded', boot);\r\n  }\r\n  window.addEventListener('load', function () { setTimeout(boot, 50); });\r\n  if (!booted && window.MutationObserver) {\r\n    var mo = new MutationObserver(function () { boot(); if (booted) { mo.disconnect(); } });\r\n    try { mo.observe(document.documentElement, { childList: true, subtree: true }); } catch (e) { }\r\n    setTimeout(function () { try { mo.disconnect(); } catch (e2) { } }, 10000);\r\n  }\r\n\r\n  return { boot: boot };\r\n})();\r\n<\/script>\n\n\n\n<style>\n.pth-container{max-width:1480px;margin:0 auto;padding:0 20px;box-sizing:border-box}\n.pth-feature-wrap{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px;margin-bottom:40px}\n.pth-feat-box{border:1px solid #e2e8f0;background:#fff;padding:24px;border-radius:12px;box-shadow:0 4px 6px -1px rgba(0,0,0,0.02)}\n.pth-feat-title{font-size:1.05rem;font-weight:800;color:#0f172a;margin:0 0 10px;display:flex;align-items:center;gap:10px}\n.pth-feat-text{font-size:.9rem;color:#1e293b;line-height:1.6;margin:0;font-weight:500}\n.pth-steps-wrap{border:1px solid #e2e8f0;background:#fff;padding:40px 30px;border-radius:16px;text-align:center;box-shadow:0 4px 6px -1px rgba(0,0,0,0.02);margin-bottom:40px;width:100%;box-sizing:border-box}\n.pth-steps-title{font-size:1.2rem;font-weight:800;color:#0f172a;text-transform:uppercase;margin:0 0 35px;letter-spacing:1px}\n.pth-steps-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:30px}\n.pth-step-item{display:flex;flex-direction:column;align-items:center}\n.pth-step-num{width:45px;height:45px;background:ACCENT;color:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:1.2rem;margin-bottom:15px}\n.pth-step-name{font-weight:800;color:#0f172a;margin:0 0 8px;font-size:1rem}\n.pth-step-desc{font-size:.9rem;color:#1e293b;line-height:1.5;margin:0;font-weight:500}\n<\/style>\n<div class=\"pth-container\">\n  <div class=\"pth-feature-wrap\">\n    <div class=\"pth-feat-box\">\n      <p class=\"pth-feat-title\"><span aria-hidden=\"true\">\ud83d\udd12<\/span> Works Completely Offline<\/p>\n      <p class=\"pth-feat-text\">Your browser reads the PDF directly from your hard drive. Because there is no server involved, your sensitive files never leave your machine and we never add a watermark.<\/p>\n    <\/div>\n    <div class=\"pth-feat-box\">\n      <p class=\"pth-feat-title\"><span aria-hidden=\"true\">\u270d\ufe0f<\/span> Real Redaction &#038; Signatures<\/p>\n      <p class=\"pth-feat-text\">Drop a signature or stamp today&#8217;s date on a form. If you black out a detail, the actual text is removed from the saved file\u2014not just covered by a black box.<\/p>\n    <\/div>\n    <div class=\"pth-feat-box\">\n      <p class=\"pth-feat-title\"><span aria-hidden=\"true\">\ud83d\udcd1<\/span> Rearrange &#038; Export<\/p>\n      <p class=\"pth-feat-text\">Drag page thumbnails to fix a scanned document that is out of order. You can delete the pages you do not need, or pull out a single page and save it as a PNG image.<\/p>\n    <\/div>\n  <\/div>\n  <div class=\"pth-steps-wrap\">\n    <p class=\"pth-steps-title\">How to Use the Offline PDF Editor<\/p>\n    <div class=\"pth-steps-grid\">\n      <div class=\"pth-step-item\">\n        <div class=\"pth-step-num\" aria-hidden=\"true\">1<\/div>\n        <p class=\"pth-step-name\">Load your file<\/p>\n        <p class=\"pth-step-desc\">Drag a PDF straight into the browser window. The file stays on your computer the entire time.<\/p>\n      <\/div>\n      <div class=\"pth-step-item\">\n        <div class=\"pth-step-num\" aria-hidden=\"true\">2<\/div>\n        <p class=\"pth-step-name\">Edit and annotate<\/p>\n        <p class=\"pth-step-desc\">Pick a tool from the menu to highlight paragraphs, fill out form fields, or draw a freehand circle around a mistake.<\/p>\n      <\/div>\n      <div class=\"pth-step-item\">\n        <div class=\"pth-step-num\" aria-hidden=\"true\">3<\/div>\n        <p class=\"pth-step-name\">Fix the layout<\/p>\n        <p class=\"pth-step-desc\">Open the thumbnail view. You can drag pages into a new order or hit the trash icon to drop the ones you do not want.<\/p>\n      <\/div>\n      <div class=\"pth-step-item\">\n        <div class=\"pth-step-num\" aria-hidden=\"true\">4<\/div>\n        <p class=\"pth-step-name\">Save the changes<\/p>\n        <p class=\"pth-step-desc\">Click export to download the updated PDF. The redactions are burned in and the document is ready to share.<\/p>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Last Updated: August 2026<\/p>\n\n\n\n<h2 id=\"\ud83d\udfe2-what-this-offline-pdf-editor-actually-does\" class=\"wp-block-heading\">\ud83d\udfe2 What This Offline PDF Editor Actually Does<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Most free PDF sites ask you to upload the document first. That is fine for a restaurant menu and a bad idea for a signed lease, a medical letter, or a marked exam paper. This page takes the other route: the file is opened by your own browser, every change happens on your device, and the saved copy is written locally. You can disconnect from the internet after the page loads and everything still works.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The workspace is split into five tabs so related jobs sit together instead of being scattered across five different websites.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Tab<\/th><th>Use it for<\/th><\/tr><\/thead><tbody><tr><td>Annotate<\/td><td>Highlight, underline, strikeout, freehand drawing, boxes, text, sticky notes, white-out<\/td><\/tr><tr><td>Pages<\/td><td>Thumbnail view, drag to reorder, rotate a single page, delete a page, insert a blank one<\/td><\/tr><tr><td>Extract<\/td><td>Text from this page or the whole file, search inside it, download .txt, pull images, save a page as PNG<\/td><\/tr><tr><td>Sign<\/td><td>Drawn or typed signature, date and check-mark stamps, filling existing form fields<\/td><\/tr><tr><td>Secure<\/td><td>Permanent redaction, document information, metadata cleaning<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 id=\"\ud83d\udd34-the-redaction-difference-worth-knowing\" class=\"wp-block-heading\">\ud83d\udd34 The Redaction Difference Worth Knowing<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This is the part people get wrong most often, so it is worth spelling out with a real example.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Open the sample document from the Load Sample button. It contains the line&nbsp;<code>Account reference: 4417-9920-3388<\/code>. Now try two things.<\/p>\n\n\n\n<h4 id=\"attempt-one-white-out\" class=\"wp-block-heading\">Attempt one: white-out<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Draw a white-out box over that number and save the PDF. Open the saved file, select the covered area with your mouse and press Ctrl+C. The account number is still on your clipboard. The white box was painted on top; the text underneath never moved.<\/p>\n\n\n\n<h4 id=\"attempt-two-redaction\" class=\"wp-block-heading\">Attempt two: redaction<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Go to the Secure tab, choose Mark Redaction Area, draw over the same number, and save again. Try to select it now. There is nothing there to select, because the marked page was rebuilt as a flat image with the black bar burned in before the file was written. The characters no longer exist in that page.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The trade-off is honest and worth stating: a redacted page becomes an image, so its remaining text stops being searchable or selectable. Redact the pages that genuinely need it and leave the rest alone.<\/p>\n\n\n\n<h2 id=\"\ud83d\udfe1-three-jobs-this-handles-well\" class=\"wp-block-heading\">\ud83d\udfe1 Three Jobs This Handles Well<\/h2>\n\n\n\n<h3 id=\"marking-student-work\" class=\"wp-block-heading\">Marking student work<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Open a submitted assignment, highlight the passages you want to discuss, strike through an incorrect line, add a sticky note with the correction, then save one copy to hand back. Because nothing uploads, student work stays on your machine.<\/p>\n\n\n\n<h3 id=\"returning-a-signed-form\" class=\"wp-block-heading\">Returning a signed form<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Open the form, use Detect Fields in the Sign tab to see whether it has real fillable fields, type your answers, draw your signature on the pad, stamp the date, tick Flatten on save, and export. Flattening turns the filled values into fixed page content so the recipient cannot edit them back.<\/p>\n\n\n\n<h3 id=\"sharing-a-document-with-private-details-removed\" class=\"wp-block-heading\">Sharing a document with private details removed<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Redact the account numbers and personal addresses, then tick the metadata cleaner before saving. That second step matters more than most people expect &#8211; PDF files quietly carry the author name, the software that produced them, and the creation date, all of which travel with the file when you email it.<\/p>\n\n\n\n<h2 id=\"\ud83d\udfe2-a-sensible-working-order\" class=\"wp-block-heading\">\ud83d\udfe2 A Sensible Working Order<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Open the PDF by dropping it onto the page, or press Load Sample to practise on a generated document first.<\/li>\n\n\n\n<li>Sort out the pages before you mark anything &#8211; reorder, rotate and delete in the Pages tab, since annotations are tied to the page you drew them on.<\/li>\n\n\n\n<li>Add your markup, signature and stamps.<\/li>\n\n\n\n<li>Mark redaction areas last, once you are certain what has to disappear.<\/li>\n\n\n\n<li>Tick the metadata cleaner if the file is leaving your organisation, then press Save PDF.<\/li>\n<\/ol>\n\n\n\n<h2 id=\"\ud83d\udd34-common-mistakes-and-real-limits\" class=\"wp-block-heading\">\ud83d\udd34 Common Mistakes and Real Limits<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83d\udd35&nbsp;<strong>Treating white-out as redaction.<\/strong>&nbsp;It hides text visually and nothing more. Use the Secure tab when the information is actually sensitive.<\/li>\n\n\n\n<li>\ud83d\udfe0&nbsp;<strong>Expecting text from a scanned document.<\/strong>&nbsp;If the PDF is a photograph of paper, there are no characters inside it to extract. The tool will tell you it found no selectable text. Recognising letters in an image needs OCR, which this page does not do.<\/li>\n\n\n\n<li>\ud83d\udfe3&nbsp;<strong>Password-protected files.<\/strong>&nbsp;An encrypted PDF cannot be opened or exported here, and you will see a clear error rather than a broken screen. Remove the password in the program that applied it first.<\/li>\n\n\n\n<li>\ud83d\udd35&nbsp;<strong>Very large documents.<\/strong>&nbsp;Everything is held in your browser&#8217;s memory, so a file of several hundred pages will feel slow on a modest laptop. Split it first, then work on the part you need.<\/li>\n\n\n\n<li>\ud83d\udfe0&nbsp;<strong>Signature placement.<\/strong>&nbsp;A signature or stamp lands in a fixed spot near the lower left of the current page. Drag-to-move is not built yet, so use Undo and place it again if the position is wrong.<\/li>\n\n\n\n<li>\ud83d\udfe3&nbsp;<strong>Image extraction misses some files.<\/strong>&nbsp;Certain compressed or masked images cannot be decoded in the browser. When that happens, Save Page as PNG gives you a reliable picture of the page instead.<\/li>\n<\/ul>\n\n\n\n<h2 id=\"\ud83d\udfe1-where-this-sits-next-to-other-tools\" class=\"wp-block-heading\">\ud83d\udfe1 Where This Sits Next to Other Tools<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This page works on the inside of one document. If you need to join several PDFs together or cut one into separate files, that is a different job and it lives in the&nbsp;<a href=\"https:\/\/schoolict.net\/tools\/pdf-merge-and-split-studio\/\">PDF Merge &amp; Split Studio<\/a>. If you are creating a PDF from scratch rather than editing an existing one, write it in&nbsp;<a href=\"https:\/\/schoolict.net\/tools\/markdown-to-pdf-converter\/\">Markdown Studio Pro<\/a>&nbsp;and export from there. For the reasoning behind keeping document work on your own machine, the deeper write-up is in&nbsp;<a href=\"https:\/\/schoolict.net\/tools\/how-pdf-files-work\/\">how PDF files work<\/a>.<\/p>\n\n\n\n<style>\n.pth-faq-section{margin:50px auto 40px;font-family:inherit;max-width:1480px;padding:0 20px;box-sizing:border-box}\n.pth-faq-header{font-size:1.8rem;font-weight:800;color:#0f172a;margin-bottom:25px;border-bottom:2px solid #e2e8f0;padding-bottom:10px;display:flex;align-items:center;gap:10px}\n.pth-faq-grid{display:grid;grid-template-columns:1fr;gap:20px}\n@media(min-width:768px){.pth-faq-grid{grid-template-columns:repeat(2,1fr)}}\n@media(min-width:1024px){.pth-faq-grid{grid-template-columns:repeat(3,1fr)}}\n.pth-faq-card{background:#f8fafc;padding:24px;border-radius:12px;border:1px solid #e2e8f0;transition:transform .2s ease;break-inside:avoid}\n.pth-faq-card:hover{transform:translateY(-3px);box-shadow:0 4px 12px rgba(0,0,0,.05)}\n.pth-faq-q{color:#0f172a;font-size:1rem;font-weight:700;margin:0 0 12px;line-height:1.4}\n.pth-faq-a{margin:0;font-size:.95rem;color:#1e293b;line-height:1.6;font-weight:500}\n<\/style>\n \n<div class=\"pth-faq-wrap\">\n  <p class=\"pth-faq-title\">Frequently Asked Questions<\/p>\n  <div class=\"pth-faq-grid\">\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Is my PDF uploaded to a server?<\/p>\n      <p class=\"pth-faq-a\">No. The file is read by your browser and every edit happens on your device. You can turn off your connection once the page has loaded and keep working.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">What is the difference between white-out and redaction?<\/p>\n      <p class=\"pth-faq-a\">White-out paints a box over the words while the text stays inside the file and can be copied out. Redaction rebuilds the marked page as a flat image, so the covered characters are removed from the saved copy.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Why can I not extract text from my scan?<\/p>\n      <p class=\"pth-faq-a\">A scanned page is a picture of paper, so there are no characters stored in it. Reading letters out of an image needs OCR, which this page does not include. Save Page as PNG still works.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Can I open a password-protected PDF?<\/p>\n      <p class=\"pth-faq-a\">No. Encrypted files are refused with a clear message. Open the document in the software that applied the password, remove the protection, then bring the unlocked copy back here.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Do page changes stay in the exported file?<\/p>\n      <p class=\"pth-faq-a\">Yes. Reordering, rotation, deletion and inserted blank pages are all written into the PDF you save, along with your annotations and signature.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Can I fill in a form and lock the answers?<\/p>\n      <p class=\"pth-faq-a\">Use Detect Fields in the Sign tab to list the fields, type your values, then tick Flatten on save. Flattening turns the answers into fixed page content that cannot be edited back.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">What does the metadata cleaner remove?<\/p>\n      <p class=\"pth-faq-a\">It clears the title, author, subject, keywords, creator and producer entries before saving. These fields often carry a real name or company software details you did not intend to share.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">How do I join two PDFs into one?<\/p>\n      <p class=\"pth-faq-a\">That is a separate job handled by the PDF Merge &amp; Split Studio. This page works inside a single document rather than across several files.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Is there a page limit or a watermark?<\/p>\n      <p class=\"pth-faq-a\">There is no watermark and no artificial limit. The practical ceiling is your device&#8217;s memory, so very long documents feel slow on older machines.<\/p>\n    <\/div>\n \n  <\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Complete Offline PDF Suite Looking for a Free Offline PDF Reader and Editor? View, draw, annotate, add images, extract text, and edit your PDF documents instantly with 100% privacy. \ud83d\udd12 Works Completely Offline Your browser reads the PDF directly from your hard drive. Because there is no server involved, your sensitive files never leave your &#8230; <a title=\"Free Offline PDF Reader and  Editor &#8211; Annotate &amp; Convert\" class=\"read-more\" href=\"https:\/\/schoolict.net\/tools\/free-offline-pdf-reader-and-editor\/\" aria-label=\"Read more about Free Offline PDF Reader and  Editor &#8211; Annotate &amp; Convert\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":3472,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-1575","page","type-page","status-publish","has-post-thumbnail"],"_links":{"self":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/1575","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/comments?post=1575"}],"version-history":[{"count":6,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/1575\/revisions"}],"predecessor-version":[{"id":7027,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/1575\/revisions\/7027"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/media\/3472"}],"wp:attachment":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/media?parent=1575"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}