Um PDF de fatura ou despesa onde os itens de linha se somam sozinhos e a data se formata automaticamente precisa de três coisas anexadas ao seu AcroForm, e o HotPDF adiciona todas as três a um arquivo carregado no Delphi: ApplyLoadedFieldCalculation para totais AFSimple_Calculate, ApplyLoadedFieldValidation para os assistentes de validação e formato AF do Acrobat, e EnsureLoadedFieldAppearanceStream para gerar o valor que um visualizador realmente desenha
O HotPDF é um componente PDF nativo de VCL para Delphi e C++Builder, e esses três métodos operam em um documento que você abriu com o LoadFromFile em vez de um que você esteja construindo do zero. Cada um recebe um índice de campo baseado em zero na matriz AcroForm /Fields — a mesma ordem que o GetLoadedFormFieldNames retorna — e grava diretamente no dicionário do campo, de modo que todo o trabalho consiste em carregar, indexar, anexar e salvar com SaveLoadedDocument. A construção do próprio conjunto de campos é uma tarefa separada, abordada em adicionando campos AcroForm a um PDF carregado no Delphi, enquanto esta página trata de fornecer aritmética e formatação a um conjunto de campos existente
Por que um formulário PDF precisa de scripts para somar seus próprios campos?
Porque o PDF não possui uma camada de planilha. Um campo de texto armazena uma string, não uma fórmula, e nada no arquivo recalcula um total quando um item de linha é alterado, a menos que um script esteja anexado para fazer isso. O mecanismo é o dicionário de ações adicionais do campo, /AA (ISO 32000-1 §12.7.5), que mapeia eventos de disparo para ações: um gatilho de pressionamento de tecla (K) conforme os caracteres chegam, um gatilho de formatação para exibição, um gatilho de validação (V) na confirmação e um gatilho de cálculo (C) que dispara sempre que qualquer campo no formulário é alterado. Cada ação é uma ação JavaScript, /S /JavaScript (ISO 32000-1 §12.6.4.14), e the script que ela executa é o que realmente realiza a soma ou a reformatação. O Acrobat fornece uma biblioteca de auxiliares prontos — AFSimple_Calculate, AFNumber_Format, AFDate_FormatEx e os demais —, de modo que o script anexado geralmente é uma chamada de linha única para essa biblioteca em vez de um código sob medida
Um detalhe decide se um formulário de várias etapas fornece a resposta correta: a ordem de cálculo. O Acrobat executa cada campo que carrega uma ação de cálculo e os executa na sequência que a matriz AcroForm /CO lista, não na ordem das páginas. O ApplyLoadedFieldCalculation anexa a ação de cálculo ao campo que você nomeia; quando um campo calculado alimenta outro — um subtotal que uma linha de imposto lê, e depois um total geral que lê ambos —, você deve confirmar que a ordem de /CO lista cada entrada antes do campo que a consome, caso contrário o total geral lerá um subtotal desatualizado e ficará uma edição atrasado
Como fazer um formulário PDF calcular totais automaticamente?
O ApplyLoadedFieldCalculation recebe o índice do campo de destino, uma operação e os nomes dos campos de origem que ele deve agregar. O HotPDF grava AFSimple_Calculate("SUM", ["lineTotal.1", "lineTotal.2", ...]) na ação de cálculo do campo de destino, e a enumeração THPDFFieldCalcOp escolhe a função: fccSum, fccAverage, fccProduct, fccMinimum e fccMaximum mapeiam para Acrobat SUM, AVG, PRD, MIN e MAX. Os nomes de origem são nomes de campos AcroForm, as mesmas strings que o GetLoadedFormFieldNames retorna, de modo que um total sobre três itens de linha e uma taxa de envio é uma única chamada
var
Pdf: THotPDF;
Names: THPDFAnsiStringArray;
i, TotalIdx: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('invoice.pdf');
Names := Pdf.GetLoadedFormFieldNames;
TotalIdx := -1;
for i := 0 to High(Names) do
if Names[i] = 'grandTotal' then TotalIdx := i;
if TotalIdx >= 0 then
begin
Pdf.ApplyLoadedFieldCalculation(TotalIdx, fccSum,
['lineTotal.1', 'lineTotal.2', 'lineTotal.3', 'shipping']);
Pdf.EnsureLoadedFieldAppearanceStream(TotalIdx);
end;
Pdf.SaveLoadedDocument('invoice-live.pdf');
finally
Pdf.Free;
end;
end;
A chamada EnsureLoadedFieldAppearanceStream no total não é cosmética. O cálculo é executado dentro do visualizador, por isso, até que um leitor compatível com scripts abra o arquivo, o campo de total não possui conteúdo renderizado próprio. Gerar um fluxo de aparência fornece a ele um valor para mostrar nesse meio tempo, que é o que um trabalho de impressão ou um gerador de miniaturas usará quando não executar o script
Formatando e validando campos com a suíte de auxiliares AF
O ApplyLoadedFieldValidation anexa os scripts de formato, pressionamento de tecla e validação em uma única chamada, e o tipo de THPDFFieldValidationKind que você passa decide qual auxiliar do Acrobat ele conecta. Passe fvkCurrency e o HotPDF grava AFNumber_Format(2,0,0,0,"$ ",true) como o script de formato com o correspondente AFNumber_Keystroke; fvkNumber usa "." para o argumento separador, fvkPercentage emite AFPercent_Format e fvkDate emite AFDate_FormatEx("dd/mm/yyyy") a menos que você passe sua própria máscara no argumento DateFormat. Os formatos especiais — fvkZipCode, fvkSSN e fvkPhone — mapeiam para AFSpecial_Format com o código que o Acrobat reserva para cada um, e fvkArbitraryMask recebe uma string de máscara por meio do argumento Mask para qualquer coisa que essas predefinições não cubram. Para os tipos numéricos, o HotPDF também anexa um script AFRange_Validate para que entradas fora do intervalo sejam recusadas no momento da confirmação
// Formate a data da fatura como ISO, the valor como moeda e aplique uma máscara a um ID fiscal
Pdf.ApplyLoadedFieldValidation(DateIdx, fvkDate, 'yyyy-mm-dd', '');
Pdf.ApplyLoadedFieldValidation(AmountIdx, fvkCurrency, '', '');
Pdf.ApplyLoadedFieldValidation(TaxIdIdx, fvkArbitraryMask, '', '999-99-9999');
Pdf.EnsureLoadedFieldAppearanceStream(AmountIdx);
Os auxiliares AF cobrem as formas comuns para que você nunca precise escrever JavaScript manualmente para eles. Quando um campo precisar de uma lógica que nenhuma predefinição expresse — um checksum, uma regra entre campos, uma regex personalizada —, anexe o script bruto você mesmo com os métodos de ação em nível de campo descritos em construindo campos e ações AcroForm com o HotPDF, que recebem uma string JavaScript diretamente. Ambos os caminhos gravam no mesmo dicionário /AA, de modo que um formato de moeda predefinido em um campo e uma validação personalizada em outro coexistem no mesmo documento
Por que você deve regenerar o fluxo de aparência?
Porque a string de valor e os pixels que um visualizador desenha são duas coisas diferentes no PDF, e alterar um não altera o outro. Um campo armazena seu valor em /V, mas o que um leitor renderiza vem do fluxo de aparência do campo, /AP /N. Edite /V através do grafo de objetos e um visualizador estrito ainda mostrará a aparência antiga até que algo a regenere. O dicionário de formulário carrega uma flag /NeedAppearances que solicita ao visualizador que reconstrua todas as aparências ao abrir, e o Acrobat a respeita — mas renderizadores incorporados em navegadores, leitores móveis, servidores de impressão e geradores de miniaturas frequentemente a ignoram e desenham qualquer fluxo que já esteja presente, ou nada. O EnsureLoadedFieldAppearanceStream fecha essa lacuna construindo o próprio Form XObject /AP /N, a partir de /DA, /V e /Rect do campo, para que o valor renderizado viaje dentro do arquivo em vez de depender do visualizador para redesenhá-lo
// Gere uma aparência para cada campo antes de salvar, para que os valores sejam renderizados em todos os lugares
Names := Pdf.GetLoadedFormFieldNames;
for i := 0 to High(Names) do
Pdf.EnsureLoadedFieldAppearanceStream(i);
Pdf.SaveLoadedDocument('invoice-flat-appearance.pdf');
Onde os scripts AF param de funcionar
Cada script AF* depende do visualizador ter um motor JavaScript, e a maioria dos visualizadores não tem. O Acrobat e alguns produtos desktop executam esses auxiliares; os visualizadores de PDF integrados no Chrome, Edge, Firefox e nas principais plataformas móveis, junto com rasterizadores e fluxos de impressão no servidor, não executam nenhum JavaScript de formulário. Nesses leitores, o cálculo nunca dispara e o formato nunca é aplicado — o campo simplesmente exibe o fluxo de aparência com o qual foi salvo pela última vez, que é exatamente o motivo pelo qual a geração do /AP /N é importante. Trate a camada AF* como uma conveniência para o subconjunto de usuários em um leitor compatível com scripts, nunca como uma garantia. Qualquer total que precise estar correto no servidor — um valor de fatura contra o qual você fará a cobrança, um valor de imposto a ser arquivado — deve ser recalculado do seu lado após o retorno do formulário, porque você não pode presumir que o leitor que o preencheu executou uma única linha de seu script
Mais um caso merece ser mencionado: formulários que chegaram como XFA. Um formulário XFA dinâmico carrega seu próprio motor de cálculo em uma carga útil XML sobre a qual os auxiliares AF* não sabem nada, portanto, anexar AFSimple_Calculate a um campo XFA não realiza nada. Achate o formulário XFA para um AcroForm simples primeiro, conforme abordado em achatamento de XFA para AcroForm no Delphi, e os três métodos aqui passarão a se aplicar aos campos AcroForm resultantes exatamente como fazem para qualquer outro formulário carregado
AcroForms de cálculo automático e autoformatação se resumem a três chamadas em um documento carregado — anexar o cálculo, anexar o formato e validação, gerar a aparência — seguidas por SaveLoadedDocument. A API de formulário carregado mostrada aqui faz parte do Componente HotPDF para Delphi e C++Builder, que documenta as enumerações completas de THPDFFieldValidationKind e THPDFFieldCalcOp ao lado do restante da superfície do AcroForm