次の方法で共有


フォントの選択

IDWriteFontSet4 インターフェイスは、フォント セットからフォントを選択するためのメソッドを公開します。 これらのメソッドを使用すると、既存のアプリケーション、ドキュメント、フォントとの互換性を維持しながら、 文字体裁フォント ファミリ モデル に移行できます。

フォントの選択 (フォント マッチングまたはフォント マッピングとも呼ばれます) は、アプリケーションによって渡される入力パラメーターに最も一致する使用可能なフォントを選択するプロセスです。 入力パラメーターは、 まとめて論理フォントと呼ばれる場合があります。 論理フォントには、フォント ファミリ名と、ファミリ内の特定のフォントを示す他の属性が含まれます。 フォント選択アルゴリズムは、論理フォント ("必要なフォント") を使用可能な 物理フォント ("持っているフォント") と一致します。

フォント ファミリは、共通のデザインを共有するフォントの名前付きグループですが、重みなどの属性が異なる場合があります。 フォント ファミリ モデルは、ファミリ内のフォントを区別するために使用できる属性を定義します。 新しい 文字体裁フォント ファミリ モデル には、Windows で使用されていた以前の 2 つのフォント ファミリ モデルに比べて多くの利点があります。 しかし、フォント ファミリ モデルを変更すると、混乱や互換性の問題の機会が生じます。 IDWriteFontSet4 インターフェイスによって公開されるメソッドは、互換性の問題を軽減しながら、文字体裁フォント ファミリ モデルの利点を提供するハイブリッド アプローチを実装します。

このトピックでは、以前のフォント ファミリ モデルと文字体裁フォント ファミリ モデルを比較します。フォント ファミリ モデルを変更することによってもたらされる互換性の課題について説明します。最後に、[IDWriteFontSet4](/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontset4) メソッドを使用してこれらの課題を克服する方法について説明します。

RBIZ フォント ファミリ モデル

GDI アプリケーション エコシステムで使用される事実上のフォント ファミリ モデルは、"4 フォント モデル" または "RBIZ" モデルと呼ばれることもあります。 このモデルの各フォント ファミリには、通常、最大 4 つのフォントがあります。 "RBIZ" ラベルは、一部のフォント ファイルで使用される名前付け規則に由来します。次に例を示します。

ファイル名 [スタイル]
verdana.ttf 通常
verdanab.ttf 太字
verdanai.ttf [斜体]
verdanaz.ttf 太字斜体

GDI では、フォントの選択に使用される入力パラメーターは、ファミリ名 ()、重み ()、斜体 (lfFaceNamelfWeightlfItalic) フィールドを含む LOGFONT 構造体によって定義されます。 フィールドは lfItalic TRUE または FALSE です。 GDI では、フィールドを lfWeightFW_THIN (100) ~ FW_BLACK (900) の範囲内の任意の値にすることができますが、従来の理由から、フォントは長い間、同じ GDI フォント ファミリに 2 つ以下の重み付けしないように設計されています。

初期の一般的なアプリケーション ユーザー インターフェイスには、斜体ボタン (斜体のオンとオフを切り替える) と太字のボタン (標準と太字の重みを切り替える) が含まれていました。 これら 2 つのボタンを使用してファミリ内のフォントを選択すると、RBIZ モデルが想定されます。 したがって、GDI 自体が 2 つ以上の重みをサポートしていても、アプリケーションの互換性により、フォント開発者は RBIZ モデルと一致する方法で GDI ファミリ名 (OpenType 名 ID 1) を設定するようにしました。

たとえば、Arial フォント ファミリに重い "黒" の重みを追加するとします。 論理的には、このフォントは Arial ファミリの一部であるため、"Arial" に設定 lfFaceNamelfWeightFW_BLACKして選択することが期待される場合があります。 ただし、アプリケーション ユーザーが 2 状態の太字ボタンを使用して 3 つの重みを選択する方法はありません。 解決策は、新しいフォントに別のファミリ名を付けることで、ユーザーはフォント ファミリの一覧から [Arial Black] を選択して選択できます。 同様に、太字と斜体のボタンのみを使用して、同じフォント ファミリ内の異なる幅の中から選択する方法がないため、Arial の狭いバージョンでは RBIZ モデルでファミリ名が異なります。 したがって、RBIZ モデルには "Arial"、"Arial Black"、および "Arial Narrow" フォントのファミレスがあります。文字体裁はすべて 1 つのファミリに属します。

