Partager via


Sélection de polices

L’interface IDWriteFontSet4 expose des méthodes pour sélectionner des polices à partir d’un jeu de polices. Ces méthodes permettent de passer au modèle de famille de polices typographiques tout en conservant la compatibilité avec les applications, documents et polices existants.

La sélection de polices (parfois appelée correspondance de police ou mappage de police) est le processus de sélection des polices disponibles qui correspondent le mieux aux paramètres d’entrée transmis par votre application. Les paramètres d’entrée sont parfois appelés collectivement une police logique . Une police logique inclut un nom de famille de polices ainsi que d’autres attributs indiquant une police particulière dans la famille. Un algorithme de sélection de police correspond à la police logique (« police souhaitée ») à une police physique disponible (« police dont vous avez »).

Une famille de polices est un groupe nommé de polices qui partagent une conception commune, mais peut différer dans les attributs tels que l’épaisseur. Un modèle de famille de polices définit les attributs qui peuvent être utilisés pour différencier les polices au sein d’une famille. Le nouveau modèle de famille de polices typographiques présente de nombreux avantages par rapport aux deux modèles de famille de polices précédents utilisés sur Windows. Toutefois, la modification des modèles de famille de polices crée des opportunités de confusion et de problèmes de compatibilité. Les méthodes exposées par l’interface IDWriteFontSet4 implémentent une approche hybride qui offre les avantages du modèle de famille de polices typographiques tout en réduisant les problèmes de compatibilité.

Cette rubrique compare les modèles de famille de polices plus anciens avec le modèle de famille de polices typographique ; il explique les défis de compatibilité posés par la modification des modèles de famille de polices ; enfin, il explique comment ces défis peuvent être surmontés à l’aide des méthodes [IDWriteFontSet4](/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontset4).

Modèle de famille de polices RBIZ

Le modèle de famille de polices de facto utilisé dans l’écosystème d’applications GDI est parfois appelé modèle « modèle à quatre polices » ou « RBIZ ». Chaque famille de polices de ce modèle comporte généralement au maximum quatre polices. L’étiquette « RBIZ » provient de la convention d’affectation de noms utilisée pour certains fichiers de police, par exemple :

Nom du fichier Style de police
verdana.ttf Régulier
verdanab.ttf Audacieux
verdanai.ttf Italique
verdanaz.ttf Italique gras

Avec GDI, les paramètres d’entrée utilisés pour sélectionner une police sont définis par la structureLOGFONT, qui inclut le nom de famille (lfFaceName), le poids (lfWeight) et les champs italiques (lfItalic). Le champ lfItalic a la valeur TRUE ou FALSE. GDI permet au champ lfWeight d’être n’importe quelle valeur de la plage FW_THIN (100) de FW_BLACK (900), mais pour des raisons historiques, les polices ont été conçues de façon à ce qu’il n’y ait pas plus de deux poids dans la même famille de polices GDI.

Les interfaces utilisateur d’application populaires du début incluent un bouton italique (pour activer et désactiver l’italique) et un bouton gras (pour basculer entre les poids normaux et gras). L’utilisation de ces deux boutons pour sélectionner des polices dans une famille suppose le modèle RBIZ. Par conséquent, même si GDI prend en charge plus de deux poids, la compatibilité des applications a conduit les développeurs de polices à définir le nom de famille GDI (ID de nom OpenType 1) d’une manière cohérente avec le modèle RBIZ.

Par exemple, supposons que vous vouliez ajouter un poids « Noir » plus lourd à la famille de polices Arial. Logiquement, cette police fait partie de la famille Arial. Vous pouvez donc vous attendre à la sélectionner en définissant lfFaceName sur « Arial » et lfWeight sur FW_BLACK. Toutefois, il n’existe aucun moyen pour un utilisateur d’application de choisir entre trois poids à l’aide d’un bouton gras à deux états. La solution était de donner à la nouvelle police un autre nom de famille, afin que l’utilisateur puisse le sélectionner en choisissant « Arial Black » dans la liste des familles de polices. De même, il n’existe aucun moyen de choisir parmi différentes largeurs dans la même famille de polices en utilisant uniquement des boutons gras et italiques, de sorte que les versions étroites d’Arial ont des noms de famille différents dans le modèle RBIZ. Ainsi, nous avons « Arial », « Arial Black », et « Arial Narrow » famille de police dans le modèle RBIZ, même si typographiquement ces derniers appartiennent à une même famille.

