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

Delphi FPDF_PAGEOBJECT tanıtıcılarının FPDFPage_TransFormWithClip ayrıştırılmış PDF sayfa içerik akışını değiştirdiğinde nasıl bayatladığını gösteren şema
Bir sayfa nesnesi tanıtıcısı tek bir ayrıştırılmış içerik akışına işaret eder ve FPDFPage_TransFormWithClip o akışı tümüyle değiştirir; eski tanıtıcı, artık var olmayan bir matris altında yanıtlamaya devam eder
var
  Info: TPdfPageObjectInfo;
  Scale: FS_MATRIX;
  Clip: TPdfRectangle;
begin
  Pdf.PageNumber:= 1;
  Info:= Pdf.PageObjectInfo(0);           // dönüşümden önce alınan anlık görüntü

  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 hâlâ dönüşüm öncesi geometriyi tutar ve Info.Handle artık
  // TransformPageContent'in zaten değiştirdiği bir sayfayı gösterir
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

TransformPageContent'taki dört adımlı tazeleme sırasının şeması: metin sayfasını boşalt, dönüştür, içerik üret, sonra PDFium sayfasını yeniden yükle
TransformPageContent dört adımı sabit bir sırada çalıştırır ve yalnızca son FPDF_LoadPage, sorgulanacak taze bir nesne grafiği üretir
// Dönüşümden sonra yeniden numaralandırın. Daha önce yakalanan hiçbir şeyi yeniden kullanmayın.
var
  I: Integer;
  Info: TPdfPageObjectInfo;
begin
  Pdf.TransformPageContent(Scale, Clip);   // metin sayfasını kaldır, dönüştür,
                                           // generate content, reload page
  for I:= 0 to Pdf.ObjectCount- 1 do
  begin
    Info:= Pdf.PageObjectInfo(I);          // yeni ayrıştırmadan gelen tanıtıcı ve sınırlar
    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

Ham PDFium çıkış-parametresi alıcılarını (sessizce varsayılanlanmış kayıtlar döndürür) Delphi'de TPdfPageObjectInfo'nun Has sentinel alanlarıyla karşılaştıran şema
Çıkış-parametreli alıcılar başarısızlığı bir dönüş koduyla bildirir; TPdfPageObjectInfo bu yüzden her türetilmiş alanı açık bir Has gözcüsüyle eşleştirir
Info:= Pdf.PageObjectInfo(I);

if Info.HasRotatedBounds then
  // RotatedBounds, çizim sırasına göre TPdfPoint'ten [1..4] dizisidir
  UseQuad(Info.RotatedBounds[1], Info.RotatedBounds[2],
          Info.RotatedBounds[3], Info.RotatedBounds[4])
else
  // yerel çağrı başarısız oldu; eksen hizalı dikdörtgene geri dön
  UseRect(Info.Bounds);

if Info.HasActiveState and (not Info.Active) then
  SkipObject(I);         // genuinely inactive
// HasActiveState False ise, nesne durumu bilinmiyordur, etkin değil değildir

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