Compartir a través de


Selección de fuentes

La interfaz IDWriteFontSet4 expone métodos para seleccionar fuentes de un conjunto de fuentes. Estos métodos permiten realizar la transición al modelo de familia de fuentes tipográficas al tiempo que mantienen la compatibilidad con las aplicaciones, documentos y fuentes existentes.

La selección de fuentes (a veces denominada coincidencia de fuentes o asignación de fuentes) es el proceso de seleccionar las fuentes disponibles que mejor coincidan con los parámetros de entrada pasados por la aplicación. Los parámetros de entrada a veces se conocen colectivamente como una fuente lógica. Una fuente lógica incluye un nombre de familia de fuentes más otros atributos que indican una fuente determinada dentro de la familia. Un algoritmo de selección de fuentes coincide con la fuente lógica ("la fuente que desea") con una fuente física disponible ("una fuente que tenga").

Una familia de fuentes es un grupo con nombre de fuentes que comparten un diseño común, pero puede diferir en atributos como el peso. Un modelo de familia de fuentes define qué atributos se pueden usar para diferenciar las fuentes dentro de una familia. El nuevo modelo de familia de fuentes tipográficas tiene muchas ventajas sobre los dos modelos de familia de fuentes anteriores usados en Windows. Pero cambiar los modelos de familia de fuentes crea oportunidades de confusión y problemas de compatibilidad. Los métodos expuestos por la interfaz IDWriteFontSet4 implementan un enfoque híbrido que ofrece las ventajas del modelo de familia de fuentes tipográficas a la vez que mitiga los problemas de compatibilidad.

En este tema se comparan los modelos de familia de fuentes más antiguos con el modelo de familia de fuentes tipográficas; explica los desafíos de compatibilidad que plantea el cambio de modelos de familia de fuentes; y, por último, explica cómo se pueden superar esos desafíos mediante los métodos [IDWriteFontSet4](/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontset4).

Modelo de familia de fuentes RBIZ

El modelo de familia de fuentes de facto usado en el ecosistema de aplicaciones GDI se denomina a veces "modelo de cuatro fuentes" o "RBIZ". Cada familia de fuentes de este modelo suele tener como máximo cuatro fuentes. La etiqueta "RBIZ" procede de la convención de nomenclatura que se usa para algunos archivos de fuente, por ejemplo:

Nombre de archivo Estilo de fuente
verdana.ttf Normal
verdanab.ttf Bold
verdanai.ttf Cursiva
verdanaz.ttf Cursiva en negrita

Con GDI, los parámetros de entrada usados para seleccionar una fuente se definen mediante la estructura LOGFONT, que incluye los campos nombre de familia (), peso (lfFaceNamelfWeight) y cursiva (lfItalic). El lfItalic campo es TRUE o FALSE. GDI permite que el lfWeight campo sea cualquier valor del intervalo FW_THIN (100) para FW_BLACK (900), pero por razones históricas se han diseñado fuentes de forma que no haya más de dos pesos en la misma familia de fuentes GDI.

Las interfaces de usuario populares de la aplicación desde el principio incluían un botón cursiva (para activar y desactivar cursiva) y un botón de negrita (para alternar entre pesos normales y negritas). El uso de estos dos botones para seleccionar fuentes dentro de una familia supone el modelo RBIZ. Por lo tanto, aunque GDI admite más de dos pesos, la compatibilidad de aplicaciones llevó a los desarrolladores de fuentes a establecer el nombre de familia GDI (Id. de nombre de OpenType 1) de una manera coherente con el modelo RBIZ.

Por ejemplo, supongamos que quería agregar un peso "Negro" más pesado a la familia de fuentes Arial. Lógicamente, esta fuente forma parte de la familia Arial, por lo que es posible que espere seleccionarla estableciendo lfFaceName en "Arial" y lfWeighten FW_BLACK. Sin embargo, no hay forma de que un usuario de la aplicación elija entre tres pesos mediante un botón de negrita de dos estados. La solución era asignar a la nueva fuente un nombre de familia diferente, por lo que el usuario podía seleccionarla eligiendo "Arial Black" en la lista de familias de fuentes. Del mismo modo, no hay forma de elegir entre diferentes anchos en la misma familia de fuentes usando solo botones en negrita y cursiva, por lo que las versiones estrechas de Arial tienen nombres de familia diferentes en el modelo RBIZ. Por lo tanto, tenemos familias de fuentes "Arial", "Arial Black" y "Arial Narrow" en el modelo RBIZ, aunque todas pertenecen a una familia.

