Att slå ihop (merge) eller dela (split) en två gigabyte stor PDF på det uppenbara sättet kostar dig två saker på en gång: wall-clock-tid och adressutrymme. Det uppenbara sättet är att ladda varje inmatning, göra jobbet, och skriva utmatningen. Inläsningen är där det går sönder. Ett skanningsarkiv som går från 300 till 600 DPI dubblar sin linjära upplösning och fyrdubblas ungefärligen på disken, så samma sammansättningsjobb som hanterade 400 MB-filer hela året börjar tröska (thrash) i samma ögonblick som en inmatning passerar en gigabyte, ofta medan man inte gör något mer än att räkna sidor. Uppgiften blev aldrig svårare. Öppna, räkna, välj intervall, sammanfoga är allt det handlar om. Fullträds-inläsning slutade helt enkelt att vara en vettig standardinställning vid den storleken. PDFlibPas, losLabs PDF-bibliotek för Delphi och C++Builder, svarar på detta med sitt Direct Access-lager: en familj av DA-prefixade funktioner uppbackade av en strömmande läsare som går igenom korsreferenstabellen (cross-reference table) på plats istället för att bygga hela dokumentet i minnet
Var minnet tar vägen i en fullständig inläsning
Att ladda en PDF "normalt" innebär att tolka xref, lösa upp varje indirekt objekt till ett träd i minnet, avkoda objektströmmar, och koppla in sidträdet, typsnitt och annotationer (annotations) till objekt du kan manipulera. För redigeringsarbetsflöden är det rätt val (trade). För sammanslagning, delning och inspektionsarbete är det mestadels slöseri. Ett 30 000-sidigt skanningsarkiv kan innehålla miljoner indirekta objekt, och ett delningsjobb behöver läsa några hundra av dem: sidnoderna i det begärda intervallet, plus vadhelst de noderna refererar till
Direct Access-lagret inverterar modellen. DAOpenFile och DAOpenFileReadOnly tolkar tailern och xref, några kilobyte i slutet av filen, och returnerar ett filhandtag. Objekt hämtas lat (lazily) när ett anrop behöver dem. Den praktiska konsekvensen är att det tar ungefär lika lång tid att öppna en multigigabyte-fil som att öppna en liten, och minnet följer det du rör vid snarare än vad filen innehåller
Att undersöka (probing) en enorm fil utan att ladda den
Mönstret nedan kommer från bibliotekets egna benchmark för stora filer: öppna skrivskyddat, ställ frågor, stäng. Inget dokumentträd existerar någonsin
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;
Skrivskyddat läge är värt att föredra när du kan: det låter intagssteget (intake stage) köras medan andra processer håller filen, och det dokumenterar uppsåt (intent). Ett sond-steg (probe stage) som oavsiktligt anropar en muterande funktion misslyckas snabbt istället för att korrumpera arkivet
PageRef är ett objekthandtag, inte ett sidnummer
Det absolut vanligaste misstaget med DA-API:et är att skicka ett sidnummer där en funktion förväntar sig en PageRef. Nästan varje DA-anrop per sida tar ett referenshandtag till sidobjektet istället för ett sidnummer: DAExtractPageText, DARenderPageToFile, DARotatePage, och DACapturePage förväntar sig alla en referens (ref). Du får en genom att översätta det människo-vända numret genom DAFindPage:
PageRef := Lib.DAFindPage(Handle, 250); // page number -> object handle
if PageRef <> 0 then
begin
Text := Lib.DAExtractPageText(Handle, PageRef, 0);
Lib.DARenderPageToFile(Handle, PageRef, 5, 150, 'page250.png');
end;
Att skicka det råa numret 250 istället lyfter inte något fel. Det adresserar vilket objekt som än råkar sitta bakom det handtagsvärdet, vilket på en bra dag misslyckas synligt och på en dålig dag extraherar text från fel sida in i ett kundvänt dokument. Om du paketerar (wrap) DA-lagret i din egen servicekod, gör översättningen omöjlig att hoppa över: acceptera sidnummer vid gränsen, anropa DAFindPage omedelbart, och skicka endast referenser internt
Att slå ihop (merging) hundratals filer med en namngiven lista
För två filer räcker MergeFiles(First, Second, Output). Batch-sammansättning (Batch assembly) skalar bättre genom fillistor: registrera inmatningar (inputs) under ett listnamn, slå sedan ihop listan i en körning (pass)
Lib.AddToFileList('Statements', 'jan.pdf');
Lib.AddToFileList('Statements', 'feb.pdf');
Lib.AddToFileList('Statements', 'mar.pdf');
Lib.MergeFileList('Statements', 'q1-statements.pdf');
// Verify the result the cheap way: direct access again
Handle := Lib.DAOpenFileReadOnly('q1-statements.pdf', '');
Writeln('merged pages: ', Lib.DAGetPageCount(Handle));
Lib.DACloseFile(Handle);
Sammanslagningsfamiljen har tre varianter, och skillnaden är inte enbart hastighet. MergeFileListFast hoppar över bevarandet av strukturträdet (structure tree); MergeFileListStrict upprätthåller strict mode; versionen utan suffix är den balanserade standarden. Den operationella regeln som faller ut: om någon inmatning är en taggad PDF (Tagged PDF) vars tillgänglighetsstruktur måste överleva, där allt som produceras för PDF/UA är det uppenbara fallet, sträck dig efter standarden eller Strict-varianten, eftersom Fast tyst tappar strukturträdet. För vanliga skanningsarkiv utan taggning är Fast gratis prestanda. Besluta per pipeline, inte efter utvecklarens humör, och anteckna vilken variant som användes i jobbloggen
Att dela utan att ladda: intervall-extrahering
Delning följer samma inläsningslösa (no-load) filosofi. ExtractFilePages(InputFileName, Password, OutputFileName, RangeList) drar ett sidintervall rakt från fil till fil, med en intervallista som '1-500', '501-1000', eller kommaseparerade urval, och källan blir aldrig ett dokumentträd. När ett dokument redan är laddat av andra skäl producerar ExtractPageRanges ett nytt minnes-dokument (in-memory document) från det nuvarande, och CopyPageRanges drar över intervall från ett annat laddat dokument via ID. För per-utdrag-delning (per-statement splitting) av konsoliderade utskriftsströmmar är formen fil-till-fil den som förhindrar en 4 GB inmatning från att någonsin expandera in i RAM
Filer som ljuger om sin geometri
Pipelines för stora filer möter skadade filer i en takt pipelines för små filer aldrig ser, helt enkelt för att inmatningarna passerar genom fler system. Två felformer förtjänar explicit hantering
Först, förskjutna rubriker (shifted headers). Mail-gateways och utskriftshanterare (print spoolers) lägger ibland (prepends) till byte till en PDF, så att markören %PDF inte längre sitter vid offset 0 och varje xref-offset i filen är fel med samma mängd. Den strömmande läsaren upptäcker detta och exponerar det (DAShiftedHeader på den platta nivån, ShiftedHeader på TSmartPDFReader), och kompenserar sedan för det under läsningar. Hemma-snickrad offset-aritmetik gör i typiska fall inte det, vilket är varför "fungerar på varje fil vi genererar, misslyckas på filer från kund X" är det klassiska symptomet
Andra, trasiga korsreferenstabeller. DACopyFile(InputFileName, OutputFileName, PageCount) strömmar hela filen till en ny kopia samtidigt som xref byggs om, och returnerar sidantalet som en biprodukt. Att köra det som ett normaliseringssteg (normalization stage) framför en kräsen nedströms-konsument omvandlar en klass av intermittenta tolkningsmisslyckanden (parse failures) till ett förutsägbart reparationssteg. Och när dina egna redigeringar behöver sparas, skriver DAAppendFile dem som en inkrementell uppdatering (incremental update), vilket lägger till (appending) en ny revision hellre än att skriva om gigabyte, vilket håller spar-kostnaden proportionell mot ändringen istället för till filen
Leveransdetaljer: linearisering (linearization) och komposition
Två närliggande förmågor avrundar en pipeline för stora filer. När den sammansatta utmatningen (assembled output) serveras över HTTP för in-browser visning, omorganiserar LinearizeFile den för byte-intervallströmning (byte-range streaming) så att den första sidan visas innan resten av ett 500 MB stort paket har laddats ner färdigt. Kör det som slutsteget, efter all sammanslagning, eftersom varje senare modifikation avlineariserar (de-linearizes) filen igen. Och när paket behöver komposition hellre än ren konkatenering, säg ett försättsblad (coversheet) stämplat bakom varje utdrag (statement) eller två källsidor ålagda (imposed) på ett utmatningsark, förvandlar DACapturePage vilken sida som helst till en återanvändbar mall som DADrawCapturedPage placerar på en destinationssida vid en godtycklig rektangel, fortfarande utan en fullständig dokumentladdning på multigigabyte-källan
Gränser och vad som förblir skrivskyddat
Formatet i sig får slut på utrymme långt innan Direct Access gör det. Offsets är Int64 hela vägen genom DA-lagret, så de verkliga taken är tillgänglig disk och det 10-siffriga xref offset-fältet i klassiska (icke-ström) korsreferenstabeller. Multigigabyte skanningsarkiv är anmärkningsvärda i praktiken, och minnet förblir begränsat oberoende av filstorlek eftersom objekt läses endast när ett anrop ber om dem
Två frågor dyker upp tillräckligt ofta för att svara direkt på. Sammanslagning genom standardvägen bär med sig dokumentstruktur, så bokmärken (bookmarks) och länkar överlever; Fast-varianten är den som byter strukturträdet mot hastighet, vilket är hela anledningen till att reservera den för otaggade inmatningar. Den säkra vanan är att öppna den sammanslagna utmatningen, gå igenom dess outline, och stickprovskontrollera några interna länkar innan den skeppas. Vad gäller redigering: det finns en användbar medelväg mellan skrivskyddad sondering och en full laddning. Operationer på sidnivå fungerar på handtaget direkt, DARotatePage, DAMovePage, och DAHidePage bland dem, tillsammans med läsning av formulärfält (form-field reads), och DAAppendFile lagrar (persists) dessa redigeringar som en inkrementell revision. Redigering på innehållsnivå, allt som skriver om marking-operatorerna inuti en sida, hör fortfarande till det fullständiga dokumentlagret
Relaterade artiklar
Om din sammanslagna utmatning måste förbli tillgänglig, täcks bakgrunden för strukturträd i artikeln om Tagged PDF accessibility, som förklarar exakt vad Fast merge-varianten skulle kasta bort. För att dra ut innehåll ur de intervall du delar, se guiden för text-, bild- och typsnittsextrahering
Den fullständiga Direct Access-funktionslistan skeppas med biblioteket; utgåvor (editions) och testnedladdningar finns på PDFlibPas produktsida