Een formulier waarvan de totalen in Acrobat herberekenen maar in uw eigen Delphi-viewer bevroren blijven, is vrijwel nooit een renderbug. PDFium schakelt alle document-JavaScript uit tenzij de host een JS-platform koppelt, en PDFium Component bedraadt dat platform vanaf versie 2.13.2 automatisch, zodat AcroForm-JavaScript, scripts op documentniveau en veldberekeningen draaien zodra de DLL met V8 is geladen. De host houdt de leiding via een reeks events die alles wat het script probeert kunnen tonen, omleiden of blokkeren
Die ene zin lost een ondersteuningsvraag op die we in verschillende vormen hebben gezien: "mijn bestelformulier berekent regeltotalen in Acrobat maar niet in mijn app", "app.alert gaat nooit af", "het datumveld formatteert zichzelf niet". Alle drie zijn terug te voeren op hetzelfde contract, en dat begrijpen is tien minuten waard, want datzelfde contract is ook uw beveiligingsgrens tegen vijandige documenten
Waarom werken formulierberekeningen in PDFium niet?
PDFium behandelt document-JavaScript als strikt opt-in: is het lid m_pJsPlatform van de formulieromgeving NULL, dan wordt elk script in het document stilzwijgend overgeslagen. Geen fout, geen logregel, geen gedeeltelijke uitvoering. Berekende velden houden hun laatst opgeslagen waarde, app.alert verdwijnt, opmaakscripts draaien nooit. Acrobat, dat altijd zijn eigen JavaScript-engine meelevert, draait hetzelfde bestand vrolijk, en daarom lijkt het verschil zo sterk op een bug in uw code terwijl het in werkelijkheid een bewuste standaardinstelling is in de bibliotheek eronder
Bij PDFium Component zat hier een tweede, historische laag op. Tot en met versie 2.13.1 koppelde de binding m_pJsPlatform alleen binnen de initialisatietak voor XFA, dus gewone AcroForm-documenten, de overgrote meerderheid van de PDF-bestanden met scripts, kregen helemaal geen JavaScript-platform, zelfs niet wanneer de DLL met V8 aanwezig was. Versie 2.13.2 haalde de koppeling uit de XFA-beslissing: InitializeFormFill bouwt de tabel IPDF_JsPlatform nu zodra V8FeaturesAvailable True teruggeeft, onafhankelijk van de vraag of het document XFA is. Het praktische gevolg is dat scripts op documentniveau, ketens van veldberekeningen en app.alert nu ook in gewone AcroForm-documenten draaien
Welke V8-DLL heeft AcroForm-JavaScript in Delphi nodig?
AcroForm-JavaScript heeft een PDFium-binary nodig die met de V8-engine is gecompileerd, en PDFium Component laadt die als pdfium.v8.dll wanneer de globale vlag EnableV8Engine True is voordat de bibliotheek voor het eerst laadt. Er is hier één valstrik die het ronduit benoemen waard is: de component detecteert XFA-documenten automatisch door vooraf op /XFA-markeringen te scannen en zet EnableV8Engine voor u om, maar een gewoon AcroForm-document met JavaScript draagt geen XFA-markering, dus die automatische detectie gebeurt niet. Moet uw viewer formulierscripts draaien, zet de vlag dan zelf, eenmalig, voordat het eerste document laadt; nadat een gewone pdfium.dll is geladen, kan het proces niet meer van engine wisselen
uses PDFium;
procedure TViewerForm.FormCreate(Sender: TObject);
begin
// Moet draaien voordat de PDFium-singleton voor het eerst laadt;
// zit de gewone pdfium.dll eenmaal in het proces, dan is wisselen onmogelijk
EnableV8Engine := True;
end;
procedure TViewerForm.OpenDocument(const FileName: string);
begin
FPdf.LoadDocument(FileName);
if not V8FeaturesAvailable then
StatusBar.SimpleText :=
'JavaScript disabled: pdfium.v8.dll not deployed';
end;
V8FeaturesAvailable is de eerlijke peiling tijdens de uitvoering: het meldt of de geladen binary de V8-afhankelijke exports werkelijk oplost, zodat de controle werkt ongeacht welke DLL een installer uiteindelijk heeft uitgerold. Dezelfde engine en dezelfde vlag voeden ook het renderen van dynamische XFA; de achtergrond bij de wisselwerking tussen XFA-detectie en de automatische V8-keuze staat in XFA-formulieren detecteren en XFA-pakketten extraheren met PDFium
Host-events voor meldingen, afdrukken, e-mail en formulierverzending
Alles zichtbaars wat een script doet, komt via events op TPdf bij uw code terug, en elk daarvan valt bij een niet-toegewezen handler terug op een veilige lege actie. OnJavaScriptAlert ontvangt het bericht, de titel, het knoptype en het pictogram van app.alert en laat u het resultaat van de ingedrukte knop teruggeven; OnJavaScriptResponse bedient de invoerprompts van app.response inclusief de wachtwoordvlag; OnJavaScriptBeep, OnJavaScriptPrint, OnJavaScriptMail en OnJavaScriptSubmitForm brengen de verzoeken om piepen, afdrukken, mailen en verzenden met hun volledige parametersets naar boven. Een viewer die er geen enkele toewijst, rendert en berekent nog steeds correct; het document kan alleen geen dialoogvensters openen, niet afdrukken en niets versturen
procedure TViewerForm.PdfJavaScriptAlert(Sender: TObject;
const Msg, Title: WString; ButtonType, Icon: Integer;
var Result_: Integer; var Handled: Boolean);
begin
// Leid app.alert via de VCL zodat ons venster de eigenaar is
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
// Er wordt niets verzonden tenzij deze handler daarvoor kiest
LogAudit(Format('Document requested form submit to %s (%d bytes)',
[Url, Length(Data)]));
end;
De scheiding in standaardgedrag telt voor uw dreigingsmodel. OnJavaScriptMail en OnJavaScriptSubmitForm zijn kennisgevend van aard: PDFium Component voert uit zichzelf geen netwerk- of MAPI-actie uit, dus een kwaadaardig document dat this.submitForm of this.mailDoc aanroept tegen een onvoorbereide host bereikt precies niets. De host moet zich aanmelden door een handler toe te wijzen en het transport zelf te implementeren, wat betekent dat de beslissing om gegevens van de machine af te voeren altijd van u is, in uw code genomen, met de URL en de lading in de hand
Hoe houdt u een kwaadaardige PDF tegen die URL wil openen?
URI-acties zijn de ene plek waar de historische standaard actief was in plaats van passief, en juist daarom kregen zij een eigen blokkade. Vóór versie 2.13.1 voerde de callback FFI_DoURIActionWithKeyboardModifier onvoorwaardelijk elke URI uit die een XFA-veld aanleverde, wat een kwaadaardig document de mogelijkheid gaf willekeurige URL's bij een klik te starten. OnXfaUriAction sluit dat af: de component raadpleegt het event vóór uitvoering, en een handler die Handled op True zet, onderdrukt de standaardaanroep van ShellExecuteW volledig. Het event niet toewijzen behoudt het oude gedrag omwille van compatibiliteit, dus een beveiligingsbewuste viewer hoort het altijd toe te wijzen
procedure TViewerForm.PdfXfaUriAction(Sender: TObject;
const Uri: WString; Modifiers: Integer; var Handled: Boolean);
begin
// Blokkeer alles wat geen gewone https is; de standaard zou de
// URI anders ongewijzigd via ShellExecuteW starten
if not SameText(Copy(Uri, 1, 8), 'https://') then
begin
LogAudit('Blocked URI action: ' + Uri);
Handled := True;
end;
end;
Een toelatingslijst op het schema is het minimum; een strengere viewer bevestigt bij de gebruiker of leidt alles door een eigen browsercomponent in een sandbox. Dezelfde blokkadefilosofie loopt door de hele eventset van v2.13.1: OnXfaEmail, OnXfaHttpRequest en OnXfaOpenFile stellen elk de tekstuele parameters van de gevraagde actie bloot terwijl de component zelf geen netwerk- of bestandstransport uitvoert. Hoe deze beveiligingen in een bredere strategie voor onvertrouwde documenten passen, inclusief isolatie bij het renderen, is het onderwerp van een veilig PDF-voorbeeld bouwen in Delphi
Naar het gefocuste veld schrijven met SetFocusedFormFieldText
TPdf.SetFocusedFormFieldText, toegevoegd in versie 2.13.2, is de schrijfkant naast FocusedFormFieldText: het selecteert de huidige inhoud van het gefocuste veld via FORM_SelectAllText en overschrijft die met FORM_ReplaceSelection, precies alsof de gebruiker de vervanging had getypt. Omdat de tekst via het interactieve bewerkpad binnenkomt, gaan alle toetsaanslag-, opmaak- en berekeningsscripts die aan het veld hangen net zo af als bij handmatige invoer, wat het de juiste bouwsteen maakt voor programmatisch vullen in een viewer die JavaScript levend houdt. Het doorlopen van velden en het focusbeheer eromheen komen aan bod in navigeren door formuliervelden met 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: de waarde legt vast in /V zodra de focus het veld verlaat.
// XFA: de schrijfactie blijft alleen in de bewerkbuffer in het geheugen
// en overleeft een opslag niet
end;
De asymmetrie in duurzaamheid in die opmerking is een echte grens, geen kanttekening voor de vorm. Bij AcroForm-tekst- en keuzelijstvelden legt de bewerkte waarde bij focusverlies vast in de vermelding /V van het veld en blijft zij bewaard bij opslaan. Bij XFA-tekstvelden landt de schrijfactie alleen in de bewerkbuffer in het geheugen, want PDFium biedt geen publieke API om widgetwaarden terug te verzoenen met het XFA-datasets-pakket; het document opslaan neemt de wijziging niet mee. Is duurzaam bewerken van XFA-gegevens de eis, bewerk dan de datasets-XML zelf en niet de widget
Grenzen die u kent voordat u uitlevert
Twee eerlijke grenzen blijven over. Ten eerste is de JavaScript-API die PDFium implementeert een werkende deelverzameling van het Acrobat-objectmodel, met de kern van het formulierscripten in het midden: veldtoegang, gebeurtenissen voor berekening en opmaak, app.alert en app.response, en verzoeken om afdrukken, mailen en verzenden. Documenten die op exotische, Acrobat-eigen objecten leunen, zullen degraderen, meestal stilzwijgend, dus test de formulieren die uw gebruikers werkelijk hanteren in plaats van gelijkwaardigheid aan te nemen. Ten tweede betekent V8 inschakelen dat u pdfium.v8.dll meelevert, een aanzienlijk grotere binary dan de gewone build; een viewer die nooit scripts of XFA nodig heeft, mag met recht de kleinere DLL houden en m_pJsPlatform ongekoppeld laten, wat op zichzelf een geldige beveiligingshouding is
Het patroon dat hieruit volgt, is aangenaam saai: zet EnableV8Engine bij het opstarten, wijs de events voor meldingen en antwoorden toe zodat dialoogvensters er systeemeigen uitzien, wijs OnXfaUriAction toe en leg de mail- en verzendevents in een auditlogboek vast, en laat al het overige op de lege standaard staan tot een eis iets anders zegt. De volledige eventreferentie en het API-oppervlak voor het invullen van formulieren staan gedocumenteerd op de productpagina van de PDFium Component