En estos ejemplos, se puede ver cómo las limitaciones de un modelo de familia de fuentes pueden afectar al modo en que las fuentes se agrupan en familias. Dado que las familias de fuentes se identifican por nombre, esto significa que la misma fuente puede tener nombres de familia diferentes en función del modelo de familia de fuentes que use.

DirectWrite no admite directamente el modelo de familia de fuentes RBIZ, pero proporciona métodos de conversión a y desde el modelo RBIZ, como IDWriteGdiInterop::CreateFontFromLOGFONT e IDWriteGdiInterop::ConvertFontToLOGFONT. También puede obtener el nombre de familia RBIZ de una fuente llamando a su método IDWriteFont::GetInformationalStrings y especificando DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES.

Modelo de familia de fuentes de estilo weight-stretch

El modelo de familia de fuentes de estilo weight-stretch es el modelo de familia de fuentes original usado por DirectWrite antes de que se introdujera el modelo de familia de fuentes tipográficas. También se conoce como pendiente de ancho de peso (WWS). En el modelo WWS, las fuentes dentro de la misma familia pueden ser diferentes por tres propiedades: peso (DWRITE_FONT_WEIGHT), stretch (DWRITE_FONT_STRETCH) y estilo (DWRITE_FONT_STYLE).

El modelo WWS es más flexible que el modelo RBIZ de dos maneras. En primer lugar, las fuentes de la misma familia se pueden diferenciar por ajuste (o ancho), así como el peso y el estilo (normal, cursiva o oblicuo). En segundo lugar, puede haber más de dos pesos en la misma familia. Esta flexibilidad es suficiente para permitir que todas las variantes de Arial se incluyan en la misma familia WWS. En la tabla siguiente se comparan las propiedades de fuente RBIZ y WWS para una selección de fuentes Arial:

Nombre completo Nombre de familia de RBIZ lfWeight lfItalic WWS FamilyName Peso Stretch Estilo
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

Como puede ver, "Arial Narrow" tiene los mismos lfWeight valores y lfItalic que "Arial", por lo que tiene un nombre de familia RBIZ diferente para evitar ambigüedad. "Arial Black" tiene un nombre de familia RBIZ diferente para evitar tener más de dos pesos en la familia "Arial". Por el contrario, todas estas fuentes están en la misma familia de estilo weight-stretch.

Sin embargo, el modelo de estilo weight-stretch no está abierto. Si dos fuentes tienen el mismo peso, ajuste y estilo, pero difieren de alguna otra manera (por ejemplo, tamaño óptico), no se pueden incluir en la misma familia de fuentes WWS. Esto nos lleva al modelo de familia de fuentes tipográficas.

Modelo de familia de fuentes tipográficas

A diferencia de sus predecesores, el modelo de familia de fuentes tipográficas está abierto. Admite cualquier número de ejes de variación dentro de una familia de fuentes.

Si piensa en parámetros de selección de fuentes como coordenadas en un espacio de diseño, el modelo de estilo weight-stretch define un sistema de coordenadas tridimensionales con peso, ajuste y estilo como ejes. Cada fuente de una familia WWS debe tener una ubicación única definida por sus coordenadas a lo largo de esos tres ejes. Para seleccionar una fuente, especifique un nombre de familia WWS y parámetros de peso, stretch y style.

Por el contrario, el modelo de familia de fuentes tipográficas tiene un espacio de diseño N dimensional. Un diseñador de fuentes puede definir cualquier número de ejes de diseño, cada uno identificado por una etiqueta de eje de cuatro caracteres. La ubicación de una fuente determinada en el espacio de diseño N dimensional se define mediante una matriz de valores de eje, donde cada valor del eje consta de una etiqueta de eje y un valor de punto flotante. Para seleccionar una fuente, especifique un nombre de familia tipográfico y una matriz de valores de eje (estructuras DWRITE_FONT_AXIS_VALUE ).

