Teknik Makale

Sistem fontlarıyla gömülü olmayan PDF fontlarını render

Bir PDF bir fontu gömmediğinde HotPDF componenti o metni, HPDFMapBaseFontToSystemin seçtiği kurulu bir Windows fontuyla render eder: metot /BaseFont adını çözer, stil kısmını soyar, GDI ailenin kurulu olduğunu doğrulayana dek birkaç yazımı dener, eksik standart 14 genişliklerini metrik uyumlu fontlardan ölçer ve çizmeden önce tek baytlık kodları Unicodea çevirir. O adımların her biri, naif sürümün gerçek dosyalarda başarısız olması yüzünden var. RenderLoadedPageToBitmap sayfa rendererı gömülü programları iyi işler; bu, dosyada hiç olmayan fontların hikâyesidir

GDI gömülü olmayan bir font için yanlış yüzü neden sessizce çizer?

GDI asla eksik font bildirmez: CreateFontIndirecte tanımadığı bir aile adı verirseniz sessizce bir yedek seçer; çoğu zaman bold ağırlığı olmayan başka bir serif. İlk renderer PDF adını neredeyse aynen geçiriyordu; TimesNewRoman,Bold, TimesNewRomanPS-BoldMT ve SegoeUI-Semibold hiçbir şeyle eşleşmiyor ve GDInin seçtiği her ne ise öyle çıkıyordu. Adlar bundan da kötü olabilir. ISO 32000-1 §7.3.5 bir adın herhangi bir baytı #xx olarak yazmasına izin verir ve CJK üreticileri font adlarını rutin olarak kaçırılmış UTF-8 ya da eski kod sayfası baytlarıyla yazar; v2.766.69dan önce kaçış dizilerinin kendisi aile adı oluveriyordu. HPDFMapBaseFontToSystem artık önce kaçışları çözer, geçerli bir UTF-8 bayt dizisini karakterleri olarak döndürür ve kalan yüksek baytları sistem kod sayfasında okur

Stili kesmek, sezgisel kuralların sahaya indiği yerdir. Virgül her zaman aileyi bitirir (Arial,Bold Arial verir); tire ise yalnızca ardından gelen kelime bir stilse bitirir: Bold, Italic, Oblique, Regular, Roman, Medium, Light, Black, Heavy, Semi, Demi, Thin, Extra, Ultra ya da Condensed. O kural MS-Minchoyu bütün tutarken Calibri-Lightı Calibriye çevirir. HotPDF sonra ailenin yazımlarını dener; ardından PSMT, MT ya da PS eki soyulmuş yazımlarını dener. v2.768.18den beri o yazımlar, bir kelimenin başlayabileceği her noktadaki her boşluk seçeneğini kapsar: küçük harfi izleyen büyük harften önce (MyriadPro Myriad Pro olur), küçük harfin izlediği bir koşunun son büyük harfinde (UIGothic) ve baştaki bir MSden sonra (MSPGothic); tam boşluklu biçimden adın yazıldığı hâle kadar. Böyle dört noktadan sonra yalnızca tam boşluklu ve bitişik ad denenir. Boşluk körlemesine uygulanamaz, çünkü Windows bazı kelimeleri bitişik tutar: SimSun tam olarak o yazımla kuruludur; MicrosoftYaHei, MicrosoftJhengHei ve MSPGothic ise Microsoft YaHei, Microsoft JhengHei ve MS PGothica aittir. v2.768.18den önce mapper her iç büyük harften önce boşluk koyuyordu; MicrosoftYaHei Microsoft Ya Hei diye aranıyor ve hiç bulunamıyordu. Bir aday, CreateFontIndirectin ardından GetTextFacein istenen adı döndürmesiyle ya da — v2.768.18den beri — seçilen fontun name tablosunun onu herhangi bir dilde aile, tam aile ya da tipografik aile adı olarak listelemesiyle kurulu sayılır; cevap ad başına cache edilir, böylece kurulu olmayan çok fontlu belgeler artık her sayfada her ad için Windowsa sormuyor

HotPDFin Delphide gömülü olmayan PDF fontlarını sistem fontlarıyla render hattı: HPDFMapBaseFontToSystem /BaseFont adındaki #xx kaçışlı baytları çözer, MS-Minchoyu bütün tutarken Bold, Italic ve Light stil eklerini soyar, Myriad Pro ile Microsoft YaHei gibi aday yazımlar kurar ve birini yalnızca GetTextFace ya da font name tablosu kurulu adı doğrulayınca kabul eder
GDI eksik fontu asla bildirmez, sessizce yedekler — eşleme adaylarını sırayla dener ve yalnızca GDInin geri verdiği ya da seçilen fontun name tablosunun listelediği ada güvenir

