Todos os extratores geométricos de texto estão a adivinhar. Leem os glifos que uma página desenha, ordenam-nos por linha de base e posição horizontal, e esperam que a disposição visual corresponda à ordem que um humano leria. Num relatório de coluna única esse palpite está certo. Num artigo de revista de duas colunas, num formulário com uma barra lateral, ou numa tabela cujas células foram emitidas coluna a coluna, está errado de formas difíceis de notar e caras de descobrir a jusante. O HotPDF responde a isto com ExtractLoadedPageStructureText, que ignora a geometria por inteiro: percorre a árvore de estrutura do documento pela ordem de autoria conforme definida na ISO 32000-1 §14.8.4, e depois remonta os glifos da página pelo seu identificador de conteúdo marcado. Para um PDF etiquetado isso não é uma heurística, é a ordem que a aplicação produtora declarou
A função devolve False quando a página não tem árvore de estrutura utilizável, que é o sinal para recuar ao extrator geométrico em vez de falhar. Esse design de dois caminhos importa mais do que o algoritmo: a admissão real de documentos vê formulários governamentais etiquetados e saída de digitalizadores na mesma pasta, e uma pipeline que só trata um deles não é uma pipeline
Porque é que a extração geométrica erra a ordem de leitura?
Porque um content stream de PDF não transporta ordem de leitura nenhuma. É uma sequência de operadores de desenho, e um produtor é livre de os emitir na sequência que sirva o seu próprio motor de layout. Os processadores de texto normalmente emitem pela ordem do fluxo e a ordenação geométrica parece boa. As ferramentas de layout, os desenhistas de formulários e os geradores de relatórios frequentemente não: um rodapé de página pode ser emitido antes do corpo, uma tabela pode ser preenchida por colunas, e uma página de duas colunas pode entrelaçar linhas das duas colunas porque o compositor as resolveu juntas
O modo de falha é silencioso. Um extrator geométrico nunca reporta um erro, apenas entrega prosa cujas frases estão entrelaçadas de duas colunas. Qualquer coisa que consuma esse texto, um índice de pesquisa, um mapeador de campos de fatura eletrónica, uma pipeline de recuperação que alimenta um modelo de linguagem, herda o dano sem aviso. O HotPDF também distribui os extratores geométricos para documentos carregados, e continuam a ser a ferramenta certa para ficheiros sem etiquetas; o ponto do caminho pela ordem da estrutura é parar de adivinhar quando o documento já transporta a resposta
O que a árvore de estrutura realmente guarda
Um PDF etiquetado guarda uma segunda descrição paralela da página. O catálogo aponta para um /StructTreeRoot, cujos filhos /K formam uma árvore de elementos de estrutura: /Document, /Sect, /P, /Table, /TR, /TD, e assim por diante. As folhas dessa árvore são referências de conteúdo marcado, inteiros que nomeiam um segmento do content stream da página. No lado do conteúdo, esses segmentos são abertos com um operador BDC que transporta um /MCID e fechados com EMC. Cada elemento de estrutura também transporta uma entrada /Pg que nomeia a página a que pertence, que é o que torna possível a travessia por página num documento cuja árvore de estrutura abrange centenas de páginas
O HotPDF atravessa essa árvore com um limite de profundidade de 128 níveis e filtra por /Pg de modo a que só a página atual contribua. A saída da travessia não é texto, é uma lista ordenada de valores MCID: a ordem de autoria dos segmentos de conteúdo marcado desta página. Remontar o texto é depois uma questão de repor os glifos nessa ordem
O MCID é registado durante a extração de glifos, não procurado depois
Este é o detalhe de implementação que torna a funcionalidade barata. O HotPDF já regista o identificador de conteúdo marcado ativo em cada glifo que extrai, no campo MCID de THPDFGlyphRecord, porque o intérprete do content stream sabe que âmbito BDC está aberto no momento em que processa cada operador Tj ou TJ. A extração pela ordem da estrutura portanto não precisa de uma segunda passagem pelo content stream. Recolhe a sequência MCID da árvore de estrutura, depois agrupa os glifos já extraídos por MCID e emite-os nessa sequência
var
Pdf: THotPDF;
PageCount, I, Untagged: Integer;
PageText, AllText: UnicodeString;
Report: TStrings; // depósito de diagnósticos da responsabilidade do chamador
begin
Pdf := THotPDF.Create(nil);
try
PageCount := Pdf.LoadFromFile('accessible-form.pdf');
AllText := '';
for I := 0 to PageCount - 1 do
begin
if Pdf.ExtractLoadedPageStructureText(I, PageText, Untagged) then
begin
// Ordem de autoria direto da árvore de estrutura
if Untagged > 0 then
Report.Add(Format('page %d: %d glyphs outside the structure tree',
[I, Untagged]));
end
else
// Sem árvore de estrutura utilizável nesta página: recuo geométrico
Pdf.ExtractLoadedPageText(I, PageText);
AllText := AllText + PageText + #13#10;
end;
finally
Pdf.Free;
end;
end;
Glifos sem etiqueta são contados, nunca abandonados em silêncio
Uma página pode estar parcialmente etiquetada. Os produtores acrescentam uma régua decorativa, um número de página, ou uma marca de água tardia fora de qualquer âmbito BDC, e esses glifos não pertencem a MCID nenhum. Abandoná-los seria a implementação arrumada e a errada, porque o mesmo buraco também aparece quando um produtor etiqueta o corpo mas se esquece da tabela, e perderia a tabela sem dar por isso
O HotPDF acrescenta os glifos não reclamados como cauda geométrica depois do texto pela ordem da estrutura e reporta o seu número através do parâmetro de saída UntaggedGlyphCount. Esse número é um sinal de qualidade em que pode agir. Um punhado de glifos numa página de dois mil é mobiliário de página e pode ser ignorado. Quarenta por cento da página fora da árvore de estrutura significa que a etiquetagem é decorativa e o extrator geométrico é a resposta mais honesta para esse ficheiro
function ExtractPageBestEffort(Pdf: THotPDF; PageIndex: Integer;
out AText: UnicodeString; out UsedStructure: Boolean): Boolean;
var
Untagged, TotalGlyphs: Integer;
Glyphs: THPDFGlyphArray;
begin
UsedStructure := False;
if Pdf.ExtractLoadedPageStructureText(PageIndex, AText, Untagged) then
begin
TotalGlyphs := 0;
if Pdf.ExtractLoadedPageGlyphs(PageIndex, Glyphs) then
TotalGlyphs := Length(Glyphs);
// Confiar na árvore de estrutura só quando reclama a maior parte da página
if (TotalGlyphs = 0) or (Untagged * 4 <= TotalGlyphs) then
begin
UsedStructure := True;
Result := True;
Exit;
end;
end;
Result := Pdf.ExtractLoadedPageText(PageIndex, AText);
end;
O que faz a função devolver False
Três casos, e valem a distinção porque só um deles é um defeito no documento. O primeiro é um PDF sem etiquetas comum: sem /StructTreeRoot, nada para percorrer, e False é simplesmente a verdade. O segundo é uma página digitalizada cujo texto vem de uma camada de OCR que nunca foi etiquetada. O terceiro é o interessante: conteúdo que transporta operadores BDC com valores /MCID mas cuja página não tem entrada /StructParents e cuja árvore de estrutura nunca referencia esses identificadores. O conteúdo marcado existe, o lado da estrutura não, e não há ordem para recuperar. O HotPDF reporta False em vez de inventar uma
Esse último caso aparece em ficheiros editados à mão e na saída de ferramentas que emitem conteúdo marcado para fins de conteúdo opcional ou de artefacto sem construir uma árvore de estrutura. Se está a produzir PDFs etiquetados você próprio, a mesma assimetria é o que a validação PDF/UA verifica, e a contraparte do lado do escritor está coberta no DOM de layout que emite saída etiquetada e paginada
Onde a ordem da estrutura se paga a si própria
A auditoria de acessibilidade é a óbvia: se está a certificar um documento contra PDF/UA, a ordem de leitura que um leitor de ecrã anunciará é exatamente a ordem da estrutura, pelo que extraí-la é como a revê sem um leitor de ecrã. A captação de dados é o caso comercial maior. Formulários governamentais etiquetados, divulgações reguladas, e anexos de fatura eletrónica transportam rótulos de campos e valores pela ordem declarada, e lê-los nessa ordem remove uma classe inteira de bugs de mapeamento que a extração geométrica cria em layouts de múltiplas colunas
O consumidor mais recente é a recuperação para modelos de linguagem. Dividir um documento em pedaços para embedding só é tão bom quanto a ordem do texto, e um pedaço que entrelaça duas colunas produz frases que nunca existiram. A extração pela ordem da estrutura é a correção mais barata disponível para isso, porque para documentos etiquetados a ordem correta já está no ficheiro e só precisa de ser lida
O HotPDF é um componente VCL nativo para Delphi e C++Builder, pelo que a travessia da árvore de estrutura e a reposição de glifos correm ambas em processo contra um documento carregado sem renderizador externo envolvido. Os detalhes completos da API para a família de extração de documentos carregados estão na página de produto do HotPDF Delphi PDF component