Technical Article

Spracovanie veľkých súborov PDF v Delphi pomocou HotPDF Direct File API

Spočítanie stránok v 1,4 GB naskenovanom archíve by malo byť nenáročné. Zavolajte však na tento súbor metódu LoadFromFile a náročnosť prudko stúpne: HotPDF spracuje údaje o krížových odkazoch a vytvorí v pamäti objekt pre každý z niekoľkých stoviek tisíc nepriamych objektov dokumentu. Tým 32-bitový proces narazí na 2 GB limit adresného priestoru niekde uprostred spracovania. Požadovaná operácia – zistenie počtu strán – pritom vôbec nepotrebovala žiadny z týchto objektov. Vyžadovala iba strom stránok (page tree) a nič iné. Tento rozdiel medzi tým, čo operácia skutočne potrebuje, a tým, čo prináša úplné načítanie, je hlavným dôvodom existencie rozhrania Direct File API.

Direct File API poskytuje vývojárom v Delphi a C++Builder prístup k PDF na úrovni súborov: počítanie stránok, kopírovanie, dešifrovanie či prírastkové pridávanie (incremental appends). Všetky tieto funkcie čítajú z disku iba to, čo skutočne potrebujú, namiesto rekonštrukcie celého modelu dokumentu v RAM. Dôležité je priradiť každej úlohe tú najúspornejšiu úroveň spracovania, ktorá dokáže dať odpoveď. Ak to urobíte správne, služba si zachová stabilnú spotrebu pamäte bez ohľadu na veľkosť vstupu. Ak sa pomýlite, prvý príliš veľký súbor spôsobí pád procesu.

Čo vás stojí úplné načítanie

Metóda LoadFromFile nie je nepriateľom. Svoju pamäť si obháji: akonáhle je strom v RAM, máte náhodný prístup ku každej stránke a objektu, čo je presne to, čo vyžadujú operácie ako InsertPagesFromDocument, MovePage a opätovná serializácia cez SaveLoadedDocument. Pre skutočnú reštrukturalizáciu neexistuje skratka; ak chcete dokument preskupiť, musíte ho mať načítaný v pamäti.

Problémy nastávajú, keď nemáte kontrolu nad veľkosťou vstupných súborov. Nahrané súbory od zákazníkov, výstupy zo skenerov a desaťročné archívy nerešpektujú predpoklady vašej testovacej sady. Ak budete bezpodmienečne načítavať každý vstup, váš pamäťový limit bude určený tým najväčším súborom, aký kedy kto odošle. Čas analýzy závisí od počtu objektov a výsledná spotreba pamäte po započítaní objektových štruktúr a dekódovaných prúdov dosiahne niekoľkonásobok veľkosti súboru. Jeden gigabajt na disku tak môže v pamäti znamenať hneď niekoľko gigabajtov.

Prekompilovanie na 64-bitový systém odstráni limit adresného priestoru, ale nezníži nároky na zdroje. Proces stále spotrebúva cenné sekundy procesora a niekoľkonásobok veľkosti súboru v pamäti RAM len na to, aby odpovedal na otázku, ktorú štruktúra súboru mohla vyriešiť za milisekundy. Pri súbežnom spracovaní (concurrency) sa situácia ešte zhoršuje: štyri veľké načítania bežiace naraz zdieľajú rovnaký pamäťový limit, čo dramaticky znižuje priepustnosť systému práve vtedy, keď je front najdlhší a výpadok si nemôžete dovoliť.

Čítanie súboru cez handle

Úroveň určená len na čítanie otvorí súbor ako handle, odpovie na štrukturálne otázky a zatvorí ho. Bez vytvárania objektového stromu, bez vykresľovania stránok a bez pamäte, ktorá rastie spolu so vstupom.

var
  Pdf: THotPDF;
  Handle, PageCount: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Handle := Pdf.DAOpenFileReadOnly('archive-2026-06.pdf', '');
    if Handle > 0 then
    try
      PageCount := Pdf.DAGetPageCount(Handle);
      RouteByPageCount('archive-2026-06.pdf', PageCount);
    finally
      Pdf.DACloseFile(Handle);
    end;
  finally
    Pdf.Free;
  end;
