字型選取
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 name ID 1)。
例如,假設您想要將較重的「黑色」粗細新增至 Arial 字型系列。 從邏輯上講,此字型是 Arial 家族的一部分,因此您可以將 lfFaceName
設定為 “Arial”,並將 lfWeight
設為 FW_BLACK來加以選取。 不過,應用程式用戶無法使用雙狀態粗體按鈕,在三個權數之間進行選擇。 解決方法是為新字型提供不同的系列名稱,讓使用者可以從字型系列清單中選擇 “Arial Black” 來選取它。 同樣地,使用粗體和斜體按鈕,無法從相同字型系列的不同寬度中選擇,因此 Arial 的窄版本在 RBIZ 模型中有不同的系列名稱。 因此,我們有 “Arial”、“Arial Black” 和 “Arial Narrow” 字型在 RBIZ 模型中,即使印刷樣式這些全都屬於一個家庭。
從這些範例中,人們可以看到字型系列模型的限制如何影響字型分組成系列的方式。 由於字型系列是以名稱識別,這表示相同的字型可以有不同的系列名稱,視您使用的字型系列模型而定。
DirectWrite 並不直接支援 RBIZ 字型系列模型,但它確實提供從 RBIZ 模型來回轉換的方法,例如 IDWriteGdiInterop::CreateFontFromLOGFONT 和 IDWriteGdiInterop::ConvertFontToLOGFONT。 您也可以呼叫其 IDWriteFont::GetInformationalStrings 方法來取得字型的 RBIZ 系列名稱,並指定 DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES。
粗細延展式字型系列模型
粗細延展式字型系列模型是 DirectWrite 在引進印刷字型系列模型之前所使用的原始字型系列模型。 它也稱為重量寬度斜率(WWS)。 在WS模型中,相同系列內的字型可以有三個屬性不同:粗細(DWRITE_FONT_WEIGHT)、伸展(DWRITE_FONT_STRETCH),以及樣式(DWRITE_FONT_STYLE)。
WWS 模型比 RBIZ 模型更有彈性,有兩種方式。 首先,同一系列中的字型可以透過伸展(或寬度)以及粗細和樣式來區分(一般、斜體或斜體)。 其次,同一個家庭中可以有兩個以上的重量。 這種彈性足以讓 Arial 的所有變體包含在相同的 WWS 系列中。 下表比較選取 Arial 字型的 RBIZ 和 WWS 字型屬性:
全名 | RBIZ 系列名稱 | lfWeight | lfItalic | WWS FamilyName | 重量 | 伸展 | 風格 |
---|---|---|---|---|---|---|---|
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 Narrow | Arial Narrow | 400 | 0 | Arial | 400 | 3 | 0 |
Arial 窄粗體 | Arial Narrow | 700 | 0 | Arial | 700 | 3 | 0 |
如您所見,“Arial Narrow” 具有與 “Arial” 相同的 lfWeight
和 lfItalic
值,因此它有不同的 RBIZ 系列名稱,以避免模棱兩可。 “Arial Black” 有不同的 RBIZ 家族名稱,以避免在 “Arial” 家族中擁有兩個以上的重量。 相比之下,所有這些字型都位於相同的粗細延展式系列中。
不過,權數伸展式模型並非開放式。 如果兩個字型具有相同的粗細、伸展和樣式,但以其他方式不同(例如光學大小),則不能包含在相同的WWS字型系列中。 這讓我們來到印刷字型系列模型。
印刷字型系列模型
與其前身不同,印刷字型系列模型 開放式。 它支援字型系列中任意數目的變化軸。
如果您將字型選取參數視為設計空間中的座標,weight-stretch-style 模型會定義粗細、伸展和樣式作為座標軸的三維座標系統。 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_ITALIC 和 DWRITE_FONT_AXIS_TAG_SLANT) 座標軸值的組合。
另一個已註冊的軸是 「opsz」 (DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE)。 Sitka 等光學字型系列包含沿著 「opsz」 軸而有所不同的字型,這表示它們的設計目的是在不同的點大小使用。 WWS 字型系列模型沒有光學大小軸,因此 Sitka 字型系列必須分割成多個 WWS 字型系列:“Sitka Small”、“Sitka Text”、“Sitka Subheading”等等。 每個WS字型系列都會對應至不同的光學大小,而使用者則需針對指定的字型大小指定正確的WS系列名稱。 使用印刷字型系列模型時,使用者只需選擇 「Sitka」,應用程式就可以根據字型大小自動設定 「opsz」 軸值。
印刷字型選取和變數字型
變化軸的概念通常與變數字型相關聯,但也適用於靜態字型。 OpenType STAT(樣式屬性) 數據表會宣告字型具有哪些設計座標軸,以及這些座標軸的值。 此表格對於可變字型是必要的,但也與靜態字型相關。
DirectWrite API 會公開每個字型的 “wght”、“wdth”、“ital” 和 “slnt” 坐標軸值,即使它們不存在於 STAT 數據表中,或沒有 STAT 數據表也一樣。 如果可能,這些值會衍生自 STAT 數據表。 否則,它們會衍生自字型粗細、字型延展和字型樣式。
字型軸可以是變數或非變數。 靜態字型只有非變數座標軸,而變數字型可能同時具有兩者。 若要使用變數字型,您必須建立變數字型 實例 其中所有變數座標軸都系結至特定值。 IDWriteFontFace 介面代表靜態字型或變數字型 的特定 實例。 可以建立具有指定座標軸值的變數字型 任意實例。 此外,變數字型可以宣告 具名實例,STAT 數據表中預先定義的座標軸值組合。 具名實例可讓變數字型的行為與靜態字型集合類似。 當您列舉 IDWriteFontFamily 或 IDWriteFontSet的專案時,每個靜態字型和每個具名變數字型實例都有一個元素。
印刷字型比對演算法會先根據姓氏選取潛在的比對候選專案。 如果比對候選專案包含變數字型,則相同變數字型的所有比對候選項目都會折疊成一個比對候選專案,其中每個變數坐標軸都盡可能接近該座標軸的要求值。 如果變數座標軸沒有要求的值,則會為該軸指派預設值。 然後,比對候選項目的順序會藉由比較其座標軸值與要求的座標軸值來決定。
例如,請考慮 Windows 中的 Sitka 印刷樣式系列。 Sitka 是一個光學字型系列,這意味著它有一個“opsz”軸。 在 Windows 11 中,Sitka 會實作為兩個具有下列座標軸值的變數字型。 請注意,opsz
和 wght
座標軸是可變的,而其他座標軸則為非變數。
檔名 | “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
。 針對每個變數字型,我們會建立變數字型的參考 實例 其中每個變數座標軸都會指派特定值。 也就是說,opsz
和 wght
座標軸分別設定為 12
和 475
。 這會產生下列相符字型,且非斜體字型排在第一位,因為它較適合 ital
和 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
在上述範例中,相符的字型是任意變數字型實例。 Sitka 沒有加權為 475 的具名實例。 相反地,加權延展樣式比對演算法只會傳回具名實例。
字型比對順序
重擔式字型系列模型有不同多載 GetMatchingFonts 方法(IDWriteFontFamily::GetMatchingFonts)和印刷字型系列模型(IDWriteFontCollection2::GetMatchingFonts)。 在這兩種情況下,輸出都是根據每個候選字型與輸入屬性相符程度,以遞減順序排列的相符字型清單。 本節說明如何判斷優先順序。
在粗細延展式模型中,輸入參數是字型粗細(DWRITE_FONT_WEIGHT)、字型延展(DWRITE_FONT_STRETCH),以及字型樣式(DWRITE_FONT_STYLE)。 尋找最佳相符專案的演算法記載於 2006 年由 Mikhail Leonov 和 David Brown 標題為“WPF 字型選擇模型”的白皮書中。 請參閱一節。本文是關於 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 維度字型屬性向量的座標。 針對每個相符候選專案,會計算比對候選字型屬性向量與輸入字型屬性向量之間的向量距離和點乘積。 具有較小向量距離的候選專案是較佳的比對。 如果距離相同,則具有較小點乘積的候選專案會比對較佳。 如果點產品也相同,則可以在指定的加權延展式系列中使用作為斷層器。
若要計算向量距離和點乘積,比對候選字型屬性向量和輸入字型屬性向量必須具有相同的軸。 因此,任一向量的遺漏座標軸值會藉由取代該座標軸的標準值來填入。 向量座標會藉由減去對應座標的標準(或“normal”) 值,並將結果乘以座標軸特定的乘數來正規化。 以下是每個座標軸的乘數和標準值:
軸 | 乘數 | 標準值 |
---|---|---|
“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 實例。 在WS模型中,每個光學大小都會公開為不同的系列名稱,而且由使用者決定要選取正確的系列名稱。
理論上,可以藉由將 opsz
軸值覆寫為 字型選取之後的個別 步驟,在粗細延展式模型下實作自動光學重設大小。 不過,只有當第一個相符字型是變數 opsz
軸的變數字型時,才能運作。 將 opsz
指定為字型選取參數,同樣適用於靜態字型。 例如,Sitka 字型系列會在 Windows 11 中實作為變數字型,但實作為 Windows 10 中靜態字型的集合。 靜態字型有不同的非重疊 opsz
座標軸範圍(這些會宣告為字型選取目的的範圍,但它們不是可變座標軸)。 將 opsz
指定為字型選取參數,可讓選取光學大小的正確靜態字型。
印刷字型選取優點和相容性問題
印刷字型選取模型比舊版模型有數個優點,但其純格式有一些潛在的相容性問題。 本節說明優點和相容性問題。 下一節說明混合式字型選取模型,可保留優點,同時降低相容性問題。
印刷字型系列模型的優點如下:
字型可以依設計工具預期分組為系列,而不是因為字型系列模型的限制而分割成子家族。
應用程式可以根據字型大小自動選取正確的
opsz
軸值,而不是將不同的光學大小公開給用戶作為不同的字型系列。可以選取可變字型的任意實例。 例如,如果變數字型支持連續範圍 100-900 中的權數,則印刷樣式模型可以選取 此範圍中的任何 權數。 較舊的字型系列模型只能從字型所定義的具名實例中選擇最接近的粗細。
印刷字型選取模型的相容性問題如下:
某些較舊的字型無法明確地使用印刷樣式系列名稱和座標軸值來選取。
現有的檔可能會依WS系列名稱或 RBIZ 系列名稱參照字型。 使用者也可能預期使用 WWS 和 RBIZ 系列名稱。 例如,檔可能會指定 「Sitka Subheading」 (WWS 系列名稱)而不是 「Sitka」 (印刷姓氏)。
連結庫或架構可能會採用印刷字型系列模型來利用自動光學重設大小,但不提供 API 來指定任意座標軸值。 即使已提供新的 API,架構可能需要使用只指定權數、延展和樣式參數的現有應用程式。
舊字型的相容性問題之所以出現,是因為印刷系列名稱的概念會預先說明字型座標軸值的概念,而此概念與 OpenType 1.8 中的變數字型一起引進。 在OpenType 1.8之前,印刷系列名稱只是表示設計工具的意圖,即一組字型相關,但不保證這些字型可以根據其屬性以程式設計方式區分。 假設範例中,假設下列所有字型都有印刷系列名稱 「Legacy」:
全名 | WWS 系列 | 重量 | 伸展 | 風格 | 錯字系列 | wght | wdth | ital | 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 |
“Legacy” 印刷樣式系列有三個重量,每個重量都有一般和“軟”變體。 如果這些是新的字型,就可以實作為宣告SOFT設計軸。 不過,這些字型會預先設定 OpenType 1.8,因此其唯一的設計軸是從粗細、伸展和樣式衍生而來。 對於每個粗細,此字型系列有兩個具有相同座標軸值的字型,因此無法單獨使用座標軸值來明確選取此系列中的字型。
混合式字型選取演算法
下一節所述的字型選取 API 會使用混合式字型選取演算法,以保留印刷字型選取的優點,同時降低其相容性問題。
混合式字型選取可讓您將字型粗細、字型延展和字型樣式值對應至對應的字型軸值,來提供較舊的字型系列模型的橋樑。 這有助於解決檔和應用程式相容性問題。
此外,混合式字型選取演算法可讓指定的系列名稱成為印刷系列名稱、粗細延展式系列名稱、GDI/RBIZ 系列名稱或完整字型名稱。 比對會以下列其中一種方式發生,優先順序遞減:
名稱符合印刷樣式系列(例如 Sitka)。 比對會發生在印刷樣式系列內,而且會使用所有要求的座標軸值。 如果名稱也符合WWS子家庭(也就是小於印刷樣式家族),則WWS子家庭中的成員資格會當做分手使用。
名稱符合 WWS 系列(例如 Sitka Text)。 比對會在 WWS 系列內發生,而且會忽略 「wght」、“wdth”、“ital” 和 “slnt” 以外的要求座標軸值。
名稱與 GDI 系列相符(例如,巴恩施里夫特·壓縮)。 比對會在 RBIZ 系列內發生,而且會忽略 「wght」、“ital” 和 「slnt」 以外的要求座標軸值。
名稱符合完整名稱(例如,巴恩施里夫特粗體壓縮)。 傳回符合完整名稱的字型。 系統會忽略要求的座標軸值。 允許以完整字型名稱比對,因為 GDI 支援它。
上一節描述一個模棱兩可的印刷系列,稱為「舊版」。 混合式演算法可藉由將「舊版」或「舊版軟」指定為系列名稱,來避免模棱兩可。 如果指定了「舊版軟體」,則不會模棱兩可,因為比對只會發生在WWS系列內。 如果「舊版」被指定,則印刷家族中的所有字型都會被視為相符的候選專案,但使用「舊版」WWS 家族的成員資格來避免模棱兩可,作為系結者。
假設檔指定系列名稱和權數、延展和樣式參數,但沒有座標軸值。 應用程式可以先呼叫 idWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues,將粗細、延展、樣式和字型大小轉換成座標軸值。 然後,應用程式可以將系列名稱和座標軸值傳遞至 IDWriteFontSet4::GetMatchingFonts。 GetMatchingFonts 會以優先順序傳回相符字型的清單,而結果適合指定家族名稱是否為印刷姓氏、粗細延展式系列名稱、RBIZ 系列名稱或全名。 如果指定的系列具有 「opsz」 軸,則會根據字型大小自動選取適當的光學大小。
假設檔指定粗細、伸展和樣式,也 指定座標軸值。 在此情況下,也可以將明確座標軸值傳入 IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues,而 方法傳回的衍生座標軸只會包含未明確指定的字型座標軸。 因此,檔(或應用程式)明確指定的座標軸值優先於衍生自粗細、延展、樣式和字型大小的座標軸值。
混合式字型選取 API
混合式字型選取模型是由下列 IDWriteFontSet4 方法實作:
IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues 方法會將字型大小、粗細、延展和樣式參數轉換為對應的座標軸值。 用戶端傳入的任何明確座標軸值會從衍生的座標軸值中排除。
IDWriteFontSet4::GetMatchingFonts 方法會傳回指定家族名稱和座標軸值陣列之相符字型的優先順序清單。 如上所述,系列名稱參數可以是印刷姓氏、WWS 系列名稱、RBIZ 系列名稱或全名。 (完整名稱可識別特定的字型樣式,例如“Arial Bold Italic”。GetMatchingFonts 支援以完整名稱比對 GDI,這也允許它。
下列其他 DirectWrite API 也會使用混合式字型選取演演算法:
IDWriteTextLayout,方法是呼叫 IDWriteTextLayout4::SetFontAxisValues 或 IDWriteTextLayout4::SetAutomaticFontAxes
使用中字型選取 API 的程式碼範例
本節示範 IDWriteFontSet4::GetMatchingFonts 和 IDWriteFontSet4::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 或 weight-stretch-style 參數來參考字型的檔。 即使應用程式新增了指定任意座標軸值的支援,它也可能需要支援較舊的參數。 若要這樣做,應用程式可以呼叫 IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues,再呼叫 IDWriteFontSet4::GetMatchingFonts。
除了座標軸值之外,下列 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;
}
若要四捨五入範例,以下是命令行剖析函式和主要 函式:
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());
}
}