Un formular ale cărui totaluri se recalculează în Acrobat, dar rămân blocate în propriul vizualizator Delphi nu este aproape niciodată o eroare de randare. PDFium dezactivează tot codul JavaScript din document, cu excepția cazului în care aplicația gazdă atașează o platformă JS, iar PDFium Component configurează acea platformă în mod automat începând cu versiunea 2.13.2, astfel încât JavaScript-ul AcroForm, scripturile la nivel de document și calculele de câmpuri să se execute oricând este încărcată biblioteca DLL cu V8 activat. Aplicația gazdă păstrează controlul printr-un set de evenimente care pot afișa, redirecționa sau bloca tot ceea ce scriptul încearcă să facă
Această singură mențiune rezolvă o întrebare de asistență pe care am văzut-o sub mai multe forme: „formularul meu de comandă calculează totalurile de rând în Acrobat, dar nu și în aplicația mea”, „app.alert nu se declanșează niciodată”, „câmpul pentru dată nu se formatează singur”. Toate acestea se rezumă la același contract, iar înțelegerea acestuia merită zece minute, deoarece el reprezintă de asemenea bariera dvs. de securitate împotriva documentelor ostile
De ce nu funcționează calculele din formularele PDF în PDFium?
PDFium tratează codul JavaScript din document în mod exclusiv ca o opțiune opt-in: dacă membrul m_pJsPlatform din contextul de completare a formularelor este NULL, fiecare script din document este omis în mod silențios. Nicio eroare, nicio linie de jurnal, nicio executare parțială. Câmpurile calculate își păstrează ultima valoare stocată, app.alert dispare, scripturile de formatare nu se execută niciodată. Acrobat, care include întotdeauna propriul său motor JavaScript, rulează fișierul fără probleme, motiv pentru care discrepanța seamănă atât de mult cu o eroare în codul dvs., când de fapt este o setare implicită deliberată a bibliotecii subiacente
PDFium Component a avut un al doilea strat, istoric, legat de acest aspect. Până la versiunea 2.13.1, legătura atașa m_pJsPlatform doar în cadrul ramurii de inițializare XFA, astfel încât documentele simple AcroForm, care reprezintă marea vastitate a PDF-urilor cu scripturi, nu primeau deloc o platformă JavaScript, chiar dacă DLL-ul cu V8 activat era prezent. Versiunea 2.13.2 a scos atașamentul din logica XFA: InitializeFormFill construiește acum tabelul IPDF_JsPlatform ori de câte ori V8FeaturesAvailable returnează True, indiferent dacă documentul este XFA sau nu. Rezultatul practic este că scripturile la nivel de document, lanțurile de calculare a câmpurilor și app.alert se execută acum și în documentele obișnuite AcroForm
De care DLL V8 are nevoie JavaScript-ul AcroForm în Delphi?
JavaScript-ul AcroForm are nevoie de o versiune binară de PDFium compilată cu motorul V8, pe care PDFium Component o încarcă ca pdfium.v8.dll când indicatorul global EnableV8Engine este setat la True înainte de prima încărcare a bibliotecii. Există o capcană ce trebuie explicată clar: componenta detectează automat documentele XFA prin pre-scanarea markerilor /XFA și comută EnableV8Engine pentru dvs., dar un document simplu AcroForm cu JavaScript nu conține niciun marker XFA, deci nu are loc nicio detectare automată. Dacă vizualizatorul dvs. ar trebui să ruleze scripturile din formular, setați indicatorul singur, o singură dată, înainte ca primul document să se încarce; după ce o versiune simplă de pdfium.dll este încărcată, procesul nu mai poate schimba motoarele
uses PDFium;
procedure TViewerForm.FormCreate(Sender: TObject);
begin
// Trebuie să ruleze înainte ca biblioteca PDFium singleton să se încarce prima dată;
// odată ce fișierul simplu pdfium.dll se află în proces, nu mai poate fi înlocuit
EnableV8Engine := True;
end;
procedure TViewerForm.OpenDocument(const FileName: string);
begin
FPdf.LoadDocument(FileName);
if not V8FeaturesAvailable then
StatusBar.SimpleText :=
'JavaScript dezactivat: pdfium.v8.dll nu este implementat';
end;
V8FeaturesAvailable este verificarea onestă la rulare: ea raportează dacă binarul încărcat rezolvă efectiv exporturile dependente de V8, astfel încât verificarea funcționează indiferent de DLL-ul pe care un kit de instalare a ajuns să-l instaleze. Același motor și același indicator alimentează, de asemenea, randarea XFA dinamică; informațiile de fundal despre modul în care interacționează detectarea XFA și selecția automată V8 sunt tratate în detectarea formularelor XFA și extragerea pachetelor XFA cu PDFium
Evenimente gazdă pentru alerte, imprimare, e-mail și trimiterea formularelor
Fiecare acțiune vizibilă pe care o face un script se întoarce în codul dvs. prin intermediul evenimentelor TPdf, iar fiecare dintre ele are ca valoare implicită o operațiune sigură de tip no-op atunci când este nealocată. OnJavaScriptAlert primește mesajul, titlul, tipul butonului și pictograma de la app.alert și vă permite să returnați rezultatul butonului apăsat; OnJavaScriptResponse gestionează solicitările de intrare app.response, inclusiv indicatorul de parolă; OnJavaScriptBeep, OnJavaScriptPrint, OnJavaScriptMail și OnJavaScriptSubmitForm expun cererile de beep, printare, e-mail și trimitere a formularelor cu seturile lor complete de parametri. Un vizualizator care nu asociază niciunul dintre acestea continuă să randeze și să calculeze corect; documentul pur și simplu nu poate deschide ferestre de dialog, nu poate imprima sau trimite nimic
procedure TViewerForm.PdfJavaScriptAlert(Sender: TObject;
const Msg, Title: WString; ButtonType, Icon: Integer;
var Result_: Integer; var Handled: Boolean);
begin
// Redirecționați app.alert prin VCL pentru a fi deținut de fereastra noastră
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
// Nimic nu este transmis decât dacă acest handler alege să facă acest lucru
LogAudit(Format('Document requested form submit to %s (%d bytes)',
[Url, Length(Data)]));
end;
Această separare a comportamentului implicit contează pentru modelarea amenințărilor. Evenimentele OnJavaScriptMail și OnJavaScriptSubmitForm sunt de tip notificare: PDFium Component nu efectuează nicio acțiune de rețea sau MAPI de la sine, prin urmare un document malițios care apelează this.submitForm sau this.mailDoc pe o gazdă nepregătită nu realizează absolut nimic. Gazda trebuie să opteze prin alocarea unui handler și implementarea transportului în sine, ceea ce înseamnă că decizia de a muta datele de pe mașină este întotdeauna a dvs., luată în codul dvs., având URL-ul și conținutul în mână
Cum blocați un PDF malițios să lanseze URL-uri?
Acțiunile URI reprezintă singurul loc în care comportamentul implicit din trecut era activ și nu pasiv, motiv pentru care au primit o blocare dedicată. Înainte de versiunea 2.13.1, callback-ul FFI_DoURIActionWithKeyboardModifier rula prin shell-execute orice URI furnizat de un câmp XFA, în mod necondiționat, ceea ce oferea unui document malițios posibilitatea de a lansa URL-uri arbitrare la clic. OnXfaUriAction închide această vulnerabilitate: componenta consultă evenimentul înainte de execuție, iar un handler care setează Handled la True anulează complet comportamentul implicit ShellExecuteW. Lăsarea evenimentului nealocat păstrează comportamentul vechi pentru compatibilitate, așa că un vizualizator orientat pe securitate ar trebui să îl asocieze întotdeauna
procedure TViewerForm.PdfXfaUriAction(Sender: TObject;
const Uri: WString; Modifiers: Integer; var Handled: Boolean);
begin
// Blocați orice nu este simplu https; comportamentul implicit ar
// rula altfel prin ShellExecuteW URI-ul ca atare
if not SameText(Copy(Uri, 1, 8), 'https://') then
begin
LogAudit('Blocked URI action: ' + Uri);
Handled := True;
end;
end;
O listă de permisiuni (allow-list) pe schemă este minimul necesar; un vizualizator mai strict cere confirmarea utilizatorului sau direcționează totul prin propria sa componentă de browser securizată (sandboxed). Aceeași filosofie de blocare se extinde pe tot setul de evenimente v2.13.1: OnXfaEmail, OnXfaHttpRequest și OnXfaOpenFile expun fiecare parametrii textuali ai acțiunii solicitate, în timp ce componenta în sine nu efectuează niciun transport de fișiere sau de rețea. Modul în care aceste protecții se încadrează într-o strategie mai largă pentru documente nesigure, inclusiv izolarea randării, este tratat în construirea unei previzualizări PDF securizate în Delphi
Scrierea în câmpul focalizat cu SetFocusedFormFieldText
TPdf.SetFocusedFormFieldText, adăugat în versiunea 2.13.2, este perechea de pe partea de scriere a FocusedFormFieldText: selectează conținutul curent al câmpului focalizat prin FORM_SelectAllText și îl suprascrie prin FORM_ReplaceSelection, exact ca și cum utilizatorul ar fi tastat textul de înlocuire. Deoarece textul intră prin calea de editare interactivă, orice script de tastă, formatare și calcul asociat câmpului se declanșează la fel ca pentru introducerea manuală, ceea ce îl face primitiva ideală pentru completarea programatică într-un vizualizator care menține JavaScript-ul activ. Parcurgerea câmpurilor și gestionarea focalizării sunt prezentate în navigarea în câmpurile de formular cu PDFium Component
if FPdf.FocusedFormFieldIndex >= 0 then
begin
if not FPdf.SetFocusedFormFieldText('42.50') then
ShowMessage('No focused field accepts text on this page');
// AcroForm: valoarea se confirmă în /V când focalizarea părăsește câmpul.
// XFA: scrierea rămâne doar în bufferul de editare din memorie și
// nu va supraviețui unei salvări
end;
Asimetria de persistență din acel comentariu este o limită reală, nu o simplă mențiune. Pentru câmpurile text și combo AcroForm, valoarea editată se confirmă în intrarea /V a câmpului la pierderea focalizării și persistă la salvare. Pentru câmpurile text XFA, scrierea se face doar în bufferul de editare din memorie, deoarece PDFium nu expune niciun API public pentru a reconcilia valorile widget-ului înapoi în pachetul de seturi de date (datasets) XFA; salvarea documentului nu va păstra modificarea. Dacă cerința este editarea durabilă a datelor XFA, editați XML-ul seturilor de date propriu-zis, nu widget-ul
Limite ce merită cunoscute înainte de lansare
Rămân două limite oneste. În primul rând, API-ul JavaScript pe care îl implementează PDFium este un subset funcțional al modelului de obiecte Acrobat, concentrat pe partea de scripting a formularelor: acces la câmpuri, evenimente de calcul și formatare, app.alert și app.response, cereri de printare, e-mail și trimitere. Documentele care se bazează pe obiecte exotice specifice doar pentru Acrobat se vor degrada, de obicei în mod silențios, așa că testați formularele reale pe care le gestionează utilizatorii dvs. în loc să presupuneți o paritate completă. În al doilea rând, activarea V8 înseamnă livrarea fișierului pdfium.v8.dll, un binar substanțial mai mare decât versiunea simplă; un vizualizator care nu are nevoie de scripting sau XFA poate păstra în mod legitim DLL-ul mai mic și poate lăsa m_pJsPlatform neatașat, ceea ce reprezintă o postură de securitate validă în sine
Modelul care rezultă din toate acestea este simplu: setați EnableV8Engine la pornire, asociați evenimentele de alertă și răspuns pentru ca ferestrele de dialog să pară native, asociați OnXfaUriAction și înregistrați în jurnal evenimentele de e-mail și trimitere, lăsând restul acțiunilor la valoarea lor implicită no-op până când o cerință impune altfel. Referința completă a evenimentelor și structura API-ului de completare a formularelor sunt documentate pe pagina produsului PDFium Component