Você tem um modelo de nota fiscal de terceiros, ou um contrato arquivado que alguém gerou anos atrás em um software que ninguém mais consegue localizar, e o requisito é torná-lo interativo: colocar uma caixa de assinatura no canto, adicionar alguns campos de texto, transformar uma lista estática em caixas de seleção reais. O problema é que você não está criando esse PDF do zero. Ele já existe, já tem páginas, fluxos de conteúdo e fontes que você não controla, e você precisa anexar widgets AcroForm a esse grafo de objetos sem reconstruí-lo. Isso é diferente de criar um formulário em um documento novo, e a parte que costuma pegar as pessoas só fica visível quando o resultado é aberto em um visualizador e os campos que você acabou de gravar simplesmente não aparecem na página
HotPDF é um componente PDF nativo VCL para Delphi e C++Builder e, a partir da v2.247.0, expõe uma família dedicada de métodos exatamente para isso: construir os seis tipos padrão de campo diretamente em um documento carregado com LoadFromFile. Este artigo mostra o que esses métodos fazem, o dicionário ISO 32000-1 que eles constroem e a única flag sem a qual toda a operação produz silenciosamente um arquivo que parece vazio
Por que um documento já carregado exige outro fluxo
Quando você constrói um PDF do zero, o HotPDF controla todo o modelo de objetos. Cada página é um wrapper gravável THPDFPage, e adicionar um campo de texto por meio de AddTextField conecta o novo widget ao objeto de anotação da página, ao objeto de página e à coleção de campos do formulário, e então gera um stream de aparência a partir dos recursos de fonte do documento. O stream de aparência é a superfície visível do widget, a caixa, a borda e qualquer texto padrão, desenhados como operadores PDF que o visualizador renderiza literalmente
Um documento carregado não oferece essa estrutura. As páginas entram como dicionários brutos; não existe um wrapper gravável THPDFPage para receber o widget e, mais importante, não há um pipeline de recursos de fonte pronto para pintar streams de aparência. O caminho de documento carregado, portanto, segue outra rota. Ele grava os dicionários dos campos diretamente no grafo de objetos já analisado e endereça as páginas por índice baseado em zero, em vez de usar o objeto de página. Os tipos de campo e os bits de flag são exatamente os mesmos do caminho de criação do zero, então um campo Text é um campo Text em qualquer caso; o que muda é a infraestrutura por baixo e, principalmente, a forma como a superfície do widget é desenhada
Por que /NeedAppearances precisa estar presente
Este é o único fato que decide se o seu trabalho aparece. Como o caminho de documento carregado não gera streams de aparência, um widget recém-adicionado chega ao visualizador sem entrada /AP: um campo sem superfície descrita. Muitos visualizadores, quando recebem um widget sem aparência e sem instrução para criá-la, não desenham nada. O campo está no arquivo, é estruturalmente válido, pode ser acessado por uma ferramenta de preenchimento e fica completamente invisível para uma pessoa
A saída está definida em ISO 32000-1 §12.7.3: o dicionário AcroForm carrega um booleano /NeedAppearances, e quando ele é true um leitor conforme precisa construir por conta própria os streams de aparência ausentes a partir da string /DA (default appearance) e do valor de cada campo. O HotPDF define isso para você. Na primeira vez que você adiciona qualquer campo a um documento carregado, EnsureLoadedAcroForm é executado: se o catalog não tiver /AcroForm, ele cria um; se não houver array /Fields, ele cria esse também; e força /NeedAppearances true. Você não chama isso diretamente, mas saber que isso existe explica o comportamento. Também explica uma ressalva de implantação que vale dizer com clareza: alguns visualizadores mínimos ou não conformes ignoram /NeedAppearances e continuam sem renderizar nada. Para leitores comuns a flag faz o trabalho, mas, se o seu público usa um renderer embutido fora do padrão, teste nele antes de prometer qualquer coisa
Incluindo os seis tipos de campo
Todos os métodos seguem a mesma forma. Você passa o índice da página baseado em zero, os quatro cantos do retângulo do widget nas coordenadas de espaço do usuário PDF, o nome do campo e quaisquer argumentos extras exigidos pelo tipo. O retângulo é X1, Y1, X2, Y2, com a origem do PDF no canto inferior esquerdo da página, então valores maiores em Y ficam mais acima; essa é a convenção de coordenadas do formato de arquivo, não a convenção de tela com origem no canto superior esquerdo, e inverter isso é o segundo erro mais comum depois de esquecer a flag. Cada chamada retorna o índice baseado em zero do novo campo, ou -1 se o índice da página estiver fora do intervalo ou se o objeto de página não puder ser resolvido
var
Pdf: THotPDF;
Idx: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('contract.pdf') <= 0 then Exit;
// Text field: name, initial value, max length (0 = unlimited)
Idx := Pdf.AddLoadedTextField(0, 72, 680, 320, 700, 'FullName', '', 0);
// CheckBox: export value, initial checked state
Pdf.AddLoadedCheckBox(0, 72, 640, 90, 658, 'AgreeTerms', 'Yes', False);
// Signature field: just a name and a rectangle
Pdf.AddLoadedSignatureField(0, 360, 72, 540, 132, 'ApproverSig');
if Idx >= 0 then
Pdf.SaveLoadedDocument('contract-interactive.pdf');
finally
Pdf.Free;
end;
end;
Os terceiro e quarto argumentos de string do campo de texto são o nome do campo e o seu valor inicial /V; o inteiro é /MaxLen, gravado apenas quando maior que zero. O HotPDF atribui a todo campo editável uma string de aparência padrão /Helv 12 Tf 0 0 0 rg, que é o que um visualizador que respeita /NeedAppearances lê para decidir a fonte e a cor com que pinta o valor. O checkbox recebe um valor de exportação, a string que o formulário envia quando a caixa é marcada, mais um booleano para o estado inicial; internamente ele grava as entradas de nome correspondentes /V, /AS e /DV para que o estado ligado/desligado fique consistente no momento em que o arquivo abre. Um valor de exportação vazio usa por padrão Yes, o nome convencional do estado "ligado" do checkbox
Campos de escolha e as flags de bit /Ff
ComboBox e ListBox são ambos campos de escolha, do tipo /Ch em ISO 32000-1 §12.7.4. A diferença entre um dropdown e uma lista rolável é um único bit no inteiro de flags do campo /Ff: o bit 18, a flag Combo, valor $40000. O HotPDF define esse bit em AddLoadedComboBox e o deixa desmarcado em AddLoadedListBox; fora isso os dois são idênticos, e ambos recebem suas opções como um array aberto de strings gravado na entrada /Opt
// Dropdown (Combo flag set internally) with an initial selection
Pdf.AddLoadedComboBox(0, 72, 600, 300, 620, 'Country', 'Canada',
['United States', 'Canada', 'Mexico']);
// Scrolling list, no initial value
Pdf.AddLoadedListBox(0, 72, 520, 300, 590, 'Priority', '',
['Low', 'Normal', 'High']);
// Push button with a caption drawn through /MK
Pdf.AddLoadedPushButton(0, 360, 600, 480, 626, 'SubmitBtn', 'Submit');
Duas observações sobre a lista de opções. O HotPDF grava cada entrada /Opt como uma string simples, em que o valor de exportação e o rótulo exibido são o mesmo texto. A ISO 32000-1 §12.7.4.4 também permite o formato de dois elementos [export display] quando você precisa que o valor enviado seja diferente do que o usuário lê; os métodos de criação para documento carregado usam o formato mais simples de string única, então, se você precisar de valores de exportação e exibição diferentes, terá de defini-los manualmente no dicionário resultante. E o valor que você passa como seleção atual do campo deve ser uma das opções fornecidas, porque o visualizador compara isso com a lista
O botão de ação é o outro caso guiado por flag: tipo de campo /Btn com o bit 17, a flag PushButton, valor $10000. Esse bit é o que separa um botão clicável de um checkbox, que também é um campo /Btn, mas sem essa flag. A legenda que você passa é gravada no dicionário de características de aparência /MK como a legenda normal /CA. Vale ser honesto quanto ao escopo: o botão é criado com a legenda e o retângulo, mas o método de criação para documento carregado não anexa uma ação, então, sozinho, ele é um botão que parece certo e não faz nada quando clicado. Conectar ações de submit, reset ou JavaScript é um assunto separado; no lado de autoria do zero, o fluxo de campo mais ação é coberto em building AcroForm fields and actions in Delphi, que é o ponto de comparação certo para o que o caminho carregado deixa deliberadamente de fora
O dicionário que todo campo compartilha
Por baixo dos seis métodos existe um único construtor compartilhado que monta a anotação do widget e a registra em dois lugares. Ele grava /Type /Annot e /Subtype /Widget, o array /Rect com as quatro coordenadas, as flags de anotação /F 4, que definem o bit Print para que o campo apareça no papel e na tela, o nome do campo /T, o tipo de campo /FT, as flags /Ff e uma referência de volta /P para o objeto da página. Depois ele acrescenta o novo campo ao array /Fields do AcroForm e ao array /Annots daquela página, resolvendo referências indiretas ao longo do caminho para estender os arrays reais em vez de deixar o widget órfão
Esse registro duplo importa porque um widget que vive em apenas uma das duas listas fica quebrado de um jeito sutil. Um campo presente em /Fields mas ausente em /Annots é conhecido pelo formulário, mas nunca é pintado; o inverso é pintado, mas desconhecido para a lógica do formulário. O HotPDF mantém os dois sincronizados a cada adição, que é o tipo de tarefa de manutenção que você teria de acertar manualmente contra a especificação se não houvesse esse auxílio
Limites que precisam ficar claros
Defina as expectativas antes de construir um fluxo em cima disso. O comportamento de achatar e regenerar depende de o visualizador respeitar /NeedAppearances, o que cobre Acrobat, os motores PDF dos navegadores modernos e os leitores de desktop mais comuns, mas não é uma garantia dura em todo renderer que existe no mercado. Se você precisar produzir um arquivo cujos campos sejam renderizados de forma idêntica em qualquer lugar, inclusive em visualizadores que ignoram a flag, você está no território de streams de aparência e o caminho de autoria do zero, que desenha /AP para você, é a escolha mais adequada. O campo de assinatura, da mesma forma, é criado como um widget de assinatura vazio, pronto para ser assinado; colocar o campo não é o mesmo que aplicar uma assinatura criptográfica
Para alterar o que já existe, em vez de acrescentar algo, a operação relacionada é o flatten de formulário, no qual você incorpora campos interativos de volta ao conteúdo estático da página para que os valores se tornem permanentes e não editáveis; essa ida e volta, incluindo como formulários com XFA são tratados, é discutida em flattening XFA and AcroForm fields in Delphi. Adicionar campos e achatar campos são duas pontas do mesmo ciclo de vida: este artigo mostra como colocar interatividade em um documento que não a tinha, e o flatten é como você a remove de novo depois que o formulário cumpriu sua função
A API de formulário para documento carregado mostrada aqui faz parte do HotPDF Component padrão para Delphi e C++Builder, junto com a referência completa das flags de campo, do tratamento de aparência e do restante do modelo AcroForm