Teknisk artikkel

Slå sammen og dele gigabyte-PDF-er i Delphi med PDF Library for Delphi Direct Access

Å slå sammen eller dele opp en PDF på to gigabyte på den opplagte måten koster deg to ting på én gang: klokketid og adresserom. Den opplagte måten er å laste hver inndatafil, gjøre jobben og skrive utdataene. Lastingen er der det ryker. Et skannearkiv som går fra 300 til 600 DPI, dobler den lineære oppløsningen og firedobler seg omtrent på disk, så den samme sammenstillingsjobben som håndterte filer på 400 MB hele året, begynner å tråkke rundt i det øyeblikket en inndatafil passerer en gigabyte, ofte mens den ikke gjør annet enn å telle sider. Oppgaven ble aldri vanskeligere. Åpne, tell, plukk områder, sett sammen er hele greia. Fullstendig lasting av treet sluttet rett og slett å være et fornuftig standardvalg ved den størrelsen. PDF Library for Delphi, losLabs PDF-bibliotek for Delphi og C++Builder, svarer på dette med sitt Direct Access-lag: en familie funksjoner med DA-prefiks, tuftet på en strømmende leser som går gjennom kryssreferansetabellen der den ligger, i stedet for å bygge hele dokumentet i minnet

Hvor minnet blir av i en full lasting

Å laste en PDF «normalt» betyr å parse xref-en, løse opp hvert indirekte objekt til et tre i minnet, dekode objektstrømmer og koble sidetreet, skriftene og annotasjonene sammen til objekter du kan manipulere. For redigeringsarbeidsflyter er det den rette avveiningen. For sammenslåing, oppdeling og inspeksjon er det stort sett bortkastet. Et skannearkiv på 30 000 sider kan romme millioner av indirekte objekter, og en oppdelingsjobb trenger å lese noen få hundre av dem: sidenodene i det forespurte området, pluss det de nodene refererer til

Direct Access-laget snur modellen. DAOpenFile og DAOpenFileReadOnly parser traileren og xref-en, noen få kilobyte helt bakerst i filen, og returnerer et filhåndtak. Objekter hentes først når et kall trenger dem. Den praktiske følgen er at å åpne en fil på flere gigabyte tar omtrent like lang tid som å åpne en liten, og at minnebruken følger det du berører, ikke det filen inneholder

PDF Library for Delphi-sammenligning av å laste en gigabyte PDF inn i et fullstendig objekttré i minnet mot å åpne den med direkte tilgang, der parsingen stopper ved trailer og xref, og en handle betjener late per-objekt-lesinger
En full innlasting dekoder hvert indirekte objekt før flettingen kan starte, så RAM og åpningstid skalerer med arkivet. Direkte tilgangsveien returnerer et fungerende håndtak etter å ha lest kilobytes og lar hvert kall trekke bare objektene det trenger

Å sondere en enorm fil uten å laste den

Mønsteret nedenfor kommer fra bibliotekets egen ytelsestest for store filer: åpne skrivebeskyttet, still spørsmål, lukk. Det finnes aldri noe dokumenttre

var
  Lib: TPDFlib;
  Handle, Pages: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Handle := Lib.DAOpenFileReadOnly('archive-2025.pdf', '');
    if Handle = 0 then
      raise Exception.Create('Direct access open failed');
    Pages := Lib.DAGetPageCount(Handle);
    Writeln('pages : ', Pages);
    Writeln('title : ', Lib.DAGetInformation(Handle, 'Title'));
    Lib.DACloseFile(Handle);
  finally
    Lib.Free;
  end;
end;

Skrivebeskyttet modus er verdt å foretrekke når du kan: den lar mottakstrinnet kjøre mens andre prosesser holder filen, og den dokumenterer hensikten. Et sonderingstrinn som ved et uhell kaller en muterende funksjon, feiler raskt i stedet for å ødelegge arkivet

PageRef er et objekthåndtak, ikke et sidenummer

Den desidert vanligste tabben med DA-API-et er å sende inn et sidenummer der en funksjon venter en PageRef. Nesten hvert DA-kall som gjelder én side, tar et referansehåndtak til sideobjektet snarere enn et sidenummer: DAExtractPageText, DARenderPageToFile, DARotatePage og DACapturePage venter alle en ref. Du får en ved å oversette det menneskevendte nummeret gjennom DAFindPage:

