Technical Article

Saf Delphi ile OpenType GSUB Stilistik Alternatifleri

Bir tasarımcı başlıklar için tek katlı bir a, tablolar için eğik çizgili sıfır veya bir kapak için süslü büyük harfler (swash capitals) içeren bir yazı tipi seçer. Bu glifler zaten yazı tipinde mevcuttur. Sadece varsayılan değillerdir. Varsayılan a, karakterden cmap tablosu aracılığıyla tek bir glife eşlenir ve alternatif, yalnızca bir ikame kuralı (substitution rule) aracılığıyla ulaşılabilen birkaç glif kimliği (glyph ID) uzakta bulunur. Bu alternatifi bir PDF'de üretmek, kuralı okumak ve içerik akışında ikame glifini vermek anlamına gelir. Bu makale, altında yerel bir şekillendirme (shaping) kütüphanesi olmadan Object Pascal'da bu kuralları (tek ikame türü) okumak hakkındadır.

Kapsam bilerek dar tutulmuştur. Stilistik setler ve alternatifler, tek glif girişli, tek glif çıkışlı ikamelerdir. Küçük, belirleyici bir tablo gezintisiyle çözebileceğiniz OpenType düzeninin parçasıdırlar; bu da onları C bağımlılıklarından uzak kalmak isteyen bir Pascal motoru için iyi bir uyum haline getirir.

Neden HarfBuzz yerine saf Delphi

HarfBuzz, "bu metni şekillendir" sorusunun bariz yanıtıdır ve tam çift yönlü (bidirectional), Hint (Indic) veya Arapça şekillendirme için doğru yanıttır. Aynı zamanda bir C kütüphanesidir. Bunu bir Delphi veya C++Builder ürüne bağlamak, her hedef platform ve mimari için yerel bir nesne göndermek, onun çağrı kuralıyla eşleşmek, sürüm temposunu takip etmek ve lisans koşullarını kendi koşullarınızla karşılaştırmak anlamına gelir. Bunların hiçbiri tek başına zor değildir. Hepsi asla kaybolmayan bir sürtünmedir ve asıl gereksinim "bana bu harfin ss01 formunu ver" olduğunda hiçbir şey kazandırmaz.

Tekli ikame (single substitution) bir şekillendirme motoruna ihtiyaç duymaz. Birkaç GSUB alt tablo biçimi için bir ayrıştırıcıya ve bir veya iki ikili aramaya (binary search) ihtiyaç duyar. Bunu Pascal'da yazmak, tüm araç zincirini tek bir derleyici içinde tutar. Dürüst sınır şudur ki, bu yaklaşım glif ikame aramalarını yönetir ve başka hiçbir şeyi yönetmez. Bu, çift yönlü (bidi) çözünürlük değildir, Hint dili yeniden sıralaması değildir ve otomatik bağlamsal şekillendirme değildir. Bunlara ihtiyaç duyulduğunda, ihtiyaç duyulurlar ve tekli ikame sorgusu onların yerini alamaz.

Yukarıdan aşağıya GSUB hiyerarşisi

Glif İkame (Glyph Substitution) tablosu bir dolaylı yönlendirmeler zinciri olarak düzenlenmiştir ve bir ikame sorgusu zinciri en üstten yürütür. En üstte ScriptList bulunur. latn gibi bir yazı sistemi (script) etiketi bir girişi seçer ve özel etiket DFLT, daha spesifik bir yazı sistemi eşleşmediğinde geçerli olan varsayılan yazı sistemidir. Yazı sistemi girişi, ortak durum için varsayılan bir LangSys ve farklı davranış gerektiren diller için isteğe bağlı adlandırılmış olanlar içeren dil sistemine (LangSys) işaret eder. Noktalı ve noktasız i harflerinin kendi işlemlerini gerektirdiği Türkçe buna alışılmış bir örnektir.

