* Parse a PDF into its top-level objects. We do this with regex-style byte * scanning rather than a full parser because we only need object boundaries * and stream offsets — not type structure.
(data: Buffer)
| 54 | * and stream offsets — not type structure. |
| 55 | */ |
| 56 | function parseObjects(data: Buffer): PdfObject[] { |
| 57 | const objects: PdfObject[] = []; |
| 58 | const text = data.toString('latin1'); // PDF tokens are latin1-safe; binary stays in Buffer |
| 59 | const objRe = /(\d+)\s+(\d+)\s+obj\b/g; |
| 60 | let m: RegExpExecArray | null; |
| 61 | while ((m = objRe.exec(text))) { |
| 62 | const num = parseInt(m[1]!, 10); |
| 63 | const gen = parseInt(m[2]!, 10); |
| 64 | const startIdx = m.index; |
| 65 | const endIdx = text.indexOf('endobj', startIdx); |
| 66 | if (endIdx === -1) continue; |
| 67 | const body = text.slice(startIdx + m[0].length, endIdx); |
| 68 | // dict between << ... >> (greedy, may be nested — we don't care, only need text ops) |
| 69 | const dictMatch = body.match(/<<([\s\S]*?)>>/); |
| 70 | const dict = dictMatch ? dictMatch[1] ?? '' : ''; |
| 71 | // stream marker |
| 72 | const streamMarker = body.indexOf('stream'); |
| 73 | let streamStart: number | undefined; |
| 74 | let streamEnd: number | undefined; |
| 75 | if (streamMarker !== -1) { |
| 76 | // The actual stream starts after 'stream' + EOL (CRLF or LF) |
| 77 | let absStart = startIdx + m[0].length + streamMarker + 'stream'.length; |
| 78 | if (data[absStart] === 0x0D && data[absStart + 1] === 0x0A) absStart += 2; |
| 79 | else if (data[absStart] === 0x0A) absStart += 1; |
| 80 | const endStreamMarker = text.indexOf('endstream', streamMarker); |
| 81 | let absEnd = startIdx + m[0].length + endStreamMarker; |
| 82 | // Trim trailing EOL |
| 83 | if (data[absEnd - 1] === 0x0A) absEnd -= 1; |
| 84 | if (data[absEnd - 1] === 0x0D) absEnd -= 1; |
| 85 | streamStart = absStart; |
| 86 | streamEnd = absEnd; |
| 87 | } |
| 88 | objects.push({ |
| 89 | num, |
| 90 | gen, |
| 91 | raw: data.subarray(startIdx, endIdx + 6), |
| 92 | dict, |
| 93 | streamStart, |
| 94 | streamEnd, |
| 95 | }); |
| 96 | } |
| 97 | return objects; |
| 98 | } |
| 99 | |
| 100 | /** Decode a stream based on its /Filter declarations. */ |
| 101 | async function decodeStream(data: Buffer, dict: string): Promise<Buffer | null> { |