Technical Article

N-up-asemointi ja sivujen uudelleenjärjestely PDFium-komponentilla

Yhdistäminen ja jakaminen ovat kaksi sivuoperaatiota, joihin kaikki tarttuvat ensimmäisenä, ja ne kattavat paljon alaa. Ne eivät kata kaikkea. On olemassa erillinen perhe töitä, jotka järjestävät sivuja uudelleen sen sijaan, että siirtäisivät kokonaisia tiedostoja: aseta neljä diaa yhdelle arkille jaettavaksi materiaaliksi, vedä sivu dokumentin takaosasta etuosaan tai vedä sivut 3, 7 ja 12 lyhyeksi otteeksi koskematta muihin. PDFium tarjoaa kolme menetelmää juuri tähän, ja jokainen niistä käyttäytyy eri tavalla kuin jo tuntemasi yhdistäminen ja jakaminen. Tämä artikkeli käy läpi mitä ne tekevät, missä tulospisteet sijaitsevat ja yhden omistajuusyksityiskohdan, joka on aiheuttanut kaatumisen käytännössä.

Nämä kolme ovat ImportNPagesToOne N-up-asemointiin, MovePages paikalliseen uudelleenjärjestelyyn ja ImportPagesByIndex osajoukon poimimiseen. Yhdistäminen pinoaa dokumentit päästä päähän ja jättää sivumäärän yhtä suureksi kuin syötteiden summa. Jakaminen kirjoittaa useita tulostiedostoja yhdestä syötteestä. Nämä kolme operaatiota sijoittuvat niiden väliin: yksi niistä muuttaa sitä, kuinka monta lähdesivua jakaa arkin, yksi niistä muuttaa järjestystä yhden dokumentin sisällä, ja yksi niistä kopioi valitun kourallisen sivuja toiseen dokumenttiin. Tietäen mikä on mikä, säästyt pakotetulta yhdistä-ja-poista-tanssilta, jossa yksittäinen kutsu riittäisi.

Mitä N-up-asemointi todellisuudessa tekee

Asemointi (imposition) on painoalan termi useiden lähdesivujen asettamiselle yhdelle suuremmalle arkille siten, että painettu ja taitettu tulos luetaan oikeassa järjestyksessä. Jokapäiväinen versio on 2-up-moniste, 4-up-kirjasen taitto tai vedosarkki, joka mahduttaa tusinan pikkukuvaa sivulle. PDFium käsittelee geometriaa yhden kutsun kautta:

function ImportNPagesToOne(
  OutputWidth, OutputHeight: Single;
  NumX, NumY               : Cardinal): TPdf;

NumX ja NumY kuvailevat ruudukkoa. Arvo 2, 1 asettaa kaksi lähdesivua rinnakkain; 2, 2 pakkaa neljä neljännesleiskaan; 4, 3 rakentaa kaksitoista-up-vedosarkin. PDFium lukee lähdesivut järjestyksessä, skaalaa jokaisen alas sopimaan soluunsa ja täyttää ruudukon vasemmalta oikealle, ylhäältä alas, aloittaen uuden tulosarkin aina, kun nykyinen ruudukko on täynnä. Lähdesivuja ei muokata. Takaisin saat uuden dokumentin, jonka sivut ovat komposiitteja.

Tulostuskoko on pisteinä, ei pikseleinä

OutputWidth ja OutputHeight ovat PDF:n käyttäjäyksiköitä, ja PDF:n käyttäjäyksikkö on yksi piste, joka on yksi 72-osa tuumasta. Yksikkö ilmoittaa tulosarkin fyysisen koon, eikä sillä ole mitään tekemistä näytön pikselien tai renderöinti-DPI:n kanssa. Tämä on yleisin paikka tehdä virhe asemoinnissa, koska bittikarttoihin tottunut kehittäjä tarttuu pikselimäärään ja päätyy arkkiin, joka on postimerkin tai mainostaulun kokoinen.

Muistamisen arvoisia lukuja ovat kaksi eniten käyttämääsi sivukokoa. US Letter on 612 x 792 pistettä, koska 8,5 tuumaa kertaa 72 on 612 ja 11 tuumaa kertaa 72 on 792. A4 on karkeasti 595 x 842 pistettä, sen 210 x 297 millimetrin mitoista. Sidoksen oma otsikko ilmoittaa säännön selvästi, että yksi yksikkö on yksi 72-osa tuumasta, ja yksikkö toimittaa PointsPerInch-vakion, joka on 72, jos lasket koon mieluummin tuumista koodissa kuin kirjoitat literaalin.

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 on sinun vapautettavissasi

Lue allekirjoitus uudelleen. ImportNPagesToOne palauttaa TPdf-olion, ei totuusarvoa. Kyseinen paluuarkki on upouusi dokumenttikahva, joka on varattu erillään lähteestä, ja kutsuja omistaa sen. Lähde-TPdf, jolle kutsuit metodia, on koskematon ja omistaa edelleen oman kahvansa; komposiitti on toinen, itsenäinen olio. Jos annat palautetun TPdf-olion mennä elinkaarensa ulkopuolelle vapauttamatta sitä, vuodat kokonaisen PDFium-dokumentin.

