Technical Article

Page Stitching and Watermark Templates in Delphi PDFs

Spajanje stranica sastavlja sadržaj postojećih stranica na zajedničko platno (canvas), što je drugačija operacija od spajanja datoteka. losLab PDF Library implementira Form XObject spajanje stranica i kompozitne predloške vodenih žigova za Delphi: StitchPageOverlay, StitchPagesSideBySide i StitchPagesVertically postavljaju snimljene stranice u bilo kojem mjerilu s potpunom vektorskom preciznošću, a TPDFlibCompositeWatermark slaže tekstualne i slikovne vodene žigove preko raspona stranica u jednom pozivu. Ako je ono što zapravo trebate spajanje cijelih stranica iz nekoliko dokumenata u jednu datoteku, to je obitelj funkcija spajanja (merge), pokrivena u članku o brzom spajanju PDF-ova na razini bajtova; ovaj članak se bavi kombiniranjem sadržaja stranice unutar jednog koordinatnog prostora

Zahtjevi koji vode do ovoga su uobičajeni i uporni. Škola želi dvije stranice ispita staviti na jedan vodoravni (landscape) list kako bi prepolovila potrošnju papira. Pregledatelj lokalizacije želi englesku stranicu i njezin prijevod jednu pored druge na jednoj stranici, s mogućnošću zajedničkog pomicanja. Pravni tim želi da svaki nacrt bude otisnut s tri sloja odjednom: logotipom u kutu, dijagonalnim CONFIDENTIAL (povjerljivo) preko sredine i malim retkom podnožja (footer), samo na stranicama od 1 do 3. Sva tri slučaja predstavljaju isti temeljni problem, a to je postavljanje već pripremljenog sadržaja stranice na neko drugo mjesto u nekoj drugoj veličini bez smanjenja kvalitete pretvaranjem u bitmapu

Zašto nadovezivanje PDF struja sadržaja ne uspijeva?

Jednostavno spajanje, odnosno kopiranje /Contents struje izvorne stranice u ciljnu stranicu, ne uspijeva iz tri strukturna razloga. Prvo, koordinate se sudaraju: obje struje sadržaja pretpostavljaju vlastito ishodište u donjem lijevom kutu vlastitog MediaBox-a, tako da zalijepljeni sadržaj završava na vrhu postojećeg sadržaja na pogrešnom mjestu. Drugo, nazivi resursa se sudaraju: svaka stranica razrješava operatore kao što su /F1 Tf i /Im0 Do u odnosu na vlastiti rječnik /Resources, a dvije stranice rutinski povezuju isti naziv s različitim fontovima ili slikama, pa se jedna od njih tiho iscrtava s pogrešnim resursom. Treće, kopiranje sirove struje ne pruža nikakvo skaliranje; najbolje što možete učiniti je translacija

Form XObject-i rješavaju sva tri problema odjednom, i zato losLab PDF Library gradi svoje spajanje na njima. ISO 32000-1 §8.10 definira form XObject kao samostalan tok sadržaja s vlastitim /Resources rječnikom i vlastitim koordinatnim prostorom, postavljen na stranicu pozivanjem njegovog naziva pod bilo kojom aktivnom matricom transformacije. CapturePage omotava cijelu postojeću stranicu, sadržaj plus resurse, u /Subtype /Form XObject, a funkcije spajanja zatim ga pozicioniraju pomoću matrice transformacije cm: skaliranje, translacija, rotacija, po želji. Budući da izvor ostaje vektorski, tekst ostaje tekst, a staze ostaju staze na bilo kojoj razini zumiranja, što je isto svojstvo koje čini vektorsku grafiku i uzorke sjenčanja neovisnima o razlučivosti. Ništa se ne rasterizira, a izolirani rječnik resursa znači da se snimljena stranica ne može sukobiti s ciljnom stranicom oko naziva /F1

Kako kombinirati dvije PDF stranice na jednu stranicu?

StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) je izravan odgovor: snima obje izvorne stranice, stvara novu širu stranicu i postavlja dvije snimke lijevo i desno s razmakom (gutter) između njih, pri čemu je svaka skalirana da stane u TargetHeight. Funkcija vraća broj nove stranice (1-baziran) ili 0 u slučaju neuspjeha, a obje izvorne stranice se snimaju i skrivaju kao dio operacije. Slučaj ispita s dvije stranice po listu je posao od tri retka

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, StitchPagesVertically(PageRanges, Gap, TargetWidth) slaže cijeli raspon stranica u jednu visoku stranicu u stilu postera. PageRanges prihvaća standardnu sintaksu raspona kao što je '1-3,5'. Svaka izvorna stranica skalira se tako da njezina širina odgovara TargetWidth, tako da je doprinos visini svake stranice njezina vlastita visina pomnožena s vlastitim faktorom skaliranja, a visina izlazne stranice je zbroj tih visina plus po jedan Gap (razmak) između svakog para. Ovo je oblik koji želite za dokumente s neprekidnim pomicanjem za pregled ili duge račune sastavljene iz izvora veličine pisma (letter-size)

Kako prekriti jednu stranicu preko druge?

StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) iscrtava jednu postojeću stranicu na drugu postojeću stranicu, umjesto stvaranja nove. Izvorna stranica se snima kao form XObject i iscrtava na cilju u pravokutniku koji zadate, s opcijom Opacity od 0 do 1 (1 je neprozirno). Širina i visina slobodni su parametri, tako da se smještaj rasteže ako se vaš omjer razlikuje od vlastitog omjera izvorne stranice; proslijedite proporcionalni par kada želite neizobličenu sličicu (thumbnail) izvora. Tipična upotreba je dokument čiji se blok odobrenja održava kao zasebna stranica i utiskuje na naslovnicu

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;

Što se događa s brojevima stranica nakon snimanja?

CapturePage troši izvornu stranicu: njezin se sadržaj premješta u XObject, a sada prazna stranica se skriva, pa se svaka stranica s brojem većim od izvorne odmah pomiče prema dolje za jedan. Ovo je najvažnija semantika u cijelom API-ju za spajanje. losLab PDF Library upravlja aritmetikom unutar svakog poziva za spajanje, StitchPageOverlay prilagođava indeks cilja kada je cilj iznad izvora, a StitchPagesSideBySide prvo snima stranicu s većim brojem kako bi indeks drugog snimanja i dalje bio valjan, bt vaš vlastiti okolni kôd mora voditi računa o pomaku između poziva. Dva pravila održavaju kôd višestrukog spajanja ispravnim. Zabilježite sve veličine stranica ili brojeve stranica koji će vam trebati prije prvog snimanja, jer se oni nakon toga mijenjaju. A kada sami snimate nekoliko stranica u petlji, prođite kroz njih u silaznom redoslijedu stranica, tako da skrivanje kasnije stranice nikada ne poremeti indeks ranije koju još niste snimili

Kako primijeniti višeslojni vodeni žig na raspon stranica?

Sloj predloška vodenog žiga u PDFlibWatermark.pas pretvara koncept 'definiraj jednom, primijeni na mnogo stranica' u objekte. TPDFlibWatermarkBase nosi ono što svi vodeni žigovi dijele: devet-točkovnu poziciju Position (od wpTopLeft do wpBottomRight, plus wpCustom koji koristi sirove X/Y koordinate), kut Angle u stupnjevima, neprozirnost Opacity od 0 do 1 i niz PageRange gdje prazan niz označava sve stranice. TPDFlibTextWatermark dodaje tekst, ID fonta (zadano je Helvetica), veličinu i RGB boju; TPDFlibImageWatermark dodaje ID slike iz prethodno dodane slike plus širinu i visinu smještaja. TPDFlibCompositeWatermark sadrži popis pod-vodenih žigova i primjenjuje ih rekurzivno, a TPDFlib.ApplyWatermark je ulazna točka s jednim pozivom koja poštuje vlastiti raspon stranica predloška i vraća broj otisnutih 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 kompozicije vrijedi pojasniti

Nasljeđivanje raspona je jednosmjerno i nedestruktivno: dijete s praznim rasponom PageRange privremeno nasljeđuje raspon kompozita tijekom primjene (Apply), dok dijete koje postavi vlastiti raspon zadržava isti, tako da možete prikvačiti podnožje na svaku stranicu dok dijagonalni pečat pokriva samo dio nacrta. A vlasništvo prati kompozit: TPDFlibCompositeWatermark.Destroy oslobađa svoju djecu, tako da jednom oslobađate kompozit i ništa više, kao u gornjem primjeru

Granice koje vrijedi znati

Primjenjuju se dva stvarna ograničenja, a oba proizlaze iz načina na koji mehanizam radi. Prvo, snimanje obuhvaća samo struju sadržaja stranice i resurse. Anotacije, poveznice, polja obrazaca i komentari žive kao zasebni objekti u rječniku stranice, a ne u struji sadržaja, pa ne prelaze u XObject; spojeni izgled nosi vidljivi sadržaj stranice, ali ne i interaktivni sloj svojih izvora. Drugo, pozicioniranje teksta u devet točaka je aproksimacija: bez potpunog mehanizma za raspored (layout engine) knjižnica ne može znati točnu iscrtanu širinu niza znakova, pa procjenjuje tekstualni okvir na 40% širine stranice prilikom razrješavanja pozicija poput wpCenter. To je dovoljno točno za centriranje kratke oznake, ali za pikselno precizno postavljanje dugih nizova, prijeđite na wpCustom i sami izračunajte koordinate

Skaliranje ovdje nije praktično ograničenje, budući da snimanje i spajanje manipuliraju referencama objekata umjesto ponovnog kodiranja sadržaja stranice; ako su vaši izvori veličine stotina megabajta, ista disciplina opisana u vodiču za spajanje i dijeljenje velikih datoteka s izravnim pristupom primjenjuje se prije spajanja. Funkcije spajanja i klase predložaka vodenih žigova prikazane ovdje dolaze u trenutnoj losLab PDF knjižnici za Delphi, C# i VB.NET, zajedno s primitivima niže razine CapturePage, DrawCapturedPage i DrawCapturedPageMatrix kada trebate raspored koji tri unaprijed izgrađena poziva za spajanje ne pokrivaju