Uma fatura ou PDF de despesas onde os itens de linha se somam sozinhos e a data se formata a si mesma precisa de três elementos integrados no seu AcroForm, e o HotPDF adiciona todos os três a um ficheiro carregado no Delphi: o ApplyLoadedFieldCalculation para totais AFSimple_Calculate, o ApplyLoadedFieldValidation para os assistentes de validação e formatação Acrobat AF, e o EnsureLoadedFieldAppearanceStream para processar o valor que um visualizador realmente desenha
O HotPDF é um componente PDF nativo de VCL para Delphi e C++Builder, e estes três métodos operam num documento que abriu com LoadFromFile em vez de um que esteja a construir do zero. Cada um recebe um índice de campo baseado em zero na matriz AcroForm /Fields — a mesma ordem que o GetLoadedFormFieldNames devolve — e escreve diretamente no dicionário de campos, pelo que todo o trabalho consiste em carregar, indexar, anexar e usar o SaveLoadedDocument. A construção do próprio conjunto de campos é uma tarefa separada, abordada em adicionar campos AcroForm a um PDF carregado no Delphi, enquanto esta página se foca em aplicar aritmética e formatação a um conjunto de campos existente
Por que um formulário PDF precisa de scripts para somar os seus próprios campos?
Porque o PDF não possui uma camada de folha de cálculo. Um campo de texto contém uma string, não uma fórmula, e nada no ficheiro recalcula um total quando um item de linha muda, a menos que um script esteja anexado para o fazer. O mecanismo é o dicionário de ações adicionais do campo, /AA (ISO 32000-1 §12.7.5), que mapeia eventos de disparo (triggers) para ações: um disparo de pressionamento de tecla (keystroke) (K) à medida que os caracteres chegam, um disparo de formatação para exibição, um disparo de validação (V) na confirmação e um disparo de cálculo (C) que é acionado sempre que qualquer campo no formulário seja alterado. Cada ação é uma ação JavaScript, /S /JavaScript (ISO 32000-1 §12.6.4.14), e the script que executa é o que realmente realiza a soma ou a reformatação. O Acrobat fornece uma biblioteca de auxiliares prontos a usar — AFSimple_Calculate, AFNumber_Format, AFDate_FormatEx e os restantes —, pelo que o script anexado é normalmente uma chamada de uma única linha para essa biblioteca, em vez de código personalizado
Um detalhe determina se um formulário com várias etapas fornece a resposta correta: a ordem de cálculo. O Acrobat executa todos os campos que contêm uma ação de cálculo, e executa-os na sequência que a matriz /CO do AcroForm lista, e não na ordem das páginas. O ApplyLoadedFieldCalculation anexa a ação de cálculo ao campo que nomear; quando um campo calculado alimenta outro — um subtotal que uma linha de impostos lê e, em seguida, um total geral que lê ambos —, tem de confirmar se a ordem da /CO lista cada entrada antes do campo que a consome, caso contrário, o total geral lerá um subtotal obsoleto e ficará um passo atrás na edição
Como fazer com que um formulário PDF calcule totais automaticamente?
O ApplyLoadedFieldCalculation recebe o índice do campo de destino, uma operação e os nomes dos campos de origem que deve agregar. O HotPDF escreve 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 devolve, pelo que um total sobre três itens de linha e um custo de envio é uma chamada única
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 do EnsureLoadedFieldAppearanceStream no total não é cosmética. O cálculo é executado dentro do visualizador, pelo que, até que um leitor capaz de executar scripts abra o ficheiro, o campo de total não possui conteúdo renderizado próprio. Processar um fluxo de aparência fornece-lhe um valor para mostrar enquanto isso não acontece, o que é útil para um trabalho de impressão ou um gerador de miniaturas (thumbnails) que nunca execute o script
Formatar e validar campos com o conjunto de auxiliares AF
O ApplyLoadedFieldValidation anexa os scripts de formatação, pressionamento de tecla (keystroke) e validação numa única chamada, e o THPDFFieldValidationKind que passar decide qual o auxiliar do Acrobat que ele liga. Passe fvkCurrency e o HotPDF escreve AFNumber_Format(2,0,0,0,"$ ",true) como o script de formatação com o AFNumber_Keystroke correspondente; fvkNumber utiliza "." para o argumento separador, fvkPercentage emite AFPercent_Format e fvkDate emite AFDate_FormatEx("dd/mm/yyyy") a menos que passe a 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 o fvkArbitraryMask recebe uma string de máscara através 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 script para que entradas fora de intervalo sejam recusadas na confirmação
// Formata a data da fatura como ISO, o montante como moeda e mascara o ID de imposto
Pdf.ApplyLoadedFieldValidation(DateIdx, fvkDate, 'yyyy-mm-dd', '');
Pdf.ApplyLoadedFieldValidation(AmountIdx, fvkCurrency, '', '');
Pdf.ApplyLoadedFieldValidation(TaxIdIdx, fvkArbitraryMask, '', '999-99-9999');
Pdf.EnsureLoadedFieldAppearanceStream(AmountIdx);
Por que deve regenerar o fluxo de aparência?
Porque a string de valor e os píxeis que um visualizador desenha são duas coisas diferentes no PDF, e alterar uma não altera a outra. Um campo armazena o seu valor em /V, mas o que um leitor renderiza vem do fluxo de aparência do campo, /AP /N. Edite o /V através do gráfico de objetos e um visualizador estrito continuará a mostrar a aparência antiga até que algo a regenere. O dicionário do formulário contém uma flag /NeedAppearances que solicita ao visualizador que reconstrua todas as aparências ao abrir, e o Acrobat honra-a — mas os renderizadores incorporados em browsers, leitores móveis, servidores de impressão e geradores de miniaturas frequentemente ignoram-na e desenham qualquer fluxo que já esteja presente, ou simplesmente nada. O EnsureLoadedFieldAppearanceStream fecha essa lacuna ao construir o próprio Form XObject /AP /N, a partir do campo /DA, /V e /Rect, para que o valor renderizado viaje dentro do ficheiro em vez de depender do visualizador para o redesenhar
// Processa uma aparência para cada campo antes de guardar, para que os valores sejam renderizados em todo o lado
Names := Pdf.GetLoadedFormFieldNames;
for i := 0 to High(Names) do
Pdf.EnsureLoadedFieldAppearanceStream(i);
Pdf.SaveLoadedDocument('invoice-flat-appearance.pdf');
Onde os scripts AF deixam de funcionar
Todos os scripts AF* dependem de o visualizador possuir um motor de JavaScript, e a maioria dos visualizadores não tem nenhum. O Acrobat e um punhado de produtos desktop executam estes auxiliares; os visualizadores de PDF integrados no Chrome, Edge, Firefox e nas principais plataformas móveis, juntamente com rasterizadores do lado do servidor e fluxos de impressão, não executam qualquer JavaScript de formulário. Nesses leitores, o cálculo nunca é acionado e a formatação nunca se aplica — o campo simplesmente mostra o fluxo de aparência com o qual foi gravado pela última vez, que é exatamente a razão pela qual o processamento do /AP /N é importante. Trate a camada AF* como uma conveniência para o subconjunto de utilizadores num leitor capaz de executar scripts, nunca como uma garantia. Qualquer total que deva estar correto no servidor — o montante de uma fatura contra o qual vai cobrar, um valor de imposto a declarar — tem de ser recalculado do seu lado após o formulário regressar, porque não pode assumir que o leitor que o preencheu tenha executado uma única linha do seu script
Mais um caso vale a pena mencionar: formulários que chegam como XFA. Um formulário XFA dinâmico carrega o seu próprio motor de cálculo numa carga útil (payload) XML sobre a qual os auxiliares AF* nada sabem, pelo que anexar o AFSimple_Calculate a um campo XFA não atinge qualquer resultado. 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 apresentados aplicar-se-ão aos campos AcroForm resultantes exatamente como fazem com qualquer outro formulário carregado
AcroForms de auto-cálculo e auto-formatação resumem-se a três chamadas num documento carregado — anexar o cálculo, anexar a formatação e validação, processar a aparência — seguidas pelo SaveLoadedDocument. A API de formulários carregados mostrada aqui faz parte do HotPDF Component para Delphi e C++Builder, que documenta as enumerações completas THPDFFieldValidationKind e THPDFFieldCalcOp juntamente com o resto da superfície AcroForm