Freigeben über


Schriftartauswahl

Die IDWriteFontSet4-Schnittstelle macht Methoden zum Auswählen von Schriftarten aus einem Schriftartensatz verfügbar. Diese Methoden ermöglichen den Übergang zum typografischen Schriftartenfamilienmodell , während die Kompatibilität mit vorhandenen Anwendungen, Dokumenten und Schriftarten beibehalten wird.

Die Schriftartenauswahl (manchmal auch als Schriftartenabgleich oder Schriftartenzuordnung bezeichnet) ist der Prozess, bei dem die verfügbaren Schriftarten ausgewählt werden, die am besten mit eingabeparametern übereinstimmen, die von Ihrer Anwendung übergeben werden. Die Eingabeparameter werden manchmal gemeinsam als logische Schriftart bezeichnet. Eine logische Schriftart enthält einen Schriftfamiliennamen sowie andere Attribute, die eine bestimmte Schriftart innerhalb der Familie angeben. Ein Schriftauswahlalgorithmus gleicht die logische Schriftart ("die gewünschte Schriftart") mit einer verfügbaren physischen Schriftart ("eine Schriftart, die Sie haben") ab.

Eine Schriftartenfamilie ist eine benannte Gruppe von Schriftarten, die ein gemeinsames Design haben, sich jedoch in Attributen wie der Gewichtung unterscheiden können. Ein Schriftfamilienmodell definiert, welche Attribute verwendet werden können, um Schriftarten innerhalb einer Familie zu unterscheiden. Das neue typografische Schriftfamilienmodell hat viele Vorteile gegenüber den beiden vorherigen Schriftartenfamilienmodellen, die unter Windows verwendet wurden. Das Ändern von Schriftfamilienmodellen führt jedoch zu Verwirrungs- und Kompatibilitätsproblemen. Die methoden, die von der IDWriteFontSet4-Schnittstelle verfügbar gemacht werden, implementieren einen Hybridansatz, der die Vorteile des typografischen Schriftfamilienmodells bietet und gleichzeitig Kompatibilitätsprobleme mindert.

In diesem Thema werden die älteren Schriftfamilienmodelle mit dem typografischen Schriftfamilienmodell verglichen. Erläutert die Kompatibilitätsanforderungen, die durch die Änderung von Schriftfamilienmodellen verbunden sind; und schließlich wird erläutert, wie diese Herausforderungen mit den Methoden [IDWriteFontSet4](/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontset4) überwunden werden können.

RBIZ-Schriftfamilienmodell

Das de facto im GDI-Anwendungsökosystem verwendete Modell der Schriftfamilie wird manchmal auch als "Modell mit vier Schriftarten" oder "RBIZ" bezeichnet. Jede Schriftartenfamilie in diesem Modell verfügt in der Regel über höchstens vier Schriftarten. Die Bezeichnung "RBIZ" stammt aus der Namenskonvention, die für einige Schriftartdateien verwendet wird, z. B.:

Dateiname Schriftschnitt
verdana.ttf Regulär
verdanab.ttf Fett
verdanai.ttf Kursiv
verdanaz.ttf Fett kursiv

Bei GDI werden die Eingabeparameter, die zum Auswählen einer Schriftart verwendet werden, durch die LOGFONT-Struktur definiert, die Familiennamen (lfFaceName), Gewichtung (lfWeight) und Kursivfelder (lfItalic) umfasst. Das lfItalic Feld ist entweder TRUE oder FALSE. GDI ermöglicht es, dass das lfWeight Feld ein beliebiger Wert im Bereich FW_THIN (100) bis FW_BLACK (900) sein kann. Aus historischen Gründen wurden Schriftarten jedoch schon lange so entworfen, dass es in derselben GDI-Schriftfamilie nicht mehr als zwei Gewichtungen gibt.

Beliebte Anwendungsbenutzeroberflächen von Anfang an enthielten eine kursive Schaltfläche (zum Aktivieren und Deaktivieren von Kursiv) und eine fett formatierte Schaltfläche (um zwischen normaler und fetter Gewichtung umzuschalten). Die Verwendung dieser beiden Schaltflächen zum Auswählen von Schriftarten innerhalb einer Familie setzt das RBIZ-Modell voraus. Obwohl GDI selbst mehr als zwei Gewichtungen unterstützt, veranlasste die Anwendungskompatibilität entwickler daher, den GDI-Familiennamen (OpenType-Name-ID 1) auf eine Weise festzulegen, die mit dem RBIZ-Modell konsistent war.

