Spočítat stránky v 1,4GB naskenovaném archivu by mělo být levné. Zavolejte na ten soubor LoadFromFile a levné to být přestane: HotPDF naparsuje data cross-reference a pro každý z několika set tisíc nepřímých objektů dokumentu sestaví objekt v paměti, a 32bitový worker narazí na strop adresního prostoru 2 GB někde uprostřed tohoto parsování. Operace, kterou jste chtěli, počet stránek, žádný z těchto objektů vůbec nepotřebovala. Potřebovala strom stránek a nic jiného. Tato mezera, mezi tím, co si úloha žádá, a tím, co dodá plné načtení, je celý důvod, proč Direct File API existuje
Direct File API dává Delphi a C++Builderu přístup k PDF na úrovni souboru: počty stránek, kopie, dešifrování, přírůstková připojení, to vše čte z disku jen to, co skutečně potřebuje, místo aby v RAM rekonstruovalo celý model dokumentu. Dovednost spočívá v tom spárovat každou úlohu s nejlehčí úrovní, která ji dokáže vyřešit. Trefíte-li se, služba drží plochou spotřebu paměti bez ohledu na velikost vstupu. Netrefíte-li se, první předimenzovaný soubor worker položí
Co vás stojí plné načtení
LoadFromFile není nepřítel. Svou paměť si zaslouží: jakmile je strom v RAM, máte náhodný přístup ke každé stránce a každému objektu, což je přesně to, co vyžadují InsertPagesFromDocument, MovePage a opětovná serializace přes SaveLoadedDocument. Pro skutečné přestavování neexistuje zkratka; abyste dokument mohli přeuspořádat, musíte jej držet celý
Potíže začínají ve chvíli, kdy velikost vstupu nemáte pod kontrolou. Nahrávky od zákazníků, výstupy skenerů a archivy staré deset let ignorují cokoli, co předpokládal váš testovací korpus. Načítejte každý vstup bezpodmínečně a váš strop paměti určí jediný největší soubor, který kdy někdo pošle. Doba parsování sleduje počet objektů a rezidentní paměť se ustálí na násobku velikosti souboru, jakmile se započtou struktury objektů a dekódované streamy, takže gigabajt na disku může znamenat několik gigabajtů v paměti
Rekompilace pro 64 bitů zvedne strop adresního prostoru, ale účet ponechá nedotčený. Worker stále spálí sekundy CPU a v RAM násobek velikosti souboru, aby zodpověděl otázku, na kterou by struktura samotného souboru mohla odpovědět za milisekundy. Při souběžnosti se matematika obrátí proti vám: čtyři velká načtení běžící najednou sdílí jeden rozpočet paměti a propustnost se propadne přesně ve chvíli, kdy je fronta nejhlubší a vy si to nejméně můžete dovolit
Čtení souboru přes handle
Úroveň jen pro čtení otevře soubor jako handle, zodpoví strukturální otázky o něm a zavře jej. Žádný strom objektů, žádné vykreslování stránek, žádná paměť, která by rostla se vstupem
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;
Tuto úroveň udržují poctivou tři návyky. Zaprvé kontrolujte návratovou hodnotu. Nekladný handle znamená, že otevření selhalo, a vypálit DAGetPageCount na mrtvý handle je typ chyby, která zůstane skrytá až do dne, kdy zákazník pošle poškozený soubor. Zadruhé párujte každé úspěšné otevření s DACloseFile uvnitř bloku finally; služba, která uniká handly, nespadne, jen prohnívá, což je horší. Zatřetí respektujte, co parametr hesla skutečně dělá. DAOpenFileReadOnly jej přijímá, ale u šifrovaných vstupů potichu sklouzne k plnému parsování jen kvůli přečtení počtu stránek, takže záruka ploché paměti se vypaří. Chráněné soubory nejdřív pošlete přes DecryptFile a zbytek pipeline zůstane levný
Stejná sonda zároveň slouží jako třídicí brána. Soubory se objevují chybně označené, nahrané jen z poloviny, nebo přejmenované z úplně jiného formátu, a kontrola DAOpenFileReadOnly to všechno odmítne hned u vstupních dveří během milisekund, s chybou přišpendlenou k viníkovi. Alternativou je nechat odpadní soubor projet hluboko do workeru fronty a nechat jej vybuchnout tam, kde rozmotání, který vstup to způsobil, může stát celé odpoledne
Kopírování, dešifrování a šifrování celých souborů
Druhá úroveň přesouvá a transformuje celé soubory, aniž by kdy odhalila jejich vnitřek. Toto jsou volání, o která se příjmové pipeline opírají nejvíc
// Strukturální kopie: ověření a přesun bez parsování stromu objektů
Status := Pdf.DACopyFile('incoming\statement.pdf', 'verified\statement.pdf');
LogDirectFileStatus('copy', Status);
// Dešifrování během kopírování: cesta Direct File k chráněným vstupům
Status := Pdf.DecryptFile('incoming\protected.pdf',
'verified\plain.pdf', 'batch-password');
LogDirectFileStatus('decrypt-copy', Status);
// Šifrování během kopírování: ochrana výstupu bez plného načtení
Status := Pdf.EncryptFile('verified\statement.pdf',
'outbound\statement.pdf', 'owner-secret', '', aes256, [prPrint]);
LogDirectFileStatus('encrypt-copy', Status);
Každé volání si svoje místo zaslouží. DACopyFile je ověřená kopie z karanténního adresáře do spravovaného úložiště: cestou otevírá a indexuje strukturu PDF, takže useknutý nebo ne-PDF vstup selže rovnou tady, ne o tři fáze dál po proudu. DecryptFile zapisuje dešifrovanou kopii po přímé přepisovací cestě AES-256, která se vyhýbá stromu objektů, kdykoli to vstup dovolí, tedy velkosouborový protějšek k toku dešifrování load-and-resave popsanému v článku o šifrování AES-256. EncryptFile běží stejný pohyb obráceně a při kopírování na úrovni souboru aplikuje ochranu heslem s parametry typu klíče a oprávnění, které už používá cesta v paměti
Připojování změn místo přepisování
Přírůstková aktualizace, definovaná v ISO 32000-1 §7.5.6, je třetí úroveň. Původní bajty zůstávají na disku tam, kde jsou, a jakékoli nové nebo změněné objekty se připojí za ně, následované čerstvou sekcí cross-reference, která se zřetězí zpět do originálu. U 900MB archivu, kterému se má přidat jediná stránka, je cenou za zápis jen delta, ne celý soubor
// Připojí auditní stránku k velkému archivu bez jeho přepsání
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'); // původní bajty + delta
Zde záleží na dvou bodech disciplíny. BeginIncrementalUpdate musí ukazovat na původní soubor, protože připojená data cross-reference se zpětně zřetězují na offsety bajtů uvnitř něj. A model je záměrně append-only: každé přírůstkové uložení soubor zvětší, nikdy jej nezmenší. Dokument orazítkovávaný každou noc poroste bez omezení, dokud jej periodická opětovná serializace, tedy načtení a zápis zpět přes SaveLoadedDocument, nezhutní. Stejná append-only povaha je tím, co dělá z přírůstkové aktualizace jediný bezpečný způsob, jak se dotknout digitálně podepsaného dokumentu, což je omezení rozebrané v článku o digitálních podpisech a PAdES. Mechanismus cross-reference, který za tím stojí, dostává vlastní zpracování v článku o object streamech a přírůstkových aktualizacích
V append-only uloženích je past, která proklouzne kolem většiny revizí. Původní bajty zůstávají v souboru, čitelné pro každého, kdo je ochotný se podívat. Přírůstková aktualizace, která stránku „nahradí", tu starou nesmaže; jen ji v aktuální revizi překryje, zatímco předchozí revize tam zůstává, plně obnovitelná. Přírůstkové aktualizace jsou proto špatný nástroj na odstraňování citlivého obsahu. Abyste opravdu zahodili historii, kterou by příjemce neměl nikdy vidět, potřebujete plnou opětovnou serializaci: LoadFromFile následované SaveLoadedDocument, které zapíše jen aktuální stav a pohřbené revize nechá za sebou
Přiřazení úrovně k operaci
Logika výběru je dost krátká na to, aby se vešla do hlavy, a vyplatí se ji zakódovat jako explicitní směrovací rozhodnutí na začátku pipeline, místo aby si každá úloha improvizovala vlastní cestu. Úroveň určuje operace, kterou potřebujete:
- Počítání, kontrola nebo klasifikace otevře handle:
DAOpenFileReadOnly,DAGetPageCount,DACloseFile - Přesun, dešifrování nebo šifrování celého souboru zůstává na úrovni souboru s
DACopyFile,DecryptFileneboEncryptFile - Přestavování stránek nebo slučování dokumentů potřebuje plné načtení:
LoadFromFile, pakInsertPagesFromDocumentneboMovePage, pakSaveLoadedDocument - Přidání malé delty k obřímu nebo podepsanému souboru volá
BeginIncrementalUpdatea uloží
Smíšené pipeline udělají dobře, když před cestu plného načtení postaví práh podle velikosti. Cokoli přes pár set megabajtů posílejte přes úrovně Direct File a plné načtení si rezervujte pro skutečné přestavování na 64bitovém workeru se skutečným rozpočtem paměti. Práh promění pád kvůli nedostatku paměti na směrovací rozhodnutí, které vidíte a můžete ladit
Ať už úlohu obsluhuje kterákoli úroveň, zapisujte její výstup pod dočasným názvem a přejmenujte jej na místo teprve poté, co výsledek projde ověřením. Napůl zapsaný soubor sedící pod finálním názvem vypadá pro další fázi pipeline úplně stejně jako dobrý, a volání Direct File dělají kontrolu levnou: ověření výstupu je jednořádková sonda pomocí handle
Direct File API je součástí HotPDF Delphi Component pro Delphi a C++Builder. Produktová stránka odkazuje na úplnou referenci funkcí, včetně zde ukázaných volání pro přírůstkovou aktualizaci