Bagikan melalui


Pilihan font

Antarmuka IDWriteFontSet4 mengekspos metode untuk memilih font dari kumpulan font. Metode tersebut memungkinkan transisi ke model keluarga font tipografi sambil mempertahankan kompatibilitas dengan aplikasi, dokumen, dan font yang ada.

Pemilihan font (terkadang disebut pencocokan font atau pemetaan font) adalah proses memilih font yang tersedia yang paling cocok dengan parameter input yang diteruskan oleh aplikasi Anda. Parameter input terkadang disebut secara kolektif sebagai font logis . Font logis menyertakan nama keluarga font ditambah atribut lain yang menunjukkan font tertentu dalam keluarga. Algoritma pilihan font cocok dengan font logis ("font yang Anda inginkan") dengan font fisik yang tersedia ("font yang Anda miliki").

Keluarga font adalah sekelompok font bernama yang memiliki desain umum, tetapi mungkin berbeda dalam atribut seperti berat. Model keluarga font menentukan atribut apa yang dapat digunakan untuk membedakan font dalam keluarga. Model keluarga font tipografi baru memiliki banyak keuntungan dibandingkan dua model keluarga font sebelumnya yang digunakan pada Windows. Tetapi mengubah model keluarga font menciptakan peluang untuk kebingungan, dan masalah kompatibilitas. Metode yang diekspos oleh antarmukaIDWriteFontSet4menerapkan pendekatan hibrid yang menawarkan keuntungan dari model keluarga font tipografis sambil mengurangi masalah kompatibilitas.

Topik ini membandingkan model keluarga font yang lebih lama dengan model keluarga font tipografis; ini menjelaskan tantangan kompatibilitas yang ditimbulkan dengan mengubah model keluarga font; dan akhirnya menjelaskan bagaimana tantangan tersebut dapat diatasi dengan menggunakan metode [IDWriteFontSet4](/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontset4).

Model keluarga font RBIZ

Model keluarga font de facto yang digunakan dalam ekosistem aplikasi GDI terkadang disebut model "model empat font" atau "RBIZ". Setiap keluarga font dalam model ini biasanya memiliki paling banyak empat font. Label "RBIZ" berasal dari konvensi penamaan yang digunakan untuk beberapa file font, misalnya:

Nama File Gaya Font
verdana.ttf Biasa
verdanab.ttf Berani
verdanai.ttf Miring
verdanaz.ttf Miring Tebal

Dengan GDI, parameter input yang digunakan untuk memilih font ditentukan oleh strukturLOGFONT, yang mencakup nama keluarga (lfFaceName), berat (lfWeight) dan bidang miring (lfItalic). Bidang lfItalic adalah TRUE atau FALSE. GDI memungkinkan bidang lfWeight menjadi nilai apa pun dalam rentang FW_THIN (100) untuk FW_BLACK (900), tetapi karena alasan historis font telah lama dirancang sehingga tidak ada lebih dari dua bobot dalam keluarga font GDI yang sama.

Antarmuka pengguna aplikasi populer dari awal menyertakan tombol miring (untuk menyalakan dan mematikan miring) dan tombol tebal (untuk beralih antara bobot normal dan tebal). Penggunaan kedua tombol ini untuk memilih font dalam keluarga mengasumsikan model RBIZ. Oleh karena itu, meskipun GDI sendiri mendukung lebih dari dua bobot, kompatibilitas aplikasi memimpin pengembang font untuk mengatur nama keluarga GDI (nama OpenType ID 1) dengan cara yang konsisten dengan model RBIZ.

Misalnya, Anda ingin menambahkan bobot "Hitam" yang lebih berat ke keluarga font Arial. Secara logis, font ini adalah bagian dari keluarga Arial, jadi Anda mungkin berharap untuk memilihnya dengan mengatur lfFaceName ke "Arial" dan lfWeight ke FW_BLACK. Namun, tidak ada cara bagi pengguna aplikasi untuk memilih antara tiga bobot menggunakan tombol tebal dua status. Solusinya adalah memberi font baru nama keluarga yang berbeda, sehingga pengguna dapat memilihnya dengan memilih "Arial Black" dari daftar keluarga font. Demikian juga, tidak ada cara untuk memilih dari antara lebar yang berbeda dalam keluarga font yang sama hanya menggunakan tombol tebal dan miring, sehingga versi sempit Arial memiliki nama keluarga yang berbeda dalam model RBIZ. Dengan demikian kita memiliki keluarga font "Arial", "Arial Black", dan "Arial Narrow" dalam model RBIZ, meskipun secara tipografis semuanya termasuk dalam satu keluarga.

Dari contoh-contoh ini, seseorang dapat melihat bagaimana batasan model keluarga font dapat memengaruhi bagaimana font dikelompokkan ke dalam keluarga. Karena keluarga font diidentifikasi berdasarkan nama, ini berarti font yang sama dapat memiliki nama keluarga yang berbeda tergantung model keluarga font mana yang Anda gunakan.

DirectWrite tidak secara langsung mendukung model keluarga font RBIZ, tetapi menyediakan metode konversi ke dan dari model RBIZ, seperti IDWriteGdiInterop::CreateFontFromLOGFONT dan IDWriteGdiInterop::ConvertFontToLOGFONT. Anda juga bisa mendapatkan nama keluarga RBIZ dari font dengan memanggil metode IDWriteFont::GetInformationalStrings, dan menentukan DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES.

Model keluarga font gaya peregangan berat

Model keluarga font gaya peregangan berat adalah model keluarga font asli yang digunakan oleh DirectWrite sebelum model keluarga font tipografi diperkenalkan. Ini juga dikenal sebagai kelereng lebar berat (WWS). Dalam model WWS, font dalam keluarga yang sama dapat ditulikan oleh tiga properti: berat (DWRITE_FONT_WEIGHT), stretch (DWRITE_FONT_STRETCH), dan gaya (DWRITE_FONT_STYLE).

Model WWS lebih fleksibel daripada model RBIZ dengan dua cara. Pertama, font dalam keluarga yang sama dapat dibingkai oleh peregangan (atau lebar) serta berat dan gaya (reguler, miring, atau miring). Kedua, bisa ada lebih dari dua berat dalam keluarga yang sama. Fleksibilitas ini cukup untuk memungkinkan semua varian Arial dimasukkan dalam keluarga WWS yang sama. Tabel berikut membandingkan properti font RBIZ dan WWS untuk pilihan font Arial:

