Technical Article

Večkrat uporabni žigi strani prek Form XObject s PDFium

Nanos vodnega znaka ali logotipa na vsako stran dokumenta je videti kot petminutno delo, dokler rezultata ne odprete v pregledovalniku velikosti datotek. Očitna pot je sprehod skozi strani in na vsaki od njih ponovna gradnja istega besedila ali slikovnih objektov. To vizualno deluje, vendar je potratno na način, ki se kopiči. Diagonalni vodni znak "DRAFT" (osnutek), narisan neposredno na dokument s sto stranmi, predstavlja sto kopij iste poti in besedilnih podatkov v tokovih vsebine, shranjena datoteka pa nosi vsako od njih.

Form XObject je struktura, ki jo PDF ponuja za preprečevanje natanko tega. Ovije kos vsebine za večkratno uporabo, celotno stran ali majhno predlogo, v en sam poimenovan objekt, ki ga je mogoče večkrat poslikati na različnih položajih. Vsebina živi v datoteki le enkrat. Vsaka stran, ki želi žig, vsebuje kratko navodilo, ki pravi: "nariši XObject N tukaj, s to transformacijo." Vodni znak na stotih straneh nato doda en objekt vsebine v datoteko namesto stotih, to pa predstavlja razliko med dokumentom, ki raste linearno s številom strani, in tistim, ki ne. Vodni znaki, žigi logotipov, predloge številk strani in pečati so enake oblike težav, Form XObject pa je pravo orodje za vsako od njih.

Zakaj en shranjen objekt premaga sto ponovnih risanj

Prihranek je strukturne in ne kozmetične narave. Stran PDF se izriše z izvajanjem toka vsebine, zaporedja risalnih operatorjev. Ko ponovno rišete žig na vsaki strani, dodajate celotno zaporedje operatorjev za ta žig v tok vsebine vsake strani, bajti pa se podvojijo tolikokrat, kolikor imate strani. Form XObject premakne te operatorje v en sam tok, shranjen enkrat v dokumentu. Referenca, ki jo ohranja posamezna stran, je majhna: potisne transformacijsko matriko, izvede XObject in obnovi stanje. Število strani ne množi več stroškov umetniškega dela.

To je najbolj pomembno, ko je žig zahteven. Vektorski pečat s stotinami odsekov poti ali bitna slika logotipa je drag za shranjevanje. Če je shranjen enkrat in se nanj sklicuje, se težki del plača enkrat, strošek na stran pa je le nekaj bajtov klica. Vizualni rezultat na strani je enak neposrednemu ponovnemu risanju, kar je bistvo. Bralec ne more opaziti razlike; velikost datoteke pa jo še kako lahko.

Zajem strani v XObject

PDFium zgradi objekt za večkratno uporabo iz obstoječe strani. Vir je stran v nekem dokumentu, ki ga imate odprtega, majhen enostranski PDF, ki ne vsebuje ničesar razen vašega vodnega znaka, ali določena stran večje datoteke. Funkcija CreateXObjectFromPage zajame vsebino te izvorne strani v ročico za večkratno uporabo, ki je v lasti ciljnega dokumenta – tistega, ki ga žigosate.

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) ...

Podpis je CreateXObjectFromPage(Source: TPdf; SourcePageIndex: Integer): TPdfXObject. Metoda v primeru napake vrne nil in ne sproži izjeme, zato eksplicitna preverba zgoraj ni neobvezna. Ročica, ki se vrne, je TPdfXObject v vaši lasti, dve omejitvi življenjske dobe, pripeti nanjo, pa sta tisti del te vaje, ki pogosto zmede razvijalce, zato imata spodaj svoj razdelek.

Namestitev žiga na stran

Zajeti XObject sam po sebi ne naredi ničesar. Da se prikaže, vstavite njegovo kopijo na trenutno stran dokumenta s klicem InsertFormObjectFromXObject. Ta klic vrne osnovni objekt strani, FPDF_PAGEOBJECT, vrnjena ročica pa je način, kako določite položaj. Brez transformacije žig pristane na izhodišču v lastnih koordinatah izvorne strani, kar je redko tam, kjer si želite.

Ker InsertFormObjectFromXObject vgradi eno kopijo na klic in vsakič vrne svež objekt strani, lahko isti XObject poslikate večkrat na isti strani z različnimi transformacijami, shranjena vsebina pa se v datoteki še vedno šteje le enkrat. Logotip v kotu in neizrazit vodni znak čez celo stran lahko izhajata iz istega zajetega 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;

Ena podrobnost o lastništvu poskrbi za varno čiščenje. Ko je objekt strani vstavljen, pripada strani in ne XObjectu. Poznejše sproščanje XObjecta ne razveljavi postavitev, ki ste jih že naredili. To omogoča delovanje vrstnega reda ustvari-postavi-sprosti, opisanega spodaj.

