Teknik Makale

Delphi'de Dönüşüm Sonrası Eskiyen PDFium Sayfa Nesnesi Tutamaçları

FPDFPage_TransFormWithClip bir sayfayı yeniden yazdığında, zaten elinizde tuttuğunuz her FPDF_PAGEOBJECT tutamacı hâlâ dönüşümden öncesinin ayrıştırmasını tanımlar. Delphi ve C++Builder için PDFium Component, bunu TransformPageContent içinde çözer, ki bu metin sayfasını boşaltır, içeriği yeniden üretir, sonra sayfayı yeniden yükler, böylece sonraki sorgular yeni koordinatları görür

Belirti sessizdir. Bir yazdırma marjı eklemek için 0,9 ölçek uygularsınız, sonra PageObjectInfo'yu okursunuz ve çağrıdan önce aldığınız tam olarak aynı sayıları alırsınız. Hiçbir istisna, hiçbir hata kodu, günlükte hiçbir şey yok. Bu, bir düzenlemeden sonra eskiyen metin sayfaları üzerine makalede anlatılan önbelleğe alınmış metin sayfasından farklı bir hatadır: orada önbellek, bırakıp yeniden inşa edebileceğiniz tek bir FPDF_TEXTPAGE tutamacıdır, burada problem kendi değişkenlerinizdeki her sayfa nesnesi tutamacı, artı çoğu çağıranın attığı bir dönüş kodu üzerinden başarısızlığı bildiren bir getter sınıfıdır

Sayfa nesnesi sınırları neden bir hata olmadan eskir?

Çünkü bir sayfa nesnesi tutamacı, bir belirli içerik akışının ayrıştırılmış bir temsiline bir işaretçidir ve tam sayfa bir dönüşüm o içerik akışını yenisiyle değiştirir. PDFium, yamalanacak tutamaçları aramak için çağrı yığınınızı dolaşmaz. Taze bir nesne grafiği inşa eder ve eskisini tam olduğu gibi bırakır, dolayısıyla eski tutamaca karşı bir okuma, dosyanın söylediğine artık karşılık gelmeyen bir yapının mükemmel derecede geçerli bir okumasıdır

ISO 32000-1 §7.8.2, içerik akışını bir sayfayı çizen operatörler dizisi olarak tanımlar ve §8.3.3, geçerli dönüşüm matrisinin kullanıcı uzayını cihaz uzayına nasıl eşlediğini tanımlar. Sayfa düzeyinde bir dönüşüm, nesne başına koordinatları yerinde düzenleyerek değil, o operatörleri sararak ve yeniden yazarak ifade edilir. Dolayısıyla nesnelerin taşıdığı koordinatlar hiç değişmeyebilir; değişen, çizildiklerinde geçerli olan matristir. Eski matris altında ayrıştırılan herhangi bir tutamaç, geometri sorularını eski matris altında yanıtlar ve onları itiraz etmeden yanıtlar

FPDFPage_TransFormWithClip gerçekte neyi yeniden yazar

Sayfayı yeniden yazar, anlık görüntülerinizi değil. FPDFPage_TransFormWithClip, bir FS_MATRIX ve bir FS_RECTF klip dikdörtgeni alır ve her ikisini de tüm sayfa içeriğine uygular. Marjlar, düzen ölçeklendirmesi ve garip boyutlu bir sayfayı bir hedef kutuya karşı normalleştirmek için doğru çağrıdır. Mevcut tutamaçların takip etmesini bekliyorsanız uzanılacak yanlış çağrıdır ve yalnızca sayfa içeriğine dokunduğunu hatırlamaya da değer: açıklamalar ayrı bir katmandır ve aynı altı matris katsayısını FPDFPage_TransformAnnots'a yönlendiren TransformPageAnnotations'a ihtiyaç duyar

var
  Info: TPdfPageObjectInfo;
  Scale: FS_MATRIX;
  Clip: TPdfRectangle;
begin
  Pdf.PageNumber:= 1;
  Info:= Pdf.PageObjectInfo(0);           // snapshot taken before the transform

  Scale.a:= 0.9;   Scale.b:= 0.0;
  Scale.c:= 0.0;   Scale.d:= 0.9;
  Scale.e:= 29.7;  Scale.f:= 42.0;        // 5% margin, A4 in points
  Clip:= Pdf.GetPageBox(pbMedia);
  Pdf.TransformPageContent(Scale, Clip);

  // Info.Bounds still holds pre-transform geometry, and Info.Handle now
  // points into a page that TransformPageContent has already replaced
