Partilhar via


Seleção de fontes

A interfaceIDWriteFontSet4 doexpõe métodos para selecionar fontes de um conjunto de fontes. Esses métodos possibilitam a transição para o modelo de família de fontes tipográficas mantendo a compatibilidade com aplicativos, documentos e fontes existentes.

A seleção de fontes (às vezes chamada de correspondência de fontes ou mapeamento de fontes) é o processo de seleção das fontes disponíveis que melhor correspondem aos parâmetros de entrada passados pelo seu aplicativo. Os parâmetros de entrada às vezes são referidos coletivamente como uma fonte lógica . Uma fonte lógica inclui um nome de família de fontes mais outros atributos que indicam uma fonte específica dentro da família. Um algoritmo de seleção de fontes corresponde à fonte lógica ("a fonte desejada") a uma fonte física disponível ("uma fonte que você tem").

Uma família de fontes é um grupo nomeado de fontes que compartilham um design comum, mas podem diferir em atributos como peso. Um modelo de família de fontes define quais atributos podem ser usados para diferenciar fontes dentro de uma família. O novo modelo de família de fontes tipográficas tem muitas vantagens em relação aos dois modelos anteriores de família de fontes usados no Windows. Mas alterar os modelos de família de fontes cria oportunidades para confusão e problemas de compatibilidade. Os métodos expostos pela interfaceIDWriteFontSet4 daimplementam uma abordagem híbrida que oferece as vantagens do modelo de família de fontes tipográficas enquanto mitiga problemas de compatibilidade.

Este tópico compara os modelos de família de fontes mais antigos com o modelo de família de fontes tipográficas; explica os desafios de compatibilidade colocados pela alteração dos modelos da família de fontes; e, finalmente, explica como esses desafios podem ser superados usando os métodos [IDWriteFontSet4](/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontset4).

Modelo da família de fontes RBIZ

O modelo de família de fontes de facto usado no ecossistema de aplicações GDI é por vezes chamado de "modelo de quatro fontes" ou modelo "RBIZ". Cada família de fontes neste modelo normalmente tem no máximo quatro fontes. O rótulo "RBIZ" vem da convenção de nomenclatura usada para alguns arquivos de fonte, por exemplo:

Nome do arquivo Estilo da fonte
verdana.ttf Regular
verdanab.ttf Negrito
verdanai.ttf Itálico
verdanaz.ttf Negrito Itálico

Com o GDI, os parâmetros de entrada usados para selecionar uma fonte são definidos pela estruturaLOGFONT, que inclui os campos de nome de família (lfFaceName), peso (lfWeight) e itálico (lfItalic). O campo lfItalic é TRUE ou FALSE. GDI permite que o campo lfWeight seja qualquer valor no intervalo FW_THIN (100) a FW_BLACK (900), mas por razões históricas as fontes foram projetadas há muito tempo para que não haja mais de dois pesos na mesma família de fontes GDI.

As interfaces de usuário de aplicativos populares desde o início incluíam um botão em itálico (para ligar e desligar o itálico) e um botão em negrito (para alternar entre pesos normal e negrito). O uso desses dois botões para selecionar fontes dentro de uma família pressupõe o modelo RBIZ. Portanto, embora o próprio GDI suporte mais de dois pesos, a compatibilidade de aplicativos levou os desenvolvedores de fontes a definir o nome da família GDI (OpenType name ID 1) de uma forma consistente com o modelo RBIZ.

Por exemplo, suponha que você queira adicionar um peso "Preto" mais pesado à família de fontes Arial. Logicamente, essa fonte faz parte da família Arial, então você pode esperar selecioná-la definindo lfFaceName como "Arial" e lfWeight como FW_BLACK. No entanto, não há como um usuário do aplicativo escolher entre três pesos usando um botão negrito de dois estados. A solução foi dar à nova fonte um nome de família diferente, para que o usuário pudesse selecioná-la escolhendo "Arial Black" na lista de famílias de fontes. Da mesma forma, não há como escolher entre diferentes larguras na mesma família de fontes usando apenas botões em negrito e itálico, de modo que as versões estreitas do Arial têm nomes de família diferentes no modelo RBIZ. Assim, temos as famílias de fontes "Arial", "Arial Black" e "Arial Narrow" no modelo RBIZ, embora tipograficamente todas elas pertençam a uma família.