Pravilo o življenjski dobi ročice, ki lahko povzroči težave

Dve omejitvi upravljata ročico XObjecta, ignoriranje katere koli pa povzroči napako, ki se zdi nepovezana s svojim vzrokom. Prvič, izvorni dokument mora biti aktiven v trenutku, ko pokličete CreateXObjectFromPage. Zajem bere vsebino izvorne strani iz živega izvornega dokumenta, zato morata biti ta dokument in njegova stran odprta in veljavna ob gradnji ročice. Drugič (in to je tisto, kar ljudi preseneti), ročico je treba sprostiti, preden se zapre izvorna stran – v praksi preden zaprete ali sprostite izvorni dokument, iz katerega je prišla.

Razlog za to je, da je XObject referenca v strukturo, ki jo izvorni dokument še vedno ima v lasti. To ni ločena, samostojna kopija, ki jo lahko nosite naokoli po tem, ko izvornik izgine. Če izvornik zaprete najprej, ročica ostane usmerjena v vsebino, ki je bila odstranjena, zato poznejše sproščanje ali kakšna druga uporaba deluje na pomnilniku, ki ni več veljaven. Simptom je klasičen za visečo ročico: kršitev dostopa (access violation) ob izklopu ali občasna korupcija, ki se premika glede na vrstni red dodeljevanja, z naborom klicnih funkcij, ki kaže na kodo za čiščenje in ne na vrstico, ki je dejansko povzročila težavo. Rešitev je v vrstnem redu in ne v obrambnem kodiranju. Zgradite XObject, ga vstavite na vsako stran, ki ga potrebuje, sprostite XObject in šele nato zaprite izvorni dokument. Destruktor TPdfXObject sprosti osnovno ročico PDFium namesto vas, tako da je sprostitev ovitka ob pravem času vaša edina odgovornost.

Matrika in kaj pomeni njenih šest števil

Postavitev je 2D afina transformacija, enaka tisti, ki jo PDF uporablja povsod za pozicioniranje vsebine (ISO 32000-1, razdelek 8.3.4). Sestavlja jo šest števil, zapisanih kot a, b, c, d, e, f, PDFium pa jih izpostavlja 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 : horizontal and vertical scale
// b, c : the shear / rotation terms
// e, f : translation (where the origin lands on the page)

Teh šest vrednosti lahko izpolnite ročno, toda ročno sestavljanje je mesto, kjer gre rotacija pogosto narobe, saj rotacija zmeša vse štiri parametre a, b, c, d skupaj. Ovitni razred TPdfMatrix sestavi skupne operacije namesto vas in jih množi sproti, tako da se Translate, Scale in Rotate verižijo v vrstnem redu klica. Diagonalni vodni znak je rotacija, ki ji sledi prevajanje (translate) za ponovno centriranje; logotip v kotu pa je skaliranje, ki mu sledi prevajanje. Ko je matrika pripravljena, predajte njeno surovo vrednost funkciji FPDFPageObj_SetMatrix(PageObj, M.Handle), kjer je M.Handle osnovna matrika FS_MATRIX. Nižjenivojska funkcija FPDFPageObj_Transform, ki teh šest vrednosti sprejme neposredno kot double, je na voljo, ko raje posredujete števila kot pa gradite ovitek.

Žigosanje vsake strani v pravilnem vrstnem redu

Celoten vzorec sestavi dele skupaj z vrstnim redom, ki ga zahteva pravilo o življenjski dobi. Odprite oba dokumenta, zajemite žig enkrat, se sprehodite skozi ciljne strani z izbiro vsake posebej ter vstavljanjem in pozicioniranjem kopije, nato sprostite XObject, nato shranite 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;
  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;

Oblika blokov try opravlja pravo delo. Notranji finally sprosti XObject, preden lahko nadzor sploh doseže zunanji finally, ki sprosti Stamp, tako da se ročica vedno sprosti, ko je njen vir še živ, tudi če se sredi zanke sproži izjema. Pravilno nastavite to gnezdenje in pravilo o življenjski dobi bo poskrbelo samo zase. (Uporabite kateri koli selektor trenutne strani, ki ga izpostavlja vaša različica; telo zanke je enako.)

Žigosanje je le en del večjega kompleta orodij za gradnjo in urejanje vsebine strani. Če je vaš žig slika in ne zajeta stran, članek pretvorba slik v dokumente PDF s PDFium opisuje, kako to bitno sliko najprej spraviti v dokument. Če pa želite poleg vidnega žiga prenašati datoteko in ne le črnila na strani, članek delo s prilogami PDF v Delphi prikazuje stran z vdelanimi datotekami. Vse to se pošilja s komponento PDFium Component za Delphi in C++Builder, skupaj z izrisovalnimi, urejevalnimi in dokumentnimi vmesniki API, obravnavanimi drugje na tem blogu.