これらの例から、フォント ファミリ モデルの制限が、フォントをファミリにグループ化する方法にどのように影響するかを確認できます。 フォント ファミリは名前で識別されるため、使用しているフォント ファミリ モデルに応じて、同じフォントに異なるファミリ名を付けることができます。

DirectWriteは RBIZ フォント ファミリ モデルを直接サポートしていませんが、IDWriteGdiInterop::CreateFontFromLOGFONTIDWriteGdiInterop::ConvertFontToLOGFONT など、RBIZ モデルとの間で変換するメソッドを提供します。 IDWriteFont::GetInformationalStrings メソッドを呼び出し、DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMESを指定することで、フォントの RBIZ ファミリ名を取得することもできます。

Weight-stretch スタイルのフォント ファミリ モデル

太さストレッチ スタイルのフォント ファミリ モデルは、文字体裁フォント ファミリ モデルが導入される前にDirectWriteによって使用される元のフォント ファミリ モデルです。 これは、重量幅傾斜 (WWS) とも呼ばれます。 WWS モデルでは、同じファミリ内のフォントは、重み (DWRITE_FONT_WEIGHT)、ストレッチ (DWRITE_FONT_STRETCH)、スタイル (DWRITE_FONT_STYLE) の 3 つのプロパティによって異なることがあります。

WWS モデルは、2 つの方法で RBIZ モデルよりも柔軟です。 最初に、同じファミリ内のフォントは、ストレッチ (または幅) と、太さとスタイル (標準、斜体、斜体) で区別できます。 次に、同じファミリに 2 つ以上の重みが存在する可能性があります。 この柔軟性は、Arial のすべてのバリエーションを同じ WWS ファミリに含めるのに十分です。 次の表では、選択した Arial フォントの RBIZ および WWS フォント プロパティを比較します。

[名前] RBIZ ファミリ名 lfWeight lfItalic WWS FamilyName Weight Stretch スタイル
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 Narrow Bold Arial Narrow 700 0 Arial 700 3 0

ご覧のように、"Arial Narrow" には "Arial" と同じ lfWeight 値と lfItalic 値があるため、あいまいさを避けるために RBIZ ファミリ名が異なります。 "Arial Black" は、"Arial" ファミリに 2 つ以上の重みを持たないように、RBIZ ファミリ名が異なります。 これに対し、これらのフォントはすべて、同じウェイト ストレッチ スタイルのファミリに含まれています。

ただし、重量ストレッチ スタイルのモデルはオープンエンドではありません。 2 つのフォントの太さ、ストレッチ、スタイルが同じでも、他の方法 (光学式サイズなど) が異なる場合は、同じ WWS フォント ファミリに含めることはできません。 これにより、文字体裁フォント ファミリ モデルに取り込まれます。

文字体裁フォント ファミリ モデル

その前身とは異なり、文字体裁フォント ファミリ モデルはオープン エンド です 。 フォント ファミリ内の任意の数のバリエーション軸をサポートします。

フォント選択パラメーターをデザイン空間の座標と考える場合、ウェイト ストレッチ スタイル モデルでは、太さ、ストレッチ、スタイルを軸として 3 次元座標系を定義します。 WWS ファミリ内の各フォントには、これらの 3 つの軸に沿った座標によって定義された一意の場所が必要です。 フォントを選択するには、WWS ファミリの名前と重み、ストレッチ、スタイルの各パラメーターを指定します。

対照的に、文字体裁フォント ファミリ モデルには、N 次元のデザイン空間があります。 フォント デザイナーは、4 文字の 軸タグで識別される任意の数のデザイン軸を定義できます。 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) 軸の値の組み合わせにマップできます。

もう 1 つの登録済み軸は "opsz" (DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE) です。 Sitka などの光学式フォント ファミリには、"opsz" 軸に沿って異なるフォントが含まれています。つまり、異なるポイント サイズで使用するように設計されています。 WWS フォント ファミリ モデルには光学式サイズ軸がないため、Sitka フォント ファミリは、"Sitka Small"、"Sitka Text"、"Sitka Subheading" など、複数の WWS フォント ファミリに分割する必要があります。 各 WWS フォント ファミリは、異なる光学式サイズに対応しており、指定されたフォント サイズに対して右の WWS ファミリ名を指定するには、ユーザーに任されます。 文字体裁フォント ファミリ モデルを使用すると、ユーザーは単に "Sitka" を選択するだけで、アプリケーションはフォント サイズに基づいて "opsz" 軸の値を自動的に設定できます。

