Artigo Técnico

PDF para Markdown e DOCX no Delphi com PDFlibPas

O PDFlibPas converte conteúdo de PDF em dois formatos editáveis sem automação do Office. ExportPageMarkdown e ExportDocumentMarkdown retornam Markdown semântico com títulos inferidos, listas ordenadas e não ordenadas e tabelas em formato pipe, enquanto SaveDOCXToFile e SaveDOCXToStream gravam um pacote WordprocessingML contendo parágrafos, títulos, numeração de lista nativa, tabelas detectadas, estilo de fonte, quebras de página e imagens PNG posicionadas

Ambos rodam inteiramente em Pascal, em um servidor, sem o Word instalado e sem COM. Essa restrição é o motivo pelo qual o recurso existe em uma biblioteca de PDF, e não em uma ferramenta desktop

Por que "PDF para Word" é genuinamente difícil?

Porque uma página de PDF não contém parágrafos. Ela contém operadores de exibição de texto que posicionam runs de glifos em coordenadas, na ordem em que o produtor os emitiu, sem nenhuma obrigação de indicar que duas runs pertencem à mesma frase, muito menos ao mesmo item de lista. O formato foi projetado para descrever uma página impressa com exatidão, e ele consegue isso descartando a estrutura que produziu a página

Então todo conversor precisa reconstruir o que o gerador descartou. O agrupamento de linhas vem do espaçamento vertical e do alinhamento de baseline. Os limites de parágrafo vêm de mudanças de espaçamento e de recuo. Um título é uma linha cuja fonte é maior ou mais pesada do que o corpo do texto e que se destaca do que vem depois. Uma lista é uma sequência de parágrafos começando com um caractere de marcador ou um padrão numérico. Uma tabela é uma grade de blocos de texto cujas bordas se alinham ao longo de linhas e colunas. Cada uma dessas é uma inferência, e inferência significa um bom resultado em documentos que seguem convenções tipográficas comuns e um resultado medíocre em documentos que não seguem

PDFs tagueados são a exceção, e uma exceção grande. Quando o documento carrega uma árvore de estrutura, os papéis de parágrafo, título, lista e tabela são registrados em vez de adivinhados, e é por isso que o trabalho de acessibilidade descrito em estrutura de acessibilidade de PDF tagueado também compensa na qualidade da conversão. Se você controla o produtor, tagear sua saída é a coisa de maior alavancagem que você pode fazer por qualquer um que mais tarde precise convertê-la

Exportação em Markdown, uma página por vez

O caminho de Markdown é o indicado quando o destino é um pipeline de texto: um site de documentação, um índice de busca, um corpus de retrieval para um assistente. As opções são uma bit mask: PDF_MARKDOWN_INCLUDE_PAGE_MARKERS, PDF_MARKDOWN_DETECT_HEADINGS, PDF_MARKDOWN_PRESERVE_STYLES, com PDF_MARKDOWN_DEFAULT combinando as três

var
  Pdf: TPDFlib;
  Md: WideString;
begin
  Pdf := TPDFlib.Create;
  try
    Pdf.LoadFromFile('handbook.pdf', '');

    // Uma página, como string
    Md := Pdf.ExportPageMarkdown(1, PDF_MARKDOWN_DEFAULT);

    // Um intervalo de páginas, gravado em disco em streaming como UTF-8 sem BOM
    Pdf.SaveMarkdownToFile('1-40',
      PDF_MARKDOWN_DETECT_HEADINGS or PDF_MARKDOWN_PRESERVE_STYLES,
      'handbook.md');
  finally
    Pdf.Free;
  end;
end;

Os marcadores de página se justificam em trabalho de retrieval. Um trecho de texto que carrega a página de onde veio pode ser citado com precisão, e um leitor que segue a citação chega exatamente onde a afirmação está. Desative-os quando o Markdown for destinado à leitura humana, onde os limites de página do layout de origem são apenas ruído

Os pontos de entrada de streaming importam para documentos grandes. SaveMarkdownToStream e SaveMarkdownToFile gravam UTF-8 uma página por vez e não armazenam em buffer a saída completa, então um manual de 900 páginas não vira antes uma string de 900 páginas na memória. A ausência de uma byte-order mark também é deliberada: um BOM em um arquivo Markdown confunde um número surpreendente de geradores de sites estáticos e ferramentas de diff

