共用方式為


字型選取

IDWriteFontSet4介面會公開從字型集選取字型的方法。 這些方法可讓您轉換到 印刷字型系列模型 ,同時維持與現有應用程式、檔和字型的相容性。

字型選取 (有時稱為字型比對或字型對應) 是選取應用程式傳入之輸入參數最符合可用字型的程式。 輸入參數有時稱為 邏輯字型。 邏輯字型包含字型系列名稱加上指出系列內特定字型的其他屬性。 字型選取演算法會比對邏輯字型 (「您想要的字型」) 到可用的 實體字 型 (「您擁有的字型」) 。

型系列 是共用通用設計的具名字型群組,但可能會因粗細等屬性而有所不同。 字型系列模型會定義哪些屬性可用來區分系列內的字型。 新的 印刷字型系列模型 比 Windows 上所使用的兩個舊版字型系列模型有許多優點。 但變更字型系列模型會創造混淆和相容性問題的機會。 IDWriteFontSet4介面所公開的方法會實作混合式方法,以提供印刷字型系列模型的優點,同時降低相容性問題。

本主題比較較舊的字型系列模型與印刷字型系列模型;它說明變更字型系列模型所造成的相容性挑戰;最後,它會說明如何使用 [IDWriteFontSet4] (/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontset4) 方法來克服這些挑戰。

RBIZ 字型系列模型

GDI 應用程式生態系統中使用的事實字型系列模型有時稱為「四字型模型」或「RBIZ」模型。 此模型中的每個字型系列通常最多有四個字型。 「RBIZ」 標籤來自用於某些字型檔案的命名慣例,例如:

檔案名稱 字型樣式
verdana.ttf 定期
verdanab.ttf 粗體
verdanai.ttf 斜體
verdanaz.ttf 粗體斜體

使用 GDI 時,用來選取字型的輸入參數是由 LOGFONT 結構所定義,其中包括系列名稱 () lfFaceName 、粗 lfWeight 細 () 和斜體 lfItalic () 欄位。 欄位 lfItalic 為 TRUE 或 FALSE。 GDI 可讓 lfWeight 欄位成為範圍 中的任何值, FW_THIN (100) FW_BLACK ( 900) ,但基於歷史理由,字型設計過長,因此相同 GDI 字型系列中沒有兩個以上的粗細。

早期的熱門應用程式使用者介面包含斜體按鈕 (,以開啟和關閉斜體) ,以及粗體按鈕 (切換一般和粗體粗體粗細) 。 使用這兩個按鈕來選取系列內的字型會假設 RBIZ 模型。 因此,即使 GDI 本身支援兩個以上的權數,應用程式相容性還是會引導字型開發人員以與 RBIZ 模型一致的方式來設定 GDI 系列名稱 (OpenType 名稱識別碼 1) 。

例如,假設您想要將較重的 「Black」 粗細新增至 Arial 字型系列。 邏輯上,此字型是 Arial 系列的一部分,因此您可能會預期將 設定 lfFaceName 為 「Arial」 並 lfWeightFW_BLACK加以選取。 不過,應用程式使用者無法使用雙狀態粗體按鈕,在三個權數之間選擇。 解決方案是為新字型提供不同的系列名稱,讓使用者可以從字型系列清單中選擇 [Arial Black] 來加以選取。 同樣地,使用粗體和斜體按鈕,無法從相同字型系列中的不同寬度中選擇,因此 Arial 的窄版本在 RBIZ 模型中有不同的系列名稱。 因此,我們在 RBIZ 模型中有 「Arial」、「Arial Black」 和 「Arial Narrow」 字型,即使這些字型全都屬於一個系列。

從這些範例中,您可以看到字型系列模型的限制如何影響字型分組成系列的方式。 由於字型系列是以名稱識別,這表示相同的字型可能會根據您使用的字型系列模型而有不同的系列名稱。

DirectWrite並不直接支援 RBIZ 字型系列模型,但提供從 RBIZ 模型來回轉換的方法,例如IDWriteGdiInterop::CreateFontFromLOGFONTIDWriteGdiInterop::ConvertFontToLOGFONT。 您也可以呼叫其 IDWriteFont::GetInformationalStrings 方法來取得字型的 RBIZ 系列名稱,並指定 DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES

粗細延展樣式字型系列模型

粗細延展樣式字型系列模型是引進印刷字型系列模型之前,DirectWrite所使用的原始字型系列模型。 也稱為權寬斜率 (WWS) 。 在 WWS 模型中,相同系列內的字型可以有三個屬性不同:粗細 (DWRITE_FONT_WEIGHT) 、延展 (DWRITE_FONT_STRETCH) ,以及樣式 (DWRITE_FONT_STYLE) 。

WWS 模型比 RBIZ 模型更有彈性,有兩種方式。 首先,相同系列中的字型可以透過延展 (或寬度) ,以及粗細和樣式 (一般、斜體或斜體) 來區分。 其次,同一系列中可以有兩個以上的權數。 此彈性足以讓 Arial 的所有變體都包含在相同的 WWS 系列中。 下表比較選取 Arial 字型的 RBIZ 和 WWS 字型屬性:

全名 RBIZ 系列名稱 lfWeight lfItalic WWS FamilyName Weight 延展 樣式
Arial Arial 400 0 Arial 400 5 0
Arial Bold Arial 700 0 Arial 700 5 0
Arial Black Arial Black 900 0 Arial 900 5 0
Arial 窄 Arial 窄 400 0 Arial 400 3 0
Arial 窄粗體 Arial Narrow 700 0 Arial 700 3 0

如您所見,「Arial Narrow」 的 和 lfItalic 值與 「Arial」 相同 lfWeight ,因此其具有不同的 RBIZ 系列名稱,以避免模棱兩可。 「Arial Black」 有不同的 RBIZ 系列名稱,以避免在 「Arial」 系列中擁有兩個以上的權數。 相反地,所有這些字型都位於相同的粗細延展樣式系列中。

不過,粗細延展樣式模型不是開放結束的。 如果兩個字型的粗細、延展和樣式相同,但有一些其他方式不同 (,例如光學大小) ,則不能包含在相同的 WWS 字型系列中。 這會將我們帶到印刷字型系列模型。

印刷字型系列模型

不同于其前置任務,印刷字型系列模型 開放式的。 它支援字型系列內任意數目的變化軸。

如果您將字型選取參數視為設計空間中的座標,粗細-延展樣式模型會定義具有粗細、延展和樣式的三維座標系統做為座標軸。 WWS 系列中的每個字型都必須在其座標沿著這三個座標軸定義唯一的位置。 若要選取字型,您可以指定 WWS 系列名稱和粗細、延展和樣式參數。

相較之下,印刷字型系列模型具有 N 維設計空間。 字型設計工具可以定義任意數目的設計軸,每個座標軸都是由四個字元 的座標軸標籤所識別。 N 維設計空間中的指定字型位置是由座標軸值的陣列所定義,其中每個 座標軸值都包含座標軸標籤和浮點值。 若要選取字型,您可以指定印刷樣式系列名稱和座標軸值的陣列, (DWRITE_FONT_AXIS_VALUE 結構) 。

雖然字型軸的數目是開放式的,但有一些具有標準意義的已註冊座標軸,而粗細、延展和樣式值可以對應至已註冊的座標軸值。 DWRITE_FONT_WEIGHT 可以對應至 「wght」 (DWRITE_FONT_AXIS_TAG_WEIGHT) 軸值。 DWRITE_FONT_STRETCH 可以對應至 「wdth」 (DWRITE_FONT_AXIS_TAG_WIDTH) 軸值。 DWRITE_FONT_STYLE 可以對應至 「ital」 和 「slnt」 (DWRITE_FONT_AXIS_TAG_ITALICDWRITE_FONT_AXIS_TAG_SLANT) 座標軸值的組合。