LangSys, bir dizi özellik dizinini adlandırır. Her dizin, bir özellik kaydının aralarında ss01'in de bulunduğu dört baytlık bir etiket ve bir arama dizinleri listesi taşıdığı FeatureList'e işaret eder. Bu dizinler nihayetinde gerçek ikame alt tablolarının yaşadığı LookupList'e işaret eder. Dolayısıyla ss01'i çözmek şu anlama gelir: yazı sistemini bul, onun LangSys'ini bul, etiketi ss01 olan özelliği bul, adlandırdığı aramaları topla ve bunları uygula. HotPDF varsayılan olarak Latin metin tasarımlarının büyük çoğunluğunun gönderildiği DFLT yazı sistemine ve varsayılan LangSys'e ayarlanır ve bir yazı tipi özelliklerini bunun yerine belirli bir yazı sistemi altında bağladığında yazı sistemi etiketini geçersiz kılmak için bir yol sunar.

Kapsam tabloları kimin katılacağına karar verir

Her ikame alt tablosu aynı soruyla başlar: bu giriş glifi bu kuralda yer alıyor mu ve eğer öyleyse, kuralın kendi dizininde nerede oturuyor. Bu soru bir Kapsam (Coverage) tablosu tarafından yanıtlanır ve yanıt, alt tablonun geri kalanının glifin neye dönüştüğünü aramak için kullandığı küçük bir sıra numarası olan kapsam dizinidir (coverage index).

Kapsam iki biçimde gelir. Biçim 1, artan düzende sıralanmış glif kimliklerinin (glyph ID) bir listesidir. Bir glifi ikili arama (binary search) ile bulursunuz ve listedeki konumu onun kapsam dizinidir. Biçim 2, her biri bir başlangıç glifi, bir bitiş glifi ve başlangıç glifinin eşlendiği kapsam dizini olan bir aralık kayıtları listesidir. Bir aralık içindeki glif, kapsam dizinini aralığın başlangıcından sapma (offset) yaparak alır. Katılımcı glifler dağınık olduğunda Biçim 1, bitişik diziler halinde düştüklerinde Biçim 2 kompakttır. Her ikisi de sıralıdır, bu nedenle her ikisi de logaritmik zamanda aranır ve her ikisi de bir kapsam dizini veya motorun glife dokunmamasını sağlayan temiz bir "kapsanmadı" döndürür。

Tekli İkame, iki biçim

Tekli İkame (Single Substitution) LookupType 1'dir ve bir glifi tam olarak bir yedekle eşler. Ayrıca iki biçimi vardır ve bu bölünme bir alan optimizasyonudur. Biçim 1, tek bir işaretli delta depolar. Çıkış glif kimliği, giriş glif kimliği artı o deltanın modulo 65536'sıdır. Bu, bir yazı tipinin, katılan her glifin kendi alternatifinden aynı sabit sapmada oturduğu bir ikameyi kodlama şeklidir; örneğin, hizalama rakamları bloğunun eşleşen eski stil rakamlarından sabit bir mesafede yer alması gibi. Kapsam tablosu hangi gliflerin uygun olduğunu söyler ve tek delta hepsine hizmet eder.

Biçim 2, yedek glif kimliklerinin açık bir dizisini depolar. Kapsam tablosundan gelen kapsam dizini, bu dizinin dizinidir; dolayısıyla kapsam dizini 0 olan glif ilk dizi girişi, kapsam dizini 1 olan ikinci dizi girişi olur ve bu şekilde devam eder. Biçim 2, alternatifler tekdüze bir sapmada olmadığında kullanılır ki bu, elle oluşturulmuş stilistik setler için yaygın durumdur. Sorgu, her iki durumda da arayanın tarafında aynıdır. Giriş glifini alın, Kapsam üzerinden çalıştırın ve kapsanıyorsa deltayı uygulayın veya dizi yuvasını okuyun.

