PDF to HTML Converter — Free, Browser-Based | SmallStudyTools
100% Free · No Signup · Browser-Based
Upload a PDF and convert its text into a clean, web-ready HTML page, right in your browser. Nothing is uploaded to a server. Pro Mode adds bulk conversion for multiple PDFs at once and optional built-in styling.
Add to Favorites
Share This Tool
● Pro Version is Free for now
Simple
Pro — Full Control
✓ Bulk convert multiple PDFs at once · ✓ Optional built-in page styling
Drag & drop your PDF here
or click to browse — only .pdf files are accepted
Browse Files
+ Add Another PDF
🌐 Convert to HTML Simple mode outputs bare semantic tags only. Turn this on to wrap the page in clean, embedded CSS typography.
Include Basic Styling
`;
}// ── CONVERSION ────────────────────────────────────────────────
async function startConversion() {
if (!queuedFiles.length || converting) return;
converting = true;
clearErr();
updateConvertBtn();
document.getElementById('progressWrap').classList.add('show');
fileResults = [];const styled = isPro && stylingOn;
const totalFiles = queuedFiles.length;try {
for (let fi = 0; fi < queuedFiles.length; fi++) {
const qf = queuedFiles[fi];
const { pages, median } = await extractDocLines(qf.file, (pageNum, pageCount) => {
setProgress(`Reading "${qf.file.name}" — page ${pageNum} of ${pageCount}`, (fi + pageNum / pageCount) / totalFiles);
});
const allLines = [].concat(...pages);
const title = qf.file.name.replace(/\.pdf$/i, '');
const bodyHtml = buildHtmlBody(allLines, median);
const htmlText = buildFullHtml(bodyHtml, title, styled);
fileResults.push({ id: qf.id, name: qf.file.name, htmlText, lineCount: allLines.length });
}
renderResults();
document.getElementById('resultsCard').classList.add('show');
document.getElementById('resultsCard').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
toast(`Converted ${fileResults.reduce((n, f) => n + f.lineCount, 0)} line(s) to HTML!`);
} catch (e) {
showErr('Something went wrong reading that PDF. Please make sure the file isn\'t corrupted or password-protected, and try again.');
} finally {
converting = false;
document.getElementById('progressWrap').classList.remove('show');
updateConvertBtn();
}
}function setProgress(label, frac) {
document.getElementById('progressLabel').textContent = label;
document.getElementById('progressFill').style.width = Math.min(100, Math.max(4, frac * 100)) + '%';
}// ── RESULTS / DOWNLOADS ───────────────────────────────────────
function renderResults() {
const wrap = document.getElementById('fileResultsList');
wrap.innerHTML = fileResults.map(fr => `
${escHtml(fr.name.replace(/\.pdf$/i, ''))}.html
${fr.lineCount} line${fr.lineCount === 1 ? '' : 's'}
Download
`).join('');const topActions = document.getElementById('resultsTopActions');
topActions.innerHTML = fileResults.length > 1
? `
Download Everything (.zip)
`
: '';const preview = document.getElementById('previewBox');
if (fileResults.length) {
preview.style.display = 'block';
const lines = fileResults[0].htmlText.split('\n');
preview.textContent = lines.slice(0, 12).join('\n') + (lines.length > 12 ? '\n…' : '');
} else {
preview.style.display = 'none';
}
}function downloadHtml(fileId) {
const fr = fileResults.find(f => f.id === fileId);
if (!fr) return;
const blob = new Blob([fr.htmlText], { type: 'text/html;charset=utf-8;' });
const base = fr.name.replace(/\.pdf$/i, '');
triggerBlobDownload(blob, `${base}.html`);
toast('HTML file downloaded!');
}async function downloadEverythingZip() {
if (!fileResults.length) return;
const zip = new JSZip();
fileResults.forEach(fr => {
const base = fr.name.replace(/\.pdf$/i, '');
zip.file(`${base}.html`, fr.htmlText);
});
const blob = await zip.generateAsync({ type: 'blob' });
triggerBlobDownload(blob, 'pdf-to-html-converted.zip');
toast('ZIP downloaded!');
}function triggerBlobDownload(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = filename; a.click();
setTimeout(() => URL.revokeObjectURL(url), 4000);
}function resetTool() {
queuedFiles = [];
fileResults = [];
renderFileChips();
updateConvertBtn();
document.getElementById('resultsCard').classList.remove('show');
document.getElementById('mainCol').scrollIntoView({ behavior: 'smooth', block: 'start' });
}// ── STYLING (pro) ─────────────────────────────────────────────
function toggleStyling() {
if (!isPro) { toast('Built-in styling is part of Pro Mode'); return; }
stylingOn = !stylingOn;
const sw = document.getElementById('stylingSwitch');
if (sw) { sw.classList.toggle('on', stylingOn); sw.setAttribute('aria-checked', String(stylingOn)); }
}// ── MODE ──────────────────────────────────────────────────────
function setMode(mode) {
isPro = mode === 'pro';
document.getElementById('btnSimple').classList.toggle('active', !isPro);
document.getElementById('btnPro').classList.toggle('active', isPro);
document.body.classList.toggle('pro-mode', isPro);
const pb = document.getElementById('proBar'); if (pb) pb.style.display = isPro ? 'flex' : 'none';
document.getElementById('fileInput').multiple = isPro;
document.getElementById('uploadCardTitle').textContent = isPro ? 'Upload Your PDFs' : 'Upload Your PDF';
document.getElementById('dropzoneHint').textContent = isPro
? 'or click to browse — only .pdf files are accepted, up to ' + MAX_FILES_PRO + ' at once'
: 'or click to browse — only .pdf files are accepted';
if (!isPro && queuedFiles.length > 1) {
queuedFiles = queuedFiles.slice(0, 1);
renderFileChips();
toast('Switched to Simple Mode — only the first file was kept');
}
if (!isPro && stylingOn) {
stylingOn = false;
const sw = document.getElementById('stylingSwitch');
if (sw) { sw.classList.remove('on'); sw.setAttribute('aria-checked', 'false'); }
}
updateConvertBtn();
}// ── UTILS ─────────────────────────────────────────────────────
function escHtml(s) { return String(s || '').replace(/&/g, '&').replace(//g, '>'); }
function toast(msg) { const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); setTimeout(() => t.classList.remove('show'), 2800); }
function formatBytes(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(0) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}