* Helper to create an XRef stream with given entries. * Uses uncompressed stream for testing (no Filter).
(
entries: Array<{ objNum: number; type: 0 | 1 | 2; field2: number; field3: number }>,
options: { w?: [number, number, number]; index?: number[] } = {},
)
| 435 | * Uses uncompressed stream for testing (no Filter). |
| 436 | */ |
| 437 | function createXRefStream( |
| 438 | entries: Array<{ objNum: number; type: 0 | 1 | 2; field2: number; field3: number }>, |
| 439 | options: { w?: [number, number, number]; index?: number[] } = {}, |
| 440 | ): Uint8Array { |
| 441 | const w = options.w ?? [1, 3, 2]; // Default: 1 byte type, 3 byte offset, 2 byte gen |
| 442 | const [w1, w2, w3] = w; |
| 443 | |
| 444 | // Build index array from entries if not provided |
| 445 | let indexArray = options.index; |
| 446 | if (!indexArray) { |
| 447 | // Simple case: contiguous from min to max |
| 448 | const objNums = entries.map(e => e.objNum).sort((a, b) => a - b); |
| 449 | const first = objNums[0]; |
| 450 | const count = objNums[objNums.length - 1] - first + 1; |
| 451 | indexArray = [first, count]; |
| 452 | } |
| 453 | |
| 454 | // Build binary data |
| 455 | const data: number[] = []; |
| 456 | for (const entry of entries) { |
| 457 | // Type field |
| 458 | const type = entry.type; |
| 459 | for (let i = w1 - 1; i >= 0; i--) { |
| 460 | data.push((type >> (i * 8)) & 0xff); |
| 461 | } |
| 462 | // Field 2 |
| 463 | const f2 = entry.field2; |
| 464 | for (let i = w2 - 1; i >= 0; i--) { |
| 465 | data.push((f2 >> (i * 8)) & 0xff); |
| 466 | } |
| 467 | // Field 3 |
| 468 | const f3 = entry.field3; |
| 469 | for (let i = w3 - 1; i >= 0; i--) { |
| 470 | data.push((f3 >> (i * 8)) & 0xff); |
| 471 | } |
| 472 | } |
| 473 | |
| 474 | const streamData = new Uint8Array(data); |
| 475 | |
| 476 | // Build the object |
| 477 | const size = Math.max(...entries.map(e => e.objNum)) + 1; |
| 478 | const dictStr = `<< /Type /XRef /Size ${size} /W [${w.join(" ")}] /Index [${indexArray.join(" ")}] /Length ${streamData.length} /Root 1 0 R >>`; |
| 479 | |
| 480 | // Create full indirect object |
| 481 | const header = new TextEncoder().encode(`99 0 obj\n${dictStr}\nstream\n`); |
| 482 | const footer = new TextEncoder().encode(`\nendstream\nendobj\n`); |
| 483 | |
| 484 | const result = new Uint8Array(header.length + streamData.length + footer.length); |
| 485 | result.set(header, 0); |
| 486 | result.set(streamData, header.length); |
| 487 | result.set(footer, header.length + streamData.length); |
| 488 | |
| 489 | return result; |
| 490 | } |
| 491 | |
| 492 | it("parses xref stream with uncompressed entries", async () => { |
| 493 | const bytes = createXRefStream([ |
no test coverage detected