Article technique

PDF vers Markdown et DOCX en Delphi avec PDFlibPas

PDFlibPas convertit le contenu PDF en deux formats modifiables sans automatisation Office. ExportPageMarkdown et ExportDocumentMarkdown renvoient du Markdown sémantique avec des titres déduits, des listes ordonnées et non ordonnées et des tableaux en pipes, tandis que SaveDOCXToFile et SaveDOCXToStream écrivent un paquet WordprocessingML contenant des paragraphes, des titres, une numérotation de liste native, des tableaux détectés, un style de police, des sauts de page et des images PNG positionnées

Les deux fonctionnent entièrement en Pascal, sur un serveur, sans Word installé et sans COM. Cette contrainte est la raison pour laquelle la fonctionnalité existe dans une bibliothèque PDF plutôt que dans un outil de bureau

Pourquoi « PDF vers Word » est-il réellement difficile ?

Parce qu'une page PDF ne contient pas de paragraphes. Elle contient des opérateurs d'affichage de texte qui placent des suites de glyphes à des coordonnées, dans l'ordre où le producteur les a émis, sans aucune obligation d'indiquer que deux suites appartiennent à la même phrase, encore moins au même élément de liste. Le format a été conçu pour décrire exactement une page imprimée, et il y parvient en abandonnant la structure qui a produit la page

Chaque convertisseur doit donc reconstruire ce que le générateur a jeté. Le regroupement en lignes provient de l'espacement vertical et de l'alignement de ligne de base. Les limites de paragraphe proviennent des changements d'espacement et de l'indentation. Un titre est une ligne dont la police est plus grande ou plus grasse que le corps du texte et qui se distingue de ce qui suit. Une liste est une suite de paragraphes commençant par une puce ou un motif numérique. Un tableau est une grille de blocs de texte dont les bords s'alignent sur les lignes et les colonnes. Chacun de ces éléments relève de l'inférence, et l'inférence donne un bon résultat sur les documents qui suivent les conventions typographiques ordinaires et un résultat médiocre sur ceux qui ne les suivent pas

Les PDF balisés sont l'exception, et une exception de taille. Quand le document porte un arbre de structure, les rôles de paragraphe, de titre, de liste et de tableau sont enregistrés plutôt que devinés, ce qui explique pourquoi le travail d'accessibilité décrit dans la structure d'accessibilité des PDF balisés profite aussi à la qualité de conversion. Si vous contrôlez le producteur, baliser votre sortie est le geste le plus rentable que vous puissiez faire pour quiconque devra la convertir plus tard

Export Markdown, une page à la fois

Le chemin Markdown est celui vers lequel se tourner quand la destination est un pipeline de texte : un site de documentation, un index de recherche, un corpus de récupération pour un assistant. Les options sont un masque de bits : PDF_MARKDOWN_INCLUDE_PAGE_MARKERS, PDF_MARKDOWN_DETECT_HEADINGS, PDF_MARKDOWN_PRESERVE_STYLES, PDF_MARKDOWN_DEFAULT combinant les trois

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

    // Une page, sous forme de chaîne
    Md := Pdf.ExportPageMarkdown(1, PDF_MARKDOWN_DEFAULT);

    // Une plage de pages, écrite en flux sur disque en UTF-8 sans BOM
    Pdf.SaveMarkdownToFile('1-40',
      PDF_MARKDOWN_DETECT_HEADINGS or PDF_MARKDOWN_PRESERVE_STYLES,
      'handbook.md');
  finally
    Pdf.Free;
  end;
end;

Les marqueurs de page justifient pleinement leur présence dans le travail de récupération. Un fragment de texte qui porte la page dont il provient peut être cité avec précision, et un lecteur qui suit la citation atterrit exactement là où se trouve l'affirmation. Désactivez-les quand le Markdown est destiné à une lecture humaine, où les limites de page issues de la mise en page source ne sont que du bruit