Eşleme fonksiyonu HPDFRenderFontMetrics ünitesinde public olduğu için bir preflight raporu, her gömülü olmayan fontun hangi kurulu aileyle render edileceğini gösterebilir; THotPDFin yüklenmiş belgeler için çoktan açtığı font numaralandırmasını kullanarak:

uses
  HPDFDoc, HPDFRenderFontMetrics;

procedure ListSystemFontMappings(Pdf: THotPDF; Log: TStrings);
var
  Page, I: Integer;
  Info: THPDFLoadedFontInfo;
begin
  for Page := 0 to Pdf.LoadedPageCount - 1 do
    for I := 0 to Pdf.GetLoadedFontCount(Page) - 1 do
      if Pdf.GetLoadedFontInfo(Page, I, Info) and not Info.IsEmbedded then
        Log.Add(Format('page %d  /%s  %s -> %s',
          [Page + 1, string(Info.ResourceName), string(Info.FontName),
           HPDFMapBaseFontToSystem(Info.FontName)]));
end;

HotPDF /Widthssi olmayan standart 14 fontlarını nasıl ölçer?

HotPDF eksik advanceleri aynı metrikli kurulu font üzerinde ölçer, çünkü ISO 32000-1 §9.6.2.2 standart 14 fontun /Widths atlamasına izin verir ve kütüphane AFM tablosu taşımaz. Arial Helvetica metriklerini taşır, Times New Roman Timesı, Courier New Courieri; HPDFMeasureBaseFontWidths eşleşen yüzü lfHeight = -1000de kurup GetCharWidth32W çağırır; o yükseklikte sonuç, PDF genişliklerinin kullandığı 1/1000 em birimlerindedir zaten. Renderer önce her kodu /Encoding, /BaseEncoding ve /Differences üzerinden, StandardEncoding varsayılanıyla Unicodea çevirir. SVG exportu ve metin çıkarması bir tuzağa daha çarpar: hiç /Encodingi olmayan standart bir Type 1 font, encoding bilgisi olmayan bir decoder üretiyordu; SVG export onu asla kaydetmiyor ve ölçülen her genişlik kullanılmıyordu. Kapalı StandardEncodingi sağlamak düzeltti, yeter ki önceden tanımlı encoding olarak işaretlensin; onu CMap-ad yoluna sokmak her kodu 0 olarak çözer ve her genişlik ona uyar

Bold, italic ve font descriptor bayraklarındaki bir kayma

Bir font descriptorın /Flags girdisi bitlerini 0dan değil 1den numaralar; yani ISO 32000-1 Tablo 123e göre ForceBold bit 19 ($40000), Italic bit 7 ($40)dir. Eski kod $20000yi test ediyordu; o bit 18, SmallCap. Hata v2.345.0dan v2.766.53e hayatta kaldı, çünkü /FontDescriptor neredeyse her zaman dolaylı bir referanstı ve font kurucusu yalnızca doğrudan objeleri okuyordu; bayrak kolunun tamamı hiç çalışmıyordu ve aynı körlük /Widths 12 0 Ri de yok sayıp metni 500 birimlik yedek advance ile diziyordu. v2.766.53 dolaylı referansları renderer üzerinden çözmeye başlayınca bit aynı değişiklikte düzeltilmek zorundaydı; yoksa her small-caps yüzü bir anda bold render olurdu:

const
  // ISO 32000-1 Tablo 123 bit konumlarını 1den sayar
  FD_ITALIC     = $00040;  // bit 7
  FD_SMALLCAP   = $20000;  // bit 18, bir ağırlık değil
  FD_FORCEBOLD  = $40000;  // bit 19

procedure ApplyDescriptorFlags(Flags: Integer; var LF: TLogFont);
begin
  if (Flags and FD_FORCEBOLD) <> 0 then
    LF.lfWeight := FW_BOLD;
  if (Flags and FD_ITALIC) <> 0 then
    LF.lfItalic := 1;
end;
PDF font descriptor /Flags girdisinin bit numaralandırması: ISO 32000-1 Tablo 123 bit 1den sayar; Italic bit 7de $40, SmallCap bit 18de $20000, ForceBold bit 19da $40000dir; HotPDFin descriptor testi $20000yi SmallCape yöneltti ve dolaylı /FontDescriptor referansları çözülmediği sürece zararsız kaldı
Bayrak kolu kırk sürüm boyunca ölü koddu, çünkü descriptor dolaylıydı — referanslar çözülünce bir kaymış bit görünür small-caps metne dönüştü

UCS2 CMapli CJK fontları neden yanlış genişlikler alır?

