Technical Article

N-up nametanje i promjena redoslijeda stranica s PDFium-om

Spajanje i dijeljenje su dvije operacije sa stranicama koje svatko prvo isproba, i one pokrivaju puno toga. Me?utim, one ne pokrivaju sve. Postoji zasebna obitelj poslova koja preslaguje stranice umjesto premje?tanja cijelih datoteka: postavite ?etiri slajda na jedan list za priru?nik, povucite stranicu sa za?elja dokumenta na po?etak, ili izuvcite stranice 3, 7 i 12 u kratki izvadak bez dodirivanja ostatka. PDFium izla?e tri metode upravo za to, a svaka se pona?a druga?ije od spajanja i dijeljenja koje ve? poznajete. Ovaj ?lanak prolazi kroz to ?to one rade, gdje se nalaze izlazne to?ke i jedan detalj vlasni?tva koji je uzrokovao ru?enje u praksi

Tri metode su ImportNPagesToOne za N-up nametanje, MovePages za promjenu redoslijeda na licu mjesta i ImportPagesByIndex za ekstrakciju podskupa. Spajanje sla?e dokumente jedan na drugi i ostavlja broj stranica jednakim zbroju ulaza. Dijeljenje zapisuje nekoliko izlaznih datoteka iz jednog ulaza. Tri operacije ovdje nalaze se izme?u: jedna od njih mijenja koliko izvornih stranica dijeli list, jedna od njih mijenja redoslijed unutar jednog dokumenta, a jedna kopira odabranu ?a?icu stranica u drugi dokument. Znanje o tome koja je koja spa?ava vas od forsiranja plesa spajanja i brisanja tamo gdje bi bio dovoljan jedan poziv

?to zapravo radi N-up nametanje

Nametanje je grafi?ki pojam za raspore?ivanje nekoliko izvornih stranica na jedan ve?i list tako da se tiskani i presavijeni rezultat ?ita u ispravnom redoslijedu. Svakodnevna verzija je priru?nik s 2 stranice na jednom listu, potpis knji?ice s 4 stranice ili kontaktni list koji smje?ta desetak sli?ica na stranicu. PDFium upravlja geometrijom kroz jedan poziv

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

NumX i NumY opisuju mre?u. Vrijednost 2, 1 postavlja dvije izvorne stranice jednu pored druge; 2, 2 pakira ?etiri u raspored kvadranta; 4, 3 gradi kontaktni list s dvanaest stranica. PDFium ?ita izvorne stranice redom, smanjuje svaku kako bi stala u svoju ?eliju i popunjava mre?u s lijeva na desno, odozgo prema dolje, zapo?inju?i novi izlazni list kad god se trenutna mre?a popuni. Izvorne stranice se ne mijenjaju. Ono ?to dobivate natrag je novi dokument ?ije su stranice kompoziti

Veli?ina izlaza je u to?kama, a ne u pikselima

OutputWidth i OutputHeight su korisni?ke jedinice PDF-a, a korisni?ka jedinica PDF-a je jedna to?ka, ?to je jedna sedamdeset i druga in?a. Jedinica deklarira fizi?ku veli?inu izlaznog lista i nema nikakve veze s pikselima zaslona ili DPI-jem prikaza. Ovo je naj?e??e mjesto na kojem se grije?i u nametanju, jer programer naviknut na bitmape pose?e za brojem piksela i zavr?i s listom veli?ine po?tanske marke ili jumbo plakata

Brojevi koje vrijedi zapamtiti su dvije veli?ine stranica koje ?ete najvi?e koristiti. US Letter je 612 puta 792 to?ke, jer je 8,5 in?a puta 72 jednako 612, a 11 in?a puta 72 je 792. A4 je otprilike 595 puta 842 to?ke, iz svojih dimenzija 210 puta 297 milimetara. Zaglavlje samog vezanja jasno navodi pravilo da je jedna jedinica jedna sedamdeset i druga in?a, a jedinica isporu?uje konstantu PointsPerInch jednaku 72 ako biste radije izra?unali veli?inu iz in?a u kodu nego pisali 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;

Vra?ena ru?ka je va?a za osloba?anje

Pro?itajte ponovno potpis. ImportNPagesToOne vra?a TPdf, a ne Boolean. Ta povratna vrijednost je potpuno nova ru?ka dokumenta, dodijeljena odvojeno od izvora, i pozivatelj je posjeduje. Izvor TPdf na kojem ste pozvali metodu je netaknut i jo? uvijek posjeduje vlastitu ru?ku; kompozit je drugi, neovisni objekt. Ako dopustite da vra?eni TPdf iza?e iz dosega bez osloba?anja, procurit ?ete cijeli PDFium dokument

Opasnija pogre?ka ide u drugom smjeru. Ispod haube, metoda tra?i od PDFium-a svje?i FPDF_DOCUMENT kroz FPDF_ImportNPagesToOne, a zatim omotava tu sirovu ru?ku unutar vra?enog TPdf-a tako da ?ivotni vijek omota?a upravlja ru?kom. Od tog trenutka postoji to?no jedan vlasnik ru?ke i to?no jedno mjesto na kojem bi trebala biti zatvorena: kada oslobodite (Free) vra?eni objekt. Nemarna putanja pogre?ke koja osloba?a omota? i tako?er poziva FPDF_CloseDocument na sirovoj ru?ki koju je uhvatila zatvara isti PDFium dokument dvaput. To je dvostruko osloba?anje i to je specifi?an bug koji je ovdje jednom pogodio pozivatelja. Pravilo koje to sprje?ava je kratko. Zatvorite dokument samo na jednoj putanji, osloba?anjem TPdf-a koji vam je metoda predala, i nikada ne pose?ite izvan omota?a kako biste zatvorili ru?ku koju je ve? usvojio

