// Minimal PDF text dumper: inflates FlateDecode streams and prints text from Tj/TJ operators. const fs = require('fs'); const zlib = require('zlib'); const buf = fs.readFileSync(process.argv[2]); const s = buf.toString('latin1'); const re = /stream\r?\n/g; let m; const out = []; while ((m = re.exec(s))) { const start = m.index + m[0].length; const end = s.indexOf('endstream', start); if (end < 0) break; const chunk = buf.subarray(start, end); let txt; try { txt = zlib.inflateSync(chunk).toString('latin1'); } catch { try { txt = zlib.inflateRawSync(chunk.subarray(2)).toString('latin1'); } catch { continue; } } if (!/BT/.test(txt)) continue; const lines = []; let cur = ''; const tokRe = /\((?:\\.|[^\\)])*\)|\[[^\]]*\]\s*TJ|T\*|Td|TD|Tm|ET|'/g; let t; while ((t = tokRe.exec(txt))) { const k = t[0]; if (k.startsWith('(')) cur += k.slice(1, -1).replace(/\\([()\\])/g, '$1'); else if (k.startsWith('[')) { const parts = k.match(/\((?:\\.|[^\\)])*\)|-?\d+\.?\d*/g) || []; for (const p of parts) { if (p.startsWith('(')) cur += p.slice(1, -1).replace(/\\([()\\])/g, '$1'); else if (Number(p) < -200) cur += ' '; } } else { if (cur.trim()) lines.push(cur); cur = ''; } } if (cur.trim()) lines.push(cur); out.push(lines.join('\n')); } process.stdout.write(out.join('\n----\n'));