end;

TransformPageContent'in kullandığı yenileme sırası

Bu sırayla dört adım: metin sayfasını boşalt, dönüştür, içerik üret, sayfayı yeniden yükle. TPdf.TransformPageContent tam olarak bu diziyi çalıştırır. CheckPageActive'i çağırır, matrisi ve klibi kendi yerel kayıt şekillerine kopyalar, UnloadTextPage'i, sonra FPDFPage_TransFormWithClip'i, sonra FPDFPage_GenerateContent'in etrafındaki sarmalayıcı olan UpdatePage'i ve son olarak ReloadPage'i çağırır

Her adım yerini hak eder. UnloadTextPage önce gelir, çünkü önbelleğe alınmış FPDF_TEXTPAGE, eski matris altında hesaplanan karakter kutularını tutar ve ayrıca ondan inşa edilen türetilmiş web bağlantısı listesini ve devam eden herhangi bir bulma oturumunu düşürür. FPDFPage_GenerateContent'in yeniden yüklemeden önce çalışması gerekir, çünkü dönüşüm, içerik akışına geri serileştirilene kadar bellekteki sayfada yaşar ve aksi halde bir yeniden yükleme değiştirilmemiş akışı yeniden ayrıştırırdı. ReloadPage, geçerli sayfa indeksine karşı FPDF_LoadPage ile kapanır, ki bu size gerçekten taze bir nesne grafiği veren tek şeydir

// After the transform, re-enumerate. Do not reuse anything captured earlier.
var
  I: Integer;
  Info: TPdfPageObjectInfo;
begin
  Pdf.TransformPageContent(Scale, Clip);   // unload text page, transform,
                                           // generate content, reload page
  for I:= 0 to Pdf.ObjectCount- 1 do
  begin
    Info:= Pdf.PageObjectInfo(I);          // handle and bounds from the new parse
    if Info.Bounds.Right> PageWidth then
      Log('object '+ IntToStr(I)+ ' still overflows after scaling');
  end;
end;

ReloadPage'deki bir ayrıntı, bu diziyi kendiniz yazarsanız kopyalamaya değer. Önce yeni sayfayı yükler ve yalnızca sonra alana işler, dolayısıyla başarısız olan bir sayfa yüklemesi, sizi yarı sökülmüş bir duruma düşürmek yerine geçerli yerel sayfayı ve türetilmiş önbelleklerinin tümünü sağlam bırakır. Yeniden yüklemek bedava değildir — sayfanın tam bir yeniden ayrıştırması için ödeme yapıyorsunuzdur — ama dönüşüm başına bir kez ödenir, sorgu başına değil ve daha ucuz doğru bir alternatif yoktur

Tutamaçları yeniden yükleme boyunca taşımayın

Yeniden yüklemeden sonra, eski tutamaçlar yalnızca eski değildir, sarkıktır. Önceki FPDF_PAGE kapatıldı ve ona ait FPDF_PAGEOBJECT değerleri, serbest bırakılmış belleğe işaretçilerdir. TPdfPageObjectInfo, yerel tutamacı Handle alanında açığa vurur, ki bu bir nesneyi doğrudan daha düşük seviyeli bir çağrıya geçirmek için gerçekten yararlıdır ve sayfayı yeniden yükleyen bir işlem boyunca bir form alanında veya bir listede tutmak için de eşit derecede gerçekten tehlikelidir. Bir anlık görüntü kaydını yalnızca içeriği yeniden üreten bir sonraki çağrıya kadar geçerli olarak ele alın, PDFium sınırında ABI ve bellek güvenliği üzerine notlarda ele alınan sahiplik kurallarıyla aynı ruhla

Bir getter başarısız olup hâlâ geçerli veri gibi görünebilir mi?

Evet ve bu aynı problemin ikinci yarısıdır. FPDFPageObj_GetRotatedBounds ve FPDFPageObj_GetIsActive, çıktı-parametreli getter'lardır: bir int başarı bayrağı döndürürler ve gerçek cevabı bir referans argümanına yazarlar. İkisi de, oluşturulmuş ama sayfası henüz yeniden ayrıştırılmamış bir nesne için FALSE döndürebilir. Bu gerçekleştiğinde çıktı parametresi dokunulmadan bırakılır ve Default(TPdfPageObjectInfo) ile başlatılmış bir Pascal kaydı tamamen sıfırdır, dolayısıyla çağıran, orijinde dört noktalı bir dörtgen ve False bir Active bayrağı görür. Başarısız bir çağrı, sessizce makul görünen veriye terfi ettirilmiştir

