Usar tipografia avançada com fontes locais

Saiba como a API Local Fonts Access permite acessar as fontes instaladas localmente pelo usuário e receber detalhes de baixo nível sobre elas.

Fontes seguras da Web

Se você já trabalha com desenvolvimento da Web há muito tempo, talvez se lembre das chamadas fontes seguras para a Web. Essas fontes estão disponíveis em quase todas as instâncias dos sistemas operacionais mais usados, como Windows, macOS, as distribuições mais comuns do Linux, Android e iOS. No início dos anos 2000, a Microsoft liderou uma iniciativa chamada TrueType core fonts for the Web, que oferecia o download sem custo financeiro dessas fontes com o objetivo de "sempre que você visitar um site que as especifica, as páginas vão aparecer exatamente como o designer do site pretendia". Sim, isso incluiu sites definidos em Comic Sans MS. Confira como um stack de fontes seguras da Web clássico (com a substituição final de qualquer fonte sans-serif) pode ficar:

body {
  font-family: Helvetica, Arial, sans-serif;
}

Fontes da Web

Os dias em que as fontes seguras para a Web eram importantes já se foram. Hoje, temos fontes da Web, algumas das quais são até mesmo fontes variáveis que podem ser ajustadas ainda mais mudando os valores dos vários eixos expostos. É possível usar fontes da Web declarando um bloco @font-face no início do CSS, que especifica os arquivos de fonte que serão transferidos por download:

@font-face {
  font-family: 'FlamboyantSansSerif';
  src: url('flamboyant.woff2');
}

Depois disso, é possível usar a fonte da Web personalizada especificando o font-family, como de costume:

body {
  font-family: 'FlamboyantSansSerif';
}

Fontes locais como vetor de impressão digital

A maioria das fontes da Web vem da Web. No entanto, um fato interessante é que a propriedade src na declaração @font-face, além da função url(), também aceita uma função local(). Isso permite que as fontes personalizadas sejam carregadas (surpresa!) localmente. Se o usuário tiver o FlamboyantSansSerif instalado no sistema operacional, a cópia local será usada em vez de ser transferida por download:

@font-face {
  font-family: 'FlamboyantSansSerif';
  src: local('FlamboyantSansSerif'), url('flamboyant.woff2');
}

Essa abordagem fornece um bom mecanismo substituto que potencialmente economiza largura de banda. Na Internet, infelizmente, não podemos ter coisas legais. O problema com a função local() é que ela pode ser usada indevidamente para impressão digital do navegador. A lista de fontes instaladas por um usuário pode ser bastante identificável. Muitas empresas têm fontes corporativas instaladas nos laptops dos funcionários. Por exemplo, o Google tem uma fonte corporativa chamada Google Sans.

O app macOS Font Book mostrando uma visualização da fonte Google Sans.
A fonte Google Sans instalada no laptop de um funcionário do Google.

Um invasor pode tentar determinar em qual empresa uma pessoa trabalha testando a existência de um grande número de fontes corporativas conhecidas, como Google Sans. O invasor tentaria renderizar o texto definido nessas fontes em uma tela e medir os glifos. Se os glifos corresponderem à forma conhecida da fonte corporativa, o invasor terá uma correspondência. Se os glifos não corresponderem, o invasor saberá que uma fonte de substituição padrão foi usada, já que a corporativa não foi instalada. Para mais detalhes sobre esse e outros ataques de impressão digital do navegador, leia o documento de pesquisa da Laperdix et al.

mesmo que apenas a lista de fontes instaladas possa identificar as fontes da empresa. A situação com esse vetor de ataque tornou-se tão ruim que, recentemente, a equipe do WebKit decidiu "incluir apenas fontes da Web [na lista de fontes disponíveis] e fontes da Web que vêm com o sistema operacional, mas não fontes instaladas pelo usuário localmente". (E aqui estou, com um artigo sobre como conceder acesso a fontes locais.)

A API Local Fonts Access

