Tutos dev’ & informatique

Optimiser ses polices web grâce au font subsetting

Publié le mercredi 29 septembre 2021
Optimisation des polices web par font-subsetting et unicode-range

Introduction

Une belle typographie, ça rend une page web tout de suite plus attractive.
Il est loin le temps des sites en Arial et Times, avec parfois de la Comic Sans (beurk !) pour les plus fantaisistes.

De nos jours, on utilise des webfonts, polices de caractères chargées par le navigateur avec les autres ressources d’une page web. Chaque page peut ainsi avoir ses propres polices, sans être restreinte aux polices installées sur la machine du visiteur.

Cet article présente une technique d’optimisation des webfonts, afin de conserver un chargement de page rapide malgré l’utilisation de polices personnalisées.

Le font subsetting, qu’est ce que c’est ?

Avant de parler du font subsetting, il faut s’intéresser aux polices elles-mêmes. Que trouve t’on à l’intérieur d’un fichier webfont ?

Une police contient des caractères identifiés par un code

La majeure partie d’une webfont est occupée par les caractères (symboles).

Chaque caractère est identifié par un code numérique généralement exprimé en base hexadécimale. Par exemple, la lettre A majuscule a pour code hexadécimal 41. Les codes sont les mêmes quelle que soit la police : le A majuscule a toujours le code hexadécimal 41.

Caractères de la police Roboto
Caractères de la police Roboto, codes 3D à 44


Une webfont contient en général les caractères de plusieurs langues : latines, cyrilliques, arabes, asiatiques… Elle contient aussi des symboles graphiques (puces, icônes).

Variations des caractères : les caractéristiques OpenType de la police

Pour chaque caractère, le designer de la police a la possibilité d’ajouter des variations de ce dernier. Les différents types de variations possibles sont rassemblés sous l’appellation caractéristiques OpenType – en anglais on parle de features.

Caractéristiques Open-Type de la police Roboto pour la lettre G
Caractéristiques OpenType du code 67 de la police Roboto : normal, petites-capitales, style alternatif 1


Ces variations disposent du même code que le caractère de base dont elles dérivent. Par exemple, les variations de la lettre G minuscule auront toutes le code hexadécimal 67. On peut indiquer dans le style CSS quand activer ou ne pas activer une variation particulière, et le moteur de rendu du navigateur se chargera d’afficher la bonne version du caractère.

Une description des features OpenType est disponible sur le site de Mozilla : explication des features OpenType. Il n’est pas nécessaire de trop approfondir (la plupart des sites n’exploitent pas les features) à moins de travailler sur des sites aux typographies complexes.

Définition du font subsetting

Le font subsetting consiste à utiliser une webfont « subset » de la police d’origine qui ne contient que les caractères et les caractéristiques OpenType qui nous intéressent.

Exemple de font subsetting sur la police Roboto
Exemple de font subsetting : suppression de 2 features (causant 4 variations) et 3 caractères


Par exemple, sur un site qui ne contient que des caractères français, il est inutile de faire télécharger au visiteur une webfont contenant toute la gamme des symboles chinois.

Cela permet de réduire drastiquement la taille des webfonts. En effet, il y a parfois plus d’un millier de caractères dans une police, avec plusieurs variations pour certains d’entre-eux !

Comment réaliser un subset

Choisir les codes de caractères à inclure dans le subset

La première étape dans la création d’un subset de webfont est le choix des caractères à inclure.

Tout dépend des langues sur le site

Le choix des caractères se fait en fonction des langues présentes sur le site. Sauf cas particulier, on se retrouve dans l’une des situations suivantes :

⚙️  Le site est mono-langue, sans possibilité aux visiteurs de laisser des commentaires. C’est le cas le plus simple, on va inclure dans le subset l’ensemble des caractères de la langue concernée, ainsi que les symboles de ponctuation.

⚙️  Le site est multi-langue, sans possibilité aux visiteurs de laisser des commentaires. Dans ce cas, on va soit inclure l’ensemble des caractères de toutes les langues (ainsi que la ponctuation) dans un seul subset, soit générer un subset pour chaque langue.

⚙️  Le site est mono ou multi-langue, et les visiteurs peuvent laisser des commentaires. C’est le cas le plus complexe. Il faut déterminer quelles seront les langues parlées dans le site et les commentaires, et soit faire un subset incluant les symboles de toutes ces langues (ainsi que la ponctuation), soit faire un subset par langue.

Lister les codes caractères avec la table des blocs unicode

Une fois que l’on a choisi quels caractères conserver, on utilise la table des caractères unicode pour déterminer les codes des caractères. Voici un petit résumé des caractères appropriés aux sites en français :