Les points d'entrée en flux comptent pour les documents volumineux. SaveMarkdownToStream et SaveMarkdownToFile écrivent l'UTF-8 une page à la fois et ne mettent pas en mémoire tampon la sortie complète, si bien qu'un manuel de 900 pages ne devient pas d'abord une chaîne de 900 pages en mémoire. L'absence de marque d'ordre des octets est également délibérée : un BOM sur un fichier Markdown perturbe un nombre surprenant de générateurs de sites statiques et d'outils de diff

DOCX sans Office sur la machine

L'écrivain DOCX produit lui-même le paquet : des entrées ZIP écrites en Deflate brut avec vérifications CRC, les parties WordprocessingML, et les relations qui les lient. Rien n'appelle Word, ce qui signifie que la conversion s'exécute sur un serveur sans interface, dans un compte de service, dans un conteneur, dans tous les endroits où l'automatisation Office est soit non licenciée, soit instable, soit interdite

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;

Les données d'image sont écrites au fur et à mesure du traitement de chaque page plutôt que collectées et ajoutées à la fin, si bien que le pic de mémoire suit une seule page plutôt que le document entier. L'ordre explicite des pages est préservé, et la page PDF sélectionnée est restaurée ensuite, ce qui compte quand l'export n'est qu'une étape à l'intérieur d'une tâche plus longue qui avait sélectionné une page pour d'autres raisons

Qu'apporte un packaging déterministe ?

Une reproductibilité octet pour octet. Deux conversions de la même entrée avec les mêmes options produisent le même paquet, ce qui signifie que vous pouvez hacher la sortie pour détecter un changement, comparer deux builds d'un document généré, et mettre en cache de façon agressive sans craindre qu'une entrée identique ait produit un artefact différent

L'automatisation Office ne peut pas le promettre. Elle incorpore des horodatages, des identifiants de révision et des métadonnées dépendantes de la machine, si bien que le même document converti deux fois diffère de façons qui mettent en échec le hachage. Le même raisonnement motive les identifiants de fichier déterministes évoqués dans les identifiants PDF déterministes pour des builds reproductibles : quand la sortie est reproductible, la vérification devient une comparaison plutôt qu'une inspection

Où la sortie est bonne, et où elle ne l'est pas

Soyez honnête avec vos utilisateurs à ce sujet, car la qualité de conversion varie davantage avec l'entrée qu'avec le convertisseur. Les PDF balisés et les documents professionnels proprement générés, factures, rapports, contrats, se convertissent bien : les titres deviennent des titres, les tableaux survivent, les listes se renumérotent correctement dans Word. Les mises en page universitaires à deux colonnes se convertissent de façon acceptable si la géométrie des colonnes est régulière. Les tableaux qui traversent des sauts de page sont réassemblés par inférence et parfois scindés. Le matériel marketing très travaillé, où le texte est placé pour un effet visuel plutôt que dans l'ordre de lecture, se convertit mal, et aucune quantité d'inférence n'y remédie

Les documents numérisés constituent un cas entièrement à part. Une page qui n'est qu'une grande image ne contient aucun objet de texte, il n'y a donc rien à exporter tant qu'une couche de texte n'existe pas ; le chemin OCR qui en produit une est un prérequis, pas une option. Avant de lancer un lot volumineux, échantillonnez une douzaine de fichiers représentatifs et examinez la sortie, et envisagez d'énumérer d'abord les éléments de page, comme décrit dans la recherche de texte et l'énumération des éléments de page, pour voir ce que les pages contiennent réellement

Pour les pipelines d'assistants et de récupération, le chemin Markdown est généralement la meilleure cible : les titres deviennent des limites de fragments, les tableaux restent lisibles sous forme de tableaux en pipes, et les marqueurs de page donnent à chaque fragment un emplacement citable. Pour l'édition humaine, DOCX est la réponse, car ce que l'utilisateur veut, ce n'est pas le texte, mais la capacité de le modifier

PDFlibPas est une bibliothèque PDF pour Delphi, C++Builder et Lazarus, avec des interfaces DLL et ActiveX correspondantes, si bien que les mêmes appels d'export sont accessibles depuis C#, C++ ou des hôtes de script. La documentation complète et une version d'essai se trouvent sur la page PDFlibPas Delphi PDF library