Seorang desainer memilih font dengan huruf a single-story untuk judul, atau nol dengan garis miring untuk tabel, atau satu set huruf kapital swash untuk sampul. Glyph-glyph tersebut sudah ada di dalam font. Mereka hanya bukan yang default. Huruf a default memetakan dari karakter melalui tabel cmap ke satu glyph, dan alternatifnya duduk beberapa id glyph jauhnya, hanya dapat dijangkau melalui aturan substitusi. Menghasilkan alternatif tersebut dalam PDF berarti membaca aturan dan memancarkan glyph pengganti dalam content stream. Artikel ini membahas cara membaca aturan tersebut, jenis single-substitution, dalam Object Pascal tanpa library shaping native di bawahnya
Cakupannya sengaja dibuat sempit. Stylistic set dan alternatif adalah substitusi single-glyph-in, single-glyph-out. Ini adalah bagian dari tata letak OpenType yang dapat diselesaikan dengan table walk Pascal kecil yang deterministik, yang menjadikannya cocok untuk engine Pascal yang ingin tetap bebas dari dependensi C
Mengapa Delphi murni daripada HarfBuzz
HarfBuzz adalah jawaban yang jelas untuk "shape teks ini", dan untuk shaping bidireksional penuh, Indic, atau Arab itu adalah jawaban yang tepat. Ini juga merupakan library C. Mengikatnya ke dalam produk Delphi atau C++Builder berarti mengirimkan objek native untuk setiap platform dan arsitektur target, mencocokkan calling convention-nya, melacak kadensnya, dan membaca ketentuan lisensinya terhadap ketentuan Anda sendiri. Tidak ada yang sulit secara individual. Semuanya adalah gesekan yang tidak pernah hilang, dan tidak membeli apa pun ketika kebutuhan sebenarnya adalah "berikan saya bentuk ss01 dari huruf ini"
Single substitution tidak membutuhkan shaping engine. Yang dibutuhkan adalah parser untuk beberapa format subtabel GSUB dan satu atau dua binary search. Menulis itu dalam Pascal menjaga seluruh toolchain di dalam satu compiler. Batas yang jujur adalah bahwa pendekatan ini hanya menangani lookup substitusi glyph dan tidak ada yang lain. Ini bukan resolusi bidi, bukan reordering Indic, dan bukan shaping kontekstual otomatis. Ketika hal-hal tersebut dibutuhkan, mereka dibutuhkan, dan query single-substitution tidak dapat menggantikannya
Hierarki GSUB dari atas ke bawah
Tabel Glyph Substitution diorganisasi sebagai rantai indirection, dan query substitusi berjalan dari atas. Di atas adalah ScriptList. Tag script seperti latn memilih entri, dan tag khusus DFLT adalah script default yang berlaku ketika tidak ada script yang lebih spesifik cocok. Entri script menunjuk ke LangSys, sistem bahasa, dengan LangSys default untuk kasus umum dan yang bernama opsional untuk bahasa yang membutuhkan perilaku berbeda. Bahasa Turki adalah contoh yang biasa, di mana i bertitik dan tidak bertitik menuntut penanganan sendiri
LangSys menamai sekumpulan indeks fitur. Setiap indeks menunjuk ke FeatureList, di mana rekaman fitur membawa tag empat byte, ss01 di antaranya, dan daftar indeks lookup. Indeks-indeks tersebut akhirnya menunjuk ke LookupList, di mana subtabel substitusi sebenarnya berada. Jadi menyelesaikan ss01 berarti: temukan script, temukan LangSys-nya, temukan fitur yang tagnya adalah ss01, kumpulkan lookup yang disebutkannya, dan terapkan. HotPDF default ke script DFLT dan LangSys default, yang dikirimkan oleh sebagian besar desain teks Latin, dan mengekspos cara untuk menimpa tag script ketika font menyambungkan fiturnya di bawah script tertentu
Tabel Coverage menentukan siapa yang berpartisipasi
Setiap subtabel substitusi dimulai dengan pertanyaan yang sama: apakah glyph input ini berpartisipasi dalam aturan ini, dan jika ya, di mana posisinya dalam pengindeksan aturan itu sendiri. Pertanyaan itu dijawab oleh tabel Coverage, dan jawabannya adalah coverage index, ordinal kecil yang digunakan oleh sisa subtabel untuk mencari tahu apa yang menjadi glyph tersebut
Coverage hadir dalam dua format. Format 1 adalah daftar id glyph yang diurutkan secara ascending. Anda menemukan glyph dengan binary search, dan posisinya dalam daftar adalah coverage index-nya. Format 2 adalah daftar rekaman rentang, masing-masing adalah glyph awal, glyph akhir, dan coverage index yang dipetakan oleh glyph awal. Glyph di dalam rentang mendapatkan coverage index-nya dengan menggeser dari awal rentang. Format 1 ringkas ketika glyph yang berpartisipasi tersebar, Format 2 ketika mereka jatuh dalam run yang berurutan. Keduanya diurutkan, sehingga keduanya dicari dalam waktu logaritmik, dan keduanya mengembalikan coverage index atau "not covered" yang bersih yang memungkinkan engine membiarkan glyph tetap utuh
Single Substitution, dua format
Single Substitution adalah LookupType 1, dan memetakan satu glyph ke tepat satu pengganti. Ini juga memiliki dua format, dan pemisahannya adalah optimasi ruang. Format 1 menyimpan satu delta bertanda. Id glyph output adalah id glyph input ditambah delta tersebut, modulo 65536. Beginilah cara font mengkodekan substitusi di mana setiap glyph yang berpartisipasi duduk dengan offset tetap yang sama dari alternatifnya, misalnya blok angka lining yang ditempatkan pada jarak konstan dari angka oldstyle yang cocok. Tabel Coverage mengatakan glyph mana yang memenuhi syarat, dan satu delta melayani semuanya
Format 2 menyimpan array eksplisit dari id glyph pengganti. Coverage index dari tabel Coverage adalah indeks ke dalam array tersebut, sehingga glyph pada coverage index 0 menjadi entri array pertama, coverage index 1 menjadi yang kedua, dan seterusnya. Format 2 digunakan ketika alternatifnya tidak berada pada offset seragam, yang merupakan kasus umum untuk stylistic set yang dibuat tangan. Query-nya sama dari sisi pemanggil. Ambil glyph input, jalankan melalui Coverage, dan jika tercakup, terapkan delta atau baca slot array
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;
Kontrak yang perlu diperhatikan adalah pass-through. GetSingleSubstituteGlyph mengembalikan id glyph input tidak berubah pada setiap miss: tidak ada font, tidak ada tabel GSUB, tidak ada fitur yang cocok, tidak ada coverage hit. Itu berarti panggilan tersebut aman dilakukan tanpa syarat. Anda meminta alternatif, dan jika tidak ada, Anda mendapatkan kembali persis apa yang Anda masukkan, sehingga kode pemanggil tidak pernah perlu membuat special-case untuk font yang tidak memiliki fitur tersebut
Arti tag fitur stylistic
Tag fitur adalah seluruh kosakata alternatif mana yang Anda minta, dan tag yang relevan untuk pekerjaan stylistic adalah daftar pendek. Pasangan utama adalah salt, stylistic alternates, akses catch-all ke bentuk alternatif glyph, dan ss01 hingga ss20, dua puluh stylistic set bernomor yang dapat didefinisikan font, masing-masing merupakan bundel substitusi bernama yang dikelompokkan bersama oleh desainer. Font mungkin menempatkan a single-story dan R dengan kaki lurus di bawah ss03, misalnya, sehingga mengaktifkan satu set tersebut merestyling keduanya
Di sekitar itu duduk beberapa tag single-substitution lagi. aalt adalah access-all-alternates, union dari setiap alternatif yang dimiliki glyph, biasanya disajikan sebagai fitur glyph-palette. titl memilih huruf kapital titling yang dipotong untuk ukuran besar. subs dan sups menukar angka subscript dan superscript sejati daripada default yang diperkecil. ordn menghasilkan bentuk ordinal, huruf yang dinaikkan dalam 1st dan 2nd. frac membangun pecahan, meskipun pecahan diagonal penuh juga bergantung pada logika ligatura dan kontekstual yang melampaui substitusi single biasa. Untuk kasus single-glyph, mekanismenya identik dengan ss01: pass tag ke query substitusi dan baca kembali glyph alternatif
// 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 format 12 dan bidang supplementary
Sebelum substitusi apa pun dapat berjalan, karakter harus menjadi glyph, dan itu adalah pekerjaan tabel cmap. Query substitusi dimulai dari id glyph, sehingga jalurnya selalu karakter ke glyph melalui cmap, kemudian glyph ke alternatif melalui GSUB. Bagian menarik dari cmap adalah jangkauannya. Subtabel format 4 mencakup Basic Multilingual Plane, 65536 code point pertama, dan itu cukup untuk sebagian besar teks Latin. Ini tidak cukup untuk code point dari U+10000 ke atas, bidang supplementary, yang merupakan tempat alfanumerik matematis, banyak simbol, dan beberapa skrip yang masih digunakan sekarang berada
Format 12 adalah subtabel yang mencakup seluruh rentang U+0000 hingga U+10FFFF. Ini adalah daftar grup yang diurutkan, setiap grup adalah code point awal, code point akhir, dan id glyph awal, sehingga run code point yang berurutan memetakan ke run glyph yang berurutan. HotPDF menyelesaikan code point dengan strategi hybrid yang cocok dengan cara data dibentuk. Code point dalam BMP disajikan dari array langsung yang diindeks oleh code point, satu lookup tanpa pencarian. Code point dalam bidang supplementary disajikan dari tabel jarang yang diurutkan berdasarkan code point dan dicari dengan binary search. Hasilnya adalah GetUnicodeGlyphForCodepoint mengambil Cardinal penuh dan menjawab dengan benar di seluruh rentang, mengembalikan id glyph 0, glyph .notdef, untuk setiap code point yang tidak dipetakan font
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;
Di mana query ini berhenti
API single-substitution menjawab satu bentuk pertanyaan, dan penting untuk memperjelas apa yang tidak mereka jawab. LookupType 1 adalah satu dari delapan jenis substitusi. Query tidak menangani LookupType 2 multiple substitution, di mana satu glyph menjadi beberapa, atau LookupType 4 ligature substitution, di mana beberapa glyph menjadi satu. Ini tidak menangani jenis kontekstual dan chaining-contextual, LookupTypes 5 dan 6, yang hanya aktif ketika glyph muncul dalam konteks tertentu, atau jenis extension dan reverse-chaining. Pecahan diagonal, conjunct Devanagari, atau kaskade initial-medial-final Arab adalah masalah sequence, dan lookup single-substitution per-glyph tidak dapat mengekspresikannya
Ini juga tidak melakukan shaping otomatis. Tidak ada di sini yang memeriksa serangkaian teks, memutuskan fitur mana yang harus diaktifkan, dan menerapkannya dalam urutan yang diperlukan script. Pemanggil memilih tag fitur dan menerapkannya glyph demi glyph. Itu adalah alat yang tepat untuk stylistic set dan alternatif, yang bersifat opt-in dan lokal, dan alat yang salah untuk script yang membutuhkan reordering. Menjaga batas ini tetap tajam adalah apa yang memungkinkan jalur substitusi tetap kecil dan dapat diprediksi
Untuk kasus yang membutuhkan pekerjaan tingkat sequence, kisah complex-script dibahas dalam artikel kami tentang complex-script text shaping di Delphi. Jika substitusi Anda adalah bagian dari pekerjaan pelaporan yang lebih besar yang juga menempatkan gambar dan font lain di halaman, panduan untuk report output dengan font dan gambar mencakup cara potongan-potongan tersebut cocok bersama. Semua ini berjalan di engine yang sama, HotPDF Component untuk Delphi dan C++Builder, yang membawa query substitusi GSUB bersama embedding font, subsetting, dan API teks yang dibahas di tempat lain di blog ini