另一個已註冊的軸是 「opsz」 (DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE) 。 Sitka 等光學字型系列包含沿著 「opsz」 軸而有所不同的字型,這表示它們的設計目的是在不同的點大小使用。 WWS 字型系列模型沒有光學大小座標軸,因此 Sitka 字型系列必須分割成多個 WWS 字型系列:「Sitka Small」、「Sitka Text」、「Sitka Subheading」等等。 每個 WWS 字型系列都會對應至不同的光學大小,而使用者則會保留指定指定字型大小的右 WWS 系列名稱。 使用印刷字型系列模型時,使用者只要選擇 「Sitka」,應用程式就可以根據字型大小自動設定 「opsz」 軸值。

印刷字型選取和變數字型

變化軸的概念通常與變數字型相關聯,但它也適用于靜態字型。 OpenType STAT (樣式屬性) 資料表會宣告字型具有哪些設計軸,以及這些座標軸的值。 此表格是變數字型的必要專案,但也與靜態字型相關。

DirectWrite API 會公開每個字型的 「wght」、「wdth」、「ital」 和 「slnt」 座標軸值,即使它們不存在於 STAT 資料表中,或沒有 STAT 資料表也一樣。 這些值可能衍生自 STAT 資料表。 否則,它們衍生自字型粗細、字型延展和字型樣式。

字型軸可以是變數或非變數。 靜態字型只有非變數座標軸,而變數字型可能同時具有兩者。 若要使用變數字型,您必須建立變數字型 實例 ,其中所有變數座標軸都已系結至特定值。 IDWriteFontFace介面代表靜態字型或變數字型的特定實例。 可以建立具有指定座標軸值的變數字型 任意實例 。 此外,變數字型可能會在STAT資料表中宣告具名實例,其中包含預先定義的座標軸值組合。 具名實例可讓變數字型的行為與靜態字型集合類似。 當您列舉 IDWriteFontFamilyIDWriteFontSet的專案時,每個靜態字型和每個具名變數字型實例都有一個元素。

印刷字型比對演算法會先根據系列名稱選取可能的比對候選項目。 如果相符專案包含變數字型,則相同變數字型的所有相符候選項目都會折迭成一個相符候選項目,其中每個變數座標軸都會指派特定值,盡可能接近該座標軸的要求值。 如果變數座標軸沒有要求的值,則會指派該座標軸的預設值。 然後,比對候選項目的順序會藉由比較其座標軸值與要求的座標軸值來決定。

例如,請考慮 Windows 中的 Sitka 印刷樣式系列。 Sitka 是光學字型系列,這表示其具有 「opsz」 軸。 在Windows 11中,Sitka 會實作為兩個具有下列座標軸值的變數字型。 請注意, opszwght 軸是可變的,而其他座標軸則是非變數。

檔案名稱 「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

假設要求的座標軸值為 opsz:12 wght:475 wdth:100 ital:0 slnt:0 。 針對每個變數字型,我們會建立變數字型 實例 的參考,其中每個變數座標軸都會指派特定值。 也就是說, opszwght 座標軸分別設定為 12475 。 這會產生下列相符的字型,且第一次排名非斜體字型,因為它是 和 slnt 軸的較佳相符 ital 專案:

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

在上述範例中,相符的字型是任意變數字型實例。 沒有具有權數 475 的 Sitka 具名實例。 相較之下,權數延展樣式比對演算法只會傳回具名實例。

字型比對順序

粗細延展樣式字型系列模型有不同的 多載 GetMatchingFonts 方法, (IDWriteFontFamily::GetMatchingFonts) 和印刷字型系列模型 (IDWriteFontCollection2::GetMatchingFonts) 。 在這兩種情況下,輸出都是以優先順序遞減順序排列的相符字型清單,根據每個候選字型與輸入屬性的相符程度。 本節說明如何決定優先順序。

在粗細延展樣式模型中,輸入參數是字型粗細 (DWRITE_FONT_WEIGHT) 、字型延展 (DWRITE_FONT_STRETCH) ,以及字型樣式 (DWRITE_FONT_STYLE) 。 尋找最佳比對的演算法記載于 2006 白皮書中,標題為「WPF 字型選取模型」,作者為 Miaviil Brownov 和 David Brown。 請參閱一節。本檔Windows Presentation Foundation (WPF) ,但稍後DirectWrite使用相同的方法。

