Partager via


Sélection de polices

L’interface IDWriteFontSet4 expose des méthodes permettant de 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 polices) est le processus de sélection des polices disponibles qui correspondent le mieux aux paramètres d’entrée passés par votre application. Les paramètres d’entrée sont parfois appelés collectivement une police logique. Une police logique comprend un nom de famille de polices ainsi que d’autres attributs indiquant une police particulière au sein de la famille. Un algorithme de sélection de polices met en correspondance la police logique (« la police souhaitée ») à une police physique disponible (« une police que vous avez »).

Une famille de polices est un groupe nommé de polices qui partagent une conception commune, mais qui peuvent différer dans des attributs tels que le poids. 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 possibilité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 atténuant les problèmes de compatibilité.

Cette rubrique compare les anciens modèles de famille de polices avec le modèle de famille de polices typographique ; il explique les problèmes 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 à quatre polices » ou « modèle RBIZ ». Chaque famille de polices de ce modèle a 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 de fichier Style
verdana.ttf Normal
verdanab.ttf Gras
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 structure LOGFONT , qui inclut les champs nom de famille (lfFaceName), poids (lfWeight) et italique (lfItalic). Le lfItalic champ est TRUE ou FALSE. GDI permet au lfWeight champ d’être n’importe quelle valeur de la plage FW_THIN (100) à FW_BLACK (900), mais pour des raisons historiques, les polices ont longtemps été conçues de telle sorte qu’il n’y ait pas plus de deux poids dans la même famille de polices GDI.

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

Par exemple, supposons que vous souhaitiez 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 la définissant sur lfFaceName « Arial » et lfWeightsur 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 consistait à donner à la nouvelle police un nom de famille différent, afin que l’utilisateur puisse la 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 des familles de polices « Arial », « Arial Black » et « Arial Narrow » dans le modèle RBIZ, même si, typographiquement, ceux-ci appartiennent tous à une même famille.

À partir de ces exemples, vous pouvez 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 leur nom, cela signifie que la même police peut avoir des noms de famille différents selon le 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 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 famille RBIZ d’une police en appelant sa méthode IDWriteFont::GetInformationalStrings et en spécifiant DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES.

Modèle de famille de polices de style étirement

