次の方法で共有


フォントの選択

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

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

たとえば、Arial フォント ファミリに重い "黒" の重みを追加するとします。 論理的には、このフォントは Arial ファミリの一部であるため、 lfFaceName を "Arial" に設定し、 lfWeightFW_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 ファミリ名を取得することもできます。

太さストレッチ スタイルのフォント ファミリ モデル

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

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

氏名 RBIZ ファミリ名 lfWeight lfItalic WWS FamilyName 重量 伸ばす 様式
Arial Arial 400 0 Arial 400 5 0
Arial Bold Arial 七百 0 Arial 七百 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 七百 0 Arial 七百 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_ITALIC および DWRITE_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 つの可変フォントとして実装されています。 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 軸はそれぞれ 12475に設定されます。 これにより、次の一致するフォントが生成され、非斜体フォントが最初にランク付けされます。これは、 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

上記の例では、一致するフォントは任意の可変フォント インスタンスです。 重量が 475 の Sitka の名前付きインスタンスはありません。 これに対し、重みストレッチ スタイルの一致アルゴリズムでは、名前付きインスタンスのみが返されます。

フォントの一致順序

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

ウェイト ストレッチ スタイル モデルでは、入力パラメーターはフォントの太さ (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 または法線) から同じ方向に向いているため、ドット積は小さくなります。

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

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

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

乗数は、ウェイト ストレッチ スタイル アルゴリズムで使用されているものと一致しますが、必要に応じてスケーリングされます。 たとえば、通常の幅は 100 で、ストレッチ 5 に相当します。 これにより、乗数は 55 対 1100 となります。 従来のスタイル属性 (0..2) は斜体と斜体を含み、文字体裁モデルでは "ital" 軸 (0..1) と "slnt" 軸 (-90..90) に分解されます。 これら 2 つの軸に対して選択された乗数は、斜めフォントの既定の 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" (文字体裁のファミリ名) の代わりに "Sitka Subheading" (WWS ファミリ名) を指定できます。

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

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

氏名 WWS ファミリ 重量 伸ばす 様式 Typo Family wght wdth ital slnt
レガシー レガシー 400 5 0 レガシー 400 100 0 0
従来の太字 レガシー 七百 5 0 レガシー 七百 100 0 0
レガシ ブラック レガシー 900 5 0 レガシー 900 100 0 0
従来のソフト 従来のソフト 400 5 0 レガシー 400 100 0 0
従来のソフト 太字 従来のソフト 七百 5 0 レガシー 七百 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" または "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 との互換性を高める完全な名前による照合がサポートされています。これにより、これを使用することもできます)。

次の他の 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 または重量ストレッチ スタイルのパラメーターを使用してフォントを参照するドキュメントを操作する必要がある場合があります。 アプリケーションで任意の軸値を指定するためのサポートが追加された場合でも、古いパラメーターもサポートする必要があります。 これを行うには、アプリケーションで 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;
}

サンプルを丸めるために、コマンド ライン解析関数と 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());
    }
}