fr

Polices de cartes : typographie cartographique, glyphs SDF et compromis MapLibre

Guide complet et sourcé des polices pour cartes vectorielles : ce qu'est un glyph SDF et un fontstack, quelles polices les fournisseurs utilisent vraiment (Noto, Roboto, SF Pro, DIN), les conventions de typographie cartographique (hiérarchie, italique pour l'eau, halos), les compromis (couverture vs poids, glyphs servis vs polices locales, CJK) et comment générer ses propres glyphs.

Quand on dit « la police d’une carte », on parle en fait de deux problèmes qui n’ont presque rien à voir. Le premier est cartographique : quelle typographie choisir, comment hiérarchiser les labels, pourquoi l’eau est en italique. Le second est technique : comment un moteur de rendu vectoriel comme MapLibre GL affiche réellement du texte — pas avec un font-family CSS, mais avec des glyphs pré-calculés en champ de distance signée (SDF) servis en Protobuf. Les deux se rejoignent dans une poignée de propriétés text-* d’un style, mais les arbitrages qu’ils imposent sont distincts.

Cet article complète la série cartographie du blog. Self-héberger OpenFreeMap couvre la production des tuiles, les sprites d’icônes MapLibre couvre l’atlas d’icônes, et éditer un fond de carte vectoriel a identifié les trois ressources externes d’un style : sources, sprite et glyphs. Cet article détaille la troisième — celle qu’on branche sans jamais la regarder.


1. Le texte n’est pas une donnée de la tuile

Premier contre-intuitif : le texte des labels n’est pas rasterisé dans les tuiles. Une tuile vectorielle contient des géométries et des attributs (name, name:fr, name:en, class, rank…). C’est le moteur de rendu, côté client, qui décide quel attribut afficher, à quelle taille, avec quelle police, et qui place le label pour éviter les collisions.

Conséquence directe : changer la police d’une carte vectorielle ne demande pas de re-générer les tuiles. On modifie le style (la propriété text-font des couches) et on pointe glyphs vers un serveur qui expose la nouvelle police. C’est ce qui distingue une carte vectorielle d’une carte raster (tuiles PNG) comme le rendu historique d’OpenStreetMap, où la police est cuite dans l’image au moment du rendu serveur.

Carte raster (OSM Carto)          Carte vectorielle (MapLibre GL)
─────────────────────────         ───────────────────────────────
tuile PNG                          tuile .pbf (géométries + name)
  │ texte déjà dessiné               │ pas de texte dessiné
  ▼ (police cuite serveur)           ▼
  affichage                        moteur GL place + dessine le label
                                     │  ← text-font (fontstack)
                                     │  ← glyphs (SDF PBF)

                                   affichage

La police d’une carte raster est un choix serveur définitif ; celle d’une carte vectorielle est un choix client, modifiable à la volée.


2. Comment MapLibre affiche du texte : glyphs, SDF et fontstack

2.1 Le champ de distance signée (SDF)

WebGL et OpenGL ES n’ont pas de rendu de texte natif. Pour dessiner un « A » net à n’importe quelle taille, rotation et avec un halo, Mapbox a introduit en 2014 le rendu par champ de distance signée (Signed Distance Field). Chaque glyph est pré-calculé non pas comme une image du caractère, mais comme une grille où chaque pixel encode la distance au bord de la forme. Un shader reconstruit ensuite le contour net en comparant cette distance à un seuil — et, en décalant le seuil, produit gratuitement le halo, le flou et le contour [Source : A History of Map Glyphs, Fonts, and SDFs — VonCannon].

C’est pour ça que les labels MapLibre restent nets quand on zoome entre deux niveaux, tournent avec la carte (labels de rues) et portent un halo sans que le fournisseur ait à générer une image par taille.

