| 649 | }; |
| 650 | |
| 651 | const getObject = (ref: PdfRef): PdfObject | null => { |
| 652 | const key = `${ref.objectNumber} ${ref.generation}`; |
| 653 | |
| 654 | // Check cache |
| 655 | if (cache.has(key)) { |
| 656 | return cache.get(key) ?? null; |
| 657 | } |
| 658 | |
| 659 | // Look up in xref |
| 660 | const entry = xref.get(ref.objectNumber); |
| 661 | |
| 662 | if (!entry) { |
| 663 | return null; |
| 664 | } |
| 665 | |
| 666 | let obj: PdfObject | null = null; |
| 667 | |
| 668 | try { |
| 669 | switch (entry.type) { |
| 670 | case "free": |
| 671 | return null; |
| 672 | |
| 673 | case "uncompressed": { |
| 674 | const parser = new IndirectObjectParser(this.scanner, lengthResolver, recoveryOptions); |
| 675 | |
| 676 | const result = parser.parseObjectAt(entry.offset); |
| 677 | |
| 678 | // Verify generation matches |
| 679 | if (result.genNum !== ref.generation) { |
| 680 | this.warnings.push( |
| 681 | `Generation mismatch for object ${ref.objectNumber}: expected ${ref.generation}, got ${result.genNum}`, |
| 682 | ); |
| 683 | } |
| 684 | |
| 685 | obj = result.value; |
| 686 | |
| 687 | // Decrypt the object |
| 688 | if (securityHandler?.isAuthenticated) { |
| 689 | obj = decryptObject(obj, ref.objectNumber, ref.generation); |
| 690 | } |
| 691 | |
| 692 | break; |
| 693 | } |
| 694 | |
| 695 | case "compressed": { |
| 696 | // Get or create object stream parser |
| 697 | let streamParser = objectStreamCache.get(entry.streamObjNum); |
| 698 | |
| 699 | if (!streamParser) { |
| 700 | // Load the object stream |
| 701 | const streamRef = PdfRef.of(entry.streamObjNum, 0); |
| 702 | const streamObj = getObject(streamRef); |
| 703 | |
| 704 | if (!streamObj || !(streamObj instanceof PdfStream)) { |
| 705 | this.warnings.push(`Object stream ${entry.streamObjNum} not found or invalid`); |
| 706 | |
| 707 | return null; |
| 708 | } |