Angenommen, Sie möchten der Arial-Schriftfamilie eine höhere "Schwarz"-Gewichtung hinzufügen. Logischerweise ist diese Schriftart Teil der Arial-Familie, daher können Sie erwarten, dass Sie sie auswählen, indem Sie auf "Arial" festlegen lfFaceName und lfWeightFW_BLACK. Es gibt jedoch keine Möglichkeit für einen Anwendungsbenutzer, zwischen drei Gewichtungen zu wählen, die eine fett formatierte Schaltfläche mit zwei Status verwenden. Die Lösung bestand darin, der neuen Schriftart einen anderen Familiennamen zu geben, sodass der Benutzer sie auswählen konnte, indem er "Arial Black" aus der Liste der Schriftfamilien auswählt. Ebenso gibt es keine Möglichkeit, zwischen verschiedenen Breiten in derselben Schriftfamilie zu wählen, die nur Fett- und Kursivschaltflächen verwendet, sodass die schmalen Versionen von Arial unterschiedliche Familiennamen im RBIZ-Modell haben. So haben wir "Arial", "Arial Black" und "Arial Narrow"-Schriftarten im RBIZ-Modell, obwohl diese alle typografisch zu einer Familie gehören.

Anhand dieser Beispiele kann man sehen, wie sich die Einschränkungen eines Schriftartenfamilienmodells auf die Gruppierung von Schriftarten in Familien auswirken können. Da Schriftfamilien anhand des Namens identifiziert werden, bedeutet dies, dass dieselbe Schriftart unterschiedliche Familiennamen haben kann, je nachdem, welches Schriftfamilienmodell Sie verwenden.

DirectWrite unterstützt das RBIZ-Schriftfamilienmodell nicht direkt, bietet jedoch Methoden zum Konvertieren in und aus dem RBIZ-Modell, z. B. IDWriteGdiInterop::CreateFontFromLOGFONT und IDWriteGdiInterop::ConvertFontToLOGFONT. Sie können auch den RBIZ-Familiennamen einer Schriftart abrufen, indem Sie deren IDWriteFont::GetInformationalStrings-Methode aufrufen und DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES angeben.

Modell der Schriftfamilie "Gewichtung", "Stretch"

Das Modell der Schriftfamilie mit Gewichtung und Stretch ist das ursprüngliche Schriftfamilienmodell, das von DirectWrite vor der Einführung des typografischen Schriftfamilienmodells verwendet wurde. Es wird auch als Gewichtsbreite-Steigung (WWS) bezeichnet. Im WWS-Modell können Schriftarten innerhalb derselben Familie durch drei Eigenschaften unterscheiden: Gewichtung (DWRITE_FONT_WEIGHT), Stretch (DWRITE_FONT_STRETCH) und Stil (DWRITE_FONT_STYLE).

Das WWS-Modell ist auf zwei Arten flexibler als das RBIZ-Modell. Erstens können Schriftarten derselben Familie nach Stretch (oder Breite) sowie nach Gewichtung und Stil (normal, kursiv oder schräg) unterschieden werden. Zweitens können in derselben Familie mehr als zwei Gewichtungen vorhanden sein. Diese Flexibilität reicht aus, um alle Varianten von Arial in die gleiche WWS-Familie aufzunehmen. In der folgenden Tabelle werden die Schriftarteigenschaften von RBIZ und WWS für eine Auswahl von Arial-Schriftarten verglichen:

Vollständiger Name RBIZ-Familienname lfWeight lfItalic WWS FamilyName Weight Stretch Stil
Arial Arial 400 0 Arial 400 5 0
Arial, fett 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, fett Arial Narrow 700 0 Arial 700 3 0

Wie Sie sehen können, hat "Arial Narrow" die gleichen lfWeight Werte lfItalic und wie "Arial", sodass es einen anderen RBIZ-Familiennamen hat, um Mehrdeutigkeiten zu vermeiden. "Arial Black" hat einen anderen RBIZ-Familiennamen, um mehr als zwei Gewichte in der "Arial"-Familie zu vermeiden. Im Gegensatz dazu befinden sich alle diese Schriftarten in derselben Gewichtungs-Stretch-Stilfamilie.

Dennoch ist das Gewichts-Stretch-Modell nicht offen. Wenn zwei Schriftarten die gleiche Gewichtung, Dehnen und den gleichen Stil aufweisen, sich aber auf andere Weise unterscheiden (z. B. optische Größe), können sie nicht in derselben WWS-Schriftfamilie enthalten sein. Dies bringt uns zum typografischen Schriftfamilienmodell.

Typografisches Schriftfamilienmodell

Im Gegensatz zu seinen Vorgängern ist das typografische Schriftfamilienmodell offen. Es unterstützt eine beliebige Anzahl von Variationsachsen innerhalb einer Schriftartenfamilie.

