Como o app de edição de imagens vetoriais Boxy SVG usa a API Local Font Access para permitir que os usuários escolham as fontes locais favoritas

A API Local Font Access oferece um mecanismo para acessar os dados de fontes instalados localmente do usuário, incluindo detalhes de nível superior, como nomes, estilos e famílias, além dos bytes brutos dos arquivos de fontes. Saiba como o app de edição SVG Boxy SVG usa essa API.

Introdução

Este artigo também está disponível em formato de vídeo.

O Boxy SVG é um editor de gráficos vetoriais. O principal caso de uso é editar desenhos no formato de arquivo SVG para criar ilustrações, logotipos, ícones e outros elementos de design gráfico. Ele foi desenvolvido pelo desenvolvedor polonês Jarosław Foksa e lançado inicialmente em 15 de março de 2013. Jarosław mantém um blog do Boxy SVG em que anuncia novos recursos adicionados ao app. O desenvolvedor é um grande defensor do Projeto Fugu do Chromium e até tem uma tag Fugu no rastreador de ideias do app.

O app Boxy SVG editando o ícone do Project Fugu no SVG.

API Local Fonts Access no SVG Boxy

Uma adição de recurso que Jarosław mencionou no blog foi a API Local Fonts Access. A API Local Fonts Access permite que os usuários acessem as fontes instaladas localmente, incluindo detalhes de nível mais alto, como nomes, estilos e famílias, além dos bytes brutos dos arquivos de fonte. Na captura de tela a seguir, você pode conferir como dei acesso ao app às fontes instaladas localmente no meu MacBook e escolhi a fonte Marker Felt para o texto.

O app Boxy SVG editando o SVG do ícone do Project Fugu, adicionando o texto "Project Fugu rocks" definido na fonte Marker Felt, que aparece selecionado no seletor de fontes.

O código é bastante simples. Quando o usuário abre o seletor da família de fontes pela primeira vez, o aplicativo verifica primeiro se o navegador da Web é compatível com a API Local Font Access.

Ela também verifica a versão experimental antiga da API e a usa, se estiver presente. A partir de 2023, você pode ignorar a API antiga com segurança, já que ela estava disponível apenas por um curto período com flags experimentais do Chrome, mas algumas derivadas do Chromium ainda podem usá-la.

let isLocalFontsApiEnabled = (
  // Local Font Access API, Chrome >= 102
  window.queryLocalFonts !== undefined ||
  // Experimental Local Font Access API, Chrome < 102
  navigator.fonts?.query !== undefined
);

Se a API Local Font Access não estiver disponível, o seletor de famílias de fontes ficará cinza. Um texto de marcador de posição será exibido para o usuário em vez da lista de fontes:

if (isLocalFontsApiEnabled === false) {
  showPlaceholder("no-local-fonts-api");
  return;
}

Seletor de fontes mostrando a mensagem &quot;Seu navegador não oferece suporte à API Local Font Access&quot;.

Caso contrário, a API Local Font Access é usada para recuperar a lista de todas as fontes do sistema operacional. Observe o bloco try…catch, que é necessário para processar erros de permissão corretamente.

let localFonts;

if (isLocalFontsApiEnabled === true) {
  try {
    // Local Font Access API, Chrome >= 102
    if (window.queryLocalFonts) {
      localFonts = await window.queryLocalFonts();
    }
    // Experimental Local Font Access API, Chrome < 102
    else if (navigator.fonts?.query) {
      localFonts = await navigator.fonts.query({
        persistentAccess: true,
      });
    }
  } catch (error) {
    showError(error.message, error.name);
  }
}

Depois que a lista de fontes locais é recuperada, uma fontsIndex simplificada e normalizada é criada a partir dela:

let fontsIndex = [];

for (let localFont of localFonts) {
  let face = "400";

  // Determine the face name
  {
    let subfamily = localFont.style.toLowerCase();
    subfamily = subfamily.replaceAll(" ", "");
    subfamily = subfamily.replaceAll("-", "");
    subfamily = subfamily.replaceAll("_", "");

    if (subfamily.includes("thin")) {
      face = "100";
    } else if (subfamily.includes("extralight")) {
      face = "200";
    } else if (subfamily.includes("light")) {
      face = "300";
    } else if (subfamily.includes("medium")) {
      face = "500";
    } else if (subfamily.includes("semibold")) {
      face = "600";
    } else if (subfamily.includes("extrabold")) {
      face = "800";
    } else if (subfamily.includes("ultrabold")) {
      face = "900";
    } else if (subfamily.includes("bold")) {
      face = "700";
    }

    if (subfamily.includes("italic")) {
      face += "i";
    }
  }

  let descriptor = fontsIndex.find((descriptor) => {
    return descriptor.family === localFont.family);
  });

  if (descriptor) {
    if (descriptor.faces.includes(face) === false) {
      descriptor.faces.push(face);
    }
  } else {
    let descriptor = {
      family: localFont.family,
      faces: [face],
    };

    fontsIndex.push(descriptor);
  }
}

for (let descriptor of fontsIndex) {
  descriptor.faces.sort();
}

O índice de fontes normalizado é armazenado no banco de dados IndexedDB para que possa ser consultado com facilidade, compartilhado entre instâncias de app e preservado entre sessões. O Boxy SVG usa o Dexie.js para gerenciar o banco de dados:

let database = new Dexie("LocalFontsManager");
database.version(1).stores({cache: "family"}).
await database.cache.clear();
await database.cache.bulkPut(fontsIndex);

Seção &quot;Storage&quot; do Chrome DevTools mostrando a tabela IndexedDB com o cache de fontes.

Depois que o banco de dados é preenchido, o widget de seletor de fontes pode consultar e mostrar os resultados na tela:

Seletor de fontes preenchido com fontes.

Vale a pena mencionar que o Boxy SVG renderiza a lista em um elemento personalizado chamado <bx-fontfamilypicker> e estiliza cada item da lista de fontes para que seja exibido na família de fontes específica. Para isolar o resto da página, o Boxy SVG usa o Shadow DOM neste e em outros elementos personalizados.

Painel &quot;Elements&quot; do Chrome DevTools mostrando o seletor de fontes sendo inspecionado: um elemento personalizado chamado &quot;bx-fontfamiliypicker&quot;.

Conclusões

O recurso de fontes locais tem sido muito usado, e os usuários estão aproveitando o acesso às fontes locais para os designs e criações. Quando a forma da API mudou e o recurso foi interrompido por um breve período, os usuários notaram imediatamente. Jarosław mudou rapidamente o código para o padrão defensivo mostrado no snippet acima, que funciona com o Chrome atualizado e outros derivados do Chromium que talvez não tenham mudado para a versão mais recente. Teste o Boxy SVG e confira as fontes instaladas localmente. Talvez você encontre clássicos esquecidos, como Zapf Dingbats ou Webdings.