A XFA (XML Forms Architecture) foi descontinuada. A norma ISO 32000-1 refere-a na secção 12.7 com a nota de que foi removida no PDF 2.0, e os visualizadores modernos estão a abandonar progressivamente os seus motores XFA. Nada disso esvaziou os arquivos. Formulários de admissão governamentais, candidaturas a seguros e extratos bancários foram criados como XFA durante a maior parte de duas décadas, e esses ficheiros continuam a chegar a caixas de correio e fluxos de documentos atualmente. Quando o visualizador que costumava renderizá-los deixa de o fazer, o formulário transforma-se numa página em branco com uma mensagem de aviso para abrir num leitor diferente. A solução duradoura consiste em simplificar (flatten) o XFA em conteúdo PDF estático que qualquer leitor consiga desenhar
A parte difícil dessa simplificação não são os campos. As caixas de texto e de seleção mapeiam-se de forma bastante direta em controlos (widgets) AcroForm. A dificuldade reside no texto formatado (rich text) que o XFA guarda dentro de um elemento gráfico (draw), num bloco <exData contentType="text/html">. Esse bloco é um subconjunto de HTML com estilos inline e, frequentemente, âncoras (hiperligações). Colocá-lo na página implica reproduzir tanto o texto formatado como as hiperligações ativas, e as hiperligações constituem a etapa em que a maioria das implementações desiste discretamente
Qual é a aparência real do texto formatado XFA
O corpo de um exData é uma pequena secção de XHTML. Um parágrafo é um <p>; um bloco formatado de caracteres é um <span> com o seu próprio CSS inline para definir espessura, postura, cor e tamanho; e uma hiperligação é um <a href="..."> que envolve o seu texto visível. Uma única linha pode conter vários blocos em sequência, cada um com formatações diferentes, e um deles pode ser uma âncora. O estilo não é um adorno que possa ser descartado. Uma cláusula renderizada em vermelho e negrito por se tratar de um aviso legal deve permanecer em negrito e vermelho após a simplificação, caso contrário o documento simplificado desvirtuará o original
Por isso, o motor de simplificação não pode tratar o bloco como uma única cadeia de texto. Tem de percorrer a estrutura inline, resolver o estilo eficaz de cada sequência sobrepondo o CSS inline do span ao tipo de letra base do elemento gráfico, e dispor as sequências uma após outra ao longo da linha. O HotPDF modela cada um destes fragmentos posicionados como um registo interno TXFARichRun. O registo transporta o texto da sequência, o seu estilo resolvido, a sua caixa de dimensões medida e, para uma âncora, o Href para o qual aponta
Dispor as sequências da esquerda para a direita
O posicionamento é o ponto em que o texto formatado deixa de ser um problema de análise (parsing) e passa a ser um problema de tipografia. As sequências partilham uma linha, pelo que cada uma começa onde a anterior terminou. Não existe nenhuma marcação que registe essas posições; elas têm de ser medidas. A rotina interna do motor, LayoutRichText, mede cada sequência com as mesmas métricas de tipo de letra que a pintarão mais tarde, definindo depois o desvio horizontal da sequência para a soma cumulativa de todas as larguras de sequências anteriores. A sequência um começa na origem da caixa do desenho, a sequência dois começa no limite da largura da primeira, a três na largura combinada das duas primeiras, e assim sucessivamente ao longo da linha
É por isso que o alinhamento do tipo de letra de medição é tão importante. A etapa de disposição (layout) mede os avanços; uma etapa de renderização separada desenha os glifos. Se estas duas etapas divergirem quanto ao tipo de letra, as caixas calculadas pela disposição não coincidirão com os glifos desenhados pelo renderizador. O HotPDF mantém-nos sincronizados mapeando o estilo resolvido de cada sequência para uma especificação de tipo de letra, através do assistente interno RunStyleToFontSpec, que corresponde às predefinições do renderizador, que são Arial a 10 pontos. O avanço medido e o texto desenhado passam a coincidir, e a caixa calculada da sequência cobre genuinamente os caracteres que o leitor vê
// Forma conceptual de uma sequência (run) disposta. O motor constrói um array destes
// internamente; nunca os constrói diretamente, mas os campos explicam como
// a área de clique de uma ligação é derivada de geometria medida e não do texto.
type
TRichRunInfo = record
Dx, Dy : Double; // canto superior esquerdo, relativo à origem da caixa de desenho
W, H : Double; // caixa da sequência medida (largura da passagem de disposição)
Text : AnsiString; // os caracteres visíveis da sequência
Href : AnsiString; // destino URI para uma sequência <a>, '' caso contrário
end;
De uma sequência de âncora a uma anotação de Ligação PDF
Uma hiperligação num PDF finalizado não faz parte do conteúdo da página. Trata-se de um objeto independente, uma anotação de Ligação (Link annotation), descrita na norma ISO 32000-1, secção 12.5.6.5. A anotação possui um /Rect que define o retângulo clicável na página e uma ação que é acionada quando o retângulo é clicado. Para uma ligação externa, a ação é uma ação URI: /S /URI com o endereço de destino na sua cadeia /URI. O texto visível sob a mesma é conteúdo normal da página; a anotação é a zona ativa invisível sobreposta ao mesmo
O caminho de simplificação segue exatamente este modelo. Quando uma sequência inclui um Href, o HotPDF desenha primeiro o texto formatado e, em seguida, cria uma anotação de Ligação sobre a caixa da sequência. O ponto de entrada público para essa anotação é o método de página AddURILink, que cria o objeto /Type /Annot /Subtype /Link com uma ação /URI e devolve o dicionário da anotação. O seu retângulo é a caixa medida da sequência, convertida a partir das coordenadas locais do elemento gráfico em coordenadas da página. O resultado é uma ligação que se posiciona exatamente sobre o texto da âncora e em mais lado nenhum
// A mesma API pública que o caminho de simplificação usa para cada sequência de âncora. Produz
// uma anotação de Ligação ISO 32000-1 12.5.6.5: /Subtype /Link com uma ação /URI
// sobre o retângulo dado. A descrição opcional preenche /Contents para que um
// leitor de ecrã possa anunciar o destino.
var
LinkRect: TRect;
Annot: THPDFDictionaryObject;
begin
LinkRect := Rect(72, 690, 268, 706); // área de clique no espaço da página para a sequência
Annot := Pdf.CurrentPage.AddURILink(LinkRect,
'https://www.example.gov/appeal', 'File an appeal online');
end;
Por que razão a área de clique deve derivar das larguras medidas
É tentador idealizar a localização da ligação pesquisando na página pelo seu texto visível e desenhando o retângulo em torno do que for encontrado. Mas isso não funciona, e a razão é fundamental para a forma como o texto simplificado é guardado. As sequências estilizadas são desenhadas utilizando subconjuntos de tipos de letra (subset fonts) incorporados. Um subconjunto de tipo de letra renumera os glifos que conserva, pelo que o fluxo de conteúdo da página contém códigos CID hexadecimais, e não os códigos de caracteres originais. Os bytes na página não são as letras que um ser humano lê, e não são pesquisáveis como texto. Uma pesquisa pelo título da âncora não encontra nada, porque esse título não existe sob a forma de texto literal em nenhum ponto do fluxo
A única referência fiável para o retângulo é a geometria que a etapa de disposição já produziu. O desvio e a largura medida de cada sequência foram calculados ao fluir a linha, antes de qualquer glifo ser renumerado, e descrevem onde o texto irá fisicamente surgir. O HotPDF obtém, portanto, o retângulo da ligação diretamente a partir da caixa da sequência posicionada, em vez de recorrer a pesquisas de texto. Como a medição utilizou o tipo de letra de renderização, a caixa está correta independentemente do subconjunto de glifos. A geometria sobrevive à codificação; o texto não. Este é o argumento principal para o posicionamento baseado na largura medida, e explica por que razão um simplificador que tenta readaptar ligações por pesquisa de texto produz áreas de clique desalinhadas ou inexistentes
Controlar a simplificação a partir do seu código
Para um PDF que já contenha um pacote XFA, o ponto de entrada é o método FlattenLoadedXFA. Carrega o documento, chama o método e guarda o resultado. O parâmetro Editable decide o que acontece aos campos do formulário: passe True para os manter como controlos AcroForm preenchíveis, ou False para marcar todos os controlos como apenas de leitura, tornando o resultado num registo congelado. Os blocos de desenho de texto formatado, com as suas sequências estilizadas e anotações de ligação, são produzidos em ambos os casos. A função devolve a contagem de controlos que emitiu
var
Pdf: THotPDF;
Emitted, i: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('xfa_appeal_form.pdf');
// True mantém os campos preenchíveis; False congela-os em apenas de leitura.
Emitted := Pdf.FlattenLoadedXFA(True);
// Qualquer coisa que o motor não consiga mapear é reportada, não lançada.
for i := 0 to Pdf.XFAFlattenWarnings.Count - 1 do
Writeln('XFA warning: ', Pdf.XFAFlattenWarnings[i]);
Pdf.SaveLoadedDocument('appeal_form_flat.pdf');
Writeln('Widgets emitted: ', Emitted);
finally
Pdf.Free;
end;
end;
Leia sempre a lista XFAFlattenWarnings após a chamada. A lista é limpa no início de cada simplificação e acumula uma linha para cada elemento que o motor se recusou a renderizar: um tipo de campo não suportado, uma imagem de desenho que não pôde ser descodificada ou um bloco exData sem sequências utilizáveis. Nenhum destes casos gera uma exceção, pelo que uma lista de avisos vazia comprova que tudo foi mapeado com sucesso, e uma lista não vazia indica-lhe exatamente quais os originais a inspecionar. Quando possui o XFA bruto como bytes XDP, em vez de um PDF carregado, o método irmão ApplyXFAAsAcroForm recebe esses bytes diretamente, partilhando o mesmo caminho de código e o mesmo comportamento de avisos. O método complementar AddXFAPacket efetua o processo inverso, incorporando um pacote XFA num documento que esteja a construir
Confirmar o resultado num leitor
Abra o ficheiro simplificado no Acrobat, ou em qualquer visualizador atual, e verifique duas coisas. Primeiro, se o texto formatado foi renderizado com os seus estilos intactos: as sequências em negrito estão a negrito, as coloridas mantêm a sua cor e os blocos organizam-se na ordem correta na linha, em vez de se sobreporem ou transbordarem a caixa. Segundo, se as hiperligações estão ativas. Passe o cursor sobre uma âncora e a barra de estado deverá mostrar o endereço de destino; clique e a ação URI deverá abri-lo. Utilize o inspetor de anotações do visualizador para confirmar que cada uma é uma anotação /Link genuína cujo /Rect contorna o texto da âncora, posicionando-se sobre conteúdos que são agora apenas glifos desenhados, em vez de XFA renderizado pelo formulário. Essa combinação - texto estático formatado mais anotações de ligação reais nos retângulos corretos - é o que faz com que o documento simplificado sobreviva aos motores XFA de que já não necessita
A simplificação dos próprios campos - caixas de texto, caixas de seleção e listas de escolha que envolvem este texto formatado - é abordada no nosso guia prático sobre a simplificação de formulários XFA em controlos AcroForm. Para uma análise mais ampla da criação e posicionamento manual de anotações de Ligação, além das geradas pelo caminho de simplificação, consulte o artigo trabalhar com anotações PDF no HotPDF. Ambos se baseiam no mesmo modelo de anotações e formulários fornecido com o HotPDF Delphi Component para Delphi e C++Builder