end;

Tri návyky pomáhajú udržať túto úroveň spoľahlivú. Po prvé, vždy kontrolujte návratovú hodnotu. Nekladný handle znamená, že otvorenie zlyhalo. Volanie DAGetPageCount na neplatný handle je chyba, ktorá môže ostať skrytá až do dňa, kedy zákazník pošle poškodený súbor. Po druhé, každé úspešné otvorenie spárujte s DACloseFile v bloku finally. Služba, ktorej unikajú handle, síce hneď nespadne, ale postupne prestane fungovať, čo je ešte horšie. Po tretie, dajte pozor na parameter hesla. DAOpenFileReadOnly ho síce prijíma, ale pri šifrovaných vstupoch potichu prejde na plnú analýzu súboru, aby načítal počet stránok, čím sa záruka nízkej spotreby pamäte stráca. Smerujte chránené súbory najskôr cez DecryptFile a zvyšok spracovania zostane pamäťovo nenáročný.

Rovnaká kontrola slúži aj ako filter chýb (triage gate). Súbory môžu prichádzať s nesprávnymi príponami, nedokončeným nahrávaním alebo premenované z úplne iných formátov. Overenie cez DAOpenFileReadOnly ich okamžite odmietne v priebehu milisekúnd a priradí chybu k danému súboru. Alternatívou by bolo pustiť poškodený súbor hlboko do spracovateľského frontu, kde by spôsobil haváriu, ktorej analýza by vás stála pol dňa.

Kopírovanie, dešifrovanie a šifrovanie celých súborov

Druhá úroveň presúva a transformuje kompletné súbory bez toho, aby odkrývala ich vnútornú štruktúru. Tieto volania sa najčastejšie využívajú v spracovateľských reťazcoch.

// Structural copy: validate-and-move without parsing the object tree
Status := Pdf.DACopyFile('incoming\statement.pdf', 'verified\statement.pdf');
LogDirectFileStatus('copy', Status);

// Decrypt while copying: the Direct File route into protected inputs
Status := Pdf.DecryptFile('incoming\protected.pdf',
  'verified\plain.pdf', 'batch-password');
LogDirectFileStatus('decrypt-copy', Status);

// Encrypt while copying: protect an output without a full load
Status := Pdf.EncryptFile('verified\statement.pdf',
  'outbound\statement.pdf', 'owner-secret', '', aes256, [prPrint]);
LogDirectFileStatus('encrypt-copy', Status);

Každé volanie má svoj význam. DACopyFile vykonáva overené kopírovanie z karanténneho adresára do spravovaného úložiska: počas prenosu otvára a indexuje štruktúru PDF, takže poškodený alebo iný ako PDF vstup zlyhá okamžite, nie až o tri kroky ďalej. DecryptFile zapisuje dešifrovanú kópiu prostredníctvom priamej prepisovacej cesty AES-256, ktorá obchádza objektový strom, ak to vstup dovolí. Ide o alternatívu pre veľké súbory k dešifrovaniu spôsobom načítaj-a-ulož, o ktorom píšeme v článku o šifrovaní AES-256. EncryptFile funguje opačne – aplikuje ochranu heslom počas kopírovania na úrovni súborov s rovnakými parametrami typu kľúča a prístupových práv, aké používa cesta v pamäti.

Pridávanie zmien namiesto prepisovania

Prírastková aktualizácia (incremental update), definovaná v norme ISO 32000-1 §7.5.6, predstavuje tretiu úroveň. Pôvodné bajty zostávajú na disku nezmenené. Akékoľvek nové alebo upravené objekty sa pripoja za ne a nasleduje nová sekcia krížových odkazov, ktorá sa odkazuje na tie pôvodné. Pre 900 MB archív, do ktorého potrebujete pridať jednu stránku, predstavuje náklad na zápis iba táto zmena (delta), nie celý súbor.