À partir de ces exemples, on peut voir comment les limitations d’un modèle de famille de polices peuvent affecter la façon dont les polices sont regroupées en familles. Étant donné que les familles de polices sont identifiées par nom, cela signifie que la même police peut avoir des noms de famille différents en fonction du modèle de famille de polices que vous utilisez.

DirectWrite ne prend pas directement en charge le modèle de famille de polices RBIZ, mais il fournit des méthodes de conversion vers et à partir du modèle RBIZ, telles que IDWriteGdiInterop ::CreateFontFromLOGFONT et IDWriteGdiInterop ::ConvertFontToLOGFONT. Vous pouvez également obtenir le nom de la famille RBIZ d’une police en appelant son IDWriteFont ::GetInformationalStrings méthode et en spécifiant DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES.

Modèle de famille de polices de style stretch de poids

Le modèle de famille de polices de style stretch de poids est le modèle de famille de polices d’origine utilisé par DirectWrite avant l’introduction du modèle de famille de polices typographique. Il est également appelé épaisseur-largeur-pente (WWS). Dans le modèle WWS, les polices au sein de la même famille peuvent être différentes par trois propriétés : poids (DWRITE_FONT_WEIGHT), stretch (DWRITE_FONT_STRETCH) et style (DWRITE_FONT_STYLE).

Le modèle WWS est plus flexible que le modèle RBIZ de deux façons. Tout d’abord, les polices de la même famille peuvent être différenciées par étirement (ou largeur) ainsi que par poids et style (normal, italique ou oblique). Deuxièmement, il peut y avoir plus de deux poids dans la même famille. Cette flexibilité est suffisante pour permettre à toutes les variantes d’Arial d’être incluses dans la même famille WWS. Le tableau suivant compare les propriétés de police RBIZ et WWS pour une sélection de polices Arial :

Nom complet Nom de la famille RBIZ lfWeight lfItalic WWS FamilyName Poids Étirer Style
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
Gras étroit arial Arial Narrow 700 0 Arial 700 3 0

Comme vous pouvez le voir, « Arial Narrow » a les mêmes lfWeight et lfItalic valeurs que « Arial », il a donc un autre nom de famille RBIZ pour éviter toute ambiguïté. « Arial Black » a un nom de famille RBIZ différent pour éviter d’avoir plus de deux poids dans la famille « Arial ». En revanche, toutes ces polices se trouvent dans la même famille de style poids stretch.

Néanmoins, le modèle de style weight-stretch n’est pas ouvert. Si deux polices ont le même poids, étirement et style, mais diffèrent d’une autre manière (par exemple, taille optique), elles ne peuvent pas être incluses dans la même famille de polices WWS. Cela nous amène au modèle de famille de polices typographiques.

Modèle de famille de polices typographiques

Contrairement à ses prédécesseurs, le modèle de famille de polices typographiques est ouvert. Il prend en charge un nombre quelconque d’axes de variation au sein d’une famille de polices.

Si vous considérez les paramètres de sélection de police comme des coordonnées dans un espace de conception, le modèle de style stretch de poids définit un système de coordonnées à trois dimensions avec poids, étirement et style comme axes. Chaque police d’une famille WWS doit avoir un emplacement unique défini par ses coordonnées le long de ces trois axes. Pour sélectionner une police, vous spécifiez un nom de famille WWS et des paramètres de poids, d’étirement et de style.

En revanche, le modèle de famille de polices typographiques a un espace de conception ndimensionnel. Un concepteur de polices peut définir n’importe quel nombre d’axes de conception, chacun identifié par une balise d’axe de quatre caractères . L’emplacement d’une police donnée dans l’espace de conception ndimensionnel est défini par un tableau de valeurs d’axe , où chaque valeur d’axe comprend une balise d’axe et une valeur à virgule flottante. Pour sélectionner une police, vous spécifiez un nom de famille typographique et un tableau de valeurs d’axe (DWRITE_FONT_AXIS_VALUE structures).

Bien que le nombre d’axes de police soit ouvert, il existe quelques axes inscrits avec des significations standard, et les valeurs de poids, d’étirement et de style peuvent être mappées aux valeurs d’axe inscrites. DWRITE_FONT_WEIGHT pouvez être mappé à une valeur d’axe « wght » (DWRITE_FONT_AXIS_TAG_WEIGHT). DWRITE_FONT_STRETCH pouvez être mappé à une valeur d’axe « wdth » (DWRITE_FONT_AXIS_TAG_WIDTH). DWRITE_FONT_STYLE pouvez être mappé à une combinaison de valeurs d’axe « ital » et « slnt » (DWRITE_FONT_AXIS_TAG_ITALIC et DWRITE_FONT_AXIS_TAG_SLANT).

