Žigosanje vodnega žiga ali logotipa na vsako stran dokumenta je videti kot pet minut dela, dokler ne odprete rezultata v pregledovalniku velikosti datotek. Očiten pristop je sprehoditi se po straneh in na vsaki znova zgraditi iste objekte besedila ali slike. To deluje vizualno, vendar je potratno na način, ki se kopiči. Diagonalen vodni žig "DRAFT", narisan neposredno na stotestransko poročilo, pomeni sto kopij iste poti in besedilnih podatkov, ki sedijo v vsebinskih tokovih, in shranjena datoteka nosi vsako od njih
Form XObject je konstrukt, ki ga PDF ponuja, da se prav temu izognemo. Ovija kos ponovno uporabne vsebine, celo stran ali majhno predlogo, v en sam poimenovan objekt, ki ga je mogoče narisati mnogokrat na mnogih položajih. Vsebina živi v datoteki samo enkrat. Vsaka stran, ki želi žig, hrani kratko navodilo, ki pravi "nariši XObject N tukaj, s to transformacijo." Stotestranski vodni žig tako doda datoteki en vsebinski objekt namesto sto, in to je razlika med dokumentom, ki linearno raste s številom strani, in tistim, ki ne. Vodni žigi, žigi logotipa, predloge za številke strani in pečati so vsi ista oblika problema, in Form XObject je pravo orodje za vsakega od njih
Zakaj en shranjen objekt premaga sto ponovnih risanj
Prihranek je strukturen, ne kozmetičen. Stran PDF se izriše z izvajanjem svojega vsebinskega toka, zaporedja operatorjev risanja. Ko na vsaki strani znova narišete žig, dodajate celotno zaporedje operatorjev za ta žig v tok vsake strani, in bajti se podvojijo tolikokrat, kolikor imate strani. Form XObject premakne te operatorje v en tok, shranjen enkrat v dokumentu. Sklic, ki ga hrani posamezna stran, je majhen: potisne transformacijsko matriko, pokliče XObject in obnovi stanje. Število strani ne pomnoži več stroškov grafike
To je najbolj pomembno, ko je žig težak. Vektorski pečat s stotinami segmentov poti ali bitna slika logotipa je draga za shranjevanje. Shranjen enkrat in referenciran, se težki del plača samo enkrat, dodaten strošek na stran pa je le nekaj bajtov klica. Vizualni rezultat na strani je identičen neposrednemu ponovnemu risanju, kar je bistvo. Bralec razlike ne more zaznati; velikost datoteke pa jo zelo lahko
Zajem strani v XObject
PDFium zgradi ponovno uporabni objekt iz obstoječe strani. Vir je stran v nekem odprtem dokumentu, majhen enostranski PDF, ki ne vsebuje ničesar razen vaše grafike vodnega žiga, ali določena stran večje datoteke. CreateXObjectFromPage zajame vsebino te izvorne strani v ponovno uporabno ročico, ki pripada ciljnemu dokumentu, tistemu, ki ga žigosate
var
Dest, Stamp: TPdf;
XObject: TPdfXObject;
begin
Dest := TPdf.Create(nil);
Stamp := TPdf.Create(nil);
try
Dest.FileName := 'Report.pdf';
Dest.Active := True;
Stamp.FileName := 'Watermark.pdf'; // ena stran grafike
Stamp.Active := True;
if not (Dest.Active and Stamp.Active) then
raise Exception.Create('Could not open the input documents');
// Zajemi stran 0 dokumenta z žigom v ponovno uporabno ročico, ki jo
// poseduje Dest. Source mora biti Active; indeks se začne pri nič.
XObject := Dest.CreateXObjectFromPage(Stamp, 0);
if XObject = nil then
raise Exception.Create('Could not build the stamp XObject');
// ... postavi jo, nato jo sprosti pred zapiranjem Stamp (glejte spodaj) ...
Podpis je CreateXObjectFromPage(Source: TPdf; SourcePageIndex: Integer): TPdfXObject. Metoda sproži izjemo, če izvorni dokument ni Active, vrne pa nil namesto sprožitve izjeme, kadar PDFium objekta ne more zgraditi, zato zgornje eksplicitno preverjanje ni izbirno. Ročica, ki jo dobite nazaj, je TPdfXObject, ki jo posedujete, dve omejitvi življenjske dobe, ki sta nanjo vezani, pa sta del celotne te vaje, ki najpogosteje zavede ljudi, zato imata spodaj svoje lastno poglavje
Postavljanje žiga na stran
Zajeti XObject sam po sebi ne naredi ničesar. Da bi se pojavil, vstavite njegovo kopijo na trenutno stran dokumenta, tisto, ki jo izbira 1-indeksirana lastnost PageNumber, z InsertFormObjectFromXObject. Ta klic vrne osnovni objekt strani, FPDF_PAGEOBJECT, in vrnjena ročica je način, kako pozicionirate postavitev. Brez transformacije žig pristane v izhodišču v lastnih koordinatah izvorne strani, kar je redko tisto, kar želite
Ker InsertFormObjectFromXObject vstavi eno kopijo na klic in vsakič vrne svež objekt strani, lahko isti XObject narišete večkrat na eni strani z različnimi transformacijami, shranjena vsebina pa se v datoteki še vedno šteje samo enkrat. Logotip v kotu in bled vodni žig čez celo stran lahko izhajata iz istega zajetega objekta
var
PageObj: FPDF_PAGEOBJECT;
M: TPdfMatrix;
RawM: FS_MATRIX;
begin
// Trenutna stran Dest prejme eno kopijo XObject.
PageObj := Dest.InsertFormObjectFromXObject(XObject);
if PageObj = nil then
raise Exception.Create('Insert failed on this page');
// Pozicioniraj jo: premakni za 200 enot desno, 500 gor, pri 70-odstotni skali.
M := TPdfMatrix.Create;
try
M.Scale(0.7, 0.7);
M.Translate(200, 500);
RawM := M.Handle;
if FPDFPageObj_SetMatrix(PageObj, RawM) = 0 then
raise Exception.Create('Cannot assign the stamp matrix');
finally
M.Free;
end;
Dest.UpdatePage; // potrdi spremembe te strani v njen vsebinski tok
// if not Dest.SaveAs(...) then ... ko je vsaka stran opravljena.
end;
Dve podrobnosti vzdrževanja to naredita varno. Prvič, ko je enkrat vstavljen, objekt strani pripada strani, ne XObject. Sproščanje XObject kasneje ne izniči postavitev, ki ste jih že naredili. To omogoča, da spodaj opisano zaporedje ustvari-postavi-sprosti deluje. Drugič, vstavljanje in pozicioniranje spremenita samo seznam objektov strani v pomnilniku; UpdatePage je tisti, ki ta seznam serializira nazaj v vsebinski tok strani, zato se stran, ki jo uredite brez klica te metode, shrani, kot da žig nikoli ni bil postavljen
Pravilo o življenjski dobi ročice, ki najpogosteje zavede
Dve omejitvi urejata ročico XObject, in če katero koli od njiju zanemarite, dobite napako, ki je videti nepovezana s svojim vzrokom. Prvič, izvorni dokument mora biti aktiven v trenutku, ko pokličete CreateXObjectFromPage. Zajem prebere vsebino izvorne strani iz živega izvornega dokumenta, zato morata biti ta dokument in njegova stran odprta in veljavna, ko se ročica zgradi. Drugič, in to je tisto, kar ljudi preseneti, se mora ročica sprostiti, preden se izvorna stran zapre, v praksi pa preden zaprete ali sprostite izvorni dokument, iz katerega izhaja
Razlog je v tem, da je XObject sklic v strukturo, ki jo izvorni dokument še vedno poseduje. Ni ločena, samostojna kopija, ki bi jo lahko nosili s seboj, ko izvora ni več. Zaprite izvor najprej in ročica ostane, ko kaže na vsebino, ki je bila podrta, tako da njeno kasnejše sproščanje ali katera koli druga njena uporaba deluje na pomnilniku, ki ni več veljaven. Simptom je klasičen za viseč kazalec (dangling handle): kršitev dostopa ob zaustavitvi ali občasna poškodba, ki se premika glede na vrstni red dodeljevanja, s skladom, ki kaže na kodo za čiščenje namesto na vrstico, ki je dejansko povzročila težavo. Popravek je zaporedje, ne obrambno programiranje. Zgradite XObject, vstavite ga na vsako stran, ki ga potrebuje, sprostite XObject in šele nato zaprite izvorni dokument. Destruktor TPdfXObject namesto vas sprosti osnovno ročico PDFium, zato je sproščanje ovojnice ob pravem času vsa vaša odgovornost
Matrika in kaj pomeni njenih šest števil
Postavitev je 2D afina transformacija, ista, ki jo PDF uporablja povsod za pozicioniranje vsebine (ISO 32000-1, razdelek 8.3.4). Je šest števil, zapisanih a, b, c, d, e, f, PDFium pa jih izpostavi kot zapis FS_MATRIX. Preslikajo točko iz lastnega prostora objekta v prostor strani:
// x' = a*x + c*y + e
// y' = b*x + d*y + f
//
// a, d : vodoravna in navpična skala
// b, c : člena striženja / rotacije
// e, f : translacija (kam na strani pristane izhodišče)
Teh šest vrednosti lahko zapolnite ročno, vendar je ravno pri ročnem sestavljanju tam, kjer rotacija zaide narobe, ker rotacija zmeša vse štiri a, b, c, d skupaj. Ovojnica TPdfMatrix iz enote FPdfMatrix za vas sestavi pogoste operacije in sproti post-množi, tako da se Translate, Scale in Rotate verižijo v vrstnem redu, v katerem jih pokličete. Diagonalen vodni žig je rotacija, ki ji sledi translacija za ponovno centriranje; logotip v kotu je skaliranje, ki mu sledi translacija. Ko je matrika pripravljena, kopirajte njeno surovo vrednost, lastnost Handle tipa FS_MATRIX, v lokalno spremenljivko in jo podajte FPDFPageObj_SetMatrix; uvoz deklarira matriko kot parameter var, zato ji lastnosti ni mogoče podati neposredno, njen rezultat pa je 0 ob neuspehu. Nižjenivojski FPDFPageObj_Transform, ki sprejme vseh šest vrednosti neposredno kot double, je na voljo, kadar raje podate številke, kot da bi gradili ovojnico
Žigosanje vsake strani, v pravilnem vrstnem redu
Celoten vzorec sestavi dele skupaj v vrstnem redu, ki ga zahteva pravilo o življenjski dobi. Odprite oba dokumenta, zajemite žig enkrat, sprehodite se po ciljnih straneh tako, da po vrsti nastavljate 1-indeksirani PageNumber ter vstavljate in pozicionirate kopijo, potrjujete vsako stran z UpdatePage, nato sprostite XObject, nato shranite z SaveAs in pustite, da se izvorni dokument zapre zadnji
procedure StampEveryPage(const ASource, AStamp, AOutput: string);
var
Dest, Stamp: TPdf;
XObject: TPdfXObject;
PageObj: FPDF_PAGEOBJECT;
M: TPdfMatrix;
RawM: FS_MATRIX;
I: Integer;
begin
Dest := TPdf.Create(nil);
Stamp := TPdf.Create(nil);
try
Dest.FileName := ASource;
Dest.Active := True;
Stamp.FileName := AStamp;
Stamp.Active := True;
if not (Dest.Active and Stamp.Active) then
raise Exception.Create('Could not open the input documents');
// 1. Zajemi grafiko enkrat. Stamp je tukaj Active.
XObject := Dest.CreateXObjectFromPage(Stamp, 0);
if XObject = nil then
raise Exception.Create('Could not capture the stamp page');
try
// 2. Postavi kopijo na vsako stran Dest. PageNumber je 1-indeksiran.
for I := 1 to Dest.PageCount do
begin
Dest.PageNumber := I; // naredi stran I trenutno
PageObj := Dest.InsertFormObjectFromXObject(XObject);
if PageObj = nil then
Continue;
M := TPdfMatrix.Create;
try
M.Rotate(45); // diagonalen vodni žig
M.Translate(150, 100); // rahlo premakni v položaj
RawM := M.Handle;
FPDFPageObj_SetMatrix(PageObj, RawM);
finally
M.Free;
end;
Dest.UpdatePage; // potrdi spremembe te strani
end;
finally
XObject.Free; // 3. sprosti PRED zaprtjem Stamp
end;
// 4. Zapiši rezultat, medtem ko je Dest še odprt.
if not Dest.SaveAs(AOutput) then
raise Exception.Create('Could not save ' + AOutput);
finally
Stamp.Free; // izvor se zapre zadnji
Dest.Free;
end;
end;
Prava dela opravlja oblika blokov try. Notranji finally sprosti XObject, preden nadzor sploh lahko doseže zunanji finally, ki sprosti Stamp, tako da se ročica vedno sprosti, medtem ko je njen izvor še vedno živ, tudi če se izjema sproži sredi zanke. Če pravilno postavite to gnezdenje, poskrbi pravilo o življenjski dobi zase
Žigosanje je le en kot večjega nabora orodij za gradnjo in urejanje vsebine strani. Če je vaš žig sam po sebi slika in ne zajeta stran, pretvarjanje slik v dokumente PDF s PDFium pokriva, kako to bitno sliko najprej spraviti v dokument. In kadar je to, kar želite nositi ob vidnem žigu, datoteka in ne črnilo na strani, delo s prilogami PDF v Delphiju prikazuje stran vgrajenih datotek. Vse to je del PDFium Component za Delphi in C++Builder, skupaj z API-ji za izrisovanje, urejanje in dokumente, opisanimi drugje na tem blogu