此演算法會使用字型 屬性向量的概念,此概念適用于指定粗細、延展和樣式的組合,如下所示:

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

請注意,每個向量座標都是透過減去對應屬性的「標準」值,並乘以常數來正規化。 乘數會補償權數、延展和樣式的輸入值範圍非常不同。 否則,權數 (100..999) 會超過樣式 (0..2) 。

針對每個相符專案,向量距離和點乘積會在相符候選項目的字型屬性向量與輸入字型屬性向量之間計算。 比較兩個比對候選項目時,具有較小向量距離的候選項目是較佳的相符專案。 如果距離相同,則具有較小點產品的候選項目較佳。 如果點乘積也相同,則會依該順序比較 X、Y 和 Z 軸之間的距離。

比較距離夠直覺,但使用點產品做為次要量值可能需要一些說明。 假設輸入權數為半曲 (600) ,而兩個候選權數為黑色 (900) ,而半細 (300) 。 每個候選權數與輸入權數的距離都相同,但黑色權數與原點 (的相同方向,也就是 400 或一般) ,因此會有較小的點乘積。

印刷樣式比對演算法是粗細延展樣式的一般化。 每個座標軸值都會被視為 N 維字型屬性向量的座標。 針對每個相符專案,向量距離和點乘積會在相符候選項目的字型屬性向量與輸入字型屬性向量之間計算。 具有較小向量距離的候選項目是較佳的相符專案。 如果距離相同,則具有較小點產品的候選項目較佳。 如果點乘積也相同,則可以在指定的粗細延展樣式系列中作為斷線器使用。

若要計算向量距離和點乘積,比對候選項目的字型屬性向量和輸入字型屬性向量必須具有相同的軸。 因此,任一向量中的任何遺漏座標軸值會藉由取代該座標軸的標準值來填入。 向量座標會藉由減去對應座標的標準 (或「一般」) 值,並將結果乘以座標軸特定的乘數來正規化。 以下是每個座標軸的乘數和標準值:

乘數 標準值
「wght」 5 400
「wdth」 55 100
「ital」 1400 0
「slnt」 35 0
「opsz」 1 12
其他 1 0

乘數與權數延展樣式演算法所使用的乘數一致,但視需要調整。 例如,一般寬度為 100,相當於延展 5。 這會產生乘數 55 與 1100。 舊版樣式屬性 (0..2) 斜體和斜體,在印刷樣式模型中會分解成「斜體」軸, (0..1) 和「slnt」 軸 (-90。90) 。 如果我們假設斜體字型的預設 20 度斜線,這兩個座標軸的乘數會提供與舊版演算法相等的結果。

印刷字型選取和光學大小

使用印刷字型系列模型的應用程式可以藉由將座標軸值指定 opsz 為字型選取參數來實作光學調整大小。 例如,文字處理應用程式可以指定等於以點為單位 opsz 字型大小的座標軸值。 在此情況下,使用者可以選取 「Sitka」 做為字型系列,而應用程式會自動選取具有正確 opsz 座標軸值的 Sitka 實例。 在 WWS 模型中,每個光學大小都會公開為不同的系列名稱,而且使用者最多可以選取正確的系列名稱。

理論上,可以在粗細延展樣式模型下實作自動光學調整大小,方法是在字型選取之後將軸值覆 opsz 寫為個別步驟。 不過,只有當第一個相符的字型是具有變數座標軸的變數 opsz 字型時,才會運作。 opsz將 指定為字型選取參數對靜態字型而言同樣適用。 例如,Sitka 字型系列會在 Windows 11 中實作為變數字型,但作為Windows 10中的靜態字型集合。 靜態字型有不同的非重迭 opsz 座標軸範圍, (這些字型會宣告為字型選取範圍,但不是變數座標軸) 。 指定 opsz 為字型選取參數可讓選取光學大小的正確靜態字型。

印刷字型選取優點和相容性問題

印刷字型選取模型對於舊版模型有數個優點,但其純格式有一些潛在的相容性問題。 本節說明優點和相容性問題。 下一節說明混合式字型選取模型,可保留優點,同時降低相容性問題。