Un autre axe inscrit est « opsz » (DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE). Une famille de polices optiques comme Sitka inclut des polices qui diffèrent le long de l’axe « opsz », ce qui signifie qu’elles sont conçues pour être utilisées à différentes tailles de point. Le modèle de famille de policeS WWS n’a pas d’axe de taille optique. La famille de polices Sitka doit donc être divisée en plusieurs familles de polices WWS : « Sitka Small », « Sitka Text », « Sitka Text », « Sitka Axis », et ainsi de suite. Chaque famille de polices WWS correspond à une taille optique différente, et elle est laissée à l’utilisateur pour spécifier le nom de famille WWS approprié pour une taille de police donnée. Avec le modèle de famille de polices typographiques, l’utilisateur peut simplement choisir « Sitka », et l’application peut définir automatiquement la valeur de l’axe « opsz » en fonction de la taille de police.

Sélection de polices typographiques et polices variables

Le concept d’axes de variation est souvent associé à des polices variables, mais il s’applique également aux polices statiques. OpenType STAT (attributs de style) table déclare les axes de conception d’une police et les valeurs de ces axes. Cette table est requise pour les polices variables, mais elle est également pertinente pour les polices statiques.

L’API DirectWrite expose les valeurs d’axe « wght », « wdth », « ital » et « slnt » pour chaque police, même si elles ne sont pas présentes dans la table STAT ou s’il n’y a pas de table STAT. Ces valeurs sont dérivées de la table STAT si possible. Sinon, ils sont dérivés de l’épaisseur de police, de l’étirement de police et du style de police.

Les axes de police peuvent être variables ou non variables. Une police statique n’a que des axes non variables, tandis qu’une police variable peut avoir les deux. Pour utiliser une police de variable, vous devez créer une police de variable instance dans laquelle tous les axes de variable ont été liés à des valeurs particulières. L’interfaceIDWriteFontFacereprésente une police statique ou une instance particulière d’une police variable. Il est possible de créer une instance arbitraire d’une police de variable avec des valeurs d’axe spécifiées. En outre, une police de variable peut déclarer instances nommées dans la table STAT avec des combinaisons prédéfinies de valeurs d’axe. Les instances nommées permettent à une police variable de se comporter comme une collection de polices statiques. Lorsque vous énumérez des éléments d’un IDWriteFontFamily ou IDWriteFontSet, il existe un élément pour chaque police statique et pour chaque instance de police de variable nommée.

L’algorithme de correspondance de police typographique sélectionne d’abord les candidats potentiels de correspondance en fonction du nom de la famille. Si les candidats de correspondance incluent des polices de variable, tous les candidats de correspondance pour la même police de variable sont réduits en un candidat de correspondance dans lequel chaque axe de variable reçoit une valeur spécifique aussi proche que possible de la valeur demandée pour cet axe. S’il n’existe aucune valeur demandée pour un axe de variable, elle reçoit la valeur par défaut de cet axe. L’ordre des candidats de correspondance est ensuite déterminé en comparant leurs valeurs d’axe aux valeurs d’axe demandées.

Par exemple, considérez la famille typographique Sitka dans Windows. Sitka est une famille de polices optiques, ce qui signifie qu’elle a un axe « opsz ». Dans Windows 11, Sitka est implémenté en tant que polices variables avec les valeurs d’axe suivantes. Notez que les axes opsz et wght sont variables, tandis que les autres axes ne sont pas variables.

Nom du fichier « 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

Supposons que les valeurs d’axe demandées sont opsz:12 wght:475 wdth:100 ital:0 slnt:0. Pour chaque police de variable, nous créons une référence à une police de variable instance dans laquelle chaque axe des variables reçoit une valeur spécifique. À savoir, les axes opsz et wght sont définis respectivement sur 12 et 475. Cela génère les polices correspondantes suivantes, avec la police non italique classée en premier, car il s’agit d’une meilleure correspondance pour les axes ital et 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

Dans l’exemple ci-dessus, les polices correspondantes sont des instances de polices variables arbitraires. Il n’y a pas d’instance nommée de Sitka avec poids 475. En revanche, l’algorithme de correspondance de style weight-stretch retourne uniquement des instances nommées.

