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 de bas niveau les concernant.

Polices Web sécurisées

Si vous travaillez dans le développement Web depuis suffisamment longtemps, vous vous souvenez peut-être des polices Web sûres. 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 appelée TrueType core fonts for the Web, qui proposait de télécharger sans frais ces polices dans le but que "chaque fois que vous visitez un site Web qui les spécifie, vous verrez les pages exactement comme le concepteur du site l'a prévu". Oui, cela inclut les sites définis en Comic Sans MS. Voici à quoi peut ressembler une pile de polices Web sécurisée classique (avec la police de remplacement ultime de la police sans-serif) :

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

Polices Web

L'époque où les polices Web sûres étaient vraiment importantes est révolue. Aujourd'hui, nous disposons de polices Web, dont certaines sont même des polices variables que nous pouvons affiner 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 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 font-family, comme d'habitude:

body {
  font-family: 'FlamboyantSansSerif';
}

Polices locales comme vecteur d'empreinte

La plupart des polices Web viennent du Web. Toutefois, il est intéressant de noter que la propriété src de la déclaration @font-face, en plus de la fonction url(), accepte également une fonction local(). Cela permet de charger (surprise !) des polices personnalisées localement. Si l'utilisateur a installé FlamboyantSansSerif sur son système d'exploitation, la copie locale est 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 qui permet potentiellement d'économiser de la bande passante. Sur Internet, malheureusement, on ne peut 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 digitale du navigateur. Il s'avère que la liste des polices installées par un utilisateur peut être très identifiante. De nombreuses entreprises ont leurs propres polices d'entreprise installées sur les ordinateurs portables de leurs employés. Par exemple, Google utilise une police d'entreprise appelée Google Sans.

Aperçu de la police Google Sans dans l'application Font Book sous macOS
Police Google Sans installée sur l'ordinateur portable d'un employé de Google.

Un pirate informatique peut essayer de déterminer l'entreprise pour laquelle une personne travaille en vérifiant l'existence d'un grand nombre de polices d'entreprise connues, comme Google Sans. Le pirate informatique tente 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 informatique reçoit un appel. 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 de l'entreprise n'a pas été installée. Pour en savoir plus sur cette attaque et sur d'autres attaques par fingerprinting des navigateurs, consultez l'article d'étude publié par Laperdix et al.

En dehors des polices de l'entreprise, même la liste des polices installées peut être identifiante. La situation avec ce vecteur d'attaque est devenue si grave que récemment, l'équipe WebKit a décidé de "n'inclure que [dans la liste des polices disponibles] les polices Web et les polices fournies avec le système d'exploitation, mais pas celles 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 a peut-être mis le moral à zéro. Ne pouvons-nous vraiment pas avoir de belles choses ? Ne vous inquiétez pas. Nous pensons que nous pouvons y arriver, et peut-être que tout n'est pas perdu. Mais d'abord, laissez-moi répondre à une question que vous vous posez peut-être.

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

Il a toujours été difficile de proposer des outils de conception et de graphisme de qualité professionnelle sur le Web. L'un des obstacles a été l'impossibilité d'accéder à la totalité des polices créées et optimisées par des professionnels et de les utiliser, que les concepteurs ont installées localement. Les polices Web permettent certains cas d'utilisation de la publication, mais n'autorisent pas l'accès programmatique aux formes de glyphes vectoriels et aux tables de polices utilisées par les tesselateurs pour afficher les contours des glyphes. Il n'est pas non plus possible d'accéder aux données binaires d'une police Web.

  • Les outils de conception ont besoin d'accéder aux octets de police pour implémenter leur propre mise en page OpenType et permettre aux outils de conception de s'intégrer à des niveaux inférieurs, pour des actions telles que l'exécution de filtres vectoriels ou de transformations sur les formes de glyphes.
  • Les développeurs peuvent avoir des piles de polices obsolètes pour leurs applications qu'ils portent sur le Web. Pour utiliser ces piles, elles nécessitent 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 autorisées à être diffusées sur le Web. Par exemple, Linotype dispose d'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. 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, possibilité de demander un accès au conteneur SFNT de bas niveau (orienté octets) qui inclut l'ensemble des données de police.

Prise en charge des navigateurs

Navigateurs pris en charge

  • Chrome: 103.
  • Edge : 103.
  • Firefox : non compatible.
  • Safari : non compatible.

Source

Utiliser l'API Local Fonts 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 approuver ou refuser. Si l'utilisateur autorise l'interrogation de ses polices locales, le navigateur renvoie un tableau contenant des données de polices que vous pouvez parcourir en boucle. 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 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

Un 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, WOFF (Web Open Font Format) 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 ci-dessous. Veillez également à consulter le code source. La démonstration 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 hautement exploitable par empreinte digitale. Cependant, 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 exactes nécessaires pour activer les cas d'utilisation mentionnés. Les API système peuvent générer une liste des 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 digitale, et les cas d'utilisation que nous souhaitons activer ne sont pas facilités par le maintien de cet ordre. 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 mis en œuvre l'API Local Font Access selon les principes fondamentaux définis dans la section Contrôler l'accès aux fonctionnalités puissantes de la plate-forme Web, 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 n'est pas autorisé, sauf si l'autorisation "local-fonts", comme indiqué dans le registre des autorisations, est accordée.

Transparence

La fiche d'informations sur le site indique si un site a été autorisé à accéder aux polices locales de l'utilisateur.

Persistance des autorisations

L'autorisation "local-fonts" est conservée entre les rechargements de page. Vous pouvez le révoquer via la fiche Informations sur le site.

Commentaires

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

Présentez-nous la conception de l'API

Y a-t-il un aspect de l'API qui ne fonctionne pas comme prévu ? Ou s'il manque 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 de spécification 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 des spécifications ? Signalez un bug sur new.crbug.com. Veillez à inclure autant de détails que possible et des instructions simples pour reproduire le bug, et saisissez Blink>Storage>FontAccess dans la zone Composants. Glitch est idéal pour partager des reproductions rapidement et facilement.

Afficher la compatibilité avec 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 pour nous indiquer où et comment vous l'utilisez.

Remerciements

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