1const PDF_LIB_URL="/vendor/pdf-lib/pdf-lib.min.js?v=20260717pdf1",QPDF_URL="/vendor/qpdf/qpdf.mjs?v=20260717pdf1";self.importScripts(PDF_LIB_URL);const{PDFArray:PDFArray,PDFDict:PDFDict,PDFDocument:PDFDocument,PDFHexString:PDFHexString,PDFName:PDFName,PDFString:PDFString}=self.PDFLib,name=e=>PDFName.of(e),DANGEROUS_ACTIONS=new Set(["JavaScript","Launch","GoToR","GoToE","SubmitForm","ResetForm","ImportData","Rendition","Sound","Movie","Thread","Hide","SetOCGState","Trans"]),UNSAFE_ANNOTATIONS=new Set(["FileAttachment","RichMedia","Screen","Movie","Sound","3D","Widget"]);function postProgress(e,t,n,a){self.postMessage({type:"progress",id:e,phase:t,percent:n,message:a})}function valueName(e){return e instanceof PDFName?e.asString().replace(/^\//,""):""}function lookupDict(e,t){if(t instanceof PDFDict)return t;try{return e.lookup(t,PDFDict)}catch{return null}}function decodePdfString(e){if(e instanceof PDFString||e instanceof PDFHexString)try{return e.decodeText()}catch{return""}return""}function safeAction(e,t){const n=lookupDict(e,t);if(!n)return!1;const a=valueName(n.get(name("S")));if("GoTo"===a)return!0;if("URI"!==a)return!1;const o=decodePdfString(n.get(name("URI"))).trim();return/^(https?:|mailto:)/i.test(o)}function arrayFrom(e,t){if(t instanceof PDFArray)return t;try{return e.lookup(t,PDFArray)}catch{return null}}function auditDocument(e){const t=e.context,n=e.catalog,a={links:0,annotations:0,forms:0,attachments:0,scripts:0,actions:0,multimedia:0,signatures:0,metadata:0};n.has(name("AcroForm"))&&(a.forms+=1),n.has(name("Metadata"))&&(a.metadata+=1),t.trailerInfo?.Info&&(a.metadata+=1),(n.has(name("OpenAction"))||n.has(name("AA")))&&(a.actions+=1),n.has(name("Collection"))&&(a.attachments+=1),n.has(name("AF"))&&(a.attachments+=1);const o=lookupDict(t,n.get(name("Names")));o?.has(name("EmbeddedFiles"))&&(a.attachments+=1),o?.has(name("JavaScript"))&&(a.scripts+=1);for(const n of e.getPages()){n.node.has(name("AA"))&&(a.actions+=1);const e=arrayFrom(t,n.node.get(name("Annots")));if(e)for(let n=0;n<e.size();n+=1){const o=lookupDict(t,e.get(n));if(!o)continue;a.annotations+=1;const r=valueName(o.get(name("Subtype")));"Link"===r&&(a.links+=1),"FileAttachment"===r&&(a.attachments+=1),UNSAFE_ANNOTATIONS.has(r)&&"FileAttachment"!==r&&"Widget"!==r&&(a.multimedia+=1),(o.has(name("AA"))||o.has(name("A"))&&!safeAction(t,o.get(name("A"))))&&(a.actions+=1)}}for(const[,e]of t.enumerateIndirectObjects()){if(!(e instanceof PDFDict))continue;const t=valueName(e.get(name("Type"))),n=valueName(e.get(name("FT"))),o=valueName(e.get(name("S")));"Sig"!==t&&"Sig"!==n||(a.signatures+=1),("JavaScript"===o||e.has(name("JS")))&&(a.scripts+=1),DANGEROUS_ACTIONS.has(o)&&(a.actions+=1)}return a}function cleanNameTree(e){const t=lookupDict(e.context,e.catalog.get(name("Names")));t&&(t.delete(name("JavaScript")),t.delete(name("EmbeddedFiles")),0===t.keys().length&&e.catalog.delete(name("Names")))}function cleanAnnotations(e,t){const n=e.context;
1for(const a of e.getPages()){const e=arrayFrom(n,a.node.get(name("Annots")));if(!e)continue;const o=PDFArray.withContext(n);for(let a=0;a<e.size();a+=1){const r=e.get(a),s=lookupDict(n,r);if(!s)continue;const i=valueName(s.get(name("Subtype")));if(!t&&"Link"===i){const e=s.has(name("Dest")),t=s.has(name("A"))&&safeAction(n,s.get(name("A")));(e||t)&&(s.delete(name("AA")),o.push(r))}}o.size()>0?a.node.set(name("Annots"),o):a.node.delete(name("Annots"))}}function removeActiveFeatures(e,t,n){const a=e.context,o=e.catalog,r=[];if(!n)try{const t=e.getForm();"function"==typeof t.deleteXFA&&t.deleteXFA(),t.getFields().length&&t.flatten({updateFieldAppearances:!0})}catch(e){r.push(`Some form appearances could not be generated: ${e.message||e}`)}cleanAnnotations(e,t.removeLinks),cleanNameTree(e);const s=["OpenAction","AA","AcroForm","AF","Collection","Perms","Requirements","SpiderInfo","PresSteps"];for(const e of s)o.delete(name(e));for(const t of e.getPages())for(const e of["AA","AF","PresSteps","Dur","Trans"])t.node.delete(name(e));for(const[,e]of a.enumerateIndirectObjects())if(e instanceof PDFDict){for(const t of["AA","OpenAction","JavaScript","JS","Next","EmbeddedFiles","AF","Collection","RichMediaContent","RichMediaSettings","3DD","3DV","3DA","Sound","Movie","Rendition"])e.delete(name(t));if(e.has(name("A"))){const n=valueName(e.get(name("Subtype"))),o=e.has(name("Title"));(t.removeLinks||!safeAction(a,e.get(name("A")))&&("Link"===n||o))&&e.delete(name("A"))}t.removeLinks&&e.has(name("Dest"))&&e.delete(name("Dest"))}if(t.removeMetadata){a.trailerInfo&&delete a.trailerInfo.Info;for(const[,e]of a.enumerateIndirectObjects())if(e instanceof PDFDict)for(const t of["Metadata","PieceInfo","LastModified"])e.delete(name(t))}return r}function difference(e,t,n){return Math.max(0,Number(e[n]||0)-Number(t[n]||0))}function redact(e,t){const n=String(e||"");return t?n.split(t).join("[redacted]"):n}async function createQpdf(e){const t=[],n=await import(QPDF_URL),a=n.default||n.createModule;if("function"!=typeof a)throw new Error("QPDF module factory is unavailable.");return{module:await a({noInitialRun:!0,noExitRuntime:!0,print:n=>t.push(redact(n,e)),printErr:n=>t.push(redact(n,e))}),output:t}}function unlink(e,t){try{e.FS.unlink(t)}catch{}}function callQpdf(e,t,n){e.output.length=0;let a=0;try{e.module.callMain(t)}catch(t){if(a=Number(t?.status??t?.exitCode??1),0!==a&&3!==a){const n=e.output.slice(-8).join("\n")||t.message||String(t);throw new Error(n)}}if(!n)return{status:a,logs:e.output.slice()};const o=e.module.FS.readFile(n);if(!o?.length)throw new Error("QPDF produced no output.");return{status:a,logs:e.output.slice(),bytes:new Uint8Array(o)}}function passwordError(e,t,n){const a=String(e?.message||e||"");if(/password|encrypted|invalid password|not supplied/i.test(a)||t&&!n){const e=new Error(n?"The PDF password was not accepted.":"This PDF requires a password.");return e.code=n?"INVALID_PASSWORD":"PASSWORD_REQUIRED",e}return e}async function flattenStructural(e,t,n){let a=new Uint8Array(t);const o=new TextDecoder("latin1").decode(a.subarray(0,Math.min(a.length,1048576))),r=/\/Encrypt\b/.test(o),s=[];let i,c=null,d=a,u=!1;postProgress(e,"normalize",12,"Loading the local QPDF WebAssembly runtime...");try{c=await createQpdf(n.password||""),c.module.FS.writeFile("input.pdf",a);const e=[];n.password&&e.push(`--password=${n.password}`),e.push("--decrypt","--object-streams=disable","--stream-data=preserve","--","input.pdf","normalized.pdf"),d=callQpdf(c,e,"normalized.pdf").bytes,u=!0,unlink(c.module,"input.pdf")}catch(e){const t=passwordError(e,r,n.password);if(t.code)throw t;s.push(`QPDF normalization was unavailable; pdf-lib fallback used: ${redact(e.message||e,n.password)}`)}a=null,postProgress(e,"inspect",30,"Inspecting forms, links, attachments, scripts, and metadata...");try{i=await PDFDocument.load(d.slice(),{ignoreEncryption:!1,updateMetadata:!1,throwOnInvalidObject:!1})}catch(e){throw passwordError(e,r,n.password)}const l=auditDocument(i),m=i.getPageCount();let f=d,p=!1;if(n.removeLinks&&c){postProgress(e,"flatten",44,"Flattening annotation and form appearances...");try{c.module.FS.writeFile("normalized-input.pdf",d),f=callQpdf(c,["--generate-appearances","--flatten-annotations=all","--object-streams=disable","--stream-data=preserve","--","normalized-input.pdf","flattened.pdf"],"flattened.pdf").bytes,p=!0,unlink(c.module,"normalized-input.pdf"),unlink(c.module,"flattened.pdf")}catch(e){s.push(`QPDF appearance flattening fell back to pdf-lib: ${redact(e.message||e,n.password)}`)}}d=null,i=null,postProgress(e,"sanitize",58,"Removing active and embedded PDF features...");const g=await PDFDocument.load(f,{ignoreEncryption:!1,updateMetadata:!1,throwOnInvali
1dObject:!1});s.push(...removeActiveFeatures(g,n,p));let h=new Uint8Array(await g.save({useObjectStreams:!1,updateFieldAppearances:!1,addDefaultPage:!1}));f=null,postProgress(e,"rewrite",74,"Rewriting the sanitized PDF for broad compatibility...");let F=h,D=!1;if(c)try{c.module.FS.writeFile("sanitized.pdf",h),F=callQpdf(c,["--object-streams=disable","--stream-data=preserve","--","sanitized.pdf","output.pdf"],"output.pdf").bytes,unlink(c.module,"sanitized.pdf"),h=null;const e=callQpdf(c,["--check","output.pdf"]);D=0===e.status||3===e.status,3===e.status&&s.push("QPDF validated the output with non-fatal warnings."),unlink(c.module,"output.pdf")}catch(e){s.push(`Final QPDF rewrite was unavailable; validated pdf-lib output retained: ${redact(e.message||e,n.password)}`)}postProgress(e,"audit",88,"Auditing the rewritten object graph...");const A=auditDocument(await PDFDocument.load(F.slice(),{ignoreEncryption:!1,updateMetadata:!1,throwOnInvalidObject:!1})),w=A.scripts+A.actions+A.attachments+A.forms+A.multimedia+A.signatures;if(w>0){const e=Object.entries(A).filter(([e,t])=>t>0&&!["links","annotations","metadata"].includes(e)).map(([e,t])=>`${e}=${t}`).join(", ");throw new Error(`Validation found ${w} active or embedded feature reference${1===w?"":"s"} in the output${e?` (${e})`:""}.`)}if(n.removeLinks&&A.links>0)throw new Error("Validation found clickable links in the output.");if(n.removeMetadata&&A.metadata>0)throw new Error("Validation found metadata references in the output.");return{bytes:F,summary:{mode:"structural",pages:m,before:l,after:A,removed:{links:difference(l,A,"links"),annotations:difference(l,A,"annotations"),forms:difference(l,A,"forms"),attachments:difference(l,A,"attachments"),scripts:difference(l,A,"scripts"),actions:difference(l,A,"actions"),multimedia:difference(l,A,"multimedia"),signatures:difference(l,A,"signatures")},metadataRemoved:n.removeMetadata,qpdfNormalized:u,qpdfFlattened:p,qpdfChecked:D,warnings:s}}}self.addEventListener("message",async e=>{const t=e.data||{};if("flatten"!==t.type||!(t.buffer instanceof ArrayBuffer))return;const n=t.id;try{const e=await flattenStructural(n,t.buffer,{removeLinks:!1!==t.options?.removeLinks,removeMetadata:!0===t.options?.removeMetadata,password:String(t.options?.password||"")});postProgress(n,"complete",96,"Structural processing complete. Validating page rendering..."),self.postMessage({type:"result",id:n,summary:e.summary,buffer:e.bytes.buffer},[e.bytes.buffer])}catch(e){self.postMessage({type:"error",id:n,code:e?.code||"STRUCTURAL_FAILURE",message:redact(e?.message||e||"PDF flattening failed.",t.options?.password||"")})}});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.