Uma ação AcroForm é um dicionário associado a um controlo (widget) que indica ao visualizador o que fazer quando algo acontece a esse controlo. Ao clicar num botão, o visualizador lê o seu dicionário de ações: uma ação URI abre um endereço web, uma ação JavaScript executa um script, uma ação SubmitForm envia os valores recolhidos dos campos para um endpoint e uma ação ResetForm repõe os valores predefinidos. A ação é informação, não um comportamento integrado no ficheiro. A norma ISO 32000-1 §12.6 define a estrutura do dicionário; o visualizador disponibiliza o motor que a interpreta. Esta divisão é importante porque uma ação perfeitamente descrita no PDF não fará nada se o leitor do utilizador final não possuir o motor necessário para a processar, e muitos dos problemas com AcroForms devem-se a essa lacuna e não a um campo mal estruturado.
O HotPDF escreve esses dicionários diretamente a partir do Delphi e C++Builder, juntamente com os controlos dos campos aos quais estão associados. Duas estruturas estão envolvidas em cada formulário interativo: o controlo que o utilizador visualiza na página e o mecanismo de campo e ação subjacente que suporta os dados e as ligações. Ambos são editados de forma independente, e um pode estar incorreto mesmo quando o outro aparenta estar correto. As secções abaixo detalham a nomenclatura dos campos, as ações dos botões, o JavaScript ao nível dos campos e a classe de defeitos que resistem a uma verificação visual por existirem exclusivamente no segundo mecanismo.
Os nomes dos campos são chaves de encaminhamento, não legendas
Cada campo de um AcroForm possui um nome totalmente qualificado. A norma ISO 32000-1 §12.7.3 estabelece que é esse nome, e não a legenda visível, a chave através da qual o valor do campo viaja quando o formulário é exportado ou submetido. Os programadores habituados ao desenvolvimento VCL tendem a tratar o nome de um controlo como um identificador de código privado, mas no PDF não é o caso. É o formato de transmissão.
A primeira consequência disso é que dois campos com o mesmo nome totalmente qualificado não constituem dois campos distintos. O PDF trata-os como duas anotações do mesmo campo que partilham o mesmo valor, pelo que escrever num deles atualiza imediatamente o outro. Isto é exatamente o desejado quando o nome de um cliente tem de ser repetido em todas as páginas de um contrato. Contudo, é um erro quando um ciclo de geração reutiliza acidentalmente o identificador 'Field1' em três páginas. Nenhuma inspeção visual deteta esta última situação: cada página continua a desenhar a sua caixa e a ligação só se torna evidente quando alguém começa a escrever.
Nomes delimitados por pontos, como applicant.email, criam uma hierarquia. O nó principal applicant agrupa os seus descendentes, permitindo que uma reposição ou submissão atinja apenas uma parte do formulário. Adotar esta nomenclatura desde o início não tem custos adicionais e compensa na primeira vez que o sistema recetor solicitar apenas o bloco do requerente.
Os botões de opção possuem regras próprias. Os botões que devem funcionar em conjunto têm de partilhar o mesmo nome de grupo. No HotPDF, as chamadas a AddRadioButton que utilizam o mesmo nome de grupo associam os respetivos controlos a um único campo pai, e o valor de exportação de cada botão (como 'basic' ou 'full') identifica a opção selecionada. Se atribuir um nome diferente a cada botão, obterá uma linha de interruptores independentes em vez de um grupo de exclusão mútua, os quais se apresentarão de forma idêntica mas funcionarão incorretamente.
Criar o conjunto de campos página a página
O HotPDF posiciona os campos através de métodos do THPDFPage, pelo que cada campo pertence ao objeto de página que o criou. A armadilha de sequência à qual se deve prestar atenção é o método AddPage. Este redireciona o CurrentPage para a nova página no instante em que é executado, pelo que qualquer chamada de campo subsequente será colocada na nova página, mesmo que o campo pertencesse logicamente à página anterior. Conclua cada página, incluindo o conteúdo desenhado e os campos, antes de chamar 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;
As coordenadas utilizam a convenção do PDF, com a origem no canto inferior esquerdo da página. Esta é a mesma origem que o TextOut utiliza para desenhar texto, pelo que um Rect(50, 100, 200, 120) posiciona-se próximo do fundo de uma página de tamanho Letter, e não no topo. A VCL coloca o Y no topo e fá-lo crescer para baixo, pelo que uma tabela de layout convertida diretamente aparecerá invertida verticalmente, com todos os campos na extremidade errada da página. Efetue a conversão apenas uma vez numa função auxiliar partilhada em vez de a fazer em cada chamada, e uma única correção resolverá todo o formulário.
Associar botões a ações de URI, JavaScript e submissão
Um botão de pressão é inerte até que uma ação lhe seja associada. O HotPDF expõe os tipos de ação da norma ISO 32000-1 §12.6.4 através da enumeração THPDFButtonAction (baURI, baJavaScript, baSubmitURL, baResetForm, baHide, baShow, baNamed) e disponibiliza dois métodos que criam o botão e associam a sua ação numa única chamada.
// 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]);
As flags de submissão merecem mais atenção do que aquela que costumam receber. O método AddPushButtonWithSubmitAction recebe um conjunto de THPDFSubmitFormFlags, e um conjunto vazio produz um envio simples codificado em URL, que é o formato aceite em muitos endpoints de teste, mas rejeitado na maioria dos ambientes de produção. Adicionar a flag sffXFDF altera o payload para XFDF. A flag sffGetMethod altera o verbo HTTP. A flag sffIncludeNoValueFields mantém os campos vazios no payload em vez de os remover silenciosamente, o que é importante quando o sistema de receção distingue valores ausentes de valores em branco. O conjunto de flags faz parte do contrato de interface com o endpoint recetor, por isso defina-o com a equipa responsável pelo processamento dos dados submetidos antes de gerar o primeiro lote.
JavaScript ao nível dos campos: digitação, formatação e validação
Os cliques em botões não são os únicos locais onde as ações residem. O HotPDF também associa JavaScript a eventos por campo que os visualizadores compatíveis executam enquanto o utilizador introduz dados. Existem três triggers que disparam em momentos diferentes do ciclo de vida da introdução de dados. Uma ação de digitação (keystroke) corre à medida que cada carácter é introduzido e novamente ao confirmar. Uma ação de formatação reescreve o valor apresentado após a alteração ser confirmada, apenas para fins de exibição. Uma ação de validação tem a palavra final, aceitando ou rejeitando o valor confirmado antes que este se torne o valor oficial do 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;');
Definir event.rc = false num script de digitação ou validação indica ao visualizador para rejeitar a introdução. O problema é que nada disto corre a menos que o visualizador possua um motor de JavaScript. O Acrobat e alguns produtos de desktop possuem-no, mas a maioria dos leitores móveis, visualizadores integrados em browsers e fluxos de impressão não o incluem, ignorando silenciosamente os scripts. Assim, os scripts de campo apenas melhoram a qualidade dos dados para o subconjunto de utilizadores cujos leitores os suportem. Não constituem uma barreira de segurança: todos os valores submetidos continuam a ter de ser validados no servidor após a chegada, uma vez que não se pode assumir que o cliente tenha validado o que quer que seja.
Defitos que superam a revisão visual
Os problemas mais difíceis de detetar em AcroForms são aqueles que residem na estrutura de dados e não na renderização gráfica, porque abrir o ficheiro e olhar para ele não revela qualquer anomalia. Existem quatro problemas comuns que merecem ser listados, e cada um tem um teste prático para o identificar antes do lançamento do documento.
- Divergência de valores de exportação. Uma caixa de verificação criada como
AddCheckBox('consent', 'Yes', ...)envia o valorYes. Um processador de dados que espereYrejeitará a submissão, embora a página pareça perfeita. Preencha o formulário, exporte-o como XFDF a partir do Acrobat e compare os valores com o esquema que o sistema de receção efetivamente aguarda. - Espelhamento acidental de valores. Dois campos que partilham o mesmo nome totalmente qualificado fundem-se num só. O problema manifesta-se no momento da introdução de dados e nunca na geração, pelo que o teste correto consiste em escrever no formulário, em vez de apenas visualizar o resultado.
- Valores de seleção fora da lista de opções. Quando o valor predefinido passado para
AddComboBoxnão consta na lista de opções fornecida, o comportamento dos visualizadores diverge: alguns mostram-no, outros deixam o campo em branco ou sinalizam um erro. Mantenha o valor padrão dentro da lista de opções para evitar esta inconsistência. - Campos ainda editáveis após a conclusão do fluxo de trabalho. O HotPDF não possui uma função de achatamento (flattening) de aspeto para campos AcroForm. A forma recomendada para fixar um formulário preenchido é criar os campos com a flag
ffReadOnly, o que mantém o valor visível através do fluxo de visualização próprio do campo, impedindo edições. O campo mantém-se como um objeto de formulário ativo, que é o que as ferramentas de assinatura e integração esperam encontrar.
Um comportamento específico dos visualizadores merece nota de regressão, embora nenhuma alteração de código o possa mitigar: políticas corporativas do Acrobat podem desativar o JavaScript ou restringir os destinos de submissão, fazendo com que uma ação que funcionava em desenvolvimento fique inativa num posto de trabalho restrito do cliente. Preveja uma alternativa visível para o caso de o botão não executar qualquer ação, mesmo que consista apenas numa instrução de texto que indique ao utilizador o que fazer.
Onde os formulários se ligam ao resto do documento
Um campo de assinatura é, por si só, um tipo de campo AcroForm. Um formulário que venha a ser certificado ou assinado posteriormente beneficia de reservar esse campo durante a geração em vez de o adicionar posteriormente; os motivos ao nível dos bytes encontram-se descritos no artigo de acompanhamento sobre assinaturas digitais e assinatura PAdES com o HotPDF. Documentos que chegam como pacotes XFA em vez de AcroForms nativos representam um cenário diferente: o achatamento de XFA em campos AcroForm é um fluxo de trabalho específico com o seu próprio modelo de perdas, uma vez que as duas tecnologias de formulários não podem coexistir no mesmo ficheiro.
Os métodos de campo, ação e trigger aqui demonstrados fazem parte da API padrão do Componente HotPDF para Delphi e C++Builder; a página do produto disponibiliza a referência completa, incluindo as sobrecargas de flags de campos e a enumeração completa de flags de submissão.