HotPDF, gömülü PDF yazı tiplerini makineye hiçbir şey yüklemeden Delphi'de işler: HPDFGlyphRender.pas içindeki gömülü glif işleme süreç hattı, PDF'in içinde saklanan yazı tipi programlarını (FontFile2'den TrueType glyf anahatlarını, FontFile3'ten CFF Type 2 karakter dizgelerini ve Type 3 glif içerik akışlarını) ayrıştırır ve bunları içi dolu GDI vektör yolları olarak yeniden oynatır. Bu makale, HotPDF ile PDF sayfalarını bir TBitmap'e dönüştürme arkasındaki yazı tipi doğruluğuna yönelik derinlemesine incelemedir: O makale işleyiciyi bir bütün olarak ele alırken, bu makale o sayfalardaki metinlerin tam şekillerini nasıl aldığını kapsar
Bir PDF neden metin yerine kutularla işlenir?
İşlenen PDF çıktısındaki kutular, boşluklar veya hafifçe yanlış karakterler, neredeyse her zaman işleyicinin dosyadaki gömülü yazı tipini kullanmak yerine işletim sisteminden bir yazı tipi istediği anlamına gelir. Şikayet her seferinde aynı şekilde gelir: Belge, onu üreten makinede mükemmel görünür, ardından bir müşteri bunu temiz bir sunucuda veya kısıtlı bir masaüstünde açar ve Japonca fatura tofu kareleri gösterir ya da ikame edilmiş benzer bir yazı tipi her satır sonunu kaydırır. Yazı tipleri o makinede hiçbir zaman mevcut değildi (yalnızca PDF'in içindeydi) ve sistem yazı tipi ikamesiyle sınırlı kalan bir işleyici bunları göremez. Alt küme (subset) yazı tipleri durumu daha da kötüleştirir: Bir alt küme, yalnızca o dosyaya özel olarak atanmış karakter kodları altında kırk glif taşıyabilir; bu, kurulu hiçbir yazı tipinin paylaşmadığı bir atamadır
HotPDF, TrueType glyf anahatlarını GDI yollarına nasıl dönüştürür?
HPDFGlyphRender.pas içindeki THPDFEmbeddedTTF, her glif kaydını bulmak için loca tablosunu okur, glyf konturlarını nokta nokta yürür ve bir GDI yolu yayar. İki TrueType kuralının açıkça ele alınması gerekir. İlk olarak, ardışık eğri dışı (off-curve) noktalar, orta noktalarında eğri üzerinde (on-curve) bir noktayı ima eder ve tüm noktaları eğri dışı olan bir kontur, son ve ilk noktalarının orta noktasından başlar; bu kurallardan herhangi birini atlarsanız yuvarlatılmış glifler düz yüzeyler geliştirir veya çöker. İkinci olarak, TrueType eğrileri ikinci dereceden Bézier'ler iken GDI'nin PolyBezierTo işlevi üçüncü dereceden eğrileri alır, bu nedenle her ikinci dereceden segment, çizgi segmentlerine düzleştirilmek yerine tam olarak derece olarak yükseltilir
// Tam derece yükseltme: ikinci dereceden (P0, Q, P2) -> üçüncü dereceden (P0, C1, C2, P2)
// C1 = P0 + 2/3 (Q - P0), C2 = P2 + 2/3 (Q - P2)
C1.X := P0.X + 2 * (Q.X - P0.X) / 3;
C1.Y := P0.Y + 2 * (Q.Y - P0.Y) / 3;
C2.X := P2.X + 2 * (Q.X - P2.X) / 3;
C2.Y := P2.Y + 2 * (Q.Y - P2.Y) / 3;
// ardından C1, C2, P2 ile PolyBezierTo — geometrik olarak özdeş eğri
Derece yükseltme kayıpsızdır: Üçüncü dereceden eğri özdeş eğriyi izler, bu nedenle işlenen anahat, uyumlu bir görüntüleyicinin aynı tablodan çizdiğiyle her yakınlaştırmada eşleşer. Kalan iş yerleştirmedir. Her glif yazı tipi birimlerinde (tipik olarak 1000 veya 2048 units-per-em ızgarası) yazılır ve işleyici, yol doldurulmadan önce ölçek matrisini, metin matrisini ve mevcut dönüşüm matrisini tek bir gliften cihaza dönüşüm matrisine birleştirir. Sıra burada göründüğünden daha önemlidir: Aynı üç matrisi tersten birleştirin ve her glif başlangıç noktasına doğru çöker; gerçek hatası bir satırlık matris cebiri olan hatalı görünen bir sayfa
Type 2 karakter dizgesi (charstring) yorumlayıcısı CFF yazı tiplerini nasıl ele alır?
THPDFEmbeddedCFF, FontFile3 programlarına gerçek bir Type 2 karakter dizgesi (charstring) yorumlayıcısı sunar: CFF INDEX yapılarını, Top DICT ve Private DICT'i ayrıştırır, ardından her karakter dizgesini çalıştırır ve yol segmentlerini doğrudan GDI'ya yayar. Çıplak CFF tablosuna ulaşmak için önce bir OpenType sarmalayıcısı (OTTO kapsayıcısı) soyulur; çıplak CIDFontType0C ve Type1C akışları doğrudan tüketilir. Karakter dizgeleri kompakt bir yığın (stack) dilidir ve bunun üç kuralı yorumlayıcının bayt akışıyla uyumlu kalıp kalmayacağını belirler. İsteğe bağlı genişlik öneki, ilk yığın temizleme operatörünün fazladan bir başlangıç işleneni (operand) taşıyabileceği anlamına gelir. hintmask operatörü, işlenenler hala yığındayken bir vstemhm ima eder ve atlanacak maske baytlarının sayısı biriken gövde (stem) sayısına bağlıdır; sayıyı bir kez yanlış hesaplarsanız sonraki her işlem kodu yanlış okunur. Ve alt program (subroutine) çağrıları aramadan önce dizinlerine bir sapma (bias - alt program sayısına bağlı olarak 107, 1131 veya 32768) ekler, bu nedenle sapmasız bir çağrı tamamen yanlış alt programa düşer
CID anahtarlı CFF, naif uygulamaları tökezleten bir dolaylılık ekler: Karakter kodu bir CID seçer ancak karakter dizgesi dizini bir GID'dir ve yazı tipinin karakter seti GID'yi CID'ye eşler; bu nedenle işleyici çizimden önce ters CID-to-GID eşlemesini oluşturur ve birden fazla taşıyan yazı tipleri için FDSelect aracılığıyla glif başına Private DICT'i seçer. Basit Type 1 yazı tipleri için olağan taşıyıcı olan ad anahtarlı Type1C programları ise tek baytlık kodları CFF programının yerleşik kodlaması veya bir sonraki bölümde açıklanan PDF düzeyindeki kodlama mekanizması aracılığıyla çözümler. Dürüst bir uyarı: Yorumlayıcı, akışı senkronize tutmak için ipucu (hint) operatörlerini okur ancak ipucu çalıştırmaz; bu, sonda tartışılan bir sınırdır
Type 3 yazı tipi nedir ve nasıl çizilir?
Bir Type 3 glifi kesinlikle bir anahat değildir; ISO 32000-1 §9.6.5 bunu bir içerik akışı olarak tanımlar, bu nedenle HotPDF grafik durumunu iterek, yazı tipi matrisini, yazı tipi boyutunu ve metin matrisini CTM üzerine birleştirerek ve glif prosedürünü sayfaları çizen aynı operatör yorumlayıcısı aracılığıyla, yazı tipinin kendi /Resources'ı kapsam dahilinde olacak şekilde işler. Doğrulak için iki spesifikasyon ayrıntısı önemlidir. Type 3 /Widths, diğer tüm yazı tipi türlerinin kullandığı 1/1000 metin alanı yerine glif alanında ifade edilir, bu nedenle ilerlemeler /FontMatrix'ten geçmelidir; aksi takdirde 0.01 matrisli barkod yazı tipleri bir büyüklük derecesi kadar yanlış adım atar. Ve d1 operatörüyle açılan bir glif prosedürü, işleyicinin uyguladığı iki vaatte bulunur: Boyama bildirilen sınırlayıcı kutuyla (bounding box) kırpılır ve ISO 32000-1 §9.6.5.2 uyarınca glif kendi renk operatörlerini göz ardı eder ve çağıranın geçerli dolgu rengiyle boyama yapar, bu nedenle prosedür içindeki rg, g, k ve onların vuruşlu (stroking) ikizleri o glif süresince bastırılır. Renk kuralını atlarsanız sayfa tarafından mavi damgalanmış bir d1 barkod yazı tipi siyah çıkar; kırpmayı atlarsanız hatalı biçimlendirilmiş bir glif hücresinin dışına taşar
Karakter kodları nasıl glif kimlikleri (glyph IDs) haline gelir?
Anahat yorumlayıcıları işin sadece yarısıdır, çünkü bir PDF metin dizgesindeki baytlar glif indeksleri değil, karakter kodlarıdır ve ISO 32000-1 eşlemeye iki alt madde ayırır. Basit yazı tipleri için, §9.6.6 katı bir öncelik öngörür: Bir /Differences dizisi, yazı tipi programının kendi haritasını geçersiz kılan temel kodlamayı (WinAnsiEncoding, MacRomanEncoding veya StandardEncoding) geçersiz kılar. HotPDF bu zinciri 256 girişli bir kod-GID tablosuna dönüştürerek glif adlarını üç yolla glif dizinlerine çevirir: CFF programı içinde tam karakter seti eşleşmesi, kelimenin tam anlamıyla dizin olarak alınan sayısal gNN/glyphNN adları ve Adobe Glyph List ad-Unicode çevirisi ve ardından TrueType programları için bir cmap araması. Bileşik yazı tipleri için, §9.7 CIDToGIDMap'i sorumlu kılar: Yaygın durum /Identity'dir ancak giriş, CID tarafından dizinlenen big-endian çiftleri akışı olabilir; ve HotPDF'in kendi Unicode çıktısı kompakt alt kümeler için tam olarak bu akış formunu kullanır, bu nedenle akış yolu egzotik bir köşe değildir
// /CIDToGIDMap akış olarak: CID tarafından dizinlenmiş big-endian Word çiftleri
if 2 * CID + 1 <= High(MapBytes) then
GID := (MapBytes[2 * CID] shl 8) or MapBytes[2 * CID + 1]
else
GID := 0; // aralık dışı olanlar .notdef ile eşleşir
Bir TrueType cmap araması gerektiğinde, HotPDF tek bir alt tabloya güvenmek yerine bir yedek (fallback) zinciri yürütür: Windows Unicode alt tabloları (biçim 4, ardından ek düzlemler için biçim 12) ilk sırada gelir, ardından düşük bayta yansıtılmış F000 özel kullanım alanı kuralıyla (3,0) sembol alt tablosu gelir (Wingdings gibi bir sembol yazı tipinin düz ASCII kodlarına yanıt vermesinin nedeni), ardından eski biçimler 6 ve 0. Biçim 2 alt tabloları kasıtlı olarak yorumlanmaz: Bunlar Unicode'u değil, Shift-JIS ve Big5 gibi eski çok baytlı kod sayfalarını eşler ve modern CJK yazı tipleri her halükarda bir biçim 4 veya biçim 12 alt tablosu taşır. Bu yolların hiçbirinden geçemeyen herhangi bir kod, o tek glif için GDI çizimine geri döner, böylece eşlenemeyen bir karakter tüm metin akışını değil, yalnızca bir glifi bozar
Gömülü yolun yapmadığı şeyler
Sınırları açıkça belirtmekte fayda var. İpuçları (hinting) uygulanmaz; anahatlar yazıldığı gibi doldurulur, bu 150 DPI ve üzerinde ipuçlu çıktıdan ayırt edilemez ancak çok küçük boyutlarda ipuçlu bir rasterleştiriciden bir piksel kadar farklılık gösterebilir. FontFile içindeki orijinal Type 1 programları (eexec şifreli karakter dizgeleri) yorumlanmaz ve OpenType değişken yazı tipi eksenleri uygulanmaz; her iki durum da, hasarlı bir yazı tipi programı veya kullanılabilir bir cmap içermeyen bir glyf tablosu gibi, sayfayı başarısız kılmak yerine sistem yazı tipi çizimine geri döner. Aynı doğruluk öncelikli yaklaşım işleyicinin başka yerlerinde de geçerlidir; eksenel (axial) ve radyal (radial) gölgeleme desenleri, gradyanların hak ettiği muamelenin aynısını görür; ve üretim tarafının, EndDoc'un yazı tipi alt kümelerini nasıl sıraladığı konusunda kendi yazı tipi incelik hikayesi vardır
Süreç hattını kullanmak yazı tipine özgü hiçbir kod gerektirmez; yukarıdaki her mekanizma, sayfa işleme çağrısının içinde otomatik olarak devreye girer
var
Pdf: THotPDF;
Bmp: TBitmap;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('invoice-embedded-fonts.pdf') > 0 then
begin
// Gömülü TrueType, CFF ve Type 3 yazı tipleri dosyanın kendisinden işlenir
// bu makineye hiçbir şey yüklenmesi gerekmez
Bmp := Pdf.RenderLoadedPageToBitmap(0, 144);
if Bmp <> nil then
try
Bmp.SaveToFile('page1.bmp');
finally
Bmp.Free;
end;
end;
finally
Pdf.Free;
end;
end;
Pratik sonuç, destek gelen kutunuzun önemsediği sonuçtur: Yazı tiplerini taşıyan bir PDF; bir derleme sunucusunda, bir Windows kapsayıcısında (container) veya yazı tipini hiç görmemiş bir müşteri masaüstünde o yazı tipleriyle işlenir. Gömülü glif işleme süreç hattı, Delphi ve C++Builder için HotPDF Bileşeni'nin bir parçası olarak gönderilir; harici DLL bağımlılığı olmaksızın PDF oluşturma, düzenleme, metin çıkarma ve sayfa işlemeyi kapsayan yerel bir VCL kütüphanesidir