Usar tipografia avançada com fontes locais

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

Fontes seguras da Web

Se você trabalha com desenvolvimento da Web há bastante 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 (Windows, macOS, as distribuições mais comuns do Linux, Android e iOS). No início dos anos 2000, a Microsoft até liderou uma iniciativa chamada TrueType core fonts for the Web (em inglês), que disponibilizava essas fontes para download sem custo financeiro com o objetivo de que "sempre que você acessar um site que as especifica, as páginas apareçam exatamente como o designer do site pretendia". Sim, isso inclui sites definidos em Comic Sans MS. Veja como uma pilha de fontes seguras da Web clássica (com o fallback final de qualquer fonte sans-serif) pode ser:

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

Fontes da Web

Os dias em que as fontes seguras para a Web eram realmente importantes já se foram. Hoje, temos fontes da Web, algumas delas são 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 a serem baixados:

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

Depois disso, você poderá 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 própria 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 fontes personalizadas sejam carregadas (surpresa!) localmente. Se o usuário tiver a fonte FlamboyantSansSerif instalada no sistema operacional, a cópia local será usada em vez de ser baixada:

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

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

O app Catálogo de Fontes do macOS mostrando uma prévia da fonte Google Sans.
A fonte Google Sans instalada no laptop de um funcionário do Google.

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

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

API Local Font Access

O início deste artigo pode ter deixado você em um estado de espírito negativo. Não podemos ter coisas boas? Não se preocupe. Acreditamos que sim, e talvez nem tudo esteja perdido. Mas primeiro, quero responder a uma pergunta que você pode estar se fazendo.

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

Historicamente, ferramentas de design e gráficos com qualidade profissional são difíceis de oferecer na Web. Um obstáculo tem sido a incapacidade de acessar e usar toda a variedade de fontes criadas e sugeridas profissionalmente que os designers têm instaladas localmente. As fontes da Web permitem alguns casos de uso de publicação, mas não o acesso programático às formas de glifos vetoriais e às tabelas de fontes usadas por rasterizadores para renderizar os contornos dos glifos. Da mesma forma, não há como acessar os dados binários de uma fonte da Web.

  • As ferramentas de design precisam de acesso aos bytes de fontes para fazer a própria implementação de layout OpenType e permitir que as ferramentas de design se conectem em níveis mais baixos, para ações como executar filtros vetoriais ou transformações nas formas de glifos.
  • Os desenvolvedores podem ter pilhas de fontes legadas para os aplicativos que estão trazendo para a Web. Para usar essas pilhas, geralmente é necessário acesso direto aos dados de fontes, algo que as fontes da Web não oferecem.
  • Algumas fontes podem não ter licença para entrega na Web. Por exemplo, a Linotype tem uma licença para algumas fontes que incluem apenas uso em computadores.

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

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

Suporte ao navegador

Browser Support

  • Chrome: 103.
  • Edge: 103.
  • Firefox: not supported.
  • Safari: not supported.

Source

Como usar a API Local Font Access

Detecção de recursos

Para verificar se a API Local Font Access é compatível, 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 você pode iterar. 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 é possível 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. 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. 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 superfície altamente identificável. No entanto, os navegadores podem retornar o que quiserem. Por exemplo, navegadores focados no anonimato podem optar por 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 aparecem no disco.

Sempre que possível, a API Local Font Access é 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. Retornar exatamente a lista de fontes instaladas fornecida por uma API do sistema pode expor dados adicionais que podem ser usados para impressão digital. Além disso, os casos de uso que queremos ativar não são ajudados pela retenção dessa ordenação. Por isso, essa API exige que os dados retornados sejam classificados antes de serem enviados.

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 Controlling Access to Powerful Web Platform Features (em inglês), incluindo controle do usuário, transparência e ergonomia.

Controle do usuário

O acesso às fontes de um usuário está totalmente sob 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

Se um site recebeu acesso às fontes locais do usuário, isso vai aparecer na ficha de informações do site.

Permanência de permissões

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

Feedback

A equipe do Chrome quer saber sobre suas experiências com a API Local Font Access.

Fale 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 implementar sua ideia? Tem uma dúvida ou um comentário sobre o modelo de segurança? Registre um problema de especificação no repositório do GitHub correspondente ou adicione suas ideias a um problema já existente.

Informar um problema com a implementação

Você encontrou um bug na implementação do Chrome? Ou a implementação é diferente da especificação? Registre um bug em new.crbug.com. Inclua o máximo de detalhes possível, instruções simples para reprodução e insira Blink>Storage>FontAccess na caixa Componentes.

Mostrar suporte para a API

Você planeja usar a API Local Font 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 diga onde e como você está usando.

Agradecimentos

A especificação da API Local Font 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.