Ordre de correspondance des polices

Il existe différentes méthodes surchargées GetMatchingFonts pour le modèle de famille de polices de style weight-stretch (IDWriteFontFamily ::GetMatchingFonts) et le modèle de famille de polices typographiques (IDWriteFontCollection2 ::GetMatchingFonts). Dans les deux cas, la sortie est une liste de polices correspondantes dans l’ordre décroissant de priorité en fonction de la façon dont chaque police candidate correspond aux propriétés d’entrée. Cette section décrit comment la priorité est déterminée.

Dans le modèle de style weight-stretch, les paramètres d’entrée sont l’épaisseur de police (DWRITE_FONT_WEIGHT), l’étirement de police (DWRITE_FONT_STRETCH) et le style de police (DWRITE_FONT_STYLE). L’algorithme permettant de trouver la meilleure correspondance a été documenté dans un livre blanc 2006 intitulé « WPF Font Selection Model » par Mikhaïl Leonov et David Brown. Consultez la section « Correspondance d’un visage à partir de la liste des visages candidats ». Ce document concerne Windows Presentation Foundation (WPF), mais DirectWrite a ensuite utilisé la même approche.

L’algorithme utilise la notion de vecteur d’attribut de police , qui pour une combinaison donnée de poids, d’étirement et de style est calculée comme suit :

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

Notez que chaque coordonnée de vecteur est normalisée en soustrayant la valeur « normale » de l’attribut correspondant et en multipliant par une constante. Les multiplicateurs compensent le fait que les plages de valeurs d’entrée pour le poids, l’étirement et le style sont très différentes. Sinon, le poids (100..999) domine sur le style (0,.2).

Pour chaque candidat de correspondance, une distance de vecteur et un produit point sont calculés entre le vecteur d’attribut de police du candidat de correspondance et le vecteur d’attribut de police d’entrée. Lors de la comparaison de deux candidats de correspondance, le candidat avec la plus petite distance vectorielle est le meilleur résultat. Si les distances sont identiques, le candidat avec le produit point plus petit est une meilleure correspondance. Si le produit point est également le même, les distances le long des axes X, Y et Z sont comparées dans cet ordre.

La comparaison des distances est suffisamment intuitive, mais l’utilisation d’un produit point comme mesure secondaire peut nécessiter une explication. Supposons que le poids d’entrée soit semi-bolé (600), et que deux poids candidats sont noirs (900) et semi-clair (300). La distance de chaque poids candidat du poids d’entrée est la même, mais le poids noir est dans la même direction de l’origine (c’est-à-dire, 400 ou normal), de sorte qu’il aura un produit point plus petit.

L’algorithme de correspondance typographique est une généralisation de celle du style weight-stretch. Chaque valeur de l’axe est traitée comme une coordonnée dans un vecteur d’attribut de police ndimensionnel. Pour chaque candidat de correspondance, une distance de vecteur et un produit point sont calculés entre le vecteur d’attribut de police du candidat de correspondance et le vecteur d’attribut de police d’entrée. Le candidat avec la plus petite distance vectorielle est la meilleure correspondance. Si les distances sont identiques, le candidat avec le produit point plus petit est une meilleure correspondance. Si le produit point est également le même, la présence dans une famille de style poids-stretch spécifiée peut être utilisée comme un disjoncteur.

Pour calculer la distance de vecteur et le produit point, le vecteur d’attribut de police d’un candidat et le vecteur d’attribut de police d’entrée doivent avoir les mêmes axes. Par conséquent, toute valeur d’axe manquante dans l’un ou l’autre vecteur est remplie en remplaçant la valeur standard de cet axe. Les coordonnées vectorielles sont normalisées en soustrayant la valeur standard (ou « normale ») pour l’axe correspondant et en multipliant le résultat par un multiplicateur spécifique à l’axe. Voici les multiplicateurs et les valeurs standard pour chaque axe :

Axe Multiplicateur Valeur standard
« wght » 5 400
« wdth » 55 100
« ital » 1400 0
« slnt » 35 0
« opsz » 1 12
autre 1 0