Iz ovoga proizlaze dvije posljedice. Prvo, metoda vra?a nil kada PDFium odbije argumente, kao ?to je nula na bilo kojoj osi mre?e ili neuspjeh dodjele memorije, pa provjera na nil pripada prije nego ?to dotaknete rezultat. Drugo, inicijalizirajte izlaznu varijablu na nil prije bloka try i oslobodite je u bloku finally, kao ?to to ?ini gornji primjer, tako da vas neuspjeh na pola puta ne ostavi s osloba?anjem nedefinirane reference ili preskakanjem osloba?anja u potpunosti

Promjena redoslijeda stranica bez ponovnog pisanja

Nametanje gradi novi dokument. Promjena redoslijeda mijenja jedan dokument na licu mjesta. MovePages podi?e skup stranica iz njihovih trenutnih pozicija i ispu?ta ih na odredi?te, pomi?u?i sve ostalo oko premje?tenog bloka tako da broj stranica ostaje isti

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

Indeksi su bazirani na nuli. PageIndices navodi stranice koje treba premjestiti, u redoslijedu u kojem bi trebale zavr?iti, a DestPageIndex je indeks na koji slije?e prva premje?tena stranica nakon ?to se premje?tanje zavr?i. Budu?i da PDFium premje?ta stranice umjesto kopiranja i ponovnog komprimiranja njihovog sadr?aja, operacija je jeftina i bez gubitaka: objekti stranica zadr?avaju svoje tokove, resurse i vjernost. Ovo je poziv iza plo?e s povla?enjem za promjenu redoslijeda stranica, gdje korisnik povla?i sli?icu u novi utor, a vi potvr?ujete novi redoslijed jednim pokretom. Vra?a False kada je indeks izvan raspona, pa potvrdite rezultat umjesto da pretpostavite da je preslagivanje uspjelo

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;

Izvla?enje podskupa po indeksu

Tre?a operacija kopira eksplicitan skup stranica iz jednog dokumenta u drugi. ImportPagesByIndex uzima izvorni dokument i niz indeksa baziran na nuli, te ume?e te stranice u ciljni dokument na odabranu poziciju

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

Pozivate ga na ciljnom dokumentu i proslje?ujete izvor kao prvi argument. PageIndices imenuje izvorne stranice koje treba povu?i, u redoslijedu u kojem ih ?elite; InsertAt je utor baziran na nuli u ciljnom dokumentu gdje ide prva uvezena stranica, tako da ih 0 postavlja ispred postoje?e prve stranice, a trenutni broj stranica cilja se pribraja. Prazan niz uvozi svaku stranicu, ?to ?ini poziv potpunom kopijom kada vam je potrebna. Vra?a False ako je bilo koji indeks izvan raspona u izvoru

Ovdje je va?na razlika u odnosu na dijeljenje. Dijeljenje zapisuje zasebne datoteke, pri ?emu jedna operacija proizvodi mnogo izlaza na disku. ImportPagesByIndex radi suprotan oblik posla: sakuplja odabrani skup stranica u jedan ciljni dokument u memoriji, koji zatim jednom spremate. Kada je zadatak "daj mi stranice 3, 7 i 12 kao jedan kratki PDF", ovo je izravan put i on obavija FPDF_ImportPagesByIndex ispod haube

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;

Uredno spajanje svega zajedno

Oblik od po?etka do kraja isti je za sve tri operacije: otvorite izvor postavljanjem FileName i prebacivanjem Active na True, izvedite operaciju, spremite s SaveAs i oslobodite ono ?to posjedujete. Jedina grana koja zahtijeva oprez je koja od njih dodjeljuje novi dokument. MovePages mijenja dokument koji ve? dr?ite, pa postoji jedan objekt za osloba?anje. ImportPagesByIndex zapisuje u cilj koji ste sami stvorili, pa osloba?ate izvor i cilj koji ste otvorili. ImportNPagesToOne je iznimka, jer je novi dokument povratna vrijednost metode, a ne ne?to ?to ste sami konstruirali, a zaboravljanje da se radi o zasebnoj ru?ki u vlasni?tvu pozivatelja na?in je na koji se doga?aju i curenje i dvostruko osloba?anje. Inicijalizirajte rezultat na nil, provjerite ga nakon poziva i oslobodite ga na jednoj putanji

Ako je posao koji zapravo imate spajanje cijelih datoteka, a ne preslagivanje stranica, pogledajte spajanje vi?e PDF datoteka u jedan dokument. Ako je obrnuto, razbijanje jednog dokumenta na vi?e datoteka, pogledajte dijeljenje PDF dokumenata na vi?e datoteka. Metode nametanja i promjene redoslijeda ovdje opisane isporu?uju se kao dio softvera PDFium Component za Delphi i C++Builder, zajedno s API-jima za u?itavanje, prikazivanje i ure?ivanje koji su obra?eni drugdje na ovom blogu