印刷字型系列模型的優點如下:

  • 由於字型系列模型的限制,字型可以依設計工具預期分組成系列,而不是分割成子家族模型。

  • 應用程式可以根據字型大小自動選取正確的 opsz 座標軸值,而不是將不同的光學大小公開給使用者作為不同的字型系列。

  • 您可以選取變數字型的任意實例。 例如,如果變數字型支援連續範圍 100-900 中的粗細,則印刷樣式模型 可以選取此 範圍中的任何粗細。 較舊的字型系列模型只能從字型所定義的具名實例中選擇最接近的粗細。

印刷字型選取模型的相容性問題如下:

  • 某些較舊的字型無法明確地使用印刷樣式系列名稱和座標軸值來選取。

  • 現有的檔可能會以 WWS 系列名稱或 RBIZ 系列名稱參照字型。 使用者也可能預期使用 WWS 和 RBIZ 系列名稱。 例如,檔可能會指定 「Sitka Subheading」 (WWS 系列名稱) ,而不是 (印刷系列名稱) 。

  • 程式庫或架構可能會採用印刷字型系列模型來利用自動光學調整大小,但不提供 API 來指定任意座標軸值。 即使提供新的 API,架構可能也需要使用只指定權數、延展和樣式參數的現有應用程式。

舊版字型的相容性問題是因為印刷樣式系列名稱的概念會前置字型座標軸值的概念,而此概念會隨著 OpenType 1.8 中的變數字型一起引進。 在 OpenType 1.8 之前,印刷樣式系列名稱只會表示設計工具的意圖與一組字型相關,但不保證這些字型可以根據其屬性以程式設計方式區分。 假設下列所有字型都有印刷系列名稱 「Legacy」 的假設範例:

全名 WWS 系列 Weight 延展 樣式 錯字系列 wght wdth 斜體 slnt
舊版 舊版 400 5 0 舊版 400 100 0 0
舊版粗體 舊版 700 5 0 舊版 700 100 0 0
舊版黑色 舊版 900 5 0 舊版 900 100 0 0
舊版軟式 舊版軟式 400 5 0 舊版 400 100 0 0
舊版虛粗體 舊版軟式 700 5 0 舊版 700 100 0 0
舊版虛黑 舊版軟式 900 5 0 舊版 900 100 0 0

「舊版」印刷樣式系列有三個權數,而每個權數都有一般和「軟」變體。 如果這些是新的字型,則可實作為宣告 SOFT 設計軸。 不過,這些字型會預先設定 OpenType 1.8,因此其唯一的設計軸是衍生自粗細、延展和樣式的軸。 對於每個粗細,此字型系列都有兩個具有相同座標軸值的字型,因此不能單獨使用座標軸值來明確選取此系列中的字型。

混合式字型選取演算法

下一節所述的字型選取 API 會使用混合式字型選取演算法,以保留印刷字型選取的優點,同時降低其相容性問題。

混合式字型選取提供舊版字型系列模型的橋接器,方法是讓字型粗細、字型延展和字型樣式值對應至對應的字型軸值。 這有助於解決檔和應用程式相容性問題。

此外,混合式字型選取演算法可讓指定的系列名稱成為印刷系列名稱、粗細延展樣式系列名稱、GDI/RBIZ 系列名稱或完整字型名稱。 比對會以下列其中一種方式進行,優先順序遞減:

  1. 名稱符合印刷樣式系列 (,例如 Sitka) 。 比對會在印刷樣式系列內發生,而且會使用所有要求的座標軸值。 如果名稱也符合 WWS 子家庭 (,則比印刷樣式系列小一個) ,則會使用 WWS 子類別的成員資格做為斷線器。

  2. 名稱符合 WWS 系列 (,例如 Sitka Text) 。 比對會發生在 WWS 系列內,並忽略 「wght」、「wdth」、「ital」 和 「slnt」 以外的要求座標軸值。

  3. 此名稱符合 GDI 系列 (,例如 一個 要) 。 比對會在 RBIZ 系列內發生,並忽略 「wght」、「ital」 和 「slnt」 以外的要求座標軸值。

  4. 名稱會比對完整名稱 (,例如 一個粗體壓縮) 。 傳回符合完整名稱的字型。 系統會忽略要求的座標軸值。 允許使用完整字型名稱比對,因為 GDI 支援它。

