Compartilhar via


Seleção de fonte

A interface IDWriteFontSet4 expõ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 fonte (às vezes chamada de correspondência de fontes ou mapeamento de fonte) é o processo de selecionar as fontes disponíveis que melhor correspondem aos parâmetros de entrada passados pelo seu aplicativo. Às vezes, os parâmetros de entrada são chamados coletivamente de fonte lógica. Uma fonte lógica inclui um nome de família de fontes, além de outros atributos que indicam uma fonte específica dentro da família. Um algoritmo de seleção de fonte 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 ser diferentes em atributos como peso. Um modelo de família de fontes define quais atributos podem ser usados para diferenciar fontes em uma família. O novo modelo de família de fontes tipográficas tem muitas vantagens em relação aos dois modelos anteriores da família de fontes usados no Windows. Mas alterar modelos de família de fontes cria oportunidades de confusão e problemas de compatibilidade. Os métodos expostos pela interface IDWriteFontSet4 implementam uma abordagem híbrida que oferece as vantagens do modelo de família de fontes tipográficas ao atenuar problemas de compatibilidade.

Este tópico compara os modelos mais antigos da família de fontes 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, por fim, explica como esses desafios podem ser superados usando os métodos [IDWriteFontSet4](/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontset4).

Modelo de família de fontes RBIZ

O modelo de família de fontes de fato usado no ecossistema de aplicativos GDI às vezes é chamado de modelo "modelo de quatro fontes" ou "RBIZ". Cada família de fontes nesse 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 de fonte
verdana.ttf Regular
verdanab.ttf Negrito
verdanai.ttf Itálico
verdanaz.ttf Negrito Itálico

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

