Article technique

Extraction de texte en ordre de structure avec HotPDF

Chaque extracteur de texte géométrique devine. Il lit les glyphes qu'une page dessine, les trie par ligne de base et position horizontale, et espère que la disposition visuelle correspond à l'ordre qu'un humain lirait. Sur un rapport à une colonne, cette devinette est juste. Sur un article de revue à deux colonnes, un formulaire avec une barre latérale, ou un tableau dont les cellules ont été émises colonne par colonne, elle est fausse de manières difficiles à remarquer et coûteuses à découvrir en aval. HotPDF répond à cela avec ExtractLoadedPageStructureText, qui ignore entièrement la géométrie : il parcourt l'arbre de structure du document en ordre de rédaction tel que défini par l'ISO 32000-1 §14.8.4, puis réassemble les glyphes de la page par leur identifiant de contenu marqué. Pour un PDF balisé, ce n'est pas une heuristique, c'est l'ordre que l'application productrice a déclaré

La fonction renvoie False quand la page n'a aucun arbre de structure utilisable, ce qui est le signal de replier vers l'extracteur géométrique plutôt que d'échouer. Cette conception à deux voies compte plus que l'algorithme : une vraie entrée de documents voit des formulaires gouvernementaux balisés et des sorties de scanner dans le même dossier, et un pipeline qui n'en gère qu'un seul n'est pas un pipeline

Pourquoi l'extraction géométrique se trompe-t-elle d'ordre de lecture ?

Parce qu'un flux de contenu PDF ne porte aucun ordre de lecture du tout. C'est une séquence d'opérateurs de dessin, et un producteur est libre de les émettre dans la séquence qui convient à son propre moteur de mise en page. Les traitements de texte émettent généralement en ordre de flux et le tri géométrique semble bien. Les outils de mise en page, les concepteurs de formulaires et les générateurs de rapports souvent non : un pied de page peut être émis avant le corps, un tableau peut être rempli colonne par colonne, et une page à deux colonnes peut entrelacer les lignes des deux colonnes parce que le compositeur les a résolues ensemble

Comparaison d'une page PDF à deux colonnes montrant l'extraction géométrique triée par ligne de base entrelaçant les colonnes face à l'extraction MCID en ordre de structure dans HotPDF
Trier les glyphes par ligne de base entrelace deux colonnes en charabia, tandis que l'arbre de structure rejoue l'ordre que le producteur a déclaré

Le mode de défaillance est silencieux. Un extracteur géométrique ne signale jamais d'erreur, il rend simplement de la prose dont les phrases sont cousues à partir de deux colonnes. Tout ce qui consomme ce texte, un index de recherche, un mappeur de champs de facture électronique, un pipeline de récupération alimentant un modèle de langage, hérite du dégât sans avertissement. HotPDF livre aussi les extracteurs géométriques pour documents chargés, et ils restent le bon outil pour les fichiers non balisés ; l'intérêt de la voie en ordre de structure est de cesser de deviner quand le document porte déjà la réponse

Ce que l'arbre de structure stocke réellement

Un PDF balisé porte une seconde description parallèle de la page. Le catalogue pointe vers un /StructTreeRoot, dont les enfants /K forment un arbre d'éléments de structure : /Document, /Sect, /P, /Table, /TR, /TD, etc. Les feuilles de cet arbre sont des références de contenu marqué, des entiers qui nomment une étendue du flux de contenu de la page. Du côté du contenu, ces étendues sont ouvertes par un opérateur BDC portant un /MCID et fermées par EMC. Chaque élément de structure porte aussi une entrée /Pg nommant la page à laquelle il appartient, ce qui rend possible le parcours par page dans un document dont l'arbre de structure couvre des centaines de pages

Anatomie de l'arbre de structure PDF reliant des éléments StructTreeRoot comme Sect, Table, TR et TD aux étendues BDC MCID dans le flux de contenu de page HotPDF
Les feuilles de l'arbre sont des références de contenu marqué, et chaque élément porte une entrée Pg qui laisse le parcours filtrer sur la page courante

HotPDF parcourt cet arbre avec un plafond de profondeur de 128 niveaux et filtre sur /Pg pour que seule la page courante contribue. La sortie du parcours n'est pas du texte, c'est une liste ordonnée de valeurs MCID : l'ordre de rédaction des étendues de contenu marqué de cette page. Réassembler le texte est ensuite une affaire de rejouer les glyphes dans cet ordre

Le MCID est enregistré pendant l'extraction des glyphes, pas recherché ensuite

C'est le détail d'implémentation qui rend la fonctionnalité bon marché. HotPDF enregistre déjà l'identifiant de contenu marqué actif sur chaque glyphe qu'il extrait, dans le champ MCID de THPDFGlyphRecord, car l'interpréteur de flux de contenu sait quelle portée BDC est ouverte au moment où il traite chaque opérateur Tj ou TJ. L'extraction en ordre de structure n'a donc besoin d'aucune seconde passe sur le flux de contenu. Elle collecte la séquence MCID depuis l'arbre de structure, puis range les glyphes déjà extraits par MCID et les émet dans cette séquence

