PDFium-komponenten gir deg én metode for PDF-deling: ImportPages. Alt annet, enten du isolerer en enkelt side, klipper på vilkårlige grenser, eller følger dokumentets egen bokmerkestruktur, er bare forskjellige måter å bestemme hvilke sidetall som går inn i hver utdatafil. Mekanikken forblir den samme. Å forstå det tidlig sparer deg for mange feilskjær
Hvordan deleløkken fungerer
Mønsteret er det samme uavhengig av hvordan du deler kildedokumentet. Opprett en fersk TPdf-forekomst (instance), kall CreateDocument på den for å initialisere en tom PDF i minnet, importer sidene du ønsker med ImportPages, lagre resultatet, og tilbakestill (reset) Active til False før neste iterasjon. Det siste trinnet er det folk går glipp av: CreateDocument lukker ikke implisitt dokumentet som fortsatt er i minnet, så du må lagre utdataene dine og tilbakestille Active := False eksplisitt før du kaller den igjen; å tilbakestille først holder tilstanden ren og veldefinert. Den ytre TPdf-forekomsten gjenbrukes på tvers av alle iterasjoner, noe som holder tildelingspresset (allocation pressure) lavt på store jobber
Slik ser side-for-side-deling ut strippet til det essensielle:
procedure SplitIntoPages(Source: TPdf; const OutputDir: string);
var
I: Integer;
PdfOut: TPdf;
OutFile: string;
begin
PdfOut := TPdf.Create(nil);
try
for I := 1 to Source.PageCount do
begin
PdfOut.CreateDocument;
// Range is a 1-based page number string; insertion point 1 = first position
if not PdfOut.ImportPages(Source, IntToStr(I), 1) then
raise Exception.CreateFmt('Failed to import page %d', [I]);
OutFile := OutputDir + '\page_' + Format('%.4d', [I]) + '.pdf';
if not PdfOut.SaveAs(OutFile) then
raise Exception.Create('Failed to save ' + OutFile);
PdfOut.Active := False; // reset before next CreateDocument
end;
finally
PdfOut.Free;
end;
end;
Range-parameteren til ImportPages er det samme strengformatet PDFium bruker internt: en kommadelt liste over sidetall eller bindestrek-delte intervaller, alle 1-baserte. '3' importerer side 3. '1-5' importerer sidene 1 til og med 5 i rekkefølge. '2,5,8' importerer de tre sidene. Den tredje parameteren er den 1-baserte innsettingsposisjonen i måldokumentet; å sende 1 plasserer alltid importerte sider i begynnelsen av en ellers tom fil, som er det du vil ha her
Deling etter sideintervaller
Når oppringeren (caller) oppgir en liste som 1-12,13-24,25-36, analyserer (parse) du den i start/slutt-par og kjører den samme løkken, og konstruerer intervallstrengen (range string) fra hvert par:
procedure SplitByRanges(Source: TPdf; const RangeList: array of string;
const OutputDir: string);
var
I: Integer;
PdfOut: TPdf;
OutFile: string;
begin
PdfOut := TPdf.Create(nil);
try
for I := 0 to High(RangeList) do
begin
PdfOut.CreateDocument;
if not PdfOut.ImportPages(Source, RangeList[I], 1) then
raise Exception.Create('Invalid page range: ' + RangeList[I]);
OutFile := Format('%s\section_%d.pdf', [OutputDir, I + 1]);
if not PdfOut.SaveAs(OutFile) then
raise Exception.Create('Failed to save ' + OutFile);
PdfOut.Active := False;
end;
finally
PdfOut.Free;
end;
end;
Validering før du når ImportPages er viktig her. ImportPages returnerer False når et sidetall i intervallstrengen overstiger Source.PageCount, men den kaster ikke et unntak (raise an exception), og den produserer ikke en delvis utdatafil du kan oppdage bare ved navn. Sjekk SaveAs sin returverdi og loggfør feil separat; et intervall som produserer en tom utdatafil er ikke åpenbart feil før noen åpner den
Deling ved bokmerkegrenser
Den tredje tilnærmingen bruker dokumentets egen struktur snarere enn en eksternt levert liste. Hvert bokmerke på toppnivå bærer et målsidetall; seksjonen det definerer løper fra den siden til én før det neste bokmerkets side, eller til slutten av dokumentet for den siste oppføringen
procedure SplitByBookmarks(Source: TPdf; const OutputDir: string);
var
Bm: TBookmarks;
I, StartPage, EndPage: Integer;
PdfOut: TPdf;
RangeStr, OutFile, SafeTitle: string;
begin
Bm := Source.Bookmarks;
if Length(Bm) = 0 then
Exit;
PdfOut := TPdf.Create(nil);
try
for I := 0 to High(Bm) do
begin
StartPage := Bm[I].PageNumber;
if I < High(Bm) then
EndPage := Bm[I + 1].PageNumber - 1
else
EndPage := Source.PageCount;
if (StartPage < 1) or (EndPage < StartPage) then
Continue;
RangeStr := Format('%d-%d', [StartPage, EndPage]);
PdfOut.CreateDocument;
if not PdfOut.ImportPages(Source, RangeStr, 1) then
begin
PdfOut.Active := False;
Continue; // skip a malformed section instead of writing an empty file
end;
SafeTitle := StringReplace(Bm[I].Title, '/', '_', [rfReplaceAll]);
SafeTitle := StringReplace(SafeTitle, ':', '_', [rfReplaceAll]);
OutFile := Format('%s\%02d_%s.pdf', [OutputDir, I + 1, SafeTitle]);
if not PdfOut.SaveAs(OutFile) then
raise Exception.Create('Failed to save ' + OutFile);
PdfOut.Active := False;
end;
finally
PdfOut.Free;
end;
end;
Et dokument som ikke har noen bokmerker, er ikke en feiltilstand verdt å vise (surfacing) til brukeren som en; det betyr bare at denne delingsmodusen ikke har noe å jobbe ut fra. Length(Bm) = 0-vakten håndterer det i det stille. Det som er verdt å vise frem er når et bokmerkes sidetall er utenfor dokumentets intervall, noe som skjer i feilformaterte (malformed) filer der oversikten aldri ble oppdatert etter at sider ble slettet. Grensesjekken på StartPage og EndPage hopper over disse oppføringene i stedet for å sende et søppelintervall til ImportPages
Navngivning av utdatafil og Active-tilbakestillingen
Filsikkerhet (filename safety) for bokmerke-utledede navn trenger eksplisitt oppmerksomhet. Bokmerketitler kan inneholde tegn som er gyldige i en PDF-streng, men ikke i en filsystemsti. Bytt ut som et minimum skråstrek (forward slash), omvendt skråstrek (backslash) og kolon før du bygger utdatastien. På Windows er også *, ?, ", <, >, og | forbudt; en enkel løkke over et fast sett dekker dem uten å trekke inn et regulært uttrykk (regex)
Active := False-linjen på slutten av hver iterasjon fortjener vektlegging fordi det er det eneste ikke-åpenbare kravet i mønsteret. CreateDocument lukker ikke implisitt det som er åpent. Hvis Active fortsatt er True når CreateDocument kjører igjen, ble dokumentet som fortsatt er i minnet aldri lukket eller lagret ordentlig, og du kan ikke stole på veldefinert adferd i den tilstanden, så lagre og tilbakestill (reset) eksplisitt før du starter det neste dokumentet. Tenk på det som paret til try/finally: finally-blokken frigjør det ytre objektet; Active := False tilbakestiller den indre dokumenttilstanden mellom løkkeiterasjoner
Minnebruk over en stor delingsjobb forblir flat med denne tilnærmingen fordi du aldri holder mer enn ett utdatadokument i minnet på en gang. Kildesidokumentet forblir åpent og skrivebeskyttet (read-only) gjennom det hele; ImportPages kopierer sidedata inn i det nye dokumentet uten å endre kilden. Hvis kilden er kryptert, åpner du den med passordet dens før løkken, og de kopierte sidene i hver utdatafil vil være ukrypterte, noe som vanligvis er den riktige adferden for delte utdata som distribueres til forskjellige mottakere
Én ting til om SaveAs: den returnerer en Boolean. En utdatakatalog som ikke eksisterer, en sti med tegn OS-et avviser, eller en disk-full-tilstand vil alle føre til at SaveAs returnerer False uten å kaste et unntak. I en satsvis jobb (batch job) som deler et 200-siders dokument inn i 200 enkeltsidige filer, er en stille feil på side 147 lett å overse. Sjekk returverdien på hvert kall og tell suksesser mot det forventede totaltallet når løkken er ferdig
Metodene ImportPages og CreateDocument vist her er en del av PDFium-komponenten for Delphi og C++Builder