Simplify your tech life.
Merge, split, compress, redact, impose booklets and convert between PDF, Word, Excel, Markdown, images, HTML and plain text. Nothing is uploaded to a server, so private documents stay private.
Change what is in the file and what order it comes in
Photos, scans, Word documents and text become one PDF
Pull pages, text or tables into a format you can edit
Stamp, number and label pages without a design app
Print shop and archive jobs most free PDF sites do not offer
How TheTechHacker PDF Converter works, what it can do, and where its limits are.
Yes, every one of the 32 tools is free and there is no account, no email capture and no trial period. There are no watermarks stamped on output files, no daily conversion cap and no premium tier holding back features. The entire application is a single HTML page, so you can save it to your computer and keep using it after you close the tab.
No. Unlike most online PDF converters, which upload your document, process it on their infrastructure and delete it some hours later, TheTechHacker PDF Converter runs entirely inside your browser using JavaScript. Your file is read from disk into memory on your own machine, processed there, and handed straight back to you as a download. Nothing is transmitted, which makes it safe for contracts, medical records, financial statements and anything else covered by confidentiality rules.
Almost. The page loads three small libraries from a public CDN when it first opens, so the initial load needs a connection. Once the page is open you can disconnect completely and every tool keeps working. If you want a fully offline copy, download the libraries and change the four script tags to point at your local files.
There is no limit imposed by the tool itself. The practical ceiling is your device's available memory, since the whole document is held in RAM while it is processed. On a typical laptop, documents of a few hundred megabytes are fine. Very large scanned files processed at 300 dpi are the most memory-hungry operation, so drop the resolution if a big job stalls.
Files with an owner password that only restricts printing or copying will usually open. Files encrypted with a user password, where the password is required to view the document at all, cannot be opened, and removing a password is not something a browser can do. Unlock the file in a desktop PDF reader first, then bring it here.
It extracts the text, detects headings by font size and produces a document that Word, Pages and Google Docs open and edit normally. It does not attempt to rebuild the original page layout, columns, tables or images, because that needs a full layout engine. Think of it as recovering the words so you can reuse them, not as producing a pixel-identical copy.
The PDF is almost certainly a scan, meaning the pages are photographs of text rather than real text. No text-based tool can read those without optical character recognition, which this tool does not include. Run the file through OCR software first, or use PDF to images if you only need the pictures.
It genuinely removes the words rather than hiding them. Each page containing a match is covered with a black box and then flattened to an image, so the underlying text layer is destroyed and cannot be recovered by copying, searching or opening the file in an editor. This is the correct way to redact, unlike drawing a rectangle over text in a PDF editor.
Any current version of Chrome, Edge, Firefox, Safari, Brave or Opera on Windows, macOS, Linux, Android or iOS. It works on phones, although large files are limited by mobile memory. No extension or plugin is needed.
Reading and converting works for any language the PDF contains. Tools that draw new text onto a page, namely watermarks and page numbers, use the built-in PDF fonts, which cover English and Western European characters only. Text in Telugu, Hindi, Arabic, Chinese or Japanese is stripped from watermarks rather than rendered incorrectly.
"+ esc(files[0].name)+" → "+esc(files[1].name)+" · "+adds+" lines added, "+dels+" removed
"+ (html || "The two documents are identical.
")+""; return [{ name:"comparison report.html", blob:new Blob([doc],{type:"text/html;charset=utf-8"}), note: adds+" added, "+dels+" removed" }]; } }; TOOLS.stats = { group:"advanced", icon:"chart", name:"Document statistics", blurb:"Word count, reading time and the most frequent terms.", lede:"Produces a small report with counts, averages and a keyword frequency table. Useful for editors, students working to a word limit, and anyone auditing content.", accept:".pdf", multiple:true, run: async (opts, tick) => { const stop = new Set(("the of and to a in is it for on that with as this by are be or an at from was were which has have had not but they their you your we our can will may all any more other than then them these those such into its if no so do does done been each also had her his she him he i us who whom what when where how"). split(" ")); const res = []; for(let f=0;fDocument statistics
"+ "Drawing the pages…
'; try{ const pdf = await loadPdfJs(files[0]); const libDoc = await loadPdfLib(files[0]); const rots = libDoc.getPages().map(p=>p.getRotation().angle); organiserState = { pages:[] }; const holder = document.createElement("div"); holder.className = "pages"; els.organiser.innerHTML = ""; els.organiser.appendChild(holder); for(let n=1;n<=pdf.numPages;n++){ const { canvas } = await renderPage(pdf, n, 0.34); organiserState.pages.push({ index:n-1, rotation:0, deleted:false, baseRotation:rots[n-1]||0, canvas }); progress(n/pdf.numPages); } els.bar.classList.remove("on"); drawOrganiser(holder); say(pdf.numPages+" page"+(pdf.numPages>1?"s":"")+" loaded."); }catch(e){ fail("That PDF could not be opened: "+e.message); els.organiser.innerHTML = ""; } } function drawOrganiser(holder){ holder.innerHTML = ""; organiserState.pages.forEach((p,i)=>{ const d = document.createElement("div"); d.className = "pg"+(p.deleted ? " del" : ""); const shot = document.createElement("div"); shot.className = "shot"; p.canvas.style.transform = "rotate("+p.rotation+"deg)"; shot.appendChild(p.canvas); d.appendChild(shot); const tag = document.createElement("span"); tag.className = "num"; tag.textContent = i+1; d.appendChild(tag); const acts = document.createElement("div"); acts.className = "acts"; acts.innerHTML = ''+ ''+ '"+ ''; acts.querySelectorAll("button").forEach(b=>{ b.addEventListener("click", ()=>{ const a = b.dataset.a, arr = organiserState.pages; if(a === "rot") p.rotation = (p.rotation + 90) % 360; if(a === "del") p.deleted = !p.deleted; if(a === "left" && i > 0) [arr[i-1],arr[i]] = [arr[i],arr[i-1]]; if(a === "right" && i < arr.length-1) [arr[i+1],arr[i]] = [arr[i],arr[i+1]]; drawOrganiser(holder); }); }); d.appendChild(acts); holder.appendChild(d); }); } /* run */ function fail(msg){ els.err.textContent = msg; els.err.classList.add("on"); els.bar.classList.remove("on"); } els.go.addEventListener("click", async ()=>{ els.err.classList.remove("on"); els.out.classList.remove("on"); els.results.innerHTML = ""; els.zipall.style.display = "none"; els.go.disabled = true; const label = els.go.textContent; els.go.textContent = "Working…"; say(""); progress(0.03); const t0 = performance.now(); try{ outputs = await current.run(readOptions(), p=>progress(Math.max(0.03, Math.min(0.99, p)))); progress(1); showResults(); say(outputs.length+" file"+(outputs.length>1?"s":"")+" ready in "+((performance.now()-t0)/1000).toFixed(1)+"s"); }catch(e){ console.error(e); fail(e && e.message ? e.message : "Something went wrong reading that file. If it is password protected, unlock it in a PDF reader first."); say(""); } els.go.textContent = label; checkReady(); }); function showResults(){ els.out.classList.add("on"); els.results.innerHTML = ""; outputs.forEach(o=>{ const url = URL.createObjectURL(o.blob); const row = document.createElement("div"); row.className = "result"; row.innerHTML = ''+esc(ext(o.name))+''+esc(o.name)+""+ ''+esc(o.note || fmtSize(o.blob.size))+""+ 'Download'; els.results.appendChild(row); }); if(outputs.length > 1){ els.zipall.style.display = ""; els.zipall.onclick = async ()=>{ const original = els.zipall.textContent; els.zipall.textContent = "Zipping…"; try{ const zip = new JSZip(); outputs.forEach(o=>zip.file(o.name, o.blob)); const blob = await zip.generateAsync({ type:"blob" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = current.name.toLowerCase().replace(/[^a-z0-9]+/g,"-")+".zip"; document.body.appendChild(a); a.click(); a.remove(); }catch(e){ fail("The zip could not be built: "+e.message); } els.zipall.textContent = original; }; } }