A PDF Library for Delphi mescla dois documentos AcroForm com uma política explícita para campos que compartilham um nome. MergeDocumentEx recebe o identificador do documento de origem e uma de três estratégias: dfsReject recusa a mesclagem, dfsMerge mantém o nome compartilhado e sincroniza os valores, e dfsAutoNumber renomeia os campos de entrada de forma determinística. A varredura de nomes acontece antes de qualquer número de objeto se deslocar, então uma mesclagem rejeitada deixa os dois documentos totalmente utilizáveis
Quem já montou um pacote de aplicação em PDF já passou por isso. Três formulários, cada um com um campo chamado Signature, Date ou Total, são mesclados em um único arquivo. Em um AcroForm, o nome totalmente qualificado do campo é a identidade do campo, então dois campos com o mesmo nome não são dois campos de fato: preencher um preenche o outro, e uma assinatura aplicada a um cobre um escopo que ninguém pretendia
Por que a colisão de nomes é decidida antes da mesclagem?
O antigo MergeDocument concatena os dois arrays de campos raiz do AcroForm e não oferece escolha. Pior, quando o resultado é inutilizável, a descoberta acontece depois que os números de objeto já foram renumerados e as árvores de página costuradas, o que deixa o chamador com um documento em um estado em que nenhum dos originais estava
MergeDocumentEx inverte a ordem. Ele coleta os nomes de campo de nível superior dos dois documentos, compara-os, e aplica a estratégia antes que qualquer coisa se mova. Uma rejeição é, portanto, um no-op limpo: o documento alvo permanece intocado, o documento de origem permanece intocado, e ambos continuam abertos e utilizáveis, o que o teste de mesclagem verifica lendo de volta o valor de um campo a partir da origem depois de uma mesclagem recusada
A comparação usa um conjunto de nomes ordenado e sensível a maiúsculas e minúsculas, então o custo é proporcional à contagem combinada de campos vezes um fator logarítmico, em vez do produto das duas contagens. A sensibilidade a maiúsculas e minúsculas é a escolha correta aqui porque nomes de campo em PDF são sensíveis a maiúsculas e minúsculas; nivelá-los mescularia campos que a especificação trata como distintos
As três estratégias, e quando cada uma é a certa
dfsReject é a estratégia para pipelines automatizados que não podem produzir documentos ambíguos. A mesclagem retorna zero e LastErrorCode relata 705, um código dedicado para que nomes duplicados possam ser distinguidos de qualquer outra falha de mesclagem e encaminhados para uma correção específica, geralmente renomear campos a montante
dfsMerge mantém o nome compartilhado deliberadamente e sincroniza o valor alvo e o valor padrão no campo de origem, então um visualizador em conformidade trata os vários widgets como um único campo logicamente nomeado, que é o comportamento padrão do AcroForm para um campo com várias anotações de widget. O que ela não faz é fundir diferentes dicionários de campo em um único objeto. Cada campo mantém sua própria associação de página, aparência e ações, porque colapsá-los descartaria silenciosamente formatação e comportamento que pertencem ao documento de entrada
dfsAutoNumber renomeia as duplicatas de entrada anexando um sufixo numérico começando em _2 e usando o primeiro livre. O resultado é reproduzível: depende apenas dos nomes presentes, nunca dos números de objeto de campo, então mesclar o mesmo par de documentos duas vezes produz os mesmos nomes nas duas vezes. Essa propriedade importa quando código a jusante, uma importação FDF ou um mapeamento de banco de dados referencia campos pelo nome
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
// Os dois documentos ainda estão intactos - tenta de novo com uma 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 o padrão de duas etapas nesse código, que só é possível porque a rejeição é não destrutiva. Tente primeiro a política estrita, inspecione o erro e depois decida. Com uma mesclagem que falha no meio do caminho, o fallback teria que recomeçar recarregando os dois arquivos
Como fica o formulário mesclado depois
Sob dfsMerge, um campo alvo chamado Shared carregando "Target value" e um campo de origem com o mesmo nome produzem dois campos, ambos chamados Shared, ambos relatando o valor alvo, porque o valor alvo e o valor padrão são sincronizados no campo de entrada. Essa é a semântica pretendida para um nome compartilhado: um campo lógico, vários widgets, um valor
Sob dfsAutoNumber, a mesma entrada produz Shared e Shared_2 como campos separados com valores independentes. Escolha entre os dois fazendo uma única pergunta: preencher um controle deveria preencher o outro? Para o nome de um signatário repetido em todas as partes de um pacote, sim, e dfsMerge é a escolha certa. Para um total que significa algo diferente em cada formulário, não, e a numeração automática é a escolha certa
// Depois de uma mesclagem, enumere o que você realmente obteve
for I := 1 to Lib.FormFieldCount do
Log(Format('%d: %s = %s',
[I, Lib.GetFormFieldTitle(I), Lib.GetFormFieldValue(I)]));
Notas práticas para montar pacotes de formulários
A mesclagem bem-sucedida consome o documento de origem: ele é removido da lista de documentos da biblioteca, motivo pelo qual DocumentCount cai de dois para um. Não continue usando o identificador de origem depois disso. A versão do documento é elevada para a maior das duas, então mesclar um formulário PDF 2.0 em um documento 1.7 produz um arquivo 2.0
A ordem importa para os nomes. Mesclar A em B e mesclar B em A produzem resultados de numeração automática diferentes, já que o documento que faz a mesclagem mantém seus nomes inalterados. Quando um pacote tem um formulário primário canônico, torne esse o alvo
Campos de assinatura merecem sua própria consideração. Uma assinatura que foi aplicada antes de uma mesclagem cobre apenas a revisão que assinou, então a mesclagem a invalida no sentido prático de que o arquivo mudou desde a assinatura. Monte primeiro e assine o documento montado, em vez de mesclar partes já assinadas. Quando a mesclagem é sobre conteúdo de página, e não sobre formulários, o caminho mais rápido descrito em mesclagem rápida de PDF com deslocamento de referências por byte é a ferramenta melhor
Por fim, planeje o lado dos dados do pacote junto com a mesclagem. Se os valores de campo chegam de um sistema externo, decida se esse sistema endereça campos pelo nome antes de escolher a numeração automática, porque Shared_2 não vai combinar com um mapeamento que espera Shared. Os formatos de importação e exportação são abordados em intercâmbio de dados de formulário FDF, XFDF e XFA, e o comportamento de scripts em nível de campo, que também pode ser afetado pela renomeação, é abordado em ações de formulário interativas e JavaScript
Mesclagem de formulários, intercâmbio de dados e assinatura rodam na mesma biblioteca para Delphi, C++Builder e Free Pascal; a lista completa de recursos está na página da PDF Library for Delphi