Aunque el número de ejes de fuente está abierto, hay algunos ejes registrados con significados estándar y los valores de peso, stretch y estilo se pueden asignar a los valores de eje registrados. DWRITE_FONT_WEIGHT se puede asignar a un valor de eje "wght" (DWRITE_FONT_AXIS_TAG_WEIGHT). DWRITE_FONT_STRETCH se puede asignar a un valor de eje "wdth" (DWRITE_FONT_AXIS_TAG_WIDTH). DWRITE_FONT_STYLE se puede asignar a una combinación de valores de eje "ital" y "slnt" (DWRITE_FONT_AXIS_TAG_ITALIC y DWRITE_FONT_AXIS_TAG_SLANT).

Otro eje registrado es "opsz" (DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE). Una familia de fuentes ópticas como Sitka incluye fuentes que difieren a lo largo del eje "opsz", lo que significa que están diseñadas para usarse en diferentes tamaños de punto. El modelo de familia de fuentes WWS no tiene un eje óptico de tamaño, por lo que la familia de fuentes Sitka debe dividirse en varias familias de fuentes WWS: "Sitka Small", "Sitka Text", "Sitka Subheading", etc. Cada familia de fuentes WWS corresponde a un tamaño óptico diferente y se deja al usuario especificar el nombre de familia WWS adecuado para un tamaño de fuente determinado. Con el modelo de familia de fuentes tipográficas, el usuario simplemente puede elegir "Sitka", y la aplicación puede establecer automáticamente el valor del eje "opsz" en función del tamaño de fuente.

Selección de fuentes tipográficas y fuentes variables

El concepto de ejes de variación suele asociarse con fuentes variables, pero también se aplica a las fuentes estáticas. La tabla OpenType STAT (atributos de estilo) declara qué ejes de diseño tiene una fuente y los valores de esos ejes. Esta tabla es necesaria para las fuentes variables, pero también es relevante para las fuentes estáticas.

La API de DirectWrite expone los valores del eje "wght", "wdth", "ital" y "slnt" para cada fuente, incluso si no están presentes en la tabla STAT o si no hay ninguna tabla STAT. Estos valores se derivan de la tabla STAT si es posible. De lo contrario, se derivan del peso de fuente, el ajuste de fuente y el estilo de fuente.

Los ejes de fuente pueden ser variables o no variables. Una fuente estática solo tiene ejes no variables, mientras que una fuente variable puede tener ambas. Para usar una fuente de variable, debe crear una instancia de fuente de variable en la que todos los ejes de variable se hayan enlazado a valores concretos. La interfaz IDWriteFontFace representa una fuente estática o una instancia determinada de una fuente variable. Es posible crear una instancia arbitraria de una fuente variable con valores de eje especificados. Además, una fuente de variable puede declarar instancias con nombre en la tabla STAT con combinaciones predefinidas de valores de eje. Las instancias con nombre permiten que una fuente variable se comporte de forma muy similar a una colección de fuentes estáticas. Cuando se enumeran los elementos de un IDWriteFontFamily o IDWriteFontSet, hay un elemento para cada fuente estática y para cada instancia de fuente de variable con nombre.

En primer lugar, el algoritmo de coincidencia de fuentes tipográficas selecciona posibles candidatos de coincidencia en función del nombre de familia. Si los candidatos coincidentes incluyen fuentes de variable, todos los candidatos de coincidencia para la misma fuente de variable se contraen en un candidato coincidente en el que cada eje de variables tiene asignado un valor específico lo más cercano posible al valor solicitado para ese eje. Si no hay ningún valor solicitado para un eje de variables, se le asigna el valor predeterminado para ese eje. A continuación, el orden de los candidatos de coincidencia se determina comparando sus valores de eje con los valores de eje solicitados.

Por ejemplo, considere la familia tipográfica Sitka en Windows. Sitka es una familia de fuentes ópticas, lo que significa que tiene un eje "opsz". En Windows 11, Sitka se implementa como dos fuentes de variable con los siguientes valores de eje. Tenga en cuenta que los opsz ejes y wght son variables, mientras que los otros ejes no son variables.

Nombre de archivo "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

Supongamos que los valores del eje solicitados son opsz:12 wght:475 wdth:100 ital:0 slnt:0. Para cada fuente de variable, se crea una referencia a una instancia de fuente de variable en la que a cada eje de variable se le asigna un valor específico. Es decir, los opsz ejes y wght se establecen 12 en y 475, respectivamente. Esto produce las siguientes fuentes coincidentes, con la fuente no cursiva clasificada primero porque es una mejor coincidencia para los ital ejes y 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