Nama Lengkap Nama Keluarga RBIZ lfWeight lfItalic Nama Keluarga WWS Berat Merentangkan Gaya
Arial Arial 400 0 Arial 400 5 0
Arial Bold Arial tujuh ratus 0 Arial tujuh ratus 5 0
Arial Black Arial Black 900 0 Arial 900 5 0
Arial Narrow Arial Narrow 400 0 Arial 400 3 0
Arial Narrow Bold Arial Narrow tujuh ratus 0 Arial tujuh ratus 3 0

Seperti yang Anda lihat, "Arial Narrow" memiliki nilai lfWeight dan lfItalic yang sama dengan "Arial", sehingga memiliki nama keluarga RBIZ yang berbeda untuk menghindari ambiguitas. "Arial Black" memiliki nama keluarga RBIZ yang berbeda untuk menghindari memiliki lebih dari dua berat dalam keluarga "Arial". Sebaliknya, semua font ini berada dalam keluarga gaya peregangan berat yang sama.

Namun demikian, model gaya peregangan berat tidak terbuka. Jika dua font memiliki berat, regangan, dan gaya yang sama tetapi berbeda dengan cara lain (misalnya, ukuran optik), maka font tersebut tidak dapat disertakan dalam keluarga font WWS yang sama. Ini membawa kita ke model keluarga font tipografi.

Model keluarga font tipografi

Tidak seperti pendahulunya, model keluarga font tipografi terbuka. Ini mendukung sejumlah sumbu variasi dalam keluarga font.

Jika Anda menganggap parameter pemilihan font sebagai koordinat dalam ruang desain, model gaya peregangan berat mendefinisikan sistem koordinat tiga dimensi dengan berat, bentang, dan gaya sebagai sumbu. Setiap font dalam keluarga WWS harus memiliki lokasi unik yang ditentukan oleh koordinatnya di sepanjang tiga sumbu tersebut. Untuk memilih font, Anda menentukan nama dan berat keluarga WWS, stretch, dan parameter gaya.

Sebaliknya, model keluarga font tipografis memiliki ruang desain N-dimensi. Perancang font dapat menentukan sejumlah sumbu desain, masing-masing diidentifikasi oleh tag sumbu empat karakter. Lokasi font tertentu di ruang desain N-dimensi ditentukan oleh array nilai sumbu , di mana setiap nilai sumbu terdiri dari tag sumbu dan nilai titik mengambang. Untuk memilih font, Anda menentukan nama keluarga tipografi dan array nilai sumbu ( strukturDWRITE_FONT_AXIS_VALUE).

Meskipun jumlah sumbu font terbuka, ada beberapa sumbu terdaftar dengan arti standar, dan nilai berat, peregangan, dan gaya dapat dipetakan ke nilai sumbu terdaftar. DWRITE_FONT_WEIGHT dapat dipetakan ke nilai sumbu "wght" (DWRITE_FONT_AXIS_TAG_WEIGHT). DWRITE_FONT_STRETCH dapat dipetakan ke nilai sumbu "wdth" (DWRITE_FONT_AXIS_TAG_WIDTH). DWRITE_FONT_STYLE dapat dipetakan ke kombinasi nilai sumbu "ital" dan "slnt" (DWRITE_FONT_AXIS_TAG_ITALIC dan DWRITE_FONT_AXIS_TAG_SLANT).

Sumbu terdaftar lainnya adalah "opsz" (DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE). Keluarga font optik seperti Sitka mencakup font yang berbeda di sepanjang sumbu "opsz", yang berarti mereka dirancang untuk digunakan pada ukuran titik yang berbeda. Model keluarga font WWS tidak memiliki sumbu ukuran optik, sehingga keluarga font Sitka harus dibagi menjadi beberapa keluarga font WWS: "Sitka Small", "Sitka Text", "Sitka Subheading", dan sebagainya. Setiap keluarga font WWS sesuai dengan ukuran optik yang berbeda, dan dibiarkan kepada pengguna untuk menentukan nama keluarga WWS kanan untuk ukuran font tertentu. Dengan model keluarga font tipografis, pengguna cukup memilih "Sitka", dan aplikasi dapat secara otomatis mengatur nilai sumbu "opsz" berdasarkan ukuran font.

Pemilihan font tipografi dan font variabel

Konsep sumbu variasi sering dikaitkan dengan font variabel, tetapi juga berlaku untuk font statis. Tabel OPENType STAT (atribut gaya) mendeklarasikan sumbu desain apa yang dimiliki font dan nilai sumbu tersebut. Tabel ini diperlukan untuk font variabel tetapi juga relevan dengan font statis.

API DirectWrite mengekspos nilai sumbu "wght", "wdth", "ital", dan "slnt" untuk setiap font, bahkan jika tidak ada dalam tabel STAT atau jika tidak ada tabel STAT. Nilai-nilai ini berasal dari tabel STAT jika memungkinkan. Jika tidak, mereka berasal dari bobot font, peregangan font, dan gaya font.

Sumbu font mungkin variabel atau non-variabel. Font statis hanya memiliki sumbu non-variabel, sedangkan font variabel mungkin memiliki keduanya. Untuk menggunakan font variabel, Anda harus membuat font variabel instans di mana semua sumbu variabel telah terikat ke nilai tertentu. AntarmukaIDWriteFontFacemewakili font statis atau instans tertentu font variabel. Dimungkinkan untuk membuat instans arbitrer font variabel dengan nilai sumbu yang ditentukan. Selain itu, font variabel dapat mendeklarasikan instans bernama dalam tabel STAT dengan kombinasi nilai sumbu yang telah ditentukan sebelumnya. Instans bernama memungkinkan font variabel berulah seperti kumpulan font statis. Saat Anda menghitung elemen IDWriteFontFamily atau IDWriteFontSet, ada satu elemen untuk setiap font statis dan untuk setiap instans font variabel bernama.