文字体裁フォントの選択と可変フォント

バリエーションの軸の概念は、多くの場合、可変フォントに関連付けられますが、静的フォントにも適用されます。 OpenType STAT (スタイル属性) テーブルは、フォントに含まれるデザイン軸とその軸の値を宣言します。 このテーブルは、可変フォントに必要ですが、静的フォントにも関連しています。

DirectWrite API は、STAT テーブルに存在しない場合や STAT テーブルがない場合でも、すべてのフォントに対して "wght"、"wdth"、"ital"、および "slnt" 軸の値を公開します。 これらの値は、可能であれば STAT テーブルから派生します。 それ以外の場合は、フォントの太さ、フォントのストレッチ、およびフォント スタイルから派生します。

フォント軸は、変数または非変数にすることができます。 静的フォントには非変数軸のみが含まれますが、可変フォントには両方が含まれる場合があります。 変数フォントを使用するには、すべての変数軸が特定の値にバインドされている変数フォント インスタンス を作成する必要があります。 IDWriteFontFace インターフェイスは、静的フォントまたは変数フォントの特定のインスタンスを表します。 指定した軸値を持つ可変フォントの 任意のインスタンス を作成できます。 さらに、変数フォントでは、軸値の定義済みの組み合わせを使用して STAT テーブル内の名前付きインスタンスを宣言できます。 名前付きインスタンスを使用すると、変数フォントは静的フォントのコレクションと同じように動作します。 IDWriteFontFamily または IDWriteFontSet の要素を列挙する場合、静的フォントごとに、および名前付き変数フォント インスタンスごとに 1 つの要素があります。

文字体裁フォントマッチングアルゴリズムは、まず、ファミリ名に基づいて潜在的な一致候補を選択します。 一致候補に可変フォントが含まれている場合、同じ変数フォントのすべての一致候補が 1 つの一致候補に折りたたまれます。この候補では、各変数軸には、その軸の要求された値にできるだけ近い特定の値が割り当てられます。 変数軸に要求された値がない場合は、その軸の既定値が割り当てられます。 次に、一致候補の順序は、その軸値と要求された軸値を比較することによって決定されます。

たとえば、Windows の Sitka 文字体裁ファミリについて考えてみましょう。 Sitka は光学式フォント ファミリです。つまり、"opsz" 軸を持ちます。 Windows 11では、Sitka は次の軸値を持つ 2 つの可変フォントとして実装されます。 と wghtopszは可変ですが、他の軸は非変数であることに注意してください。

ファイル名 "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 軸はそれぞれ と 47512設定されます。 これにより、次の一致するフォントが生成され、斜体以外のitalslntフォントが最初にランク付けされます。

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 の名前付きインスタンスはありません。 これに対し、重みストレッチ スタイルの一致アルゴリズムでは、名前付きインスタンスのみが返されます。

フォントの一致順序

weight-stretch スタイルのフォント ファミリ モデル (IDWriteFontFamily::GetMatchingFonts) と文字体裁フォント ファミリ モデル (IDWriteFontCollection2::GetMatchingFonts) には、オーバーロードされた GetMatchingFonts メソッドが異なります。 どちらの場合も、出力は、各候補フォントが入力プロパティとどの程度一致するかに基づいて、優先順位の降順で一致するフォントの一覧です。 このセクションでは、優先度の決定方法について説明します。

weight-stretch スタイル モデルでは、入力パラメーターはフォントの太さ (DWRITE_FONT_WEIGHT)、フォント ストレッチ (DWRITE_FONT_STRETCH)、フォント スタイル (DWRITE_FONT_STYLE) です。 最適な一致を見つけるためのアルゴリズムは、Mikhail Leonov と David Brown による "WPF フォント選択モデル" というタイトルの 2006 ホワイト ペーパーに記載されています。 「候補の顔リストから顔を照合する」セクションを参照してください。このペーパーはWindows Presentation Foundation (WPF) についてでしたが、DirectWrite後で同じアプローチを使用しました。

