PDF Compiler — Merge Multiple PDFs Into One
A lightweight PDF Compiler that lets you upload multiple PDF files, reorder them, and compile them into one combined PDF — all in the browser (nothing gets uploaded).
Install: Save as /tools/pdf-compiler/index.html and visit the folder in your browser.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>PDF Compiler — Merge PDFs</title>
<meta name="description" content="Compile multiple PDFs into one file in your browser. Reorder, remove, and download the merged PDF. No uploads, no database." />
<meta name="robots" content="index,follow" />
<link rel="canonical" href="https://your-site.com/tools/pdf-compiler/" />
<style>
:root{
--bg:#0b0f16; --panel:#111a26; --panel2:#0c1420;
--text:#e8eef8; --muted:rgba(232,238,248,.72);
--line:rgba(255,255,255,.10); --accent:#2a8cff;
--shadow:0 18px 55px rgba(0,0,0,.45); --r:16px;
--ok:#7CFF9A; --bad:#FF7C7C;
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--text);font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif}
.wrap{max-width:980px;margin:0 auto;padding:18px}
.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:14px;margin:12px 0;box-shadow:var(--shadow)}
.pill{display:inline-block;padding:4px 10px;border-radius:999px;background:rgba(42,140,255,.14);border:1px solid rgba(42,140,255,.25);font-weight:900;font-size:12px}
.muted{color:var(--muted)}
.row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
input[type="text"]{
width:100%;padding:10px 12px;border-radius:12px;border:1px solid var(--line);
background:var(--panel2);color:var(--text);outline:none;font-weight:800;
}
input[type="file"]{
width:100%;
padding:10px 12px;border-radius:12px;border:1px dashed rgba(255,255,255,.22);
background:rgba(255,255,255,.03);color:var(--muted);
}
.btn{
display:inline-flex;align-items:center;justify-content:center;gap:8px;
border:0;border-radius:12px;padding:10px 12px;background:var(--accent);
color:#06101a;font-weight:950;cursor:pointer;
}
.btn2{
display:inline-flex;align-items:center;justify-content:center;gap:8px;
border:1px solid var(--line);border-radius:12px;padding:10px 12px;background:transparent;
color:var(--text);font-weight:950;cursor:pointer;
}
.btn2[disabled], .btn[disabled]{opacity:.5;cursor:not-allowed}
.list{display:grid;gap:10px;margin-top:10px}
.item{
padding:10px;border-radius:14px;border:1px solid rgba(255,255,255,.10);
background:rgba(255,255,255,.03);
display:grid;grid-template-columns:1fr auto;gap:10px;align-items:center;
}
.name{font-weight:950}
.meta{font-size:12px;color:var(--muted);margin-top:4px}
.actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end}
.mini{
border:1px solid rgba(255,255,255,.12);background:transparent;color:var(--text);
border-radius:10px;padding:8px 10px;font-weight:950;cursor:pointer;
}
.hr{height:1px;background:var(--line);margin:12px 0}
.log{
padding:12px;border-radius:14px;border:1px solid var(--line);background:var(--panel2);
white-space:pre-wrap;word-break:break-word;
font:650 12px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
min-height:86px;
}
.ok{color:var(--ok);font-weight:950}
.bad{color:var(--bad);font-weight:950}
</style>
<!-- PDF-Lib (client-side PDF merge) -->
<script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js"></script>
</head>
<body>
<div class="wrap">
<div class="card">
<div class="pill">Tool</div>
<h1 style="margin:10px 0 6px;font-size:22px">PDF Compiler</h1>
<div class="muted">Add PDFs → reorder → compile → download (runs locally in your browser).</div>
</div>
<div class="card">
<b>Add PDF files</b>
<div class="muted" style="margin-top:6px">Nothing is uploaded. Your PDFs stay on your device.</div>
<div class="hr"></div>
<div class="row">
<div style="flex:1;min-width:280px">
<input id="file" type="file" accept="application/pdf" multiple />
<div class="muted" style="margin-top:6px;font-size:12px">Tip: You can add more files later — they’ll append to the list.</div>
</div>
<div style="flex:1;min-width:280px">
<div class="muted" style="font-size:12px;margin-bottom:6px">Output filename</div>
<input id="outName" type="text" value="compiled.pdf" />
</div>
<div class="row" style="align-self:flex-end">
<button class="btn" id="compile" type="button">🧩 Compile</button>
<button class="btn2" id="clear" type="button">🧹 Clear</button>
</div>
</div>
<div class="hr"></div>
<b>Files</b>
<div id="list" class="list"></div>
<div class="hr"></div>
<b>Status</b>
<div id="log" class="log">Add PDFs to begin…</div>
</div>
</div>
<script>
(function(){
var files = []; // {file:File, id:string}
var $ = function(id){ return document.getElementById(id); };
function fmtBytes(n){
n = Number(n||0);
if (n < 1024) return n + " B";
var kb = n / 1024;
if (kb < 1024) return kb.toFixed(1) + " KB";
var mb = kb / 1024;
if (mb < 1024) return mb.toFixed(2) + " MB";
var gb = mb / 1024;
return gb.toFixed(2) + " GB";
}
function log(msg, cls){
var el = $("log");
el.innerHTML = (cls ? '<span class="'+cls+'">' : '') + escapeHtml(msg) + (cls ? '</span>' : '');
}
function appendLog(msg){
var el = $("log");
el.textContent = (el.textContent ? el.textContent + "\n" : "") + msg;
}
function escapeHtml(s){
return String(s||"").replace(/[&<>"]/g, function(c){
return ({'&':'&','<':'<','>':'>','"':'"'}[c]);
});
}
function uid(){
return Math.random().toString(16).slice(2) + Date.now().toString(16);
}
function render(){
var host = $("list");
host.innerHTML = "";
if (!files.length){
host.innerHTML = '<div class="muted" style="font-size:13px">No PDFs added yet.</div>';
return;
}
files.forEach(function(x, idx){
var div = document.createElement("div");
div.className = "item";
var left = document.createElement("div");
left.innerHTML =
'<div class="name">' + escapeHtml((idx+1) + ". " + x.file.name) + '</div>' +
'<div class="meta">Size: <b>' + escapeHtml(fmtBytes(x.file.size)) + '</b></div>';
var actions = document.createElement("div");
actions.className = "actions";
var up = document.createElement("button");
up.className = "mini";
up.type = "button";
up.textContent = "↑";
up.disabled = idx === 0;
up.addEventListener("click", function(){
var tmp = files[idx-1]; files[idx-1] = files[idx]; files[idx] = tmp;
render();
});
var down = document.createElement("button");
down.className = "mini";
down.type = "button";
down.textContent = "↓";
down.disabled = idx === files.length - 1;
down.addEventListener("click", function(){
var tmp = files[idx+1]; files[idx+1] = files[idx]; files[idx] = tmp;
render();
});
var del = document.createElement("button");
del.className = "mini";
del.type = "button";
del.textContent = "✕";
del.addEventListener("click", function(){
files = files.filter(function(y){ return y.id !== x.id; });
render();
});
actions.appendChild(up);
actions.appendChild(down);
actions.appendChild(del);
div.appendChild(left);
div.appendChild(actions);
host.appendChild(div);
});
}
function safeOutName(){
var n = $("outName").value.trim() || "compiled.pdf";
if (!/\.pdf$/i.test(n)) n += ".pdf";
n = n.replace(/[^\w\.\-\(\)\s]+/g, "_");
return n;
}
async function compile(){
if (!files.length) { log("Add at least one PDF first.", "bad"); return; }
if (!window.PDFLib || !window.PDFLib.PDFDocument) {
log("PDF-Lib failed to load. Check your connection (CDN).", "bad");
return;
}
$("compile").disabled = true;
$("clear").disabled = true;
log("Compiling…");
try{
var PDFDocument = window.PDFLib.PDFDocument;
var merged = await PDFDocument.create();
for (var i=0;i<files.length;i++){
var f = files[i].file;
appendLog("Loading: " + f.name);
var buf = await f.arrayBuffer();
var src = await PDFDocument.load(buf, { ignoreEncryption: true });
var pages = await merged.copyPages(src, src.getPageIndices());
pages.forEach(function(p){ merged.addPage(p); });
appendLog("Added pages: " + pages.length);
}
appendLog("Saving merged PDF…");
var bytes = await merged.save();
var blob = new Blob([bytes], {type:"application/pdf"});
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = url;
a.download = safeOutName();
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
log("Done! Download started: " + a.download, "ok");
} catch(e){
log("Error: " + (e && e.message ? e.message : String(e)), "bad");
} finally {
$("compile").disabled = false;
$("clear").disabled = false;
}
}
$("file").addEventListener("change", function(){
var picked = Array.prototype.slice.call($("file").files || []);
if (!picked.length) return;
picked.forEach(function(f){
if (!/\.pdf$/i.test(f.name) && f.type !== "application/pdf") return;
files.push({file:f, id:uid()});
});
$("file").value = "";
render();
log("Added " + picked.length + " file(s). Reorder if needed, then Compile.");
});
$("compile").addEventListener("click", compile);
$("clear").addEventListener("click", function(){
if (!confirm("Clear the file list?")) return;
files = [];
render();
log("Cleared. Add PDFs to begin…");
});
render();
})();
</script>
</body>
</html>
Comments (0)
No comments yet — be the first.