Les multiplicateurs sont cohérents avec ceux utilisés par l’algorithme de style weight-stretch, mais mis à l’échelle si nécessaire. Par exemple, la largeur normale est 100, ce qui équivaut à s’étirer 5. Cela génère un multiplicateur de 55 contre 1100. L’attribut de style hérité (0..2) entangle l’italique et l’oblique, qui, dans le modèle typographique, sont décomposés en un axe « ital » (0..1) et un axe « slnt » (-90..90). Les multiplicateurs choisis pour ces deux axes donnent des résultats équivalents à l’algorithme hérité si nous partons d’un angle par défaut de 20 degrés pour les polices obliques.

Sélection de police typographique et taille optique

Une application utilisant le modèle de famille de polices typographiques peut implémenter le dimensionnement optique en spécifiant une valeur d’axe opsz comme paramètre de sélection de police. Par exemple, une application de traitement de texte peut spécifier une valeur d’axe opsz égale à la taille de police en points. Dans ce cas, un utilisateur peut sélectionner « Sitka » comme famille de polices, et l’application sélectionne automatiquement une instance de Sitka avec la valeur d’axe correcte opsz. Sous le modèle WWS, chaque taille optique est exposée sous la forme d’un nom de famille différent et c’est à l’utilisateur de sélectionner celle qui convient.

En théorie, on pourrait implémenter le dimensionnement optique automatique sous le modèle de style stretch de poids en remplaçant la valeur de l’axe opsz en tant qu’étape distincte après sélection de police. Toutefois, cela ne fonctionne que si la première police correspondante est une police variable avec une variable opsz axe. La spécification de opsz en tant que paramètre de sélection de police fonctionne également bien pour les polices statiques. Par exemple, la famille de polices Sitka est implémentée en tant que polices variables dans Windows 11, mais comme collection de polices statiques dans Windows 10. Les polices statiques ont des plages d’axes opsz différentes qui ne se chevauchent pas (elles sont déclarées comme des plages à des fins de sélection de polices, mais elles ne sont pas des axes variables). La spécification de opsz en tant que paramètre de sélection de police permet de sélectionner la police statique appropriée pour la taille optique.

Avantages de la sélection de police typographique et problèmes de compatibilité

Le modèle de sélection de police typographique présente plusieurs avantages par rapport aux modèles précédents, mais sous sa forme pure, il présente des problèmes de compatibilité potentiels. Cette section décrit les avantages et les problèmes de compatibilité. La section suivante décrit un modèle de sélection de police hybride qui conserve les avantages tout en atténuant les problèmes de compatibilité.

Les avantages du modèle de famille de polices typographiques sont les suivants :

  • Les polices peuvent être regroupées en familles comme prévu par le concepteur, au lieu d’être divisées en sous-famille en raison de limitations du modèle de famille de polices.

  • Une application peut sélectionner automatiquement la valeur de l’axe de opsz correcte en fonction de la taille de police, plutôt que d’exposer différentes tailles optiques à l’utilisateur en tant que familles de polices différentes.

  • Les instances arbitraires de polices variables peuvent être sélectionnées. Par exemple, si une police variable prend en charge les pondérations dans la plage continue 100-900, le modèle typographique peut sélectionner n’importe quel poids dans cette plage. Les modèles de famille de polices plus anciens peuvent choisir uniquement le poids le plus proche parmi les instances nommées définies par la police.

Les problèmes de compatibilité avec le modèle de sélection de police typographique sont les suivants :

  • Certaines polices plus anciennes ne peuvent pas être sélectionnées sans ambiguïté en utilisant uniquement le nom de famille typographique et les valeurs d’axe.

  • Les documents existants peuvent faire référence à des polices par nom de famille WWS ou par nom de famille RBIZ. Les utilisateurs peuvent également s’attendre à utiliser des noms de famille WWS et RBIZ. Par exemple, un document peut spécifier « Sitka Sous-titre » (nom de famille WWS) au lieu de « Sitka » (nom de famille typographique).

  • Une bibliothèque ou une infrastructure peut adopter le modèle de famille de polices typographiques pour tirer parti du dimensionnement optique automatique, mais ne fournit pas d’API pour la spécification de valeurs d’axe arbitraires. Même si une nouvelle API est fournie, l’infrastructure peut avoir besoin d’utiliser des applications existantes qui spécifient uniquement des paramètres de poids, d’étirement et de style.

Le problème de compatibilité avec les polices plus anciennes se produit parce que le concept de nom de famille typographique précède le concept de valeurs d’axe de police, qui ont été introduites avec les polices de variables dans OpenType 1.8. Avant OpenType 1.8, le nom de la famille typographique a simplement exprimé l’intention du concepteur qu’un ensemble de polices était lié, mais sans garantie que ces polices pouvaient être différenciées par programmation en fonction de leurs propriétés. Par exemple hypothétique, supposons que toutes les polices suivantes ont le nom de famille typographique « Legacy » :

