Technisch artikel

Grote PDF's verwerken in Delphi met HotPDF Direct File API

Het aantal pagina's tellen in een gescand archief van 1,4 GB zou goedkoop moeten zijn. Roep LoadFromFile aan op dat bestand en het stopt goedkoop te zijn: HotPDF parseert de cross-reference-data en bouwt een in-memory object voor elk van de enkele honderdduizenden indirecte objecten van het document, en een 32-bit worker raakt het 2 GB-adresruimte-plafond ergens midden in die parse. De bewerking die u wilde, het pagina-aantal, had none van die objecten nodig. Het had de page-tree nodig en niets anders. Die kloof, tussen wat een klus vraagt en wat een volledige load levert, is de hele reden dat de Direct File API bestaat

De Direct File API geeft Delphi en C++Builder toegang tot een PDF op bestandsniveau: pagina-aantallen, kopieën, ontsleuteling, incrementele appends, allemaal lezend van schijf wat ze werkelijk nodig hebben in plaats van het hele documentmodel in RAM te reconstrueren. De vaardigheid is elke klus te matchen aan de lichtste tier die hem kan beantwoorden. Krijg die match goed en een service houdt vlak geheugen over elke inputgrootte heen. Krijg hem verkeerd en het eerste oversized bestand neemt de worker neer

Taakrouteringsdiagram voor de HotPDF Direct File API in Delphi: alleen-lezen handle-probes, kopiëren en versleutelen van het hele bestand, incrementele toevoegingen, en volledige in-memory loads gerangschikt op geheugenprofiel
Koppel elke bewerking aan de lichtste laag die haar kan beantwoorden, en houd het residente geheugen vlak ongeacht de invoergrootte

Wat een volledige load u kost

LoadFromFile is niet de vijand. Het verdient zijn geheugen: zodra de structuur in RAM staat heeft u willekeurige toegang tot elke pagina en elk object, wat precies is wat InsertPagesFromDocument, MovePage en reserialisatie via SaveLoadedDocument vereisen. Er is geen snelkoppeling voor echte herstructurering; u moet het document vasthouden om het te herschikken

De problemen beginnen wanneer inputgrootten niet van u zijn om te beheersen. Klant-uploads, scanner-output en archieven van tien jaar geleden negeren wat uw test-corpus ook maar aannam. Laad elke input onvoorwaardelijk en uw geheugenplafond wordt bepaald door het ene grootste bestand dat iemand ooit zal indienen. Parse-tijd volgt het objectaantal, en resident geheugen stabiliseert op een veelvoud van de bestandsgrootte nadat objectstructuren en gedecodeerde streams zijn meegeteld, dus een gigabyte op schijf kan meerdere gigabytes resident betekenen

Hercompileren voor 64-bit tilt het adresruimte-plafond maar laat de rekening intact. De worker verbrandt nog steeds seconden CPU en een veelvoud van het bestand in RAM om een vraag te beantwoorden die de eigen structuur van het bestand in milliseconden had kunnen beantwoorden. Onder concurrency wordt de rekenkunde vijandig: vier grote loads tegelijk delen één geheugenbudget, en de throughput stort precies in wanneer de queue het diepst is en u het het minst kunt missen

Een bestand lezen via een handle

De read-only-tier opent een bestand als handle, beantwoordt structurele vragen erover, en sluit het. Geen objectstructuur, geen pagina-rendering, geen geheugen dat met de input meegroeit

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;

Drie gewoonten houden deze tier eerlijk. Ten eerste, controleer de returnwaarde. Een niet-positieve handle betekent dat het openen faalde, en DAGetPageCount afvuren op een dode handle is het soort bug dat verborgen blijft tot de dag dat een klant een misvormd bestand stuurt. Ten tweede, paart elke succesvolle open met DACloseFile binnen een finally-blok; een service die handles lekt crasht niet, hij rot alleen, wat erger is. Ten derde, respecteer wat de wachtwoord-parameter werkelijk doet. DAOpenFileReadOnly accepteert er een, maar voor versleutelde inputs valt hij stilletjes terug op een volledige parse om het pagina-aantal te lezen, dus de flat-memory-garantie verdampt. Route beschermde bestanden eerst door DecryptFile en de rest van de pipeline blijft goedkoop

Dezelfde probe dient dubbel als triage-poort. Bestanden duiken verkeerd gelabeld, half-geüpload, of hernoemd vanuit een heel ander formaat op, en een DAOpenFileReadOnly-controle verwerpt al die bij de voordeur in milliseconden, met de fout vastgepind op het betreffende bestand. Het alternatief is een junk-bestand diep in een queue-worker te laten meerijden en daar te laten ontploffen, waar het ontwarren welke input het veroorzaakte een middag kan kosten

Hele bestanden kopiëren, ontsleutelen en versleutelen

De tweede tier verplaatst en transformeert complete bestanden zonder ooit hun internals bloot te stellen. Dit zijn de aanroepen waar intake-pipelines het meest op leunen

// Structurele kopie: valideer-en-verplaats zonder de objectboom te parsen
Status := Pdf.DACopyFile('incoming\statement.pdf', 'verified\statement.pdf');
LogDirectFileStatus('copy', Status);

// Ontsleutel tijdens het kopiëren: de Direct File-route naar beveiligde inputs
Status := Pdf.DecryptFile('incoming\protected.pdf',
  'verified\plain.pdf', 'batch-password');
LogDirectFileStatus('decrypt-copy', Status);