Algoritma pencocokan font tipografi terlebih dahulu memilih kandidat kecocokan potensial berdasarkan nama keluarga. Jika kandidat kecocokan menyertakan font variabel, semua kandidat yang cocok untuk font variabel yang sama diciutkan ke dalam satu kandidat kecocokan di mana setiap sumbu variabel diberi nilai tertentu sedekat mungkin dengan nilai yang diminta untuk sumbu tersebut. Jika tidak ada nilai yang diminta untuk sumbu variabel, nilai default untuk sumbu tersebut ditetapkan. Urutan kandidat kecocokan kemudian ditentukan dengan membandingkan nilai sumbunya dengan nilai sumbu yang diminta.

Misalnya, pertimbangkan keluarga tipografi Sitka di Windows. Sitka adalah keluarga font optik, yang berarti memiliki sumbu "opsz". Di Windows 11, Sitka diimplementasikan sebagai dua font variabel dengan nilai sumbu berikut. Perhatikan bahwa sumbu opsz dan wght bersifat variabel, sementara sumbu lainnya bukan variabel.

Nama File "opsz" "wght" "wdth" "ital" "slnt"
SitkaVF.ttf 6-27.5 400-700 100 0 0
SitkaVF-Italic.ttf 6-27.5 400-700 100 1 -12

Misalkan nilai sumbu yang diminta opsz:12 wght:475 wdth:100 ital:0 slnt:0. Untuk setiap font variabel, kami membuat referensi ke font variabel instans di mana setiap sumbu variabel diberi nilai tertentu. Yaitu, sumbu opsz dan wght diatur ke 12 dan 475, masing-masing. Ini menghasilkan font yang cocok berikut, dengan font non-miring peringkat pertama karena lebih cocok untuk sumbu ital dan slnt:

SitkaVF.ttf opsz:12 wght:475 wdth:100 ital:0 slnt0
SitkaVF-Italic.ttf opsz:12 wght:475 wdth:100 ital:1 slnt:-12

Dalam contoh di atas, font yang cocok adalah instans font variabel arbitrer. Tidak ada instans bernama Sitka dengan berat 475. Sebaliknya, algoritma pencocokan gaya peregangan berat hanya mengembalikan instans bernama.

Urutan pencocokan font

Ada berbagai metode GetMatchingFonts yang berbeda untuk model keluarga font gaya peregangan berat (IDWriteFontFamily::GetMatchingFonts) dan model keluarga font tipografi (IDWriteFontCollection2::GetMatchingFonts). Dalam kedua kasus, output adalah daftar font yang cocok dalam urutan prioritas turun berdasarkan seberapa baik setiap font kandidat cocok dengan properti input. Bagian ini menjelaskan bagaimana prioritas ditentukan.

Dalam model gaya peregangan berat, parameter input adalah bobot font (DWRITE_FONT_WEIGHT), rentang font (DWRITE_FONT_STRETCH), dan gaya font (DWRITE_FONT_STYLE). Algoritma untuk menemukan kecocokan terbaik didokumenkan dalam laporan resmi tahun 2006 berjudul "WPF Font Selection Model" oleh Mikhail Leonov dan David Brown. Lihat bagian "Mencocokkan wajah dari daftar wajah kandidat." Makalah ini adalah tentang Windows Presentation Foundation (WPF), tetapi DirectWrite kemudian menggunakan pendekatan yang sama.

Algoritma menggunakan gagasan vektor atribut font , yang untuk kombinasi berat, peregangan, dan gaya tertentu dihitung sebagai berikut:

FontAttributeVector.X = (stretch - 5) * 1100;
FontAttributeVector.Y = style * 700;
FontAttributeVector.Z = (weight - 400) * 5;

Perhatikan bahwa setiap koordinat vektor dinormalisasi dengan mengurangi nilai "normal" untuk atribut yang sesuai, dan dikalikan dengan konstanta. Pengali mengkompensasi fakta bahwa rentang nilai input untuk berat, peregangan, dan gaya sangat berbeda. Jika tidak, berat (100..999) akan mendominasi atas gaya (0,.2).

Untuk setiap kandidat kecocokan, jarak vektor dan produk titik dihitung antara vektor atribut font kandidat yang cocok dan vektor atribut font input. Ketika membandingkan dua kandidat yang cocok, kandidat dengan jarak vektor yang lebih kecil adalah kecocokan yang lebih baik. Jika jaraknya sama, kandidat dengan produk titik yang lebih kecil adalah kecocokan yang lebih baik. Jika produk titik juga sama, jarak di sepanjang sumbu X, Y, dan Z dibandingkan dalam urutan tersebut.

Membandingkan jarak cukup intuitif, tetapi menggunakan produk titik sebagai ukuran sekunder mungkin memerlukan beberapa penjelasan. Misalkan berat input semibold (600), dan dua berat kandidat berwarna hitam (900) dan semilight (300). Jarak setiap berat kandidat dari berat input sama, tetapi berat hitam berada dalam arah yang sama dari asal (yaitu, 400 atau normal), sehingga akan memiliki produk titik yang lebih kecil.

Algoritma pencocokan tipografis adalah generalisasi dari algoritma untuk gaya peregangan berat. Setiap nilai sumbu diperlakukan sebagai koordinat dalam vektor atribut font N-dimensi. Untuk setiap kandidat kecocokan, jarak vektor dan produk titik dihitung antara vektor atribut font kandidat yang cocok dan vektor atribut font input. Kandidat dengan jarak vektor yang lebih kecil adalah kecocokan yang lebih baik. Jika jaraknya sama, kandidat dengan produk titik yang lebih kecil adalah kecocokan yang lebih baik. Jika produk titik juga sama, kehadiran dalam keluarga gaya peregangan berat tertentu dapat digunakan sebagai tie-breaker.

Untuk menghitung jarak vektor dan produk titik, vektor atribut font kandidat yang cocok dan vektor atribut font input harus memiliki sumbu yang sama. Oleh karena itu, setiap nilai sumbu yang hilang di salah satu vektor diisi dengan menggantikan nilai standar untuk sumbu tersebut. Koordinat vektor dinormalisasi dengan mengurangi nilai standar (atau "normal") untuk sumbu yang sesuai dan mengalikan hasilnya dengan pengali khusus sumbu. Berikut ini adalah perkalian dan nilai standar untuk setiap sumbu:

Sumbu Pengganda Nilai Standar
"wght" 5 400
"wdth" 55 100
"ital" 1400 0
"slnt" 35 0
"opsz" 1 12
lain 1 0