A partir desses exemplos, pode-se ver como as limitações de um modelo de família de fontes podem afetar como as fontes são agrupadas em famílias. Como as famílias de fontes são identificadas pelo nome, isso significa que a mesma fonte pode ter nomes de família diferentes, dependendo do modelo de família de fontes que você está usando.

O DirectWrite não suporta diretamente o modelo da família de fontes RBIZ, mas fornece métodos de conversão de e para o modelo RBIZ, como IDWriteGdiInterop::CreateFontFromLOGFONT e IDWriteGdiInterop::ConvertFontToLOGFONT. Você também pode obter o nome de família RBIZ de uma fonte chamando seu IDWriteFont::GetInformationalStrings método e especificando DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES.

Modelo de família de fontes estilo peso-estiramento

O modelo de família de fontes weight-stretch-style é o modelo original da família de fontes usado pelo DirectWrite antes da introdução do modelo de família de fontes tipográficas. É também conhecido como peso-largura-inclinação (WWS). No modelo WWS, as fontes dentro da mesma família podem ser diferenciadas por três propriedades: peso (DWRITE_FONT_WEIGHT), stretch (DWRITE_FONT_STRETCH) e estilo (DWRITE_FONT_STYLE).

O modelo WWS é mais flexível do que o modelo RBIZ de duas maneiras. Primeiro, as fontes da mesma família podem ser diferenciadas por alongamento (ou largura), bem como peso e estilo (regular, itálico ou oblíquo). Em segundo lugar, pode haver mais de dois pesos na mesma família. Esta flexibilidade é suficiente para permitir que todas as variantes de Arial sejam incluídas na mesma família WWS. A tabela a seguir compara as propriedades de fonte RBIZ e WWS para uma seleção de fontes Arial:

Nome Completo Nome de família RBIZ lfPeso lfItalic WWS Sobrenome Peso Esticar Estilo
Arial Arial 400 0 Arial 400 5 0
Arial Ousado Arial 700 0 Arial 700 5 0
Arial Preto Arial Preto 900 0 Arial 900 5 0
Arial Estreito Arial Estreito 400 0 Arial 400 3 0
Arial Estreito Ousado Arial Estreito 700 0 Arial 700 3 0

Como você pode ver, "Arial Narrow" tem os mesmos valores lfWeight e lfItalic que "Arial", por isso tem um nome de família RBIZ diferente para evitar ambiguidade. "Arial Black" tem um nome de família RBIZ diferente para evitar ter mais de dois pesos na família "Arial". Por outro lado, todas essas fontes estão na mesma família de estilo peso-estiramento.

No entanto, o modelo estilo peso-estiramento não é aberto. Se duas fontes tiverem o mesmo peso, alongamento e estilo, mas diferirem de alguma outra forma (por exemplo, tamanho ótico), elas não poderão ser incluídas na mesma família de fontes WWS. Isso nos leva ao modelo de família de fontes tipográficas.

Modelo de família de fontes tipográficas

Ao contrário de seus antecessores, o modelo de família de fontes tipográficas é aberto. Ele suporta qualquer número de eixos de variação dentro de uma família de fontes.

Se você pensar em parâmetros de seleção de fonte como coordenadas em um espaço de design, o modelo de estilo peso-estiramento define um sistema de coordenadas tridimensional com peso, estiramento e estilo como eixos. Cada fonte em uma família WWS deve ter um local único definido por suas coordenadas ao longo desses três eixos. Para selecionar uma fonte, especifique um nome de família WWS e parâmetros de peso, alongamento e estilo.

Por outro lado, o modelo de família de fontes tipográficas tem um espaço de design N-dimensional. Um designer de fontes pode definir qualquer número de eixos de design, cada um identificado por uma marca de eixo de quatro caracteres. A localização de uma determinada fonte no espaço de design N-dimensional é definida por uma matriz de valores de eixo , onde cada valor de eixo compreende uma marca de eixo e um valor de ponto flutuante. Para selecionar uma fonte, especifique um nome de família tipográfico e uma matriz de valores de eixo (estruturasDWRITE_FONT_AXIS_VALUE).