Wenn Sie sich Schriftartenauswahlparameter als Koordinaten in einem Entwurfsraum vorstellen, definiert das Modell im Format "Gewichtung/Dehnung" ein dreidimensionales Koordinatensystem mit Gewichtung, Dehnung und Stil als Achsen. Jede Schriftart in einer WWS-Familie muss über eine eindeutige Position verfügen, die durch ihre Koordinaten entlang dieser drei Achsen definiert ist. Um eine Schriftart auszuwählen, geben Sie einen WWS-Familiennamen und die Parameter für Gewichtung, Stretch und Stil an.

Im Gegensatz dazu verfügt das typografische Schriftfamilienmodell über einen N-dimensionalen Entwurfsraum. Ein Schriftarten-Designer kann eine beliebige Anzahl von Entwurfsachsen definieren, die jeweils durch ein vierstellige Achsentag identifiziert werden. Die Position einer bestimmten Schriftart im N-dimensionalen Entwurfsraum wird durch ein Array von Achsenwerten definiert, wobei jeder Achsenwert ein Achsentag und einen Gleitkommawert umfasst. Um eine Schriftart auszuwählen, geben Sie einen typografischen Familiennamen und ein Array von Achsenwerten (DWRITE_FONT_AXIS_VALUE Strukturen) an.

Obwohl die Anzahl der Schriftartachsen offen ist, gibt es einige registrierte Achsen mit Standardbedeutungen, und die Gewichtungs-, Stretch- und Stilwerte können registrierten Achsenwerten zugeordnet werden. DWRITE_FONT_WEIGHT kann einem Achsenwert für "Ght" (DWRITE_FONT_AXIS_TAG_WEIGHT) zugeordnet werden. DWRITE_FONT_STRETCH kann einem "wdth"-Achsenwert (DWRITE_FONT_AXIS_TAG_WIDTH) zugeordnet werden. DWRITE_FONT_STYLE können einer Kombination aus "Ital"- und "slnt"-Achsenwerten (DWRITE_FONT_AXIS_TAG_ITALIC und DWRITE_FONT_AXIS_TAG_SLANT) zugeordnet werden.

Eine weitere registrierte Achse ist "opsz" (DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE). Eine optische Schriftfamilie wie Sitka enthält Schriftarten, die sich entlang der Opsz-Achse unterscheiden, was bedeutet, dass sie für die Verwendung in verschiedenen Punktgrößen konzipiert sind. Das WWS-Schriftfamilienmodell verfügt nicht über eine optische Größenachse, sodass die Sitka-Schriftfamilie in mehrere WWS-Schriftfamilien aufgeteilt werden muss: "Sitka Small", "Sitka Text", "Sitka Subheading" usw. Jede WWS-Schriftfamilie entspricht einer anderen optischen Größe, und es bleibt dem Benutzer überlassen, den richtigen WWS-Familiennamen für einen bestimmten Schriftgrad anzugeben. Mit dem typografischen Schriftfamilienmodell kann der Benutzer einfach "Sitka" auswählen, und die Anwendung kann den Achsenwert "opsz" basierend auf dem Schriftgrad automatisch festlegen.

Typografische Schriftartenauswahl und Variable Schriftarten

Das Konzept der Variationsachsen wird häufig variablen Schriftarten zugeordnet, gilt aber auch für statische Schriftarten. Die OpenType STAT-Tabelle (Formatvorlagenattribute) deklariert, welche Entwurfsachsen eine Schriftart hat und welche Werte diese Achsen haben. Diese Tabelle ist für variable Schriftarten erforderlich, ist aber auch für statische Schriftarten relevant.

Die DirectWrite-API macht die Achsenwerte "wght", "wdth", "ital" und "slnt" für jede Schriftart verfügbar, auch wenn sie nicht in der STAT-Tabelle vorhanden sind oder keine STAT-Tabelle vorhanden ist. Diese Werte werden nach Möglichkeit aus der STAT-Tabelle abgeleitet. Andernfalls werden sie von der Schriftstärke, der Schriftstreckung und dem Schriftstil abgeleitet.

Schriftartachsen können variabel oder nicht variabel sein. Eine statische Schriftart verfügt nur über nicht variable Achsen, während eine variable Schriftart beides haben kann. Um eine Variablenschriftart zu verwenden, müssen Sie eine variable Schriftart instance erstellen, in der alle Variablenachsen an bestimmte Werte gebunden wurden. Die IDWriteFontFace-Schnittstelle stellt entweder eine statische Schriftart oder eine bestimmte instance einer Variablenschriftart dar. Es ist möglich, eine beliebige instance einer Variablenschriftart mit angegebenen Achsenwerten zu erstellen. Darüber hinaus kann eine Variableschriftart benannte Instanzen in der STAT-Tabelle mit vordefinierten Kombinationen von Achsenwerten deklarieren. Benannte Instanzen ermöglichen es einer Variablenschriftart, sich ähnlich wie eine Sammlung statischer Schriftarten zu verhalten. Wenn Sie Elemente eines IDWriteFontFamily- oder IDWriteFontSet-Elements aufzählen, gibt es ein Element für jede statische Schriftart und für jede benannte Variablenschriftart instance.

