Se hai mai provato a estrarre testo in grassetto o corsivo da un PDF, probabilmente hai iniziato con una regex. Sembra la mossa ovvia. Cerca la parola "Bold" nel nome del font, contrassegna il testo e vai avanti. Questa strategia funziona quanto basta per darti una falsa sicurezza. Poi qualcuno esporta lo stesso documento da una versione diversa di Acrobat, o da LibreOffice, o da un driver print-to-PDF, e ogni tua assunzione codificata va in pezzi.

Perché i nomi dei font mentono

PDF.js ti restituirà stringhe come ABCDEF+TimesNewRomanPS-BoldMT. I primi sei caratteri sono un prefisso casuale iniettato durante l'esportazione e cambiano ogni volta che il file viene rigenerato. Scommettere il proprio parser su quel prefisso significa scommettere sul rumore. Altri esportatori sono ancora meno utili. Alcuni emettono identificatori puri come Font12 o F1. Queste etichette non hanno alcun significato semantico; sono tag di risorse interne che si trovavano nelle vicinanze quando il file è stato scritto.

Poiché il Portable Document Format non è mai stato progettato per facilitare l'estrazione del testo a valle, i nomi dei font sono semplicemente riferimenti a risorse incorporate o suddivise (subsetted). Non sono mai stati pensati per fungere da API stabile per il rilevamento dello stile. Quando scrivi una regex che cerca la sottostringa Bold o Italic, stai facendo scraping di un'etichetta che l'applicazione creatrice era libera di formattare come preferiva. Non stai leggendo le reali proprietà tipografiche. Stai leggendo una convenzione di denominazione dei file, e le convenzioni non sono contratti.

Leggi il descrittore, non l'etichetta

La verità oggettiva risiede altrove. In PDF.js, ogni oggetto pagina espone commonObjs, una mappa che contiene i veri descrittori dei font necessari per renderizzare la pagina. Quando la libreria analizza una pagina, popola questa mappa con veri oggetti font. Quegli oggetti espongono proprietà booleane per bold e italic. Quei booleani non sono inferiti dalla stringa del nome. Originano dal descrittore del font incorporato nel PDF, derivato dalle metriche dei glifi, dai flag della tabella OS/2 e dai descrittori simbolici che il compositore ha scritto nel documento.

Ciò significa che puoi smettere di tirare a indovinare. Prima di scorrere gli elementi di testo in una pagina, crea un fontStyleMap iterando su page.commonObjs. Per ogni ID font, memorizza un oggetto che registri le reali proprietà .bold e .italic fornite dall'oggetto font. In seguito, quando elabori ogni elemento di testo, cerca il suo riferimento al font nella tua mappa e leggi i flag precomputati. All'improvviso avrai una risposta deterministica che non cambia tra le esportazioni.

Puoi mantenere un fallback. Se il descrittore è in qualche modo assente o incompleto, pulisci il nome del font — rimuovi il prefisso, scarta i tag casuali — ed esegui una regex conservativa su ciò che rimane. Ma questa dovrebbe essere l'ultima risorsa, non la tua logica primaria. La differenza in termini di affidabilità è drastica. Mentre il parsing basato sul nome si frammenta tra i diversi esportatori, il parsing basato sul descrittore rimane stabile perché chiede al file cosa contiene realmente.

Quando anche il font mente: stili sintetici

Anche il descrittore può mancare un colpo. Alcuni creatori di PDF non si disturbano a incorporare un carattere corsivo separato. Invece, prendono il font roman dritto e lo inclinano con una matrice di trasformazione. Questo è comune nei file generati da strumenti di progettazione o vecchi elaboratori di testi che privilegiano la dimensione del file rispetto alla purezza tipografica.

Ogni elemento di testo in PDF.js trasporta un array transform, una matrice affine a sei elementi che mappa il sistema di coordinate dei glifi nel sistema di coordinate della pagina. Il terzo elemento di quell'array controlla lo shear (inclinazione) orizzontale. Quando quel valore è diverso da zero, il testo viene inclinato meccanicamente dal renderer. Se ti fidi solo del descrittore del font, classificherai questo testo come roman dritto. Se ispezioni la matrice, intercetti il corsivo sintetico e lo segni correttamente. La stessa logica si applica al grassetto sintetico creato tramite sovrapposizione (overprinting), anche se è più difficile da rilevare basandosi solo sulla geometria. Per il testo inclinato, il valore dello shear è la tua prova schiacciante.

Le sottolineature sono disegnate, non dichiarate

Il grassetto e il corsivo sono proprietà del font. La sottolineatura no. In un PDF, una sottolineatura è un percorso vettoriale. Il renderer emette un comando di disegno per un sottile segmento orizzontale posizionato vicino alla linea di base del testo. È un elemento grafico che si trova sotto i glifi, non un attributo del carattere memorizzato in una cmap.

This distinction matters because no amount of font descriptor reading will reveal an underline. You need to look at the raw drawing operators on the page, or at the geometry-level output, for short horizontal line segments that run parallel to the baseline at the correct proximity. When your extraction engine spots such a segment underneath a text run, you tag that run as underlined. Treating this as a separate detection layer keeps your data model honest: bold and italic are intrinsic to the font, while underline is extrinsic decoration rendered by the document.

A Working Pipeline

A clean extraction system separates concerns into distinct layers. First, a geometry worker walks the page. It queries page.commonObjs to assemble your fontStyleMap, inspects each text item’s transform array to catch synthetic italics, and scans nearby vector paths to spot underlines. Its output is a clean intermediate structure call it textMeta where every text run carries three simple booleans: bold, italic, and underline.

Next, a text rebuilder consumes that structure and emits marked-up output. Nesting order is important here. The correct hierarchy places underline outermost, then italic, then bold innermost. That means a fully styled run becomes <u><i><b>text</b></i></u>. This ordering prevents invalid HTML overlaps and keeps rendering consistent across browsers and document converters. It also mirrors the typographic logic: decoration wraps semantic emphasis, and semantic emphasis wraps structural weight.

The power of this approach is that it uses only what the PDF already knows about itself. There is no OCR involved, no cloud vision service, and no machine learning model guessing at styles from rasterized pixels. You are reading the file’s own semantic layer, exposed through the geometry and metadata that the creator application already calculated. The result is fast, deterministic, and accurate across the chaotic landscape of PDF generators.

The Real Takeaway

Regex against font names is a trap. It feels like a shortcut because it works on the one file you tested, but it collapses under the mild pressure of a second export. The real information is already inside the PDF, sitting in descriptors, matrices, and vector paths. Build your pipeline around those facts. Ask the font object whether it is bold. Check the transform matrix for shear. Look at the drawn segments for underlines. If you query the document’s own engineering instead of scraping its surface labels, you get styles that survive from one PDF generator to the next.