Si alguna vez has intentado extraer texto en negrita o cursiva de un PDF, probablemente hayas empezado con una regex. Parece el movimiento obvio. Buscar la palabra “Bold” en el nombre de la fuente, marcar el texto y seguir adelante. Esa estrategia funciona lo suficiente como para darte una falsa confianza. Luego, alguien exporta el mismo documento desde una versión diferente de Acrobat, o desde LibreOffice, o desde un controlador de impresión a PDF, y cada suposición que programaste se desmorona.
Por qué los nombres de las fuentes mienten
PDF.js te entregará cadenas como ABCDEF+TimesNewRomanPS-BoldMT. Los primeros seis caracteres son un prefijo aleatorio inyectado durante la exportación, y cambian cada vez que se regenera el archivo. Apostar tu parser a ese prefijo es apostar al ruido. Otros exportadores son incluso menos útiles. Algunos emiten identificadores simples como Font12 o F1. Estas etiquetas no tienen ningún significado semántico; son etiquetas de recursos internos que simplemente estaban cerca cuando se escribió el archivo.
Debido a que el Portable Document Format nunca fue diseñado para facilitar la extracción de texto en etapas posteriores, los nombres de las fuentes son simplemente referencias a recursos incrustados o subconjuntos. Nunca tuvieron la intención de actuar como una API estable para la detección de estilos. Cuando escribes una regex que busca la subcadena Bold o Italic, estás extrayendo una etiqueta que la aplicación creadora era libre de formatear como quisiera. No estás leyendo las propiedades tipográficas reales. Estás leyendo una convención de nomenclatura de archivos, y las convenciones no son contratos.
Lee el descriptor, no la etiqueta
La verdad absoluta reside en otro lugar. En PDF.js, cada objeto de página expone commonObjs, un mapa que contiene los descriptores de fuente reales necesarios para renderizar la página. Cuando la librería analiza una página, puebla este mapa con objetos de fuente genuinos. Esos objetos exponen propiedades booleanas para bold e italic. Esos booleanos no se infieren de la cadena del nombre. Se originan en el descriptor de la fuente incrustado dentro del PDF, derivado de las métricas de los glifos, los flags de la tabla OS/2 y los descriptores simbólicos que el componedor escribió en el documento.
Eso significa que puedes dejar de adivinar. Antes de recorrer los elementos de texto de una página, crea un fontStyleMap iterando sobre page.commonObjs. Para cada ID de fuente, almacena un objeto que registre las propiedades reales .bold y .italic proporcionadas por el objeto de fuente. Más tarde, cuando proceses cada elemento de texto, busca su referencia de fuente en tu mapa y lee los flags precalculados. De repente, tendrás una respuesta determinista que no cambia entre exportaciones.
Puedes mantener un fallback. Si el descriptor está de alguna manera ausente o incompleto, limpia el nombre de la fuente —elimina el prefijo, descarta las etiquetas aleatorias— y ejecuta una regex conservadora sobre lo que quede. Pero esto debería ser tu último recurso, no tu lógica principal. La diferencia en fiabilidad es drástica. Mientras que el análisis basado en nombres se fractura entre exportadores, el análisis basado en descriptores se mantiene estable porque le pregunta al archivo lo que realmente contiene.
Cuando la fuente también miente: estilos sintéticos
Incluso el descriptor puede fallar. Algunos creadores de PDF no se molestan en incrustar una tipografía cursiva separada. En su lugar, toman la fuente romana vertical y la inclinan con una matriz de transformación. Esto es común en archivos generados por herramientas de diseño o procesadores de texto antiguos que favorecen el tamaño del archivo sobre la pureza tipográfica.
Cada elemento de texto en PDF.js lleva un array transform, una matriz afín de seis elementos que mapea el sistema de coordenadas de los glifos al sistema de coordenadas de la página. El tercer elemento de ese array controla el cizallamiento horizontal. Cuando ese valor es distinto de cero, el texto está siendo inclinado mecánicamente por el renderizador. Si solo confías en el descriptor de la fuente, clasificarás este texto como romana vertical. Si inspeccionas la matriz, detectarás la cursiva sintética y la marcarás correctamente. La misma lógica se aplica a la negrita sintética creada mediante sobreimpresión, aunque eso es más difícil de detectar solo mediante la geometría. Para el texto inclinado, el valor de cizallamiento es tu prueba irrefutable.
Los subrayados se dibujan, no se declaran
La negrita y la cursiva son propiedades de la fuente. El subrayado no lo es. En un PDF, un subrayado es una ruta vectorial. El renderizador emite un comando de dibujo para un segmento horizontal delgado posicionado cerca de la línea de base del texto. Es un elemento gráfico que resulta estar debajo de los glifos, no un atributo de carácter almacenado en un 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.
