Technisch Artikel

Niet-standaard PDF's decoderen: Bestanden zonder een /Pages-dictionary

De PDF-specificatie schrijft een strikte hiërarchie voor. Aan de basis bevindt zich de /Catalog dictionary, de root van het document, die een verplichte /Pages entry moet bevatten. Deze entry wijst naar de dictionary van het paginaboom (page tree) knooppunt, die op zijn beurt pagina-knooppunten /Kids bijhoudt en de totale /Count vaststelt. Wanneer een PDF-renderer een bestand opent, begint deze zoektocht exact bij die pointer. Wat gebeurt er als deze ontbreekt?

PDF object tree showing a missing or unlinked /Pages dictionary rendering it orphaned from the /Catalog
Een correcte PDF-paginaboom is geworteld in de /Pages-dictionary; wanneer de verwijzing ontbreekt of het knooppunt leeg is, falen renderers en bibliotheken te openen of werpen ze count-fouten.

De anatomie van een ontbrekende Pages Dictionary

Een PDF-bestand dat geen Pages-dictionary in zijn Catalog bevat, is volgens de norm onjuist gevormd (malformed). De Adobe Acrobat ISO 32000-2:2020-standaard eist zijn aanwezigheid. Systemen voor documentgeneratie die intern worden gebouwd, oude geautomatiseerde scanner-OCR-engines, of gefragmenteerde en halfvolle downloads vanaf servers resulteren soms in deze defecte documenten. Je Delphi-applicatie gooit een uitzondering tijdens LoadFromFile of rapporteert het totale pagina-aantal als 0 of geeft een Acces Violation in extreme render-edge-cases

// Fouten zoals deze verschijnen bij het laden van onjuist gevormde structuren
try
  Pdf.LoadFromFile('broken_scan.pdf'); // Werpt EPDFInvalidStructure Exception
  ShowMessage(IntToStr(Pdf.PageCount));
except
  on E: Exception do
    ShowMessage('Failed: ' + E.Message); // "Object /Pages not found in /Catalog"
end;

Om te begrijpen hoe dit kapot is gegaan, moet je bedenken hoe de structuur op de schijf in tekst is weergegeven:

%PDF-1.4
1 0 obj
<< /Type /Catalog
   /Pages 2 0 R % <--- Deze lijn ontbreekt in kapotte bestanden
>>
endobj
2 0 obj
<< /Type /Pages
   /Count 1
   /Kids [3 0 R]
>>
endobj

Fallback en herstelstrategieën

Bij het repareren van een onjuist gevormd document kun je de renderer (zoals Pdfium of Ghostscript) opdracht geven of je eigen code dwingen om af te wijken van het standaard parseren in een cross-reference (XRef) sweep scan. In plaats van de xref tabel strikt top-down te doorlopen (van de Trailer naar de Catalog, naar Pages, naar objecten), forceer je de library om lineair alle gedefinieerde objecten te scannen. Wanneer je lineair scant, zoek je naar elk object in het bestand met een /Type /Page of /Type /Pages eigenschap. Als je individuele pagina-objecten in wees-dictionaries identificeert, kun je virtueel de vereiste /Pages root en een XRef table array reconstrueren en aan de PDF hechten. Pdfium Component heeft een robuuste verwerkingslogica om bestanden met weesknooppunten veilig weer te geven in dit sweep scan paradigma, het verwerpt dergelijke ontbrekende dictionary pointers om op brute kracht (brute-force) op paginatypes te testen

HotPDF inzetten voor Document Reparsing

De HotPDF Delphi component implementeert fouttolerantie wanneer de index faalt en XRef reparatie is ingeschakeld, via een methodologie waarbij fouten in bestanden via zijn interne object parser object dictionary sweep worden opgespoord en opgeruimd tijdens een simpele laad/bewaar-routine (Load and Save)

var
  RecoverPdf: THotPDF;
begin
  RecoverPdf := THotPDF.Create(nil);
  try
    // HotPDF zal proberen object strings lineair te parseren
    RecoverPdf.LoadFromFile('broken_scan.pdf');
    // Opslaan schrijft de correcte ISO /Pages en Trailer dictionarary hiërarchieën
    RecoverPdf.SaveToFile('fixed_scan.pdf');
  finally
    RecoverPdf.Free;
  end;
end;

De interne fouttolerantie herstelt de bestandsbeschadiging zolang de fysieke byte-stroom en byte-counts voor de daadwerkelijke contentstreams niet radicaal versleuteld of onvolledig getrimd zijn in on-disk schijfcorruptie

Wat als de pagina-objecten echt verdwenen zijn?

Af en toe is de referentie niet weggelaten, maar werd deze per ongeluk afgekapt (truncated). De download liep vast of de file stream werd niet geflushed en vrijgegeven door de creatie library (wat het typische einde van een generatieronde weglaat). In dergelijke bestanden vind je in wezen bytes van objecten met compressiestreams (meestal FlateDecode). Je PDF is dan onherstelbaar zonder deep-layer byte extraction van fragmenten (text of images), een handmatige dissectie en redding in string logica voor stream decompressie in zlib die ver boven de betrouwbare codeerbudgetten ligt