// Versleutel tijdens het kopiëren: beveilig een uitvoer zonder volledig te laden
Status := Pdf.EncryptFile('verified\statement.pdf',
  'outbound\statement.pdf', 'owner-secret', '', aes256, [prPrint]);
LogDirectFileStatus('encrypt-copy', Status);

Elke aanroep verdient zijn plaats. DACopyFile is de gevalideerde kopie van een quarantaine-map naar beheerde opslag: het opent en indexeert de PDF-structuur onderweg, dus een afgekapt of niet-PDF input faalt hier direct in plaats van drie fasen stroomafwaarts. DecryptFile schrijft een ontsleutelde kopie langs een direct AES-256-herschrijfpad dat de objectstructuur overslaat wanneer de input het toelaat, de large-file-tegenhanger van de load-and-resave-ontsleutelingsflow behandeld in het AES-256-versleutelingsartikel. EncryptFile draait dezelfde beweging omgekeerd, en past wachtwoordbescherming toe tijdens een kopie op bestandsniveau met de key-type- en toestellingsparameters die het in-memory-pad al gebruikt

Wijzigingen appenden in plaats van herschrijven

Incremental update, gedefinieerd in ISO 32000-1 §7.5.6, is de derde tier. De originele bytes blijven waar ze op schijf staan, en alle nieuwe of gewijzigde objecten worden erna geappend, gevolgd door een verse cross-reference-sectie die terugketent in het origineel. Voor een archief van 900 MB dat één pagina nodig heeft toegevoegd, is de schrijfkosten de delta, niet het hele bestand

Anatomie van een PDF-incrementele update geproduceerd door HotPDF: originele bytes blijven onaangeroerd terwijl een delta van nieuwe objecten en een geketende kruisverwijzingssectie wordt toegevoegd, en eerdere revisies herstelbaar blijven tot een volledige SaveLoadedDocument-herschriften ze laat vallen
Incrementele opslagacties voegen alleen hun delta toe en behouden eerdere revisies, dus verdichten is een aparte, weloverwogen herschrijving
// Voeg een auditpagina toe aan een groot archief zonder het te herschrijven
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');  // originele bytes + delta

Twee punten van discipline tellen hier. BeginIncrementalUpdate moet naar het originele bestand wijzen, want de geappende cross-reference-data ketent terug naar byte-offsets erbinnen. En het model is append-only bij ontwerp: elke incrementele save laat het bestand groeien, nooit krimpen. Een document dat nachtelijks wordt gestempeld zal zonder limiet zwellen tot een periodieke reserialisatie — het laden en terugschrijven via SaveLoadedDocument — het compacteert. Diezelfde append-only-aard is wat incremental update de enige veilige manier maakt om een digitaal ondertekend document aan te raken, een beperking onderzocht in het artikel over digitale handtekeningen en PAdES. De onderliggende cross-reference-machinerie krijgt een eigen behandeling in het artikel over object streams en incremental updates

Er zit een val in append-only-saves die de meeste reviews passeert. De originele bytes blijven in het bestand, leesbaar voor iedereen die wil kijken. Een incremental update die een pagina "vervangt" verwijdert de oude niet; hij supersedeert hem in de huidige revisie terwijl de vorige revisie er zit, volledig herstelbaar. Dus incremental updates zijn het verkeerde gereedschap om gevoelige inhoud te strippen. Om geschiedenis die een ontvanger nooit zou moeten zien werkelijk te droppen, heeft u een volledige reserialisatie nodig: LoadFromFile gevolgd door SaveLoadedDocument, die alleen de huidige staat uitschrijft en de begraven revisies achterlaat

De tier matchen aan de bewerking

De selectielogica is kort genoeg om in uw hoofd te houden, en het betaalt om haar als een expliciete routing-beslissing bovenaan een pipeline te encoderen in plaats van elke klus zijn eigen pad te laten improviseren. De bewerking die u nodig heeft beslist de tier:

  • Tellen, inspecteren of classificeren opent een handle: DAOpenFileReadOnly, DAGetPageCount, DACloseFile
  • Een heel bestand verplaatsen, ontsleutelen of versleutelen blijft op bestandsniveau met DACopyFile, DecryptFile of EncryptFile
  • Pagina's herstructureren of documenten samenvoegen heeft de volledige load nodig: LoadFromFile, dan InsertPagesFromDocument of MovePage, dan SaveLoadedDocument
  • Een kleine delta toevoegen aan een enorm of ondertekend bestand roept BeginIncrementalUpdate aan en slaat op

Gemengde pipelines doen er goed aan een groottedrempel voor het full-load-pad te zetten. Stuur alles voorbij een paar honderd megabyte door de Direct File-tiers, en reserveer de volledige load voor echte herstructurering op een 64-bit worker met een reëel geheugenbudget. De drempel zet een out-of-memory-crash om in een routing-beslissing die u kunt zien en tunen

Welke tier een klus ook afhandelt, schrijf zijn output naar een tijdelijke naam en hernoem pas naar de definitieve plek zodra het resultaat valideert. Een half-geschreven bestand onder de definitieve naam ziet er voor de volgende fase van de pipeline net zo uit als een goede, en de Direct File-aanroepen maken de controle goedkoop: een output bevestigen is een één-regel handle-probe

De Direct File API wordt geleverd als deel van de HotPDF Delphi Component voor Delphi en C++Builder. De productpagina linkt de volledige functie-referentie, inclusief de incremental-update-aanroepen die hier getoond worden