Der typografische Schriftabgleichsalgorithmus wählt zunächst potenzielle Übereinstimmungskandidaten basierend auf dem Familiennamen aus. Wenn die Übereinstimmungskandidaten variable Schriftarten enthalten, werden alle Übereinstimmungskandidaten für dieselbe Variable Schriftart in einen Übereinstimmungskandidaten reduziert, wobei jeder Variablenachse ein bestimmter Wert zugewiesen wird, der dem angeforderten Wert für diese Achse so nahe wie möglich ist. Wenn kein angeforderter Wert für eine variable Achse vorhanden ist, wird ihr der Standardwert für diese Achse zugewiesen. Die Reihenfolge der Übereinstimmungskandidaten wird dann bestimmt, indem ihre Achsenwerte mit den angeforderten Achsenwerten verglichen werden.

Betrachten Sie beispielsweise die typografische Sitka-Familie in Windows. Sitka ist eine optische Schriftfamilie, was bedeutet, dass sie eine "opsz"-Achse hat. In Windows 11 wird Sitka als zwei Variablenschriftarten mit den folgenden Achsenwerten implementiert. Beachten Sie, dass die opsz Achsen und wght variabel sind, während die anderen Achsen nicht variabel sind.

Dateiname "opsz" "wght" "wdth" "kursiv" "slnt"
SitkaVF.ttf 6-27.5 400-700 100 0 0
SitkaVF-Italic.ttf 6-27.5 400-700 100 1 -12

Angenommen, die angeforderten Achsenwerte sind opsz:12 wght:475 wdth:100 ital:0 slnt:0. Für jede Variablenschriftart erstellen wir einen Verweis auf eine Variableschriftart instance, in der jeder Variablenachse ein bestimmter Wert zugewiesen wird. Die opsz Achsen und wght sind auf 12 bzw 475. festgelegt. Dies ergibt die folgenden übereinstimmenden Schriftarten, wobei die nicht kursiv formatierte Schriftart an erster Stelle steht, da sie eine bessere Übereinstimmung mit den ital Achsen und slnt ist:

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

Im obigen Beispiel sind die übereinstimmenden Schriftarten beliebige Variablenschriftarten. Es gibt keine benannte instance von Sitka mit dem Gewicht 475. Im Gegensatz dazu gibt der Abgleichsalgorithmus im Weight-Stretch-Stil nur benannte Instanzen zurück.

Schriftartenabgleichsreihenfolge

Es gibt verschiedene überladene GetMatchingFonts-Methoden für das Schriftfamilienmodell im Gewichts-Stretch-Stil (IDWriteFontFamily::GetMatchingFonts) und das typografische Schriftfamilienmodell (IDWriteFontCollection2::GetMatchingFonts). In beiden Fällen ist die Ausgabe eine Liste übereinstimmender Schriftarten in absteigender Reihenfolge der Priorität, je nachdem, wie gut die einzelnen Kandidatenschriftarten mit den Eingabeeigenschaften übereinstimmen. In diesem Abschnitt wird beschrieben, wie die Priorität bestimmt wird.

Im Format "Weight/Stretch"-Format sind die Eingabeparameter Schriftstärke (DWRITE_FONT_WEIGHT), Schriftstreckung (DWRITE_FONT_STRETCH) und Schriftartformat (DWRITE_FONT_STYLE). Der Algorithmus für die Suche nach der besten Übereinstimmung wurde in einem Whitepaper von 2006 mit dem Titel "WPF Font Selection Model" von Mikhail Leonov und David Brown dokumentiert. Weitere Informationen finden Sie im Abschnitt "Abgleichen eines Gesichts aus der Kandidaten-Gesichtsliste". In diesem Artikel ging es um Windows Presentation Foundation (WPF), aber DirectWrite verfolgten später den gleichen Ansatz.

Der Algorithmus verwendet das Konzept des Schriftartattributvektors, der für eine bestimmte Kombination aus Gewichtung, Stretch und Stil wie folgt berechnet wird:

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

Beachten Sie, dass jede Vektorkoordinate normalisiert wird, indem der "normal"-Wert für das entsprechende Attribut subtrahiert und mit einer Konstante multipliziert wird. Die Multiplikatoren kompensieren die Tatsache, dass die Bereiche der Eingabewerte für Gewichtung, Stretch und Stil sehr unterschiedlich sind. Andernfalls würde die Gewichtung (100..999) den Stil (0..2) überwiegt.

