Spajanje stranica (page stitching) sklapa sadržaj postojećih stranica na zajedničko platno (canvas), što je drugačija operacija od spajanja (merging) datoteka. losLab PDF biblioteka implementira spajanje stranica preko Form XObject-a i modularne šablone vodenih žigova za Delphi: metode StitchPageOverlay, StitchPagesSideBySide i StitchPagesVertically postavljaju snimljene stranice u bilo kojoj razmeri sa potpunom vektorskom preciznošću, a TPDFlibCompositeWatermark slaže tekstualne i slikovne vodene žigove preko opsega stranica u jednom pozivu. Ako vam je zapravo potrebno da dodate cele stranice iz nekoliko dokumenata u jednu datoteku, to spada u porodicu spajanja (merge), što je pokriveno u članku o brzom spajanju PDF-a na nivou bajtova. Ovaj članak govori o kombinovanju sadržaja stranica unutar jednog koordinatnog prostora
Zahtevi koji vode do ovog rešenja su uobičajeni i uporni. Škola želi dve stranice ispita postavljene na jedan list u položenom (landscape) formatu kako bi prepolovila potrošnju papira. Recenzent lokalizacije želi englesku stranicu i njen prevod jednu pored druge na jednoj stranici, tako da se skroluju zajedno. Pravni tim želi da svaki nacrt bude overen sa tri sloja odjednom: logotipom u uglu, dijagonalnim natpisom CONFIDENTIAL preko sredine i malim redom u podnožju, i to samo na stranicama od 1 do 3. Sva tri primera predstavljaju isti osnovni problem, a to je postavljanje već složenog sadržaja stranice na neko drugo mesto u nekoj drugoj veličini bez degradiranja na nivo bitmape
Zašto spajanje tokova sadržaja (content streams) u PDF-u puca?
Naivno spajanje, odnosno kopiranje toka /Contents izvorne stranice u ciljnu stranicu, propada iz tri strukturna razloga. Prvo, koordinate se sudaraju: oba toka sadržaja pretpostavljaju sopstveni početak koordinata u donjem levom uglu sopstvenog MediaBox-a, tako da ubačeni sadržaj sleće preko postojećeg sadržaja na pogrešnom mestu. Drugo, nazivi resursa se sudaraju: svaka stranica razrešava operatore kao što su /F1 Tf i /Im0 Do u odnosu na sopstveni /Resources rečnik, a dve stranice rutinski povezuju isti naziv sa različitim fontovima ili slikama, pa se jedna od njih tiho renderuje sa pogrešnim resursom. Treće, sirovo kopiranje toka ne pruža nikakvo skaliranje; najbolje što možete da uradite je da izvršite translaciju
Form XObject-i rešavaju sva tri problema odjednom, i zato losLab PDF biblioteka gradi svoje spajanje na njima. Standard ISO 32000-1 §8.10 definiše Form XObject kao samostalan tok sadržaja sa sopstvenim rečnikom /Resources i sopstvenim koordinatnim prostorom, koji se postavlja na stranicu pozivanjem njegovog imena pod aktivnom matricom transformacije. Metoda CapturePage obmotava čitavu postojeću stranicu, sadržaj plus resurse, u /Subtype /Form XObject, a funkcije spajanja ga zatim pozicioniraju pomoću cm matrice: skaliranje, translacija, rotacija, po želji. Pošto izvor osataje u vektorskom formatu, tekst ostaje tekst, a putanje ostaju putanje na bilo kom nivou zumiranja, što je isto svojstvo koje čini vektorsku grafiku i šablone senčenja nezavisnim od rezolucije. Ništa se ne rasterizuje, a izolovani rečnik resursa znači da se snimljena stranica ne može sukobiti sa ciljnom stranicom oko naziva /F1
Kako kombinovati dve PDF stranice na jednu stranicu?
Metoda StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) je direktan odgovor: ona snima obe izvorne stranice, kreira novu širu stranicu i postavlja dva snimka levo i desno sa razmakom (gutter) između njih, pri čemu je svaka stranica skalirana da odgovara visini TargetHeight. Funkcija vraća broj nove stranice (u bazi od 1) ili 0 u slučaju neuspeha, a obe izvorne stranice se snimaju i sakrivaju u sklopu ove operacije. Slučaj sa dve ispitne stranice po listu papira je posao od tri linije koda
var
Lib: TPDFlib;
NewPage: Integer;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('exam.pdf', '');
// Pages 1 and 2 on one new wider page with an 18pt gutter,
// both scaled to a 595pt output height (A4 landscape)
NewPage := Lib.StitchPagesSideBySide(1, 2, 18, 595);
if NewPage > 0 then
Lib.SaveToFile('exam-2up.pdf');
finally
Lib.Free;
end;
end;
Za vertikalno slaganje, metoda StitchPagesVertically(PageRanges, Gap, TargetWidth) slaže čitav opseg stranica u jednu visoku stranicu u stilu postera. Parametar PageRanges uzima standardnu sintaksu opsega kao što je '1-3,5'. Svaka izvorna stranica se skalira tako da njena širina odgovara širini TargetWidth, pa je doprinos visini svake stranice njena sopstvena visina pomnožena sa sopstvenim faktorom skaliranja, a visina izlazne stranice je zbir tih visina plus po jedan razmak Gap između svakog para. Ovo je oblik koji želite za dokumente za pregled sa neprekidnim skrolovanjem ili za dugačke račune sastavljene iz izvora standardnih veličina pisma (letter-size)
Kako preklopiti jednu stranicu preko druge?
Metoda StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) iscrtava jednu postojeću stranicu preko druge postojeće stranice, umesto da kreira novu. Izvorna stranica se snima kao Form XObject i crta na ciljnoj stranici unutar zadatog pravougaonika, sa neprovidnošću Opacity od 0 do 1 (gde je 1 potpuno neprovidno). Širina i visina su slobodni parametri, pa se postavljanje rasteže ako se vaša razmera razlikuje od sopstvene razmere izvorne stranice. Prosledite proporcionalni par kada želite neizobličenu sličicu (thumbnail) izvora. Tipična upotreba je dokument čiji se blok odobrenja održava kao sopstvena stranica i utiskuje na naslovnu stranu
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('report.pdf', '');
// Page 5 holds the approval block; place it on page 1
// in a 260 x 180 box at 45% opacity. Page 5 is consumed:
// it is captured and hidden, so pages 6..n become 5..n-1
if Lib.StitchPageOverlay(5, 1, 300, 500, 260, 180, 0.45) = 1 then
Lib.SaveToFile('report-approved.pdf');
finally
Lib.Free;
end;
end;
Šta se dešava sa brojevima stranica nakon snimanja?
Metoda CapturePage konzumira izvornu stranicu: njen sadržaj se premešta u XObject, a sada prazna stranica se sakriva, tako da se svaka stranica sa brojem većim od izvorne odmah pomera nadole za jedan. Ovo je najvažnija semantika u čitavom API-ju za spajanje. losLab PDF biblioteka rukuje ovom aritmetikom unutar svakog poziva spajanja: StitchPageOverlay prilagođava indeks ciljne stranice kada ona stoji iznad izvorne, a StitchPagesSideBySide prvo snima stranicu sa većim brojem kako bi indeks drugog snimanja i dalje bio važeći, ali vaš sopstveni okolni kod mora voditi računa o ovom pomeranju između poziva. Dva pravila održavaju kod sa višestrukim spajanjem ispravnim. Zabeležite sve veličine ili brojeve stranica koji će vam trebati pre prvog snimanja, primers se oni menjaju nakon toga. A kada sami snimate nekoliko stranica u petlji, vršite iteraciju u opadajućem redosledu stranica, tako da sakrivanje kasnije stranice nikada ne poremeti indeks ranije stranice koju još niste snimili
Kako primeniti višeslojni vodeni žig na opseg stranica?
Sloj šablona vodenih žigova u PDFlibWatermark.pas pretvara koncept "definiši jednom, primeni na mnogo stranica" u objekte. Klasa TPDFlibWatermarkBase nosi ono što svaki vodeni žig deli: svojstvo Position u devet tačaka (od wpTopLeft do wpBottomRight, plus wpCustom koje koristi sirove X/Y koordinate), ugao Angle u stepenima, neprovidnost Opacity od 0 do 1 i string opsega stranica PageRange gde prazna vrednost označava sve stranice. TPDFlibTextWatermark dodaje tekst, ID fonta (podrazumevano Helvetica), veličinu i RGB boju. TPDFlibImageWatermark dodaje ID slike iz prethodno dodate slike plus širinu i visinu za postavljanje. Klasa TPDFlibCompositeWatermark drži listu pod-vodnih žigova i primenjuje ih rekurzivno, a TPDFlib.ApplyWatermark je ulazna tačka sa jednim pozivom koja poštuje sopstveni opseg stranica šablona i vraća broj žigosanih stranica. Troslojni pravni pečat izgleda ovako
var
Lib: TPDFlib;
Logo: TPDFlibImageWatermark;
Diagonal, Footer: TPDFlibTextWatermark;
Stack: TPDFlibCompositeWatermark;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('draft.pdf', '');
Stack := TPDFlibCompositeWatermark.Create;
try
Logo := TPDFlibImageWatermark.Create;
Logo.ImageID := Lib.AddImageFromFile('logo.png', 0);
Logo.Position := wpTopRight;
Logo.Width := 90;
Logo.Height := 32;
Stack.Add(Logo);
Diagonal := TPDFlibTextWatermark.Create;
Diagonal.Text := 'CONFIDENTIAL';
Diagonal.Position := wpCenter;
Diagonal.Angle := 45;
Diagonal.FontSize := 60;
Diagonal.Opacity := 0.15;
Stack.Add(Diagonal);
Footer := TPDFlibTextWatermark.Create;
Footer.Text := 'Internal review copy';
Footer.Position := wpBottomCenter;
Footer.FontSize := 9;
Footer.Opacity := 0.6;
Stack.Add(Footer);
Stack.PageRange := '1-3'; // children inherit this range
Lib.ApplyWatermark(Stack);
Lib.SaveToFile('draft-marked.pdf');
finally
Stack.Free; // composite frees its children
end;
finally
Lib.Free;
end;
end;
Dva detalja o kompoziciji vredi pojasniti. Nasleđivanje opsega je jednosmerno i nedestruktivno: pod-žig sa praznim PageRange privremeno nasleđuje opseg kompozita tokom poziva Apply, while pod-žig koji postavi sopstveni opseg zadržava taj svoj opseg, tako da možete zakačiti podnožje na svaku stranicu dok dijagonalni pečat pokriva samo odeljak sa nacrtom. Takođe, vlasništvo prati kompozit: destruktor TPDFlibCompositeWatermark.Destroy oslobađa svoje potomke, tako da jednom oslobađate kompozit i ništa više, kao u gornjem primeru
Granice koje vredi znati
Važe dva iskrena ograničenja, i oba proizilaze iz načina na koji ovaj mehanizam funkcioniše. Prvo, snimanje obuhvata samo tok sadržaja stranice i resurse. Anotacije, veze (linkovi), polja obrazaca i komentari žive kao odvojeni objekti u rečniku stranice, a ne u toku sadržaja, tako da se ne prenose u XObject. Spojeni raspored nosi vidljivi sadržaj stranice, ali ne i interaktivni sloj svojih izvora. Drugo, pozicioniranje teksta u devet tačaka je aproksimacija: bez kompletnog mehanizma za raspored (layout engine), biblioteka ne može znati tačnu renderovanu širinu stringa, pa procenjuje okvir teksta na 40% širine stranice kada razrešava pozicije poput wpCenter. To je dovoljno precizno za centriranje kratke oznake, ali za piksel-precizno postavljanje dugačkih stringova pređite na wpCustom i sami izračunajte koordinate
Razmera ovde nije praktično ograničenje, pošto snimanje i spajanje manipulišu referencama objekata, a ne ponovnim kodiranjem sadržaja stranice. Ako su vaši izvori veličine stotina megabajta, primenjuje se ista disciplina opisana u vodiču za spajanje i deljenje velikih datoteka sa direktnim pristupom pre nego što počnete sa spajanjem. Funkcije spajanja i klase šablona vodenih žigova prikazane ovde isporučuju se u aktuelnoj losLab PDF biblioteci za Delphi, C# i VB.NET, zajedno sa nižerazrednim primitivima CapturePage, DrawCapturedPage i DrawCapturedPageMatrix za slučajeve kada vam je potreban raspored koji tri unapred izgrađena poziva za spajanje ne pokrivaju