Toplamları Acrobat'ta yeniden hesaplanan ancak kendi Delphi görüntüleyicinizde donuk duran bir form neredeyse hiçbir zaman bir oluşturma (rendering) hatası değildir. PDFium, sunucu (host) bir JS platformu bağlamadığı sürece tüm belge JavaScript'ini devre dışı bırakır ve PDFium Bileşeni v2.13.2 sürümünden itibaren bu platformu otomatik olarak bağlar; böylece V8 özellikli DLL yüklendiğinde AcroForm JavaScript, belge düzeyindeki betikler ve alan hesaplamaları yürütülür. Sunucu, betiğin yapmaya çalıştığı her şeyi görüntüleyebilen, yönlendirebilen veya veto edebilen bir dizi olay aracılığıyla yönetimi elinde tutar
Bu tek cümle, çeşitli şekillerde gördüğümüz destek sorularını çözmektedir: "sipariş formum Acrobat'ta satır toplamlarını hesaplıyor ama benim uygulamamda hesaplamıyor", "app.alert asla tetiklenmiyor", "tarih alanı kendini biçimlendirmiyor". Hepsi aynı sözleşmeye dayanır ve bunu anlamak on dakikaya değer çünkü aynı sözleşme aynı zamanda düşman belgelere karşı güvenlik sınırınızdır
PDF form hesaplamaları neden PDFium'da çalışmıyor?
PDFium, belge JavaScript'ini kesinlikle isteğe bağlı (opt-in) olarak ele alır: Form doldurma ortamının m_pJsPlatform üyesi NULL ise, belgedeki her betik sessizce atlanır. Hata yok, günlük satırı yok, kısmi yürütme yok. Hesaplanan alanlar son saklanan değerlerini korur, app.alert kaybolur, biçimlendirme betikleri asla çalışmaz. Her zaman kendi JavaScript motoruyla birlikte gelen Acrobat aynı dosyayı memnuniyetle çalıştırır; bu nedenle aradaki fark, alttaki kütüphanede kasıtlı bir varsayılan olduğunda kodunuzda bir hataymış gibi görünür
PDFium Bileşeni bununla ilgili ikinci, tarihsel bir katmana sahipti. Sürüm 2.13.1'e kadar bağlama, yalnızca XFA başlatma dalının içinde m_pJsPlatform'u bağlıyordu, bu nedenle betikli PDF'lerin ezici çoğunluğunu oluşturan düz AcroForm belgeleri, V8 özellikli DLL mevcut olduğunda bile hiçbir zaman bir JavaScript platformu almadı. Sürüm 2.13.2, bağlamayı XFA kararının dışına çıkardı: InitializeFormFill artık belgenin XFA olup olmamasından bağımsız olarak, V8FeaturesAvailable True döndürdüğünde IPDF_JsPlatform tablosunu oluşturuyor. Pratik sonuç, belge düzeyindeki betiklerin, alan hesaplama zincirlerinin ve app.alert'ün artık sıradan AcroForm belgelerinde de yürütülmesidir
AcroForm JavaScript, Delphi'de hangi V8 DLL'ine ihtiyaç duyar?
AcroForm JavaScript, V8 motoruyla derlenmiş bir PDFium ikili dosyasına ihtiyaç duyar; PDFium Bileşeni, kütüphane ilk yüklenmeden önce genel EnableV8Engine bayrağı True olduğunda bunu pdfium.v8.dll olarak yükler. Burada açıkça belirtilmesi gereken bir tuzak var: Bileşen, XFA belgelerini /XFA işaretçilerini önceden tarayarak otomatik olarak algılar ve sizin için EnableV8Engine bayrağını çevirir, ancak JavaScript içeren düz bir AcroForm belgesi XFA işaretçisi taşımaz, dolayısıyla otomatik algılama gerçekleşmez. Görüntüleyicinizin form betiklerini çalıştırması gerekiyorsa, bayrağı ilk belge yüklenmeden önce bir kez kendiniz ayarlayın; düz bir pdfium.dll yüklendikten sonra işlem motorları değiştiremez
uses PDFium;
procedure TViewerForm.FormCreate(Sender: TObject);
begin
// Singleton PDFium kütüphanesi ilk kez yüklenmeden önce çalışmalıdır;
// düz pdfium.dll sürece dahil edildikten sonra değiştirilemez
EnableV8Engine := True;
end;
procedure TViewerForm.OpenDocument(const FileName: string);
begin
FPdf.LoadDocument(FileName);
if not V8FeaturesAvailable then
StatusBar.SimpleText :=
'JavaScript devre dışı: pdfium.v8.dll dağıtılmadı';
end;
V8FeaturesAvailable dürüst çalışma zamanı araştırmasıdır: Yüklenen ikili dosyanın V8'e bağımlı dışa aktarımları gerçekten çözümleyip çözümlemediğini bildirir, böylece kontrol bir yükleyicinin hangi DLL'i dağıttığından bağımsız olarak çalışır. Aynı motor ve aynı bayrak aynı zamanda dinamik XFA oluşturmayı (rendering) da besler; XFA algılama ve V8 otomatik seçiminin nasıl etkileşime girdiğine dair arka plan, PDFium ile XFA formlarını algılama ve XFA paketlerini çıkarma bölümünde ele alınmıştır
Uyarılar, yazdırma, posta ve form gönderimi için sunucu (host) olayları
Bir betiğin yaptığı her görünür şey, TPdf olayları aracılığıyla kodunuza geri yönlendirilir ve atanmadığında her biri varsayılan olarak güvenli bir boş işlem (no-op) olur. OnJavaScriptAlert receive'leri; mesajı, başlığı, buton türünü ve simgeyi app.alert'ten alır ve basılan buton sonucunu döndürmenizi sağlar; OnJavaScriptResponse, şifre bayrağı dahil olmak üzere app.response giriş istemlerine hizmet eder; OnJavaScriptBeep, OnJavaScriptPrint, OnJavaScriptMail ve OnJavaScriptSubmitForm; bip sesi, yazdırma, posta ve gönderme isteklerini tam parametre setleriyle yüzeye çıkarır. Bunların hiçbirini atamayan bir görüntüleyici hala doğru şekilde işler ve hesaplar; belge sadece iletişim kutuları açamaz, yazdıramaz veya hiçbir şey gönderemez
procedure TViewerForm.PdfJavaScriptAlert(Sender: TObject;
const Msg, Title: WString; ButtonType, Icon: Integer;
var Result_: Integer; var Handled: Boolean);
begin
// app.alert'ü penceremize ait olması için VCL üzerinden yönlendirin
Result_ := MessageBox(Handle, PWideChar(Msg), PWideChar(Title),
MB_OK or MB_ICONINFORMATION);
Handled := True;
end;
procedure TViewerForm.PdfJavaScriptSubmitForm(Sender: TObject;
const Url: WString; const Data: TBytes);
begin
// Bu işleyici göndermeyi seçmedikçe hiçbir şey iletilmez
LogAudit(Format('Document requested form submit to %s (%d bytes)',
[Url, Length(Data)]));
end;
Varsayılan davranış ayrımı, tehdit modellemesi için önemlidir. OnJavaScriptMail ve OnJavaScriptSubmitForm bildirim tarzındadır: PDFium Bileşeni kendi başına hiçbir ağ veya MAPI eylemi gerçekleştirmez, bu nedenle hazırlıksız bir sunucuya karşı this.submitForm veya this.mailDoc çağıran kötü amaçlı bir belge tam olarak hiçbir şey başaramaz. Sunucu, bir işleyici atayarak ve taşımayı kendisi uygulayarak dahil olmalıdır, bu da verileri makineden taşıma kararının her zaman URL ve yük elinizde olarak sizin kodunuzda verildiği anlamına gelir
Kötü amaçlı bir PDF'in URL'leri başlatmasını nasıl durdurursunuz?
Sürüm 2.13.1'den önce, FFI_DoURIActionWithKeyboardModifier geri araması (callback), bir XFA alanının sağladığı herhangi bir URI'yi koşulsuz olarak kabukta yürütüyordu (shell-executed); bu da kötü amaçlı bir belgeye tıklandığında rastgele URL'leri başlatma yeteneği veriyordu. OnXfaUriAction bunu kapatır: Bileşen yürütmeden önce olaya danışır ve Handled değerini True olarak ayarlayan bir işleyici, ShellExecuteW varsayılanını tamamen bastırır. Olayı atanmamış olarak bırakmak uyumluluk için eski davranışı korur, bu nedenle güvenliğe duyarlı bir görüntüleyici her zaman bunu atamalıdır
procedure TViewerForm.PdfXfaUriAction(Sender: TObject;
const Uri: WString; Modifiers: Integer; var Handled: Boolean);
begin
// Düz https olmayan her şeyi veto edin; aksi takdirde varsayılan davranış
// URI'yi olduğu gibi ShellExecuteW ile yürütür
if not SameText(Copy(Uri, 1, 8), 'https://') then
begin
LogAudit('Blocked URI action: ' + Uri);
Handled := True;
end;
end;
Şema üzerinde bir izin listesi (allow-list) minimumdur; daha katı bir görüntüleyici kullanıcıyla onaylar veya her şeyi kendi korumalı alan (sandboxed) tarayıcı bileşeni üzerinden yönlendirir. Aynı veto felsefesi v2.13.1 olay setine de uzanır: OnXfaEmail, OnXfaHttpRequest ve OnXfaOpenFile olaylarının her biri, bileşenin kendisi hiçbir ağ veya dosya taşıması gerçekleştirmezken istenen eylemin metinsel parametrelerini ortaya koyar. Bu korumaların, oluşturma yalıtımı dahil olmak üzere daha geniş bir güvenilmeyen belge stratejisine nasıl uyduğu, Delphi'de güvenli bir PDF önizlemesi oluşturma konusunun konusudur
SetFocusedFormFieldText ile odaklanmış alana yazma
Sürüm 2.13.2'ye eklenen TPdf.SetFocusedFormFieldText, FocusedFormFieldText'in yazma tarafındaki eşidir: Odaklanmış alanın mevcut içeriğini FORM_SelectAllText aracılığıyla seçer ve tıpkı kullanıcının yerine yazdığı gibi FORM_ReplaceSelection aracılığıyla üzerine yazar. Metin etkileşimli düzenleme yoluyla girdiğinden, alana ekli herhangi bir tuş vuruşu, biçim ve hesaplama betikleri manuel giriş için olduğu gibi tetiklenir; bu da onu JavaScript'i canlı tutan bir görüntüleyicide programlı doldurma için doğru ilkel kılar. Alan geçişi ve etrafındaki odak yönetimi, PDFium Bileşeni ile form alanı navigasyonu bölümünde ele alınan, PDFium Bileşeni ile form alanı navigasyonu
if FPdf.FocusedFormFieldIndex >= 0 then
begin
if not FPdf.SetFocusedFormFieldText('42.50') then
ShowMessage('No focused field accepts text on this page');
// AcroForm: odak alandan ayrıldığında değer /V'ye işlenir.
// XFA: yazma yalnızca bellek içi düzenleme tamponunda kalır ve
// kaydetmeden sonra hayatta kalmaz
end;
Bu yorumdaki kalıcılık asimetrisi gerçek bir sınırdır, form olsun diye söylenmiş bir uyarı değildir. AcroForm metin ve birleşik giriş (combo) alanları için, düzenlenen değer odak kaybında alanın /V girişine işlenir ve kaydetme boyunca kalıcı olur. XFA metin alanları için, yazma yalnızca bellek içi düzenleme tamponuna düşer, çünkü PDFium, widget değerlerini XFA veri kümeleri (datasets) paketine geri uzlaştırmak için hiçbir genel API sunmaz; belgeyi kaydetmek değişikliği taşımayacaktır. Kalıcı XFA veri düzenleme gereksinimiyse, widget yerine veri kümeleri XML'inin kendisini düzenleyin
Yayınlamadan önce bilinmesi gereken sınırlar
İki dürüst sınır kalmaktadır. İlk olarak, PDFium'un uyguladığı JavaScript API'si, form komut dosyası çekirdeğine odaklanan Acrobat nesne modelinin çalışan bir alt kümesidir: Alan erişimi, hesaplama ve biçim olayları, app.alert ve app.response, yazdırma, posta ve gönderme istekleri. Yalnızca Acrobat'a özel egzotik nesnelere dayanan belgeler genellikle sessizce bozulacaktır, bu nedenle eşitlik varsaymak yerine kullanıcılarınızın işlediği gerçek formları test edin. İkincisi, V8'i etkinleştirmek, düz yapıdan önemli ölçüde daha büyük bir ikili dosya olan pdfium.v8.dll'i dağıtmak anlamına gelir; komut dosyasına veya XFA'ya ihtiyaç duymayan bir görüntüleyici meşru olarak daha küçük olan DLL'i tutabilir ve m_pJsPlatform'u bağlı olmadan bırakabilir; bu da kendi başına geçerli bir güvenlik duruşudur
Tüm bunlardan ortaya çıkan kalıp hoş bir şekilde sıkıcıdır: Başlangıçta EnableV8Engine'i ayarlayın, iletişim kutularının yerel görünmesi için uyarı (alert) ve yanıt (response) olaylarını atayın, OnXfaUriAction'ı atayın ve posta ile gönderim olaylarını denetim günlüğüne kaydedin ve bir gereksinim aksini söyleyene kadar geri kalan her şeyi boş işlem (no-op) varsayılanında bırakın. Tam olay referansı ve form doldurma API yüzeyi PDFium Bileşeni ürün sayfasında belgelenmiştir