Una acción AcroForm es un diccionario adjunto a un widget que le indica al visor qué hacer cuando ocurre algo con ese widget. Al hacer clic en un botón, el visor lee su diccionario de acciones: una acción URI abre una dirección web, una acción JavaScript ejecuta un script, una acción SubmitForm envía los valores recopilados del campo a un endpoint, una acción ResetForm los devuelve a los valores predeterminados. La acción es un dato, no un comportamiento integrado en el archivo. ISO 32000-1 §12.6 define la estructura del diccionario; el visor proporciona el motor que lo interpreta. Esa división es importante porque una acción escrita perfectamente en el PDF no hace nada si el lector en el otro extremo no tiene un motor para ella, y muchos de los problemas con AcroForm se deben a esa brecha en lugar de a un campo mal formado
HotPDF escribe esos diccionarios directamente desde Delphi y C++Builder, junto a los widgets de los campos a los que pertenecen. Entran en juego dos estructuras en cada formulario interactivo: el widget que el usuario ve en la página, y la maquinaria subyacente del campo y la acción que transporta los datos y la lógica. Se editan de forma independiente, y cualquiera de los dos puede ser incorrecto mientras el otro luce bien. Las secciones a continuación detallan la nomenclatura de los campos, las acciones de los botones en sí, el JavaScript a nivel de campo y el tipo de defecto que sobrevive a una revisión visual porque reside completamente en la segunda estructura
Los nombres de los campos son claves de enrutamiento, no leyendas
Cada campo de AcroForm tiene un nombre completamente calificado. ISO 32000-1 §12.7.3 hace que ese nombre, no la leyenda visible, sea la clave bajo la cual viaja el valor del campo cuando se exporta o envía el formulario. Los desarrolladores que provienen del diseño de VCL tienden a tratar el nombre de un control como un identificador de código privado, y aquí no lo es. Es el formato de transferencia
Lo primero que se deduce es que dos campos con el mismo nombre completamente calificado no son dos campos. PDF los trata como dos anotaciones de widget de un solo campo, compartiendo un solo valor, de modo que escribir en uno actualiza el otro al instante. Eso es exactamente lo que desea cuando el nombre de un cliente tiene que repetirse en cada página de un contrato. Es un error cuando un ciclo de generación reutiliza 'Field1' en tres páginas por accidente. Ninguna inspección visual detecta el segundo caso. Cada página sigue dibujando su propio cuadro, y la conexión solo sale a la luz cuando alguien comienza a escribir
Los nombres con puntos, como applicant.email, construyen una jerarquía. El nodo principal applicant agrupa a sus hijos, lo que permite que un restablecimiento o envío apunte solo a una parte de un formulario. Nombrar los campos de esta manera desde el principio no cuesta nada, y vale la pena la primera vez que el sistema receptor solicita solo el bloque del solicitante
Los botones de opción (radio buttons) tienen su propia regla. Los botones que deben alternarse juntos deben compartir un nombre de grupo. En HotPDF, las llamadas a AddRadioButton que pasan el mismo nombre de grupo adjuntan sus widgets a un campo principal, y el valor de exportación de cada botón ('basic' o 'full') identifica la opción elegida. Si le da a cada botón un nombre distinto, obtendrá una fila de interruptores de encendido/apagado independientes en lugar de un grupo mutuamente excluyente, lo que se renderiza idénticamente pero se comporta de forma incorrecta
Creación del conjunto de campos página por página
HotPDF coloca los campos a través de los métodos de THPDFPage, por lo que cada campo pertenece al objeto de la página que lo creó. La trampa de secuencia a la que debe prestar atención es AddPage. Redirige CurrentPage a la nueva página en el instante en que retorna, por lo que cualquier llamada de campo después de ella aterriza en la nueva página, incluso cuando el campo pertenecía lógicamente a la página que acaba de dejar. Termine cada página, el contenido dibujado y los campos juntos, antes de llamar a AddPage
procedure BuildClaimForm(Pdf: THotPDF);
begin
// Page 1: applicant block
Pdf.CurrentPage.AddTextField('applicant.name', '', Rect(50, 700, 300, 722));
Pdf.CurrentPage.AddTextField('applicant.email', '', Rect(50, 660, 300, 682));
Pdf.CurrentPage.AddCheckBox('consent', 'Y', Rect(50, 620, 70, 640), False);
Pdf.CurrentPage.AddRadioButton('coverage', 'basic', Rect(50, 580, 70, 600), True);
Pdf.CurrentPage.AddRadioButton('coverage', 'full', Rect(90, 580, 110, 600), False);
Pdf.CurrentPage.AddComboBox('plan', 'Standard',
['Basic', 'Standard', 'Premium'], Rect(50, 540, 200, 565));
Pdf.AddPage; // CurrentPage now points at page 2
Pdf.CurrentPage.AddListBox('riders', 'None',
['None', 'Flood', 'Earthquake'], Rect(50, 500, 200, 600));
end;
Las coordenadas usan la convención de PDF, con el origen en la esquina inferior izquierda de la página. Este es el mismo origen que usa TextOut para el texto dibujado, por lo que Rect(50, 100, 200, 120) se ubica cerca de la parte inferior de una página tamaño carta, no en la parte superior. VCL coloca Y en la parte superior y crece hacia abajo, por lo que una tabla de diseño portada directamente resulta invertida verticalmente, con cada campo volteado hacia el extremo equivocado de la página. Haga la conversión una vez en un asistente auxiliar compartido en lugar de en cada punto de llamada, y una sola corrección solucionará todo el formulario
Conexión de botones a acciones URI, JavaScript y submit
Un botón está inactivo hasta que se le adjunta una acción. HotPDF expone los tipos de acción de ISO 32000-1 §12.6.4 a través de la enumeración THPDFButtonAction (baURI, baJavaScript, baSubmitURL, baResetForm, baHide, baShow, baNamed), y proporciona dos métodos que crean el botón y vinculan su acción en una sola llamada
// Open a help page in the system browser
Pdf.CurrentPage.AddPushButtonWithAction('btnHelp', 'Help',
'https://www.example.com/claims-help', Rect(320, 700, 420, 730), baURI);
// Run viewer-side JavaScript
Pdf.CurrentPage.AddPushButtonWithAction('btnRecalc', 'Recalculate',
'app.alert("Totals updated.");', Rect(320, 660, 420, 690), baJavaScript);
// Submit as XFDF and keep empty fields in the payload
Pdf.CurrentPage.AddPushButtonWithSubmitAction('btnSubmit', 'Submit claim',
'https://api.example.com/claims', Rect(320, 620, 420, 650),
[sffXFDF, sffIncludeNoValueFields]);
Las opciones de envío merecen más reflexión de la que suelen recibir. AddPushButtonWithSubmitAction toma un conjunto THPDFSubmitFormFlags, y un conjunto vacío produce un envío normal codificado como URL (url-encoded), que es el formato que muchos endpoints de ejemplo aceptan y que muchos endpoints de producción rechazan. Agregar sffXFDF cambia el payload a XFDF. sffGetMethod cambia el verbo HTTP. sffIncludeNoValueFields mantiene los campos vacíos en el payload en lugar de descartarlos silenciosamente, lo cual es importante en el momento en que el consumidor distingue "ausente" de "en blanco". El conjunto de opciones es parte de su contrato de interfaz con el endpoint receptor, así que defínalo con el equipo que procesa el envío, no después del primer lote rechazado
JavaScript a nivel de campo: pulsación de tecla, formato, validación
Los clics en botones no son el único lugar donde residen las acciones. HotPDF también adjunta JavaScript a los eventos por campo que disparan los visores compatibles con scripts mientras un usuario ingresa datos. Hay tres desencadenadores (triggers), y se disparan en diferentes puntos del ciclo de vida de la entrada. Una acción de pulsación de tecla (keystroke) se ejecuta cuando llega cada carácter y nuevamente al confirmar (commit). Una acción de formato (format) reescribe el valor mostrado después de que se ha confirmado un cambio, puramente por presentación. Una acción de validación (validate) tiene la última palabra, aceptando o rechazando el valor confirmado antes de que se convierta en el valor del campo
// Reject committed values that are not plausible email addresses
Pdf.AttachFieldKeyStrokeAction('applicant.email',
'if (event.willCommit && !/^[\w.-]+@[\w.-]+\.\w+$/.test(event.value)) event.rc = false;');
// Display US phone numbers as (NNN) NNN-NNNN
Pdf.AttachFieldFormatAction('applicant.phone',
'event.value = event.value.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");');
// Refuse applicants under 18 at commit time
Pdf.AttachFieldValidateAction('applicant.age',
'if (parseInt(event.value) < 18) event.rc = false;');
Configurar event.rc = false dentro de un script de pulsación de tecla o validación le indica al visor que rechace la entrada. El problema es que nada de esto se ejecuta a menos que el visor incluya un motor JavaScript. Acrobat y algunos productos de escritorio tienen uno. La mayoría de los lectores móviles, renderizadores integrados en navegadores y flujos de impresión no lo tienen, y descartan los scripts sin avisar. Por lo tanto, los scripts de campo mejoran la calidad de los datos para el subconjunto de usuarios cuyo lector los ejecuta, y eso es todo lo que hacen. No son un límite de seguridad. Cada valor enviado aún debe validarse en el servidor una vez que llega, porque no puede asumir que el cliente comprobó algo
Defectos que superan la revisión visual
Los defectos más difíciles de detectar en AcroForm son los que residen en la estructura de datos en lugar de en la renderización, porque abrir el archivo y mirarlo no le dice nada. Cuatro surgen con suficiente frecuencia como para que valga la pena nombrarlos, y cada uno tiene una prueba mecánica que lo encuentra antes del lanzamiento
- Desviación del valor de exportación. Una casilla de verificación creada como
AddCheckBox('consent', 'Yes', ...)envíaYes. Un consumidor que coincide conYrechaza cada envío mientras la página se ve perfecta. Llene el formulario, expórtelo como XFDF desde Acrobat y compare los valores con el esquema que el consumidor realmente espera - Duplicación accidental de valores. Dos campos que comparten un nombre completamente calificado se fusionan en uno. El síntoma aparece en el momento de la entrada de datos y nunca en el momento de la generación, por lo que la prueba consiste en escribir en el formulario, no en renderizarlo y revisar el resultado visualmente
- Valores combinados (combo) fuera de la lista de opciones. Cuando el valor actual pasado a
AddComboBoxno es una de las opciones de la lista, los visores no se ponen de acuerdo sobre si mostrarlo, dejarlo en blanco o marcarlo. Mantenga el valor predeterminado dentro de la lista y el desacuerdo desaparecerá - Campos que aún se pueden editar después de cerrar el flujo de trabajo. HotPDF no tiene una llamada para aplanar (flatten) la apariencia de los campos AcroForm. La forma admitida de congelar un formulario completado es crear los campos con la opción
ffReadOnly, que mantiene el valor visible a través de la propia secuencia de apariencia del campo al mismo tiempo que rechaza las ediciones. El campo sigue siendo un objeto de formulario activo, que es lo que las herramientas de ensamblaje y firma posteriores esperan encontrar
Vale la pena mencionar un comportamiento del lado del visor para realizar pruebas de regresión, incluso si ningún cambio de código lo aborda. Las implementaciones empresariales de Acrobat pueden desactivar JavaScript o restringir los destinos de envío por políticas, por lo que una acción que funcionó en todas las compilaciones de desarrollo puede quedar inactiva en el escritorio bloqueado de un cliente. Planifique una alternativa visible para el caso en que el botón no haga nada, incluso si esa alternativa es solo una instrucción impresa que le dice al usuario qué hacer en su lugar
Dónde se conecta el trabajo del formulario con el resto del documento
Un campo de firma es en sí mismo un tipo de campo AcroForm. Para un formulario que será certificado o contrafirmado más adelante, es mejor reservar ese campo durante la generación que aplicarle un parche después, y las razones a nivel de bytes del por qué están en el artículo complementario sobre firmas digitales y firma PAdES con HotPDF. Las entradas que llegan como paquetes XFA en lugar de AcroForm nativo son una situación diferente: el aplanamiento de XFA en campos AcroForm es su propio flujo de trabajo con su propio modelo de pérdida, porque las dos tecnologías de formulario no pueden coexistir en un archivo
Los métodos de campos, acciones y desencadenadores que se muestran aquí forman parte de la API estándar del Componente HotPDF para Delphi y C++Builder; la página del producto enlaza a la referencia completa, incluidas las sobrecargas de las opciones (flags) del campo y la enumeración completa de las opciones de envío