Articolo tecnico

Testo PDF in ordine di struttura con HotPDF in Delphi

Ogni estrattore di testo geometrico sta indovinando. Legge i glifi che una pagina disegna, li ordina per baseline e posizione orizzontale, e spera che la disposizione visiva corrisponda all'ordine in cui una persona leggerebbe. Su un report a colonna singola quell'ipotesi è giusta. Su un articolo di rivista a due colonne, un modulo con una barra laterale o una tabella le cui celle sono state emesse colonna per colonna, è sbagliato in modi difficili da notare e costosi da scoprire a valle. HotPDF risponde a questo con ExtractLoadedPageStructureText, che ignora completamente la geometria: percorre l'albero della struttura del documento in ordine di scrittura come definito da ISO 32000-1 §14.8.4, poi ricompone i glifi della pagina per il loro marked-content identifier. Per un PDF taggato non è un'euristica, è l'ordine che l'applicazione produttrice ha dichiarato

La funzione restituisce False quando la pagina non ha un albero della struttura utilizzabile, che è il segnale per ripiegare sull'estrattore geometrico anziché fallire. Quel progetto a due vie conta più dell'algoritmo: l'ingesto reale di documenti vede moduli governativi taggati e output di scanner nella stessa cartella, e una pipeline che ne gestisce solo uno non è una pipeline

Perché l'estrazione geometrica sbaglia l'ordine di lettura?

Perché un content stream di PDF non trasporta alcun ordine di lettura. È una sequenza di operatori di disegno, e un produttore è libero di emetterli nella sequenza che conviene al proprio motore di impaginazione. I word processor di solito emettono in ordine di flusso e l'ordinamento geometrico sembra buono. I tool di impaginazione, i progettisti di moduli e i generatori di report spesso no: un piè di pagina può essere emesso prima del corpo, una tabella può essere riempita per colonne, e una pagina a due colonne può intrecciare righe di entrambe le colonne perché il compositore le ha risolte insieme

Confronto di una pagina PDF a due colonne che mostra l'estrazione geometrica ordinata per baseline che intreccia le colonne contro l'estrazione MCID in ordine di struttura di HotPDF
Ordinare i glifi per baseline intreccia due colonne in nonsense, mentre l'albero della struttura riproduce l'ordine che il produttore ha dichiarato

La modalità di guasto è silenziosa. Un estrattore geometrico non riporta mai un errore, restituisce semplicemente prosa le cui frasi sono cucite da due colonne. Chiunque consumi quel testo, un indice di ricerca, un mappatore di campi per fatture elettroniche, una pipeline di recupero che alimenta un modello linguistico, eredita il danno senza un avvertimento. HotPDF include anche gli estrattori geometrici per documenti caricati, e restano lo strumento giusto per i file non taggati; il senso della via in ordine di struttura è smettere di indovinare quando il documento porta già la risposta

Cosa memorizza davvero l'albero della struttura

Un PDF taggato conserva una seconda descrizione parallela della pagina. Il catalogo punta a uno /StructTreeRoot, i cui figli /K formano un albero di elementi di struttura: /Document, /Sect, /P, /Table, /TR, /TD, e così via. Le foglie di quell'albero sono riferimenti a marked-content, interi che denominano un intervallo dello stream di contenuto della pagina. Sul lato contenuto, quegli intervalli sono aperti con un operatore BDC che trasporta un /MCID e chiusi con EMC. Ogni elemento di struttura trasporta anche una voce /Pg che nomina la pagina a cui appartiene, il che rende possibile l'attraversamento per pagina in un documento il cui albero della struttura si estende su centinaia di pagine

Anatomia dell'albero della struttura PDF che collega elementi StructTreeRoot come Sect, Table, TR e TD agli intervalli BDC MCID nello stream di contenuto della pagina di HotPDF
Le foglie dell'albero sono riferimenti a marked-content, e ogni elemento trasporta una voce Pg che lascia filtrare l'attraversamento alla pagina corrente

HotPDF attraversa quell'albero con un tetto di profondità di 128 livelli e filtra su /Pg così solo la pagina corrente contribuisce. L'output dell'attraversamento non è testo, è una lista ordinata di valori MCID: l'ordine di scrittura degli intervalli di marked-content su questa pagina. Ricomporre il testo è quindi questione di riprodurre i glifi in quell'ordine

L'MCID è registrato durante l'estrazione dei glifi, non cercato dopo

Questo è il dettaglio implementativo che rende la funzione economica. HotPDF registra già il marked-content identifier attivo su ogni glifo che estrae, nel campo MCID di THPDFGlyphRecord, perché l'interprete dello stream di contenuto sa quale ambito BDC sia aperto nel momento in cui elabora ogni operatore Tj o TJ. L'estrazione in ordine di struttura non richiede quindi una seconda passata sullo stream di contenuto. Raccoglie la sequenza di MCID dall'albero della struttura, poi raggruppa i glifi già estratti per MCID e li emette in quella sequenza

