HotPDF Delphi Component, dalla v2.768.15, rimuove i caratteri duplicati che fake bold e loghi sovrastampati lasciano nel testo PDF estratto. Quando un produttore disegna lo stesso glifo due volte a una frazione di punto di distanza, ExtractLoadedPageText, ExtractLoadedPageTextLayout e ExtractLoadedPageTextColumns ora leggono quel carattere una volta sola, così «Bold words» non esce più come «Bold wordsBold words»
È uno di quei bug che sembrano un fallimento del decoder e non lo sono. Ogni glifo si decodifica correttamente; il testo sta davvero due volte sulla pagina. La correzione deve decidere quale di due glifi perfettamente validi è la copia, e farlo senza mangiarsi lettere ripetute legittime come la doppia l di «fill» o le celle identiche di una tabella. Il resto dell'articolo spiega la regola che HotPDF usa, le soglie dietro di essa, dove diverge deliberatamente dagli altri strumenti di estrazione, e un secondo bug di estrazione, non correlato, dello stesso treno di release che tagliava il testo alla fine delle righe
Perché il fake bold produce testo raddoppiato nell'estrazione PDF?
Il fake bold raddoppia il testo estratto perché il produttore disegna la stringa due volte ed entrambe le copie sono vere operazioni di mostrare testo. Quando una famiglia di font non ha la variante bold, o lo strumento di scrittura semplicemente non si dà pensiero, molti generatori emulano il bold mostrando la stessa stringa con Tj o TJ, spostando la posizione del testo a destra di qualche decimo di punto e mostrandola di nuovo. Sullo schermo le due copie si fondono in un tratto più spesso. Nel content stream ci sono due sequenze complete di codici carattere, e ISO 32000-1 §9.4 non dà a un estrattore alcun flag che dica «questa è decorazione»
I loghi sovrastampati colpiscono lo stesso problema da un angolo diverso. Un logotipo può essere disegnato una volta come fill e di nuovo come stroke o come un'ombra leggermente scostata, usando lo stesso font e gli stessi caratteri. Un estrattore ingenuo che percorre il content stream in ordine di disegno emette tutta la prima sequenza, poi tutta la seconda, ed è per questo che il risultato legge «Bold wordsBold words» invece di «BBoolldd». Prima della v2.768.15, l'assemblaggio del testo di HotPDF teneva entrambe le copie, e il duplicato rompeva la cosa che alla maggior parte dei chiamanti importa davvero: una ricerca di «Bold words» nel testo della pagina poteva fallire perché il confine di parola fra le due sequenze era sparito
Il vero text rendering mode 2 (fill poi stroke, ISO 32000-1 §9.3.6) non ha questo problema, perché è una sola operazione di mostrare testo che produce un unico insieme di glifi. I duplicati compaiono solo quando il produttore emette il testo due volte
Come decide HotPDF quale glifo è il duplicato?
HotPDF tratta due glifi come un solo carattere disegnato quando portano lo stesso valore Unicode, hanno la stessa direzione di scrittura, stanno entro il 5% l'uno dell'altro in dimensione e si trovano quasi sovrapposti: non più di 0.1 della dimensione del glifo di distanza lungo la baseline e non più di 0.2 della dimensione del glifo di distanza attraverso di essa. Di ogni simile coppia, il glifo disegnato per primo viene tenuto e quello successivo scartato. Questi sono i valori dell'implementazione v2.768.15, e ognuno c'è per un motivo
- Stesso Unicode, stessa direzione. Solo caratteri identici possono essere duplicati, e un glifo orizzontale non viene mai confrontato con uno verticale (WMode 1)
- Dimensione entro il 5%. Una grande lettera di titolo e una piccola lettera di didascalia che per caso si sovrappongono non sono lo stesso testo, quindi una discrepanza di dimensione esclude la coppia
- 0.1 della dimensione lungo la baseline. Gli scostamenti da fake bold sono di qualche decimo di punto, qualche percento di una normale dimensione del testo. Due vere lettere adiacenti, persino una «ll» con kerning stretto, sono separate di circa un intero advance width, un ordine di grandezza più di questa tolleranza
- 0.2 della dimensione attraverso la baseline. Ombre e passaggi outline spesso si spostano in basso oltre che a destra, quindi la tolleranza verticale è più larga, ma comunque molto al di sotto della distanza dalla riga successiva
- Vince il primo disegnato. Tenere il glifo precedente preserva l'ordine di lettura della prima sequenza, così il testo superstite scorre ancora come l'ha scritto il produttore
Le distanze sono misurate nel sistema di riferimento della baseline del glifo stesso invece che nelle X e Y grezze della pagina, così il testo ruotato e il testo su una baseline inclinata usano le stesse tolleranze delle righe orizzontali ordinarie. La dimensione usata è quella del glifo come disegnato sulla pagina, derivata dal suo quad, non l'operando di dimensione dato a Tf; conta per i documenti che impostano Tf a 1 e scalano tutto attraverso la matrice di testo, dove una dimensione font grezza renderebbe ogni tolleranza inutile
Gli spazi non sono mai candidati. I code point fino a U+0020, lo spazio no-break U+00A0 e lo spazio ideografico U+3000 vengono saltati, perché i glifi vuoti non portano inchiostro visibile da duplicare e scartarne uno fonderebbe le parole
Tenere il controllo veloce sulle pagine dense
Il controllo dei duplicati in HotPDF gira in tempo circa n log n, non n al quadrato, perché ordina prima e confronta i glifi solo dentro una piccola finestra. Confrontare ogni glifo con ogni altro sarebbe corretto e inutilizzabile: una pagina densa di corpo piccolo porta facilmente decine di migliaia di glifi, e il controllo gira a ogni chiamata di estrazione del testo
La routine interna ordina i glifi non spazio per valore Unicode, poi per origine X, poi per ordine di disegno, usando un merge sort stabile. Dopo l'ordinamento, ogni potenziale duplicato di un glifo sta immediatamente dopo di lui nell'ordine, così la scansione avanza solo finché il valore Unicode corrisponde e la differenza di X resta entro 0.25 della dimensione del glifo. La finestra di 0.25 è volutamente più larga della tolleranza di 0.1 lungo la baseline, così una coppia su una baseline leggermente inclinata ci ricade comunque prima che parta il test preciso nel sistema della baseline. Un caso sottile: se la scansione dal glifo A incontra un glifo corrispondente disegnato prima di A, A è la copia, quindi A viene scartato e la scansione si ferma, lasciando il glifo precedente continuare il confronto con tutto ciò che viene dopo. Quella regola è ciò che fa valere «vince il primo disegnato» per i tripli colpi oltre che per le coppie
Il confronto è sul valore Unicode, non sul font o sul codice carattere. Due glifi che mappano allo stesso Unicode attraverso font diversi, per esempio un passaggio fill in un subset incorporato e un passaggio stroke in un altro, vengono comunque riconosciuti come un carattere. Il rovescio della medaglia è che un glifo il cui codice si risolve in Unicode 0 non ha nulla di affidabile da confrontare, che è un motivo in più per tenere d'occhio il conteggio dei non risolti trattato in estrazione del testo da PDF caricati con HotPDF
Quali chiamate di estrazione HotPDF rimuovono i glifi duplicati?
Tutte le chiamate che producono testo in HotPDF rimuovono i duplicati, e la chiamata dei glifi grezzi no. La deduplicazione vive in THotPDF.ExtractVisibleLoadedPageGlyphs, il passaggio che scarta anche i glifi fuori dal box visibile della pagina, e l'estrazione di testo semplice, colonne, layout e testo semantico leggono tutti i loro glifi da lì. THotPDF.ExtractLoadedPageGlyphs restituisce ancora ogni glifo che il content stream disegna, sovrastampe incluse, perché i chiamanti che costruiscono strumenti di evidenziazione, redazione o forense hanno bisogno del registro completo di ciò che è stato disegnato
Per il lavoro di testo ordinario nulla cambia nel codice chiamante. Lo stesso ciclo che prima della v2.768.15 estraeva testo raddoppiato ora estrae ogni parola una volta
var
Pdf: THotPDF;
PageCount, I: Integer;
PageText: UnicodeString;
begin
Pdf := THotPDF.Create(nil);
try
PageCount := Pdf.LoadFromFile('brochure.pdf');
for I := 0 to PageCount - 1 do
if Pdf.ExtractLoadedPageText(I, PageText) then
if Pos('Bold words', PageText) > 0 then
Writeln('Found on page ', I + 1);
finally
Pdf.Free;
end;
end;
L'output di layout e colonne guadagna altrettanto, dato che una sequenza duplicata spingeva di lato il resto della riga sulla griglia di caratteri. ExtractLoadedPageTextColumns conserva il suo moltiplicatore di gap opzionale; il filtro dei duplicati gira prima della divisione in colonne, quindi non interagisce con quell'impostazione
var
Grid, Columns: UnicodeString;
begin
if Pdf.ExtractLoadedPageTextLayout(0, Grid) then
TFile.WriteAllText('page1-layout.txt', Grid, TEncoding.UTF8);
// Le colonne si dividono dove un gap supera 3x l'advance medio del glifo
if Pdf.ExtractLoadedPageTextColumns(0, Columns, 3.0) then
TFile.WriteAllText('page1-columns.txt', Columns, TEncoding.UTF8);
end;
Se volete sapere se una pagina contiene proprio testo sovrastampato, confrontate le due chiamate dei glifi. Sappiate che la differenza conta anche i glifi che cadono fuori dal crop box, quindi trattatela come un limite superiore dei duplicati invece che come una cifra esatta
var
AllGlyphs, Visible: THPDFGlyphArray;
begin
if Pdf.ExtractLoadedPageGlyphs(0, AllGlyphs) and
Pdf.ExtractVisibleLoadedPageGlyphs(0, Visible) then
Writeln(Format('%d painted, %d read as text (%d overprinted or off-page)',
[Length(AllGlyphs), Length(Visible),
Length(AllGlyphs) - Length(Visible)]));
end;
Le regole di spaziatura delle parole e di interruzione di riga che trasformano questi glifi in testo non sono cambiate dal filtro; sono trattate in dettaglio in come HotPDF colloca spazi tra parole e interruzioni di riga nel testo di pagina
Dove la regola diverge deliberatamente dagli altri strumenti
HotPDF scarta i duplicati esatti che alcuni altri strumenti di estrazione tengono, e quella differenza è intenzionale. Quando la modifica v2.768.15 fu verificata contro un corpus di riferimento, un piè di pagina disegnato due volte alla stessa identica posizione venne estratto una volta in HotPDF e due volte in un estrattore di riferimento. Un glifo disegnato sopra un glifo identico è invisibile come carattere separato, quindi leggerlo una volta è ciò che la pagina mostra davvero
Il limite onesto gira dall'altro lato. Un fake bold che scosta il secondo passaggio di più di 0.1 della dimensione del glifo lungo la baseline non viene beccato, e nemmeno un passaggio ombra disegnato a una dimensione notevolmente diversa. Quei layout sono rari in pratica, perché l'effetto visivo smette di sembrare bold non appena lo scostamento diventa così grande, ma se vedete testo raddoppiato da un titolo decorativo, confrontate i valori X dei glifi da ExtractLoadedPageGlyphs prima di dare per rotto il filtro. Il filtro inoltre non fa nulla per duplicati che non sono co-locati geometricamente, come un'intestazione corrente ripetuta in un layer nascosto altrove sulla pagina; quella è una questione di contenuto, non una sovrastampa
Perché i finali di riga venivano tagliati nel testo in font CID?
Un bug separato corretto in HotPDF v2.768.12 perdeva i finali delle righe nel testo composto con font CID-keyed (Type 0) il cui array di larghezze /W non era ordinato per CID. ISO 32000-1 §9.7.4.3 definisce il formato dell'array /W ma non richiede che le sue voci siano in ordine di CID crescente, e alcuni generatori le scrivono nell'ordine in cui i glifi furono usati per la prima volta nel documento
Il decoder di estrazione del testo cercava le larghezze con una ricerca binaria, che assume silenziosamente input ordinato. Su un array non ordinato la maggior parte delle ricerche mancava e ricadeva sulla larghezza predefinita /DW, che è 1000 unità, un em intero. Il testo latino in un simile font veniva quindi composto come se ogni lettera fosse a larghezza intera, le righe correvano ben oltre il bordo destro della pagina, e i glifi che finivano fuori dal box visibile venivano scartati, così i finali delle righe sparivano dal testo estratto e dalla ricerca. Niente crashava e niente veniva riportato, ed è ciò che rendeva il problema difficile da vedere
HotPDF ora ordina le voci /W con un merge sort stabile subito dopo averle lette, così dove due voci si sovrappongono vince ancora quella scritta per prima, come si legge l'array in ordine di file. Il renderer di pagina non è mai stato toccato, perché tiene la sua copia dei dati di larghezza e li scansiona linearmente, e anche l'array di larghezze verticale /W2 è scansionato linearmente. Quel taglio merita di essere ricordato quando una pagina si vede perfetta ma si estrae male: rendering ed estrazione in una libreria PDF spesso condividono molto meno codice di quanto vi aspettereste
FAQ
HotPDF rimuove le lettere ripetute come la doppia l di «fill»?
No. Due vere lettere adiacenti distano circa un advance width, mentre HotPDF fonde solo caratteri identici che stanno entro 0.1 della dimensione del glifo lungo la baseline e 0.2 attraverso di essa, quindi le ripetizioni legittime vengono tenute
Come ottengo ogni glifo, incluse le sovrastampe duplicate?
Chiamate THotPDF.ExtractLoadedPageGlyphs, che restituisce ogni glifo che la pagina disegna in ordine di disegno. THotPDF.ExtractVisibleLoadedPageGlyphs e tutte le chiamate di estrazione del testo restituiscono l'insieme deduplicato e visibile
Quale versione di HotPDF corregge il testo raddoppiato da fake bold?
HotPDF v2.768.15 ha aggiunto la rimozione dei glifi duplicati all'estrazione di testo, layout, colonne e testo semantico, e la v2.768.12 ha corretto i finali di riga mancanti nei font CID con un array /W non ordinato
Entrambe le correzioni sono nella versione attuale del componente HotPDF Delphi PDF, che estrae testo da PDF caricati in Delphi e C++Builder accanto alle sue funzioni di creazione, modifica e rendering PDF