Bir PDF'den kalın (bold) veya eğik (italic) metin çıkarmaya çalıştıysanız, muhtemelen işe bir regex ile başlamışsınızdır. Bu mantıklı bir adım gibi görünür. Yazı tipi adında "Bold" kelimesini arayın, metni işaretleyin ve devam edin. Bu strateji, size sahte bir güven verecek kadar uzun süre işe yarar. Sonra birisi aynı belgeyi Acrobat'ın farklı bir sürümünden, LibreOffice'den veya bir print-to-PDF sürücüsünden dışa aktarır ve kodladığınız tüm varsayımlar çöker.
Yazı Tipi İsimleri Neden Yalan Söyler
PDF.js size ABCDEF+TimesNewRomanPS-BoldMT gibi dizeler verecektir. İlk altı karakter, dışa aktarma sırasında eklenen rastgele bir önektir ve dosya her yeniden oluşturulduğunda değişirler. Ayrıştırıcınızı (parser) bu öneke dayandırmak, gürültüye bahis oynamaktır. Diğer dışa aktarıcılar daha da az yardımcıdır. Bazıları Font12 veya F1 gibi yalın tanımlayıcılar üretir. Bu etiketlerin hiçbir anlamsal değeri yoktur; bunlar dosya yazılırken yakınlarda bulunan dahili kaynak etiketleridir.
Taşınabilir Belge Formatı (PDF), sonraki aşamalarda metin çıkarmayı kolaylaştırmak için tasarlanmadığından, yazı tipi isimleri yalnızca gömülü veya alt küme haline getirilmiş kaynaklara yapılan referanslardır. Asla stil tespiti için kararlı bir API olarak işlev görmeleri amaçlanmamıştır. Bold veya Italic alt dizelerini arayan bir regex yazdığınızda, oluşturucu uygulamanın dilediği gibi biçimlendirebileceği bir etiketi kazımış olursunuz. Asıl tipografik özellikleri okumuyorsunuzdur. Bir dosya adlandırma kuralını okuyorsunuzdur ve kurallar sözleşme değildir.
Etiketi Değil, Tanımlayıcıyı Okuyun
Gerçek bilgi başka bir yerdedir. PDF.js'de her sayfa nesnesi, sayfayı işlemek için gereken gerçek yazı tipi tanımlayıcılarını tutan bir eşleme (map) olan commonObjs yapısını sunar. Kütüphane bir sayfayı ayrıştırdığında, bu eşlemeyi gerçek yazı tipi nesneleriyle doldurur. Bu nesneler, bold ve italic için boolean özellikler sunar. Bu boolean değerleri isim dizisinden çıkarılmaz. Bunlar, PDF'nin içine gömülü olan; glif metriklerinden, OS/2 tablo bayraklarından ve dizgici tarafından belgeye yazılan sembolik tanımlayıcılardan türetilen yazı tipi tanımlayıcısından gelir.
Bu, tahmin yürütmeyi bırakabileceğiniz anlamına gelir. Bir sayfadaki metin öğelerini taramadan önce, page.commonObjs üzerinde yineleyerek (iterate ederek) bir fontStyleMap oluşturun. Her yazı tipi ID'si için, yazı tipi nesnesi tarafından sağlanan gerçek .bold ve .italic özelliklerini kaydeden bir nesne saklayın. Daha sonra her metin öğesini işlerken, yazı tipi referansını eşlemenizde arayın ve önceden hesaplanmış bayrakları okuyun. Aniden, dışa aktarmalar arasında değişmeyen deterministik bir cevaba sahip olursunuz.
Bir yedek plan (fallback) tutabilirsiniz. Eğer tanımlayıcı bir şekilde eksik veya yetersizse, yazı tipi adını temizleyin, öneki çıkarın, rastgele etiketleri atın ve kalan kısım üzerinde muhafazakar bir regex çalıştırın. Ancak bu, birincil mantığınız değil, son çareniz olmalıdır. Güvenilirlik farkı çarpıcıdır. İsim tabanlı ayrıştırma farklı dışa aktarıcılar arasında parçalanırken, tanımlayıcı tabanlı ayrıştırma sabit kalır çünkü dosyaya aslında ne içerdiğini sorar.
Yazı Tipi de Yalan Söylediğinde: Sentetik Stiller
Tanımlayıcı bile bir noktayı kaçırabilir. Bazı PDF oluşturucular ayrı bir eğik (italic) yazı tipi gömmekle uğraşmazlar. Bunun yerine, dik roman yazı tipini alıp bir dönüşüm matrisiyle (transform matrix) eğik hale getirirler. Bu, tipografik saflık yerine dosya boyutuna önem veren tasarım araçları veya eski kelime işlemciler tarafından oluşturulan dosyalarda yaygındır.
PDF.js'deki her metin öğesi, glif koordinat sistemini sayfa koordinat sistemine eşleyen altı elemanlı bir afin matris olan bir transform dizisi taşır. Bu dizinin üçüncü elemanı yatay kaymayı (horizontal shear) kontrol eder. Bu değer sıfırdan farklı olduğunda, metin işleyici (renderer) tarafından mekanik olarak eğilmektedir. Eğer sadece yazı tipi tanımlayıcısına güvenirseniz, bu metni dik roman olarak sınıflandırırsınız. Eğer matrisi incelerseniz, sentetik eğikliği yakalar ve doğru şekilde işaretlersiniz. Aynı mantık, üst üste yazdırma (overprinting) ile oluşturulan sentetik kalınlık için de geçerlidir, ancak bunu sadece geometriden tespit etmek daha zordur. Eğik metinler için kayma (shear) değeri sizin en büyük kanıtınızdır.
Alt Çizgiler Beyan Edilmez, Çizilir
Kalın ve eğik yazı tipi özellikleridir. Alt çizgi ise değildir. PDF'de bir alt çizgi, bir vektör yoludur (vector path). İşleyici, metin taban çizgisine (baseline) yakın konumlandırılmış ince bir yatay segment için bir çizim komutu verir. Bu, bir cmap içinde saklanan bir karakter özniteliği değil, gliflerin altında bulunan grafiksel bir öğedir.
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.