Für jeden Übereinstimmungskandidaten werden ein Vektorabstand und ein Punktprodukt zwischen dem Attributvektor des Übereinstimmungskandidaten und dem Eingabeschrift-Attributvektor berechnet. Beim Vergleich von zwei Übereinstimmungskandidaten ist der Kandidat mit dem kleineren Vektorabstand die bessere Übereinstimmung. Wenn die Entfernungen identisch sind, ist der Kandidat mit dem kleineren Punktprodukt eine bessere Übereinstimmung. Wenn auch das Punktprodukt identisch ist, werden die Abstände entlang der X-, Y- und Z-Achse in dieser Reihenfolge verglichen.

Der Vergleich von Entfernungen ist intuitiv genug, aber die Verwendung von Punktprodukt als sekundäres Maß kann eine Erklärung erfordern. Angenommen, die Eingabegewichtung ist semibold (600), und zwei Kandidatengewichtungen sind schwarz (900) und semilight (300). Der Abstand der einzelnen Kandidatengewichte von der Eingabegewichtung ist identisch, aber das schwarze Gewicht liegt in derselben Richtung vom Ursprung (d. h. 400 oder normal), sodass es ein kleineres Punktprodukt aufweist.

Der typografische Abgleichsalgorithmus ist eine Generalisierung des Algorithmus für den Gewichtungs-Stretch-Stil. Jeder Achsenwert wird als Koordinate in einem N-dimensionalen Schriftart-Attributvektor behandelt. Für jeden Übereinstimmungskandidaten werden ein Vektorabstand und ein Punktprodukt zwischen dem Schriftartattributevektor des Übereinstimmungskandidaten und dem Eingabeschrifttributvektor berechnet. Der Kandidat mit dem kleineren Vektorabstand ist die bessere Übereinstimmung. Wenn die Entfernungen identisch sind, ist der Kandidat mit dem kleineren Punktprodukt besser geeignet. Wenn auch das Punktprodukt identisch ist, kann das Vorhandensein in einer angegebenen Gewichts-Stretch-Stil-Familie als Tiebreaker verwendet werden.

Zum Berechnen des Vektorabstands- und Punktprodukts müssen der Schriftartattributevektor eines Kandidaten und der Attributvektor der Eingabeschrift die gleichen Achsen aufweisen. Daher wird jeder fehlende Achsenwert in einem der beiden Vektoren ausgefüllt, indem der Standardwert für diese Achse ersetzt wird. Vektorkoordinaten werden normalisiert, indem der Standardwert (oder "normal") für die entsprechende Achse subtrahiert und das Ergebnis mit einem achsenspezifischen Multiplikator multipliziert wird. Im Folgenden sind die Multiplikatoren und Standardwerte für jede Achse aufgeführt:

Achse Multiplikator Standardwert
"wght" 5 400
"wdth" 55 100
"Kursiv" 1400 0
"slnt" 35 0
"opsz" 1 12
Andere 1 0

Die Multiplikatoren sind konsistent mit denen, die vom Algorithmus im Gewichts-Stretch-Stil verwendet werden, aber nach Bedarf skaliert. Beispielsweise ist die normale Breite 100, was der Stretch 5 entspricht. Dies ergibt einen Multiplikator von 55 gegenüber 1100. Das Legacystilattribut (0..2) verschränkt Kursiv und Schrägheit, die im typografischen Modell in eine "kursiv"-Achse (0..1) und eine "slnt"-Achse (-90..90) zerlegt werden. Die gewählten Multiplikatoren für diese beiden Achsen liefern dem Legacyalgorithmus gleichwertige Ergebnisse, wenn wir von einer Standardneigung von 20 Grad für schräge Schriftarten ausgehen.

Typografische Schriftauswahl und optische Größe

Eine Anwendung, die das typografische Schriftfamilienmodell verwendet, kann die optische Größenanpassung implementieren, indem sie einen opsz Achsenwert als Schriftartauswahlparameter angibt. Beispielsweise könnte eine Textverarbeitungsanwendung einen opsz Achsenwert angeben, der dem Schriftgrad in Punkt entspricht. In diesem Fall könnte ein Benutzer "Sitka" als Schriftfamilie auswählen, und die Anwendung wählt automatisch eine instance von Sitka mit dem richtigen opsz Achsenwert aus. Unter dem WWS-Modell wird jede optische Größe als anderer Familienname verfügbar gemacht, und es liegt beim Benutzer, die richtige Größe auszuwählen.