このアルゴリズムでは、 フォント属性ベクトルの概念を使用します。これは、重み、ストレッチ、スタイルの特定の組み合わせに対して、次のように計算されます。

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

各ベクトル座標は、対応する属性の "normal" 値を減算し、定数を乗算することによって正規化されることに注意してください。 乗数は、重み、ストレッチ、スタイルの入力値の範囲が大きく異なるという事実を補います。 それ以外の場合、スタイル (0..2) よりも重み (100..999) が優先されます。

一致候補ごとに、一致候補のフォント属性ベクトルと入力フォント属性ベクトルの間でベクター距離とドット積が計算されます。 2 つの一致候補を比較する場合、ベクトル距離が小さい候補の方が一致しやすくなります。 距離が同じ場合、ドット積が小さい候補の方が一致しやすくなります。 ドット積も同じ場合、X 軸、Y 軸、Z 軸に沿った距離がその順序で比較されます。

距離の比較は十分に直感的ですが、二次的なメジャーとしてドット積を使用するには、何らかの説明が必要になる場合があります。 入力の重みが半曲線 (600) で、2 つの候補の重みが黒 (900) と半明 (300) であるとします。 入力ウェイトからの各候補ウェイトの距離は同じですが、黒の重みは原点 (つまり、400 または標準) から同じ方向に配置されるため、ドット積が小さくなります。

文字体裁照合アルゴリズムは、weight-stretch スタイルの一般化です。 各軸値は、N 次元フォント属性ベクトル内の座標として扱われます。 一致候補ごとに、一致候補のフォント属性ベクトルと入力フォント属性ベクトルの間でベクター距離とドット積が計算されます。 ベクトル距離が小さい候補の方が一致します。 距離が同じ場合、ドット積が小さい候補の方が一致しやすくなります。 ドット積も同じ場合は、指定されたウェイト ストレッチ スタイル ファミリ内のプレゼンスをタイ ブレーカーとして使用できます。

ベクトル距離とドット積を計算するには、一致する候補のフォント属性ベクトルと入力フォント属性ベクトルの軸が同じである必要があります。 したがって、いずれかのベクター内の欠落している軸値は、その軸の標準値に置き換えることによって入力されます。 ベクトル座標は、対応する軸の標準 (または "normal") 値を減算し、結果に軸固有の乗数を乗算することによって正規化されます。 各軸の乗数と標準値を次に示します。

乗数 標準値
"wght" 5 400
"wdth" 55 100
"ital" 1400 0
"slnt" 35 0
"opsz" 1 12
other 1 0

乗数は、重量ストレッチ スタイルのアルゴリズムで使用されるものと一致しますが、必要に応じてスケーリングされます。 たとえば、通常の幅は 100 で、ストレッチ 5 に相当します。 これにより、55 対 1100 の乗数が生成されます。 レガシ スタイル属性 (0..2) は斜体と斜体をもつれ、文字体裁モデルでは "ital" 軸 (0..1) と "slnt" 軸 (-90..90) に分解されます。 斜めフォントの既定の 20 度傾斜を想定している場合、これら 2 つの軸に対して選択した乗数は、従来のアルゴリズムと同等の結果を提供します。

文字体裁フォントの選択と光学サイズ

文字体裁フォント ファミリ モデルを使用するアプリケーションでは、フォント選択パラメーターとして軸の値を opsz 指定することで、光学サイズ設定を実装できます。 たとえば、ワープロ アプリケーションでは、フォント サイズと opsz 等しい軸の値をポイント単位で指定できます。 この場合、ユーザーはフォント ファミリとして "Sitka" を選択でき、アプリケーションは正しい opsz 軸値を持つ Sitka のインスタンスを自動的に選択します。 WWS モデルでは、各光学サイズは異なるファミリ名として公開され、ユーザーが適切なものを選択する必要があります。