上一節說明稱為「舊版」的模棱兩可的印刷樣式系列。 混合式演算法可藉由將「舊版」或「舊版軟體」指定為系列名稱來避免模棱兩可。 如果指定了「舊版軟體」,則不會模棱兩可,因為比對只會發生在 WWS 系列內。 如果已指定「舊版」,則印刷樣式系列中的所有字型都會視為相符候選項目,但使用「舊版」WWS 系列的成員資格做為系結中斷器,可避免模棱兩可。

假設檔指定系列名稱和粗細、延展和樣式參數,但沒有座標軸值。 應用程式可以先呼叫 IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues,將粗細、延展、樣式和字型大小轉換成座標軸值。 然後,應用程式可以將系列名稱和座標軸值傳遞至 IDWriteFontSet4::GetMatchingFontsGetMatchingFonts 會以優先順序傳回相符字型清單,而且結果適合指定系列名稱是印刷系列名稱、粗細延展樣式系列名稱、RBIZ 系列名稱或全名。 如果指定的系列具有 「opsz」 軸,則會根據字型大小自動選取適當的光學大小。

假設檔指定粗細、延展和樣式, 同時也 指定座標軸值。 在此情況下,明確座標軸值也可以傳入 IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues,而方法傳回的衍生座標軸值只會包含未明確指定的字型座標軸。 因此,檔 (或應用程式明確指定的座標軸值) 優先于從粗細、延展、樣式和字型大小衍生的座標軸值。

混合式字型選取 API

混合式字型選取模型是由下列 IDWriteFontSet4 方法實作:

  • IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues方法會將字型大小、粗細、延展和樣式參數轉換成對應的座標軸值。 用戶端傳入的任何明確座標軸值,都從衍生的座標軸值中排除。

  • IDWriteFontSet4::GetMatchingFonts方法會傳回符合字型的優先順序清單,指定座標軸值的系列名稱和陣列。 如上所述,系列名稱參數可以是印刷系列名稱、WWS 系列名稱、RBIZ 系列名稱或完整名稱。 (完整名稱可識別特定的字型樣式,例如「Arial Bold Italic」。 GetMatchingFonts 支援使用 GDI 以完整名稱比對,以使用 GDI 進行比對,這也允許它。)

下列其他DirectWrite API 也會使用混合式字型選取演算法:

使用中字型選取 API 的程式碼範例

本節示範 IDWriteFontSet4::GetMatchingFontsIDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues 方法的完整主控台應用程式。 首先,讓我們包含一些標頭:

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

IDWriteFontSet4::GetMatchingFonts方法會以優先順序傳回符合指定系列名稱和座標軸值的字型清單。 下列 MatchAxisValues 函式會將參數輸出至 IDWriteFontSet4::GetMatchingFonts ,以及傳回字型集中相符字型的清單。

// 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;
}

應用程式可能有粗細、延展和樣式參數,而不是 (,或除了) 軸值之外。 例如,應用程式可能需要使用 RBIZ 或粗細延展樣式參數來參考字型的檔。 即使應用程式新增了指定任意座標軸值的支援,也可能需要支援較舊的參數。 若要這樣做,應用程式可以在呼叫IDWriteFontSet4::GetMatchingFontFonts之前呼叫IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues

下列 MatchFont 函式除了座標軸值之外,還會採用粗細、延展、樣式和字型大小參數。 它會將這些參數轉送到 IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues 方法,以計算附加至輸入軸值的衍生座標軸值。 它會將結合的座標軸值傳遞至上述 MatchAxisValues 函式

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);
}

下列函式示範使用一些範例輸入呼叫上述 MatchFont 函式的結果:

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);
}

以下是上述 TestFontSelection 函式的輸出:

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

以下是上述宣告的多載運算子實作。 MatchAxisValues會使用這些值來寫入輸入座標軸值和產生的字型臉部參考:

// 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;
}

若要進位範例,以下是命令列剖析函式和 main 函式:

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());
    }
}