Le compromis est dans la génération : les SDF sont cuits à une taille fixe (l’outil de référence, sdf-glyph-foundry/node-fontnik, rend à 24 px avec une marge de quelques pixels autour du glyph) [Source : mapbox/sdf-glyph-foundry]. À très grand corps, le SDF finit par montrer ses limites (bords légèrement mous), et pour les idéogrammes CJK denses, la résolution SDF est un problème connu de qualité de rendu [Source : maplibre-gl-js #2990].

2.2 Le format : {fontstack}/{range}.pbf

La propriété racine glyphs d’un style est un template d’URL :

{
  "glyphs": "https://fonts.example.com/{fontstack}/{range}.pbf"
}

Source : MapLibre Style Spec — Glyphs.

Deux tokens que le moteur remplit à la volée :

  • {fontstack} : la liste de polices demandée par une couche (voir 2.3).
  • {range} : une plage de 256 codepoints Unicode0-255, 256-511, 512-767… Le plan multilingue de base (BMP) fait 65 536 codepoints, soit 256 fichiers PBF au maximum par police [Source : MapLibre Style Spec — Glyphs].

Le moteur ne télécharge que les plages nécessaires au texte réellement affiché. Une carte de France en alphabet latin ne charge en pratique que 0-255 (Basic Latin + Latin-1) et parfois 256-511 (Latin étendu). C’est l’astuce qui rend le système viable : on n’envoie jamais toute une police au client.

text-font: ["Noto Sans Regular"]  +  label "Chamonix"

        ▼  le moteur détermine les codepoints → plage 0-255
GET https://fonts.example.com/Noto%20Sans%20Regular/0-255.pbf

        ▼  PBF gzippé (~quelques dizaines de Ko), mis en cache
   256 glyphs SDF prêts pour le shader

2.3 text-font est le fontstack — et c’est une pile de secours

Dans une couche symbol, la propriété text-font est un tableau ordonné : une police primaire suivie de polices de secours. C’est ça, un « fontstack » [Source : Mapbox — Manage fontstacks].

{
  "id": "place-city",
  "type": "symbol",
  "source-layer": "place",
  "layout": {
    "text-field": ["get", "name:latin"],
    "text-font": ["Noto Sans Medium", "Noto Sans Regular"],
    "text-size": 14
  }
}

Le fallback fonctionne glyph par glyph, pas label par label : pour chaque caractère, le moteur prend le premier fontstack de la liste qui possède ce glyph. Un label mêlant latin et un caractère absent de la police primaire composera donc les deux polices sur le même mot. Côté serveur, la valeur du token {fontstack} devient la liste jointe par des virgules — et le serveur de glyphs doit savoir concaténer ces polices à la volée (c’est ce que fait le serveur d’OpenMapTiles) ou exposer un répertoire pour chaque combinaison exacte [Source : openmaptiles/fonts].

Piège majeur : le nom du fontstack doit correspondre exactement à un répertoire disponible sur le serveur de glyphs. "Noto Sans Regular" et "Noto Sans" ne sont pas la même chose ; si le répertoire n’existe pas, la requête renvoie un 404 et les labels disparaissent silencieusement. C’est la cause n°1 de « ma carte n’a plus de texte » quand on change de fournisseur de tuiles sans changer le serveur de glyphs [Source : openmaptiles/fonts #7].


3. Quelles polices les fournisseurs utilisent vraiment

La réponse courte : du sans-serif, et pour l’open source, presque toujours du Noto. Le détail est plus instructif, parce qu’il oppose deux mondes — les fournisseurs propriétaires avec leur police maison, et l’écosystème vectoriel ouvert convergé sur Noto.

FournisseurPolice(s) des labelsType de renduRéutilisable ?
Google MapsRoboto (interface : Product Sans)maisonNon (propriétaire)
Apple MapsSan Francisco (SF Pro)maisonNon (propriétaire système Apple)
Mapbox StreetsDIN Pro + polices maisonvectoriel SDFNon (licence commerciale)
MapTilerNoto Sans, Roboto, Metropolisvectoriel SDFOui (OFL/Apache)
OpenMapTiles (fonts)Noto Sans, Open Sans, PT Sans, Roboto, Metropolisvectoriel SDFOui (OFL/Apache)
ProtomapsNoto Sans (Regular/Medium/Italic)vectoriel SDFOui (OFL)
OpenStreetMap CartoNoto → DejaVu Sans → Hanazono → UnifontrasterOui (OFL/libre)

Sources : DesignYourWay — Google Maps font, OpenStreetMap Wiki — CartoCSS/fonts, openmaptiles/fonts, protomaps/basemaps-assets, MapTiler — fonts and labels.

3.1 Pourquoi Noto domine l’open source

Noto est le projet de Google visant à couvrir tous les scripts Unicode — le nom vient de « no more tofu », le « tofu » étant le rectangle vide affiché quand un caractère n’a pas de glyph. Trois raisons en font le défaut de fait des cartes vectorielles libres :

  1. Couverture : une carte est mondiale par nature. Les labels OSM contiennent du latin, du cyrillique, du grec, de l’arabe, du CJK, du devanagari… Noto Sans couvre presque tout, ce qui minimise les tofus [Source : OpenStreetMap Wiki — CartoCSS/fonts].
  2. Cohérence : les différents scripts de Noto partagent des proportions communes, donc un label bilingue reste harmonieux.
  3. Licence OFL : la SIL Open Font License autorise l’embarquement, la modification et la redistribution, y compris commerciale. C’est ce qui permet à MapTiler, Protomaps et OpenMapTiles de la servir sans négociation [Source : openmaptiles/fonts].

MapTiler part d’un socle « Noto Sans » patché par Klokan Technologies, complété par Roboto et Metropolis pour les styles au caractère plus géométrique [Source : MapTiler — fonts and labels].

3.2 Le corollaire licence : on ne réutilise pas la police du voisin

Roboto (Google Maps, Apache 2.0) est réutilisable, mais Product Sans, DIN Pro et San Francisco ne le sont pas. San Francisco est réservé aux plateformes Apple par sa licence ; recréer « le look d’Apple Maps » ou « de Google Maps » avec leur police exacte sur sa propre carte est un problème juridique, pas technique. C’est une raison de plus pour laquelle l’écosystème ouvert s’est aligné sur Noto/Roboto : ce sont les seules polices de qualité mondiale librement embarquables dans un serveur de glyphs.


4. Bonnes pratiques de typographie cartographique

Le choix de la fonte SDF n’est que la plomberie. Le rendu final dépend surtout de comment on l’utilise. Les conventions ci-dessous sont largement partagées entre cartographes ; les sources faisant autorité sont le guide d’Axis Maps (cartographes) et le guide de design Mapbox.

4.1 Sans-serif par défaut à l’écran

Aucune étude ne montre une supériorité intrinsèque du serif ou du sans-serif en cartographie ; la règle est de choisir et de rester cohérent [Source : Axis Maps — Labeling]. Mais à l’écran, aux petits corps typiques d’une carte (10–14 px), le sans-serif l’emporte : les labels font un à trois mots, et les polices à grande hauteur d’x, contreformes ouvertes et sans empattements restent lisibles là où un serif se brouille [Source : Cartography Guide — Typography]. D’où le choix universel de Noto Sans, Roboto ou équivalents.

Les serifs et italiques gardent un usage sémantique classique (voir 4.3), pas décoratif.

4.2 Hiérarchie : donner un rôle à chaque axe typographique

Une carte lisible encode l’importance et la nature des objets dans la typographie. Les axes disponibles, du plus fort au plus subtil :

AxeEncode typiquement
Taille (text-size)Importance / rang (métropole vs village)
Graisse (text-font)Hiérarchie primaire vs secondaire
Couleur (text-color)Catégorie (routes, eau, POI)
Casse (text-transform)Rang administratif (pays en capitales)
Style (italique)Nature physique (eau, relief)
Interlettrage (text-letter-spacing)Étendue spatiale (grandes régions espacées)

La règle : un axe = une variable. Ne pas encoder l’importance à la fois par la taille et la couleur si ces deux dimensions doivent porter des informations différentes [Source : Axis Maps — Labeling].

4.3 L’eau en italique

Convention cartographique ancienne et quasi universelle : les hydronymes (mers, lacs, rivières) sont en italique, souvent dans un bleu, pour les distinguer immédiatement des toponymes terrestres. L’italique évoque le mouvement et sépare visuellement le réseau hydrographique du reste [Source : Axis Maps — Labeling]. C’est pour ça que les serveurs de glyphs livrent systématiquement une variante Italic : ce n’est pas décoratif, c’est une catégorie sémantique.

4.4 Capitales et interlettrage pour l’administratif

Les entités étendues (pays, régions, océans) sont souvent en capitales avec un fort interlettrage positif. L’espacement suggère l’étendue géographique et permet d’étaler le label sur toute la zone sans grossir le corps. Pour les labels primaires, un tracking de +20 à +30 (unités Mapbox, soit ~0,02–0,03 em) ouvre le texte sans le disloquer ; au-delà, l’effet devient illisible [Source : Guide to map design — Mapbox].

4.5 Halos : détacher le texte du fond sans le montrer

Le halo (contour clair autour du texte) est ce qui permet à un label de rester lisible par-dessus une forêt, une route ou une orthophoto. Les principes :

  • Halo clair sur texte foncé, ou l’inverse. En général, halo blanc (1–2 px) autour d’un texte sombre.
  • Discret : le but est de détacher le label, pas d’afficher un contour visible. On joue sur l’opacité (halo semi-transparent) pour éviter l’effet « autocollant » [Source : Guide to map design — Mapbox].
  • Limite technique : dans MapLibre, text-halo-width est plafonné à 1/4 du corps de la police (text-size). Au-delà, le halo ne s’épaissit plus [Source : MapLibre Style Spec — text-halo-width].

4.6 Traduction en propriétés MapLibre

Toutes ces conventions se codent dans les propriétés text-* d’une couche symbol. Les principales [Source : MapLibre Style Spec — Symbol] :

PropriétéRôleConvention
text-fontFontstack (police + secours)Graisse = hiérarchie
text-sizeCorps en pxRang / zoom
text-colorCouleur du texteBleu pour l’eau
text-halo-color / -widthHaloBlanc, ≤ ¼ du corps
text-transformuppercase / lowercaseCapitales pour l’admin
text-letter-spacingInterlettrage (em)Positif pour les grandes zones
text-max-widthRetour à la ligne (em)~8 pour éviter les labels trop larges
text-fieldAttribut affichéname:fr, name:latin

Exemple d’une couche « pays » appliquant capitales + interlettrage + halo :

{
  "id": "place-country",
  "type": "symbol",
  "source-layer": "place",
  "filter": ["==", ["get", "class"], "country"],
  "layout": {
    "text-field": ["get", "name:fr"],
    "text-font": ["Noto Sans Bold"],
    "text-transform": "uppercase",
    "text-letter-spacing": 0.15,
    "text-size": 14,
    "text-max-width": 8
  },
  "paint": {
    "text-color": "#334155",
    "text-halo-color": "rgba(255,255,255,0.8)",
    "text-halo-width": 1.5
  }
}

5. Les compromis à arbitrer

C’est le cœur de la question. Choisir une police de carte, ce n’est pas choisir un font-family : c’est arbitrer entre couverture, poids réseau, nombre de fontstacks, licence et qualité de rendu.

5.1 Couverture linguistique vs poids

Plus une police couvre de scripts, plus il y a de fichiers PBF à générer et à héberger. Noto Sans complet, c’est jusqu’à 256 plages par graisse. En pratique le client ne télécharge que ce qu’il affiche, mais le CJK change l’échelle du problème : le texte chinois/japonais/coréen a une très mauvaise localité dans l’espace des codepoints — une seule tuile peut réclamer des dizaines de fichiers PBF différents, et le volume total est énorme [Source : Mapbox — Use locally generated ideographs].

Arbitrage : si la carte est régionale (Europe), servir une police latine + les scripts voisins suffit. Si elle est mondiale et affiche les noms locaux, il faut assumer le poids CJK — ou passer aux polices locales (5.3).

5.2 Nombre de fontstacks = coût d’hébergement et de requêtes

Chaque combinaison unique de text-font dans le style est un répertoire de PBF distinct à générer et héberger, et chaque fontstack supplémentaire visible sur une tuile est une requête HTTP de plus. Trop de graisses différentes multiplie les allers-retours réseau et, pour les cartes hors-ligne mobiles, le volume à embarquer [Source : Mapbox — Manage fontstacks].

Corollaire souvent ignoré : les polices variables ne sont pas supportées. Chaque graisse (Regular, Medium, Bold) et chaque style (Italic) est un fontstack séparé, cuit indépendamment. Une hiérarchie riche (Regular + Medium + Bold + Italic) = quatre jeux de PBF. La bonne pratique est de se limiter à 2–3 graisses réellement nécessaires.

5.3 Glyphs servis (SDF) vs polices locales

Il existe une échappatoire au poids du CJK : localIdeographFontFamily. Cette option de MapLibre GL JS (héritée de Mapbox) dit au moteur de générer localement les glyphs des plages « CJK Unified Ideographs » et « Hangul Syllables » à partir d’une police installée sur l’appareil, via TinySDF (l’API Canvas du navigateur), au lieu de les télécharger [Source : Mapbox — Use locally generated ideographs].

AspectGlyphs servis (SDF PBF)localIdeographFontFamily
CouvertureTous scripts fournisCJK + Hangul uniquement
Poids réseau CJKTrès lourdNul (police locale)
RenduIdentique partoutDépend de l’OS/navigateur
Contrôle typoTotalAucun (police système)
Latin, arabe, devanagariServis normalementToujours servis (non concernés)
const map = new maplibregl.Map({
  container: "map",
  style: "https://tiles.example.com/style.json",
  // CJK rendu avec la police système, le reste servi en SDF
  localIdeographFontFamily: "'Noto Sans CJK SC', 'Hiragino Sans', sans-serif",
});

Arbitrage clair : pour une carte grand public multilingue, localIdeographFontFamily économise énormément de bande passante au prix d’un rendu CJK non maîtrisé (variable selon l’appareil). Pour une carte où le rendu doit être pixel-identique partout (impression, capture), on sert tout en SDF et on assume le poids.

5.4 Licence

Résumé opérationnel : OFL et Apache 2.0 autorisent l’hébergement et la redistribution des glyphs, y compris en usage commercial — c’est le cas de Noto, Roboto, Open Sans, PT Sans, Metropolis, tous packagés par OpenMapTiles [Source : openmaptiles/fonts]. À l’inverse, les polices maison des grands (Product Sans, DIN Pro, San Francisco) sont hors de portée. Vérifier la licence avant de cuire une police en glyphs qu’on va servir publiquement : servir des PBF, c’est redistribuer la police sous forme dérivée.

5.5 Qualité de rendu SDF

Le SDF est cuit à 24 px : c’est excellent aux corps usuels d’une carte, correct en zoom, mais montre ses limites en très grand corps (titres) et, surtout, pour les glyphs CJK complexes dont les traits fins souffrent de la résolution SDF fixe [Source : maplibre-gl-js #2990]. Si le CJK est central et doit être net, localIdeographFontFamily (rendu vectoriel local) donne souvent un meilleur résultat que les PBF servis.


6. Générer et servir ses propres glyphs

Si l’on suit la logique de la série (tuiles auto-hébergées, sprite régénéré en CI), servir ses propres glyphs est le dernier maillon pour ne plus dépendre du serveur de fonts d’un fournisseur.

6.1 Les outils

OutilTechnoNotes
node-fontnikNode + C++La référence historique ; utilisé par le repo OpenMapTiles
openmaptiles/fontsNodeEmpaquette Noto/Open Sans/PT Sans/Roboto/Metropolis, prêt à builder
MapLibre Font MakerWebUI : dépose un TTF/OTF, télécharge les fontstacks + ranges
build_pbf_glyphsRustPlus rapide, sans dépendances C++ à compiler
versatiles-glyphsRustGénérateur SDF récent

Toutes produisent la même chose : un répertoire par fontstack, contenant les fichiers {range}.pbf (0-255.pbf, 256-511.pbf…) [Source : build_pbf_glyphs].

6.2 Générer avec le repo OpenMapTiles

git clone https://github.com/openmaptiles/fonts
cd fonts
npm install
node ./generate.js
# → _output/Noto Sans Regular/0-255.pbf, 256-511.pbf, …
#   _output/Noto Sans Bold/…  etc.

Source : openmaptiles/fonts — README.

6.3 Servir en statique

Les PBF sont des fichiers statiques : n’importe quel serveur ou CDN fait l’affaire — exactement comme le sprite. Deux points de vigilance :

  • gzip : les PBF de glyphs sont pré-gzippés par les générateurs. Servir avec le bon Content-Encoding: gzip, ou dé-gzipper, sinon le moteur ne les lit pas [Source : openmaptiles/fonts].
  • CORS : la carte tourne dans un navigateur sur un autre domaine que le serveur de glyphs → il faut Access-Control-Allow-Origin.

Le style pointe alors :

{
  "glyphs": "https://fonts.mondomaine.fr/{fontstack}/{range}.pbf"
}

C’est aussi un candidat idéal pour un pipeline CI (comme le sprite Pinhead de l’article dédié) : on cuit les glyphs une fois, on les pousse sur le CDN, on ne les touche plus.


7. Pièges connus

7.1 Le fontstack introuvable (404 silencieux)

Le plus fréquent. Le text-font du style référence "Metropolis Regular" mais le serveur de glyphs n’expose que "Noto Sans Regular" → 404, labels absents, aucune erreur visible sur la carte. Toujours vérifier que chaque valeur de text-font du style a un répertoire correspondant sur le serveur de glyphs. C’est le point qui casse quand on migre un style MapTiler vers un serveur de fonts maison [Source : openmaptiles/fonts #7].

7.2 Les espaces dans le nom du fontstack

Les noms contiennent des espaces (Noto Sans Regular). Dans l’URL, ils sont encodés (Noto%20Sans%20Regular). Un serveur mal configuré qui ne décode pas correctement renverra un 404 sur un répertoire qui existe pourtant.

7.3 Le fallback ne remplace pas une police absente du serveur

Le fallback de text-font opère par glyph manquant dans la police, pas par fontstack absent du serveur. Si la police primaire existe sur le serveur mais n’a pas le caractère, MapLibre passe à la suivante. Mais si le fontstack lui-même renvoie 404, il n’y a pas de repli automatique vers une autre entrée — le label ne s’affiche simplement pas. Le fallback n’est donc pas un filet de sécurité contre les erreurs d’hébergement.

7.4 Le texte de droite à gauche (RTL)

L’arabe et l’hébreu ont besoin du plugin mapbox-gl-rtl-text chargé explicitement (maplibregl.setRTLTextPlugin(...)), sinon le texte s’affiche à l’envers. Ce n’est pas une question de police mais de moteur de mise en forme — mais ça touche directement le rendu des labels dans ces scripts.

7.5 Les scripts non supportés par MapLibre (devanagari, etc.)

Certains scripts complexes (devanagari, birman…) demandent un réordonnancement et des ligatures que le moteur de shaping de MapLibre ne fait pas nativement. Protomaps contourne ça avec les Positioned Glyph Fonts (PGF) : les tuiles exportent le nom déjà positionné (pgf:name:*), et une fontstack custom mappe les codepoints sur des glyphs pré-positionnés [Source : Devanagari in the Protomaps Basemap — oliverwipfli.ch]. À connaître si une carte doit afficher correctement ces scripts.


8. Recommandation

Pour une carte vectorielle auto-hébergée dans l’esprit de cette série :

  • Police : Noto Sans (OFL), servie en glyphs SDF. C’est le défaut de fait, pour la couverture et la licence. Ajouter une variante géométrique (Metropolis, Roboto) seulement si le style le justifie.
  • Graisses : se limiter à 2–3 fontstacks (Regular + Medium/Bold + un Italic pour l’eau). Chaque fontstack en plus = hébergement + requêtes.
  • Typographie : sans-serif partout, hiérarchie par taille et graisse, eau en italique bleu, admin en capitales espacées, halo blanc discret (≤ ¼ du corps).
  • CJK : si la carte est mondiale et le CJK secondaire, activer localIdeographFontFamily pour éviter le poids réseau ; si le rendu CJK doit être maîtrisé, servir en SDF et assumer.
  • Hébergement : cuire les glyphs une fois (OpenMapTiles fonts ou build_pbf_glyphs), pousser sur un CDN en gzip + CORS, et pointer glyphs dessus dans le style — le même schéma que le sprite.

La police d’une carte se change en une ligne (text-font + glyphs). Ce qui coûte, c’est la couverture, le nombre de fontstacks et la qualité CJK — les trois seuls compromis qui méritent réflexion.