PDF Library for Delphi: Oversettelsesflyt fra sidenummer til PageRef som viser DAFindPage mate per-side direkte tilgangskall, mot en rå heltallsref som lander på et vilkårlig objekt og produserer stille feilside-tekst
Hvert direkte tilgangskall per side konsumerer en PageRef produsert av DAFindPage, aldri det menneskevennlige nummeret. Å hoppe over den oversettelsen gjør at heltallet utgir seg for å være en objekt-id, og tekst på feil side kan sendes usynlig ut
PageRef := Lib.DAFindPage(Handle, 250);          // sidenummer -> objekthåndtak
if PageRef <> 0 then
begin
  Text := Lib.DAExtractPageText(Handle, PageRef, 0);
  Lib.DARenderPageToFile(Handle, PageRef, 5, 150, 'page250.png');
end;

Å sende inn det rå tallet 250 i stedet utløser ingen feil. Det adresserer det objektet som tilfeldigvis ligger bak den håndtaksverdien, noe som på en god dag feiler synlig og på en dårlig dag trekker ut tekst fra feil side inn i et dokument kunden skal se. Pakker du DA-laget inn i din egen tjenestekode, gjør oversettelsen umulig å hoppe over: ta imot sidenumre i grensesnittet, kall DAFindPage med én gang, og send bare refer videre internt

Å slå sammen hundrevis av filer med en navngitt liste

For to filer holder MergeFiles(First, Second, Output). Batchsammenstilling skalerer bedre gjennom fillister: registrer inndatafilene under et listenavn, og slå så sammen listen i én runde

PDF Library for Delphi: Navngitt fillistearbeidsflyt der januar-, februar- og mars-oppgaver registreres under ett listenavn og slås sammen i ett enkelt pass, med Fast-, standard- og strict-variantene som bytter strukturtrebevaring mot hastighet
Hundrevis av registrerte inndata kollapser til ett enkelt MergeFileList-gjennomløp hvis resultat verifiseres på millisekunder gjennom enda en skrivebeskyttet probe. Varianten er en per-pipeline-beslutning, for Fast mister Tagged PDF-strukturetreet
Lib.AddToFileList('Statements', 'jan.pdf');
Lib.AddToFileList('Statements', 'feb.pdf');
Lib.AddToFileList('Statements', 'mar.pdf');
Lib.MergeFileList('Statements', 'q1-statements.pdf');

// Verifiser resultatet på den billige måten: direkte tilgang igjen
Handle := Lib.DAOpenFileReadOnly('q1-statements.pdf', '');
Writeln('merged pages: ', Lib.DAGetPageCount(Handle));
Lib.DACloseFile(Handle);

Sammenslåingsfamilien har tre varianter, og forskjellen er ikke hastighet alene. MergeFileListFast hopper over bevaring av strukturtreet; MergeFileListStrict håndhever streng modus; versjonen uten suffiks er det balanserte standardvalget. Driftsregelen som faller ut: er noen av inndatafilene en Tagged PDF hvis tilgjengelighetsstruktur må overleve, der alt som lages for PDF/UA er det opplagte tilfellet, grip til standard- eller Strict-varianten, for Fast forkaster strukturtreet stilltiende. For rene skannearkiver uten tagging er Fast gratis ytelse. Bestem per pipeline, ikke per utviklerhumør, og noter varianten som ble brukt, i jobbloggen

Oppdeling uten lasting: uttrekk av områder

Oppdeling følger den samme filosofien om å ikke laste. ExtractFilePages(InputFileName, Password, OutputFileName, RangeList) trekker et sideområde rett fra fil til fil, med en områdeliste som '1-500', '501-1000' eller kommaseparerte utvalg, og kilden blir aldri et dokumenttre. Når et dokument allerede er lastet av andre grunner, produserer ExtractPageRanges et nytt dokument i minnet fra det gjeldende, og CopyPageRanges henter områder over fra et annet lastet dokument ved ID. For oppdeling per kontoutskrift av samlede utskriftsstrømmer er formen fra fil til fil den som hindrer en inndatafil på 4 GB i noensinne å svulme opp i RAM

Filer som lyver om sin egen geometri