O início deste artigo pode ter deixado você de mau humor. Não podemos ter coisas legais? Não se preocupe. Acreditamos que podemos, e talvez não seja o fim do mundo. Mas primeiro, vamos responder a uma pergunta que você pode estar fazendo a si mesmo.

Por que precisamos da API Local Fonts Access quando há fontes da Web?

Ferramentas de design e gráficos de qualidade profissional sempre foram difíceis de oferecer na Web. Um obstáculo tem sido a incapacidade de acessar e usar a variedade completa de fontes criadas e sugeridas profissionalmente que os designers instalaram localmente. As fontes da Web permitem alguns casos de uso de publicação, mas não permitem o acesso programático às formas de glifo vetorial e às tabelas de fontes usadas por rastreadores para renderizar os contornos de glifo. Da mesma forma, não há como acessar os dados binários de uma fonte da Web.

  • As ferramentas de design precisam ter acesso aos bytes da fonte para fazer a própria implementação de layout OpenType e permitir que as ferramentas de design sejam conectadas em níveis mais baixos, para ações como executar filtros de vetor ou transformações nas formas de glifos.
  • Os desenvolvedores podem ter pilhas de fontes legados para os aplicativos que estão trazendo para a Web. Para usar essas pilhas, elas geralmente exigem acesso direto aos dados de fontes, algo que as fontes da Web não fornecem.
  • Algumas fontes podem não ter licença para entrega pela Web. Por exemplo, a Linotype tem uma licença para algumas fontes que inclui apenas o uso em computadores.

A API Local Fonts Access é uma tentativa de resolver esses desafios. Ele consiste em duas partes:

  • Uma API de enumeração de fontes, que permite que os usuários concedam acesso ao conjunto completo de fontes do sistema disponíveis.
  • Em cada resultado de enumeração, a capacidade de solicitar acesso ao contêiner SFNT de baixo nível (orientado a bytes) que inclui todos os dados da fonte.

Suporte ao navegador

Compatibilidade com navegadores

  • Chrome: 103
  • Edge: 103.
  • Firefox: não é compatível.
  • Safari: incompatível.

Origem

Como usar a API Local Fonts Access

Detecção de recursos

Para verificar se a API Local Font Access tem suporte, use:

if ('queryLocalFonts' in window) {
  // The Local Font Access API is supported
}

Enumerar fontes locais

Para conferir uma lista das fontes instaladas localmente, chame window.queryLocalFonts(). Na primeira vez, isso vai acionar uma solicitação de permissão, que o usuário pode aprovar ou negar. Se o usuário aprovar a consulta das fontes locais, o navegador vai retornar uma matriz com dados de fontes que podem ser usados em um loop. Cada fonte é representada como um objeto FontData com as propriedades family (por exemplo, "Comic Sans MS"), fullName (por exemplo, "Comic Sans MS"), postscriptName (por exemplo, "ComicSansMS") e style (por exemplo, "Regular").

// Query for all available fonts and log metadata.
try {
  const availableFonts = await window.queryLocalFonts();
  for (const fontData of availableFonts) {
    console.log(fontData.postscriptName);
    console.log(fontData.fullName);
    console.log(fontData.family);
    console.log(fontData.style);
  }
} catch (err) {
  console.error(err.name, err.message);
}

Se você tiver interesse apenas em um subconjunto de fontes, também poderá filtrá-las com base nos nomes PostScript adicionando um parâmetro postscriptNames.

const availableFonts = await window.queryLocalFonts({
  postscriptNames: ['Verdana', 'Verdana-Bold', 'Verdana-Italic'],
});

Como acessar dados SFNT

O acesso total ao SFNT está disponível pelo método blob() do objeto FontData. O SFNT é um formato de arquivo de fonte que pode conter outras fontes, como PostScript, TrueType, OpenType, Web Open Font Format (WOFF) e outras.

