Artículo técnico

Combinar formularios PDF en Delphi: reglas de campos duplicados

PDF Library for Delphi combina dos documentos AcroForm con una política explícita para los campos que comparten nombre. MergeDocumentEx recibe el identificador del documento de origen y una de tres estrategias: dfsReject rechaza la combinación, dfsMerge conserva el nombre compartido y sincroniza los valores, y dfsAutoNumber renombra los campos entrantes de forma determinista. El escaneo de nombres ocurre antes de que se desplace ningún número de objeto, así que una combinación rechazada deja ambos documentos completamente utilizables

Cualquiera que haya ensamblado un paquete de solicitud en PDF se ha topado con esto. Tres formularios, cada uno con un campo llamado Signature, Date o Total, se combinan en un solo archivo. En un AcroForm, el nombre de campo totalmente cualificado es la identidad del campo, así que dos campos con el mismo nombre no son dos campos en absoluto: rellenar uno rellena el otro, y una firma aplicada a uno cubre un alcance que nadie pretendía

¿Por qué se decide la colisión de nombres antes de la combinación?

El antiguo MergeDocument concatena los dos arrays de campos raíz de AcroForm y no ofrece elección. Peor aún, cuando el resultado es inutilizable, el descubrimiento ocurre después de renumerar los números de objeto y coser los árboles de página, lo cual deja a quien llama con un documento en un estado en el que ninguno de los originales estaba

MergeDocumentEx invierte el orden. Recopila los nombres de campo de nivel superior de ambos documentos, los compara, y aplica la estrategia antes de que se mueva nada. Un rechazo es, por tanto, una operación nula limpia: el documento de destino queda intacto, el documento de origen queda intacto, y ambos permanecen abiertos y utilizables, algo que la prueba de combinación verifica leyendo de nuevo un valor de campo del origen tras una combinación rechazada

La comparación usa un conjunto de nombres ordenado y sensible a mayúsculas, así que el coste es proporcional al recuento combinado de campos multiplicado por un factor logarítmico en lugar de al producto de los dos recuentos. La sensibilidad a mayúsculas es la elección correcta aquí porque los nombres de campo PDF distinguen mayúsculas de minúsculas; plegarlos combinaría campos que la especificación trata como distintos

Las tres estrategias, y cuándo es correcta cada una

dfsReject es la estrategia para canalizaciones automatizadas que no deben producir documentos ambiguos. La combinación devuelve cero y LastErrorCode informa de 705, un código dedicado para que los nombres duplicados puedan distinguirse de cualquier otro fallo de combinación y encaminarse a un remedio específico, normalmente renombrar campos en origen

dfsMerge conserva el nombre compartido de forma deliberada y sincroniza el valor de destino y el valor predeterminado en el campo de origen, de modo que un visor conforme trata los varios widgets como un único campo lógicamente nombrado, que es el comportamiento estándar de AcroForm para un campo con varias anotaciones de widget. Lo que no hace es fusionar distintos diccionarios de campo en un único objeto. Cada campo conserva su propia asociación de página, apariencia y acciones, porque colapsarlos descartaría silenciosamente formato y comportamiento que pertenecen al documento entrante

dfsAutoNumber renombra los duplicados entrantes añadiendo un sufijo numérico que empieza en _2 y toma el primero libre. El resultado es reproducible: depende solo de los nombres presentes, nunca de los números de objeto de campo, así que combinar el mismo par de documentos dos veces produce los mismos nombres ambas veces. Esa propiedad importa cuando código posterior, una importación FDF o una correspondencia de base de datos hace referencia a los campos por nombre

uses
  PDFlibrary;

var
  Lib: TPDFlib;
  TargetDoc, SourceDoc: Integer;
begin
  Lib := TPDFlib.Create;
  try
    TargetDoc := Lib.SelectedDocument;
    Lib.LoadFromFile('application-part1.pdf', '');

    SourceDoc := Lib.NewDocument;
    Lib.LoadFromFile('application-part2.pdf', '');

    Lib.SelectDocument(TargetDoc);
    if Lib.MergeDocumentEx(SourceDoc, dfsReject) = 0 then
    begin
      if Lib.LastErrorCode = 705 then
      begin
        // Ambos documentos siguen intactos - reintentar con una política
        Log('duplicate field names; retrying with auto-numbering');
        Lib.MergeDocumentEx(SourceDoc, dfsAutoNumber);
      end;
    end;

    Lib.SaveToFile('application-complete.pdf');
  finally
    Lib.Free;
  end;
end;

Observe el patrón de dos pasos de ese código, que solo es posible porque el rechazo no es destructivo. Pruebe primero la política estricta, inspeccione el error, y luego decida. Con una combinación que falla a mitad de camino, el respaldo tendría que empezar de nuevo recargando ambos archivos

Qué aspecto tiene el formulario combinado después

Con dfsMerge, un campo de destino llamado Shared que lleva «Target value» y un campo de origen con el mismo nombre producen dos campos, ambos llamados Shared, ambos informando del valor de destino, porque el valor de destino y el valor predeterminado se sincronizan en el campo entrante. Esa es la semántica prevista para un nombre compartido: un campo lógico, varios widgets, un valor

Con dfsAutoNumber, la misma entrada produce Shared y Shared_2 como campos separados con valores independientes. Elija entre las dos haciéndose una única pregunta: ¿debería rellenar un control rellenar también el otro? Para un nombre de firmante repetido en cada parte de un paquete, sí, y dfsMerge es correcto. Para un total que significa algo distinto en cada formulario, no, y la numeración automática es correcta

// Tras una combinación, enumere lo que realmente obtuvo
for I := 1 to Lib.FormFieldCount do
  Log(Format('%d: %s = %s',
    [I, Lib.GetFormFieldTitle(I), Lib.GetFormFieldValue(I)]));

Notas prácticas para ensamblar paquetes de formularios

Una combinación exitosa consume el documento de origen: se elimina de la lista de documentos de la biblioteca, razón por la que DocumentCount baja de dos a uno. No siga usando el identificador de origen después. La versión del documento se eleva a la mayor de las dos, así que combinar un formulario PDF 2.0 en un documento 1.7 produce un archivo 2.0

El orden importa para los nombres. Combinar A en B y combinar B en A producen resultados de numeración automática distintos, ya que el documento que realiza la combinación conserva sus nombres sin cambios. Cuando un paquete tiene un formulario principal canónico, haga que ese sea el destino

Los campos de firma merecen su propia consideración. Una firma aplicada antes de una combinación cubre solo la revisión que firmó, así que la combinación la invalida en el sentido práctico de que el archivo ha cambiado desde la firma. Ensamble primero y firme el documento ensamblado, en lugar de combinar partes ya firmadas. Cuando la combinación trata sobre contenido de página en lugar de formularios, la vía más rápida descrita en la combinación rápida de PDF con desplazamiento de referencias por bytes es la mejor herramienta

Por último, planifique el lado de los datos del paquete junto con la combinación. Si los valores de campo llegan de un sistema externo, decida si ese sistema direcciona los campos por nombre antes de elegir la numeración automática, porque Shared_2 no coincidirá con una correspondencia que espera Shared. Los formatos de importación y exportación se tratan en el intercambio de datos de formulario FDF, XFDF y XFA, y el comportamiento de scripting a nivel de campo que también puede verse afectado por el renombrado se trata en las acciones de formulario interactivas y JavaScript

La combinación de formularios, el intercambio de datos y la firma se ejecutan en la misma biblioteca para Delphi, C++Builder y Free Pascal; la lista completa de funciones está en la página de PDF Library for Delphi