var
  Pdf: THotPDF;
  BaseGID, AltGID: Word;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.BeginDoc;
    Pdf.RegisterUnicodeTTF('C:\Fonts\MyStylisticFace.ttf');
    Pdf.SetFont('My Stylistic Face', 12, []);

    // Default glyph for 'a' through the font's cmap.
    BaseGID := Pdf.GetUnicodeGlyphForCodepoint(Ord('a'));

    // Stylistic Set 1: resolve the alternate via GSUB LookupType 1.
    AltGID := Pdf.GetSingleSubstituteGlyph(BaseGID, 'ss01');

    // AltGID = BaseGID means the feature did not touch this glyph.
    if AltGID <> BaseGID then
      { emit AltGID in the content stream };
  finally
    Pdf.Free;
  end;
end;

Dikkat edilmesi gereken sözleşme doğrudan geçiştir. GetSingleSubstituteGlyph her başarısızlıkta giriş glif kimliğini değiştirmeden döndürür: yazı tipi yok, GSUB tablosu yok, eşleşen özellik yok, kapsam isabeti yok. Bu, çağrının koşulsuz olarak yapılmasının güvenli olduğu anlamına gelir. Alternatifi istersiniz ve eğer yoksa, tam olarak koyduğunuz şeyi geri alırsınız, böylece çağıran kodun bu özellikten yoksun bir yazı tipini özel olarak ele alması gerekmez。

Stilistik özellik etiketlerinin anlamı

Özellik etiketi, hangi alternatifi istediğinizin tüm kelime dağarcığıdır ve stilistik çalışmayla ilgili etiketler kısa bir listedir. Ana ikili, bir glifin alternatif formlarına genel erişim sağlayan stilistik alternatifler olan salt ve bir yazı tipinin tanımlayabileceği, her biri tasarımcının bir araya getirdiği adlandırılmış bir ikameler paketi olan ss01 ile ss20 arasındaki yirmi numaralı stilistik settir. Bir yazı tipi, örneğin ss03 altına tek katlı bir a ve düz bacaklı bir R koyabilir, böylece bu tek setin etkinleştirilmesi her ikisini de yeniden stillendirir。

Bunların etrafında birkaç tane daha tekli ikame etiketi bulunur. aalt, bir glifin sahip olduğu her alternatifin birleşimi olan tüm alternatiflere erişimdir (access-all-alternates) ve genellikle bir glif paleti özelliği olarak sunulur. titl büyük boyutlar için kesilmiş başlık büyük harlerini seçer. subs ve sups ölçeklendirilmiş varsayılanlar yerine gerçek alt simge (subscript) ve üst simge (superscript) rakamlarını değiştirir. ordn sıra formlarını üretir, örneğin 1st ve 2nd'deki yükseltilmiş harfler. frac kesirleri oluşturur, ancak tam eğik kesirler aynı zamanda düz tekli ikamenin ötesine geçen ligatür ve bağlamsal mantığa da dayanır. Tek glif durumları için mekanizma ss01 ile aynıdır: etiketi ikame sorgusuna iletin ve alternatif glifi geri okuyun。

// Try a stylistic-set feature, then fall back to plain alternates.
function ResolveAlternate(Pdf: THotPDF; BaseGID: Word;
  const PreferredTag: AnsiString): Word;
begin
  Result := Pdf.GetSingleSubstituteGlyph(BaseGID, PreferredTag);
  if Result = BaseGID then
    Result := Pdf.GetSingleSubstituteGlyph(BaseGID, 'salt');
  // Still BaseGID if neither feature covers this glyph.
end;

cmap biçim 12 ve ek düzlemler

Herhangi bir ikame çalışmadan önce, bir karakterin bir glif haline gelmesi gerekir ve bu, cmap tablosunun işidir. İkame sorgusu bir glif kimliğinden (ID) başlar, dolayısıyla yol her zaman cmap aracılığıyla karakterden glife, ardından GSUB aracılığıyla gliften alternatife şeklindedir. cmap tablosunun ilginç kısmı erişimidir. Biçim 4 alt tablosu Temel Çok Dilli Düzlemi (Basic Multilingual Plane - BMP), yani ilk 65536 kod noktasını kapsar ve bu, çoğu Latin metni için yeterlidir. U+10000 ve yukarısındaki kod noktaları, yani matematiksel alfanümeriklerin, many symbols, and several living scripts now live.