try {
  const availableFonts = await window.queryLocalFonts({
    postscriptNames: ['ComicSansMS'],
  });
  for (const fontData of availableFonts) {
    // `blob()` returns a Blob containing valid and complete
    // SFNT-wrapped font data.
    const sfnt = await fontData.blob();
    // Slice out only the bytes we need: the first 4 bytes are the SFNT
    // version info.
    // Spec: https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font
    const sfntVersion = await sfnt.slice(0, 4).text();

    let outlineFormat = 'UNKNOWN';
    switch (sfntVersion) {
      case '\x00\x01\x00\x00':
      case 'true':
      case 'typ1':
        outlineFormat = 'truetype';
        break;
      case 'OTTO':
        outlineFormat = 'cff';
        break;
    }
    console.log('Outline format:', outlineFormat);
  }
} catch (err) {
  console.error(err.name, err.message);
}

Demonstração

Confira a API Local Font Access em ação na demonstração abaixo. Confira também o código-fonte. A demonstração mostra um elemento personalizado chamado <font-select> que implementa um seletor de fontes local.

Considerações sobre privacidade

A permissão "local-fonts" parece fornecer uma plataforma altamente legível. No entanto, os navegadores são livres para retornar o que quiserem. Por exemplo, navegadores focados no anonimato podem fornecer apenas um conjunto de fontes padrão integradas ao navegador. Da mesma forma, os navegadores não precisam fornecer dados de tabela exatamente como eles aparecem no disco.

Sempre que possível, a API Local Fonts Access foi projetada para expor apenas as informações necessárias para ativar os casos de uso mencionados. As APIs do sistema podem produzir uma lista de fontes instaladas não em uma ordem aleatória ou classificada, mas na ordem de instalação da fonte. O retorno exato da lista de fontes instaladas fornecida por essa API do sistema pode expor outros dados que podem ser usados para impressão digital, e os casos de uso que queremos ativar não são auxiliados pela manutenção dessa ordem. Como resultado, essa API exige que os dados retornados sejam classificados antes de serem retornados.

Segurança e permissões

A equipe do Chrome projetou e implementou a API Local Font Access usando os princípios básicos definidos em Como controlar o acesso a recursos avançados da plataforma Web, incluindo controle do usuário, transparência e ergonomia.

Controle do usuário

O acesso às fontes de um usuário está totalmente sob o controle dele e não será permitido, a menos que a permissão "local-fonts", conforme listado no registro de permissões, seja concedida.

Transparência

Essa informação fica visível na página de informações do site.

Persistência de permissões

A permissão "local-fonts" será mantida entre os recarregamentos de página. Ele pode ser revogado na página de informações do site.

Feedback

A equipe do Chrome quer saber sobre sua experiência com a API Local Fonts Access.

Conte sobre o design da API

Há algo na API que não funciona como esperado? Ou há métodos ou propriedades ausentes que você precisa para implementar sua ideia? Tem alguma dúvida ou comentário sobre o modelo de segurança? Envie um problema de especificação no repositório do GitHub correspondente ou adicione sua opinião a um problema existente.

Informar um problema com a implementação

Você encontrou um bug na implementação do Chrome? Ou a implementação é diferente das especificações? Registre um bug em new.crbug.com. Inclua o máximo de detalhes possível, instruções simples para reprodução e digite Blink>Storage>FontAccess na caixa Components. O Glitch é ótimo para compartilhar reprosagens rápidas e fáceis.

Mostrar suporte à API

Você planeja usar a API Local Fonts Access? Seu apoio público ajuda a equipe do Chrome a priorizar recursos e mostra a outros fornecedores de navegadores a importância de oferecer suporte a eles.

Envie um tweet para @ChromiumDev usando a hashtag #LocalFontAccess e informe onde e como você está usando.

Agradecimentos

A especificação da API Local Fonts Access foi editada por Emil A. Eklund, Alex Russell, Joshua Bell e Olivier Yiptong. Este artigo foi revisado por Joe Medley, Dominik Röttsches e Olivier Yiptong. Imagem principal de Brett Jordan no Unsplash.