DOCX sem Office na máquina

O gravador de DOCX produz o pacote por conta própria: entradas ZIP gravadas como Deflate bruto com verificações CRC, as partes WordprocessingML, e os relacionamentos que as ligam. Nada chama o Word, o que significa que a conversão roda em um servidor headless, dentro de uma conta de serviço, em um container, em todos os lugares onde a automação do Office é sem licença, instável ou proibida

var
  Pdf: TPDFlib;
  Target: TFileStream;
begin
  Pdf := TPDFlib.Create;
  Target := TFileStream.Create('handbook.docx', fmCreate);
  try
    Pdf.LoadFromFile('handbook.pdf', '');
    Pdf.SaveDOCXToStream('1-40',
      PDF_DOCX_INCLUDE_IMAGES or PDF_DOCX_DETECT_HEADINGS or
      PDF_DOCX_PRESERVE_STYLES or PDF_DOCX_PRESERVE_PAGE_BREAKS,
      Target);
  finally
    Target.Free;
    Pdf.Free;
  end;
end;

Os dados de imagem são gravados à medida que cada página é processada, em vez de coletados e anexados no final, então o pico de memória acompanha uma página, e não o documento inteiro. A ordem explícita das páginas é preservada, e a página de PDF selecionada é restaurada depois, o que importa quando a exportação é uma etapa dentro de um job mais longo que tinha uma página selecionada por outros motivos

O que o empacotamento determinístico traz de benefício?

Reprodutibilidade byte a byte. Duas conversões da mesma entrada com as mesmas opções produzem o mesmo pacote, o que significa que você pode calcular o hash da saída para detectar mudanças, comparar dois builds de um documento gerado, e usar cache de forma agressiva sem se preocupar que uma entrada idêntica tenha produzido um artefato diferente

A automação do Office não pode prometer isso. Ela incorpora timestamps, identificadores de revisão e metadados dependentes da máquina, então o mesmo documento convertido duas vezes difere de formas que inviabilizam o hashing. O mesmo raciocínio motiva os identificadores de arquivo determinísticos discutidos em IDs de PDF determinísticos para builds reproduzíveis: quando a saída é reproduzível, a verificação vira uma comparação em vez de uma inspeção

Onde a saída é boa, e onde não é

Seja honesto com seus usuários sobre isso, porque a qualidade da conversão varia mais com a entrada do que com o conversor. PDFs tagueados e documentos comerciais gerados de forma limpa, notas fiscais, relatórios, contratos, convertem bem: títulos viram títulos, tabelas sobrevivem, listas são renumeradas corretamente no Word. Layouts acadêmicos de duas colunas convertem de forma aceitável se a geometria das colunas for regular. Tabelas que atravessam quebras de página são remontadas por inferência e às vezes ficam divididas. Material de marketing fortemente desenhado, onde o texto é posicionado por efeito visual em vez de ordem de leitura, converte mal, e nenhuma quantidade de inferência corrige isso

Documentos digitalizados são um caso totalmente separado. Uma página que é uma única imagem grande não contém nenhum objeto de texto, então não há nada para exportar até que exista uma camada de texto; o caminho de OCR que produz essa camada é um pré-requisito, não uma opção. Antes de rodar um lote grande, amostre uma dúzia de arquivos representativos e observe a saída, e considere enumerar os elementos da página primeiro, como descrito em busca de texto e enumeração de elementos de página, para ver o que as páginas realmente contêm

Para pipelines de assistentes e de retrieval, o caminho de Markdown costuma ser o alvo melhor: títulos viram limites de chunk, tabelas continuam legíveis como tabelas pipe, e os marcadores de página dão a cada chunk uma localização citável. Para edição humana, DOCX é a resposta, porque o que o usuário quer não é o texto, e sim a capacidade de alterá-lo

O PDFlibPas é uma biblioteca de PDF para Delphi, C++Builder e Lazarus com interfaces DLL e ActiveX correspondentes, de modo que as mesmas chamadas de exportação estão disponíveis a partir de C#, C++ ou hosts de script. A documentação completa e uma versão de teste estão na página da biblioteca PDFlibPas Delphi para PDF