Biçim 12, tüm U+0000 ila U+10FFFF aralığını kapsayan alt tablodur. Grupların sıralı bir listesidir, her grup bir başlangıç kod noktası, bir bitiş kod noktası ve bir başlangıç glif kimliğidir, böylece bitişik bir kod noktası dizisi bitişik bir glif dizisine eşlenir. HotPDF, verilerin nasıl şekillendirildiğine uyan hibrit bir stratejiyle kod noktalarını çözer. BMP'deki kod noktaları, arama yapmadan tek bir aramayla kod noktasına göre dizinlenmiş doğrudan bir diziden sunulur. Ek düzlemlerdeki kod noktaları, kod noktasına göre sıralanmış ve ikili arama ile aranan seyrek bir tablodan sunulur. Sonuç olarak, GetUnicodeGlyphForCodepoint tam bir Cardinal alır ve tüm aralık boyunca doğru yanıt verir, yazı tipinin eşlemediği herhangi bir kod noktası için glif kimliği 0 olan .notdef glifini döndürür。

var
  Pdf: THotPDF;
  Cp: Cardinal;
  GID, StyledGID: Word;
begin
  // A supplementary-plane code point: U+1D49C MATHEMATICAL SCRIPT CAPITAL A.
  Cp := $1D49C;
  GID := Pdf.GetUnicodeGlyphForCodepoint(Cp);  // format 12 lookup
  if GID <> 0 then
    StyledGID := Pdf.GetSingleSubstituteGlyph(GID, 'ss01')
  else
    StyledGID := 0;  // font has no glyph for this code point
end;

Bu sorguların durduğu yerler

Tekli ikame API'leri tek bir soru biçimine yanıt verir ve neye yanıt vermediklerini netleştirmek önemlidir. LookupType 1, sekiz ikame tipinden biridir. Sorgu, bir glifin birkaç glife dönüştüğü LookupType 2 çoklu ikameyi veya birkaç glifin tek bir glife dönüştüğü LookupType 4 ligatür ikamesini işlemez. Bir glif yalnızca belirli bir mahallede göründüğünde tetiklenen bağlamsal ve zincirleme bağlamsal tipler olan LookupTypes 5 ve 6'yı, uzantı ve ters zincirleme tiplerini işlemez. Eğik bir kesir, bir Devanagari birleşimi veya bir Arapça başlangıç-orta-son basamağı bir sıra problemidir ve glif başına tekli ikame araması bunu ifade edemez。

Ayrıca otomatik şekillendirme yapmaz. Buradaki hiçbir şey bir metin dizisini incelemez, hangi özelliklerin açılacağına karar vermez ve bunları yazı sisteminin gerektirdiği sırada uygulamaz. Arayan özellik etiketini seçer ve bunu glif glif uygular. Bu, isteğe bağlı ve yerel olan stilistik setler ve alternatifler için tam olarak doğru araçtır ve yeniden sıralama gerektiren bir yazı sistemi için tam olarak yanlış araçtır. Sınırı keskin tutmak, ikame yolunun küçük ve öngörülebilir kalmasını sağlayan şeydir。

Sıra düzeyinde çalışma gerektiren durumlar için, karmaşık yazı hikayesi Delphi'de karmaşık yazı metni şekillendirme hakkındaki makalemizde ele alınmaktadır. İkameleriniz, sayfaya resimler ve diğer yazı tiplerini de yerleştiren daha büyük bir raporlama işinin parçasıysa, yazı tipleri ve resimlerle rapor çıktısı kılavuzu bu parçaların nasıl bir araya geldiğini kapsar. Bunların tümü, bu blogun başka yerlerinde ele alınan yazı tipi gömme, alt kümeleme ve metin API'lerinin yanı sıra GSUB ikame sorgularını taşıyan Delphi ve C++Builder için HotPDF Bileşeni ile aynı motor üzerinde çalışır。