var
  Pdf: THotPDF;
  PageCount, I, Untagged: Integer;
  PageText, AllText: UnicodeString;
  Report: TStrings;   // puits de diagnostics possédé par l'appelant
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
        // Ordre de rédaction directement depuis l'arbre de structure
        if Untagged > 0 then
          Report.Add(Format('page %d: %d glyphs outside the structure tree',
            [I, Untagged]));
      end
      else
        // Aucun arbre de structure utilisable sur cette page : repli géométrique
        Pdf.ExtractLoadedPageText(I, PageText);
      AllText := AllText + PageText + #13#10;
    end;
  finally
    Pdf.Free;
  end;
end;

Les glyphes non balisés sont comptés, jamais jetés en silence

Une page peut être partiellement balisée. Les producteurs ajoutent un filet décoratif, un numéro de page ou un filigrane tardif hors de toute portée BDC, et ces glyphes n'appartiennent à aucun MCID. Les jeter serait l'implémentation propre et la mauvaise, car le même écart apparaît aussi quand un producteur balise le corps mais oublie le tableau, et vous perdriez le tableau sans vous en apercevoir

HotPDF ajoute les glyphes non réclamés comme une queue géométrique après le texte en ordre de structure et signale leur nombre par le paramètre de sortie UntaggedGlyphCount. Ce nombre est un signal de qualité sur lequel vous pouvez agir. Une poignée de glyphes sur une page de deux mille est du mobilier de page et peut être ignoré. Quarante pour cent de la page hors de l'arbre de structure signifie que le balisage est décoratif et que l'extracteur géométrique est la réponse la plus honnête pour ce fichier

Flux de décision pour l'extraction de texte de structure HotPDF avec repli géométrique quand une page n'a aucun arbre de structure utilisable ou un balisage décoratif
True signifie ordre de structure avec la queue non balisée ajoutée, et False aiguille la page vers l'extracteur géométrique au lieu d'échouer
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);
    // Faire confiance à l'arbre de structure seulement quand il revendique la majeure partie de la page
    if (TotalGlyphs = 0) or (Untagged * 4 <= TotalGlyphs) then
    begin
      UsedStructure := True;
      Result := True;
      Exit;
    end;
  end;
  Result := Pdf.ExtractLoadedPageText(PageIndex, AText);
end;

Ce qui fait renvoyer False à la fonction

Trois cas, et ils valent la peine d'être distingués car un seul est un défaut du document. Le premier est un PDF non balisé ordinaire : pas de /StructTreeRoot, rien à parcourir, et False est simplement la vérité. Le second est une page numérisée dont le texte vient d'une couche OCR qui n'a jamais été balisée. Le troisième est l'intéressant : du contenu qui porte des opérateurs BDC avec des valeurs /MCID mais dont la page n'a aucune entrée /StructParents et dont l'arbre de structure ne référence jamais ces identifiants. Le contenu marqué existe, le côté structure non, et il n'y a aucun ordre à récupérer. HotPDF rapporte False plutôt que d'en inventer un

Ce dernier cas apparaît dans des fichiers édités à la main et dans la sortie d'outils qui émettent du contenu marqué pour des besoins de contenu optionnel ou d'artefact sans construire d'arbre de structure. Si vous produisez vous-même des PDF balisés, la même asymétrie est ce que la validation PDF/UA vérifie, et le pendant côté écrivain est couvert dans le DOM de mise en page qui émet une sortie balisée et paginée

Là où l'ordre de structure se paie tout seul

L'audit d'accessibilité est l'évident : si vous certifiez un document contre PDF/UA, l'ordre de lecture qu'annoncera un lecteur d'écran est exactement l'ordre de structure, donc l'extraire est la façon de le réviser sans lecteur d'écran. La capture de données est le cas commercial plus grand. Les formulaires gouvernementaux balisés, les divulgations réglementées et les pièces jointes de facture électronique portent des libellés et des valeurs de champs en ordre déclaré, et les lire dans cet ordre élimine toute une classe de bugs de mappage que l'extraction géométrique crée sur les mises en page multi-colonnes

Le consommateur le plus récent est la récupération pour modèles de langage. Découper un document en morceaux pour embedding ne vaut que ce que vaut l'ordre du texte, et un morceau qui coud deux colonnes produit des phrases qui n'ont jamais existé. L'extraction en ordre de structure est la correction la moins chère disponible pour cela, car pour les documents balisés l'ordre correct est déjà dans le fichier et n'a besoin que d'être lu

HotPDF est un composant VCL natif pour Delphi et C++Builder, donc le parcours de l'arbre de structure et le rejeu des glyphes s'exécutent tous deux en processus contre un document chargé sans aucun moteur de rendu externe impliqué. Les détails complets de l'API pour la famille d'extraction de documents chargés sont sur la page produit du HotPDF Delphi PDF component