Önceden tanımlı UCS2 CMapli CJK metni, bir renderer kodu CID olarak işlediğinde doğru glyphleri yanlış aralıklarla çizer, çünkü /W karakter koduna değil CIDe göre indekslenir. STSong-Light ve UniGB-UCS2-H ile kod tesadüfen Unicode değerine eşittir; GDI doğru karakterleri çizer ve hata advancelerde saklanır: küçük harfler 97 ve üzeri kodlar olarak gelir, [1 95 500] gibi bir /W girdisinin dışında kalır ve hepsi 1000lik varsayılan /DWyi alır. v2.766.56dan beri HotPDF rendererı kodları CMap codespace aralıkları üzerinden (ISO 32000-1 §9.7.6.2) okur ve genişliklere bakmadan önce CIDLere eşler. Yalnızca gömülü UCS2 ve UTF16 tabloları ile gömülü CMap streamleri kullanılır; GBK-EUC-H gibi bir şey için kimlik yaklaşıklığı destekleniyor gibi görünürken yanlış çıktı üretirdi, renderer da rol yapmaz

UCS2 CMapli CJK metninin doğru glyphleri yanlış advancelerle çizmesinin sebebi: /W CIDe göre indekslenirken kodlar Unicode değeridir; STSong-Light ile UniGB-UCS2-H altında 97 ve üzeri küçük harf kodları [1 95 500] girdisini ıskalar ve /DW varsayılanını alır; HotPDF bunu kodları CMap codespace aralıklarıyla CIDLere eşleyerek düzeltti
Hata, kodun burada Unicodea eşit olması yüzünden saklanır — glyphler doğru görünürken her advance sessizce varsayılana düşer; o yüzden CJK renderini şekillere değil aralıklara göre yargılayın

Aksanlı karakterler Çince Windowsta neden soru işaretine dönüşür?

Tek baytlık kodlar asla ANSI ("A") GDI fonksiyonlarına ulaşmamalı, çünkü GetGlyphOutlineA ve GetGlyphIndicesA baytları sistem kod sayfasında yorumlarken TextOutA seçili fontun karakter setini kullanır. Çince bir sistemde Arialın $A9 baytı (Windows-1252deki telif işareti) GBK lead baytı oldu ve "?" olarak render edildi; v2.766.83ün eklediği unhinted outline yolu düz içine yürüdü. v2.767.3, gerçekleşen fonta GetTextCharsetle karakter setini soruyor, onu TranslateCharsetInfo ile kod sayfasına çeviriyor, baytı MultiByteToWideChardan geçirip W fonksiyonlarını çağırıyor; symbol fontlar bunun yerine U+F000 artı kodu kullanıyor. Windows-1252yle anlaşamayan encodingler — /Differences, StandardEncoding, MacRomanEncoding — herhangi bir sistem fontu göremeden Unicodea eşlenir

Sistem fontlarıyla çizmenin sınırları nelerdir?

Sistem fontuyla render bir yaklaşımdır ve HotPDF componenti nerede durduğunu dürüstçe söyler. v2.768.18den önce kurulu kontrolü yalnızca GetTextFacein döndürdüğü adla karşılaştırıyordu; yerelleştirilmiş Windowsda o fonksiyon aile adını sistem dilinde bildirir, böylece Çince Windowsda Microsoft YaHei ya da Japonca Windowsda Yu Mincho eksik yargılanıp bir GDI yedeğiyle çiziliyordu; v2.768.18den beri başka adla geri gelen bir yüz de fontun name tablosunda aranır ve bu fontlar bulunur. Metrik uyumluluk yalnızca Helvetica, Times ve Courier aileleri için garanti edilir; Symbol Symbola, ZapfDingbats Wingdingsa eşlenir; bu bir eşleşme değil, geçici bir köprüdür. Yukarıdaki preflight ayrıca yalnızca her sayfanın /Resources sözlüğündeki fontları görür; form XObjectlerin içinden referans verilenleri görmez. Bir kod hâlâ çizilemiyorsa çizim zamanı çözülemeyen glyph takibi bildirir; thumbnail göz kararından iyidir

Kalıcı çözüm yazım tarafındadır. HotPDFin kendisi varsayılan olarak FontEmbedding True ile yazar; kod SetFontu Helvetica ile çağırsa bile gömülü bir Arial yedekler ve gömülü metin, yukarıdaki kumarın hiçbirinden geçmeden gömülü font glyph rendererından gider. Gelen dosyalar için ucuz bir koruma, eşlenen aile ekran fontu listesinde yoksa renderden önce uyarmaktır:

// VCL: Screen.Fonts kurulu aile adlarını listeler (Forms uniti)
function MissingSystemFamily(const BaseFont: AnsiString): Boolean;
begin
  Result := Screen.Fonts.IndexOf(HPDFMapBaseFontToSystem(BaseFont)) < 0;
end;

Sayfa renderi, metin çıkarma ve yazım tarafında font subsetting dahil bütün component için HotPDF Delphi PDF component ürün sayfasına bakın