Vandens ženklo ar logotipo užspaudimas ant kiekvieno dokumento puslapio atrodo kaip penkių minučių darbas, kol neatidarote rezultato failo dydžio inspektoriuje. Akivaizdus būdas yra vaikščioti per puslapius ir kiekviename iš jų vėl kurti tuos pačius teksto ar vaizdo objektus. Tai veikia vizualiai, tačiau tai yra švaistymas, kuris kaupiasi. Įstrižas „DRAFT“ vandens ženklas, nupieštas tiesiogiai ant šimto puslapių ataskaitos, yra šimtas to paties kelio ir teksto duomenų kopijų, sėdinčių faile, o tai padidina dydį ir sulėtina peržiūros programą, kuri turi iš naujo analizuoti tą pačią geometriją kiekviename žingsnyje. Teisingas kelias, kaip nurodo ISO 32000-1 §8.10, yra apibrėžti antspaudą kartą kaip Form XObject ir tada jį nurodyti kiekviename puslapyje, kur jo reikia
Šiame straipsnyje apžvelgiama Form XObject koncepcija, kaip ją apibrėžia specifikacija, ir parodoma, kaip PDFium komponentas, skirtas Delphi ir Lazarus, kuria, užpildo ir pakartotinai naudoja šiuos XObject objektus. Pagrindinis dėmesys skiriamas rankenų (angl. handles) gyvavimo trukmės taisyklei – detalėi, kuri nusprendžia, ar jūsų antspaudai lieka faile, ar išnyksta išmetus atmintį
Kas yra Form XObject
Nepainiokite pavadinimo. Form XObject neturi nieko bendra su interaktyviomis PDF formomis (AcroForm ar XFA). Žodis „Form“ čia kilęs iš senesnės PostScript prasmės – tai reiškia dažų formą, suformuotą geometriją, kurią galima atkurti savavališkai. Specifikacijoje Form XObject yra pilnas turinio srautas, nepriklausomas puslapis puslapyje, turintis savo koordinačių erdvę ir savo išteklių žodyną. Kai jį piešiate pagrindiniame puslapyje, jūsų puslapio srautas tiesiog sako: „paleiskite šį XObject čia su šia transformacijos matrica“
Nauda yra dvejopo. Pirmiausia failo dydis. XObject turinys faile rašomas vieną kartą, todėl šimto puslapių vandens ženklas prideda tik kelis šimtus baitų puslapio lygio nuorodoms, o ne šimtą geometrijos kopijų. Antra, atvaizdavimo greitis. Moderni peržiūros programa sužino, kad XObject yra pakartotinai naudojamas, sugeneruoja jo vaizdą atmintyje (angl. cache) ir pakartotinai naudoja tą vaizdą kituose puslapiuose be pakartotinio geometrijos apdorojimo. Tai padaro peržiūrą sklandžią, o spausdinimą – greitą
XObject sukūrimas iš išteklių
Kurti Form XObject pradeda nuo tuščio objekto gavimo. Componente FPDF_NewFormObject sukuria naują tuščią XObject dokumente ir grąžina jo objekto rankeną. Tada turite nustatyti jo ribas. FPDFFormObj_SetBounds nustato /BBox (apgaubiantį rėmelį), kuris apibrėžia XObject koordinačių erdvės ribas. Viskas, kai nupiešite už šių ribų, bus nukirpta peržiūros programoje, todėl rėmelis turi būti pakankamai didelis, kad tilptų visas jūsų antspaudas
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) ...
Užpildymas vyksta per atskirą srautą. Jūs sukuriate turinio objektus – tekstą su FPDFPageObj_CreateTextObj arba kelius su FPDFPageObj_CreateNewPath – ir pridedate juos prie XObject naudodami FPDFFormObj_AddPageObj. Tai tas pats modelis, kurį naudotumėte piešdami tiesiai puslapyje, tačiau čia elementai dedami į XObject konteinerį. Žemiau pateiktame pavyzdyje sukuriamas vandens ženklo XObject su pasvirusiu pilku tekstu, nustatomos jo ribos ir jis užpildomas
Užpildyto XObject patalpinimas puslapiuose
Kai Form XObject yra užpildytas ir paruoštas, jo patalpinimas puslapyje yra transformacijos matricos taikymo klausimas. Form XObject puslapyje pats savaime yra puslapio objektas (tipas FPDF_PAGEOBJECT), kurį gaunate iš XObject rankenos. Kad jį nupieštumėte, pridedate šį puslapio objektą prie puslapio naudodami FPDFPage_InsertObject
Transformacijos matrica nusprendžia, kur antspaudas nusileidžia ir kokiu kampu. Kadangi XObject turi savo koordinačių erdvę, matrica gali jį mastelizuoti, pasukti ir pastumti. Stačiam vandens ženklui taikote sukimo matricą; logotipui kampe taikote pastūmimo matricą. Svarbi taisyklė yra ta, kad kiekvienam patalpinimui reikalingas atskiras puslapio objekto atstovas, nurodantis tą patį pagrindinį Form XObject srautą. Jūs nekopijuojate paties turinio; sukuriate naują nukreipiantį objektą puslapyje, rodantį į bendrą Form XObject išteklių
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;
Rankenų gyvavimo trukmės taisyklė
Štai detalė, kurioje programuotojai dažniausiai suklumpa, o rezultatas būna atminties nutekėjimas arba tušti puslapiai. Kada Form XObject yra saugus išvalyti? Atsakymas priklauso nuo skirtumo tarp atminties objektų ir failo objektų
Kai sukuriate Form XObject naudodami FPDF_NewFormObject, dokumentas sukuria atitinkamą ištekliaus įrašą. Tačiau atmintyje esantis XObject išlieka aktyvus, kol jį uždarote, arba kol dokumentas uždaromas. Jei uždarysite jį per anksti, kol puslapis dar nebuvo įrašytas į diską, puslapis bandys nurodyti objektą, kuris buvo sunaikintas atmintyje, ir išvestis bus sugadinta. Jei jo išvis neuždarysite, gausite atminties nutekėjimą, kai dokumentas bus uždarytas
Taisyklė yra tokia: Form XObject rankena turi likti gyva tol, kol visi puslapiai, į kuriuos jis buvo įterptas, yra aktyvūs ir kol dokumentas nėra išsaugotas. Kai dokumentas išsaugomas per FPDF_SaveAsCopy, ištekliai įrašomi į failą, ir tik po to saugu iškviesti sunaikinimo funkcijas. Delphi programoje geriausia valdyti šią gyvavimo trukmę apgaubiant rankeną klasėje su integruotu automatiniu naikinimu vykdant dokumento destrukciją
Antspaudavimo procedūra darbe
Sujunkite žingsnius ir antspaudavimas taps dviejų etapų procesu. Pirmiausia sukurkite ir užpildykite Form XObject vieną kartą visam dokumentui. Antra, eikite per puslapius, sukurkite puslapio lygio objekto atstovą kiekvienam puslapiui, pritaikykite transformacijos matricą vietai ir įterpkite jį. Žemiau pateiktame pavyzdyje šis metodas pritaikomas visam failui, uždedant vandens ženklą ant kiekvieno puslapio vidurio ir išsaugant
// 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)
Verta pastebėti matricų nustatymą. Matrica [1, 0, 0, 1, X, Y] tiesiog nustato pastūmimą, perkeliančią antspaudą į puslapio vidurį, o kampo sukimas atliekamas su sinusų ir kosinusų reikšmėmis. Pirmiausia atliekamas mastelis, tada sukimas, o pastūmimas paskiausiai, kad transformacijos veiktų teisingai
Patvirtinimas failo struktūroje
Atidarykite sugeneruotą dokumentą Acrobat programoje arba bet kuriame PDF analizatoriuje ir patikrinkite failo dydį – jis turėtų padidėti tik nežymiai, palyginti su originalu, nesvarbu, kiek puslapių buvo antspauduota. Jei failas išaugo keliais megabaitais, vadinasi, sugeneravote geometriją iš naujo kiekviename puslapyje, užuot pakartotinai naudoję XObject. Taip pat galite patikrinti puslapio turinio srautą: kiekviename puslapyje turėtų būti matomas tik vienas Do operatorius su XObject pavadinimu, o ne ilgas piešimo komandų sąrašas. Šis struktūrinis švarumas užtikrina, kad jūsų antspauduoti failai išliks maži ir greitai atsidarys bet kuriame skaitytuve
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;
Jei norite taikyti šį metodą interaktyviems laukams užšaldyti (tai dar vienas Form XObject taikymo būdas), žr. mūsų straipsnį apie interaktyvių formų užšaldymą Delphi aplinkoje. Platesniam vaizdui apie turinio kūrimą tiesiogiai puslapiuose, nenaudojant daug kartų naudojamų šablonų, žr. puslapio turinio kūrimą su PDFium. Abu naudoja tą patį vektorinį vaizdavimo modelį ir platinami kartu su PDFium komponentu, skirtu Delphi ir C++Builder
Stamping is one corner of a larger toolkit for building and editing page content. If your stamp is itself an image rather than a captured page, converting images to PDF documents with PDFium covers getting that bitmap into a document first. And when the thing you want to carry alongside the visible stamp is a file rather than ink on the page, working with PDF attachments in Delphi shows the embedded-file side. All of it ships with the PDFium Component for Delphi and C++Builder, alongside the rendering, editing, and document APIs covered elsewhere on this blog.