Egy vízjel vagy logó ráhelyezése a dokumentum minden oldalára ötperces munkának tűnik, egészen addig, amíg meg nem nyitja az eredményt egy fájlméret-vizsgálóban. A kézenfekvő megközelítés az oldalak bejárása, és mindegyiken ugyanazon szöveges vagy képobjektumok újraépítése. Ez vizuálisan működik, de halmozódó módon pazarol. Egy átlós „DRAFT” (VÁZLAT) vízjel, amelyet közvetlenül egy százoldalas jelentésre rajzolnak, ugyanazon útvonal- és szöveges adatok száz másolatát jelenti a tartalomfolyamokban, és a mentett fájl mindegyiket hordozza.
A Form XObject az a konstrukció, amelyet a PDF biztosít pontosan ennek elkerülésére. Az újrahasználható tartalom egy darabját – egy egész oldalt vagy egy kis sablont – egyetlen nevesített objektumba csomagolja, amelyet többször meg lehet festeni sok pozícióban. A tartalom egyszer szerepel a fájlban. Minden oldal, amely a bélyegzőt kéri, egy rövid utasítást tartalmaz: „fesd ide az N. XObject-et ezzel a transzformációval”. Egy százoldalas vízjel így egyetlen tartalomobjektumot ad a fájlhoz száz helyett, és ez a különbség az oldalszámmal lineárisan növekvő méretű dokumentum és a nem növekvő között. A vízjelek, logóbélyegzők, oldalszámsablonok és pecsétek mind azonos típusú problémák, és a Form XObject a megfelelő eszköz mindegyikre.
Miért jobb egy tárolt objektum, mint száz újra-rajzolás
A megtakarítás strukturális, nem kozmetikai. A PDF oldal a tartalomfolyamának – a rajzoló operátorok sorozatának – végrehajtásával renderelődik. Amikor oldalonként újra-rajzol egy bélyegzőt, a bélyegző teljes operátorsorozatát hozzáfűzi minden oldal folyamához, és a bájtok annyiszor duplikálódnak, ahány oldala van. A Form XObject ezeket az operátorokat egyetlen, a dokumentumban egyszer tárolt folyamatba helyezi át. Az egyes oldalak által megtartott hivatkozás kicsi: elment egy transzformációs mátrixot, meghívja az XObject-et, és visszaállítja az állapotot. Az oldalszám többé nem sokszorozza meg a rajz költségét.
Ez akkor számít leginkább, ha a bélyegző nehéz. Egy több száz útvonalszegmensből álló vektoros pecsét vagy egy logó bitkép tárolása drága. Egyszer tárolva és hivatkozva a nehéz rész költsége egyszer jelentkezik, az oldalonkénti többletköltség pedig mindössze néhány bájtnyi hívás. A vizuális eredmény az oldalon megegyezik a közvetlen újra-rajzolással, és ez a lényeg. Az olvasó nem veszi észre a különbséget; a fájlméret viszont nagyon is.
Oldal rögzítése XObject-be
A PDFium a meglévő oldalból építi fel az újrahasználható objektumot. A forrás egy megnyitott dokumentum egy oldala, egy kis egyoldalas PDF, amely csak a vízjel alkotását tartalmazza, vagy egy nagyobb fájl egy adott oldala. A CreateXObjectFromPage a forrásoldal tartalmát rögzíti egy újrahasználható fogantyúba (handle), amely a céldokumentum tulajdonában van – annak, amelyet éppen lebélyegez.
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) ...
A szignatúra: CreateXObjectFromPage(Source: TPdf; SourcePageIndex: Integer): TPdfXObject. A metódus hiba esetén nil értéket ad vissza kivétel emelése helyett, így a fenti explicit ellenőrzés nem opcionális. A visszaadott fogantyú egy TPdfXObject, amely az Ön tulajdonában van, és a hozzá kapcsolódó két élettartam-korlátozás az a része az egésznek, ami megzavarhatja a fejlesztőket, ezért ezek alább külön szakaszt kapnak.
A bélyegző elhelyezése az oldalon
A rögzített XObject önmagában nem csinál semmit. Megjelenítéséhez be kell szúrnia egy másolatot az aktuális oldalra a InsertFormObjectFromXObject segítségével. Ez a hívás visszaadja a mögöttes oldalobjektumot, egy FPDF_PAGEOBJECT-et, és a visszaadott fogantyúval pozicionálhatja az elhelyezést. Transzformáció nélkül a bélyegző a forrásoldal saját koordinátáinak kezdőpontjában landol, ami ritkán felel meg a kívánt helynek.
Mivel az InsertFormObjectFromXObject hívásonként egy másolatot szúr be, and minden alkalommal egy friss oldalobjektumot ad vissza, ugyanazt az XObject-et többször is megfestheti egy oldalon különböző transzformációkkal, és a tárolt tartalom továbbra is csak egyszer számít a fájlban. Egy saroklogó és egy halvány, teljes oldalas vízjel származhat ugyanabból a rögzített objektumból.
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;
Egy tulajdonosi részlet biztonságossá teszi a tisztítást. A beszúrás után az oldalobjektum az oldalhoz tartozik, nem az XObject-hez. Az XObject későbbi felszabadítása nem teszi érvénytelenné a már elvégzett elhelyezéseket. Ez teszi lehetővé az alább leírt létrehozás-elhelyezés-felszabadítás sorrend működését.
A fogantyú élettartam-szabálya, amely gondot szokott okozni
Két korlátozás irányítja az XObject fogantyút, és bármelyik figyelmen kívül hagyása olyan hibát eredményez, amely függetlennek tűnik az okától. Elsőként, a forrásdokumentumnak aktívnak kell lennie a CreateXObjectFromPage hívás pillanatában. A rögzítés a forrásoldal tartalmát az élő forrásdokumentumból olvassa be, így annak a dokumentumnak és az oldalának nyitva és érvényesnek kell lennie a fogantyú felépítésekor. Másodszor – és ez az, ami meglepi a fejlesztőket –, a fogantyút fel kell szabadítani a forrásoldal bezárása előtt, és a gyakorlatban a forrásdokumentum bezárása vagy felszabadítása előtt, amelyből származott.
Az ok az, hogy az XObject egy olyan struktúrára mutató hivatkozás, amelyet a forrásdokumentum továbbra is birtokol. Ez nem egy leválasztott, önálló másolat, amelyet magával vihet a forrás megszűnése után. Ha először bezárja a forrást, a fogantyú lebontott tartalomra fog mutatni, így annak későbbi felszabadítása vagy bármilyen egyéb használata már nem érvényes memórián dolgozik. A tünet a lógó fogantyú (dangling handle) klasszikus esete: hozzáférési hiba (access violation) leálláskor, vagy időszakos korrupció, amely az elosztási sorrendtől függően vándorol, olyan veremmel, amely a tisztító kódra mutat, nem pedig a ténylegesen hibát okozó sorra. A javítás a sorrendiség, nem a védekező kódolás. Építse fel az XObject-et, szúrja be minden oldalra, amely igényli, szabadítsa fel az XObject-et, és csak ezután zárja be a forrásdokumentumot. A TPdfXObject destruktora felszabadítja a mögöttes PDFium fogantyút Ön helyett, így a wrapper megfelelő időben történő felszabadítása teljes mértékben az Ön felelőssége.
A mátrix, és mit jelent a hat száma
Az elhelyezés egy 2D affin transzformáció, ugyanaz, amelyet a PDF mindenhol használ a tartalom pozicionálására (ISO 32000-1, 8.3.4. szakasz). Ez hat szám, leírva: a, b, c, d, e, f, és a PDFium ezt FS_MATRIX rekordként teszi elérhetővé. Egy pontot képeznek le az objektum saját teréből az oldaltérbe:
// 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)
Ezt a hat értéket kézzel is kitöltheti, de a kézi összeállításnál romlik el a forgatás, mivel a forgatás mind a négy a, b, c, d értéket összekeveri. A TPdfMatrix burkoló (wrapper) összeállítja a gyakori műveleteket Ön helyett, és menet közben utólag szoroz (post-multiplies), így a Translate, Scale és Rotate hívások láncolhatók a meghívásuk sorrendjében. Az átlós vízjel egy forgatás, amelyet egy eltolás követ a középpontba helyezéshez; a saroklogó egy skálázás, amelyet eltolás követ. Amikor a mátrix kész, adja át a nyers értékét a FPDFPageObj_SetMatrix(PageObj, M.Handle) függvénynek, ahol az M.Handle a mögöttes FS_MATRIX. Az alacsonyabb szintű FPDFPageObj_Transform, amely a hat értéket közvetlenül double-ként fogadja el, akkor használható, ha inkább számokat adna át a burkoló felépítése helyett.
Minden oldal lebélyegzése a megfelelő sorrendben
A teljes minta összerakja a darabokat az élettartam-szabály által megkövetelt sorrendben. Nyissa meg mindkét dokumentumot, rögzítse a bélyegzőt egyszer, járja be a céldokumentum oldalait, kiválasztva mindegyiket, beszúrva és pozicionálva a másolatot, majd szabadítsa fel az XObject-et, mentse el, és hagyja utolsóként lezárni a forrásdokumentumot.
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;
A try blokkok formája végzi a valódi munkát. A belső finally felszabadítja az XObject-et, mielőtt a vezérlés elérné a külső finally-t, amely felszabadítja a Stamp-et, így a fogantyú mindig akkor szabadul fel, amikor a forrása még él, még akkor is, ha kivétel lép fel a ciklus közben. Ha ezt a beágyazást jól csinálja, az élettartam-szabály magától működik. (Használja azt a lapválasztót, amelyet a buildje elérhetővé tesz; a ciklustörzs mindkét esetben azonos.)
A bélyegzés egy nagyobb eszköztár része az oldaltartalom létrehozásához és szerkesztéséhez. Ha a bélyegzője önmagában kép, nem pedig rögzített oldal, a képek PDF dokumentumokká alakítása a PDFium segítségével leírja, hogyan juttathatja el a bitképet először a dokumentumba. És amikor a látható bélyegző mellett hordozni kívánt dolog fájl, nem pedig tinta az oldalon, a PDF csatolmányok kezelése Delphi-ben bemutatja a beágyazott fájlok oldalát. Mindez a Delphi és C++Builder platformokhoz készült PDFium Component részét képezi, a blogunkon máshol ismertetett renderelő, szerkesztő és dokumentum API-kkal együtt.