Theoretisch könnte man eine automatische optische Größenanpassung unter dem Gewichts-Stretch-Stil-Modell implementieren, indem der Achsenwert als separater Schritt nach der opsz Schriftartauswahl überschrieben wird. Dies funktioniert jedoch nur, wenn die erste übereinstimmende Schriftart eine variable Schriftart mit einer variablen opsz Achse ist. Die Angabe opsz als Schriftauswahlparameter funktioniert auch für statische Schriftarten. Beispielsweise wird die Sitka-Schriftfamilie als variable Schriftarten in Windows 11 implementiert, aber als Sammlung statischer Schriftarten in Windows 10. Die statischen Schriftarten weisen unterschiedliche, nicht überlappende opsz Achsenbereiche auf (diese werden für die Schriftauswahl als Bereiche deklariert, aber keine variablen Achsen). Wenn Sie als Schriftartenauswahlparameter angeben opsz , kann die richtige statische Schriftart für die optische Größe ausgewählt werden.

Vorteile der typografischen Schriftauswahl und Kompatibilitätsprobleme

Das typografische Schriftauswahlmodell hat mehrere Vorteile gegenüber früheren Modellen, aber in seiner reinen Form hat es einige potenzielle Kompatibilitätsprobleme. In diesem Abschnitt werden die Vorteile und Kompatibilitätsprobleme beschrieben. Im nächsten Abschnitt wird ein Hybridschriftartenauswahlmodell beschrieben, bei dem die Vorteile beibehalten und gleichzeitig die Kompatibilitätsprobleme gemildert werden.

Vorteile des typografischen Schriftfamilienmodells sind:

  • Schriftarten können wie vom Designer vorgesehen in Familien gruppiert werden, anstatt aufgrund von Einschränkungen des Schriftfamilienmodells in Unterfamilien aufzuteilen.

  • Eine Anwendung kann automatisch den richtigen opsz Achsenwert basierend auf dem Schriftgrad auswählen, anstatt dem Benutzer unterschiedliche optische Größen als unterschiedliche Schriftartenfamilien verfügbar zu machen.

  • Es können beliebige Instanzen von Variablenschriftarten ausgewählt werden. Wenn beispielsweise eine variable Schriftart Gewichtungen im kontinuierlichen Bereich 100-900 unterstützt, kann das typografische Modell eine beliebige Gewichtung in diesem Bereich auswählen. Die älteren Schriftfamilienmodelle können nur die nächste Gewichtung aus den benannten Instanzen auswählen, die durch die Schriftart definiert sind.

Kompatibilitätsprobleme mit dem typografischen Schriftartauswahlmodell sind:

  • Einige ältere Schriftarten können nicht eindeutig ausgewählt werden, indem nur der typografische Familienname und die Achsenwerte verwendet werden.

  • Vorhandene Dokumente können auf Schriftarten anhand des WWS-Familiennamens oder des RBIZ-Familiennamens verweisen. Benutzer können auch erwarten, WWS- und RBIZ-Familiennamen zu verwenden. Ein Dokument kann beispielsweise "Sitka-Unterüberschriften" (ein WWS-Familienname) anstelle von "Sitka" (typografischer Familienname) angeben.

  • Eine Bibliothek oder ein Framework kann das typografische Schriftfamilienmodell übernehmen, um die vorteile der automatischen optischen Größenanpassung zu nutzen, aber keine API zum Angeben beliebiger Achsenwerte bereitstellen. Selbst wenn eine neue API bereitgestellt wird, muss das Framework möglicherweise mit vorhandenen Anwendungen arbeiten, die nur Gewichts-, Stretch- und Stilparameter angeben.

Das Kompatibilitätsproblem mit älteren Schriftarten tritt auf, da das Konzept des typografischen Familiennamens vor dem Konzept der Schriftartachsenwerte zurückstammt, die zusammen mit variablen Schriftarten in OpenType 1.8 eingeführt wurden. Vor OpenType 1.8 drückte der typografische Familienname lediglich die Absicht des Designers aus, dass eine Reihe von Schriftarten miteinander in Beziehung stehen, aber ohne Garantie, dass diese Schriftarten programmgesteuert basierend auf ihren Eigenschaften unterschieden werden könnten. Angenommen, alle folgenden Schriftarten haben den typografischen Familiennamen "Legacy":

Vollständiger Name WWS-Familie Weight Stretch Stil Tippfehlerfamilie wght wdth Ital slnt
Vorversion Vorversion 400 5 0 Vorversion 400 100 0 0
Legacy Fett Vorversion 700 5 0 Vorversion 700 100 0 0
Legacy Black Vorversion 900 5 0 Vorversion 900 100 0 0
Legacy Soft Legacy Soft 400 5 0 Vorversion 400 100 0 0
Legacy Soft Bold Legacy Soft 700 5 0 Vorversion 700 100 0 0
Legacy Soft Black Legacy Soft 900 5 0 Vorversion 900 100 0 0

