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ê.
// Conceptual shape of one laid-out run. The engine builds an array of these
// internally; you never construct them yourself, but the fields explain how a
// link's hit box is derived from measured geometry rather than from text.
type
TRichRunInfo = record
Dx, Dy : Double; // top-left, relative to the draw-box origin
W, H : Double; // measured run box (width from the layout pass)
Text : AnsiString; // the run's visible characters
Href : AnsiString; // URI target for an <a> run, '' otherwise
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.
// The same public API the flatten path uses for each anchor run. It produces
// an ISO 32000-1 12.5.6.5 Link annotation: /Subtype /Link with a /URI action
// over the given rectangle. The optional description fills /Contents so a
// screen reader can announce the target.
var
LinkRect: TRect;
Annot: THPDFDictionaryObject;
begin
LinkRect := Rect(72, 690, 268, 706); // page-space hit box for the run
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 keeps fields fillable; False freezes them read-only.
Emitted := Pdf.FlattenLoadedXFA(True);
// Anything the engine could not map is reported, not raised.
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 Component para Delphi e C++Builder.