Embora o número de eixos de fonte seja aberto, existem alguns eixos registrados com significados padrão, e os valores de peso, estiramento e estilo podem ser mapeados para valores de eixo registrados. DWRITE_FONT_WEIGHT pode ser mapeado para um valor de eixo "wght" (DWRITE_FONT_AXIS_TAG_WEIGHT). DWRITE_FONT_STRETCH pode ser mapeado para um valor de eixo "wdth" (DWRITE_FONT_AXIS_TAG_WIDTH). DWRITE_FONT_STYLE pode ser mapeada para uma combinação de valores dos eixos "ital" e "slnt" (DWRITE_FONT_AXIS_TAG_ITALIC e DWRITE_FONT_AXIS_TAG_SLANT).

Outro eixo registado é "opsz" (DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE). Uma família de fontes óticas como Sitka inclui fontes que diferem ao longo do eixo "opsz", o que significa que elas são projetadas para serem usadas em diferentes tamanhos de ponto. O modelo da família de fontes WWS não tem um eixo de tamanho ótico, então a família de fontes Sitka deve ser dividida em várias famílias de fontes WWS: "Sitka Small", "Sitka Text", "Sitka Subheading" e assim por diante. Cada família de fontes WWS corresponde a um tamanho ótico diferente, e cabe ao usuário especificar o nome de família WWS correto para um determinado tamanho de fonte. Com o modelo de família de fontes tipográficas, o usuário pode simplesmente escolher "Sitka", e o aplicativo pode definir automaticamente o valor do eixo "opsz" com base no tamanho da fonte.

Seleção de fontes tipográficas e fontes variáveis

O conceito de eixos de variação é frequentemente associado a fontes variáveis, mas também se aplica a fontes estáticas. A tabela de OpenType STAT (atributos de estilo) declara quais eixos de design uma fonte tem e os valores desses eixos. Esta tabela é necessária para fontes variáveis, mas também é relevante para fontes estáticas.

A API DirectWrite expõe os valores dos eixos "wght", "wdth", "ital" e "slnt" para cada fonte, mesmo que eles não estejam presentes na tabela STAT ou se não houver uma tabela STAT. Estes valores são derivados da tabela STAT, se possível. Caso contrário, eles são derivados da espessura da fonte, extensão da fonte e estilo da fonte.

Os eixos dos tipos de letra podem ser variáveis ou não variáveis. Uma fonte estática tem apenas eixos não variáveis, enquanto uma fonte variável pode ter ambos. Para usar uma fonte variável, você deve criar uma fonte variável instância na qual todos os eixos variáveis foram vinculados a valores específicos. A interface IDWriteFontFace representa uma fonte estática ou uma instância específica de uma fonte variável. É possível criar uma instância arbitrária de uma fonte variável com valores de eixo especificados. Além disso, uma fonte variável pode declarar instâncias nomeadas na tabela de STAT do com combinações predefinidas de valores de eixo. As instâncias nomeadas permitem que uma fonte variável se comporte como uma coleção de fontes estáticas. Quando você enumera elementos de um IDWriteFontFamily ou IDWriteFontSet, há um elemento para cada fonte estática e para cada ocorrência de fonte variável nomeada.

O algoritmo de correspondência tipográfica de fontes primeiro seleciona potenciais candidatos de correspondência com base no nome da família. Se os candidatos de correspondência incluírem fontes variáveis, todos os candidatos de correspondência para a mesma fonte variável serão recolhidos em um candidato de correspondência no qual cada eixo de variável recebe um valor específico o mais próximo possível do valor solicitado para esse eixo. Se não houver nenhum valor solicitado para um eixo variável, será atribuído o valor padrão para esse eixo. A ordem dos candidatos de correspondência é então determinada comparando os valores dos seus eixos com os valores dos eixos solicitados.

Por exemplo, considere a família tipográfica Sitka no Windows. Sitka é uma família de fontes óticas, o que significa que tem um eixo "opsz". No Windows 11, Sitka é implementado como duas fontes variáveis com os seguintes valores de eixo. Note-se que os eixos opsz e wght são variáveis, enquanto os outros eixos não são variáveis.

Nome do arquivo "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