Vaarallisempi virhe toimii toiseen suuntaan. Pinnan alla metodi pyytää PDFiumilta tuoreen FPDF_DOCUMENT-kahvan FPDF_ImportNPagesToOne-funktion kautta, ja käärii sitten kyseisen raakahahmon palautetun TPdf-olion sisään, jotta kääreen elinikä hallitsee kahvan elinikää. Siitä eteenpäin kahvalla on täsmälleen yksi omistaja ja täsmälleen yksi paikka, jossa se tulisi sulkea: kun vapautat (Free) palautetun olion. Huolimaton virhepolku, joka sekä vapauttaa kääreen että kutsuu FPDF_CloseDocument-funktiota sen kaappaamalle raakahahmolle, sulkee saman PDFium-dokumentin kahdesti. Kyseessä on kaksinkertainen vapauttaminen, ja se on se tietty virhe, joka iski kutsujaan täällä kerran. Sääntö, joka estää sen, on lyhyt. Sulje dokumentti vain yhdellä polulla, vapauttamalla metodi sinulle antama TPdf, äläkä koskaan kurota kääreen ohi sulkeaksesi kahvan, jonka se on jo omaksunut.

Tästä seuraa kaksi johtopäätöstä. Ensinnäkin metodi palauttaa nil, kun PDFium hylkää argumentit, kuten nollan kummallakin ruudukon akselilla tai varausvirheen, joten nil-tarkistus kuuluu tehdä ennen kuin kosket tulokseen. Toiseksi alusta tulosmuuttujasi arvoon nil ennen try-lohkoa ja vapauta se finally-lohkossa, kuten yllä oleva näyte tekee, jotta puolivälissä tapahtuva epäonnistuminen ei voi jättää sinua vapauttamaan määrittelemätöntä viitettä tai ohittamaan vapauttamista kokonaan.

Sivujen uudelleenjärjestely ilman niiden uudelleenkirjoittamista

Asemointi rakentaa uuden dokumentin. Uudelleenjärjestely muuttaa yhtä dokumenttia paikallaan. MovePages nostaa joukon sivuja nykyisistä sijainneistaan ja pudottaa ne kohteeseen, siirtäen kaikkea muuta siirretyn lohkon ympärillä siten, että sivumäärä pysyy samana:

function MovePages(
  const PageIndices: array of Integer;
  DestPageIndex    : Integer): Boolean;

Indeksit ovat nollapohjaisia. PageIndices luettelee siirrettävät sivut siinä järjestyksessä, johon niiden pitäisi päätyä, ja DestPageIndex is index the first moved page lands on after the move settles. Koska PDFium siirtää sivut sen sijaan, että kopioisi ja pakkaisi niiden sisällön uudelleen, operaatio on halpa ja häviötön: sivuoliot säilyttävät virtansa, resurssinsa ja uskollisuutensa. Tämä on kutsu vedä-ja-järjestä-sivupaneelin takana, jossa käyttäjä vetää pikkukuvan uuteen paikkaan ja sitoudut uuteen järjestykseen yhdellä siirrolla. Se palauttaa arvon False, kun indeksi on alueen ulkopuolella, joten validoi tulos sen sijaan, että olettaisit uudelleenjärjestelyn onnistuneen.

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 poimiminen indeksin mukaan

Kolmas operaatio kopioi nimenomaisen joukon sivuja yhdestä dokumentista toiseen. ImportPagesByIndex ottaa lähdedokumentin ja nollapohjaisen indeksitaulukon ja lisää kyseiset sivut kohteeseen valittuun sijaintiin:

function ImportPagesByIndex(
  Source           : TPdf;
  const PageIndices: array of Integer;
  InsertAt         : Integer= 0): Boolean;

Kutsut sitä kohdedokumentille ja välität lähteen ensimmäisenä argumenttina. PageIndices nimeää vedettävät lähdesivut siinä järjestyksessä kuin haluat ne; InsertAt on nollapohjainen paikka kohteessa, johon ensimmäinen tuotu sivu menee, joten 0 asettaa ne ennen olemassa olevaa ensimmäistä sivua ja kohteen nykyinen sivumäärä kasvaa. Tyhjä taulukko tuo jokaisen sivun, mikä tekee kutsusta täydellisen kopion silloin, kun tarvitset sellaisen. Se palauttaa arvon False, jos jokin indeksi on alueen ulkopuolella lähteessä.

Tässä kohdassa ero jakamiseen (split) on merkitsevä. Jakaminen kirjoittaa erillisiä tiedostoja, jolloin yksi operaatio tuottaa useita tulosteita levylle. ImportPagesByIndex tekee vastakkaisen muotoisen työn: se kerää valitun joukon sivuja yhdeksi kohdedokumentiksi muistiin, jonka tallennat sitten kerran. Kun tehtävä on "anna minulle sivut 3, 7 ja 12 yhtenä lyhyenä PDF-tiedostona", tämä on suora reitti, ja se käärii FPDF_ImportPagesByIndex-funktion allaan.

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 siististi

Päästä päähän -muoto on sama kaikissa kolmessa: avaa lähde asettamalla FileName ja kytkemällä Active arvoon True, suorita operaatio, tallenna SaveAs-metodilla ja vapauta se minkä omistat. Yksi haara, joka vaatii huomiota, on se, mitkä kutsut varaavat uuden dokumentin. MovePages muuttaa jo hallussasi olevaa dokumenttia, joten vapautettavia olioita on yksi. ImportPagesByIndex kirjoittaa itse luomaasi kohteeseen, joten vapautat lähteen ja avaamasi kohteen. ImportNPagesToOne on poikkeus, koska uusi dokumentti on metodin paluuarkki eikä jotakin rakentamaasi, ja sen unohtaminen, että kyseessä on erillinen, kutsujan omistama kahva, on tapa, jolla sekä vuoto että kaksinkertainen vapauttaminen tapahtuvat. Alusta tulos arvoon nil, tarkista se kutsun jälkeen ja vapauta se vain yhdellä polulla.

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.