Pengali konsisten dengan yang digunakan oleh algoritma gaya peregangan berat, tetapi diskalakan seperlunya. Misalnya, lebar normal adalah 100, yang setara dengan stretch 5. Ini menghasilkan pengali 55 vs. 1100. Atribut gaya warisan (0..2) menjerat miring dan miring, yang dalam model tipografis diurai menjadi sumbu "ital" (0,.1) dan sumbu "slnt" (-90..90). Pengali yang dipilih untuk kedua sumbu ini memberikan hasil yang setara dengan algoritma warisan jika kita mengasumsikan kemiringan 20 derajat default untuk font miring.

Pemilihan font tipografis dan ukuran optik

Aplikasi yang menggunakan model keluarga font tipografi dapat menerapkan ukuran optik dengan menentukan nilai sumbu opsz sebagai parameter pemilihan font. Misalnya, aplikasi pemrosesan kata dapat menentukan nilai sumbu opsz sama dengan ukuran font dalam poin. Dalam hal ini, pengguna dapat memilih "Sitka" sebagai keluarga font, dan aplikasi akan secara otomatis memilih instans Sitka dengan nilai sumbu opsz yang benar. Di bawah model WWS, setiap ukuran optik diekspos sebagai nama keluarga yang berbeda dan terserah pengguna untuk memilih yang tepat.

Secara teori, seseorang dapat menerapkan ukuran optik otomatis di bawah model gaya peregangan berat dengan mengambil alih nilai sumbu opsz sebagai langkah terpisah setelah pemilihan font. Namun, ini hanya berfungsi jika font pencocokan pertama adalah font variabel dengan variabel opsz sumbu. Menentukan opsz sebagai parameter pemilihan font bekerja sama baiknya untuk font statis. Misalnya, keluarga font Sitka diimplementasikan sebagai font variabel di Windows 11, tetapi sebagai kumpulan font statis di Windows 10. Font statis memiliki rentang sumbu opsz yang berbeda dan tidak tumpang tindih (ini dinyatakan sebagai rentang untuk tujuan pemilihan font, tetapi bukan sumbu variabel). Menentukan opsz sebagai parameter pemilihan font memungkinkan font statis yang benar untuk ukuran optik yang akan dipilih.

Keuntungan pemilihan font tipografis, dan masalah kompatibilitas

Model pemilihan font tipografis memiliki beberapa keunggulan dibandingkan model sebelumnya, tetapi dalam bentuk murni memiliki beberapa potensi masalah kompatibilitas. Bagian ini menjelaskan keuntungan dan masalah kompatibilitas. Bagian berikutnya menjelaskan model pemilihan font hibrid yang mempertahankan keuntungan sambil mengurangi masalah kompatibilitas.

Keuntungan dari model keluarga font tipografis adalah:

  • Font dapat dikelompokkan ke dalam keluarga seperti yang dimaksudkan oleh perancang, daripada dibagi menjadi subfamilies karena keterbatasan model keluarga font.

  • Aplikasi dapat secara otomatis memilih nilai sumbu opsz yang benar berdasarkan ukuran font, daripada mengekspos ukuran optik yang berbeda kepada pengguna sebagai keluarga font yang berbeda.

  • Instans arbitrer font variabel dapat dipilih. Misalnya, jika font variabel mendukung bobot dalam rentang berkelanjutan 100-900, model tipografi dapat memilih bobot dalam rentang ini. Model keluarga font yang lebih lama hanya dapat memilih bobot terdekat dari antara instans bernama yang ditentukan oleh font.

Masalah kompatibilitas dengan model pemilihan font tipografis adalah:

  • Beberapa font lama tidak dapat dipilih secara tidak ambigu hanya menggunakan nama keluarga tipografi dan nilai sumbu.

  • Dokumen yang ada mungkin merujuk ke font berdasarkan nama keluarga WWS atau nama keluarga RBIZ. Pengguna mungkin juga mengharapkan untuk menggunakan nama keluarga WWS dan RBIZ. Misalnya, dokumen mungkin menentukan "Sitka Subheading" (nama keluarga WWS) alih-alih "Sitka" (nama keluarga tipografi).

  • Pustaka atau kerangka kerja mungkin mengadopsi model keluarga font tipografis untuk memanfaatkan ukuran optik otomatis, tetapi tidak menyediakan API untuk menspesifikasikan nilai sumbu sewenang-wenang. Bahkan jika API baru disediakan, kerangka kerja mungkin perlu bekerja dengan aplikasi yang ada yang hanya menentukan parameter berat, bentang, dan gaya.

Masalah kompatibilitas dengan font yang lebih lama muncul karena konsep nama keluarga tipografis mendahului konsep nilai sumbu font, yang diperkenalkan bersama dengan font variabel di OpenType 1.8. Sebelum OpenType 1.8, nama keluarga tipografi hanya mengekspresikan niat perancang bahwa sekumpulan font terkait, tetapi tanpa jaminan bahwa font tersebut dapat dibedakan secara terprogram berdasarkan propertinya. Sebagai contoh hipotetis, misalkan semua font berikut memiliki nama keluarga tipografi "Warisan":

Nama Lengkap Keluarga WWS Berat Merentangkan Gaya Keluarga Typo wght wdth ital slnt
Warisan Warisan 400 5 0 Warisan 400 100 0 0
Tebal Warisan Warisan tujuh ratus 5 0 Warisan tujuh ratus 100 0 0
Warisan Hitam Warisan 900 5 0 Warisan 900 100 0 0
Warisan Lembut Warisan Lembut 400 5 0 Warisan 400 100 0 0
Tebal Lembut Warisan Warisan Lembut tujuh ratus 5 0 Warisan tujuh ratus 100 0 0
Warisan Hitam Lembut Warisan Lembut 900 5 0 Warisan 900 100 0 0

Keluarga tipografi "Warisan" memiliki tiga berat, dan setiap berat memiliki varian reguler dan "Lembut". Jika ini adalah font baru, font tersebut dapat diimplementasikan sebagai mendeklarasikan sumbu desain SOFT. Namun, font ini mendahului OpenType 1.8, sehingga satu-satunya sumbu desainnya berasal dari berat, peregangan, dan gaya. Untuk setiap berat, keluarga font ini memiliki dua font dengan nilai sumbu yang identik, sehingga tidak dimungkinkan untuk memilih font secara tidak ambigu dalam keluarga ini menggunakan nilai sumbu saja.

