Utiliser une typographie avancée avec des polices locales

Découvrez comment l'API Local Font Access vous permet d'accéder aux polices installées localement par l'utilisateur et d'obtenir des informations détaillées à leur sujet.

Polices Web sécurisées

Si vous développez des sites Web depuis un certain temps, vous vous souvenez peut-être des polices Web sécurisées. Ces polices sont connues pour être disponibles sur presque toutes les instances des systèmes d'exploitation les plus utilisés (à savoir Windows, macOS, les distributions Linux les plus courantes, Android et iOS). Au début des années 2000, Microsoft a même lancé une initiative intitulée TrueType core fonts for the Web (Polices TrueType de base pour le Web) qui proposait ces polices en téléchargement sans frais dans le but de "vous permettre de voir les pages exactement comme le concepteur du site l'a prévu chaque fois que vous visitez un site Web qui les spécifie". Oui, cela inclut les sites définis en Comic Sans MS. Voici à quoi pourrait ressembler une pile de polices Web sécurisées classiques (avec la police sans-serif comme police de secours ultime) :

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

Polices Web

L'époque où les polices Web sécurisées étaient vraiment importantes est révolue. Aujourd'hui, nous avons des polices Web, dont certaines sont même des polices variables que nous pouvons ajuster davantage en modifiant les valeurs des différents axes exposés. Vous pouvez utiliser des polices Web en déclarant un bloc @font-face au début du CSS, qui spécifie les fichiers de police à télécharger :

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

Vous pouvez ensuite utiliser la police Web personnalisée en spécifiant font-family, comme d'habitude :

body {
  font-family: 'FlamboyantSansSerif';
}

Polices locales en tant que vecteur d'empreinte digitale

La plupart des polices Web proviennent, eh bien, du Web. Toutefois, il est intéressant de noter que la propriété src dans la déclaration @font-face, en plus de la fonction url(), accepte également une fonction local(). Cela permet de charger des polices personnalisées (surprise !) localement. Si l'utilisateur a installé FlamboyantSansSerif sur son système d'exploitation, la copie locale sera utilisée au lieu d'être téléchargée :

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

Cette approche fournit un mécanisme de secours intéressant qui permet potentiellement d'économiser de la bande passante. Sur Internet, malheureusement, nous ne pouvons pas avoir de belles choses. Le problème avec la fonction local() est qu'elle peut être utilisée à des fins abusives pour l'empreinte numérique du navigateur. Il s'avère que la liste des polices installées par un utilisateur peut être très révélatrice. De nombreuses entreprises disposent de leurs propres polices d'entreprise, qui sont installées sur les ordinateurs portables des employés. Par exemple, Google utilise une police d'entreprise appelée Google Sans.

Aperçu de la police Google Sans dans l'application Livre de polices de macOS.
La police Google Sans installée sur l'ordinateur portable d'un employé Google.

Un pirate informatique peut essayer de déterminer pour quelle entreprise travaille une personne en testant l'existence d'un grand nombre de polices d'entreprise connues, comme Google Sans. Le pirate informatique tenterait d'afficher le texte défini dans ces polices sur un canevas et de mesurer les glyphes. Si les glyphes correspondent à la forme connue de la police de l'entreprise, le pirate a réussi son attaque. Si les glyphes ne correspondent pas, l'auteur de l'attaque sait qu'une police de remplacement par défaut a été utilisée, car la police de l'entreprise n'était pas installée. Pour en savoir plus sur cette attaque et d'autres attaques d'empreinte numérique du navigateur, consultez l'étude de Laperdix et al.

En dehors des polices d'entreprise, la simple liste des polices installées peut être identifiante. La situation concernant ce vecteur d'attaque est devenue si grave que l'équipe WebKit a récemment décidé de "n'inclure [dans la liste des polices disponibles] que les polices Web et celles fournies avec le système d'exploitation, mais pas les polices installées localement par l'utilisateur". (Et me voilà, avec un article sur l'octroi d'accès aux polices locales.)

API Local Font Access

Le début de cet article vous a peut-être mis de mauvaise humeur. Ne pouvons-nous vraiment pas avoir de belles choses ? Ne vous inquiétez pas. Nous pensons que nous pouvons le faire, et peut-être que tout n'est pas perdu. Mais avant cela, laissez-moi répondre à une question que vous vous posez peut-être.

Pourquoi avons-nous besoin de l'API Local Font Access alors qu'il existe des polices Web ?

Les outils de conception et de création graphique de qualité professionnelle ont toujours été difficiles à proposer sur le Web. L'un des principaux obstacles a été l'impossibilité d'accéder à la grande variété de polices professionnelles, construites et insérées par les typographes, que les designers ont installées localement. Les polices Web permettent certains cas d'utilisation de publication, mais ne permettent pas l'accès programmatique aux formes des glyphes vectoriels et aux tables de polices utilisées par les rasterizers pour afficher les contours des glyphes. De même, il n'existe aucun moyen d'accéder aux données binaires d'une typographie Web.

  • Les outils de conception ont besoin d'accéder aux octets de la police pour implémenter leur propre mise en page OpenType et permettre aux outils de conception de s'accrocher à des niveaux inférieurs, pour des actions telles que l'exécution de filtres vectoriels ou de transformations sur les formes des glyphes.
  • Les développeurs peuvent avoir des piles de polices héritées pour leurs applications qu'ils portent sur le Web. Pour utiliser ces piles, il faut généralement un accès direct aux données de police, ce que les polices Web ne fournissent pas.
  • Il est possible que certaines polices ne soient pas concédées sous licence pour être diffusées sur le Web. Par exemple, Linotype possède une licence pour certaines polices qui n'inclut que l'utilisation sur ordinateur.

