Artículo técnico

PDF a Markdown y DOCX en Delphi con PDFlibPas

PDFlibPas convierte contenido PDF en dos formatos editables sin automatización de Office. ExportPageMarkdown y ExportDocumentMarkdown devuelven Markdown semántico con encabezados inferidos, listas ordenadas y no ordenadas y tablas con barras, mientras que SaveDOCXToFile y SaveDOCXToStream escriben un paquete WordprocessingML con párrafos, encabezados, numeración de listas nativa, tablas detectadas, estilo de fuente, saltos de página e imágenes PNG posicionadas

Ambos funcionan enteramente en Pascal, en un servidor, sin Word instalado y sin COM. Esa restricción es la razón de que la función exista en una biblioteca PDF y no en una herramienta de escritorio

¿Por qué es genuinamente difícil "PDF a Word"?

Porque una página PDF no contiene párrafos. Contiene operadores de presentación de texto que colocan tramos de glifos en coordenadas, en el orden en que el productor los emitió, sin ninguna obligación de indicar que dos tramos pertenecen a la misma frase, y mucho menos al mismo elemento de lista. El formato se diseñó para describir una página impresa con exactitud, y lo consigue precisamente descartando la estructura que produjo esa página

Así que cada conversor tiene que reconstruir lo que el generador desechó. La agrupación de líneas viene del espaciado vertical y la alineación de línea base. Los límites de párrafo vienen de cambios de espaciado y sangría. Un encabezado es una línea cuya fuente es mayor o más gruesa que la del cuerpo y que se distingue de lo que sigue. Una lista es una serie de párrafos que empiezan con una viñeta o un patrón numérico. Una tabla es una cuadrícula de bloques de texto cuyos bordes se alinean entre filas y columnas. Cada uno de estos casos es una inferencia, y eso significa un buen resultado en documentos que siguen convenciones tipográficas normales y uno mediocre en los que no las siguen

Los PDFs etiquetados son la excepción, y una excepción importante. Cuando el documento lleva un árbol de estructura, los roles de párrafo, encabezado, lista y tabla quedan registrados en lugar de conjeturados, y por eso el trabajo de accesibilidad descrito en la estructura de accesibilidad del PDF etiquetado también se traduce en mejor calidad de conversión. Si controla el productor, etiquetar su salida es lo de mayor impacto que puede hacer por cualquiera que después tenga que convertirla

Exportación a Markdown, página a página

La ruta Markdown es la que conviene usar cuando el destino es una canalización de texto: un sitio de documentación, un índice de búsqueda, un corpus de recuperación para un asistente. Las opciones son una máscara de bits: PDF_MARKDOWN_INCLUDE_PAGE_MARKERS, PDF_MARKDOWN_DETECT_HEADINGS, PDF_MARKDOWN_PRESERVE_STYLES, y PDF_MARKDOWN_DEFAULT combina las tres

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

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

    // Un rango de páginas, en flujo a disco como UTF-8 sin BOM
    Pdf.SaveMarkdownToFile('1-40',
      PDF_MARKDOWN_DETECT_HEADINGS or PDF_MARKDOWN_PRESERVE_STYLES,
      'handbook.md');
  finally
    Pdf.Free;
  end;
end;

Los marcadores de página se ganan su sitio en el trabajo de recuperación. Un fragmento de texto que lleva la página de la que procede se puede citar con precisión, y un lector que sigue la cita llega justo donde está la afirmación. Desactívelos cuando el Markdown se destine a lectura humana, donde los límites de página del diseño original son ruido

Los puntos de entrada en flujo importan para documentos grandes. SaveMarkdownToStream y SaveMarkdownToFile escriben UTF-8 página a página y no acumulan la salida completa en un búfer, así que un manual de 900 páginas no se convierte primero en una cadena de 900 páginas en memoria. La ausencia de marca de orden de bytes también es deliberada: un BOM en un fichero Markdown confunde a un número sorprendente de generadores de sitios estáticos y herramientas de diff

DOCX sin Office en la máquina

El escritor de DOCX produce el paquete él mismo: entradas ZIP escritas como Deflate en bruto con comprobaciones CRC, las partes WordprocessingML y las relaciones que las vinculan. Nada llama a Word, lo que significa que la conversión corre en un servidor sin cabeza, dentro de una cuenta de servicio, en un contenedor, en todos los sitios donde la automatización de Office está sin licencia, es inestable o está prohibida

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;

Los datos de imagen se escriben a medida que se procesa cada página en lugar de acumularse y añadirse al final, así que el pico de memoria sigue el ritmo de una página y no del documento entero. Se conserva el orden explícito de páginas, y la página PDF seleccionada se restaura después, lo cual importa cuando la exportación es un paso más dentro de un trabajo más largo que tenía una página seleccionada por otros motivos

¿Qué le aporta el empaquetado determinista?

Reproducibilidad byte a byte. Dos conversiones de la misma entrada con las mismas opciones producen el mismo paquete, lo que significa que puede aplicar un hash a la salida para detectar cambios, comparar dos compilaciones de un documento generado y almacenar en caché de forma agresiva sin preocuparse de que una entrada idéntica produjera un artefacto distinto

La automatización de Office no puede prometer eso. Incrusta marcas de tiempo, identificadores de revisión y metadatos dependientes de la máquina, así que el mismo documento convertido dos veces difiere de formas que arruinan el hash. El mismo razonamiento impulsa los identificadores de fichero PDF deterministas tratados en los identificadores de PDF deterministas para compilaciones reproducibles: cuando la salida es reproducible, la verificación se convierte en una comparación en lugar de una inspección

Dónde es buena la salida, y dónde no lo es

Sea honesto con sus usuarios en esto, porque la calidad de conversión varía más según la entrada que según el conversor. Los PDFs etiquetados y los documentos empresariales generados de forma limpia, facturas, informes, contratos, convierten bien: los encabezados llegan como encabezados, las tablas sobreviven, las listas se renumeran correctamente en Word. Las maquetas académicas a dos columnas convierten de forma aceptable si la geometría de columnas es regular. Las tablas que cruzan saltos de página se reconstruyen por inferencia y a veces se dividen. El material de marketing muy diseñado, donde el texto se coloca por efecto visual y no en orden de lectura, convierte mal, y ninguna cantidad de inferencia lo arregla

Los documentos escaneados son un caso completamente aparte. Una página que es una única imagen no contiene objetos de texto, así que no hay nada que exportar hasta que exista una capa de texto; la ruta de OCR que la produce es un requisito previo, no una opción. Antes de lanzar un lote grande, tome una muestra de una docena de ficheros representativos y examine la salida, y considere enumerar primero los elementos de página, como se describe en la búsqueda de texto y enumeración de elementos de página, para ver qué contienen realmente las páginas

Para canalizaciones de asistentes y recuperación, la ruta Markdown suele ser el mejor destino: los encabezados se convierten en límites de fragmento, las tablas siguen siendo legibles como tablas con barras, y los marcadores de página dan a cada fragmento una ubicación citable. Para edición humana, DOCX es la respuesta, porque lo que el usuario quiere no es el texto, sino la capacidad de cambiarlo

PDFlibPas es una biblioteca PDF para Delphi, C++Builder y Lazarus con interfaces DLL y ActiveX equivalentes, así que las mismas llamadas de exportación están disponibles desde C#, C++ u hosts de scripting. La documentación completa y una compilación de prueba están en la página de PDFlibPas para Delphi