As interfaces populares do usuário do aplicativo desde o início incluíam um botão itálico (para ativar e desativar o itálico) e um botão em negrito (para alternar entre pesos normais e em negrito). O uso desses dois botões para selecionar fontes em uma família pressupõe o modelo RBIZ. Portanto, embora a própria GDI dê suporte a mais de dois pesos, a compatibilidade do aplicativo levou os desenvolvedores de fontes a definir o nome da família GDI (ID de nome OpenType 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, portanto, você pode esperar selecioná-la definindo lfFaceName como "Arial" e lfWeightpara FW_BLACK. No entanto, não há como um usuário de aplicativo escolher entre três pesos usando um botão negrito de dois estados. A solução era 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 larguras diferentes na mesma família de fontes usando apenas botões em negrito e itálico, de modo que as versões estreitas do Arial tenham nomes de família diferentes no modelo RBIZ. Assim, temos familes de fonte "Arial", "Arial Black" e "Arial Narrow" no modelo RBIZ, mesmo que tipograficamente todos eles pertençam a uma família.

Nesses exemplos, é possível 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 por nome, isso significa que a mesma fonte pode ter nomes de família diferentes, dependendo de qual modelo de família de fontes você está usando.

DirectWrite não dá suporte diretamente ao modelo de 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 da família RBIZ de uma fonte chamando seu método IDWriteFont::GetInformationalStrings e especificando DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES.

Modelo de família de fontes de estilo alongado de peso

O modelo de família de fontes estilo alongamento de peso é o modelo de família de fontes original usado por DirectWrite antes do modelo de família de fontes tipográficas ser introduzido. Também é conhecido como WWS (inclinação de largura de peso). No modelo WWS, as fontes dentro da mesma família podem ser diferentes por três propriedades: peso (DWRITE_FONT_WEIGHT), alongamento (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 na mesma família podem ser diferenciadas por alongamento (ou largura), bem como peso e estilo (regular, itálico ou oblíquo). Segundo, pode haver mais de dois pesos na mesma família. Essa flexibilidade é suficiente para permitir que todas as variantes do 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 da família 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 você pode ver, "Arial Narrow" tem os mesmos lfWeight valores e lfItalic como "Arial", portanto, ele 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 de alongamento de peso.

No entanto, o modelo de estilo de alongamento de peso não é aberto. Se duas fontes tiverem o mesmo peso, alongamento e estilo, mas diferem de alguma outra maneira (por exemplo, tamanho óptico), 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 dá suporte a qualquer número de eixos de variação em 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 de alongamento de peso definirá um sistema de coordenadas tridimensional com peso, alongamento e estilo como eixos. Cada fonte em uma família WWS deve ter um local exclusivo 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 fonte 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, em que 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 (DWRITE_FONT_AXIS_VALUE estruturas).

Embora o número de eixos de fonte seja aberto, há alguns eixos registrados com significados padrão e os valores de peso, alongamento 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 mapeado para uma combinação de valores de eixo "ital" e "slnt" (DWRITE_FONT_AXIS_TAG_ITALIC e DWRITE_FONT_AXIS_TAG_SLANT).

Outro eixo registrado é "opsz" (DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE). Uma família de fontes ópticas, como Sitka, inclui fontes que diferem ao longo do eixo "opsz", o que significa que elas foram projetadas para serem usadas em diferentes tamanhos de ponto. O modelo da família de fontes WWS não tem um eixo de tamanho óptico, portanto, 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 óptico 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 fonte tipográfica e fontes variáveis

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

A API DirectWrite expõe os valores do eixo "wght", "wdth", "ital" e "slnt" para cada fonte, mesmo que não estejam presentes na tabela STAT ou se não houver nenhuma tabela STAT. Esses valores são derivados da tabela STAT, se possível. Caso contrário, eles são derivados do peso da fonte, do alongamento da fonte e do estilo da fonte.

Os eixos de fonte 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 instância de fonte variável na qual todos os eixos de variáveis foram associados 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 STAT com combinações predefinidas de valores de eixo. As instâncias nomeadas permitem que uma fonte variável se comporte muito 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 instância de fonte variável nomeada.

O algoritmo de correspondência de fonte tipográfica primeiro seleciona possíveis candidatos de correspondência com base no nome da família. Se os candidatos à correspondência incluirem 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 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, ele será atribuído ao valor padrão para esse eixo. A ordem dos candidatos de correspondência é determinada comparando seus valores de eixo com os valores de eixo solicitados.

Por exemplo, considere a família tipográfica Sitka no Windows. Sitka é uma família de fontes ópticas, o que significa que tem um eixo "opsz". Em Windows 11, Sitka é implementado como duas fontes variáveis com os seguintes valores de eixo. Observe que os opsz eixos 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 de variável, criamos uma referência a uma instância de fonte variável na qual cada eixo de variável recebe um valor específico. Ou seja, os opsz eixos e wght são definidos como 12 e 475, respectivamente. Isso gera as seguintes fontes correspondentes, com a fonte não itálica classificada em primeiro lugar porque é uma correspondência melhor para os ital eixos 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ável arbitrárias. Não há nenhuma instância nomeada de Sitka com peso 475. Por outro lado, o algoritmo de correspondência de estilo de alongamento de peso retorna apenas instâncias nomeadas.

Ordem de correspondência de fonte

Há diferentes métodos GetMatchingFonts sobrecarregados para o modelo de família de fontes de estilo alongamento de peso (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 no quão bem cada fonte candidata corresponde às propriedades de entrada. Esta seção descreve como a prioridade é determinada.

No modelo de estilo de alongamento de peso, os parâmetros de entrada são peso da fonte (DWRITE_FONT_WEIGHT), alongamento de fonte (DWRITE_FONT_STRETCH) e estilo de 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" de Mikhail Leonov e David Brown. Confira a seção "Correspondência de um rosto da lista de rostos do candidato". Este artigo era sobre Windows Presentation Foundation (WPF), mas 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 é computada da seguinte maneira:

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

Observe que cada coordenada de vetor é 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, alongamento e estilo são muito diferentes. Caso contrário, o peso (100,999) dominaria o estilo (0,2).

Para cada candidato à correspondência, uma distância de vetor e um produto de ponto são calculados entre o vetor de atributo de fonte do candidato correspondente e o vetor de atributo de fonte de entrada. Ao comparar dois candidatos, o candidato com a distância de vetor menor é a melhor correspondência. Se as distâncias forem as mesmas, o candidato com o produto de ponto menor será uma correspondência melhor. Se o produto de ponto também for o mesmo, as distâncias ao longo dos eixos X, Y e Z serã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 semibold (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), portanto, terá um produto de ponto menor.

O algoritmo de correspondência tipográfica é uma generalização daquela para estilo de alongamento de peso. Cada valor de eixo é tratado como uma coordenada em um vetor de atributo de fonte N dimensional. Para cada candidato à correspondência, uma distância de vetor 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 distância de vetor menor é a melhor correspondência. Se as distâncias forem as mesmas, o candidato com o produto de ponto menor será uma correspondência melhor. Se o produto de ponto também for o mesmo, a presença em uma família de estilo de alongamento de peso especificada poderá ser usada como desempate.

Para calcular a distância do vetor e o produto de ponto, um vetor de atributo de fonte do 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 por esse eixo. As coordenadas de vetor 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. A seguir estão os multiplicadores e os valores padrão para cada eixo:

Axis Multiplicador Valor Padrão
"wght" 5 400
"wdth" 55 100
"ital" 1.400 0
"slnt" 35 0
"opsz" 1 12
other 1 0

Os multiplicadores são consistentes com aqueles usados pelo algoritmo de estilo de alongamento de peso, mas dimensionados conforme necessário. Por exemplo, a largura normal é 100, o que é equivalente ao trecho 5. Isso gera um multiplicador de 55 versus 1100. O atributo de estilo herdado (0..2) emaranha itálico e oblíquo, que no modelo tipográfico é decomposto em um eixo "ital" (0..1) e um eixo "slnt" (-90..90). Os multiplicadores escolhidos para esses dois eixos fornecem resultados equivalentes ao algoritmo herdado se assumirmos uma inclinação padrão de 20 graus para fontes oblíquas.

Seleção de fonte tipográfica e tamanho óptico

Um aplicativo que usa o modelo de família de fontes tipográficas pode implementar o dimensionamento óptico especificando um opsz valor de eixo como um parâmetro de seleção de fonte. Por exemplo, um aplicativo de processamento de palavras pode especificar um opsz valor de eixo 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 de eixo correto opsz . No modelo WWS, cada tamanho óptico é exposto como um nome de família diferente e cabe ao usuário selecionar o tamanho certo.

Em teoria, pode-se implementar o dimensionamento óptico automático no modelo de estilo de alongamento de peso substituindo o valor do opsz eixo como uma etapa separada após a seleção da fonte. No entanto, isso só funcionará se a primeira fonte correspondente for uma fonte variável com um eixo variável opsz . Especificar opsz como um parâmetro de seleção de fonte funciona igualmente bem para fontes estáticas. Por exemplo, a família de fontes Sitka é implementada como fontes variáveis em Windows 11, mas como uma coleção de fontes estáticas em Windows 10. As fontes estáticas têm intervalos de eixos diferentes e não sobrepostos opsz (eles são declarados como intervalos para fins de seleção de fonte, 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 óptico seja selecionado.

Vantagens de seleção de fonte tipográfica e problemas de compatibilidade

O modelo de seleção de fonte tipográfica tem várias vantagens em relação aos modelos anteriores, mas em sua forma pura ele tem alguns possíveis problemas 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íbrida que preserva as vantagens ao atenuar 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ímilias devido a limitações do modelo de família de fontes.

  • Um aplicativo pode selecionar automaticamente o valor do eixo correto opsz com base no tamanho da fonte, em vez de expor diferentes tamanhos ópticos ao 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 der suporte a pesos no intervalo contínuo de 100 a 900, o modelo tipográfico poderá selecionar qualquer peso nesse intervalo. Os modelos mais antigos da família de fontes 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 fonte tipográfica são:

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

  • Os documentos existentes podem se referir a fontes pelo nome da família WWS ou pelo nome da 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 aproveitar o dimensionamento óptico automático, mas não fornecer uma API para especificar valores arbitrários de eixo. 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áfica antecede o 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 expressou a intenção do designer de que um conjunto de fontes estava relacionado, mas sem nenhuma garantia de que essas fontes poderiam ser diferenciadas programaticamente com base em suas propriedades. Como exemplo hipotético, suponha que todas as seguintes fontes tenham o nome da família tipográfica "Legacy":

Nome completo Família WWS Peso Stretch Estilo Família typo wght wdth Ital slnt
Herdada Herdada 400 5 0 Herdada 400 100 0 0
Negrito Herdado Herdada 700 5 0 Herdada 700 100 0 0
Preto Herdado Herdada 900 5 0 Herdada 900 100 0 0
Soft herdado Soft herdado 400 5 0 Herdada 400 100 0 0
Negrito Suave Herdado Soft herdado 700 5 0 Herdada 700 100 0 0
Preto Macio Herdado Soft herdado 900 5 0 Herdada 900 100 0 0

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

Algoritmo de seleção de fonte híbrida

As APIs de seleção de fonte descritas na próxima seção usam um algoritmo de seleção de fonte híbrida que preserva as vantagens da seleção de fonte tipográfica ao atenuar seus problemas de compatibilidade.

A seleção de fonte híbrida fornece uma ponte de modelos mais antigos da família de fontes, permitindo que os valores de peso da fonte, alongamento 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 fonte híbrida permite que o nome de família especificado seja um nome de família tipográfico, nome de família de estilo de alongamento de peso, nome da 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ímil WWS (ou seja, uma menor que a família tipográfica), a associação à subfamímil WWS será usada como desempate.

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

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

  4. O nome corresponde a um nome completo (por exemplo, Bahnschrift Bold Condensed). A fonte que corresponde ao nome completo é retornada. Os valores de eixo solicitados são ignorados. A correspondência por nome de fonte completa é permitida porque a GDI dá suporte a ela.

A seção anterior descreveu uma família tipográfica ambígua chamada "Legacy". O algoritmo híbrido permite que a ambiguidade seja evitada especificando "Legacy" ou "Legacy Soft" como o nome da família. Se "Herdado Suave" for especificado, não haverá ambiguidade porque a correspondência ocorre apenas dentro da família WWS. Se "Legacy" for especulado, todas as fontes da família tipográfica serão consideradas como candidatas correspondentes, mas a ambiguidade será evitada usando a associação na família "Legacy" da WWS como 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. Em seguida, o aplicativo pode passar os valores de nome de família e eixo para IDWriteFontSet4::GetMatchingFonts. GetMatchingFonts retorna uma lista de fontes correspondentes na ordem de prioridade e o resultado é apropriado se o nome da família especificado é um nome de família tipográfico, nome de família de estilo de peso estendido, nome da família RBIZ ou nome completo. Se a família especificada tiver um eixo "opsz", o tamanho óptico apropriado será selecionado automaticamente com base no tamanho da fonte.

Suponha que um documento especifique peso, alongamento e estilo e também especifique valores de eixo. Nesse caso, os valores explícitos do eixo 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 valores de eixo derivados de peso, alongamento, estilo e tamanho da fonte.

APIs de seleção de fonte híbrida

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

  • O método IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues converte 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, considerando um nome de família e uma matriz de valores de eixo. Conforme descrito acima, o parâmetro de nome de família pode ser um nome de família tipográfico, nome da família WWS, nome da família RBIZ ou nome completo. (O nome completo identifica um estilo de fonte específico, como "Arial Bold Italic". GetMatchingFonts dá suporte à correspondência por nome completo para maior comaptibiltiy com GDI, o que também permite isso.)

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

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

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

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

O método IDWriteFontSet4::GetMatchingFonts retorna uma lista de fontes na ordem de prioridade que correspondem aos valores de eixo e nome de família especificados. A função MatchAxisValues a seguir gera 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 referenciam fontes usando RBIZ ou parâmetros de estilo de alongamento de peso. Mesmo que o aplicativo adicione suporte para especificar valores arbitrários do eixo, talvez seja necessário dar suporte também aos parâmetros mais antigos. 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 de 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 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);
}

Veja a seguir 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 declarados acima. Eles são usados por MatchAxisValues para gravar os valores do eixo de entrada e as referências de rosto 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 arredondar o exemplo, veja a seguir funções de análise de linha de comando e a função main:

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