理論的には、フォント選択に軸の値を別のステップとしてオーバーライドopszすることで、重みストレッチスタイルモデルの下で自動光学サイズ設定を実装できます。 ただし、これは、最初に一致するフォントが可変軸を持つ可変フォントである opsz 場合にのみ機能します。 フォント選択パラメーターとして を指定すると opsz 、静的フォントでも同様に機能します。 たとえば、Sitka フォント ファミリは、Windows 11では可変フォントとして実装されますが、Windows 10の静的フォントのコレクションとして実装されます。 静的フォントには、重複 opsz しない異なる軸範囲があります (これらはフォント選択の目的で範囲として宣言されていますが、可変軸ではありません)。 opszフォント選択パラメーターとして を指定すると、光学式サイズに適した静的フォントを選択できます。

文字体裁フォントの選択の利点と互換性の問題

文字体裁フォント選択モデルには、以前のモデルよりもいくつかの利点がありますが、純粋な形式では互換性の問題が発生する可能性があります。 このセクションでは、利点と互換性の問題について説明します。 次のセクションでは、互換性の問題を軽減しながら利点を維持するハイブリッド フォント選択モデルについて説明します。

文字体裁フォント ファミリ モデルの利点は次のとおりです。

  • フォント ファミリ モデルの制限により、サブファミに分割されるのではなく、デザイナーが意図したとおりにフォントをファミリにグループ化できます。

  • アプリケーションでは、異なる光学サイズをユーザーに異なるフォント ファミリとして公開するのではなく、フォント サイズに基づいて正しい opsz 軸の値を自動的に選択できます。

  • 可変フォントの任意のインスタンスを選択できます。 たとえば、変数フォントで連続範囲 100 から 900 の太さがサポートされている場合、タイポグラフィ モデルでは、この範囲内 の任意 の重みを選択できます。 古いフォント ファミリ モデルでは、フォントで定義されている名前付きインスタンスの中から最も近い太さのみを選択できます。

文字体裁フォント選択モデルに関する互換性の問題は次のとおりです。

  • 一部の古いフォントは、文字体裁ファミリ名と軸の値のみを使用して明確に選択することはできません。

  • 既存のドキュメントは、WWS ファミリ名または RBIZ ファミリ名でフォントを参照している場合があります。 また、ユーザーは WWS と RBIZ のファミリ名を使用することが期待される場合もあります。 たとえば、ドキュメントでは、"Sitka" (文字体裁のファミリ名) の代わりに "Sitka Subheading" (WWS ファミリ名) を指定できます。

  • ライブラリまたはフレームワークでは、文字体裁フォント ファミリ モデルを採用して自動光学サイズ設定を利用できますが、任意の軸値を指定するための API は提供されません。 新しい API が提供されている場合でも、フレームワークでは、重み、ストレッチ、スタイルの各パラメーターのみを指定する既存のアプリケーションを操作する必要がある場合があります。

以前のフォントとの互換性の問題は、文字体裁ファミリ名の概念が、OpenType 1.8 の可変フォントと共に導入されたフォント軸値の概念よりも前に発生するためです。 OpenType 1.8 より前のバージョンでは、文字体裁ファミリ名は、フォントのセットが関連するというデザイナーの意図を表していましたが、それらのフォントがプロパティに基づいてプログラムで区別される保証はありません。 仮定の例として、次のすべてのフォントに文字体裁ファミリ名 "Legacy" があるとします。

[名前] WWS ファミリ Weight Stretch スタイル 入力ミス ファミリ 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

"レガシ" 文字体裁ファミリには 3 つの重みがあり、各重みには通常のバリエーションと "ソフト" のバリエーションがあります。 新しいフォントの場合は、SOFT デザイン軸の宣言として実装できます。 ただし、これらのフォントは OpenType 1.8 より前であるため、デザイン軸は重み、ストレッチ、スタイルから派生したものだけです。 重みごとに、このフォント ファミリには同じ軸値を持つ 2 つのフォントがあるため、軸の値だけを使用してこのファミリ内のフォントを明確に選択することはできません。

ハイブリッド フォント選択アルゴリズム

次のセクションで説明するフォント選択 API では、互換性の問題を軽減しながら、文字体裁フォントの選択の利点を保持するハイブリッド フォント選択アルゴリズムを使用します。

ハイブリッド フォントの選択では、フォントの太さ、フォント のストレッチ、フォント スタイルの値を対応するフォント軸の値にマップできるようにすることで、古いフォント ファミリ モデルのブリッジが提供されます。 これは、ドキュメントとアプリケーションの互換性の問題に対処するのに役立ちます。

