Utiliser une typographie avancée avec des polices locales

Découvrez comment accéder aux polices installées localement sur l'utilisateur et obtenir des informations de bas niveau à leur sujet avec l'API Local Font Access.

Publié le 24 août 2020

Polices Web sécurisées

Si vous développez pour le Web depuis un certain temps, vous vous souvenez peut-être des polices Web dites 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 de base TrueType pour le Web) qui proposait ces polices en téléchargement sans frais dans le but que "lorsque vous consultez un site Web qui les spécifie, vous voyez les pages exactement comme le concepteur du site l'a prévu". Oui, cela inclut les sites définis dans Comic Sans MS. Voici à quoi pourrait ressembler une pile de polices Web sécurisées classique (avec la police sans-serif comme solution de repli 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 disposons de 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 @font-face bloc au début du code CSS, qui spécifie le ou 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 le font-family, comme d'habitude :

body {
  font-family: 'FlamboyantSansSerif';
}

Polices locales comme vecteur d'empreinte

La plupart des polices Web proviennent du Web. Toutefois, il est intéressant de noter que la src propriété de la @font-face déclaration, en plus de la url() fonction, accepte également une local() fonction. 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 repli intéressant qui permet d'économiser de la bande passante. Malheureusement, sur Internet, nous ne pouvons pas avoir de belles choses. Le problème avec la fonction local() est qu'elle peut être utilisée de manière abusive pour l'empreinte du navigateur. Il s'avère que la liste des polices installées par un utilisateur peut être assez identifiante. De nombreuses entreprises ont leurs propres polices d'entreprise qui sont installées sur les ordinateurs portables de leurs employés. Par exemple, Google possède 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é de 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, telles que Google Sans. Il tente de rendre 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 d'entreprise, le pirate informatique a trouvé une correspondance. Si les glyphes ne correspondent pas, le pirate informatique sait qu'une police de remplacement par défaut a été utilisée, car la police d'entreprise n'a pas été installée. Pour en savoir plus sur cette attaque et d'autres attaques d'empreinte de navigateur, consultez l' étude de Laperdix et al.

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

L'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, et peut-être que tout n'est pas désespéré. Mais tout d'abord, 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 graphiques de qualité professionnelle ont toujours été difficiles à fournir sur le Web. L'un des principaux obstacles était l'impossibilité d'accéder à la variété complète des polices conçues et suggérées par des professionnels que les concepteurs ont installées localement. Les polices Web permettent certains cas d'utilisation de publication, mais ne permettent pas d'accéder par programmation aux formes de glyphes vectoriels et aux tables de polices utilisées par les rasteriseurs pour afficher les contours des glyphes. Il n'existe pas non plus de moyen d'accéder aux données binaires d'une police Web.

  • Les outils de conception doivent accéder aux octets de police pour effectuer leur propre implémentation de mise en page OpenType et permettre aux outils de conception de s'intégrer à des niveaux inférieurs, pour des actions telles que l'application de filtres ou de transformations vectoriels aux formes de glyphes.
  • Les développeurs peuvent avoir des piles de polices héritées pour leurs applications qu'ils apportent sur le Web. Pour utiliser ces piles, ils ont généralement besoin d'un accès direct aux données de police, ce que les polices Web ne fournissent pas.
  • Certaines polices ne sont peut-être 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 tente de résoudre ces problèmes. Elle se compose de deux parties :

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

Prise en charge des navigateurs

Browser Support

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

Source

Comment utiliser l'API Local Font Access ?

Détection de fonctionnalités

Pour vérifier si l'API Local Font Access est compatible, utilisez :

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éclenche une invite d'autorisation que l'utilisateur peut accepter ou refuser. Si l'utilisateur autorise l'interrogation de ses polices locales, le navigateur renvoie un tableau contenant les données des polices que vous pouvez parcourir. Chaque police est représentée par 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 n'êtes intéressé que par 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 SFNT complet 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 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émo. 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 polices locales.

Considérations liées à la confidentialité

L'autorisation "local-fonts" semble fournir une surface hautement empreinte. Toutefois, les navigateurs sont libres de renvoyer ce qu'ils souhaitent. 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 de table 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 activer les cas d'utilisation mentionnés. Les API système peuvent produire une liste de polices installées non pas dans un ordre aléatoire ou 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, et les cas d'utilisation que nous souhaitons activer ne sont pas facilités par la conservation de cet ordre. Par conséquent, cette API nécessite 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 Contrôler l'accès aux fonctionnalités puissantes de la plate-forme Web, y compris le contrôle des utilisateurs, 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 pas autorisé, sauf si l' "local-fonts" autorisation, telle qu'elle est répertoriée dans le registre des autorisations, est accordée.

Transparence

Le fait qu'un site ait obtenu l'accès aux polices locales de l'utilisateur sera visible dans la fiche d'informations du site.

Persistance des autorisations

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

Commentaires

L'équipe Chrome souhaite connaître votre expérience avec l'API Local Font Access.

Parlez-nous de la conception de l'API

Y a-t-il quelque chose dans l'API qui ne fonctionne pas comme prévu ? Ou y a-t-il des méthodes ou des propriétés manquantes 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 de spécification dans le dépôt GitHub correspondant ou ajoutez vos commentaires à un problème existant.

Signaler un problème lié à l'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 la reproduction et saisissez Blink>Storage>FontAccess dans la zone Composants.

Soutenir l'API

Prévoyez-vous d'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 indiquez 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.