Technical Article

PDFium ile Form XObject'leri Aracılığıyla Yeniden Kullanılabilir Sayfa Damgaları

Bir belgenin her sayfasına filigran veya logo damgalamak, sonucu bir dosya boyutu inceleyicisinde açana kadar beş dakikalık bir iş gibi görünür. Belirgin yaklaşım, sayfaları yürütmek ve her birinde aynı metin veya resim nesnelerini tekrar oluşturmaktır. Bu görsel olarak çalışır ve biriken bir şekilde israftır. Yüz sayfalık bir rapora doğrudan çizilen eğik bir "TASLAK" filigranı, içerik akışlarında oturan aynı yol ve metin verilerinin yüz kopyasıdır ve kaydedilen dosya bunların her birini taşır。

Bir Form XObject, PDF'nin tam olarak bunu önlemek için sağladığı yapıdır. Bir parça yeniden kullanılabilir içeriği, tüm bir sayfayı veya küçük bir şablonu, many times at many positions boyanabilen tek bir adlandırılmış nesne halinde sarar. İçerik dosyada bir kez yaşar. Damgayı isteyen her sayfa, "XObject N'yi buraya, bu dönüşümle boya" diyen kısa bir talimat tutar. Yüz sayfalık bir filigran daha sonra dosyaya yüz tane yerine bir içerik nesnesi ekler ve sayfa sayısıyla doğrusal olarak büyüyen bir belge ile büyümeyen arasındaki fark budur. Filigranlar, logo damgaları, sayfa numarası şablonları ve mühürler aynı türden sorunlardır ve Form XObject her biri için doğru araçtır。

Neden saklanan tek bir nesne yüz çizimi yener

Tasarruf kozmetik değil yapısaldır. Bir PDF sayfası, bir çizim operatörleri dizisi olan içerik akışını yürüterek işlenir. Sayfa başına bir damgayı yeniden çizdiğinizde, o damga için tam operatör dizisini her sayfanın akışına eklersiniz ve baytlar, sayfalarınız olduğu kadar çok kez çoğaltılır. Bir Form XObject bu operatörleri belgede bir kez depolanan tek bir akışa taşır. Ayrı bir sayfanın tuttuğu referans küçüktür: bir dönüşüm matrisi (transformation matrix) iter, XObject'i çağırır ve durumu geri yükler. Sayfa sayısı artık sanat eserinin maliyetini katlamaz。

Bu en çok damga ağır olduğunda önemlidir. Yüzlerce yol segmentine sahip bir vektör mühür veya bir logo bit eşlemi (bitmap), depolamak için pahalıdır. Bir kez depolandığında ve referans gösterildiğinde, ağır kısım tek bir kez ödenir ve sayfa başına ek yük birkaç baytlık çağrıdır. Sayfadaki görsel sonuç, doğrudan yeniden çizimle aynıdır ki asıl önemli olan da budur. Okuyucu farkı anlayamaz; dosya boyutu ise çok iyi anlar。

Bir sayfayı XObject olarak yakalama

Kaynak, açtığınız bir belgedeki bir sayfa, yalnızca filigran sanat eserinizi içeren küçük, tek sayfalık bir PDF veya daha büyük bir dosyanın belirli bir sayfasıdır. CreateXObjectFromPage bu kaynak sayfanın içeriğini, damgaladığınız hedef belgeye ait yeniden kullanılabilir bir tanıtıcıya (handle) yakalar。

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

İmza şöyledir: CreateXObjectFromPage(Source: TPdf; SourcePageIndex: Integer): TPdfXObject. Yöntem hata durumunda istisna oluşturmak yerine nil döndürür, bu nedenle yukarıdaki açık kontrol isteğe bağlı değildir. Geri gelen tanıtıcı, sahibi olduğunuz bir TPdfXObject'tir ve ona bağlı iki ömür (lifetime) kısıtlaması, bu tüm uygulamanın insanları hazırlıksız yakalayan kısmıdır; bu nedenle aşağıda kendi bölümlerini alırlar。

Damgayı bir sayfaya yerleştirme

Yakalanan bir XObject kendi başına hiçbir şey yapmaz. Görünmesini sağlamak için, InsertFormObjectFromXObject ile belgenin geçerli sayfasına bir kopyasını eklersiniz. Bu çağrı, temel sayfa nesnesini, yani FPDF_PAGEOBJECT'i döndürür ve döndürülen tanıtıcı, yerleşimi nasıl konumlandıracağınızdır. Bir dönüşüm (transform) olmadan, damga kaynak sayfanın kendi koordinatlarındaki başlangıç noktasına (origin) iner, bu da nadiren istediğiniz yerdir。

InsertFormObjectFromXObject çağrı başına bir kopya eklediğinden ve her seferinde yeni bir sayfa nesnesi teslim ettiğinden, aynı XObject'i bir sayfada farklı dönüşümlerde birkaç kez boyayabilirsiniz ve saklanan içerik dosyada hala bir kez sayılır. Bir köşe logosu ve soluk bir tam sayfa filigranı aynı yakalanan nesneden gelebilir。

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;

Bir sahiplik ayrıntısı temizliği güvenli kılar. Eklendikten sonra sayfa nesnesi XObject'e değil, sayfaya aittir. XObject'i daha sonra serbest bırakmak (free), yaptığınız yerleşimleri geçersiz kılmaz. Aşağıda açıklanan oluştur-yerleştir-serbest bırak sırasının çalışmasını sağlayan şey budur。

