Tehnički članak

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

Dijagram PDFium N-up namještanja koje pakira šest izvornih stranica na sastavne listove US Letter u rasporedu po dva i po četiri
ImportNPagesToOne NumX puta NumY izvornih stranica pakuje na listove dimenzionirane u točkama, puneći s lijeva na desno i odozgo prema dolje prije početka svježeg lista

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;

    // Četiri izvorne stranice po listu Letter, mreža 2 x 2
    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;   // vidi sljedeći odjeljak: ovo je obavezno
    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

Dijagram životnog ciklusa TPdf ručice koju vraća PDFium ImportNPagesToOne sa sigurnom putanjom jednog oslobođenja nasuprot putanji rušenja dvostrukog oslobođenja
Vraćeni TPdf drugi je dokument u vlasništvu pozivatelja: inicijalizirajte ga na nil, provjerite ga, i omotnicu oslobodite na samo jednom putu

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

PDFium Component: karta preuređenja MovePages koja pomjera PDF stranicu četiri na indeks nula izvještaja od pet stranica dok se preostale stranice pomjeraju jedan utor udesno
MovePages stranicu 4 podiže na indeks 0 dok se preostale stranice pomjeraju za jedan utor i broj stranica ostaje pet
var
  Doc: TPdf;
begin
  Doc := TPdf.Create(nil);
  try
    Doc.FileName := 'report.pdf';
    Doc.Active := True;

    // Pomakni zadnju stranicu (indeks 4 u datoteci od 5 stranica) sasvim na početak
    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;   // započni prazan cilj

    // Uvuci stranice 3, 7 i 12 (indeksi od 0: 2, 6, 11) u izvadak
    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