O PDFlibPas consegue marcar um documento enquanto ele é desenhado. Ative SetAutoTagMode e chamadas comuns de DrawText viram parágrafos, o texto desenhado logo após RegisterHeading vira um título daquele nível, cabeçalhos e rodapés correntes viram artefatos que um leitor ignora, imagens viram figuras, e DrawTableRows leva a tabela, suas linhas e suas células para a árvore de estrutura
A alternativa — e até recentemente a única opção — era envolver cada chamada de desenho em BeginTag e EndTag à mão. Isso funciona, e para documentos com estrutura incomum ainda é a ferramenta certa. Para o relatório, fatura ou demonstrativo comuns, significa que a acessibilidade da saída depende de ninguém nunca esquecer um par, em todos os caminhos de código que desenham alguma coisa
O que os bits de modo cobrem
SetAutoTagMode recebe uma máscara de bits e devolve o modo que estava em vigor antes. AUTOTAG_TEXT (1) marca texto como parágrafo, ou como título quando um estiver pendente. AUTOTAG_FURNITURE (2) marca cabeçalhos, rodapés e números de página correntes como artefatos. AUTOTAG_FIGURE (4) transforma uma imagem desenhada em figura, ou em artefato quando ela foi declarada decorativa. AUTOTAG_TABLE (8) leva tabelas desenhadas para a árvore de estrutura. AUTOTAG_DEFAULT é 15, ou seja, todos os quatro
Ativar o modo também marca o documento como tagged, e esse passo é menos cosmético do que parece. Um leitor considera um documento como não tagged a menos que o catálogo diga o contrário (ISO 32000-1 §14.7.1), então um arquivo que carrega uma árvore de estrutura completa sem a declaração /MarkInfo é anunciado pela tecnologia assistiva como não tendo estrutura alguma. A árvore está lá; nada a lê
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.SetAutoTagMode(AUTOTAG_DEFAULT); // text + furniture + figures + tables
Lib.AddStandardFont(4);
Lib.SetTextSize(18);
Lib.RegisterHeading(1, 'Annual service report');
Lib.DrawText(72, 96, 'Annual service report'); // becomes H1
Lib.SetTextSize(11);
Lib.DrawText(72, 130, 'Every unit installed before 2024 was inspected.');
Lib.SaveToFile('report.pdf');
finally
Lib.Free;
end;
end;
Como um título sabe a qual texto pertence?
RegisterHeading nomeia o nível para o próximo texto desenhado, e ele aguarda por texto. Se uma imagem for desenhada no meio, a imagem vira figura e o título segue pendente para o texto seguinte. Esse comportamento é deliberado: a alternativa, em que a imagem assume o nível do título, produzia documentos nos quais uma linha decorativa abaixo de um título era anunciada como o próprio título
A mesma regra de consumido por um item rege as figuras. RegisterFigure fornece a descrição que a próxima imagem carrega, e RegisterDecoration declara a próxima imagem como uma linha, borda ou fundo sem significado. Ambos são consumidos por uma única imagem, de modo que uma imagem posterior nunca herda uma descrição destinada a uma anterior — exatamente como um texto alternativo acaba preso à figura errada em código marcado à mão
A descrição importa mais do que qualquer outra string isolada em um documento acessível. Um leitor não vidente recebe a descrição no lugar da imagem, e isso é tudo o que ele recebe. "Gráfico" não é uma descrição; "Receita trimestral por região, com a região leste mais alta em Q3" é
Lib.RegisterFigure('Exploded view of the gearbox assembly');
Lib.AddImageFromFile('gearbox.png', 0); // becomes a tagged Figure
Lib.RegisterDecoration; // meaningless rule
Lib.AddImageFromFile('divider.png', 0); // drawn inside a layout artifact
Tabelas, cabeçalhos e onde fica a decisão de repetição
Com o bit de tabela ligado, DrawTableRows leva a tabela, suas linhas e suas células para a árvore de estrutura, de modo que um leitor consegue dizer em qual coluna um valor se encontra em vez de ler a tabela inteira como uma sequência de texto sem relação. SetTableHeaderRowCount diz quantas linhas iniciais são cabeçalhos; essas linhas são escritas como células de cabeçalho com escopo de coluna, o que permite a um leitor anunciar o título do valor em que o usuário está
As linhas de cabeçalho nomeadas dessa forma ficam onde estão. Repeti-las no topo de cada página é uma decisão de layout, e segue sendo uma: DrawTaggedTableRows recebe um argumento RepeatHeaderRows exatamente para isso. Manter as duas separadas evita que a árvore de estrutura ganhe uma segunda cópia do cabeçalho a cada quebra de página, que é o que uma repetição automática produziria
var
TableID: Integer;
begin
TableID := Lib.CreateTable(40, 3);
Lib.SetTableHeaderRowCount(TableID, 1); // row 1 is the header band
Lib.SetTableCellContent(TableID, 1, 1, 'Part');
Lib.SetTableCellContent(TableID, 1, 2, 'Torque');
Lib.SetTableCellContent(TableID, 1, 3, 'Unit');
// ... fill the data rows ...
// Draw rows 1..40 into a 600pt band, repeating one header row per page
Lib.DrawTaggedTableRows(TableID, 72, 150, 600, 1, 40, 1);
end;
Misturando marcação automática e manual
A marcação automática se afasta dentro de uma tag aberta à mão. Parte de um documento pode ser descrita pelo seu código e o restante deixado para a biblioteca, sem que os dois se aninhem — que é o arranjo que a maioria dos documentos reais quer. A capa e o bloco de assinatura têm estrutura que só você entende; as duzentas páginas de texto do corpo no meio não têm
Duas regras de segurança mantêm a saída limpa. Nada é marcado dentro de um artefato, porque conteúdo marcado como artefato não pode carregar nenhum elemento de estrutura. E texto vazio não abre elemento, de modo que uma chamada perdida de DrawText com uma string vazia não consegue produzir um elemento de estrutura que um leitor anunciaria como em branco. Ambos são o tipo de defeito que documentos marcados à mão acumulam silenciosamente e que um validador relata em massa meses depois
O que a marcação automática ainda não decide por você
Ordem de leitura além da ordem de desenho, papéis semânticos que não sejam parágrafo, título, figura ou tabela, e declarações de idioma. A marcação automática atribui estrutura na ordem em que o conteúdo é desenhado — se o seu código de layout desenha a barra lateral antes do corpo, essa é a ordem que a árvore registra. Para documentos em que a ordem visual e a ordem de leitura diferem de fato, a API de marcação manual continua sendo a ferramenta certa, e o guia sobre estrutura e acessibilidade em tagged PDF cobre papéis, escopos e vínculos de cabeçalho em detalhe
Quando o documento estiver pronto, valide em vez de supor: as notas sobre preflight de PDF/A e PDF/UA mostram como obter um veredito sobre a estrutura que você produziu, e o guia de exportação de relatórios orientada por dados cobre onde essas chamadas se encaixam em um motor de relatórios que gera seu layout a partir dos dados
O PDFlibPas é uma biblioteca PDF nativa em Pascal para Delphi, C++Builder e Lazarus sem nenhum runtime externo de PDF, de modo que a saída acessível é produzida pelo mesmo código que desenha o documento — veja a página do produto PDFlibPas para a lista completa de APIs e plataformas