Nom complet Famille WWS Poids Étirer Style Famille typo wght wdth Ital slnt
Héritage Héritage 400 5 0 Héritage 400 100 0 0
Gras hérité Héritage 700 5 0 Héritage 700 100 0 0
Noir hérité Héritage 900 5 0 Héritage 900 100 0 0
Soft hérité Soft hérité 400 5 0 Héritage 400 100 0 0
Gras doux hérité Soft hérité 700 5 0 Héritage 700 100 0 0
Noir doux hérité Soft hérité 900 5 0 Héritage 900 100 0 0

La famille typographique « Legacy » a trois poids, et chaque poids a des variantes régulières et « Soft ». S’il s’agissait de nouvelles polices, elles peuvent être implémentées comme déclarant un axe de conception SOFT. Toutefois, ces polices précèdent OpenType 1.8, de sorte que leurs seuls axes de conception sont ceux dérivés du poids, de l’étirement et du style. Pour chaque poids, cette famille de polices a deux polices avec des valeurs d’axe identiques. Il n’est donc pas possible de sélectionner sans ambiguïté une police dans cette famille à l’aide de valeurs d’axe seules.

Algorithme de sélection de police hybride

Les API de sélection de police décrites dans la section suivante utilisent un algorithme de sélection de police hybride qui préserve les avantages de la sélection de police typographique tout en réduisant ses problèmes de compatibilité.

La sélection de polices hybrides fournit un pont à partir de modèles de famille de polices plus anciens en activant l’épaisseur de police, l’étirement de police et les valeurs de style de police à mapper aux valeurs d’axe de police correspondantes. Cela permet de résoudre les problèmes de compatibilité des documents et des applications.

En outre, l’algorithme de sélection de police hybride permet au nom de famille spécifié d’être un nom de famille typographique, un nom de famille de style stretch de poids, un nom de famille GDI/RBIZ ou un nom de police complet. La correspondance se produit de l’une des manières suivantes, dans l’ordre décroissant de priorité :

  1. Le nom correspond à une famille typographique (par exemple, Sitka). La correspondance se produit dans la famille typographique et toutes les valeurs d’axe demandées sont utilisées. Si le nom correspond également à une sous-famille WWS (autrement dit, une plus petite que la famille typographique), l’appartenance à la sous-famille WWS est utilisée comme un disjoncteur.

  2. Le nom correspond à une famille WWS (par exemple, Sitka Text). La correspondance se produit dans la famille WWS et les valeurs d’axe demandées autres que « wght », « wdth », « ital » et « slnt » sont ignorées.

  3. Le nom correspond à une famille GDI (par exemple, Syntaxschrift Condensed). La correspondance se produit dans la famille RBIZ et les valeurs d’axe demandées autres que « wght », « ital » et « slnt » sont ignorées.

  4. Le nom correspond à un nom complet (par exemple, Syntaxschrift Bold Condensed). La police correspondant au nom complet est retournée. Les valeurs d’axe demandées sont ignorées. La correspondance par nom de police complet est autorisée, car GDI le prend en charge.

La section précédente a décrit une famille typographique ambiguë appelée « Legacy ». L’algorithme hybride permet d’éviter l’ambiguïté en spécifiant « Legacy » ou « Legacy Soft » comme nom de famille. Si « Soft hérité » est spécifié, il n’y a aucune ambiguïté, car la correspondance se produit uniquement dans la famille WWS. Si « Legacy » est spécifié, toutes les polices de la famille typographique sont considérées comme des candidats à la correspondance, mais l’ambiguïté est évitée en utilisant l’appartenance à la famille WWS « Legacy » comme un disjoncteur.

Supposons qu’un document spécifie un nom de famille et un poids, des paramètres d’étirement et de style, mais aucune valeur d’axe. L’application peut d’abord convertir le poids, l’étirement, le style et la taille de police en valeurs d’axe en appelant IDWriteFontSet4 ::ConvertWeightStretchStyleToFontAxisValues. L’application peut ensuite passer le nom de famille et les valeurs d’axe à IDWriteFontSet4 ::GetMatchingFonts. GetMatchingFonts retourne une liste de polices correspondantes dans l’ordre de priorité, et le résultat est approprié si le nom de famille spécifié est un nom de famille typographique, un nom de famille de style weight-stretch, un nom de famille RBIZ ou un nom complet. Si la famille spécifiée a un axe « opsz », la taille optique appropriée est automatiquement sélectionnée en fonction de la taille de police.

