Sujungimas ir padalijimas yra dvi puslapių operacijos, kurių visi griebiasi pirmiausia, ir jos apima daug sričių. Tačiau jos neapima visko. Yra atskira darbų grupė, kuri pertvarko puslapius, o ne perkelia ištisus failus: išdėstyti keturias skaidres viename lape dalomajai medžiagai, nutempti puslapį iš dokumento pabaigos į pradžią arba ištraukti 3, 7 ir 12 puslapius į trumpą ištrauką neliečiant likusios dalies. PDFium siūlo tris metodus būtent tam, ir kiekvienas iš jų veikia kitaip nei jums jau žinomi sujungimo ir padalijimo būdai. Šiame straipsnyje apžvelgiama, ką jie daro, kur yra išvesties taškai ir viena nuosavybės detalė, kuri praktikoje buvo sukėlusi programos lūžimą.
Šie trys metodai yra: ImportNPagesToOne – N-up puslapių išdėstymui, MovePages – tvarkos keitimui vietoje ir ImportPagesByIndex – poaibio ištraukimui. Sujungimas sudeda dokumentus vieną po kito, todėl galutinis puslapių skaičius lygus įvesčių sumai. Padalijimas įrašo kelis išvesties failus iš vieno įvesties failo. Trys čia aprašytos operacijos yra tarpinės: viena iš jų keičia, kiek šaltinio puslapių dalijasi vienu lape, kita keičia puslapių tvarką viename dokumente, o trečioji nukopijuoja pasirinktus puslapius į kitą dokumentą. Žinodami, kuris yra kuris, išvengsite sujungimo ir trynimo procedūrų, kai pakaktų tik vieno iškvietimo.
Ką iš tikrųjų daro N-up puslapių išdėstymas
Puslapių išdėstymas (imposition) yra spaudos paruošimo terminas, reiškiantis kelių šaltinio puslapių išdėstymą viename didesniame lape taip, kad atspausdintas ir sulankstytas rezultatas būtų skaitomas teisinga tvarka. Kasdienė versija yra dviejų puslapių (2-up) dalomoji medžiaga, keturių puslapių (4-up) brošiūra arba kontaktinis lapas, kuriame telpa keliolika miniatiūrų. PDFium valdo šią geometriją vienu iškvietimu:
function ImportNPagesToOne(
OutputWidth, OutputHeight: Single;
NumX, NumY : Cardinal): TPdf;
NumX ir NumY aprašo tinklelį. Reikšmė 2, 1 išdėsto du šaltinio puslapius šalia vienas kito; 2, 2 supakuoja keturis į kvadranto išdėstymą; 4, 3 sukuria dvylikos puslapių kontaktinį lapą. PDFium nuskaito šaltinio puslapius iš eilės, sumažina kiekvieną iš jų, kad tilptų į savo langelį, ir užpildo tinklelį iš kairės į dešinę, iš viršaus į apačią, pradėdamas naują išvesties lapą, kai tik esamas tinklelis užpildomas. Šaltinio puslapiai nekeičiami. Atgal gaunate naują dokumentą, kurio puslapiai yra sudėtiniai.
Išvesties dydis nurodomas taškais (points), o ne pikseliais
OutputWidth ir OutputHeight yra PDF naudotojo vienetai, o PDF naudotojo vienetas yra vienas taškas (point), kuris sudaro 1/72 colio dalį. Šis vienetas nurodo fizinį išvesties lapo dydį ir netui nieko bendra su ekrano pikseliais ar vaizdavimo DPI. Tai yra dažniausia vieta, kur suklystama atliekant puslapių išdėstymą, nes kūrėjas, pripratęs prie rastrinių vaizdų (bitmaps), nurodo pikselių skaičių ir gauna pašto ženklo arba stendo dydžio lapą.
Skaičiai, kuriuos verta įsiminti, yra du dažniausiai naudojami puslapių dydžiai. US Letter yra 612 iš 792 taškų (nes 8.5 colio padauginus iš 72 yra 612, o 11 colių padauginus iš 72 yra 792). A4 dydis yra maždaug 595 iš 842 taškų, pagal jo 210 iš 297 milimetrų matmenis. Paties susiejimo antraštėje aiškiai nurodyta taisyklė, kad vienas vienetas yra 1/72 colio dalis, o modulyje pateikiama PointsPerInch konstanta, lygi 72, jei norite dydį apskaičiuoti kode iš colių, o ne rašyti skaičių.
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;
Gautą deskriptorių privalote atlaisvinti patys
Perskaitykite parašą dar kartą. ImportNPagesToOne grąžina TPdf objektą, o ne Boolean reikšmę. Ši grąžinama reikšmė yra visiškai naujas dokumento deskriptorius, išskirtas atskirai nuo šaltinio, ir jo savininkas yra iškvietėjas. Šaltinio TPdf, kuriam iškvietėte metodą, lieka nepaliestas ir vis dar valdo savo deskriptorių; sudėtinis dokumentas yra antras, nepriklausomas objektas. Jei leisite grąžintam TPdf išeiti iš matomumo zonos jo neatlaisvinę, nutekinsite visą PDFium dokumentą.
Pavojingesnė klaida yra priešinga. Viduje šis metodas paprašo PDFium sukurti naują FPDF_DOCUMENT per FPDF_ImportNPagesToOne, tada apgaubia šį neapdorotą deskriptorių grąžinamame TPdf objekte, kad apvalkalo gyvavimo ciklas valdytų deskriptorių. Nuo to momento yra tik vienas deskriptoriaus savininkas ir tik viena vieta, kur jis turėtų būti uždarytas – kai iškviečiate grąžinto objekto Free. Nerūpestingas klaidos kelias, kuris atlaisvina apvalkalą ir kartu iškviečia FPDF_CloseDocument neapdorotam deskriptoriui, uždaro tą patį PDFium dokumentą du kartus. Tai yra dvigubas atlaisvinimas, ir tai yra konkreti klaida, su kuria naudotojas čia kartą susidūrė. Taisyklė, kuri to išvengia, yra trumpa. Uždarykite dokumentą tik vienu keliu – atlaisvindami TPdf objektą, kurį jums perdavė metodas, ir niekada nebandykite uždaryti deskriptoriaus tiesiogiai, aplenkdami jį jau perėmusį apvalkalą.
Iš to seka dvi išvados. Pirma, metodas grąžina nil, kai PDFium atmeta argumentus, pavyzdžiui, nurodžius nulį bet kurioje tinklelio ašyje arba įvykus atminties paskyrimo klaidai, todėl nil patikra privaloma prieš paliečiant rezultatą. Antra, inicializuokite savo išvesties kintamąjį kaip nil prieš try bloką ir atlaisvinkite jį finally sekcijoje, kaip parodyta pavyzdyje viršuje, kad klaida proceso viduryje nepaliktų jūsų bandančio atlaisvinti neapibrėžtos nuorodos arba išvis praleisti atlaisvinimą.
Puslapių tvarkos keitimas jų neperrašant
Puslapių išdėstymas sukuria naują dokumentą. Tvarkos keitimas keičia tą patį dokumentą vietoje. MovePages perkelia puslapių rinkinį iš esamų pozicijų ir padeda juos paskirties vietoje, pastumdamas visa kita aplink perkeltą bloką taip, kad puslapių skaičius liktų toks pat:
function MovePages(
const PageIndices: array of Integer;
DestPageIndex : Integer): Boolean;
Indeksai yra nuliniai (zero-based). PageIndices išvardija perkeliamus puslapius ta tvarka, kuria jie turėtų išsidėstyti, o DestPageIndex yra indeksas, kuriame nusileidžia pirmasis perkeltas puslapis po perkėlimo. Kadangi PDFium perkelia puslapius, o ne kopijuoja ir iš naujo suspaudžia jų turinį, ši operacija yra greita ir nepraranda duomenų: puslapio objektai išlaiko savo srautus, išteklius bei kokybę. Tai yra iškvietimas, naudojamas puslapių tempimo ir pertvarkymo skydelyje, kur naudotojas pertempia miniatiūrą į naują vietą, o kai jūs patvirtinate naują tvarką vienu žingsniu. Metodas grąžina False, jei indeksas yra už ribų, todėl patikrinkite rezultatą, užuot darę prielaidą, kad tvarka pasikeitė sėkmingai.
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;
Poaibio ištraukimas pagal indeksą
Trečioji operacija nukopijuoja tam tikrą puslapių rinkinį iš vieno dokumento į kitą. ImportPagesByIndex paima šaltinio dokumentą ir nuliniu indeksu pagrįstą indeksų masyvą bei įterpia šiuos puslapius į tikslinį dokumentą pasirinktoje pozicijoje:
function ImportPagesByIndex(
Source : TPdf;
const PageIndices: array of Integer;
InsertAt : Integer= 0): Boolean;
Šį metodą iškviečiate tiksliniam dokumentui ir perduodate šaltinį kaip pirmąjį argumentą. PageIndices nurodo šaltinio puslapius, kuriuos reikia paimti, norima tvarka; InsertAt is the zero-based slot in the target where the first imported page goes, so 0 places them before the existing first page and the target's current page count appends. An empty array imports every page, which makes the call a full copy when you need one. It returns False if any index is out of range in the source.
Čia svarbus skirtumas nuo padalijimo (split) operacijos. Padalijimas įrašo atskirus failus diske. ImportPagesByIndex atlieka priešingą darbą: ji surenka pasirinktus puslapius į vieną tikslinį dokumentą atmintyje, kurį vėliau išsaugote vieną kartą. Kai užduotis yra „pateikti 3, 7 ir 12 puslapius kaip vieną trumpą PDF failą“, tai yra tiesiausias kelias, o po juo slepiasi FPDF_ImportPagesByIndex iškvietimas.
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;
Tvarkingas apjungimas
Visų trijų metodų eiga yra panaši: atidarykite šaltinį nustatydami FileName ir nustatydami Active į True, atlikite operaciją, išsaugokite naudodami SaveAs ir atlaisvinkite tai, kas jums priklauso. Vienintelė šaka, kuriai reikia atsargumo, yra ta, kuri išskiria naują dokumentą. MovePages keičia dokumentą, kurį jau turite, todėl reikia atlaisvinti tik vieną objektą. ImportPagesByIndex rašo į tikslinį dokumentą, kurį sukūrėte patys, todėl atlaisvinate ir šaltinį, ir tikslinį dokumentą. ImportNPagesToOne yra išskirtinis atvejis, nes naujas dokumentas yra metodo grąžinama reikšmė, o ne kažkas, ką patys sukūrėte. Pamiršus, kad tai yra atskiras, iškvietėjui priklausantis deskriptorius, atsiranda tiek nutekėjimai, tiek dvigubi atlaisvinimai. Inicializuokite rezultatą kaip nil, patikrinkite jį po iškvietimo ir atlaisvinkite tik vienu keliu.
If the work you actually have is combining whole files rather than rearranging pages, see merging multiple PDF files into one document. If it is the reverse, breaking one document into several files, see splitting PDF documents into multiple files. The imposition and reordering methods described here ship as part of the PDFium Component for Delphi and C++Builder, alongside the loading, rendering, and editing APIs covered elsewhere on this blog.