var
  Pdf: THotPDF;
  PageCount, I, Untagged: Integer;
  PageText, AllText: UnicodeString;
  Report: TStrings;   // sink di diagnostica di proprietà del chiamante
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
        // Ordine di scrittura dritto dall'albero della struttura
        if Untagged > 0 then
          Report.Add(Format('page %d: %d glyphs outside the structure tree',
            [I, Untagged]));
      end
      else
        // Nessun albero della struttura utilizzabile su questa pagina: fallback geometrico
        Pdf.ExtractLoadedPageText(I, PageText);
      AllText := AllText + PageText + #13#10;
    end;
  finally
    Pdf.Free;
  end;
end;

I glifi non taggati sono contati, mai scartati in silenzio

Una pagina può essere parzialmente taggata. I produttori aggiungono una linea decorativa, un numero di pagina o un watermark tardivo fuori da ogni ambito BDC, e quei glifi non appartengono ad alcun MCID. Scartarli sarebbe l'implementazione pulita e quella sbagliata, perché lo stesso vuoto compare anche quando un produttore tagga il corpo ma dimentica la tabella, e si perderebbe la tabella senza accorgersene

HotPDF aggiunge i glifi non rivendicati come coda geometrica dopo il testo in ordine di struttura e riporta il loro numero tramite il parametro di output UntaggedGlyphCount. Quel numero è un segnale di qualità su cui si può agire. Una manciata di glifi su una pagina di duemila è arredo di pagina e può essere ignorato. Il quaranta per cento della pagina fuori dall'albero della struttura significa che il tagging è decorativo e l'estrattore geometrico è la risposta più onesta per quel file

Flusso decisionale per l'estrazione del testo di struttura di HotPDF con fallback geometrico quando una pagina non ha un albero della struttura utilizzabile o un tagging decorativo
True significa ordine di struttura con la coda non taggata aggiunta, e False incanala la pagina verso l'estrattore geometrico invece di fallire
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);
    // Fidarsi dell'albero della struttura solo quando rivendica la maggior parte della pagina
    if (TotalGlyphs = 0) or (Untagged * 4 <= TotalGlyphs) then
    begin
      UsedStructure := True;
      Result := True;
      Exit;
    end;
  end;
  Result := Pdf.ExtractLoadedPageText(PageIndex, AText);
end;

Cosa fa restituire False alla funzione

Tre casi, e vale la pena distinguerli perché solo uno è un difetto del documento. Il primo è un comune PDF non taggato: nessuno /StructTreeRoot, nulla da percorrere, e False è semplicemente la verità. Il secondo è una pagina scannerizzata il cui testo viene da uno strato OCR mai taggato. Il terzo è quello interessante: contenuto che trasporta operatori BDC con valori /MCID ma la cui pagina non ha voce /StructParents e il cui albero della struttura non riferisce mai quegli identificatori. Il marked content esiste, il lato struttura no, e non c'è alcun ordine da recuperare. HotPDF riporta False invece di inventarsene uno

L'ultimo caso compare in file modificati a mano e in output di tool che emettono marked content per scopi di optional content o artifact senza costruire un albero della struttura. Se Lei produce PDF taggati, la stessa asimmetria è ciò che la validazione PDF/UA controlla, e la controparte lato writer è trattata nel layout DOM che emette output taggato e impaginato

Dove l'ordine di struttura ripaga da solo

L'audit di accessibilità è quello ovvio: se si certifica un documento contro PDF/UA, l'ordine di lettura che uno screen reader annuncerà è esattamente l'ordine di struttura, quindi estrarlo è il modo di rivederlo senza uno screen reader. L'acquisizione dati è il caso commerciale più grande. I moduli governativi taggati, le divulgazioni regolamentate e gli allegati di fatturazione elettronica trasportano etichette e valori dei campi in ordine dichiarato, e leggerli in quell'ordine rimuove un'intera classe di bug di mappatura che l'estrazione geometrica crea sui layout a più colonne

Il consumatore più recente è il recupero per i modelli linguistici. Il chunking di un documento per embedding è buono solo quanto l'ordine del testo, e un chunk che cuce due colonne produce frasi mai esistite. L'estrazione in ordine di struttura è la correzione più economica disponibile, perché per i documenti taggati l'ordine corretto è già nel file e deve solo essere letto

HotPDF è un componente VCL nativo per Delphi e C++Builder, quindi l'attraversamento dell'albero della struttura e la riproduzione dei glifi girano entrambi in-process su un documento caricato senza alcun renderer esterno coinvolto. I dettagli completi dell'API per la famiglia di estrazione da documenti caricati sono sulla pagina di prodotto di HotPDF Delphi PDF component