En el ejemplo anterior, las fuentes coincidentes son instancias de fuente de variable arbitrarias. No hay ninguna instancia con nombre de Sitka con peso 475. En cambio, el algoritmo de coincidencia de estilo weight-stretch devuelve solo instancias con nombre.

Orden de coincidencia de fuentes

Hay diferentes métodos sobrecargados GetMatchingFonts para el modelo de familia de fuentes de estilo weight-stretch (IDWriteFontFamily::GetMatchingFonts) y el modelo de familia de fuentes tipográficas (IDWriteFontCollection2::GetMatchingFonts). En ambos casos, la salida es una lista de fuentes coincidentes en orden descendente de prioridad en función de la forma en que cada fuente candidata coincida con las propiedades de entrada. En esta sección se describe cómo se determina la prioridad.

En el modelo de estilo weight-stretch, los parámetros de entrada son peso de fuente (DWRITE_FONT_WEIGHT), ajuste de fuente (DWRITE_FONT_STRETCH) y estilo de fuente (DWRITE_FONT_STYLE). El algoritmo para encontrar la mejor coincidencia se documentó en una notas del producto de 2006 titulada "WpF Font Selection Model" (Modelo de selección de fuentes de WPF) de Ivan Leonov y David Brown. Consulte la sección "Coincidencia de una cara de la lista de caras candidatas". Este documento fue sobre Windows Presentation Foundation (WPF), pero DirectWrite más adelante usó el mismo enfoque.

El algoritmo usa la noción de vector de atributo de fuente, que para una combinación determinada de peso, stretch y estilo se calcula de la siguiente manera:

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

Tenga en cuenta que cada coordenada vectorial se normaliza restando el valor "normal" del atributo correspondiente y multiplicando por una constante. Los multiplicadores compensan el hecho de que los intervalos de valores de entrada para el peso, el ajuste y el estilo son muy diferentes. De lo contrario, el peso (100..999) dominaría el estilo (0..2).

Para cada candidato de coincidencia, se calcula una distancia vectorial y un producto de puntos entre el vector de atributo de fuente del candidato coincidente y el vector de atributo de fuente de entrada. Al comparar dos candidatos de coincidencia, el candidato con la distancia vectorial más pequeña es la mejor coincidencia. Si las distancias son las mismas, el candidato con el producto de punto más pequeño es una mejor coincidencia. Si el producto de puntos también es el mismo, las distancias a lo largo de los ejes X, Y y Z se comparan en ese orden.

La comparación de distancias es lo suficientemente intuitiva, pero el uso del producto de puntos como medida secundaria puede requerir alguna explicación. Supongamos que el peso de entrada es semibold (600) y dos pesos candidatos son negros (900) y semiluminación (300). La distancia de cada peso candidato del peso de entrada es la misma, pero el peso negro está en la misma dirección desde el origen (es decir, 400 o normal), por lo que tendrá un producto de punto más pequeño.

El algoritmo de coincidencia tipográfica es una generalización de la que se usa para el estilo de ajuste de peso. Cada valor del eje se trata como una coordenada en un vector de atributo de fuente N dimensional. Para cada candidato de coincidencia, se calcula una distancia vectorial y un producto de puntos entre el vector de atributo de fuente del candidato coincidente y el vector de atributo de fuente de entrada. El candidato con la distancia de vector más pequeña es la mejor coincidencia. Si las distancias son las mismas, el candidato con el producto de punto más pequeño es una mejor coincidencia. Si el producto de puntos también es el mismo, la presencia en una familia de estilo weight-stretch especificado se puede usar como un desempate.

Para calcular la distancia vectorial y el producto de puntos, el vector de atributo de fuente de un candidato coincidente y el vector del atributo de fuente de entrada deben tener los mismos ejes. Por lo tanto, cualquier valor de eje que falte en cualquiera de los vectores se rellena sustituyendo el valor estándar de ese eje. Las coordenadas vectoriales se normalizan restando el valor estándar (o "normal") del eje correspondiente y multiplicando el resultado por un multiplicador específico del eje. A continuación se muestran los multiplicadores y los valores estándar de cada eje:

Eje Multiplicador Valor estándar
"wght" 5 400
"wdth" 55 100
"ital" 1400 0
"slnt" 35 0
"opsz" 1 12
otro 1 0

Los multiplicadores son coherentes con los usados por el algoritmo de estilo weight-stretch, pero se escalan según sea necesario. Por ejemplo, el ancho normal es 100, que equivale a estirar 5. Esto produce un multiplicador de 55 frente a 1100. El atributo de estilo heredado (0..2) entrelaza en cursiva y oblicuo, que en el modelo tipográfico se descompone en un eje "ital" (0..1) y un eje "slnt" (-90..90). Los multiplicadores elegidos para estos dos ejes proporcionan resultados equivalentes al algoritmo heredado si se supone un slant predeterminado de 20 grados para fuentes oblicuas.

Selección de fuentes tipográficas y tamaño óptico

Una aplicación que usa el modelo de familia de fuentes tipográficas puede implementar el ajuste de tamaño óptico especificando un opsz valor de eje como parámetro de selección de fuente. Por ejemplo, una aplicación de procesamiento de texto podría especificar un opsz valor de eje igual al tamaño de fuente en puntos. En este caso, un usuario podría seleccionar "Sitka" como familia de fuentes y la aplicación seleccionaría automáticamente una instancia de Sitka con el valor de eje correcto opsz . En el modelo WWS, cada tamaño óptico se expone como un nombre de familia diferente y el usuario debe seleccionar el adecuado.

En teoría, uno podría implementar el ajuste de tamaño óptico automático bajo el modelo de estilo weight-stretch reemplazando el valor del eje como un paso independiente después de la opsz selección de fuente. Sin embargo, esto solo funciona si la primera fuente coincidente es una fuente variable con un eje de variables opsz . Especificar opsz como parámetro de selección de fuente funciona igualmente bien para las fuentes estáticas. Por ejemplo, la familia de fuentes Sitka se implementa como fuentes variables en Windows 11, pero como una colección de fuentes estáticas en Windows 10. Las fuentes estáticas tienen intervalos de opsz ejes diferentes y no superpuestos (se declaran como rangos con fines de selección de fuentes, pero no son ejes variables). Especificar opsz como parámetro de selección de fuente permite seleccionar la fuente estática correcta para el tamaño óptico.

Ventajas de la selección de fuentes tipográficas y problemas de compatibilidad

El modelo de selección de fuentes tipográficas tiene varias ventajas sobre los modelos anteriores, pero en su forma pura tiene algunos posibles problemas de compatibilidad. En esta sección se describen las ventajas y los problemas de compatibilidad. En la sección siguiente se describe un modelo de selección de fuentes híbrida que conserva las ventajas al mitigar los problemas de compatibilidad.

Las ventajas del modelo de familia de fuentes tipográficas son:

  • Las fuentes se pueden agrupar en familias según lo previsto por el diseñador, en lugar de dividirse en subfamias debido a limitaciones del modelo de familia de fuentes.

  • Una aplicación puede seleccionar automáticamente el valor de eje correcto opsz en función del tamaño de fuente, en lugar de exponer diferentes tamaños ópticos al usuario como diferentes familias de fuentes.

  • Se pueden seleccionar instancias arbitrarias de fuentes de variables. Por ejemplo, si una fuente variable admite pesos en el intervalo continuo 100-900, el modelo tipográfico puede seleccionar cualquier peso de este intervalo. Los modelos de familia de fuentes anteriores solo pueden elegir el peso más cercano entre las instancias con nombre definidas por la fuente.

Los problemas de compatibilidad con el modelo de selección de fuentes tipográficas son:

  • Algunas fuentes anteriores no se pueden seleccionar de forma inequívoca con solo el nombre de familia tipográfico y los valores del eje.

  • Los documentos existentes pueden hacer referencia a fuentes por nombre de familia WWS o nombre de familia RBIZ. Los usuarios también pueden esperar usar nombres de familia WWS y RBIZ. Por ejemplo, un documento podría especificar "Sitka Subheading" (un nombre de familia WWS) en lugar de "Sitka" (un nombre de familia tipográfico).

  • Una biblioteca o marco podría adoptar el modelo de familia de fuentes tipográficas para aprovechar el ajuste de tamaño óptico automático, pero no proporcionar una API para especificar valores de eje arbitrarios. Incluso si se proporciona una nueva API, es posible que el marco de trabajo tenga que trabajar con aplicaciones existentes que especifiquen solo parámetros de peso, ajuste y estilo.