Algoritma pemilihan font hibrid

API pemilihan font yang dijelaskan di bagian berikutnya menggunakan algoritma pemilihan font hibrid yang mempertahankan keunggulan pemilihan font tipografis sambil mengurangi masalah kompatibilitasnya.

Pilihan font hibrid menyediakan penghubung dari model keluarga font yang lebih lama dengan mengaktifkan bobot font, peregangan font, dan nilai gaya font untuk dipetakan ke nilai sumbu font yang sesuai. Itu membantu mengatasi masalah kompatibilitas dokumen dan aplikasi.

Selain itu, algoritma pemilihan font hibrid memungkinkan nama keluarga yang ditentukan untuk menjadi nama keluarga tipografis, nama keluarga gaya tandus berat, nama keluarga GDI/RBIZ, atau nama font lengkap. Pencocokan terjadi dengan salah satu cara berikut, dalam urutan prioritas turun:

  1. Nama cocok dengan keluarga tipografis (misalnya, Sitka). Pencocokan terjadi dalam keluarga tipografis dan semua nilai sumbu yang diminta digunakan. Jika namanya juga cocok dengan subfamali WWS (yaitu, satu lebih kecil dari keluarga tipografis) maka keanggotaan dalam subfamali WWS digunakan sebagai pemecah ikat.

  2. Nama cocok dengan keluarga WWS (misalnya, Teks Sitka). Pencocokan terjadi dalam keluarga WWS, dan nilai sumbu yang diminta selain "wght", "wdth", "ital", dan "slnt" diabaikan.

  3. Nama ini cocok dengan keluarga GDI (misalnya, Bahnschrift Condensed). Pencocokan terjadi dalam keluarga RBIZ dan nilai sumbu yang diminta selain "wght", "ital", dan "slnt" diabaikan.

  4. Nama cocok dengan nama lengkap (misalnya, Bahnschrift Bold Condensed). Font yang cocok dengan nama lengkap dikembalikan. Nilai sumbu yang diminta diabaikan. Pencocokan dengan nama font lengkap diperbolehkan karena GDI mendukungnya.

Bagian sebelumnya menjelaskan keluarga tipografi ambigu yang disebut "Warisan". Algoritma hibrid memungkinkan ambiguitas dihindari dengan menentukan "Warisan" atau "Warisan Lunak" sebagai nama keluarga. Jika "Legacy Soft" ditentukan, maka tidak ada ambiguitas karena pencocokan hanya terjadi dalam keluarga WWS. Jika "Warisan" ditentukan, maka semua font dalam keluarga tipografi dianggap sebagai kandidat yang cocok, tetapi ambiguitas dihindari dengan menggunakan keanggotaan dalam keluarga WWS "Warisan" sebagai tie-breaker.

Misalkan dokumen menentukan nama dan berat keluarga, rentang, dan parameter gaya, tetapi tidak ada nilai sumbu. Aplikasi pertama-tama dapat mengonversi bobot, rentang, gaya, dan ukuran font menjadi nilai sumbu dengan memanggil IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues. Aplikasi kemudian dapat meneruskan nilai nama keluarga dan sumbu ke IDWriteFontSet4::GetMatchingFonts. GetMatchingFonts mengembalikan daftar font yang cocok dalam urutan prioritas, dan hasilnya sesuai apakah nama keluarga yang ditentukan adalah nama keluarga tipografi, nama keluarga gaya bentang berat, nama keluarga RBIZ, atau nama lengkap. Jika keluarga yang ditentukan memiliki sumbu "opsz", maka ukuran optik yang sesuai secara otomatis dipilih berdasarkan ukuran font.

Misalkan dokumen menentukan berat, rentang, dan gaya, dan juga menentukan nilai sumbu. Dalam hal ini, nilai sumbu eksplisit juga dapat diteruskan ke IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues, dan nilai sumbu turunan yang dikembalikan oleh metode hanya akan menyertakan sumbu font yang tidak ditentukan secara eksplisit. Dengan demikian, nilai sumbu yang ditentukan secara eksplisit oleh dokumen (atau aplikasi) lebih diutamakan daripada nilai sumbu yang berasal dari berat, rentang, gaya, dan ukuran font.

API pemilihan font hibrid

Model pemilihan font hibrid diimplementasikan oleh metodeIDWriteFontSet4berikut:

  • Metode IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues mengonversi ukuran font, berat, peregangan, dan parameter gaya ke nilai sumbu yang sesuai. Setiap nilai sumbu eksplisit yang diteruskan oleh klien dikecualikan dari nilai sumbu turunan.

  • MetodeIDWriteFontSet4::GetMatchingFontsmengembalikan daftar font yang cocok yang diprioritaskan dengan nama keluarga dan array nilai sumbu. Seperti yang dijelaskan di atas, parameter nama keluarga dapat berupa nama keluarga tipografi, nama keluarga WWS, nama keluarga RBIZ, atau nama lengkap. (Nama lengkap mengidentifikasi gaya font tertentu, seperti "Arial Bold Miring". GetMatchingFonts mendukung pencocokan dengan nama lengkap untuk kompatibilitas yang lebih besar dengan GDI, yang juga memungkinkannya.)

Api DirectWrite lainnya berikut ini juga menggunakan algoritma pemilihan font hibrid:

Contoh kode API pemilihan font yang digunakan

Bagian ini menunjukkan aplikasi konsol lengkap yang menunjukkan IDWriteFontSet4::GetMatchingFonts dan IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues metode. Pertama mari kita sertakan beberapa header:

#include <dwrite_core.h>
#include <wil/com.h>
#include <iostream>
#include <string>
#include <vector>

Metode IDWriteFontSet4::GetMatchingFonts mengembalikan daftar font dalam urutan prioritas yang cocok dengan nilai nama keluarga dan sumbu yang ditentukan. Fungsi MatchAxisValues berikut menghasilkan parameter untuk IDWriteFontSet4::GetMatchingFonts dan daftar font yang cocok dalam kumpulan font yang dikembalikan.