L'API Local Font Access vise à résoudre ces problèmes. Il se compose de deux parties :

  • Une API d'énumération des polices, qui permet aux utilisateurs d'accorder l'accès à l'ensemble des polices système disponibles.
  • À partir de chaque résultat d'énumération, vous pouvez demander un accès au conteneur SFNT de bas niveau (orienté octet) qui inclut les données complètes de la police.

Prise en charge des navigateurs

Browser Support

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

Source

Utiliser l'API Local Font Access

Détection de caractéristiques

Pour vérifier si l'API Local Font Access est prise en charge, utilisez la commande suivante :

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

Énumérer les polices locales

Pour obtenir la liste des polices installées localement, vous devez appeler window.queryLocalFonts(). La première fois, cela déclenchera une invite d'autorisation que l'utilisateur pourra approuver ou refuser. Si l'utilisateur autorise l'interrogation de ses polices locales, le navigateur renvoie un tableau avec les données des polices sur lesquelles vous pouvez effectuer une boucle. Chaque police est représentée sous la forme d'un objet FontData avec les propriétés family (par exemple, "Comic Sans MS"), fullName (par exemple, "Comic Sans MS"), postscriptName (par exemple, "ComicSansMS") et style (par exemple, "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);
}

Si vous ne vous intéressez qu'à un sous-ensemble de polices, vous pouvez également les filtrer en fonction des noms PostScript en ajoutant un paramètre postscriptNames.

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

Accéder aux données SFNT

L'accès complet à SFNT est disponible via la méthode blob() de l'objet FontData. SFNT est un format de fichier de police qui peut contenir d'autres polices, telles que PostScript, TrueType, OpenType, Web Open Font Format (WOFF) et d'autres.

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

Démo

Vous pouvez voir l'API Local Font Access en action dans la démonstration. N'oubliez pas de consulter également le code source. La démo présente un élément personnalisé appelé <font-select> qui implémente un sélecteur de police local.

Considérations liées à la confidentialité

L'autorisation "local-fonts" semble fournir une surface très empreinte. Toutefois, les navigateurs sont libres de renvoyer ce qu'ils veulent. Par exemple, les navigateurs axés sur l'anonymat peuvent choisir de ne fournir qu'un ensemble de polices par défaut intégrées au navigateur. De même, les navigateurs ne sont pas tenus de fournir les données du tableau exactement telles qu'elles apparaissent sur le disque.

Dans la mesure du possible, l'API Local Font Access est conçue pour n'exposer que les informations nécessaires pour permettre les cas d'utilisation mentionnés. Les API système peuvent générer une liste des polices installées qui n'est pas dans un ordre aléatoire ni trié, mais dans l'ordre d'installation des polices. Le fait de renvoyer exactement la liste des polices installées fournie par une telle API système peut exposer des données supplémentaires qui peuvent être utilisées pour l'empreinte numérique. De plus, la conservation de cet ordre n'aide pas les cas d'utilisation que nous souhaitons activer. Par conséquent, cette API exige que les données renvoyées soient triées avant d'être renvoyées.

Sécurité et autorisations

L'équipe Chrome a conçu et implémenté l'API Local Font Access en utilisant les principes de base définis dans Controlling Access to Powerful Web Platform Features, y compris le contrôle utilisateur, la transparence et l'ergonomie.

Contrôle des utilisateurs

L'accès aux polices d'un utilisateur est entièrement sous son contrôle et ne sera autorisé que si l'autorisation "local-fonts", telle qu'indiquée dans le registre des autorisations, est accordée.

Transparence

Vous pouvez voir si un site a été autorisé à accéder aux polices locales de l'utilisateur dans la fiche d'informations sur le site.

Persistance des autorisations

L'autorisation "local-fonts" sera conservée entre les rechargements de page. Il peut être révoqué via la feuille Informations sur le site.

Commentaires

L'équipe Chrome souhaite connaître votre avis sur l'API Local Font Access.

Parlez-nous de la conception de l'API

Y a-t-il un aspect de l'API qui ne fonctionne pas comme prévu ? Ou bien manquent-ils des méthodes ou des propriétés dont vous avez besoin pour mettre en œuvre votre idée ? Vous avez une question ou un commentaire sur le modèle de sécurité ? Signalez un problème lié aux spécifications dans le dépôt GitHub correspondant ou ajoutez vos commentaires à un problème existant.

Signaler un problème d'implémentation

Avez-vous trouvé un bug dans l'implémentation de Chrome ? Ou l'implémentation est-elle différente de la spécification ? Signalez un bug sur new.crbug.com. Veillez à inclure autant de détails que possible, des instructions simples pour reproduire le problème et saisissez Blink>Storage>FontAccess dans la zone Composants.

Soutenir l'API

Comptez-vous utiliser l'API Local Font Access ? Votre soutien public aide l'équipe Chrome à hiérarchiser les fonctionnalités et montre aux autres fournisseurs de navigateurs à quel point il est essentiel de les prendre en charge.

Envoyez un tweet à @ChromiumDev avec le hashtag #LocalFontAccess et dites-nous où et comment vous l'utilisez.

Remerciements

La spécification de l'API Local Font Access a été modifiée par Emil A. Eklund, Alex Russell, Joshua Bell et Olivier Yiptong. Cet article a été examiné par Joe Medley, Dominik Röttsches et Olivier Yiptong. Image principale de Brett Jordan sur Unsplash.