Nom du blocCodesContient notamment
Basic LatinU+0000-007FMinuscules et majuscules sans accent, chiffres, ponctuation et symboles de base (@, #, $, !)
Latin-1 SupplementU+0080-00FFLettres accentuées communes (é, ï, ñ), ponctuation et symboles communs (£, §, ¡, ¿, «), espace insécable nbsp
Latin Extended-AU+0100-017Fœ (U+0153), accents étrangers (ů, ũ)
General PonctuationU+2000-206FGuillemets élégants (‘, “), puces (•, ‣), primes (‵, ‶, ‷)
Currency SymbolsU+20A0-20CF€ (U+20AC), devises étrangères (₤, ₿)
Quelques blocs de caractères unicode adaptés à la langue française


Par exemple, pour un petit site français, on pourra utiliser les caractères U+0000-007F,U+0080-00FF,U+0153,U+20AC.

Trouver les caractères présents sur un site avec glyphhanger

L’outil nodejs glyphhanger permet de lister les caractères présents sur une page web ou sur un site entier. Ca permet d’éviter d’oublier certains caractères spéciaux (tiens, il y avait un symbole de puce ici !).

Il peut être installé via le gestionnaire de packages NodeJS npm. La documentation indique qu’il faut aussi installer pyftsubset, mais ce n’est obligatoire que si on veut générer un subset directement depuis glyphhanger, ce qui n’est pas notre cas.

# Installation de glyphhanger (peut demander un sudo)
npm install -g glyphhanger

# Alternative non-root
npm install glyphhanger

Son utilisation pour lister les caractères en ligne de commandes est simple :

# Déterminer les caractères présents sur une seule page
glyphhanger https://example.com/

# Déterminer les caractères présents sur un site (3 niveaux)
glyphhanger https://example.com/ --spider-limit=3

# Résultat :
# U+A,U+20,U+2E,U+44,U+45,U+4D,U+54,U+59,U+61,U+63-69,U+6B-70,U+72-79

Il ne faut pas limiter le subset aux codes de caractères renvoyés par glyphhanger ! Ce n’est pas parce qu’un site ne contient pas la lettre Z pour l’instant qu’elle ne sera pas utilisée plus tard !

On limitera son utilisation à détecter s’il y a des caractères spéciaux (hors alphabet et symboles communs) à inclure.

Déterminer les caractéristiques à conserver

Pour déterminer les caractéristiques à conserver, il faut déjà connaitre celles qui sont présentes dans la police d’origine. On peut utiliser pour cela l’analyseur de police Wakamai Fondue.

Caractéristiques OpenType de la police Roboto
Caractéristiques OpenType de la police Roboto


Sans action de notre part, les features OpenType actives sur une page web sont le Kerning kern, les Contextual Alternates calt et les Ligatures liga et clig. Il est préférable de toujours les conserver.

L’analyse révèle parfois des caractéristiques requises (en anglais Required Layout Features). Il est possible de les conserver également. Cependant, elles sont souvent requises uniquement pour certains langages (cyrilliques, asiatiques), et peuvent être omises pour les subsets en langues occidentales.

Caractéristiques OpenType requises de la police Roboto
Caractéristiques OpenType requises pour la police Roboto


Pour les autres caractéristiques, elles ne sont appliquées que si elles sont activées dans style CSS. On peut donc déterminer si elles sont utilisées en explorant les styles du site à la recherche des instructions associées :

Instruction CSSCaractéristique OpenType
font-kerningKerning : kern
font-variant-ligaturesLigatures : liga, clig, dlig, hlig, calt
font-variant-positionMise en exposant et indice : sups, subs
font-variant-alternatesStyles alternatifs : salt, ss01..ss20
font-variant-capsPetites capitales : smcp, c2sc, pcap, unic, titl
font-variant-numericChiffres et fractions : ordn, zero, lnum, onum, pnum, tnum, frac, afrc
font-variant / font-feature-settingsPotentiellement toutes, lire le CSS
Correspondance caractéristique OpenType et code CSS


Sur de nombreux sites ces caractéristiques ne sont pas utilisées. Pour reprendre l’exemple du petit site français, on pourrait sans doute se limiter à conserver les features kern,calt,liga,clig.

Générer le subset avec les caractères et les features

Une fois que l’on a choisi quels caractères et quelles features OpenType conserver dans notre subset, il est temps de le générer. Cela peut se faire en ligne ou localement.

Génération de subset en ligne avec Font Squirrel

Le mode expert du générateur de webfont Font Squirrel permet d’extraire un subset d’une police, et de le générer dans différents formats. Malgré son appellation, il reste simple à utiliser pour ce que l’on veut faire. Il n’arrive parfois pas à retirer certaines caractéristiques OpenType, mais permet tout de même de bien réduire la taille des webfonts.

Cliquez sur l’image ci-dessous pour voir une capture d’écran des réglages de font subsetting. Pour résumer, tout est désactivé sauf ce qui concerne le choix des caractères et des features, et les formats sont WOFF et WOFF2 (on peut se limiter au format WOFF2 si le support de Internet Explorer n’est pas souhaité).

Réglages Font Squirrel pour exporter un subset de police
Réglages de création de subset dans Font Squirrel

 

Génération de subset en local avec pyftsubset

Pour générer localement un subset on peut utiliser l’outil pyftsubset disponible dans le package python des fontTools. Il peut être installé via les gestionnaires pip ou pip3. Par exemple, pour la version Python 3 :

# Installation des fontTools (peut demander un sudo)
pip3 install fonttools[woff,unicode]

# Alternative non-root
pip3 install fonttools[woff,unicode] --user

Une fois installé il est assez simple à utiliser, à moins que l’on veuille se perdre dans toutes les possibilités qui sont offertes par l’outil. La documentation en ligne est plutôt bien fournie.

Si on reprend l’exemple du petit site français pour lequel on a estimé qu’il fallait un subset avec les caractères U+0000-007F,U+0080-00FF,U+0153,U+20AC et les features kern,calt,liga,clig, la commande est la suivante :

# Création d'un subset
# - Format WOFF2
# - Caractères U+0000-007F, U+0080-00FF, U+0153 et U+20AC
# - Caractéristiques OpenType kern,liga,clig

pyftsubset police.ttf --unicodes=U+0000-007F,U+0080-00FF,U+0153,U+20AC --layout-features=kern,calt,liga,clig --flavor=woff2 --output-file=police-subset.woff2

On peut aussi supprimer toutes les caractéristiques OpenType, ou bien toutes les conserver :

# Conserver toutes les caractéristiques OpenType
pyftsubset police.ttf --unicodes=U+0000-007F,U+0080-00FF,U+0153,U+20AC --layout-features='*' --flavor=woff --output-file=police-subset.woff

# Supprimer toutes les caractéristiques OpenType
pyftsubset police.ttf --unicodes=U+0000-007F,U+0080-00FF,U+0153,U+20AC --layout-features='' --flavor=woff --output-file=police-subset.woff

On obtient un fichier WOFF ou WOFF2 prêt à être utilisé sur le site internet.

Utiliser le subset en CSS

Les fichiers subset peuvent être utilisés comme n’importe quelle webfont dans le code du site internet. Cela se passe au niveau des styles CSS :

@font-face {
  font-family: Roboto;
  src: url(fonts/roboto-subset.woff2) format('woff2'), url(fonts/roboto-subset.woff) format('woff');
  font-weight: normal; font-style: normal;
}

body{ font-family: Roboto }

On peut utiliser l’instruction unicode-range à l’intérieur de la définition @font-face pour indiquer quels sont les caractères présents dans le subset. C’est totalement optionnel, à moins d’avoir plusieurs subset pour la même police (un par langue par exemple). Cela permet au navigateur de charger le bon subset en fonction des caractères à afficher.

@font-face {
  font-family: Roboto;
  src: url(fonts/roboto-francais.woff2) format('woff2'), url(fonts/roboto-francais.woff) format('woff');
  font-weight: normal; font-style: normal;
  unicode-range: U+0000-007F,U+0080-00FF,U+0153,U+20AC; /* sera utilisé pour les caractères français */
}

@font-face {
  font-family: Roboto;
  src: url(fonts/roboto-cyrillic.woff2) format('woff2'), url(fonts/roboto-cyrillic.woff) format('woff');
  font-weight: normal; font-style: normal;
  unicode-range: U+0400-04FF; /* sera utilisé pour les caractères cyrilliques */
}

body{ font-family: Roboto }

Conclusion

Voila, on dispose maintenant des notions et d’une méthodologie pour découper les webfonts en subsets plus légers à charger.

Le prochain article concerne une mise en pratique du font subsetting.

D’ici là bon code à vous, à la prochaine !

Bien entendu, si vous avez besoin d’une aide pour optimiser votre site web, n’hésitez pas à me contacter.

Références

Analyseur de police (caractères et fonctionnalités) – Wakamai Fondue
Guide des caractéristiques de police – Mozilla Developers
Tableau des caractères unicode – Unicode-Table
Glyphhanger – Github
Webfont Generator – Font Squirrel
Pyftsubset ligne de commande – Documentation fontTools

Photo de profil de Loïc Burtin Code Colibri développeur web à Dijon

A propos de l'auteur

Loïc Burtin

Développeur web indépendant, avec une tendance à couper les plumes en quatre pour que les sites se chargent plus rapidement.

Partager cet article :

Inscription à la newsletter Inscription à la newsletter

Ce sujet vous intéresse ?

Inscrivez-vous à la newsletter pour être informé de la publication des nouveaux articles !

Ce formulaire est protégé par hCaptcha, dont la Politique de Confidentialité et les Conditions d'Utilisation s'appliquent.

Chargement en cours
Succès de l'opération