Le modèle de famille de polices de style poids étirement est le modèle de famille de polices d’origine utilisé par DirectWrite avant l’introduction du modèle de famille de polices typographiques. Il est également connu sous le nom de poids-largeur-pente (WWS). Dans le modèle WWS, les polices de la même famille peuvent être différentes par trois propriétés : weight (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 le poids et le style (régulier, 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 Stretch 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
Arial Narrow Bold Arial Narrow 700 0 Arial 700 3 0

Comme vous pouvez le voir, « Arial Narrow » a les mêmes lfWeight valeurs et lfItalic que « Arial », de sorte qu’il a un nom de famille RBIZ différent 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-étirement.

Néanmoins, le modèle de style poids-stretch n’est pas ouvert. Si deux polices ont le même poids, stretch et style, mais diffèrent d’une autre manière (par exemple, la 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 n’importe quel nombre 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 poids-étirement définit un système de coordonnées tridimensionnel avec le poids, l’étirement et le 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 à n dimensions. Un concepteur de polices peut définir n’importe quel nombre d’axes de conception, chacun identifié par une balise d’axe à quatre caractères. L’emplacement d’une police donnée dans l’espace de conception de n dimensions 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 peut être mappé à une valeur d’axe « wght » (DWRITE_FONT_AXIS_TAG_WEIGHT). DWRITE_FONT_STRETCH peut être mappé à une valeur d’axe « wdth » (DWRITE_FONT_AXIS_TAG_WIDTH). DWRITE_FONT_STYLE peut ê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 telle que Sitka comprend 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’ayant pas d’axe de taille optique, la famille de polices Sitka doit être divisée en plusieurs familles de polices WWS : « Sitka Small », « Sitka Text », « Sitka Sous-titre », etc. Chaque famille de polices WWS correspond à une taille optique différente, et il est laissé à l’utilisateur de 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é aux polices variables, mais il s’applique également aux polices statiques. La table OpenType STAT (attributs de style) 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 du poids 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 a uniquement des axes non variables, alors qu’une police variable peut avoir les deux. Pour utiliser une police 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’interface IDWriteFontFace repré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 des 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 police de variable nommée instance.

L’algorithme de correspondance de police typographique sélectionne d’abord les candidats de correspondance potentielle en fonction du nom de famille. Si les candidats de correspondance incluent des polices variables, tous les candidats de correspondance pour la même police de variable sont réduits en un seul candidat de correspondance dans lequel chaque axe des variables se voit attribuer une valeur spécifique aussi proche que possible de la valeur demandée pour cet axe. Si aucune valeur n’est demandée pour un axe de variable, la valeur par défaut de cet axe lui est attribuée. 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é comme deux polices variables avec les valeurs d’axe suivantes. Notez que les opsz axes et wght sont variables, tandis que les autres axes ne sont pas variables.

Nom de 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 soient 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 une valeur spécifique est affectée à chaque axe de variable. À savoir, les opsz axes et wght sont définis sur 12 et 475, respectivement. 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 ital axes 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 aucune instance nommée de Sitka avec un poids de 475. En revanche, l’algorithme de correspondance poids-stretch-style retourne uniquement les instances nommées.

Ordre de correspondance des polices

Il existe différentes méthodes GetMatchingFonts surchargées 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 weight-stretch-style, 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 de 2006 intitulé « WPF Font Selection Model » par Mikhail Leonov et David Brown. Consultez la section « Correspondance d’un visage de la liste des visages candidats ». Cet article portait sur 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 vectorielle 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 distance de vecteur plus petite 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, les distances le long des axes X, Y et Z sont comparées dans cet ordre.

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

L’algorithme de correspondance typographique est une généralisation de celui pour le style poids-étirement. Chaque valeur d’axe est traitée comme une coordonnée dans un vecteur d’attribut de police à plusieurs dimensions. Pour chaque candidat de correspondance, une distance vectorielle 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 les mêmes, le candidat avec le produit à points plus petit correspond mieux. Si le produit à points est également le même, la présence dans une famille de style poids-stretch spécifiée peut être utilisée comme un coupe-égalité.

Pour calculer la distance vectorielle et le produit point, le vecteur d’attribut de police d’un candidat de correspondance 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 des vecteurs est renseignée en remplaçant la valeur standard pour cet axe. Les coordonnées vectorielles sont normalisées en soustrayant la valeur standard (ou « normal ») 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
other 1 0

Les multiplicateurs sont cohérents avec ceux utilisés par l’algorithme de style poids-étirement, mais mis à l’échelle en fonction des besoins. Par exemple, la largeur normale est de 100, ce qui équivaut à l’étirement 5. Cela génère un multiplicateur de 55 contre 1100. L’attribut de style hérité (0..2) empêtre 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 supposons une inclinaison 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 un instance de Sitka avec la valeur d’axe correcteopsz. Sous le modèle WWS, chaque taille optique est exposée sous la forme d’un nom de famille différent et il appartient à l’utilisateur de sélectionner le bon.

En théorie, on peut implémenter le dimensionnement optique automatique sous le modèle de style poids-étirement en remplaçant la valeur de l’axe opsz comme une étape distincte après la sélection de la police. Toutefois, cela ne fonctionne que si la première police correspondante est une police variable avec un axe variable opsz . La spécification en opsz 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 en tant que collection de polices statiques dans Windows 10. Les polices statiques ont des plages d’axes différentes qui ne se chevauchent opsz pas (elles sont déclarées en tant que plages à des fins de sélection de polices, mais elles ne sont pas des axes variables). La spécification 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 dans 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, plutôt que d’être divisées en sous-familles en raison des limitations du modèle de famille de polices.

  • Une application peut sélectionner automatiquement la valeur d’axe appropriée opsz 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.

  • Vous pouvez sélectionner des instances arbitraires de polices variables. Par exemple, si une police variable prend en charge des pondérations dans la plage continue 100-900, le modèle typographique peut sélectionner n’importe quel poids dans cette plage. Les anciens modèles de famille de polices 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 les valeurs de nom de famille typographique et d’axe.

  • Les documents existants peuvent faire référence à des polices par nom de famille WWS ou 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 Entre-temps » (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 pas fournir d’API pour spécifier des valeurs d’axe arbitraires. Même si une nouvelle API est fournie, l’infrastructure peut avoir besoin de fonctionner avec 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 est antérieur au concept de valeurs d’axe des polices, qui ont été introduits avec les polices variables dans OpenType 1.8. Avant OpenType 1.8, le nom de famille typographique exprimait simplement l’intention du concepteur qu’un ensemble de polices était lié, mais sans garantie que ces polices puissent être différenciées par programmation en fonction de leurs propriétés. À titre d’exemple hypothétique, supposons que toutes les polices suivantes ont le nom de famille typographique « Legacy » :

Nom complet Famille WWS Poids Stretch Style Famille de fautes de frappe wght wdth Ital slnt
Hérité Hérité 400 5 0 Hérité 400 100 0 0
Gras hérité Hérité 700 5 0 Hérité 700 100 0 0
Noir hérité Hérité 900 5 0 Hérité 900 100 0 0
Logiciel logiciel hérité Logiciel logiciel hérité 400 5 0 Hérité 400 100 0 0
Gras souple hérité Logiciel logiciel hérité 700 5 0 Hérité 700 100 0 0
Noir souple hérité Logiciel logiciel hérité 900 5 0 Hérité 900 100 0 0

La famille typographique « Héritée » a trois pondérations, et chaque poids a des variantes régulières et « soft ». S’il s’agissait de nouvelles polices, elles pourraient être implémentées en tant que déclaration d’un axe de conception SOFT. Toutefois, ces polices sont antérieures à OpenType 1.8, donc 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 uniquement.

Algorithme de sélection de polices hybrides

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 typographique des polices tout en atténuant ses problèmes de compatibilité.

La sélection de polices hybrides fournit un pont à partir d’anciens modèles de famille de polices en permettant aux valeurs de poids de police, d’étirement de police et de style de police d’être mappées 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 poids-étirement, 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 au sein de la famille typographique et toutes les valeurs d’axe demandées sont utilisées. Si le nom correspond également à une sous-famille WWS (c’est-à-dire 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 au sein de 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, Bahnschrift Condensed). La correspondance se produit au sein de 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, Bahnschrift 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 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 au sein de la famille WWS. Si « Hérité » est spécifié, toutes les polices de la famille typographique sont considérées comme des candidats de correspondance, mais l’ambiguïté est évitée en utilisant l’appartenance à la famille WWS « Héritée » comme un casse-égalité.

Supposons qu’un document spécifie un nom de famille et des paramètres de poids, 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é, que le nom de famille spécifié soit un nom de famille typographique, un nom de famille de style poids-étirement, 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 qu’il spécifie également 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. Ainsi, les valeurs d’axe spécifiées explicitement par le document (ou l’application) sont prioritaires sur les valeurs d’axe dérivées du poids, 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 les paramètres de taille de police, de poids, d’étirement et 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 le permet également.)

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

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

Cette section présente une application console complète qui illustre les méthodes IDWriteFontSet4::GetMatchingFonts et IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues . Commençons par inclure 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 par ordre de priorité qui correspondent aux valeurs de nom de famille et d’axe spécifiées. 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 de). Par exemple, l’application peut avoir besoin d’utiliser des documents qui référencent des polices à l’aide de paramètres RBIZ ou de style poids-étirement. Même si l’application ajoute la prise en charge de la spécification de valeurs d’axe arbitraires, elle peut également devoir 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 plus des valeurs d’axe des paramètres de poids, d’étirement, de style et de taille de police. Il transfère ces paramètres à la méthode IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues pour calculer des 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. Ils sont utilisés par MatchAxisValues pour écrire les valeurs d’axe d’entrée et les références de police qui en résultent :

// 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 compléter l’exemple, voici les fonctions d’analyse de ligne de commande et la fonction main :

char const g_usage[] =
"ParseCmdLine <args>\n"
"\n"
" -test             Test sample font selection parameters.\n"
" <familyname>      Sets the font family name.\n"
" -size <value>     Sets the font size in DIPs.\n"
" -bold             Sets weight to bold (700).\n"
" -weight <value>   Sets a weight in the range 100-900.\n"
" -italic           Sets style to DWRITE_FONT_STYLE_ITALIC.\n"
" -oblique          Sets style to DWRITE_FONT_STYLE_OBLIQUE.\n"
" -stretch <value>  Sets a stretch in the range 1-9.\n"
" -<axis>:<value>   Sets an axis value (for example, -opsz:24).\n"
" -nosim            Disallow font simulations.\n"
"\n";
struct CmdArgs
{
    std::wstring familyName;
    FontStyleParams styleParams;
    std::vector<DWRITE_FONT_AXIS_VALUE> axisValues;
    DWRITE_FONT_SIMULATIONS allowedSimulations = DWRITE_FONT_SIMULATIONS_BOLD | DWRITE_FONT_SIMULATIONS_OBLIQUE;
    bool test = false;
};
template<typename T>
_Success_(return)
bool ParseEnum(_In_z_ WCHAR const* arg, long minValue, long maxValue, _Out_ T* result)
{
    WCHAR* endPtr;
    long value = wcstol(arg, &endPtr, 10);
    *result = static_cast<T>(value);
    return value >= minValue && value <= maxValue && *endPtr == L'\0';
}
_Success_(return)
bool ParseFloat(_In_z_ WCHAR const* arg, _Out_ float* value)
{
    WCHAR* endPtr;
    *value = wcstof(arg, &endPtr);
    return *arg != L'\0' && *endPtr == L'\0';
}
bool ParseCommandLine(int argc, WCHAR** argv, CmdArgs& cmd)
{
    for (int argIndex = 1; argIndex < argc; argIndex++)
    {
        WCHAR const* arg = argv[argIndex];
        if (*arg != L'-')
        {
            if (!cmd.familyName.empty())
                return false;
            cmd.familyName = argv[argIndex];
        }
        else if (!wcscmp(arg, L"-test"))
        {
            cmd.test = true;
        }
        else if (!wcscmp(arg, L"-size"))
        {
            if (++argIndex == argc)
                return false;
            if (!ParseFloat(argv[argIndex], &cmd.styleParams.fontSize))
                return false;
        }
        else if (!wcscmp(arg, L"-bold"))
        {
            cmd.styleParams.fontWeight = DWRITE_FONT_WEIGHT_BOLD;
        }
        else if (!wcscmp(arg, L"-weight"))
        {
            if (++argIndex == argc)
                return false;
            if (!ParseEnum(argv[argIndex], 100, 900, &cmd.styleParams.fontWeight))
                return false;
        }
        else if (!wcscmp(arg, L"-italic"))
        {
            cmd.styleParams.fontStyle = DWRITE_FONT_STYLE_ITALIC;
        }
        else if (!wcscmp(arg, L"-oblique"))
        {
            cmd.styleParams.fontStyle = DWRITE_FONT_STYLE_OBLIQUE;
        }
        else if (!wcscmp(arg, L"-stretch"))
        {
            if (++argIndex == argc)
                return false;
            if (!ParseEnum(argv[argIndex], 1, 9, &cmd.styleParams.fontStretch))
                return false;
        }
        else if (wcslen(arg) > 5 && arg[5] == L':')
        {
            // Example: -opsz:12
            DWRITE_FONT_AXIS_VALUE axisValue;
            axisValue.axisTag = DWRITE_MAKE_FONT_AXIS_TAG(arg[1], arg[2], arg[3], arg[4]);
            if (!ParseFloat(arg + 6, &axisValue.value))
                return false;
            cmd.axisValues.push_back(axisValue);
        }
        else if (!wcscmp(arg, L"-nosim"))
        {
            cmd.allowedSimulations = DWRITE_FONT_SIMULATIONS_NONE;
        }
        else
        {
            return false;
        }
    }
    return true;
}
int __cdecl wmain(int argc, WCHAR** argv)
{
    CmdArgs cmd;
    if (!ParseCommandLine(argc, argv, cmd))
    {
        std::cerr << "Invalid command. Type TestFontSelection with no arguments for usage.\n";
        return 1;
    }
    if (cmd.familyName.empty() && !cmd.test)
    {
        std::cout << g_usage;
        return 0;
    }
    wil::com_ptr<IDWriteFactory7> factory;
    THROW_IF_FAILED(DWriteCoreCreateFactory(
        DWRITE_FACTORY_TYPE_SHARED,
        __uuidof(IDWriteFactory7),
        (IUnknown**)&factory
    ));
    if (!cmd.familyName.empty())
    {
        MatchFont(
            factory.get(),
            cmd.familyName.c_str(),
            cmd.styleParams,
            std::move(cmd.axisValues),
            cmd.allowedSimulations
        );
    }
    if (cmd.test)
    {
        TestFontSelection(factory.get());
    }
}