A extração de texto em PDF parece simples até se deparar com documentos sem camada de texto, com dados corrompidos ou fragmentados em sequências de caracteres sem ordem lógica. O PDFium Component disponibiliza duas abordagens: o array Character[] para acesso de baixo nível indexado a cada elemento gráfico (glyph) da página, e o método ReadablePageContent para leitura estruturada que reconstrói parágrafos e cabeçalhos a partir da árvore de etiquetas (tags) ou análise heurística. Conhecer a finalidade de cada método é essencial para escolher a lógica adequada
Abertura do documento e tratamento de falhas silenciosas
O componente TPdf abre o ficheiro definindo FileName e aplicando Active := True. Detalhe relevante: a atribuição Active := True não gera exceções. Se o ficheiro estiver em falta, encriptado com palavra-passe ou corrompido, o PDFium gere a falha internamente e a propriedade Active permanece False. Desta forma, as rotinas de extração devem validar este estado:
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Active := True;
if not Pdf.Active then
begin
ShowMessage('Não foi possível abrir o PDF (ficheiro corrompido ou palavra-passe incorreta)');
Exit;
end;
// a extração de texto segue aqui
finally
Pdf.Active := False;
Pdf.Free;
end;
Ficheiros protegidos com palavra-passe requerem a definição prévia de Pdf.Password := '...' antes de aplicar Active := True. Caso a validação de Active falhe, deverá fechar e reabrir o documento com a palavra-passe correta
Extração página a página com Character[]
O método de baixo nível analisa individualmente os caracteres de cada página. Defina a propriedade Pdf.PageNumber para carregar os dados textuais da página e percorra os elementos até ao limite de CharacterCount através de Character[]. Convém validar dois sinalizadores em cada iteração: CharacterGenerated[i] identifica caracteres sintéticos inseridos pelo renderizador (ex: hífenes em quebras de linha) sem correspondência Unicode real, e CharacterMapError[i] indica que o PDFium não conseguiu mapear o elemento gráfico para um Unicode (frequente em codificações de fonte sem tabela ToUnicode)
procedure ExtractAllText(Pdf: TPdf; Output: TStrings);
var
Page, I: Integer;
Line: string;
Ch: WideChar;
begin
for Page := 1 to Pdf.PageCount do
begin
Pdf.PageNumber := Page;
Line := '';
for I := 0 to Pdf.CharacterCount - 1 do
begin
if Pdf.CharacterGenerated[I] or Pdf.CharacterMapError[I] then
Continue;
Ch := Pdf.Character[I];
if Ch = #13 then
Ch := #10; // normalizar CR para LF
Line := Line + Ch;
end;
Output.Add(Line);
end;
end;
O resultado consiste numa string simples de caracteres Unicode ordenados conforme a leitura lógica do PDFium (a ordem em que surgem no stream de conteúdo), não correspondendo obrigatoriamente à ordem visual de leitura da esquerda para a direita. Para a maioria dos documentos ocidentais gerados por ferramentas comuns, esta ordem é adequada. Em PDFs digitalizados processados por OCR com sequências não padrão ou textos com escrita da direita para a esquerda, a ordenação pode apresentar falhas. Nesses cenários, recomenda-se adotar o método ReadablePageContent
Extração estruturada com ReadablePageContent
O método ReadablePageContent opera a um nível superior: retorna um registo TPdfReadableContent cujo array Fragments contém blocos de conteúdo etiquetados, com a propriedade Kind a identificar parágrafos, cabeçalhos, tabelas, itens de listas, etc. Se o documento possuir uma estrutura lógica de tags (verificável via Pdf.IsTagged), a leitura adota a origem rosStructure (cuja ordem de leitura é fidedigna). Em documentos sem tags, o PDFium recorre a rosHeuristic, agrupando os caracteres com base em caixas delimitadoras lógicas sem garantia absoluta de exatidão
procedure ExtractStructured(Pdf: TPdf; Output: TStrings);
var
Page: Integer;
Content: TPdfReadableContent;
Fragment: TPdfContentFragment;
begin
for Page := 1 to Pdf.PageCount do
begin
Content := Pdf.ReadablePageContent(Page);
for Fragment in Content.Fragments do
begin
case Fragment.Kind of
cfHeading : Output.Add('# ' + Fragment.Text);
cfParagraph : Output.Add(Fragment.Text);
cfListItem : Output.Add('- ' + Fragment.Text);
else
Output.Add(Fragment.Text);
end;
end;
end;
end;
Se a origem for rosHeuristic e o texto resultante surgir misturado, a camada de texto original não foi gerada respeitando a ordem de leitura. Nestes casos, a solução requer a reexportação do documento original com marcações de tags corretas ou um tratamento pós-extração que ordene as coordenadas dos caracteres por Y e depois por X
Coordenadas e áreas delimitadoras de caracteres
Ambas as propriedades indicam a localização do carácter na página (em pontos, com origem no canto inferior esquerdo e eixo Y crescente para o topo). CharacterOrigin[i] indica a coordenada de ancoragem na linha de base e CharacterRectangle[i] define a caixa delimitadora (bounding box). Estes dados suportam análises complexas: identificação de colunas, agrupamento de linhas por comparação de Y dentro de uma tolerância ou desenho de seleções de texto na interface. Para localizar o carácter sob as coordenadas do rato, o método CharacterIndexAtPos(X, Y, ToleranceX, ToleranceY) efetua a pesquisa sem necessidade de percorrer as caixas manualmente
Distribuição e carregamento das DLLs nativas
O PDFium Component delega o processamento em DLLs nativas (pdfium32.dll ou pdfium64.dll em função da plataforma). O componente inclui a rotina CopyDlls.bat que copia os ficheiros para a diretoria de sistema do Windows. Executá-lo como Administrador no ambiente de desenvolvimento é suficiente, devendo em produção copiar a DLL para a pasta do executável da aplicação. As variantes com motor V8 (pdfium32v8.dll, pdfium64v8.dll) são maiores e necessárias apenas para execução de JavaScript em formulários, sendo a DLL padrão a recomendada para extração simples de texto
Se a DLL nativa não estiver disponível, a chamada Active := True falhará silenciosamente, tal como em ficheiros inexistentes. Valide a distribuição das bibliotecas antes do deploy da aplicação
Utilização de FontSize[] e Character[] em análises de layout
Adicionalmente, a API disponibiliza a propriedade FontSize[i] que indica o tamanho do carácter em pontos. Em conjunto com CharacterOrigin[i] e CharacterRectangle[i], permite distinguir o corpo do texto de títulos em documentos sem tags. Variações bruscas no tamanho dos caracteres identificam títulos, rodapés ou legendas. Estas informações são lidas diretamente da camada de texto sem processamentos gráficos ou renderização
Nota técnica: o valor de FontSize[i] reflete a dimensão após a aplicação da matriz de transformação (CTM) da página. Se o documento contiver páginas redimensionadas globalmente, os valores de tamanho serão ajustados proporcionalmente. Em comparações entre páginas com dimensões distintas, normalize os valores contra a altura da MediaBox da página
Gravar a informação extraída em ficheiro
A classe TStringList do Delphi suporta exportações UTF-8 de forma nativa. Defina WriteBOM := False para evitar a escrita de marcas BOM (aspeto que inviabiliza leituras noutras aplicações):
var
Lines: TStringList;
begin
Lines := TStringList.Create;
try
ExtractAllText(Pdf, Lines);
Lines.WriteBOM := False;
Lines.SaveToFile('output.txt', TEncoding.UTF8);
finally
Lines.Free;
end;
end;
Para documentos grandes onde o uso de memória é relevante, grave o fluxo diretamente via TStreamWriter com codificação TEncoding.UTF8 no ciclo de leitura da página em vez de acumular os dados em memória
As rotinas de gestão e análise de texto Character[], CharacterCount, CharacterOrigin[], CharacterRectangle[], ReadablePageContent e CharacterIndexAtPos integram o PDFium Component para Delphi e C++Builder