UNPKG

pdf.js-extract

Version:

super-simple async PDF reader that extracts text with x,y page positions based on pdf.js

177 lines (161 loc) 4.11 kB
import { Buffer } from 'node:buffer'; export function toBase64(data) { return data ? Buffer.from(data).toString('base64') : undefined; } export function xStats(page) { const x = {}; page.content.forEach(item => { const xx = item.x.toFixed(0); x[xx] = (x[xx] || 0) + 1; }); return Object.keys(x) .map(key => ({ x: key, val: x[key] })) .filter(o => o.val > 1) .sort((a, b) => a.x - b.x); } export function lineStartWithStrings(line, strings) { if (line.length < strings.length) return false; for (let i = 0; i < strings.length; i++) { if (line[i].str.indexOf(strings[i]) !== 0) return false; } return true; } export function extractTextRows(lines) { return lines.map(line => line.map(cell => { if (!cell) return null; return cell.str; }) ); } export function extractColumnRows(lines, columns, maxdiff) { const colLines = extractColumnLines(lines, columns, maxdiff); return extractTextRows(colLines); } export function extractColumnLines(lines, columns, maxdiff) { const getCol = x => { let col = 0; for (let i = columns.length - 1; i >= 0; i--) { if (x < columns[i]) col = i; } return col; }; return lines.map(line => { const row = []; line.forEach((cell, j) => { const x = cell.x; const col = getCol(x); if (row[col]) { const before = line[j - 1]; if (!before) { return; } const diff = cell.x - (before.x + before.width); if (diff < maxdiff) { cell.str = row[col].str + cell.str; row[col].merged = true; row[col].str = ""; } else { cell.str = `${row[col].str}\n${cell.str}`; } } while (row.length <= col) { row.push(null); } row[col] = cell; }); return row; }); } export function extractLines(lines, startStrings, endStrings) { let includeLine = -1; return lines.filter(line => { if (line.length === 0) return false; if (includeLine === -1) { if (lineStartWithStrings(line, startStrings)) { includeLine = 1; } } else if (includeLine > -1) { if (lineStartWithStrings(line, endStrings)) { includeLine = -1; } else { includeLine++; } } return includeLine > 0; }); } export function pageToLines(page, maxDiff) { const collector = {}; page.content.forEach(text => { collector[text.y] = collector[text.y] || []; collector[text.y].push(text); }); const list = Object.keys(collector) .map(key => ({ y: key, items: collector[key] })) .sort((a, b) => a.y - b.y); if (maxDiff > 0) { for (let i = list.length - 1; i > 0; i--) { const r1 = list[i - 1]; const r2 = list[i]; const diff = r2.y - r1.y; if (diff < maxDiff) { r1.items = r1.items.concat(r2.items); r2.items = []; } } } list.forEach(item => { item.items = item.items.sort((a, b) => a.x - b.x); }); return list .filter(item => item.items.length > 0) .map(item => item.items); } export function extractAllPagesTextRows(pages, maxDiff) { return pages.map(page => { const lines = pageToLines(page, maxDiff); return extractTextRows(lines); }); } function isNumericKeyObject(obj) { if (typeof obj !== 'object' || obj === null) return false; const keys = Object.keys(obj); if (keys.length === 0) return false; const numKeys = keys.map(k => { const num = Number(k); return Number.isInteger(num) && num >= 0 ? num : null; }); if (numKeys.some(k => k === null)) return false; numKeys.sort((a, b) => a - b); for (let i = 0; i < numKeys.length; i++) { if (numKeys[i] !== i) return false; } return true; } export function compactObj(obj) { if (obj === null || obj === undefined) { return undefined; } if (isNumericKeyObject(obj)) { return Object.values(obj); } return Object.fromEntries(Object.entries(obj) .filter(([, v]) => v != null) .map(([k, v]) => { if (ArrayBuffer.isView(v)) { return [k, Array.from(v)]; } else if (Array.isArray(v)) { return [k, v.map(item => { if (typeof item === 'object' && item !== null) { return compactObj(item); } return item; })]; } else if (typeof v === 'object') { return [k, compactObj(v)]; } else { return [k, v]; } })); }