İnsanları zorlayan tanıtıcı ömür kuralı

XObject tanıtıcısını iki kısıtlama yönetir ve ikisini de göz ardı etmek, nedeni ile alakasız görünen bir hataya yol açar. İlk olarak, CreateXObjectFromPage çağrısı sırasında kaynak belge aktif olmalıdır. Yakalama işlemi kaynak sayfanın içeriğini canlı kaynak belgeden okur, bu nedenle tanıtıcı oluşturulurken bu belgenin ve sayfasının açık ve geçerli olması gerekir. İkincisi ve insanları şaşırtan kısım ise, tanıtıcının kaynak sayfa kapatılmadan önce ve pratikte geldiği kaynak belge kapatılmadan veya serbest bırakılmadan önce serbest bırakılması (free) gerektiğidir。

Nedeni, XObject'in kaynak belgenin hala sahip olduğu yapıya yönelik bir referans olmasıdır. Kaynak belge yok olduktan sonra yanınızda taşıyabileceğiniz bağımsız, kendine yeten bir kopya değildir. Önce kaynak belgeyi kapatın; tanıtıcı artık geçerli olmayan bir içeriği işaret eder, bu nedenle onu daha sonra serbest bırakmak veya başka bir şekilde kullanmak artık geçerli olmayan bir bellek üzerinde çalışır. Semptom, sarkan bir tanıtıcı (dangling handle) için klasiktir: kapatma sırasında bir erişim ihlali (access violation) veya bellek tahsis sırasına göre değişen, aslında soruna neden olan satırı değil temizleme kodunu işaret eden geçici bozulmalar. Çözüm savunma amaçlı kod yazmak değil, sıralamadır. XObject'i oluşturun, ihtiyaç duyan her sayfaya yerleştirin, XObject'i serbest bırakın ve ancak ondan sonra kaynak belgeyi kapatın. TPdfXObject yıkıcısı (destructor) temel PDFium tanıtıcısını sizin için serbest bırakır, bu nedenle sarmalayıcıyı doğru zamanda serbest bırakmak tamamen sizin sorumluluğunuzdur。

Matris ve altı sayısının anlamı

Yerleştirme, PDF'nin içeriği konumlandırmak için her yerde kullandığı 2D afin dönüşümüdür (ISO 32000-1, bölüm 8.3.4). a, b, c, d, e, f olarak yazılan altı sayıdır ve PDFium bunları FS_MATRIX kaydı olarak sunar. Bir noktayı nesnenin kendi alanından sayfa alanına eşlerler:

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

Bu altı değeri elle doldurabilirsiniz, ancak bunu elle birleştirmek dönüşün (rotation) yanlış gittiği yerdir; çünkü dönüş a, b, c, d'nin dördünü de birbirine karıştırır. TPdfMatrix sarmalayıcısı ortak işlemleri sizin için birleştirir ve gittikçe arkadan çarpar, böylece Translate, Scale ve Rotate çağrı sıranıza göre zincirlenir. Eğik bir filigran, onu yeniden merkezlemek için bir ötelemenin (translate) takip ettiği bir dönmedir; bir köşe logosu bir ötelemenin takip ettiği bir ölçektir. Matris hazır olduğunda, M.Handle'ın temel FS_MATRIX olduğu FPDFPageObj_SetMatrix(PageObj, M.Handle)'a ham değerini iletin. Matrisleri double olarak doğrudan alan daha düşük seviyeli FPDFPageObj_Transform, bir sarmalayıcı oluşturmak yerine sayıları iletmeyi tercih ettiğinizde kullanılabilir。

Her sayfayı doğru sırayla damgalamak

Tam kalıp, parçaları ömür kuralının gerektirdiği sıralamayla bir araya getirir. Her iki belgeyi de açın, damgayı bir kez yakalayın, hedef sayfaları yürüyerek her birini sırayla seçin ve bir kopyasını ekleyip konumlandırın, ardından XObject'i serbest bırakın, ardından kaydedin ve kaynak belgenin en son kapanmasına izin verin.

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;

try bloklarının şekli asıl işi yapmaktadır. İçteki finally, Stamp'i serbest bırakan dıştaki finally'ye ulaşmadan önce XObject'i serbest bırakır, böylece döngü ortasında bir istisna tetiklense bile tanıtıcı her zaman kaynağı hayattayken serbest bırakılır. Bu iç içe yerleştirmeyi doğru yaptığınızda, ömür kuralı kendiliğinden hallolur。

Damgalama, sayfa içeriği oluşturmak ve düzenlemek için daha büyük bir araç setinin bir köşesidir. Damganız bir yakalanan sayfa yerine bir resimse, PDFium ile resimleri PDF belgelerine dönüştürme konusu bu bit eşlemin önce belgeye nasıl aktarılacağını kapsar. Ve görünür damganın yanında taşımak istediğiniz şey sayfadaki mürekkep yerine bir dosyayla, Delphi'de PDF ekleriyle çalışma gömülü dosya tarafını gösterir. Tüm bunlar, bu blogun başka yerlerinde ele alınan işleme, düzenleme ve belge API'lerinin yanı sıra Delphi ve C++Builder için PDFium Bileşeni ile birlikte gönderilir。