Un formulario cuyos totales se recalculan en Acrobat pero que permanecen congelados en su propio visor de Delphi casi nunca se debe a un error de renderizado. PDFium deshabilita todo el JavaScript del documento a menos que el host conecte una plataforma JS, y el Componente PDFium conecta esa plataforma automáticamente a partir de la versión 2.13.2 en adelante, por lo que el JavaScript de AcroForm, los scripts a nivel de documento y los cálculos de campos se ejecutan siempre que se cargue la DLL compatible con V8. El host mantiene el control a través de un conjunto de eventos que pueden mostrar, redireccionar o vetar todo lo que el script intente realizar
Esa única frase resuelve una pregunta de soporte que hemos visto bajo diversas formas: "mi formulario de pedido calcula los totales de las líneas en Acrobat pero no en mi aplicación", "app.alert nunca se activa", "el campo de fecha no se formatea solo". Todas ellas se remontan al mismo contrato, y comprenderlo merece diez minutos de atención porque ese mismo contrato representa también su límite de seguridad frente a documentos hostiles
¿Por qué no funcionan los cálculos de formularios PDF en PDFium?
PDFium trata el JavaScript del documento como estrictamente opcional (opt-in): si el miembro m_pJsPlatform del entorno de llenado de formularios es NULL, cada script del documento se omite silenciosamente. Sin error, sin línea de registro, sin ejecución parcial. Los campos calculados conservan su último valor almacenado, app.alert desaparece y los scripts de formato nunca se ejecutan. Acrobat, que siempre incluye su propio motor de JavaScript, ejecuta el mismo archivo sin problemas, razón por la cual la discrepancia parece un error en su código cuando en realidad se trata de un valor predeterminado deliberado en la biblioteca subyacente
El Componente PDFium tenía una segunda capa histórica para esto. Hasta la versión 2.13.1, la vinculación solo conectaba m_pJsPlatform dentro de la rama de inicialización de XFA, por lo que los documentos AcroForm comunes, que representan la gran mayoría de los PDFs con scripts, nunca recibían una plataforma de JavaScript, incluso cuando la DLL compatible con V8 estaba presente. La versión 2.13.2 extrajo la conexión de la decisión de XFA: InitializeFormFill ahora compila la tabla IPDF_JsPlatform siempre que V8FeaturesAvailable devuelva True, independientemente de si el documento es XFA. El resultado práctico es que los scripts a nivel de documento, las cadenas de cálculo de campos y app.alert ahora se ejecutan también en documentos AcroForm comunes
¿Qué DLL de V8 necesita el JavaScript de AcroForm en Delphi?
El JavaScript de AcroForm necesita un binario de PDFium compilado con el motor V8, que el Componente PDFium carga como pdfium.v8.dll cuando la bandera global EnableV8Engine es True antes de que la biblioteca se cargue por primera vez. Hay una trampa aquí que conviene mencionar claramente: el componente detecta automáticamente los documentos XFA pre-escaneando marcadores /XFA y activa EnableV8Engine por usted, pero un documento AcroForm común con JavaScript no contiene ningún marcador XFA, por lo que no se realiza la detección automática. Si su visor debe ejecutar scripts de formulario, establezca la bandera usted mismo, una sola vez, antes de cargar el primer documento; una vez que se carga un pdfium.dll común, el proceso no puede cambiar de motor
uses PDFium;
procedure TViewerForm.FormCreate(Sender: TObject);
begin
// Debe ejecutarse antes de que la biblioteca PDFium singleton se cargue por primera vez;
// una vez que el archivo pdfium.dll común está en el proceso, no se puede intercambiar
EnableV8Engine := True;
end;
procedure TViewerForm.OpenDocument(const FileName: string);
begin
FPdf.LoadDocument(FileName);
if not V8FeaturesAvailable then
StatusBar.SimpleText :=
'JavaScript deshabilitado: pdfium.v8.dll no desplegado';
end;
V8FeaturesAvailable representa la prueba sincera en tiempo de ejecución: informa de si el binario cargado realmente resuelve las exportaciones dependientes de V8, por lo que la comprobación funciona independientemente de qué DLL haya terminado desplegando un instalador. El mismo motor y la misma bandera también impulsan el renderizado dinámico de XFA; la información de fondo sobre cómo interactúan la detección de XFA y la autoselección de V8 se cubre en detección de formularios XFA y extracción de paquetes XFA con PDFium
Eventos del host para alertas, impresión, correo y envío de formularios
Cada acción visible que realiza un script se reconduce a su código a través de los eventos de TPdf, y cada uno de ellos tiene por defecto una operación vacía (no-op) segura cuando no está asignado. OnJavaScriptAlert recibe el mensaje, título, tipo de botón e icono de app.alert y le permite devolver el resultado del botón pulsado; OnJavaScriptResponse gestiona las solicitudes de entrada de app.response, incluyendo la bandera de contraseña; OnJavaScriptBeep, OnJavaScriptPrint, OnJavaScriptMail, y OnJavaScriptSubmitForm exponen las solicitudes de pitido, impresión, correo y envío de formularios con sus conjuntos de parámetros completos. Un visor que no asigne ninguno de ellos sigue renderizando y calculando correctamente; simplemente el documento no puede abrir diálogos, imprimir o enviar nada
procedure TViewerForm.PdfJavaScriptAlert(Sender: TObject;
const Msg, Title: WString; ButtonType, Icon: Integer;
var Result_: Integer; var Handled: Boolean);
begin
// Enrutar app.alert a través de la VCL para que sea propiedad de nuestra ventana
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
// No se transmite nada a menos que este manejador elija enviarlo
LogAudit(Format('Document requested form submit to %s (%d bytes)',
[Url, Length(Data)]));
end;
La división del comportamiento por defecto resulta importante para el modelado de amenazas. OnJavaScriptMail y OnJavaScriptSubmitForm son de tipo notificación: el Componente PDFium no realiza ninguna acción de red o MAPI por sí mismo, por lo que un documento malicioso que llame a this.submitForm o this.mailDoc contra un host no preparado no logra absolutamente nada. El host debe dar su consentimiento (opt-in) asignando un manejador e implementando el transporte por sí mismo, lo que significa que la decisión de trasladar datos fuera de la máquina es siempre suya, tomada en su código, con el URL y el contenido útil en la mano
¿Cómo evitar que un PDF malicioso inicie URLs?
Las acciones URI son el único punto donde el comportamiento predeterminado histórico era activo en lugar de pasivo, y es por ello que recibieron un veto dedicado. Antes de la versión 2.13.1, la llamada de retorno FFI_DoURIActionWithKeyboardModifier ejecutaba mediante ShellExecute cualquier URI suministrada por un campo XFA de forma incondicional, lo que otorgaba a un documento malicioso la capacidad de lanzar URLs arbitrarias al hacer clic. OnXfaUriAction cierra esa brecha: el componente consulta el evento antes de la ejecución y un manejador que establezca Handled en True suprime por completo el comportamiento predeterminado de ShellExecuteW. Dejar el evento sin asignar conserva el comportamiento antiguo por compatibilidad, por lo que un visor preocupado por la seguridad siempre debería asignarlo
procedure TViewerForm.PdfXfaUriAction(Sender: TObject;
const Uri: WString; Modifiers: Integer; var Handled: Boolean);
begin
// Vedar todo lo que no sea estrictamente https; de lo contrario, el comportamiento predeterminado
// aplicaría ShellExecuteW a la URI tal cual
if not SameText(Copy(Uri, 1, 8), 'https://') then
begin
LogAudit('Blocked URI action: ' + Uri);
Handled := True;
end;
end;
Una lista de permitidos sobre el esquema es el mínimo; un visor más estricto confirma con el usuario o canaliza todo a través de su propio componente de navegación seguro (sandboxed). La misma filosofía de veto se extiende a todo el conjunto de eventos de la versión v2.13.1: OnXfaEmail, OnXfaHttpRequest, y OnXfaOpenFile exponen cada uno los parámetros textuales de la acción solicitada mientras el propio componente no realiza ningún transporte de red o de archivos. Cómo encajan estas protecciones en una estrategia más amplia de documentos no confiables, incluyendo el aislamiento del renderizado, es el tema de la guía sobre la creación de una vista previa segura de PDF en Delphi
Escribir en el campo enfocado con SetFocusedFormFieldText
El método TPdf.SetFocusedFormFieldText, añadido en la versión 2.13.2, es el equivalente en el lado de escritura de FocusedFormFieldText: selecciona el contenido actual del campo enfocado mediante FORM_SelectAllText y lo sobrescribe a través de FORM_ReplaceSelection, exactamente igual que si el usuario hubiera escrito el reemplazo. Debido a que el texto se introduce a través de la ruta de edición interactiva, cualquier script de pulsación de tecla, formato y cálculo asociado al campo se activa de la misma manera que para la entrada manual, lo que lo convierte en la primitiva adecuada para el llenado programático en un visor que mantenga activo el JavaScript. El recorrido por los campos y la gestión del enfoque a su alrededor se tratan en navegación por campos de formulario con el Componente PDFium
if FPdf.FocusedFormFieldIndex >= 0 then
begin
if not FPdf.SetFocusedFormFieldText('42.50') then
ShowMessage('No focused field accepts text on this page');
// AcroForm: el valor se confirma en /V cuando el enfoque sale del campo.
// XFA: la escritura permanece únicamente en el búfer de edición en memoria y
// no sobrevivirá a un guardado
end;
Límites que conviene conocer antes de la distribución
Quedan dos límites honestos. En primer lugar, la API de JavaScript que implementa PDFium es un subconjunto de trabajo del modelo de objetos de Acrobat, centrado en el núcleo de scripting de formularios: acceso a campos, eventos de cálculo y formato, app.alert y app.response, solicitudes de impresión, correo y envío. Los documentos que dependen de objetos exóticos exclusivos de Acrobat se degradarán, típicamente de forma silenciosa, por lo que conviene probar los formularios reales que manejan sus usuarios en lugar de asumir la paridad. En segundo lugar, habilitar V8 implica distribuir pdfium.v8.dll, un binario sustancialmente mayor que la compilación común; un visor que nunca necesite scripting o XFA puede legítimamente conservar la DLL más pequeña y dejar m_pJsPlatform sin conectar, lo que representa una postura de seguridad válida en sí misma
El patrón resultante de todo esto es agradablemente sencillo: establezca EnableV8Engine al inicio, asigne los eventos de alerta y respuesta para que los diálogos luzcan nativos, asigne OnXfaUriAction y registre en el log de auditoría los eventos de correo y envío, y deje todo lo demás en su valor no-op predeterminado a menos que un requisito indique lo contrario. La referencia completa de eventos y la interfaz de la API de llenado de formularios están documentadas en la página del producto Componente PDFium