// Forward declarations of overloaded output operators used by MatchAxisValues.
std::wostream& operator<<(std::wostream& out, DWRITE_FONT_AXIS_VALUE const& axisValue);
std::wostream& operator<<(std::wostream& out, IDWriteFontFile* fileReference);
std::wostream& operator<<(std::wostream& out, IDWriteFontFaceReference1* faceReference);
//
// MatchAxisValues calls IDWriteFontSet4::GetMatchingFonts with the
// specified parameters and outputs the matching fonts.
//
void MatchAxisValues(
    IDWriteFontSet4* fontSet,
    _In_z_ WCHAR const* familyName,
    std::vector<DWRITE_FONT_AXIS_VALUE> const& axisValues,
    DWRITE_FONT_SIMULATIONS allowedSimulations
    )
{
    // Write the input parameters.
    std::wcout << L"GetMatchingFonts(\"" << familyName << L"\", {";
    for (DWRITE_FONT_AXIS_VALUE const& axisValue : axisValues)
    {
        std::wcout << L' ' << axisValue;
    }
    std::wcout << L" }, " << allowedSimulations << L"):\n";
    // Get the matching fonts for the specified family name and axis values.
    wil::com_ptr<IDWriteFontSet4> matchingFonts;
    THROW_IF_FAILED(fontSet->GetMatchingFonts(
        familyName,
        axisValues.data(),
        static_cast<UINT32>(axisValues.size()),
        allowedSimulations,
        &matchingFonts
    ));
    // Output the matching font face references.
    UINT32 const fontCount = matchingFonts->GetFontCount();
    for (UINT32 fontIndex = 0; fontIndex < fontCount; fontIndex++)
    {
        wil::com_ptr<IDWriteFontFaceReference1> faceReference;
        THROW_IF_FAILED(matchingFonts->GetFontFaceReference(fontIndex, &faceReference));
        std::wcout << L"    " << faceReference.get() << L'\n';
    }
    std::wcout << std::endl;
}

Aplikasi mungkin memiliki parameter berat, peregangan, dan gaya alih-alih nilai sumbu (atau selain) . Misalnya, aplikasi mungkin perlu bekerja dengan dokumen yang mereferensikan font menggunakan RBIZ atau parameter gaya peregangan berat. Bahkan jika aplikasi menambahkan dukungan untuk menentukan nilai sumbu arbitrer, aplikasi mungkin juga perlu mendukung parameter yang lebih lama. Untuk melakukannya, aplikasi dapat memanggil IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues sebelum memanggil IDWriteFontSet4::GetMatchingFonts.

Fungsi MatchFont berikut ini menurunkan parameter berat, peregangan, gaya, dan ukuran font selain nilai sumbu. Ini meneruskan parameter ini ke IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues metode untuk menghitung nilai sumbu turunan, yang ditambahkan ke nilai sumbu input. Ini meneruskan nilai sumbu gabungan ke fungsi MatchAxisValues di atas.

struct FontStyleParams
{
    FontStyleParams() {}
    FontStyleParams(DWRITE_FONT_WEIGHT fontWeight) : fontWeight{ fontWeight } {}
    FontStyleParams(float fontSize) : fontSize{ fontSize } {}
    DWRITE_FONT_WEIGHT fontWeight = DWRITE_FONT_WEIGHT_NORMAL;
    DWRITE_FONT_STRETCH fontStretch = DWRITE_FONT_STRETCH_NORMAL;
    DWRITE_FONT_STYLE fontStyle = DWRITE_FONT_STYLE_NORMAL;
    float fontSize = 0.0f;
};
//
// MatchFont calls IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues to convert
// the input parameters to axis values and then calls MatchAxisValues.
//
void MatchFont(
    IDWriteFactory7* factory,
    _In_z_ WCHAR const* familyName,
    FontStyleParams styleParams = {},
    std::vector<DWRITE_FONT_AXIS_VALUE> axisValues = {},
    DWRITE_FONT_SIMULATIONS allowedSimulations = DWRITE_FONT_SIMULATIONS_BOLD | DWRITE_FONT_SIMULATIONS_OBLIQUE
    )
{
    wil::com_ptr<IDWriteFontSet2> fontSet2;
    THROW_IF_FAILED(factory->GetSystemFontSet(/*includeDownloadableFonts*/ false, &fontSet2));
    wil::com_ptr<IDWriteFontSet4> fontSet;
    THROW_IF_FAILED(fontSet2->QueryInterface(&fontSet));
    // Ensure the total number of axis values can't overflow a UINT32.
    size_t const inputAxisCount = axisValues.size();
    if (inputAxisCount > UINT32_MAX - DWRITE_STANDARD_FONT_AXIS_COUNT)
    {
        THROW_HR(E_INVALIDARG);
    }
    // Reserve space at the end of axisValues vector for the derived axis values.
    // The maximum number of derived axis values is DWRITE_STANDARD_FONT_AXIS_COUNT.
    axisValues.resize(inputAxisCount + DWRITE_STANDARD_FONT_AXIS_COUNT);
    // Convert the weight, stretch, style, and font size to derived axis values.
    UINT32 derivedAxisCount = fontSet->ConvertWeightStretchStyleToFontAxisValues(
        /*inputAxisValues*/ axisValues.data(),
        static_cast<UINT32>(inputAxisCount),
        styleParams.fontWeight,
        styleParams.fontStretch,
        styleParams.fontStyle,
        styleParams.fontSize,
        /*out*/ axisValues.data() + inputAxisCount
        );
    // Resize the vector based on the actual number of derived axis values returned.
    axisValues.resize(inputAxisCount + derivedAxisCount);
    // Pass the combined axis values (explicit and derived) to MatchAxisValues.
    MatchAxisValues(fontSet.get(), familyName, axisValues, allowedSimulations);
}

Fungsi berikut menunjukkan hasil panggilan fungsi MatchFont di atas dengan beberapa contoh input:

void TestFontSelection(IDWriteFactory7* factory)
{
    // Request "Cambria" with bold weight, with and without allowing simulations.
    //  - Matches a typographic/WWS family.
    //  - Result includes all fonts in the family ranked by priority.
    //  - Useful because Cambria Bold might have fewer glyphs than Cambria Regular.
    //  - Simulations may be applied if allowed.
    MatchFont(factory, L"Cambria", DWRITE_FONT_WEIGHT_BOLD);
    MatchFont(factory, L"Cambria", DWRITE_FONT_WEIGHT_BOLD, {}, DWRITE_FONT_SIMULATIONS_NONE);
    // Request "Cambria Bold".
    //  - Matches the full name of one static font.
    MatchFont(factory, L"Cambria Bold");
    // Request "Bahnschrift" with bold weight.
    //  - Matches a typographic/WWS family.
    //  - Returns an arbitrary instance of the variable font.
    MatchFont(factory, L"Bahnschrift", DWRITE_FONT_WEIGHT_BOLD);
    // Request "Segoe UI Variable" with two different font sizes.
    //  - Matches a typographic family name only (not a WWS family name).
    //  - Font size maps to "opsz" axis value (Note conversion from DIPs to points.)
    //  - Returns an arbitrary instance of the variable font.
    MatchFont(factory, L"Segoe UI Variable", 16.0f);
    MatchFont(factory, L"Segoe UI Variable", 32.0f);
    // Same as above with an explicit opsz axis value.
    // The explicit value is used instead of an "opsz" value derived from the font size.
    MatchFont(factory, L"Segoe UI Variable", 16.0f, { { DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE, 32.0f } });
    // Request "Segoe UI Variable Display".
    //  - Matches a WWS family (NOT a typographic family).
    //  - The input "opsz" value is ignored; the optical size of the family is used.
    MatchFont(factory, L"Segoe UI Variable Display", 16.0f);
    // Request "Segoe UI Variable Display Bold".
    //  - Matches the full name of a variable font instance.
    //  - All input axes are ignored; the axis values of the matching font are used.
    MatchFont(factory, L"Segoe UI Variable Display Bold", 16.0f);
}

Berikut adalah output dari fungsi TestFontSelection di atas:

GetMatchingFonts("Cambria", { wght:700 wdth:100 ital:0 slnt:0 }, 3):
    cambriab.ttf wght:700 wdth:100 ital:0 slnt:0
    cambria.ttc wght:400 wdth:100 ital:0 slnt:0 BOLDSIM
    cambriaz.ttf wght:700 wdth:100 ital:1 slnt:-12.4
    cambriai.ttf wght:400 wdth:100 ital:1 slnt:-12.4 BOLDSIM
GetMatchingFonts("Cambria", { wght:700 wdth:100 ital:0 slnt:0 }, 0):
    cambriab.ttf wght:700 wdth:100 ital:0 slnt:0
    cambriaz.ttf wght:700 wdth:100 ital:1 slnt:-12.4
    cambria.ttc wght:400 wdth:100 ital:0 slnt:0
    cambriai.ttf wght:400 wdth:100 ital:1 slnt:-12.4
GetMatchingFonts("Cambria Bold", { wght:400 wdth:100 ital:0 slnt:0 }, 3):
    cambriab.ttf wght:700 wdth:100 ital:0 slnt:0
GetMatchingFonts("Bahnschrift", { wght:700 wdth:100 ital:0 slnt:0 }, 3):
    bahnschrift.ttf wght:700 wdth:100 ital:0 slnt:0
GetMatchingFonts("Segoe UI Variable", { wght:400 wdth:100 ital:0 slnt:0 opsz:12 }, 3):
    SegUIVar.ttf opsz:12 wght:400 wdth:100 ital:0 slnt:0
GetMatchingFonts("Segoe UI Variable", { wght:400 wdth:100 ital:0 slnt:0 opsz:24 }, 3):
    SegUIVar.ttf opsz:24 wght:400 wdth:100 ital:0 slnt:0
GetMatchingFonts("Segoe UI Variable", { opsz:32 wght:400 wdth:100 ital:0 slnt:0 }, 3):
    SegUIVar.ttf opsz:32 wght:400 wdth:100 ital:0 slnt:0
GetMatchingFonts("Segoe UI Variable Display", { wght:400 wdth:100 ital:0 slnt:0 opsz:12 }, 3):
    SegUIVar.ttf opsz:36 wght:400 wdth:100 ital:0 slnt:0
GetMatchingFonts("Segoe UI Variable Display Bold", { wght:400 wdth:100 ital:0 slnt:0 opsz:12 }, 3):
    SegUIVar.ttf opsz:36 wght:700 wdth:100 ital:0 slnt:0

Berikut ini adalah implementasi operator yang kelebihan beban yang dinyatakan di atas. Ini digunakan oleh MatchAxisValues untuk menulis nilai sumbu input dan referensi wajah font yang dihasilkan:

// Output a font axis value.
std::wostream& operator<<(std::wostream& out, DWRITE_FONT_AXIS_VALUE const& axisValue)
{
    UINT32 tagValue = axisValue.axisTag;
    WCHAR tagChars[5] =
    {
        static_cast<WCHAR>(tagValue & 0xFF),
        static_cast<WCHAR>((tagValue >> 8) & 0xFF),
        static_cast<WCHAR>((tagValue >> 16) & 0xFF),
        static_cast<WCHAR>((tagValue >> 24) & 0xFF),
        '\0'
    };
    return out << tagChars << L':' << axisValue.value;
}
// Output a file name given a font file reference.
std::wostream& operator<<(std::wostream& out, IDWriteFontFile* fileReference)
{
    wil::com_ptr<IDWriteFontFileLoader> loader;
    THROW_IF_FAILED(fileReference->GetLoader(&loader));
    wil::com_ptr<IDWriteLocalFontFileLoader> localLoader;
    if (SUCCEEDED(loader->QueryInterface(&localLoader)))
    {
        const void* fileKey;
        UINT32 fileKeySize;
        THROW_IF_FAILED(fileReference->GetReferenceKey(&fileKey, &fileKeySize));
        WCHAR filePath[MAX_PATH];
        THROW_IF_FAILED(localLoader->GetFilePathFromKey(fileKey, fileKeySize, filePath, MAX_PATH));
        WCHAR* fileName = wcsrchr(filePath, L'\\');
        fileName = (fileName != nullptr) ? fileName + 1 : filePath;
        out << fileName;
    }
    return out;
}
// Output a font face reference.
std::wostream& operator<<(std::wostream& out, IDWriteFontFaceReference1* faceReference)
{
    // Output the font file name.
    wil::com_ptr<IDWriteFontFile> fileReference;
    THROW_IF_FAILED(faceReference->GetFontFile(&fileReference));
    std::wcout << fileReference.get();
    // Output the face index if nonzero.
    UINT32 const faceIndex = faceReference->GetFontFaceIndex();
    if (faceIndex != 0)
    {
        out << L'#' << faceIndex;
    }
    // Output the axis values.
    UINT32 const axisCount = faceReference->GetFontAxisValueCount();
    std::vector<DWRITE_FONT_AXIS_VALUE> axisValues(axisCount);
    THROW_IF_FAILED(faceReference->GetFontAxisValues(axisValues.data(), axisCount));
    for (DWRITE_FONT_AXIS_VALUE const& axisValue : axisValues)
    {
        out << L' ' << axisValue;
    }
    // Output the simulations.
    DWRITE_FONT_SIMULATIONS simulations = faceReference->GetSimulations();
    if (simulations & DWRITE_FONT_SIMULATIONS_BOLD)
    {
        out << L" BOLDSIM";
    }
    if (simulations & DWRITE_FONT_SIMULATIONS_OBLIQUE)
    {
        out << L" OBLIQUESIM";
    }
    return out;
}