Die typografische Familie "Legacy" hat drei Gewichtungen, und jede Gewichtung hat reguläre und "weiche" Varianten. Wenn es sich um neue Schriftarten handelt, könnten sie als Deklarierung einer SOFT-Entwurfsachse implementiert werden. Diese Schriftarten sind jedoch älter als OpenType 1.8, sodass ihre einzigen Entwurfsachsen diejenigen sind, die von Gewicht, Stretch und Stil abgeleitet sind. Für jede Gewichtung verfügt diese Schriftartenfamilie über zwei Schriftarten mit identischen Achsenwerten, sodass es nicht möglich ist, eine Schriftart in dieser Familie allein mithilfe von Achsenwerten eindeutig auszuwählen.

Algorithmus für die Hybridschriftartenauswahl

Die im nächsten Abschnitt beschriebenen APIs für die Schriftauswahl verwenden einen Hybridschriftauswahlalgorithmus, der die Vorteile der typografischen Schriftauswahl beibehalten und gleichzeitig ihre Kompatibilitätsprobleme mindert.

Die Hybridschriftartenauswahl bietet eine Brücke zu älteren Schriftartenfamilienmodellen, indem die Werte für Schriftgrad, Schriftgrad und Schriftartformat den entsprechenden Werten der Schriftartachse zugeordnet werden können. Dies hilft, Probleme mit der Dokument- und Anwendungskompatibilität zu beheben.

Darüber hinaus ermöglicht der Hybridschriftauswahlalgorithmus, dass der angegebene Familienname ein typografischer Familienname, ein Familienname im Gewichts-Stretch-Stil, ein GDI/RBIZ-Familienname oder ein vollständiger Schriftname sein kann. Der Abgleich erfolgt auf eine der folgenden Arten, in absteigender Reihenfolge der Priorität:

  1. Der Name entspricht einer typografischen Familie (z. B. Sitka). Der Abgleich erfolgt innerhalb der typografischen Familie, und alle angeforderten Achsenwerte werden verwendet. Wenn der Name auch mit einer WWS-Unterfamilie übereinstimmt (d. h. eine, die kleiner ist als die typografische Familie), wird die Mitgliedschaft in der WWS-Unterfamilie als Tiebreaker verwendet.

  2. Der Name entspricht einer WWS-Familie (z. B. Sitka Text). Der Abgleich erfolgt innerhalb der WWS-Familie, und andere angeforderte Achsenwerte als "wght", "wdth", "ital" und "slnt" werden ignoriert.

  3. Der Name entspricht einer GDI-Familie (z. B. Bahnschrift Condensed). Der Abgleich erfolgt innerhalb der RBIZ-Familie, und andere angeforderte Achsenwerte als "wght", "ital" und "slnt" werden ignoriert.

  4. Der Name entspricht einem vollständigen Namen (z. B. Bahnschrift Bold Condensed). Die Schriftart, die dem vollständigen Namen entspricht, wird zurückgegeben. Angeforderte Achsenwerte werden ignoriert. Der Abgleich mit dem vollständigen Schriftnamen ist zulässig, da GDI dies unterstützt.

Im vorherigen Abschnitt wurde eine mehrdeutige typografische Familie namens "Legacy" beschrieben. Mit dem Hybridalgorithmus kann die Mehrdeutigkeit vermieden werden, indem entweder "Legacy" oder "Legacy Soft" als Familienname angegeben wird. Wenn "Legacy Soft" angegeben ist, besteht keine Mehrdeutigkeit, da der Abgleich nur innerhalb der WWS-Familie erfolgt. Wenn "Legacy" angegeben ist, werden alle Schriftarten in der typografischen Familie als Übereinstimmungskandidaten betrachtet, aber Mehrdeutigkeit wird vermieden, indem die Mitgliedschaft in der WWS-Familie "Legacy" als Tiebreaker verwendet wird.

Angenommen, ein Dokument gibt einen Familiennamen und Gewichtungs-, Stretch- und Stilparameter an, aber keine Achsenwerte. Die Anwendung kann zunächst die Gewichtung, das Stretchen, formatieren und den Schriftgrad in Achsenwerte konvertieren, indem IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues aufgerufen wird. Die Anwendung kann dann sowohl den Familiennamen als auch die Achsenwerte an IDWriteFontSet4::GetMatchingFonts übergeben. GetMatchingFonts gibt eine Liste übereinstimmender Schriftarten in prioritärer Reihenfolge zurück, und das Ergebnis ist geeignet, unabhängig davon, ob es sich bei dem angegebenen Familiennamen um einen typografischen Familiennamen, einen Gewichts-Stretch-Familiennamen, einen RBIZ-Familiennamen oder einen vollständigen Namen handelt. Wenn die angegebene Familie über eine "opsz"-Achse verfügt, wird die entsprechende optische Größe automatisch basierend auf der Schriftgröße ausgewählt.

