Utiskivanje vodenog žiga ili logotipa na svaku stranicu dokumenta izgleda kao posao od pet minuta dok ne otvorite rezultat u inspektoru veličine datoteke. Očigledan pristup jeste proći kroz stranice i na svakoj od njih ponovo izgraditi iste objekte teksta ili slika. To vizuelno radi, ali je rasipnički na način koji se sabira. Dijagonalni vodeni žig „DRAFT” nacrtan direktno na izveštaju od sto stranica jeste sto kopija iste putanje i podataka teksta koji sede u tokovima sadržaja, a sačuvana datoteka nosi svaku od njih.
Form XObject je struktura koju PDF pruža kako bi se izbeglo upravo ovo. On obavija deo višekratnog sadržaja, celu stranicu ili mali šablon, u jedan imenovani objekat koji se može oslikati mnogo puta na mnogim pozicijama. Sadržaj živi u datoteci jednom. Svaka stranica koja želi pečat drži kratko uputstvo koje kaže „oslikaj XObject N ovde, sa ovom transformacijom”. Vodeni žig na sto stranica tada dodaje jedan objekat sadržaja u datoteku umesto stotinu, i to je razlika između dokumenta koji raste linearno sa brojem stranica i onog koji ne raste. Vodeni žigovi, pečati logotipa, šabloni brojeva stranica i pečati su svi isti oblik problema, a Form XObject je pravi alat za svaki od njih.
Zašto jedan sačuvani objekat pobeđuje stotinu ponovnih iscrtavanja
Ušteda je strukturna, a ne kozmetička. PDF stranica se renderuje izvršavanjem svog toka sadržaja, niza crtačkih operatera. Kada ponovo iscrtavate pečat po stranici, vi dodajete pun niz operatera za taj pečat toku svake stranice, i bajtovi se dupliraju onoliko puta koliko imate stranica. Form XObject pomera te operatere u jedan tok koji se čuva jednom u dokumentu. Referenca koju pojedinačna stranica zadržava je mala: ona gura matricu transformacije, poziva XObject i obnavlja stanje. Broj stranica više ne umnožava cenu grafike.
Ovo je najvažnije kada je pečat masivan. Vektorski pečat sa stotinama segmenata putanje, ili bitmapa logotipa, skupi su za skladištenje. Kada se sačuvaju jednom i referenciraju, teži deo se plaća samo jednom, a režijski trošak po stranici je nekoliko bajtova poziva. Vizuelni rezultat na stranici je identičan direktnom ponovnom iscrtavanju, što je i poenta. Čitač ne može da primeti razliku; veličina datoteke itekako može.
Snimanje stranice u XObject
PDFium gradi višekratni objekat iz postojeće stranice. Izvor je stranica u nekom dokumentu koji imate otvoren, mali PDF od jedne stranice koji ne sadrži ništa osim vašeg grafičkog rada za vodeni žig, ili određena stranica veće datoteke. CreateXObjectFromPage snima sadržaj te izvorne stranice u višekratnu ručicu koja pripada odredišnom dokumentu, onom na kojem utiskujete pečat.
var
Dest, Stamp: TPdf;
XObject: TPdfXObject;
begin
Dest := TPdf.Create;
Stamp := TPdf.Create;
try
Dest.LoadFromFile('Report.pdf');
Stamp.LoadFromFile('Watermark.pdf'); // one page of artwork
// Capture page 0 of the stamp document into a reusable handle that
// is owned by Dest. Source must be active; the index is zero-based.
XObject := Dest.CreateXObjectFromPage(Stamp, 0);
if XObject = nil then
raise Exception.Create('Could not build the stamp XObject');
// ... place it, then free it before closing Stamp (see below) ...
Potpis je CreateXObjectFromPage(Source: TPdf; SourcePageIndex: Integer): TPdfXObject. Metoda vraća nil u slučaju neuspeha umesto da prijava grešku, tako da eksplicitna provera iznad nije opciona. Ručica koja se vraća je TPdfXObject koji posedujete, a dva ograničenja životnog veka koja su za nju vezana su deo ove cele vežbe koji sapliće ljude, pa stoga dobijaju sopstveni odeljak u nastavku.
Postavljanje pečata na stranicu
Snimljeni XObject sam po sebi ne radi ništa. Da bi se pojavio, umećete njegovu kopiju na trenutnu stranicu dokumenta pomoću InsertFormObjectFromXObject. Taj poziv vraća osnovni objekat stranice, FPDF_PAGEOBJECT, a vraćena ručica je način na koji pozicionirate plasman. Bez transformacije, pečat sleće na početak koordinatnog sistema u sopstvenim koordinatama izvorne stranice, što je retko mesto gde ga želite.
Pošto InsertFormObjectFromXObject umeće jednu kopiju po pozivu i svaki put vraća svež objekat stranice, možete oslikati isti XObject nekoliko puta na jednoj stranici sa različitim transformacijama, a sačuvani sadržaj se i dalje računa jednom u datoteci. Ugaoni logo i bledi vodeni žig preko cele stranice mogu doći iz istog snimljenog objekta.
var
PageObj: FPDF_PAGEOBJECT;
M: TPdfMatrix;
begin
// The current page of Dest receives one copy of the XObject.
PageObj := Dest.InsertFormObjectFromXObject(XObject);
if PageObj = nil then
raise Exception.Create('Insert failed on this page');
// Position it: move 200 units right, 500 up, at 70% scale.
M := TPdfMatrix.Create;
try
M.Scale(0.7, 0.7);
M.Translate(200, 500);
FPDFPageObj_SetMatrix(PageObj, M.Handle);
finally
M.Free;
end;
// Dest.SaveLoadedDocument(...) when every page is done.
end;
Jedan detalj o vlasništvu čini čišćenje bezbednim. Jednom umetnut, objekat stranice pripada stranici, a ne XObject-u. Kasnije oslobađanje XObject-a ne poništava plasmane koje ste već napravili. To je ono što omogućava da redosled kreiraj-postavi-oslobodi, opisan u nastavku, funkcioniše.
Pravilo životnog veka ručice koje sapliće ljude
Dva ograničenja upravljaju ručicom XObject-a, a ignorisanje bilo kog od njih proizvodi otkaz koji deluje nepovezano sa svojim uzrokom. Prvo, izvorni dokument mora biti aktivan u trenutku kada pozovete CreateXObjectFromPage. Snimanje čita sadržaj izvorne stranice iz aktivnog izvodnog dokumenta, tako da taj dokument i njegova stranica moraju biti otvoreni i važeći kada se ručica gradi. Drugo, i to je ono što iznenađuje ljude, ručica mora biti oslobođena pre nego što se izvorna stranica zatvori, a u praksi pre nego što zatvorite ili oslobodite izvorni dokument iz kojeg je došla.
Razlog je taj što je XObject referenca u strukturu koju izvorni dokument još uvek poseduje. To nije odvojena, samostalna kopija koju možete nositi okolo nakon što izvor nestane. Zatvorite prvo izvor i ručica ostaje da pokazuje na sadržaj koji je uklonjen, pa oslobađanje ručice kasnije, ili bilo koja druga upotreba, radi na memoriji koja više nije važeća. Simptom je klasičan za viseću ručicu: greška u pristupu pri gašenju, ili povremeno oštećenje koje se pomera u zavisnosti od redosleda alokacije, sa stekom koji ukazuje na kod za čišćenje umesto na liniju koja je zapravo izazvala problem. Rešenje je redosled, a ne defanzivno kodiranje. Izgradite XObject, umetnite ga na svaku stranicu kojoj je potreban, oslobodite XObject, pa tek onda zatvorite izvorni dokument. Destruktor TPdfXObject oslobađa osnovnu PDFium ručicu umesto vas, tako da je oslobađanje omotača u pravo vreme cela vaša odgovornost.
Matrica i šta znači njenih šest brojeva
Plasman je 2D afina transformacija, ista ona koju PDF koristi svuda za pozicioniranje sadržaja (ISO 32000-1, odeljak 8.3.4). Sastoji se od šest brojeva, zapisanih kao a, b, c, d, e, f, a PDFium ih izlaže kao zapis FS_MATRIX. Oni mapiraju tačku iz sopstvenog prostora objekta u prostor stranice:
// x' = a*x + c*y + e
// y' = b*x + d*y + f
//
// a, d : horizontal and vertical scale
// b, c : the shear / rotation terms
// e, f : translation (where the origin lands on the page)
Tih šest vrednosti možete popuniti ručno, ali ručno komponovanje je mesto gde rotacija greši, jer rotacija meša sva četiri parametra a, b, c, d zajedno. Omotač TPdfMatrix komponuje uobičajene operacije umesto vas i naknadno ih množi tokom rada, tako da se Translate, Scale i Rotate nadovezuju redosledom kojim ih pozivate. Dijagonalni vodeni žig je rotacija praćena translacijom da se ponovo centrirao; ugaoni logo je skaliranje praćeno translacijom. Kada je matrica spremna, predajte njenu sirovu vrednost funkciji FPDFPageObj_SetMatrix(PageObj, M.Handle), gde je M.Handle osnovna FS_MATRIX. Niži nivo FPDFPageObj_Transform, koji uzima šest vrednosti direktno kao double tipove, dostupan je kada biste radije prosledili brojeve nego pravili omotač.
Utiskivanje pečata na svaku stranicu, ispravnim redosledom
Pun šablon spaja delove sa redosledom koji zahteva pravilo životnog veka. Otvorite oba dokumenta, snimite pečat jednom, prođite kroz odredišne stranice birajući svaku redom i umećući plus pozicionirajući kopiju, zatim oslobodite XObject, zatim sačuvajte i pustite da se izvorni dokument zatvori poslednji.
procedure StampEveryPage(const ASource, AStamp, AOutput: string);
var
Dest, Stamp: TPdf;
XObject: TPdfXObject;
PageObj: FPDF_PAGEOBJECT;
M: TPdfMatrix;
i: Integer;
begin
Dest := TPdf.Create;
Stamp := TPdf.Create;
try
Dest.LoadFromFile(ASource);
Stamp.LoadFromFile(AStamp);
// 1. Capture the artwork once. Stamp is active here.
XObject := Dest.CreateXObjectFromPage(Stamp, 0);
if XObject = nil then
raise Exception.Create('Could not capture the stamp page');
try
// 2. Place a copy on every page of Dest.
for i := 0 to Dest.PageCount - 1 do
begin
Dest.CurrentPageIndex := i; // make page i current
PageObj := Dest.InsertFormObjectFromXObject(XObject);
if PageObj = nil then
Continue;
M := TPdfMatrix.Create;
try
M.Rotate(45); // diagonal watermark
M.Translate(150, 100); // nudge into position
FPDFPageObj_SetMatrix(PageObj, M.Handle);
finally
M.Free;
end;
end;
finally
XObject.Free; // 3. free BEFORE Stamp closes
end;
// 4. Write the result while Dest is still open.
Dest.SaveLoadedDocument(AOutput);
finally
Stamp.Free; // source closes last
Dest.Free;
end;
end;
Oblik try blokova obavlja stvarni posao. Unutrašnji finally oslobađa XObject pre nego što kontrola uopšte stigne do spoljašnjeg finally koji oslobađa Stamp, tako da se ručica uvek oslobađa dok je njen izvor još uvek živ, čak i ako se izuzetak aktivira usred petlje. Postavite to ugnežđivanje ispravno i pravilo životnog veka će se pobrinuti za sebe. (Koristite bilo koji selektor trenutne stranice koji vaš sistem izlaže; telo petlje je isto u svakom slučaju.)
Utiskivanje pečata je jedan ugao većeg skupa alata za izgradnju i uređivanje sadržaja stranice. Ako je vaš pečat sam po sebi slika, a ne snimljena stranica, pretvaranje slika u PDF dokumente pomoću PDFium-a pokriva kako da tu bitmapu prvo unesete u dokument. A kada je stvar koju želite da nosite pored vidljivog pečata datoteka, a ne mastilo na stranici, rad sa PDF prilozima u Delphi-ju prikazuje stranu ugrađenih datoteka. Sve se isporučuje sa PDFium komponentom za Delphi i C++Builder, zajedno sa API-jima za renderovanje, uređivanje i dokumente koji su pokriveni na drugim mestima na ovom blogu.