Supposons qu’un document spécifie le poids, l’étirement et le style, et également spécifie des valeurs d’axe. Dans ce cas, les valeurs d’axe explicites peuvent également être transmises à IDWriteFontSet4 ::ConvertWeightStretchStyleToFontAxisValues, et les valeurs d’axe dérivées retournées par la méthode incluent uniquement les axes de police qui n’ont pas été spécifiés explicitement. Par conséquent, les valeurs d’axe spécifiées explicitement par le document (ou l’application) sont prioritaires sur les valeurs de l’axe dérivées de la pondération, de l’étirement, du style et de la taille de police.

API de sélection de polices hybrides

Le modèle de sélection de police hybride est implémenté par les méthodes IDWriteFontSet4 suivantes :

  • La méthode IDWriteFontSet4 ::ConvertWeightStretchStyleToFontAxisValues convertit la taille de police, l’épaisseur, l’étirement et les paramètres de style en valeurs d’axe correspondantes. Toutes les valeurs d’axe explicites transmises par le client sont exclues des valeurs d’axe dérivées.

  • La méthode IDWriteFontSet4 ::GetMatchingFonts retourne une liste hiérarchisée de polices correspondantes en fonction d’un nom de famille et d’un tableau de valeurs d’axe. Comme décrit ci-dessus, le paramètre de nom de famille peut être un nom de famille typographique, un nom de famille WWS, un nom de famille RBIZ ou un nom complet. (Le nom complet identifie un style de police particulier, tel que « Arial Bold Italic ». GetMatchingFonts prend en charge la correspondance par nom complet pour une plus grande comaptibiltiy avec GDI, ce qui lui permet également.)

Les autres API DirectWrite suivantes utilisent également l’algorithme de sélection de police hybride :

Exemples de code des API de sélection de polices en cours d’utilisation

Cette section présente une application console complète qui illustre l'IDWriteFontSet4 ::GetMatchingFonts et IDWriteFontSet4 ::ConvertWeightStretchStyleToFontAxisValues méthodes. Tout d’abord, incluons quelques en-têtes :

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

La méthode IDWriteFontSet4 ::GetMatchingFonts retourne une liste de polices dans l’ordre de priorité qui correspondent au nom de famille et aux valeurs d’axe spécifiés. La fonction MatchAxisValues suivante génère les paramètres dans IDWriteFontSet4 ::GetMatchingFonts et la liste des polices correspondantes dans le jeu de polices retourné.

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

Une application peut avoir des paramètres de poids, d’étirement et de style au lieu de valeurs d’axe (ou en plus). Par exemple, l’application peut avoir besoin d’utiliser des documents qui référencent des polices à l’aide de paramètres de style RBIZ ou weight-stretch-style. Même si l’application ajoute la prise en charge de la spécification de valeurs d’axe arbitraires, il peut également être nécessaire de prendre en charge les paramètres plus anciens. Pour ce faire, l’application peut appeler IDWriteFontSet4 ::ConvertWeightStretchStyleToFontAxisValues avant d’appeler IDWriteFontSet4 ::GetMatchingFonts.

La fonction MatchFont suivante prend en charge les paramètres de taille de police, d’étirement, de style et de taille de police en plus des valeurs d’axe. Il transfère ces paramètres à la méthode IDWriteFontSet4 ::ConvertWeightStretchStyleToFontAxisValues méthode pour calculer les valeurs d’axe dérivées, qui sont ajoutées aux valeurs de l’axe d’entrée. Il transmet les valeurs d’axe combinées à la fonction MatchAxisValues ci-dessus.

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

La fonction suivante illustre les résultats de l’appel de la fonction MatchFont ci-dessus avec quelques exemples d’entrées :

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

Voici la sortie de la fonction TestFontSelection ci-dessus :

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

Voici les implémentations des opérateurs surchargés déclarés ci-dessus. Celles-ci sont utilisées par MatchAxisValues pour écrire les valeurs de l’axe d’entrée et les références de visage de police résultantes :

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

Pour arrondir l’exemple, voici les fonctions d’analyse de ligne de commande et la fonction principale :

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