Pečaćenje vodenog žiga ili logotipa na svaku stranicu dokumenta izgleda kao petominutni posao dok ne otvorite rezultat u pregledniku veličine datoteke. Očigledan pristup je proći kroz stranice i na svakoj od njih ponovno izgraditi isti tekst ili slikovne objekte. To vizualno funkcionira i rastrošno je na način koji se zbraja. Dijagonalni vodeni žig "DRAFT" nacrtan izravno na izvješću od sto stranica je stotinu kopija iste staze i podataka o tekstu koji sjede u tokovima sadržaja, a spremljena datoteka nosi svaku od njih.
Form XObject je struktura koju PDF pruža kako bi se to izbjeglo. On omotava dio višekratnog sadržaja, cijelu stranicu ili mali predložak, u jedan imenovani objekt koji se može iscrtati mnogo puta na mnogim pozicijama. Sadržaj živi u datoteci jednom. Svaka stranica koja želi pečat sadrži kratku uputu koja kaže "nacrtaj XObject N ovdje, s ovom transformacijom." Vodeni žig od sto stranica tada dodaje jedan objekt sadržaja datoteci umjesto stotinu, i to je razlika između dokumenta koji raste linearno s brojem stranica i onog koji ne raste. Vodeni žigovi, pečati logotipa, predlošci brojeva stranica i pečati su svi isti oblik problema, a Form XObject je pravi alat za svaki od njih.
Zašto jedan pohranjeni objekt pobjeđuje stotinu ponovnih crtanja
Ušteda je strukturna, a ne kozmetička. PDF stranica se iscrtava izvođenjem svog toka sadržaja, niza operatora crtanja. Kada ponovno crtate pečat po stranici, dodajete puni niz operatora za taj pečat svakom toku stranice, a bajtovi se dupliciraju onoliko puta koliko imate stranica. Form XObject pomiče te operatore u jedan tok pohranjen jednom u dokumentu. Referenca koju pojedinačna stranica zadržava je mala: ona gura matricu transformacije, poziva XObject i vraća stanje. Broj stranica više ne množi cijenu grafike.
Ovo je najvažnije kada je pečat težak. Vektorski pečat sa stotinama segmenata staze, ili bitmapa logotipa, skup je za pohranu. Pohranjen jednom i referenciran, teški dio se plaća samo jednom, a režijski trošak po stranici je nekoliko bajtova poziva. Vizualni rezultat na stranici identičan je izravnom ponovnom crtanju, što je i poanta. Čitač ne može uočiti razliku; veličina datoteke itekako može.
Snimanje stranice u XObject
PDFium gradi višekratni objekt 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še grafike vodenog žiga, ili određena stranica veće datoteke. CreateXObjectFromPage snima sadržaj te izvorne stranice u višekratnu ručku (handle) koja pripada odredišnom dokumentu, onom koji pečatite.
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 neuspjeha umjesto da podigne iznimku, pa eksplicitna provjera iznad nije opcionalna. Ručka koja se vraća je TPdfXObject koji posjedujete, a dva ograničenja životnog vijeka koja su joj priložena su dio ove cijele vježbe koji zbunjuje ljude, pa stoga dobivaju svoj zasebni odjeljak u nastavku.
Postavljanje pečata na stranicu
Snimljeni XObject ne čini ništa sam po sebi. Da bi se pojavio, umetnete njegovu kopiju na trenutnu stranicu dokumenta pomoću InsertFormObjectFromXObject. Taj poziv vraća osnovni objekt stranice, FPDF_PAGEOBJECT, a vraćena ručka je način na koji pozicionirate plasman. Bez transformacije, pečat slijeće na ishodište u vlastitim koordinatama izvorne stranice, što je rijetko ono što želite.
Budući da InsertFormObjectFromXObject umeće jednu kopiju po pozivu i vraća novu ručku objekta stranice svaki put, možete iscrtati isti XObject nekoliko puta na jednoj stranici na različitim transformacijama, a pohranjeni sadržaj se i dalje broji jednom u datoteci. Logotip u kutu i blijedi vodeni žig preko cijele 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 vlasništva čini čišćenje sigurnim. Nakon umetanja, objekt stranice pripada stranici, a ne XObjectu. Kasnije oslobađanje XObjecta ne poništava plasmane koje ste već napravili. To je ono što omogućuje rad redoslijeda stvori-postavi-oslobodi opisanog u nastavku.
Pravilo o životnom vijeku ručke koje stvara probleme ljudima
Dva ograničenja upravljaju ručkom XObjecta, a njihovo zanemarivanje proizvodi pogrešku koja izgleda nepovezano sa svojim uzrokom. Prvo, izvorni dokument mora biti aktivan u trenutku kada pozovete CreateXObjectFromPage. Snimanje čita sadržaj izvorne stranice iz aktivnog izvornog dokumenta, tako da taj dokument i njegova stranica moraju biti otvoreni i valjani kada se gradi ručka. Drugo, i to je ono što iznenađuje ljude, ručka mora biti oslobođena prije nego što se zatvori izvorna stranica, a u praksi prije 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š uvijek posjeduje. To nije odvojena, samostalna kopija koju možete nositi uokolo nakon što izvora više nema. Zatvorite izvor najprije i ručka ostaje pokazivati na sadržaj koji je srušen, pa kasnije oslobađanje ili bilo koje drugo korištenje radi na memoriji koja više nije valjana. Simptom je klasičan za viseću ručku: narušavanje pristupa (access violation) pri gašenju, ili povremeni lomovi koji se pomiču ovisno o redoslijedu dodjele, sa stogom koji pokazuje na kod za čišćenje, a ne na liniju koja je zapravo uzrokovala problem. Rješenje je redoslijed, a ne obrambeno kodiranje. Izgradite XObject, umetnite ga na svaku stranicu koja ga treba, oslobodite XObject, pa tek onda zatvorite izvorni dokument. Destruktor TPdfXObject otpušta osnovnu PDFium ručku za vas, tako da je oslobađanje omotača u pravo vrijeme vaša jedina odgovornost.
Matrica i što znači njezinih šest brojeva
Plasman je 2D afina transformacija (affine transform), ista ona koju PDF koristi posvuda za pozicioniranje sadržaja (ISO 32000-1, odjeljak 8.3.4). To je šest brojeva, napisanih kao a, b, c, d, e, f, a PDFium ih izlaže kao zapis FS_MATRIX. Oni mapiraju točku iz 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)
Tehnički možete sami popuniti tih šest vrijednosti, no njihovo ručno sastavljanje je mjesto gdje rotacija kreće po zlu, jer rotacija miješa sve četiri vrijednosti a, b, c, d zajedno. Omotač TPdfMatrix sastavlja uobičajene operacije za vas i množi ih usput, pa se Translate, Scale i Rotate ulančavaju redoslijedom kojim ih pozivate. Dijagonalni vodeni žig je rotacija praćena translacijom radi ponovnog centriranja; logotip u kutu je skaliranje praćeno translacijom. Kada je matrica spremna, predajte njezinu sirovu vrijednost funkciji FPDFPageObj_SetMatrix(PageObj, M.Handle), gdje je M.Handle osnovni FS_MATRIX. Niža razina FPDFPageObj_Transform, koja uzima šest vrijednosti izravno kao double, dostupna je kada radije prosljeđujete brojeve nego da gradite omotač.
Pečaćenje svake stranice, u ispravnom redoslijedu
Puni uzorak spaja dijelove s redoslijedom koji pravilo o životnom vijeku zahtijeva. Otvorite oba dokumenta, snimite pečat jednom, prođite kroz odredišne stranice birajući svaku redom te umećući i pozicionirajući kopiju, zatim oslobodite XObject, zatim spremite, i dopustite da se izvorni dokument zatvori posljednji.
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 radi pravi posao. Unutarnji finally oslobađa XObject prije nego što kontrola uopće stigne do vanjskog finally koji oslobađa Stamp, tako da se ručka uvijek otpušta dok je njezin izvor još živ, čak i ako se petlja prekine iznimkom. Shvatite to ugniježđenje ispravno i pravilo o životnom vijeku će se samo pobrinuti za sebe.
Pečaćenje je jedan kut većeg alata za izgradnju i uređivanje sadržaja stranice. Ako je vaš pečat slika, a ne snimljena stranica, pretvaranje slika u PDF dokumente s PDFiumom pokriva uvođenje te bitmape u dokument najprije. A kada stvar koju želite nositi uz vidljivi pečat ima oblik datoteke, a ne tinte na stranici, rad s PDF privicima u Delphiu pokazuje stranu ugniježđenih datoteka. Sve se to isporučuje s PDFium komponentom za Delphi i C++Builder, uz API-je za iscrtavanje, uređivanje i dokumente pokrivene drugdje na ovom blogu.