Yhdistäminen (merge) ja jakaminen (split) ovat ne kaksi sivutoimintoa, joihin kaikki tarttuvat ensimmäisenä, ja ne kattavat paljon alaa. Ne eivät kata kaikkea. On olemassa erillinen työperhe (family of work), joka järjestelee sivuja uudelleen sen sijaan, että siirrettäisiin kokonaisia tiedostoja: aseta neljä diaa yhdelle arkille monisteeksi (handout), vedä sivu asiakirjan lopusta alkuun, tai vedä (pull) sivut 3, 7 ja 12 lyhyeksi otteeksi (excerpt) koskematta loppuosaan. PDFium paljastaa (exposes) kolme metodia juuri tätä varten, ja jokainen niistä käyttäytyy eri tavalla kuin se yhdistäminen ja jakaminen, jonka jo tunnet. Tämä artikkeli käy läpi, mitä ne tekevät, missä tulostepisteet (output points) asuvat, ja yhden omistajuusyksityiskohdan (ownership detail), joka on aiheuttanut kaatumisen kentällä
Nämä kolme ovat ImportNPagesToOne N-up-asemointia (N-up imposition) varten, MovePages paikan päällä tapahtuvaa uudelleenjärjestelyä (in-place reordering) varten, ja ImportPagesByIndex osajoukon poimintaa (subset extraction) varten. Yhdistäminen (merge) pinoaa asiakirjat peräkkäin (end to end) ja jättää sivumäärän syötteiden summaa vastaavaksi. Jakaminen kirjoittaa useita tulostetiedostoja yhdestä syötteestä (input). Nämä kolme toimintoa sijoittuvat näiden väliin: yksi niistä muuttaa sitä, kuinka monta lähdesivua (source pages) jakaa yhden arkin (sheet), yksi niistä muuttaa järjestystä yhden asiakirjan sisällä, ja yksi niistä kopioi valitun kourallisen sivuja toiseen asiakirjaan. Sen tietäminen, mikä on mikä, säästää sinut pakottamasta yhdistä-ja-poista-tanssia (merge-and-delete dance), kun yksittäinen kutsu (single call) riittäisi
Mitä N-up-asemointi (N-up imposition) oikeastaan tekee
Asemointi (imposition) on prepress-termi (prepress term) sille, että useita lähdesivuja (source pages) järjestellään yhdelle suuremmalle arkille (sheet) siten, että tulostettu ja taitettu tulos luetaan oikeassa järjestyksessä. Arkinen versio on 2-up-moniste (handout), 4-up-vihkonen (booklet signature) tai pinnakkaisarkki (contact sheet), johon mahtuu tusina pikkukuvaa (thumbnails) yhdelle sivulle. PDFium käsittelee geometriaa yhdellä kutsulla:
function ImportNPagesToOne(
OutputWidth, OutputHeight: Single;
NumX, NumY : Cardinal): TPdf;
NumX ja NumY kuvaavat ruudukkoa (grid). Arvo 2, 1 asettaa kaksi lähdesivua (source pages) vierekkäin; 2, 2 pakkaa neljä neljänneksen (quadrant) asetteluun; 4, 3 rakentaa 12-up-pinnakkaisarkin (contact sheet). PDFium lukee lähdesivut järjestyksessä, skaalaa (scales down) jokaisen sopimaan soluunsa (cell), ja täyttää ruudukon vasemmalta oikealle, ylhäältä alas, aloittaen uuden tulostearkin (output sheet) aina, kun nykyinen ruudukko on täynnä. Lähdesivuja ei muokata. Se, mitä saat takaisin, on uusi asiakirja, jonka sivut ovat yhdistelmiä (composites)
Tulostekoko on pisteissä (points), ei pikseleissä
OutputWidth ja OutputHeight ovat PDF-käyttäjäyksiköitä (user units), ja yksi PDF-käyttäjäyksikkö on yksi piste (point), mikä on yksi seitsemäskymmeneskahdesosa tuumaa (one seventy-second of an inch). Yksikkö ilmoittaa tulostearkin (output sheet) fyysisen koon, eikä sillä ole mitään tekemistä näytön pikseleiden tai renderöinti-DPI:n (render DPI) kanssa. Tämä on yksittäinen yleisin paikka saada asemointi (imposition) väärin, koska bittikarttoihin (bitmaps) tottunut kehittäjä kurottaa pikselimäärään ja päätyy saamaan arkin (sheet), joka on postimerkin tai mainostaulun (billboard) kokoinen
Muistamisen arvoiset luvut ovat ne kaksi sivukokoa, joita tulet käyttämään eniten. US Letter on 612 kertaa 792 pistettä (points), koska 8,5 tuumaa kertaa 72 on 612 ja 11 tuumaa kertaa 72 on 792. A4 on karkeasti 595 kertaa 842 pistettä, mikä johtuu sen 210 kertaa 297 millimetrin mitoista. Sidoksen (binding) oma otsikkotiedosto (header) esittää säännön selvästi, että yksi yksikkö on yksi seitsemäskymmeneskahdesosa tuumaa, ja yksikkö toimittaa PointsPerInch-vakion, joka on yhtä kuin 72, jos lasket koon mieluummin tuumista koodissa kuin kirjoitat literaalin (literal)
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;
Palautettu kahva (returned handle) on sinun vapautettavissasi
Lue allekirjoitus (signature) uudelleen. ImportNPagesToOne palauttaa TPdf:n, ei totuusarvoa (Boolean). Tuo paluuarvo (return value) on upouusi asiakirjakahva (document handle), joka on varattu (allocated) erillään lähteestä (source), ja kutsuja (caller) omistaa sen. Lähde-TPdf, johon metodia kutsuit, on koskematon ja se omistaa yhä oman kahvansa; yhdistelmä (composite) on toinen, itsenäinen objekti. Jos annat palautetun TPdf:n mennä skooppialueen (scope) ulkopuolelle vapauttamatta sitä, vuodat koko PDFium-asiakirjan
Vaarallisempi virhe kulkee toiseen suuntaan. Pinnan alla metodi pyytää PDFiumilta tuoretta FPDF_DOCUMENT-objektia FPDF_ImportNPagesToOne-kutsun kautta, ja käärii (wraps) sitten tuon raa'an kahvan (raw handle) palautetun TPdf:n sisään, jotta kääreen elinikä (lifetime) hallitsee kahvan elinikää. Siitä hetkestä lähtien kahvalla on täsmälleen yksi omistaja, ja täsmälleen yksi paikka, missä se tulisi sulkea: kun suoritat palautetun objektin vapautuksen (Free). Huolimaton virhepolku (careless error path), joka sekä vapauttaa kääreen että myös kutsuu FPDF_CloseDocument sen kaappaamalle raa'alle kahvalle, sulkee saman PDFium-asiakirjan kahdesti. Se on tuplavapautus (double-free), ja se on se erityinen bugi (specific bug), joka on puraissut kutsujaa tässä kohdassa kerran. Sääntö, joka estää sen, on lyhyt. Sulje asiakirja vain yhdellä polulla vapauttamalla TPdf, jonka metodi ojensi sinulle, äläkä koskaan kurota (reach past) kääreen ohi sulkeaksesi kahvan, jonka se jo adoptoi
Tästä seuraa kaksi päätelmää (corollaries). Ensinnäkin metodi palauttaa nil, kun PDFium hylkää argumentit, kuten nollan kummallakin ruudukon akselilla (grid axis) tai varaamisen epäonnistumisen (allocation failure), joten nil-tarkistus kuuluu tehdä ennen kuin kosket tulokseen. Toiseksi, alusta tulostemuuttujasi (output variable) arvoon nil ennen try-lohkoa ja vapauta (free) se finally-lohkossa, aivan kuten yllä oleva esimerkki tekee, jotta epäonnistuminen puolivälissä ei voi jättää sinua vapauttamaan määrittelemätöntä viittausta (undefined reference) tai ohittamaan vapautusta kokonaan
Sivujen uudelleenjärjestely (reordering) ilman niiden uudelleenkirjoitusta
Asemointi (imposition) rakentaa uuden asiakirjan. Uudelleenjärjestely (reordering) muuttaa yhtä asiakirjaa paikoillaan. MovePages nostaa (lifts) sivujoukon ulos niiden nykyisistä sijainneista (positions) ja pudottaa ne määränpäähän (destination), siirtäen kaikkea muuta (shifting everything else) siirretyn lohkon ympärillä niin, että sivumäärä (page count) pysyy samana:
function MovePages(
const PageIndices: array of Integer;
DestPageIndex : Integer): Boolean;
Indeksit (indices) ovat nollapohjaisia (zero-based). PageIndices luettelee siirrettävät sivut siinä järjestyksessä kuin mihin niiden tulisi päätyä, ja DestPageIndex on indeksi, johon ensimmäinen siirretty sivu laskeutuu siirron asettumisen jälkeen. Koska PDFium sijoittaa (relocates) sivut uudelleen sen sijaan, että se kopioisi ja pakkaisi niiden sisällön uudelleen (recompressing), toiminto on halpa ja häviötön (lossless): sivuobjektit (page objects) säilyttävät virtansa (streams), resurssinsa (resources) ja uskollisuutensa (fidelity). Tämä on kutsu (call) vedä-ja-järjestä -sivupaneelin (drag-to-reorder page panel) takana, jossa käyttäjä vetää pikkukuvan (thumbnail) uuteen koloon (slot) ja sinä teetät (commit) uuden järjestyksen yhdellä siirrolla. Se palauttaa False, kun indeksi on alueen ulkopuolella (out of range), joten validoi (validate) tulos sen sijaan, että olettaisit järjestelyn (rearrange) menneen perille
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;
Osajoukon (subset) poimiminen indeksin perusteella
Kolmas toiminto kopioi eksplisiittisen (explicit) sivujoukon (set of pages) yhdestä asiakirjasta toiseen. ImportPagesByIndex ottaa lähdeasiakirjan (source document) ja nollapohjaisen (zero-based) indeksitaulukon (index array), ja lisää nuo sivut kohteeseen (target) valittuun sijaintiin (position):
function ImportPagesByIndex(
Source : TPdf;
const PageIndices: array of Integer;
InsertAt : Integer= 0): Boolean;
Kutsut sitä kohdeasiakirjalle (target document) ja välität lähteen (source) ensimmäisenä argumenttina. PageIndices nimeää (names) poimittavat (pull) lähdesivut (source pages) siinä järjestyksessä kuin haluat ne; InsertAt on nollapohjainen kolo (zero-based slot) kohteessa, mihin ensimmäinen tuotu (imported) sivu menee, joten 0 sijoittaa ne ennen olemassa olevaa ensimmäistä sivua, ja kohteen (target) nykyinen sivumäärä (page count) lisää ne loppuun (appends). Tyhjä taulukko (empty array) tuo (imports) jokaisen sivun, mikä tekee kutsusta täyden kopion (full copy), kun tarvitset sellaista. Se palauttaa False, jos yksikään indeksi on lähteessä (source) alueen ulkopuolella (out of range)
Tässä kontrasti jakamiseen (split) on merkittävä. Jakaminen kirjoittaa erillisiä tiedostoja (separate files), ja yksi toiminto tuottaa monia tulosteita (outputs) levylle. ImportPagesByIndex tekee päinvastaisen muotoista työtä: se kokoaa (gathers) valitun joukon (chosen set) sivuja yhteen (single) kohdeasiakirjaan (target document) muistissa, jonka sitten tallennat kerran. Kun työ on "anna minulle sivut 3, 7 ja 12 yhtenä lyhyenä PDF-tiedostona", tämä on suora reitti, ja se käärii (wraps) alleen FPDF_ImportPagesByIndex-kutsun
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;
Kokoaminen puhtaasti yhteen
Päästä päähän -muoto (end-to-end shape) on sama kaikissa kolmessa: avaa lähde (source) asettamalla FileName ja vaihtamalla Active tilaan True, suorita toiminto, tallenna SaveAs-kutsulla ja vapauta se (free), minkä omistat. Ainoa haara (branch), joka kaipaa huolellisuutta, on se, mitkä kutsut varaavat (allocate) uuden asiakirjan. MovePages muuntaa (mutates) asiakirjaa, jota jo pitelet, joten vapautettavana (free) on yksi objekti. ImportPagesByIndex kirjoittaa kohteeseen (target), jonka loit itse, joten vapautat lähteen ja avaamasi kohteen. ImportNPagesToOne on poikkeus (outlier), koska uusi asiakirja on metodin paluuarvo (return value) eikä jotain, mitä itse rakensit (constructed), ja sen unohtaminen, että se on erillinen, kutsujan omistama (caller-owned) kahva (handle), on tapa, jolla sekä vuoto (leak) että tuplavapautus (double-free) tapahtuvat. Alusta tulos (result) arvoon nil, tarkista se kutsun jälkeen, ja vapauta (free) se yhdellä yksittäisellä polulla
Jos todellinen työsi on kokonaisten tiedostojen yhdistäminen sivujen uudelleenjärjestelyn (rearranging) sijaan, katso merging-multiple-pdf-files-into-one-document-with-pdfium-vcl.html, useiden PDF-tiedostojen yhdistäminen yhdeksi asiakirjaksi (merging multiple PDF files into one document). Jos kyse on päinvastaisesta, yhden asiakirjan jakamisesta (breaking) useiksi tiedostoiksi, katso splitting-pdf-documents-into-multiple-files-with-pdfium-delphi.html, PDF-asiakirjojen jakaminen useiksi tiedostoiksi (splitting PDF documents into multiple files). Tässä kuvatut asemointi- (imposition) ja uudelleenjärjestelymetodit (reordering methods) toimitetaan osana PDFium Component -komponenttia Delphiä ja C++Builderia varten lataus- (loading), renderöinti- (rendering) ja muokkaus-API:den (editing APIs) ohella, joita käsitellään muualla tässä blogissa