PDFium Component ger dig en metod för PDF-delning: ImportPages. Allt annat, oavsett om du isolerar en enskild sida, klipper vid godtyckliga gränser eller följer dokumentets egen bokmärkesstruktur, är bara olika sätt att bestämma vilka sidnummer som hamnar i varje utdatafil. Mekaniken förblir densamma. Att förstå det tidigt besparar många felsteg
Hur delningsslingan fungerar
Mönstret är detsamma oavsett hur du delar källdokumentet. Skapa en ny TPdf-instans, anropa CreateDocument på den för att initiera en tom PDF i minnet, importera de sidor du vill ha med ImportPages, spara resultatet, och återställ sedan Active till False före nästa iteration. Det sista steget är det som folk missar: CreateDocument stänger inte implicit dokumentet som fortfarande finns i minnet, så du måste spara dina utdata och återställa Active := False explicit innan du anropar det igen; att återställa först håller tillståndet rent och väldefinierat. Den yttre TPdf-instansen återanvänds över alla iterationer, vilket håller allokeringstrycket lågt vid stora jobb
Så här ser sidvis delning ut, avskalad till dess väsentligheter:
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;
Parametern Range till ImportPages har samma strängformat som PDFium använder internt: en kommaseparerad lista med sidnummer eller bindestrecksavgränsade intervall, alla 1-baserade. '3' importerar sida 3. '1-5' importerar sidorna 1 till och med 5 i ordning. '2,5,8' importerar dessa tre sidor. Den tredje parametern är den 1-baserade insättningspositionen i destinationsdokumentet; att skicka in 1 placerar alltid importerade sidor i början av en annars tom fil, vilket är vad du vill ha här
Dela efter sidintervall
När anroparen tillhandahåller en lista som 1-12,13-24,25-36 analyserar du den i start-/slutpar och kör samma slinga, och konstruerar intervallsträngen från varje 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 innan du når ImportPages är viktigt här. ImportPages returnerar False när ett sidnummer i intervallsträngen överskrider Source.PageCount, men det kastar inte ett undantag och det producerar inte en ofullständig utdatafil som du kan upptäcka bara genom namnet. Kontrollera returvärdet för SaveAs och logga fel separat; ett intervall som producerar en tom utdatafil är inte uppenbart fel förrän någon öppnar den
Dela vid bokmärkesgränser
Det tredje tillvägagångssättet använder dokumentets egen struktur istället för en externt tillhandahållen lista. Varje toppnivåbokmärke har ett målsidnummer; sektionen det definierar löper från den sidan till en före nästa bokmärkes sida, eller till slutet av dokumentet för den sista posten
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;
Ett dokument som saknar bokmärken är inte ett feltillstånd värt att lyfta fram till användaren som ett; det betyder bara att detta delningsläge inte har något att utgå ifrån. Vakten Length(Bm) = 0 hanterar det tyst. Det som är värt att lyfta fram är när ett bokmärkes sidnummer ligger utanför dokumentets intervall, vilket händer i felformaterade filer där innehållsförteckningen aldrig uppdaterades efter att sidor tagits bort. Gränskontrollen på StartPage och EndPage hoppar över dessa poster istället för att skicka ett ogiltigt intervall till ImportPages
Namngivning av utdatafiler och Active-återställningen
Filsäkerhet för bokmärkeshärledda namn kräver explicit uppmärksamhet. Bokmärkestitlar kan innehålla tecken som är giltiga i en PDF-sträng men inte i en filsystemsökväg. Byt åtminstone ut snedstreck, omvänt snedstreck och kolon innan du bygger utdatasökvägen. I Windows är också *, ?, ", <, > och | förbjudna; en enkel loop över en fast uppsättning täcker dem utan att dra in ett regex
Raden Active := False i slutet av varje iteration förtjänar att betonas eftersom det är det enda icke-uppenbara kravet i mönstret. CreateDocument stänger inte implicit vad som än är öppet. Om Active fortfarande är True när CreateDocument körs igen, stängdes eller sparades aldrig dokumentet som fortfarande fanns i minnet ordentligt, och du kan inte förlita dig på välkänt beteende i det tillståndet, så spara och återställ explicit innan du startar nästa dokument. Tänk på det som paret till try/finally: finally-blocket frigör det yttre objektet; Active := False återställer det inre dokumenttillståndet mellan loopiterationerna
Minnesanvändningen över ett stort delningsjobb förblir platt med det här tillvägagångssättet eftersom du aldrig håller mer än ett utdatadokument i minnet åt gången. Källdokumentet förblir öppet och skrivskyddat genomgående; ImportPages kopierar siddata in i det nya dokumentet utan att modifiera källan. Om källan är krypterad, öppna den med dess lösenord före loopen, och de kopierade sidorna i varje utdatafil kommer att vara okrypterade, vilket vanligtvis är rätt beteende för delade utdata som distribueras till olika mottagare
En sak till om SaveAs: det returnerar en Boolean. En utdatakatalog som inte existerar, en sökväg med tecken som operativsystemet avvisar, eller en full disk-situation kommer alla att få SaveAs att returnera False utan att kasta ett undantag. I ett batchjobb som delar upp ett 200-sidigt dokument i 200 ensidiga filer är ett tyst fel på sida 147 lätt att förbise. Kontrollera returvärdet vid varje anrop och räkna antalet lyckade mot den förväntade totalen när slingan avslutas
Metoderna ImportPages och CreateDocument som visas här är en del av PDFium Component för Delphi och C++Builder