Untuk membulatkan sampel, berikut adalah fungsi penguraian baris perintah dan fungsi utama:

char const g_usage[] =
"ParseCmdLine <args>\n"
"\n"
" -test             Test sample font selection parameters.\n"
" <familyname>      Sets the font family name.\n"
" -size <value>     Sets the font size in DIPs.\n"
" -bold             Sets weight to bold (700).\n"
" -weight <value>   Sets a weight in the range 100-900.\n"
" -italic           Sets style to DWRITE_FONT_STYLE_ITALIC.\n"
" -oblique          Sets style to DWRITE_FONT_STYLE_OBLIQUE.\n"
" -stretch <value>  Sets a stretch in the range 1-9.\n"
" -<axis>:<value>   Sets an axis value (for example, -opsz:24).\n"
" -nosim            Disallow font simulations.\n"
"\n";
struct CmdArgs
{
    std::wstring familyName;
    FontStyleParams styleParams;
    std::vector<DWRITE_FONT_AXIS_VALUE> axisValues;
    DWRITE_FONT_SIMULATIONS allowedSimulations = DWRITE_FONT_SIMULATIONS_BOLD | DWRITE_FONT_SIMULATIONS_OBLIQUE;
    bool test = false;
};
template<typename T>
_Success_(return)
bool ParseEnum(_In_z_ WCHAR const* arg, long minValue, long maxValue, _Out_ T* result)
{
    WCHAR* endPtr;
    long value = wcstol(arg, &endPtr, 10);
    *result = static_cast<T>(value);
    return value >= minValue && value <= maxValue && *endPtr == L'\0';
}
_Success_(return)
bool ParseFloat(_In_z_ WCHAR const* arg, _Out_ float* value)
{
    WCHAR* endPtr;
    *value = wcstof(arg, &endPtr);
    return *arg != L'\0' && *endPtr == L'\0';
}
bool ParseCommandLine(int argc, WCHAR** argv, CmdArgs& cmd)
{
    for (int argIndex = 1; argIndex < argc; argIndex++)
    {
        WCHAR const* arg = argv[argIndex];
        if (*arg != L'-')
        {
            if (!cmd.familyName.empty())
                return false;
            cmd.familyName = argv[argIndex];
        }
        else if (!wcscmp(arg, L"-test"))
        {
            cmd.test = true;
        }
        else if (!wcscmp(arg, L"-size"))
        {
            if (++argIndex == argc)
                return false;
            if (!ParseFloat(argv[argIndex], &cmd.styleParams.fontSize))
                return false;
        }
        else if (!wcscmp(arg, L"-bold"))
        {
            cmd.styleParams.fontWeight = DWRITE_FONT_WEIGHT_BOLD;
        }
        else if (!wcscmp(arg, L"-weight"))
        {
            if (++argIndex == argc)
                return false;
            if (!ParseEnum(argv[argIndex], 100, 900, &cmd.styleParams.fontWeight))
                return false;
        }
        else if (!wcscmp(arg, L"-italic"))
        {
            cmd.styleParams.fontStyle = DWRITE_FONT_STYLE_ITALIC;
        }
        else if (!wcscmp(arg, L"-oblique"))
        {
            cmd.styleParams.fontStyle = DWRITE_FONT_STYLE_OBLIQUE;
        }
        else if (!wcscmp(arg, L"-stretch"))
        {
            if (++argIndex == argc)
                return false;
            if (!ParseEnum(argv[argIndex], 1, 9, &cmd.styleParams.fontStretch))
                return false;
        }
        else if (wcslen(arg) > 5 && arg[5] == L':')
        {
            // Example: -opsz:12
            DWRITE_FONT_AXIS_VALUE axisValue;
            axisValue.axisTag = DWRITE_MAKE_FONT_AXIS_TAG(arg[1], arg[2], arg[3], arg[4]);
            if (!ParseFloat(arg + 6, &axisValue.value))
                return false;
            cmd.axisValues.push_back(axisValue);
        }
        else if (!wcscmp(arg, L"-nosim"))
        {
            cmd.allowedSimulations = DWRITE_FONT_SIMULATIONS_NONE;
        }
        else
        {
            return false;
        }
    }
    return true;
}
int __cdecl wmain(int argc, WCHAR** argv)
{
    CmdArgs cmd;
    if (!ParseCommandLine(argc, argv, cmd))
    {
        std::cerr << "Invalid command. Type TestFontSelection with no arguments for usage.\n";
        return 1;
    }
    if (cmd.familyName.empty() && !cmd.test)
    {
        std::cout << g_usage;
        return 0;
    }
    wil::com_ptr<IDWriteFactory7> factory;
    THROW_IF_FAILED(DWriteCoreCreateFactory(
        DWRITE_FACTORY_TYPE_SHARED,
        __uuidof(IDWriteFactory7),
        (IUnknown**)&factory
    ));
    if (!cmd.familyName.empty())
    {
        MatchFont(
            factory.get(),
            cmd.familyName.c_str(),
            cmd.styleParams,
            std::move(cmd.axisValues),
            cmd.allowedSimulations
        );
    }
    if (cmd.test)
    {
        TestFontSelection(factory.get());
    }
}