Angenommen, ein Dokument gibt Gewichtung, Dehnung und Stil an und gibt auch Achsenwerte an. In diesem Fall können die expliziten Achsenwerte auch an IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues übergeben werden, und die von der Methode zurückgegebenen abgeleiteten Achsenwerte enthalten nur Schriftartachsen, die nicht explizit angegeben wurden. Daher haben die vom Dokument (oder der Anwendung) explizit angegebenen Achsenwerte Vorrang vor Achsenwerten, die von Gewichtung, Dehnung, Format und Schriftgrad abgeleitet werden.

APIs für die Hybridschriftauswahl

Das Hybridschriftartenauswahlmodell wird durch die folgenden IDWriteFontSet4-Methoden implementiert:

  • Die IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues-Methode konvertiert Schriftgröße, Gewicht, Stretch und Formatvorlagenparameter in die entsprechenden Achsenwerte. Alle vom Client übergebenen expliziten Achsenwerte werden von den abgeleiteten Achsenwerten ausgeschlossen.

  • Die IDWriteFontSet4::GetMatchingFonts-Methode gibt eine priorisierte Liste übereinstimmender Schriftarten mit einem Familiennamen und einem Array von Achsenwerten zurück. Wie oben beschrieben, kann der Familiennameparameter ein typografischer Familienname, WWS-Familienname, RBIZ-Familienname oder vollständiger Name sein. (Der vollständige Name gibt einen bestimmten Schriftstil an, z. B. "Arial Bold Italic". GetMatchingFonts unterstützt den Abgleich mit dem vollständigen Namen, um die Kompatibilität mit GDI zu erhöhen, was dies auch zulässt.)

Die folgenden anderen DirectWrite-APIs verwenden auch den Hybridschriftauswahlalgorithmus:

Codebeispiele für verwendete APIs für die Schriftauswahl

Dieser Abschnitt zeigt eine vollständige Konsolenanwendung, die die Methoden IDWriteFontSet4::GetMatchingFonts und IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues veranschaulicht. Zunächst schließen wir einige Header ein:

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

Die IDWriteFontSet4::GetMatchingFonts-Methode gibt eine Liste von Schriftarten in der Prioritätsreihenfolge zurück, die den angegebenen Familiennamen und Achsenwerten entsprechen. Die folgende MatchAxisValues-Funktion gibt die Parameter an IDWriteFontSet4::GetMatchingFonts und die Liste der übereinstimmenden Schriftarten im zurückgegebenen Schriftartensatz aus.

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

Eine Anwendung kann Gewichts-, Stretch- und Stilparameter anstelle von (oder zusätzlich zu) Achsenwerten aufweisen. Beispielsweise muss die Anwendung möglicherweise mit Dokumenten arbeiten, die auf Schriftarten verweisen, die RBIZ- oder weight-stretch-style-Parameter verwenden. Auch wenn die Anwendung Unterstützung für die Angabe beliebiger Achsenwerte hinzufügt, muss sie möglicherweise auch die älteren Parameter unterstützen. Dazu kann die Anwendung IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues aufrufen, bevor IDWriteFontSet4::GetMatchingFonts aufgerufen wird.

Die folgende MatchFont-Funktion übernimmt zusätzlich zu Achsenwerten die Parameter Gewicht, Stretch, Format und Schriftgrad. Es leitet diese Parameter an die IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues-Methode weiter, um abgeleitete Achsenwerte zu berechnen, die an die Werte der Eingabeachse angefügt werden. Die kombinierten Achsenwerte werden an die oben genannte MatchAxisValues-Funktion übergeben.

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

Die folgende Funktion veranschaulicht die Ergebnisse des Aufrufens der obigen MatchFont-Funktion mit einigen Beispieleingaben:

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

Im Folgenden ist die Ausgabe der obigen TestFontSelection-Funktion aufgeführt:

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

Im Folgenden sind die Implementierungen der oben deklarierten überladenen Operatoren aufgeführt. Diese werden von MatchAxisValues verwendet, um die Eingabeachsenwerte und die resultierenden Schriftsichtverweise zu schreiben:

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

Um das Beispiel abzurunden, sind die folgenden Befehlszeilenanalysefunktionen und die Standard-Funktion aufgeführt:

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