Pipeliner for store filer møter ødelagte filer i et omfang pipeliner for små filer aldri ser, rett og slett fordi inndataene passerer flere systemer. To feilformer fortjener uttrykkelig håndtering

For det første forskjøvne filhoder. Postgatewayer og utskriftskøer legger av og til byte foran en PDF, slik at %PDF-markøren ikke lenger ligger på offset 0, og hver xref-offset i filen er feil med det samme beløpet. Den strømmende leseren oppdager dette og eksponerer det (DAShiftedHeader på det flate nivået, ShiftedHeaderTSmartPDFReader), og kompenserer så for det under lesing. Hjemmesnekret offsetaritmetikk gjør vanligvis ikke det, og derfor er «virker på hver fil vi selv lager, feiler på filer fra kunde X» det klassiske symptomet

For det andre ødelagte kryssreferansetabeller. DACopyFile(InputFileName, OutputFileName, PageCount) strømmer hele filen til en ny kopi mens den bygger xref-en på nytt, og returnerer sideantallet som et biprodukt. Å kjøre den som et normaliseringstrinn foran en kresen konsument nedstrøms gjør en hel klasse sporadiske parsefeil om til ett forutsigbart reparasjonstrinn. Og når dine egne redigeringer må lagres, skriver DAAppendFile dem som en inkrementell oppdatering, og legger til en ny revisjon framfor å skrive om gigabyte, noe som holder lagringskostnaden proporsjonal med endringen i stedet for med filen

Leveringsdetaljer: linearisering og komposisjon

To beslektede muligheter runder av en pipeline for store filer. Når de sammenstilte utdataene serveres over HTTP for visning i nettleseren, omorganiserer LinearizeFile dem for strømming med byteområder, slik at den første siden vises før resten av en pakke på 500 MB er ferdig nedlastet. Kjør den som siste trinn, etter all sammenslåing, for enhver senere endring avlineariserer filen igjen. Og når pakker trenger komposisjon snarere enn ren sammenkjeding, si et forsideark stemplet bak hver kontoutskrift eller to kildesider satt opp på ett utdataark, gjør DACapturePage enhver side om til en gjenbrukbar mal som DADrawCapturedPage plasserer på en målside i et vilkårlig rektangel, fortsatt uten en full dokumentlasting av kilden på flere gigabyte

Grenser og hva som forblir skrivebeskyttet

Selve formatet går tom for plass lenge før Direct Access gjør det. Offsetene er Int64 hele veien gjennom DA-laget, så de virkelige takene er tilgjengelig diskplass og det tisifrede xref-offsetfeltet i klassiske kryssreferansetabeller uten strøm. Skannearkiver på flere gigabyte er upåfallende i praksis, og minnebruken forblir avgrenset uansett filstørrelse, fordi objekter først leses når et kall ber om dem

To spørsmål kommer ofte nok til å fortjene et direkte svar. Sammenslåing gjennom standardveien tar dokumentstrukturen med over, så bokmerker og lenker overlever; Fast-varianten er den som bytter bort strukturtreet mot fart, og det er hele grunnen til å forbeholde den for utaggede inndata. Den trygge vanen er å åpne det sammenslåtte resultatet, gå gjennom disposisjonen og stikkprøve noen interne lenker før det sendes videre. Når det gjelder redigering: det finnes en nyttig mellomting mellom skrivebeskyttet sondering og en full lasting. Operasjoner på sidenivå virker direkte på håndtaket, DARotatePage, DAMovePage og DAHidePage blant dem, sammen med lesing av skjemafelter, og DAAppendFile lagrer de redigeringene som en inkrementell revisjon. Redigering på innholdsnivå, alt som skriver om markeringsoperatorene inne i en side, hører fortsatt hjemme i det fulle dokumentlaget

Beslektede artikler

Må de sammenslåtte utdataene forbli tilgjengelige, dekkes bakgrunnen om strukturtreet i artikkelen om tilgjengelighet i Tagged PDF, som forklarer nøyaktig hva Fast-varianten av sammenslåing ville forkastet. For å hente innhold ut av områdene du deler opp, se guiden til uttrekk av tekst, bilder og skrifter

Den fullstendige funksjonslisten for Direct Access følger med biblioteket; utgaver og prøvenedlastinger finnes på produktsiden for PDF Library for Delphi