また、ハイブリッド フォント選択アルゴリズムを使用すると、指定したファミリ名を文字体裁ファミリ名、重量ストレッチ スタイルファミリ名、GDI/RBIZ ファミリ名、または完全なフォント名にすることができます。 照合は、優先順位の降順で次のいずれかの方法で行われます。

  1. 名前は、文字体裁ファミリ (Sitka など) と一致します。 照合は、文字体裁ファミリ内で行われ、要求されたすべての軸値が使用されます。 名前が WWS サブファミリー (つまり、タイポグラフィ ファミリよりも 1 つ小さい) にも一致する場合は、WWS サブファミリーのメンバーシップがタイ ブレーカーとして使用されます。

  2. 名前は WWS ファミリ (Sitka Text など) と一致します。 一致は WWS ファミリ内で行われ、"wght"、"wdth"、"ital"、"slnt" 以外の要求された軸値は無視されます。

  3. 名前は GDI ファミリ (Bahnschrift Condensed など) と一致します。 照合は RBIZ ファミリ内で行われ、"wght"、"ital"、"slnt" 以外の要求された軸値は無視されます。

  4. 名前は完全な名前と一致します (例: Bahnschrift Bold Condensed)。 完全な名前に一致するフォントが返されます。 要求された軸の値は無視されます。 GDI でサポートされているため、完全なフォント名による照合が許可されます。

前のセクションでは、"レガシ" と呼ばれるあいまいな文字体裁ファミリについて説明しました。 ハイブリッド アルゴリズムを使用すると、ファミリ名として "Legacy" または "Legacy Soft" を指定することで、あいまいさを回避できます。 "Legacy Soft" を指定した場合、一致は WWS ファミリ内でのみ発生するため、あいまいさはありません。 "Legacy" が指定されている場合、文字体裁ファミリ内のすべてのフォントは一致候補と見なされますが、"Legacy" WWS ファミリのメンバーシップをタイ ブレーカーとして使用することであいまいさは回避されます。

ドキュメントでファミリ名、太さ、ストレッチ、スタイルの各パラメーターを指定し、軸の値は指定していないとします。 アプリケーションはまず、 IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues を呼び出すことによって、太さ、ストレッチ、スタイル、フォント サイズを軸の値に変換できます。 その後、アプリケーションはファミリ名と軸の値の両方を IDWriteFontSet4::GetMatchingFonts に渡すことができます。 GetMatchingFonts は、一致するフォントの一覧を優先順位で返します。指定したファミリ名が文字体裁ファミリ名、太さストレッチ スタイルのファミリ名、RBIZ ファミリ名、フル ネームのいずれであるかに関係なく、結果は適切です。 指定したファミリに "opsz" 軸がある場合は、フォント サイズに基づいて適切な光学サイズが自動的に選択されます。

ドキュメントで重み、ストレッチ、スタイルを指定し、軸の値 指定するとします。 その場合、明示的な軸の値を IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues に渡すこともできます。メソッドによって返される派生軸の値には、明示的に指定されていないフォント軸のみが含まれます。 したがって、ドキュメント (またはアプリケーション) によって明示的に指定された軸の値は、太さ、ストレッチ、スタイル、フォント サイズから派生した軸の値よりも優先されます。

ハイブリッド フォント選択 API

ハイブリッド フォント選択モデルは、次の IDWriteFontSet4 メソッドによって実装されます。

  • IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues メソッドは、フォント サイズ、太さ、ストレッチ、スタイルの各パラメーターを対応する軸の値に変換します。 クライアントによって渡された明示的な軸値は、派生軸の値から除外されます。

  • IDWriteFontSet4::GetMatchingFonts メソッドは、ファミリ名と軸値の配列を指定して、一致するフォントの優先順位付きリストを返します。 前述のように、ファミリ名パラメーターには、文字体裁ファミリ名、WWS ファミリ名、RBIZ ファミリ名、またはフル ネームを指定できます。 (フル ネームは、"Arial Bold Italic" などの特定のフォント スタイルを識別します。 GetMatchingFonts では、 GDI を使用して comaptibiltiy を大きくするために、完全な名前による照合がサポートされています。これにより、これを使用することもできます)。

次の他のDirectWrite API では、ハイブリッド フォント選択アルゴリズムも使用されます。

使用中のフォント選択 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());
    }
}