El problema de compatibilidad con las fuentes anteriores se produce porque el concepto de nombre de familia tipográfico precede al concepto de valores del eje de fuentes, que se introdujeron junto con fuentes variables en OpenType 1.8. Antes de OpenType 1.8, el nombre de familia tipográfico simplemente expresó la intención del diseñador de que un conjunto de fuentes estuviera relacionado, pero sin ninguna garantía de que esas fuentes se pudieran diferenciar mediante programación en función de sus propiedades. Como ejemplo hipotético, supongamos que todas las fuentes siguientes tienen el nombre de familia tipográfico "Legacy":

Nombre completo Familia WWS Peso Stretch Estilo Familia de errores tipográficos wght wdth Ital slnt
Heredado Heredado 400 5 0 Heredado 400 100 0 0
Negrita heredada Heredado 700 5 0 Heredado 700 100 0 0
Negro heredado Heredado 900 5 0 Heredado 900 100 0 0
Software heredado Software heredado 400 5 0 Heredado 400 100 0 0
Negrita suave heredada Software heredado 700 5 0 Heredado 700 100 0 0
Negro suave heredado Software heredado 900 5 0 Heredado 900 100 0 0

La familia tipográfica "Heredada" tiene tres pesos, y cada peso tiene variantes regulares y "Suaves". Si fueran fuentes nuevas, se podrían implementar como eje de diseño SOFT. Sin embargo, estas fuentes predescriben OpenType 1.8, por lo que sus únicos ejes de diseño son los derivados de peso, stretch y style. Para cada peso, esta familia de fuentes tiene dos fuentes con valores de eje idénticos, por lo que no es posible seleccionar de forma inequívoca una fuente en esta familia usando valores de eje solo.

Algoritmo de selección de fuentes híbridas

Las API de selección de fuentes descritas en la sección siguiente usan un algoritmo de selección de fuentes híbrida que conserva las ventajas de la selección de fuentes tipográficas a la vez que mitiga sus problemas de compatibilidad.

La selección de fuentes híbridas proporciona un puente de los modelos de familia de fuentes anteriores al permitir asignar valores de peso de fuente, ajuste de fuente y estilo de fuente a los valores de eje de fuentes correspondientes. Esto ayuda a solucionar problemas de compatibilidad de documentos y aplicaciones.

Además, el algoritmo de selección de fuentes híbridas permite que el nombre de familia especificado sea un nombre de familia tipográfico, un nombre de familia de estilo weight-stretch, un nombre de familia GDI/RBIZ o un nombre de fuente completo. La coincidencia se produce de una de las siguientes maneras, en orden descendente de prioridad:

  1. El nombre coincide con una familia tipográfica (por ejemplo, Sitka). La coincidencia se produce dentro de la familia tipográfica y se usan todos los valores de eje solicitados. Si el nombre también coincide con un subfamily WWS (es decir, uno más pequeño que la familia tipográfica), la pertenencia a la subfamily WWS se usa como un desempate.

  2. El nombre coincide con una familia WWS (por ejemplo, Sitka Text). La coincidencia se produce dentro de la familia WWS y se omiten los valores de eje solicitados distintos de "wght", "wdth", "ital" y "slnt".

  3. El nombre coincide con una familia GDI (por ejemplo, Bahnschrift Condensed). La coincidencia se produce dentro de la familia RBIZ y se omiten los valores de eje solicitados distintos de "wght", "ital" y "slnt".

  4. El nombre coincide con un nombre completo (por ejemplo, Bahnschrift Bold Condensed). Se devuelve la fuente que coincide con el nombre completo. Los valores de eje solicitados se omiten. Se permite la coincidencia por nombre de fuente completo porque GDI lo admite.

En la sección anterior se describió una familia tipográfica ambigua denominada "Heredado". El algoritmo híbrido permite evitar la ambigüedad especificando "Heredado" o "Software heredado" como nombre de familia. Si se especifica "Software heredado", no hay ambigüedad porque la coincidencia solo se produce dentro de la familia WWS. Si se especifica "Heredado", todas las fuentes de la familia tipográfica se consideran candidatos de coincidencia, pero la ambigüedad se evita mediante el uso de la pertenencia a la familia WWS "Heredada" como un desempate.