Suponha que os valores de eixo solicitados sejam opsz:12 wght:475 wdth:100 ital:0 slnt:0. Para cada fonte variável, criamos uma referência a uma fonte variável instância na qual cada eixo variável recebe um valor específico. Ou seja, os eixos opsz e wght são definidos para 12 e 475, respectivamente. Isso produz as seguintes fontes correspondentes, com a fonte não itálica classificada em primeiro lugar porque é uma melhor correspondência para os eixos ital e 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

No exemplo acima, as fontes correspondentes são instâncias de fonte variáveis arbitrárias. Não há nenhum caso nomeado de Sitka com peso 475. Por outro lado, o algoritmo de correspondência peso-estiramento retorna apenas instâncias nomeadas.

Ordem de correspondência de fontes

Existem diferentes métodos de de sobrecarregados GetMatchingFonts para o modelo de família de fontes weight-stretch-style (IDWriteFontFamily::GetMatchingFonts) e o modelo de família de fontes tipográficas (IDWriteFontCollection2::GetMatchingFonts). Em ambos os casos, a saída é uma lista de fontes correspondentes em ordem decrescente de prioridade com base em quão bem cada fonte candidata corresponde às propriedades de entrada. Esta seção descreve como a prioridade é determinada.

No modelo weight-stretch-style, os parâmetros de entrada são o peso da fonte (DWRITE_FONT_WEIGHT), o alongamento da fonte (DWRITE_FONT_STRETCH) e o estilo da fonte (DWRITE_FONT_STYLE). O algoritmo para encontrar a melhor correspondência foi documentado em um white paper de 2006 intitulado "WPF Font Selection Model" por Mikhail Leonov e David Brown. Consulte a secção "Correspondência de um rosto da lista de rostos de candidatos". Este artigo era sobre o Windows Presentation Foundation (WPF), mas o DirectWrite mais tarde usou a mesma abordagem.

O algoritmo usa a noção de vetor de atributo de fonte, que para uma determinada combinação de peso, alongamento e estilo é calculado da seguinte forma:

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

Observe que cada coordenada vetorial é normalizada subtraindo o valor "normal" para o atributo correspondente e multiplicando por uma constante. Os multiplicadores compensam o fato de que os intervalos de valores de entrada para peso, estiramento e estilo são muito diferentes. Caso contrário, o peso (100..999) dominaria sobre o estilo (0..2).

Para cada candidato de correspondência, uma distância vetorial e um produto de ponto são calculados entre o vetor de atributo de fonte do candidato de correspondência e o vetor de atributo de fonte de entrada. Ao comparar dois candidatos de correspondência, o candidato com a menor distância vetorial é a melhor correspondência. Se as distâncias forem as mesmas, o candidato com o produto de ponto menor é uma melhor combinação. Se o produto ponto também for o mesmo, as distâncias ao longo dos eixos X, Y e Z são comparadas nessa ordem.

Comparar distâncias é intuitivo o suficiente, mas usar o produto ponto como uma medida secundária pode exigir alguma explicação. Suponha que o peso de entrada seja seminegrito (600) e dois pesos candidatos sejam preto (900) e semileve (300). A distância de cada peso candidato do peso de entrada é a mesma, mas o peso preto está na mesma direção da origem (ou seja, 400 ou normal), por isso terá um produto de ponto menor.

O algoritmo de correspondência tipográfica é uma generalização do algoritmo para peso-estiramento. Cada valor de eixo é tratado como uma coordenada em um vetor de atributo de fonte N-dimensional. Para cada candidato de correspondência, uma distância vetorial e um produto de ponto são calculados entre o vetor de atributo de fonte do candidato de correspondência e o vetor de atributo de fonte de entrada. O candidato com a menor distância vetorial é o melhor match. Se as distâncias forem as mesmas, o candidato com o produto de ponto menor é uma melhor combinação. Se o produto ponto também for o mesmo, a presença em uma família específica de estilo peso-estiramento pode ser usada como critério de desempate.

Para calcular a distância vetorial e o produto de pontos, o vetor de atributo de fonte de um candidato de correspondência e o vetor de atributo de fonte de entrada devem ter os mesmos eixos. Portanto, qualquer valor de eixo ausente em qualquer vetor é preenchido substituindo o valor padrão para esse eixo. As coordenadas vetoriais são normalizadas subtraindo o valor padrão (ou "normal") para o eixo correspondente e multiplicando o resultado por um multiplicador específico do eixo. Seguem-se os multiplicadores e os valores-padrão para cada eixo:

Eixo Multiplicador Valor padrão
"Wght" 5 400
"WDTH" 55 100
"Ital" 1400 0
"SLNT" 35 0
"OPSZ" 1 12
Outros 1 0

Os multiplicadores são consistentes com os usados pelo algoritmo de peso-estiramento, mas dimensionados conforme necessário. Por exemplo, a largura normal é 100, o que equivale ao alongamento 5. Isso produz um multiplicador de 55 vs. 1100. O atributo de estilo legado (0..2) entrelaça itálico e obliquidade, que no modelo tipográfico são decompostos em um eixo "ital" (0..1) e um eixo "slnt" (-90..90). Os multiplicadores escolhidos para esses dois eixos dão resultados equivalentes ao algoritmo legado se assumirmos uma inclinação padrão de 20 graus para fontes oblíquas.

Seleção tipográfica de fontes e tamanho ótico

Um aplicativo usando o modelo de família de fontes tipográficas pode implementar o dimensionamento ótico especificando um valor de eixo opsz como um parâmetro de seleção de fonte. Por exemplo, um aplicativo de processamento de texto pode especificar um valor de eixo opsz igual ao tamanho da fonte em pontos. Nesse caso, um usuário poderia selecionar "Sitka" como a família de fontes, e o aplicativo selecionaria automaticamente uma instância de Sitka com o valor correto do eixo opsz. No modelo WWS, cada tamanho ótico é exposto como um nome de família diferente e cabe ao usuário selecionar o nome certo.

Em teoria, poder-se-ia implementar o dimensionamento ótico automático sob o modelo de estilo peso-estiramento substituindo o valor do eixo opsz como uma etapa separada após seleção de fontes. No entanto, isso funciona somente se a primeira fonte correspondente for uma fonte variável com um eixo de opsz variável. Especificar opsz como um parâmetro de seleção de fontes funciona igualmente bem para fontes estáticas. Por exemplo, a família de fontes Sitka é implementada como fontes variáveis no Windows 11, mas como uma coleção de fontes estáticas no Windows 10. As fontes estáticas têm intervalos de eixos opsz diferentes e não sobrepostos (estes são declarados como intervalos para fins de seleção de fontes, mas não são eixos variáveis). Especificar opsz como um parâmetro de seleção de fonte permite que a fonte estática correta para o tamanho ótico seja selecionada.

Vantagens da seleção de fontes tipográficas e problemas de compatibilidade

O modelo de seleção de fontes tipográficas tem várias vantagens em relação aos modelos anteriores, mas em sua forma pura tem alguns problemas potenciais de compatibilidade. Esta seção descreve as vantagens e os problemas de compatibilidade. A próxima seção descreve um modelo de seleção de fonte híbrido que preserva as vantagens enquanto atenua os problemas de compatibilidade.

As vantagens do modelo de família de fontes tipográficas são:

  • As fontes podem ser agrupadas em famílias, conforme pretendido pelo designer, em vez de serem divididas em subfamílias devido a limitações do modelo de família de fontes.

  • Um aplicativo pode selecionar automaticamente o valor correto do eixo opsz com base no tamanho da fonte, em vez de expor diferentes tamanhos óticos para o usuário como diferentes famílias de fontes.

  • Instâncias arbitrárias de fontes variáveis podem ser selecionadas. Por exemplo, se uma fonte variável suportar pesos no intervalo contínuo 100-900, o modelo tipográfico pode selecionar qualquer peso nesse intervalo. Os modelos de família de fontes mais antigos podem escolher apenas o peso mais próximo entre as instâncias nomeadas definidas pela fonte.

Os problemas de compatibilidade com o modelo de seleção de fontes tipográficas são:

  • Algumas fontes mais antigas não podem ser selecionadas inequivocamente usando apenas o nome da família tipográfica e os valores do eixo.

  • Os documentos existentes podem referir-se a fontes por nome de família WWS ou nome de família RBIZ. Os usuários também podem esperar usar nomes de família WWS e RBIZ. Por exemplo, um documento pode especificar "Sitka Subheading" (um nome de família WWS) em vez de "Sitka" (um nome de família tipográfico).

  • Uma biblioteca ou estrutura pode adotar o modelo de família de fontes tipográficas para tirar proveito do dimensionamento ótico automático, mas não fornecer uma API para especificar valores de eixo arbitrários. Mesmo que uma nova API seja fornecida, a estrutura pode precisar trabalhar com aplicativos existentes que especificam apenas parâmetros de peso, alongamento e estilo.