// Append an audit page to a large archive without rewriting it
Pdf.BeginIncrementalUpdate('archive-2026-06.pdf');
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Processed by intake service 2026-06-11');
Pdf.SaveIncrementalUpdate('archive-2026-06-stamped.pdf');  // original bytes + delta

Tu sú dôležité dva zásadné body. BeginIncrementalUpdate musí odkazovať na pôvodný súbor, pretože pripojené údaje krížových odkazov smerujú na bajtové posuny vo vnútri tohto súboru. Tento model funguje na princípe pridávania iba na koniec (append-only): každé prírastkové uloženie súbor zväčší, nikdy ho nezmenší. Dokument, do ktorého sa každú noc zapisujú údaje, bude rásť bez obmedzenia, kým ho periodická opätovná serializácia (načítanie a uloženie cez SaveLoadedDocument) neskomprimuje. Práve táto vlastnosť pridávania na koniec robí z prírastkových aktualizácií jediný bezpečný spôsob, ako upraviť digitálne podpísaný dokument, čo podrobnejšie rozoberáme v článku o digitálnych podpisoch a PAdES. Podkladové mechanizmy krížových odkazov sú popísané v článku o prúdoch objektov a prírastkových aktualizáciách.

V zápisoch typu append-only sa skrýva pasca, ktorá často uniká pozornosti. Pôvodné bajty zostávajú v súbore čitateľné pre kohokoľvek, kto sa doň pozrie. Prírastková aktualizácia, ktorá „nahradí“ stránku, tú pôvodnú neodstráni. Iba ju nahradí v aktuálnej revízii, pričom predchádzajúca revízia zostáva plne obnoviteľná. Prírastkové aktualizácie sú preto nevhodným nástrojom na odstraňovanie citlivého obsahu. Ak chcete natrvalo vymazať históriu, ktorú príjemca nemá vidieť, musíte vykonať plnú serializáciu: LoadFromFile a následne SaveLoadedDocument, čo zapíše iba aktuálny stav a vynechá predchádzajúce revízie.

Priradenie úrovne k operácii

Rozhodovacia logika je dostatočne jednoduchá na to, aby ste ju udržali v hlave. Oplatí sa ju implementovať ako explicitné smerovanie na začiatku spracovateľského reťazca, než nechať každú úlohu hľadať vlastnú cestu. Požadovaná operácia určuje úroveň:

  • Počítanie, kontrola alebo klasifikácia otvára handle: DAOpenFileReadOnly, DAGetPageCount, DACloseFile.
  • Presun, dešifrovanie alebo šifrovanie celého súboru prebieha na úrovni súborov cez DACopyFile, DecryptFile alebo EncryptFile.
  • Reštrukturalizácia stránok alebo zlučovanie dokumentov vyžaduje úplné načítanie: LoadFromFile, potom InsertPagesFromDocument alebo MovePage a nakoniec SaveLoadedDocument.
  • Pridanie malej zmeny do veľkého alebo podpísaného súboru využíva BeginIncrementalUpdate a ukladanie.

V kombinovaných reťazcoch je vhodné nastaviť limit veľkosti pred spustením plného načítania. Čokoľvek nad niekoľko stoviek megabajtov posielajte cez Direct File úrovne a plné načítanie vyhraďte pre reštrukturalizáciu v 64-bitovom prostredí s dostatočnou pamäťou. Tento limit premení riziko pádu z dôvodu nedostatku pamäte na kontrolovateľné rozhodnutie o smerovaní.

Bez ohľadu na použitú úroveň ukladajte výstup pod dočasným názvom a premenujte ho až po úspešnom overení. Nedokončený súbor pod finálnym názvom by ďalší krok spracovania považoval za platný. Volania Direct File navyše robia toto overenie nenáročným – kontrola výstupu predstavuje len jeden riadok s otvorením handlu.

Rozhranie Direct File API sa dodáva ako súčasť komponentu HotPDF Component pre Delphi a C++Builder. Stránka produktu obsahuje odkaz na kompletnú referenčnú príručku vrátane tu zobrazených volaní prírastkových aktualizácií.