Compartir a través de


Selección de fuentes

La interfazIDWriteFontSet4 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 , a la vez que se mantiene 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. A veces, los parámetros de entrada 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") a un fuente física disponible ("una fuente que tiene").

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 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 mitigan problemas de compatibilidad.

En este tema se comparan los modelos de familia de fuentes anteriores con el modelo de familia de fuentes tipográficas; explica los desafíos de compatibilidad que plantea el cambio de los modelos de familia de fuentes; y finalmente 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 a veces se denomina "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 Regular
verdanab.ttf Audaz
verdanai.ttf Itálico
verdanaz.ttf Cursiva negrita

Con GDI, los parámetros de entrada que se usan para seleccionar una fuente se definen mediante la estructuraLOGFONT de, que incluye el nombre de familia (lfFaceName), el peso (lfWeight) y los campos cursiva (lfItalic). El campo lfItalic es TRUE o FALSE. GDI permite que el campo de lfWeight 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 (alternar entre pesos normales y negrita). 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 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 lfWeight en 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 dar 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 manera de elegir entre distintos anchos de 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 estas pertenecen de forma tipográfica a una familia.

En estos ejemplos, se puede ver cómo las limitaciones de un modelo de familia de fuentes pueden afectar a cómo se agrupan las fuentes 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 y IDWriteGdiInterop::ConvertFontToLOGFONT. También puede obtener el nombre de familia RBIZ de una fuente llamando a su IDWriteFont::GetInformationalStrings método 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 stretch (o width) así como por peso y estilo (regular, 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 de Arial:

Nombre completo Nombre de familia de RBIZ lfWeight lfItalic WWS FamilyName Peso Elasticidad Estilo
Arial Arial 400 0 Arial 400 5 0
Negrita Arial 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
Negrita estrecha de Arial Arial Narrow 700 0 Arial 700 3 0

Como puede ver, "Arial Narrow" tiene los mismos valores lfWeight 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 stretch de peso.

Sin embargo, el modelo de estilo de ajuste de peso no está abierto. Si dos fuentes tienen el mismo peso, ajuste y estilo, pero difieren de alguna otra manera (por ejemplo, tamaño óptico), entonces 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 los parámetros de selección de fuentes como coordenadas en un espacio de diseño, el modelo de estilo de ajuste de peso define un sistema de coordenadas tridimensional con peso, stretch 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 y un peso, stretch y style de WWS.

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 (DWRITE_FONT_AXIS_VALUE estructuras).

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 de tamaño óptico, 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 valores de 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 variable, debe crear una fuente de variable instancia en la que todos los ejes de variable se han enlazado a valores concretos. La interfazIDWriteFontFace derepresenta una fuente estática o una instancia de determinada de una fuente variable. Es posible crear una instancia arbitraria de una fuente variable con valores de eje especificados. Además, una fuente variable puede declarar instancias con nombre en la tabla de 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. Al enumerar elementos de un IDWriteFontFamily o IDWriteFontSet, hay un elemento para cada fuente estática y para cada instancia de fuente 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 variables, todos los candidatos de coincidencia para la misma fuente de variable se contraen en un candidato de coincidencia en el que cada eje de variable 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 del 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 variables con los siguientes valores de eje. Tenga en cuenta que los ejes opsz 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 variable, creamos una referencia a una fuente de variable instancia en la que se asigna un valor específico a cada eje de variables. Es decir, los ejes opsz y wght se establecen en 12 y 475, respectivamente. Esto produce las siguientes fuentes coincidentes, con la fuente no cursiva clasificada primero porque es una mejor coincidencia para el ital y slnt ejes:

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 variable arbitrarias. No hay ninguna instancia con nombre de Sitka con peso 475. Por el contrario, 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 coincide 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 el peso de fuente (DWRITE_FONT_WEIGHT), la extensión de fuente (DWRITE_FONT_STRETCH) y el estilo de fuente (DWRITE_FONT_STYLE). El algoritmo para encontrar la mejor coincidencia se documentó en una notas del producto de 2006 titulada "Modelo de selección de fuentes WPF" de Miguel Leonov y David Brown. Consulte la sección "Coincidencia de una cara de la lista de caras candidatas". Este documento se refiere a 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 peso, ajuste y 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 punto 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 puntos más pequeño es una coincidencia mejor. 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 podría requerir alguna explicación. Supongamos que el peso de entrada es semibold (600) y dos pesos candidatos son negros (900) y semiligero (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 punto entre el vector de atributo de fuente del candidato coincidente y el vector de atributo de fuente de entrada. 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 puntos más pequeño es una coincidencia mejor. Si el producto de puntos también es el mismo, la presencia en una familia de estilo peso-stretch-style especificada se puede usar como un desempate.

Para calcular la distancia de vector y el producto de punto, el vector de atributo de fuente de un candidato coincidente y el vector de 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 utilizados 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 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 que hay un slant predeterminado de 20 grados para las 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 óptico de tamaño especificando un valor de eje opsz como parámetro de selección de fuentes. Por ejemplo, una aplicación de procesamiento de texto podría especificar un valor de eje opsz 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 del eje opsz correcto. En el modelo WWS, cada tamaño óptico se expone como un nombre de familia diferente y es el usuario el que debe seleccionar el correcto.

En teoría, uno podría implementar el ajuste óptico automático de tamaño bajo el modelo de estilo de ajuste de peso reemplazando el valor del eje opsz como un paso independiente después de selección de fuentes. Sin embargo, esto solo funciona si la primera fuente coincidente es una fuente variable con una variable opsz eje. Especificar opsz como parámetro de selección de fuentes 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 ejes diferentes y no superpuestos opsz (se declaran como intervalos con fines de selección de fuentes, pero no son ejes variables). Especificar opsz como parámetro de selección de fuentes permite seleccionar la fuente estática correcta para el tamaño óptico.

Ventajas de 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 a la vez que se mitigan 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 subfamílidos debido a limitaciones del modelo de familia de fuentes.

  • Una aplicación puede seleccionar automáticamente el valor de eje de opsz correcto 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 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 mediante solo los valores de nombre de familia y eje tipográficos.

  • Los documentos existentes pueden hacer referencia a fuentes por el nombre de familia wwS o el nombre de familia RBIZ. Los usuarios también podrían 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 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 solo especifiquen parámetros de peso, ajuste y estilo.

El problema de compatibilidad con las fuentes anteriores surge porque el concepto de nombre de familia tipográfico predepone el 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 Elasticidad Estilo Familia tipográfico wght wdth Ital slnt
Legado Legado 400 5 0 Legado 400 100 0 0
Negrita heredada Legado 700 5 0 Legado 700 100 0 0
Negro heredado Legado 900 5 0 Legado 900 100 0 0
Software heredado Software heredado 400 5 0 Legado 400 100 0 0
Negrita suave heredada Software heredado 700 5 0 Legado 700 100 0 0
Negro suave heredado Software heredado 900 5 0 Legado 900 100 0 0

La familia tipográfica "Heredada" tiene tres pesos, y cada peso tiene variantes regulares y "Suaves". Si estas fueran nuevas fuentes, se podrían implementar como declarando un 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 del peso, la extensión y el estilo. 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 solo valores de eje.

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íbrida proporciona un puente de modelos de familia de fuentes anteriores al permitir que los valores de peso de fuente, ajuste de fuente y estilo de fuente se asignen 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íbrida 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 una subfamily wwS (es decir, una más pequeña 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 del eje solicitados se omiten. Se permite la coincidencia por nombre de fuente completo porque GDI lo admite.

En la sección anterior se describe una familia tipográfica ambigua denominada "Legacy". El algoritmo híbrido permite evitar la ambigüedad especificando "Legacy" o "Legacy Soft" como nombre de familia. Si se especifica "Legacy Soft", no hay ambigüedad porque la coincidencia solo se produce dentro de la familia WWS. Si se especifica "Legacy", 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 "Legacy" 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 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 de eje explícitos también se pueden pasar a IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValuesy 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 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 de IDWriteFontSet4:

  • El método IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues convierte los parámetro s 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 dada 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, 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 IDWriteFontSet4::GetMatchingFonts y IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues métodos. 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 para 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 tenga que 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 método 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 función siguiente 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. Estos se usan en MatchAxisValues para escribir los valores del eje de entrada y las referencias de la cara 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 de :

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