PDFium Component détecte les tableaux sur une page PDF et les retourne sous forme de grille de cellules avec fusions de lignes et de colonnes, lignes d'en-tête et une valeur de confiance, via ExtractTables pour une page et ExtractDocumentTables pour un document entier. Chaque tableau se convertit en CSV ou JSON en un seul appel, et les tableaux qui se poursuivent au-delà d'un saut de page peuvent être reliés en une chaîne de continuation
Le PDF n'a pas d'objet tableau. Un tableau dans un PDF est un ensemble de fragments de texte positionnés de façon à ce qu'un humain les lise comme une grille, parfois entourés de lignes tracées et souvent non. Reconstruire la grille signifie reconstituer une intention que le fichier n'a jamais enregistrée, ce qui explique pourquoi chaque outil d'extraction produit des résultats légèrement différents et pourquoi un outil qui indique sa confiance est plus utile qu'un outil qui ne le fait pas
Deux modes de détection pour deux types de tableaux
La détection par lignes tracées utilise les lignes dessinées. Chaque segment de tracé est transformé en coordonnées de page via la matrice de l'objet page, les lignes horizontales et verticales sont intersectées, et les intersections forment des composantes connexes. Chaque composante devient sa propre grille triée de positions X et Y, ce qui empêche deux tableaux distincts sur la même page d'être fusionnés en une grille absurde unique
La détection par espaces gère les tableaux dessinés par alignement plutôt que par des lignes. Les boîtes de mots sont regroupées en lignes visuelles, les écarts à l'intérieur d'une ligne la divisent en colonnes candidates, et un tableau n'est accepté que lorsqu'au moins MinRows lignes répètent au moins MinColumns ancrages alignés à gauche dans AlignmentTolerance. Le facteur d'écart entre lignes vaut 3 par défaut, ce qui couvre l'espacement de ligne de base d'environ 30 points typique d'un texte de 12 points sans laisser une seule ligne contenant plusieurs fragments de texte se faire passer pour un tableau
uses
PDFium;
var
Pdf: TPdf;
Options: TPdfTableExtractionOptions;
Tables: TPdfTables;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'annual-report.pdf';
Pdf.LoadDocument;
Pdf.PageNumber := 12; // basé sur 1
Options := TPdfTableExtractionOptions.Default;
Options.DetectRuledTables := True;
Options.DetectWhitespaceTables := True;
Options.MinConfidence := 0.6; // la valeur par défaut est 0.5
Options.HeaderRowCount := 1;
Tables := Pdf.ExtractTables(Options);
for I := 0 to High(Tables) do
Writeln(Format('table %d: %dx%d cells, confidence %.2f, mode %d',
[I, Tables[I].RowCount, Tables[I].ColumnCount,
Tables[I].Confidence, Ord(Tables[I].DetectionMode)]));
if Length(Tables) > 0 then
SaveText('page12-table0.csv', Tables[0].ToCsv);
finally
Pdf.Free;
end;
end;
Comment les cellules fusionnées sont-elles retrouvées ?
C'est la partie que les extracteurs naïfs traitent mal. Une cellule fusionnée ne peut pas être identifiée à partir de la seule grille globale, car la grille est dérivée de toutes les lignes de la page et une région fusionnée manque simplement de la ligne intérieure qui l'aurait séparée
La règle utilisée ici est locale : deux cellules de base adjacentes sont fusionnées quand aucune ligne de frontière ne couvre l'intervalle entre elles. Une union-find les relie, les composantes rectangulaires résultantes deviennent des valeurs RowSpan et ColumnSpan, et le texte est assigné à une cellule de base par son point central puis suit cette cellule jusqu'à sa racine de fusion. Procéder ainsi garde aussi le coût linéaire en mots plus cellules, au lieu du balayage quadratique que produit le test de chaque mot contre chaque cellule
L'effet pratique est qu'un tableau financier avec un en-tête « Total » fusionné s'étendant sur trois colonnes ressort avec une cellule de fusion trois, plutôt qu'une cellule peuplée et deux vides mystérieuses
Continuation entre les pages
Les longs tableaux se coupent entre les pages, et traiter le fragment de chaque page comme un tableau indépendant force l'appelant à les recoudre. ExtractDocumentTables peut les relier à la place, mais seulement sous des conditions strictes : le fragment doit être le tableau le plus bas de la page précédente, le suivant doit être le tableau le plus haut de la page suivante, les numéros de page doivent être adjacents, et les frontières de colonnes doivent correspondre
Les quatre conditions réunies sont ce qui empêche l'erreur évidente, qui consisterait à enchaîner tous les tableaux à quatre colonnes d'un document en une méga-table imaginaire simplement parce qu'ils partagent un nombre de colonnes. Quand les conditions sont réunies, les tableaux partagent un identifiant de groupe de continuation et portent des métadonnées de continuation ; quand elles ne le sont pas, vous obtenez des tableaux séparés et pouvez décider par vous-même
L'extraction au niveau du document partage les budgets MaxCells et MaxTables entre les pages plutôt que de les réinitialiser par page, et elle restaure la page active dans un bloc finally, si bien qu'une extraction lancée dans un visualiseur laisse l'utilisateur sur la page où il se trouvait
Exporter sans corrompre les données
Les deux exportateurs sont rigoureux sur l'échappement. Le CSV met toujours les champs entre guillemets et double les guillemets internes, ce qui évite l'échec classique où une cellule contenant une virgule devient silencieusement deux colonnes. Pour les cellules fusionnées, le contenu n'est émis qu'à l'ancre supérieure gauche, si bien qu'un aller-retour CSV ne duplique pas un en-tête étendu sur les colonnes qu'il couvre
Le JSON préserve l'Unicode plutôt que de l'échapper en ASCII, échappe les caractères de contrôle, et inclut les métadonnées dont un consommateur a besoin pour juger de la qualité : mode de détection, confiance, limites, valeurs de fusion, indicateurs d'en-tête et informations de continuation. Si vous alimentez un système en aval avec des tableaux extraits, préférez JSON, car une ligne CSV ne peut pas vous dire que le tableau dont elle provient a obtenu un score de confiance de 0,51 :
// Extraction à l'échelle du document, en ne gardant que les tableaux dignes de confiance
Tables := Pdf.ExtractDocumentTables(Options);
for I := 0 to High(Tables) do
begin
if Tables[I].Confidence < 0.75 then
begin
Log(Format('page %d table needs review (%.2f)',
[Tables[I].PageNumber, Tables[I].Confidence]));
Continue;
end;
if Tables[I].ContinuationGroup > 0 then
AppendToGroup(Tables[I].ContinuationGroup, Tables[I].ToJson)
else
EmitStandalone(Tables[I].ToJson);
end;
Réglage, et savoir quand s'arrêter
Trois réglages comptent plus que les autres. MinConfidence est le seuil de qualité, et 0,5 est délibérément permissif ; augmentez-le pour une ingestion automatisée et abaissez-le pour une interface de révision où un humain confirme chaque résultat. MinColumnGap décide de ce qui constitue une frontière de colonne en mode espaces, et les tableaux resserrés des rapports denses peuvent nécessiter de le réduire par rapport à la valeur par défaut de 12 points. MaxRowGapFactor décide quand une distance verticale met fin à un tableau, ce qui compte pour les tableaux comportant des lignes vides occasionnelles
Soyez honnête sur les limites. Les tableaux à lignes tracées s'extraient de façon fiable. Les tableaux à espaces proprement alignés s'extraient bien. Les tableaux avec du texte pivoté, des tableaux imbriqués, ou des cellules dont le contenu se replie en quelque chose qui ressemble à une autre ligne nécessiteront une révision quels que soient les paramètres. Pour ces cas, le modèle de texte structuré vous donne la matière première pour construire un lecteur spécifique à votre domaine, décrit dans les blocs de texte structuré et l'ordre de lecture
Une association utile : quand un document scanné n'a aucun texte du tout, la détection de tableaux n'a rien avec quoi travailler tant qu'une couche de texte n'existe pas. Ajoutez-en une d'abord, comme décrit dans ajouter une couche de texte consultable aux PDF scannés, puis extrayez. Les boîtes de mots qu'un fournisseur OCR retourne sont exactement l'entrée dont la détection par espaces a besoin
L'extraction de tableaux, le texte structuré et le reflux se lisent tous depuis le même modèle de page en Delphi, C++Builder et Lazarus ; l'API complète est décrite sur la page du composant PDFium pour Delphi