Supongamos que un documento especifica un nombre de familia y parámetros de peso, ajuste y estilo, pero sin valores de eje. La aplicación puede convertir primero el tamaño de peso, ajuste, estilo y fuente en valores de eje llamando a IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues. A continuación, la aplicación puede pasar los valores de nombre de familia y eje a IDWriteFontSet4::GetMatchingFonts. GetMatchingFonts devuelve una lista de fuentes coincidentes en orden de prioridad y el resultado es adecuado si el nombre de familia especificado es un nombre de familia tipográfico, un nombre de familia de estilo weight-stretch, un nombre de familia de estilo RBIZ o un nombre completo. Si la familia especificada tiene un eje "opsz", el tamaño óptico adecuado se selecciona automáticamente en función del tamaño de fuente.

Supongamos que un documento especifica el peso, el ajuste y el estilo, y también especifica valores de eje. En ese caso, los valores explícitos del eje también se pueden pasar a IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues y los valores de eje derivados devueltos por el método incluirán solo ejes de fuente que no se especificaron explícitamente. Por lo tanto, los valores de eje especificados explícitamente por el documento (o la aplicación) tienen prioridad sobre los valores de eje derivados del peso, el ajuste, el estilo y el tamaño de fuente.

API de selección de fuentes híbridas

El modelo de selección de fuentes híbrida se implementa mediante los siguientes métodos IDWriteFontSet4 :

  • El método IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues convierte los parámetros de tamaño de fuente, peso, stretch y estilo en los valores de eje correspondientes. Los valores de eje explícitos pasados por el cliente se excluyen de los valores del eje derivado.

  • El método IDWriteFontSet4::GetMatchingFonts devuelve una lista prioritaria de fuentes coincidentes dadas un nombre de familia y una matriz de valores de eje. Como se ha descrito anteriormente, el parámetro de nombre de familia puede ser un nombre de familia tipográfico, un nombre de familia WWS, un nombre de familia RBIZ o un nombre completo. (El nombre completo identifica un estilo de fuente determinado, como "Arial Bold Italic". GetMatchingFonts admite la coincidencia por nombre completo para una comaptibiltiy mayor con GDI, lo que también lo permite).

Las siguientes API de DirectWrite también usan el algoritmo de selección de fuentes híbridas:

Ejemplos de código de las API de selección de fuentes en uso

En esta sección se muestra una aplicación de consola completa que muestra los métodos IDWriteFontSet4::GetMatchingFonts e IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues . En primer lugar, vamos a incluir algunos encabezados:

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

El método IDWriteFontSet4::GetMatchingFonts devuelve una lista de fuentes en orden de prioridad que coinciden con los valores de eje y nombre de familia especificados. La siguiente función MatchAxisValues genera los parámetros en IDWriteFontSet4::GetMatchingFonts y la lista de fuentes coincidentes en el conjunto de fuentes devueltos.

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

Una aplicación puede tener parámetros de peso, ajuste y estilo en lugar de valores de eje (o además). Por ejemplo, es posible que la aplicación necesite trabajar con documentos que hagan referencia a fuentes mediante parámetros de estilo RBIZ o weight-stretch. Incluso si la aplicación agrega compatibilidad para especificar valores de eje arbitrarios, es posible que también tenga que admitir los parámetros anteriores. Para ello, la aplicación puede llamar a IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues antes de llamar a IDWriteFontSet4::GetMatchingFonts.

La siguiente función MatchFont toma parámetros de peso, ajuste, estilo y tamaño de fuente, además de los valores del eje. Reenvía estos parámetros al método IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues para calcular los valores del eje derivado, que se anexan a los valores del eje de entrada. Pasa los valores del eje combinado a la función MatchAxisValues anterior.

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 siguiente función muestra los resultados de llamar a la función MatchFont anterior con algunas entradas de ejemplo:

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

A continuación se muestra la salida de la función TestFontSelection anterior:

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

A continuación se muestran las implementaciones de los operadores sobrecargados declarados anteriormente. MatchAxisValues usa estas opciones para escribir los valores del eje de entrada y las referencias de las caras de fuente resultantes:

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

Para redondear el ejemplo, a continuación se muestran las funciones de análisis de línea de comandos y la función principal :

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