Fletning (merge) og opdeling (split) er de to sidehandlinger, alle rækker ud efter først, og de dækker meget jord. De dækker ikke alt. Der er en separat familie af arbejde, der omarrangerer sider i stedet for at flytte hele filer: Læg fire dias på ét ark til et uddelingsmateriale, træk en side fra bagsiden af et dokument til forsiden, eller træk side 3, 7 og 12 ind i et kort uddrag uden at røre ved resten. PDFium afslører tre metoder til netop dette, og hver enkelt opfører sig anderledes end den fletning og opdeling, du allerede kender. Denne artikel gennemgår, hvad de gør, hvor output-punkterne befinder sig, og en ejerskabsdetalje, der har forårsaget et crash i praksis.
De tre er ImportNPagesToOne til N-up-imposition, MovePages til omorganisering på stedet og ImportPagesByIndex til undersætsudtrækning. Fletning stabler dokumenter ende til ende og efterlader sideantallet lig med summen af inputtene. Opdeling skriver flere outputfiler fra ét input. De tre handlinger her sidder midt imellem: en af dem ændrer, hvor mange kildesider der deler et ark, en af dem ændrer rækkefølgen inde i et enkelt dokument, og en af dem kopierer en valgt håndfuld sider over i et andet dokument. At vide, hvad der er hvad, sparer dig for at gennemtvinge en flette-og-slette-dans, hvor et enkelt kald ville være nok.
Hvad N-up-imposition faktisk gør
Imposition er prepress-betegnelsen for at arrangere flere kildesider på ét større ark, så det trykte og foldede resultat læses i den rigtige rækkefølge. Den daglige version er 2-up-uddelingsmaterialet, 4-up-hæftesignaturen eller kontaktsiden, der passer til et dusin miniaturebilleder på en side. PDFium håndterer geometrien via et enkelt kald:
function ImportNPagesToOne(
OutputWidth, OutputHeight: Single;
NumX, NumY : Cardinal): TPdf;
NumX og NumY beskriver gitteret. En værdi på 2, 1 placerer to kildesider side om side; 2, 2 pakker fire ind i et kvadrant-layout; 4, 3 bygger en 12-up kontaktside. PDFium læser kildesiderne i rækkefølge, skalerer hver enkelt ned for at passe til cellen og udfylder gitteret fra venstre mod højre, top til bund, og starter et nyt output-ark, når det aktuelle gitter er fuldt. Kildesiderne ændres ikke. Det, du får tilbage, er et nyt dokument, hvis sider er sammensatte.
Outputstørrelsen er i punkter, ikke pixels
OutputWidth og OutputHeight er PDF-brugerenheder, og en PDF-brugerenhed er ét punkt, hvilket er en tooghalvfjerdsindstyvendedel af en tomme. Enheden angiver den fysiske størrelse af output-arket, og den har intet at gøre med skærmpixels eller renderings-DPI. Dette er det absolut mest almindelige sted at tage fejl af imposition, fordi en udvikler, der er vant til bitmaps, griber ud efter et pixelantal og ender med et ark på størrelse med et frimærke eller et billboard.
De tal, der er værd at huske, er de to sidestørrelser, du vil bruge mest. US Letter er 612 gange 792 punkter, fordi 8,5 tommer gange 72 is 612, og 11 tommer gange 72 er 792. A4 is cirka 595 gange 842 punkter, baseret på dens dimensioner på 210 gange 297 millimeter. Bindingens egen overskrift angiver reglen tydeligt, at én enhed er en tooghalvfjerdsindstyvendedel af en tomme, og enheden leverer en PointsPerInch-konstant lig med 72, hvis du hellere vil beregne en størrelse fra tommer i kode frem for at skrive værdien direkte.
const
LetterW = 612.0; // 8.5 in * 72
LetterH = 792.0; // 11 in * 72
var
Source, Composite: TPdf;
begin
Source := TPdf.Create(nil);
Composite := nil;
try
Source.FileName := 'slides.pdf';
Source.Active := True;
// Four source pages per Letter sheet, 2 by 2 grid.
Composite := Source.ImportNPagesToOne(LetterW, LetterH, 2, 2);
if Composite = nil then
raise Exception.Create('PDFium rejected the imposition arguments');
Composite.SaveAs('slides-4up.pdf');
finally
Composite.Free; // see the next section: this is mandatory
Source.Free;
end;
end;
Det returnerede håndtag er dit at frigøre
Læs signaturen igen. ImportNPagesToOne returnerer en TPdf, ikke en Boolean. Den returværdi er et helt nyt dokumenthåndtag, allokeret separat fra kilden, og kalderen ejer det. Den kilde-TPdf, du kaldte metoden på, er uberørt og ejer stadig sit eget håndtag; det sammensatte er et andet, uafhængigt objekt. Hvis du lader den returnerede TPdf gå ud af scope uden at frigøre den, lækker du et helt PDFium-dokument.
Den mere farlige fejl går den anden vej. Nedenunder beder metoden PDFium om en ny FPDF_DOCUMENT via FPDF_ImportNPagesToOne, og pakker derefter det rå håndtag ind i den returnerede TPdf, så wrapperens levetid styrer håndtagets. Fra dette tidspunkt er der præcis én ejer af håndtaget, og præcis ét sted, det skal lukkes: når du kalder Free på det returnerede objekt. En skødesløs fejlsti, der både frigør wrapperen og også kalder FPDF_CloseDocument på det rå håndtag, den fangede, lukker det samme PDFium-dokument to gange. Det er en dobbelt-frigivelse, og det er den specifikke fejl, der ramte en kalder her én gang. Reglen, der forhindrer det, er kort. Luk kun dokumentet på én sti ved at frigøre den TPdf, som metoden gav dig, og ræk aldrig forbi wrapperen for at lukke håndtaget, den allerede har overtaget.
To følgevirkninger udspringer af dette. For det første returnerer metoden nil, når PDFium afviser argumenterne, såsom et nul på en gitterakse eller en allokeringsfejl, så en nil-kontrol hører hjemme, før du rører ved resultatet. For det andet skal du initialisere din outputvariabel to nil før try og frigøre den i finally, som eksemplet ovenfor gør, så en fejl midtvejs ikke kan efterlade dig med at frigøre en udefineret reference eller springe frigivelsen helt over.
Omorganisering af sider uden at genskrive dem
Imposition bygger et nyt dokument. Omorganisering ændrer et dokument på stedet. MovePages løfter et sæt sider ud af deres nuværende positioner og placerer dem på en destination, og flytter alt andet rundt om den flyttede blok, så sideantallet forbliver det samme:
function MovePages(
const PageIndices: array of Integer;
DestPageIndex : Integer): Boolean;
Indekserne er nulbaserede. PageIndices viser de sider, der skal flyttes, i den rækkefølge, de skal ende i, og DestPageIndex er det indeks, som den første flyttede side lander på, efter at flytningen er faldet på plads. Fordi PDFium flytter siderne i stedet for at kopiere og genkomprimere deres indhold, er handlingen billig og tabsfri: sideobjekterne beholder deres strømme, deres ressourcer og deres troskab. Dette er kaldet bag et træk-for-at-omarrangere-sidepanel, hvor en bruger trækker et miniaturebillede til en ny plads, og du udfører den nye rækkefølge med ét flyt. Det returnerer False, når et indeks er uden for rækkevidde, så valider resultatet i stedet for at antage, at omarrangeringen lykkedes.
var
Doc: TPdf;
begin
Doc := TPdf.Create(nil);
try
Doc.FileName := 'report.pdf';
Doc.Active := True;
// Move the last page (index 4 in a 5-page file) to the very front.
if not Doc.MovePages([4], 0) then
raise Exception.Create('MovePages rejected the index');
Doc.SaveAs('report-reordered.pdf');
finally
Doc.Free;
end;
end;
Udtrækning af et undersæt efter indeks
Den tredje handling kopierer et eksplicit sæt sider fra ét dokument over i et andet. ImportPagesByIndex tager kildedokumentet og et nulbaseret indeksarray og indsætter disse sider i målet på en valgt position:
function ImportPagesByIndex(
Source : TPdf;
const PageIndices: array of Integer;
InsertAt : Integer= 0): Boolean;
Du kalder det på måldokumentet og sender kilden som det første argument. PageIndices angiver de kildesider, der skal trækkes ud, i den rækkefølge, du ønsker dem; InsertAt er den nulbaserede plads i målet, hvor den første importerede side lander, så 0 placerer dem før den eksisterende første side, og målets nuværende sideantal tilføjes. Et tomt array importerer hver side, hvilket gør kaldet til en fuld kopi, når du har brug for en. Det returnerer False, hvis et indeks er uden for rækkevidde i kilden.
Det er her, kontrasten til opdeling er vigtig. Opdeling skriver separate filer, hvor én handling producerer mange output på disken. ImportPagesByIndex gør det modsatte stykke arbejde: det samler et valgt sæt sider i et enkelt måldokument i hukommelsen, som du derefter gemmer én gang. Når opgaven er "giv mig side 3, 7 og 12 som én kort PDF", dette er den direkte vej, og den pakker FPDF_ImportPagesByIndex ind nedenunder.
var
Source, Excerpt: TPdf;
begin
Source := TPdf.Create(nil);
Excerpt := TPdf.Create(nil);
try
Source.FileName := 'manual.pdf';
Source.Active := True;
Excerpt.CreateDocument; // start an empty target
// Pull pages 3, 7 and 12 (zero-based 2, 6, 11) into the excerpt.
if not Excerpt.ImportPagesByIndex(Source, [2, 6, 11], 0) then
raise Exception.Create('A requested page index is out of range');
Excerpt.SaveAs('manual-excerpt.pdf');
finally
Excerpt.Free;
Source.Free;
end;
end;
Samling på en ren måde
Ende-til-ende-formen er den samme på tværs af alle tre: Åbn kilden ved at indstille FileName og skifte Active til True, udfør handlingen, gem med SaveAs og frigør det, du ejer. Den eneste gren, der kræver omhu, er, hvilke kald der allokerer et nyt dokument. MovePages ændrer det dokument, du allerede har, så der er ét objekt at frigøre. ImportPagesByIndex skriver ind i et mål, du selv har oprettet, så du frigør kilden og det mål, du åbnede. ImportNPagesToOne er undtagelsen, fordi det nye dokument er metodens returværdi frem for noget, du selv har konstrueret, og hvis man glemmer, at det er et separat, kalder-ejet håndtag, er det sådan, at både lækagen og dobbelt-frigivelsen sker. Initialiser resultatet til nil, kontroller det efter kaldet, og frigør det på en enkelt sti.
Hvis det arbejde, du rent faktisk har, er at kombinere hele filer i stedet for at omarrangere sider, se fletning af flere PDF-filer til ét dokument. Hvis det er det omvendte, at opdele ét dokument i flere filer, se opdeling af PDF-dokumenter i flere filer. De impositions- og omorganiseringsmetoder, der er beskrevne her, leveres som en del af PDFium Component til Delphi og C++Builder sammen med API'erne til indlæsning, rendering og redigering, der er dækket andre steder på denne blog.