NNicheStackTutorials
← All tutorials
Frontend Tools

pdftext: Extract Resume Text in the Browser (No Upload to sketchy sites)

HR workflow needed plain text from PDFs on a locked-down laptop. pdf.js in a static page — page-by-page extraction with progress bar.

·13 min read·Frontend Tools
pdftext: Extract Resume Text in the Browser (No Upload to sketchy sites) cover

Tested on: pdf.js 4.8.69, Chrome 136 Constraint: No server. Files never leave the machine.

Full extractor

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>PDF Text Extractor</title>
  <style>
    body { font-family: system-ui; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
    textarea { width: 100%; min-height: 240px; font-family: monospace; font-size: 13px; }
    progress { width: 100%; height: 8px; }
    .err { color: #b91c1c; }
  </style>
</head>
<body>
  <h1>PDF Text Extractor</h1>
  <input type="file" id="file" accept="application/pdf">
  <progress id="prog" value="0" max="100" hidden></progress>
  <p id="status"></p>
  <textarea id="out" readonly placeholder="Extracted text"></textarea>
  <button id="copy" disabled>Copy</button>
  <script type="module">
    import * as pdfjsLib from 'https://cdn.jsdelivr.net/npm/pdfjs-dist@4.8.69/build/pdf.min.mjs';
    pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@4.8.69/build/pdf.worker.min.mjs';

    const fileInput = document.getElementById('file');
    const out = document.getElementById('out');
    const status = document.getElementById('status');
    const prog = document.getElementById('prog');
    const copyBtn = document.getElementById('copy');

    fileInput.onchange = async () => {
      const file = fileInput.files[0];
      if (!file) return;
      out.value = '';
      status.textContent = 'Loading...';
      status.className = '';
      prog.hidden = false;
      prog.value = 0;

      try {
        const buf = await file.arrayBuffer();
        const pdf = await pdfjsLib.getDocument({ data: buf }).promise;
        const pages = pdf.numPages;
        let full = '';

        for (let i = 1; i <= pages; i++) {
          const page = await pdf.getPage(i);
          const content = await page.getTextContent();
          const text = content.items.map(it => it.str).join(' ');
          full += '\n--- Page ' + i + ' ---\n' + text + '\n';
          prog.value = Math.round((i / pages) * 100);
          status.textContent = 'Page ' + i + ' of ' + pages;
        }

        out.value = full.trim();
        copyBtn.disabled = false;
        status.textContent = 'Done — ' + pages + ' pages, ' + full.length + ' characters';
      } catch (e) {
        status.textContent = e.message;
        status.className = 'err';
      }
    };

    copyBtn.onclick = () => navigator.clipboard.writeText(out.value);
  </script>
</body>
</html>

Scanned PDF = empty output

Done — 2 pages, 0 characters

pdf.js extracts text layers only. Scanned images need OCR (Tesseract.js) — document that limitation upfront.

Worker CORS on file://

Opening HTML directly fails worker load. Serve via npx serve or deploy to static host.

Password-protected PDFs

PasswordException: No password given

Add optional password field wired to getDocument({ password }) if needed.

Verify

Digital PDF resume → text appears with page markers 20-page doc → progress bar advances Copy button fills clipboard

Tags: PDFpdf.jsVanilla JSPrivacy