O problema de compatibilidade com fontes mais antigas surge porque o conceito de nome de família tipográfico é anterior ao conceito de valores de eixo de fonte, que foram introduzidos junto com fontes variáveis no OpenType 1.8. Antes do OpenType 1.8, o nome da família tipográfica apenas expressava a intenção do designer de que um conjunto de fontes estava relacionado, mas sem nenhuma garantia de que essas fontes pudessem ser programaticamente diferenciadas com base em suas propriedades. Como exemplo hipotético, suponha que todas as seguintes fontes tenham o nome de família tipográfico "Legado":

Nome Completo Família WWS Peso Esticar Estilo Família Typo wght WDTH ital SLNT
Legado Legado 400 5 0 Legado 400 100 0 0
Legado ousado Legado 700 5 0 Legado 700 100 0 0
Preto Legado Legado 900 5 0 Legado 900 100 0 0
Legado Soft Legado Soft 400 5 0 Legado 400 100 0 0
Legado Soft Bold Legado Soft 700 5 0 Legado 700 100 0 0
Legado Preto Suave Legado Soft 900 5 0 Legado 900 100 0 0

A família tipográfica "Legacy" tem três pesos, e cada peso tem variantes regulares e "Soft". Se fossem novas fontes, poderiam ser implementadas como declarando um eixo de design SOFT. No entanto, essas fontes são anteriores ao OpenType 1.8, portanto, seus únicos eixos de design são aqueles derivados de peso, estiramento e estilo. Para cada peso, esta família de fontes tem duas fontes com valores de eixo idênticos, portanto, não é possível selecionar inequivocamente uma fonte nessa família usando apenas valores de eixo.

Algoritmo de seleção de fontes híbrido

As APIs de seleção de fontes descritas na próxima seção usam um algoritmo híbrido de seleção de fontes que preserva as vantagens da seleção de fontes tipográficas enquanto reduz seus problemas de compatibilidade.

A seleção de fonte híbrida fornece uma ponte de modelos de família de fontes mais antigos, permitindo que os valores de espessura de fonte, extensão de fonte e estilo de fonte sejam mapeados para valores de eixo de fonte correspondentes. Isso ajuda a resolver problemas de compatibilidade de documentos e aplicativos.

Além disso, o algoritmo de seleção de fontes híbridas permite que o nome de família especificado seja um nome de família tipográfico, um nome de família estilo weight-stretch, um nome de família GDI/RBIZ ou um nome de fonte completo. A correspondência ocorre de uma das seguintes maneiras, em ordem decrescente de prioridade:

  1. O nome corresponde a uma família tipográfica (por exemplo, Sitka). A correspondência ocorre dentro da família tipográfica e todos os valores de eixo solicitados são usados. Se o nome também corresponder a uma subfamília WWS (ou seja, uma menor que a família tipográfica), então a associação à subfamília WWS é usada como critério de desempate.

  2. O nome corresponde a uma família WWS (por exemplo, Sitka Text). A correspondência ocorre dentro da família WWS, e valores de eixo solicitados diferentes de "wght", "wdth", "ital" e "slnt" são ignorados.

  3. O nome corresponde a uma família GDI (por exemplo, Bahnschrift Condensed). A correspondência ocorre dentro da família RBIZ e valores de eixo solicitados diferentes de "wght", "ital" e "slnt" são ignorados.

  4. O nome corresponde a um nome completo (por exemplo, Bahnschrift Bold Condensed). A fonte correspondente ao nome completo é retornada. Os valores de eixo solicitados são ignorados. A correspondência pelo nome completo da fonte é permitida porque o GDI a suporta.

A seção anterior descrevia uma família tipográfica ambígua chamada "Legado". O algoritmo híbrido permite que a ambiguidade seja evitada, especificando "Legacy" ou "Legacy Soft" como o nome da família. Se "Legacy Soft" for especificado, então não há ambiguidade porque a correspondência ocorre apenas dentro da família WWS. Se "Legacy" for especificado, então todas as fontes da família tipográfica são consideradas como candidatas de correspondência, mas a ambiguidade é evitada usando a associação à família WWS "Legacy" como critério de desempate.