TPdfPageObjectInfo buna açık bekçilerle yanıt verir. HasRotatedBounds, FPDFPageObj_GetRotatedBounds çağrısının sonucunu taşır, HasActiveState, FPDFPageObj_GetIsActive'in sonucunu taşır ve geometri ve durum alanları yalnızca karşılık gelen bekçi True olduğunda yazılır. Aynı şekil, diğer çıktı-parametreli getter'lar için kayıt boyunca tekrarlanır, dolayısıyla HasMatrix, HasFillColor, HasStrokeColor ve HasStrokeWidth hepsi aynı şeyi ifade eder: yerel çağrı başarılı oldu ve komşu alan anlamlıdır

Info:= Pdf.PageObjectInfo(I);

if Info.HasRotatedBounds then
  // RotatedBounds is array [1..4] of TPdfPoint, in draw order
  UseQuad(Info.RotatedBounds[1], Info.RotatedBounds[2],
          Info.RotatedBounds[3], Info.RotatedBounds[4])
else
  // the native call failed; fall back to the axis-aligned rectangle
  UseRect(Info.Bounds);

if Info.HasActiveState and (not Info.Active) then
  SkipObject(I);         // genuinely inactive
// if HasActiveState is False, the object state is unknown, not inactive

Desen, dönüş-kodu-artı-çıktı-parametresi kuralını izleyen her PDFium getter'ına genellenir ve bunlardan çok var. Bir sarmalayıcı o kuralı sade bir fonksiyon sonucuna sıkıştırırsa, "cevap sıfırdır" ile "hiç cevap yoktur"u ayıran tek sinyali atmış demektir. Alan başına bir ekstra boolean taşımak bir bayta mal olur ve varsayılan bir kaydın bir ölçüm sanıldığı tüm bir hata kategorisini ortadan kaldırır

Bunun hâlâ ısırdığı yer

Üç dürüst sınır. Birincisi, yenileme sayfa başınadır: ikinci sayfayı dönüştürün ve birinci sayfa için tuttuğunuz herhangi bir tutamaç etkilenmez, ama artık farklı zamanlarda ayrıştırılmış iki sayfanız var ve hangi anlık görüntülerin hangisinden geldiğini hatırlamak size kalmış. İkincisi, indeks kararlılığı bir içerik yeniden üretimi boyunca garanti edilmez — yeniden yüklemeden sonra, indeks 3, yeni ayrıştırmada indeks 3 her ne ise odur, dolayısıyla nesneleri konumların tutulduğunu varsaymak yerine türlerine ve geometrilerine göre yeniden tanımlayın. Üçüncüsü, FPDFPage_TransFormWithClip'teki klip dikdörtgeni sayfa içeriğine uygulanır ve sayfa kutularından hiçbirini yeniden boyutlandırmaz; içeriği bir marj oluşturmak için küçültürseniz, MediaBox hâlâ her zamanki boyuttadır ve bir görüntüleyici, içinde çizim küçültülmüş orijinal sayfayı gösterir. Bunların hiçbiri egzotik değildir — ayrıştırılmış duruma işaretçiler dağıtan ve ömrü çağırana bırakan bir C API'nin sıradan sonucudur. Düzeltme, her yerde işe yarayan aynıdır: bir anlık görüntünün tam olarak ne zaman süresi dolduğunu tanımlayın, o sınırda yenileyin ve başarısız bir çağrının hiçbir zaman bir değer gibi görünmesine izin vermeyin

Matris davranışını daha genel olarak çalışıyorsanız, bir dönüşümün nereye ineceğine karar veren çarpma sırası, matrislerle prepend, append ve pivot üzerine makalede ele alınmıştır. Burada anlatılan dönüşüm ve sayfa nesnesi API'leri, ürün sayfası sayfa nesnesi anlık görüntü kaydı ve bekçi alanları için tam referansı taşıyan, Delphi ve C++Builder için PDFium Component ile birlikte gönderilir