Suponha que um documento especifique um nome de família e parâmetros de peso, alongamento e estilo, mas nenhum valor de eixo. O aplicativo pode primeiro converter o peso, o alongamento, o estilo e o tamanho da fonte em valores de eixo chamando IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues. O aplicativo pode então passar o nome da família e os valores do eixo para IDWriteFontSet4::GetMatchingFonts. GetMatchingFonts retorna uma lista de fontes correspondentes em ordem de prioridade, e o resultado é apropriado se o nome de família especificado for um nome de família tipográfico, nome de família estilo weight-stretch, nome de família RBIZ ou nome completo. Se a família especificada tiver um eixo "opsz", o tamanho ótico apropriado será selecionado automaticamente com base no tamanho da fonte.

Suponha que um documento especifica peso, alongamento e estilo, e também especifica valores de eixo. Nesse caso, os valores de eixo explícito também podem ser passados para IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues, e os valores de eixo derivados retornados pelo método incluirão apenas eixos de fonte que não foram especificados explicitamente. Assim, os valores de eixo especificados explicitamente pelo documento (ou aplicativo) têm precedência sobre os valores de eixo derivados de peso, alongamento, estilo e tamanho da fonte.

APIs de seleção de fontes híbridas

O modelo de seleção de fonte híbrida é implementado pelos seguintes métodosIDWriteFontSet4:

  • O métodoIDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValuesconverte os parâmetros de tamanho, peso, alongamento e estilo da fonte para os valores de eixo correspondentes. Todos os valores de eixo explícitos passados pelo cliente são excluídos dos valores de eixo derivados.

  • O método IDWriteFontSet4::GetMatchingFonts retorna uma lista priorizada de fontes correspondentes com um nome de família e uma matriz de valores de eixo. Como descrito acima, o parâmetro de nome de família pode ser um nome de família tipográfico, nome de família WWS, nome de família RBIZ ou nome completo. (O nome completo identifica um estilo de fonte específico, como "Arial Bold Italic". GetMatchingFonts suporta correspondência por nome completo para maior compatibilidade com GDI, o que também permite.)

As seguintes outras APIs do DirectWrite também usam o algoritmo de seleção de fonte híbrida:

Exemplos de código de APIs de seleção de fontes em uso

Esta seção mostra um aplicativo de console completo que demonstra os IDWriteFontSet4::GetMatchingFonts e IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues métodos. Primeiro, vamos incluir alguns cabeçalhos:

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

O métodoIDWriteFontSet4::GetMatchingFontsretorna uma lista de fontes em ordem de prioridade que correspondem ao nome da família especificado e aos valores do eixo. A função MatchAxisValues a seguir saída os parâmetros para IDWriteFontSet4::GetMatchingFonts e a lista de fontes correspondentes no conjunto de fontes retornado.

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

Um aplicativo pode ter parâmetros de peso, alongamento e estilo em vez de (ou além de) valores de eixo. Por exemplo, o aplicativo pode precisar trabalhar com documentos que fazem referência a fontes usando RBIZ ou parâmetros de estilo weight-stretch. Mesmo que o aplicativo adicione suporte para especificar valores de eixo arbitrários, talvez seja necessário oferecer suporte aos parâmetros mais antigos também. Para fazer isso, o aplicativo pode chamar IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues antes de chamar IDWriteFontSet4::GetMatchingFonts.

A função MatchFont a seguir usa parâmetros de peso, alongamento, estilo e tamanho da fonte, além dos valores de eixo. Ele encaminha esses parâmetros para o método IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues para calcular valores de eixo derivados, que são acrescentados aos valores do eixo de entrada. Ele passa os valores de eixo combinados para a função MatchAxisValues acima.

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

A função a seguir demonstra os resultados de chamar a função de MatchFont acima com algumas entradas de exemplo:

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 seguir está a saída da função TestFontSelection acima:

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 seguir estão as implementações dos operadores sobrecarregados declaradas acima. Eles são usados por MatchAxisValues para gravar os valores do eixo de entrada e as referências de face de fonte 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 completar o exemplo, a seguir estão as funções de análise de linha de comando e a função 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());
    }
}