Polices dans Matplotlib #

Matplotlib a besoin de polices pour fonctionner avec son moteur de texte, dont certaines sont livrées avec l'installation. La police par défaut est DejaVu Sans qui couvre la plupart des systèmes d'écriture européens. Cependant, les utilisateurs peuvent configurer les polices par défaut et fournir leurs propres polices personnalisées. Voir Personnalisation des propriétés de texte pour plus de détails et Texte avec des glyphes non latins en particulier pour les glyphes non pris en charge par DejaVu Sans.

Matplotlib fournit également une option pour décharger le rendu de texte vers un moteur TeX ( usetex=True), voir Rendu de texte avec LaTeX .

Polices en PDF et PostScript #

Les polices ont une longue (et parfois incompatible) histoire en informatique, conduisant à différentes plates-formes prenant en charge différents types de polices. En pratique, il existe 3 types de spécifications de polices prises en charge par Matplotlib (en plus des "polices de base" en pdf qui sont expliquées plus loin dans le guide) :

Type de polices #

Type 1 (PDF)

Type 3 (PDF/PS)

TrueType (PDF)

L'un des types les plus anciens, introduit par Adobe

Semblable au type 1 en termes d'introduction

Plus récent que les types précédents, couramment utilisé aujourd'hui, introduit par Apple

Sous-ensemble restreint de PostScript, les charstrings sont en bytecode

Langage PostScript complet, permet d'intégrer du code arbitraire (en théorie, même de rendre les fractales lors de la pixellisation !)

Incluez une machine virtuelle capable d'exécuter du code !

Ces polices prennent en charge l'indication de police

Ne prend pas en charge l'indication de police

Hinting pris en charge (la machine virtuelle traite les "indices")

Non-sous-ensemble via Matplotlib

Sous-ensemble via le module externe ttconv

Sous-ensemble via le module externe fonttools

REMARQUE : Adobe désactivera la prise en charge de la création avec les polices Type 1 en janvier 2023. Pour en savoir plus, cliquez ici.

Autres spécifications de police prises en charge par Matplotlib :

  • Polices de type 42 (PS) :

  • Polices OpenType :

    • OpenType est une nouvelle norme pour les polices de caractères numériques, développée conjointement par Adobe et Microsoft

    • Contiennent généralement un jeu de caractères beaucoup plus important !

    • Support limité avec Matplotlib

Sous-ensemble de police #

Les formats PDF et PostScript prennent en charge l'incorporation de polices dans les fichiers permettant au programme d'affichage de restituer correctement le texte, indépendamment des polices installées sur l'ordinateur de l'utilisateur et sans qu'il soit nécessaire de pré-rastériser le texte. Cela garantit que si la sortie est agrandie ou redimensionnée, le texte ne devient pas pixélisé. Cependant, l'intégration de polices complètes dans le fichier peut entraîner des fichiers de sortie volumineux, en particulier avec des polices comportant de nombreux glyphes telles que celles prenant en charge CJK (chinois/japonais/coréen).

La solution à ce problème consiste à créer un sous-ensemble des polices utilisées dans le document et à n'intégrer que les glyphes réellement utilisés. Cela permet d'obtenir à la fois du texte vectoriel et de petites tailles de fichiers. Le calcul du sous-ensemble de la police requise et l'écriture de la nouvelle police (réduite) sont tous deux des problèmes complexes et Matplotlib s'appuie donc sur fontTools et un fork de ttconv .

Actuellement, les polices Type 3, Type 42 et TrueType sont des sous-ensembles. Les polices de type 1 ne le sont pas.

Polices de base #

En plus de la possibilité d'intégrer des polices, dans le cadre des spécifications PostScript et PDF , il existe 14 polices principales dont les visualiseurs conformes doivent s'assurer qu'ils sont disponibles. Si vous limitez votre document à ces polices uniquement, vous n'avez pas besoin d'intégrer d'informations sur les polices dans le document, mais vous obtenez toujours du texte vectoriel.

Ceci est particulièrement utile pour générer des documents vraiment légers . :

# trigger core fonts for PDF backend
plt.rcParams["pdf.use14corefonts"] = True
# trigger core fonts for PS backend
plt.rcParams["ps.useafm"] = True

chars = "AFM ftw!"
fig, ax = plt.subplots()
ax.text(0.5, 0.5, chars)

fig.savefig("AFM_PDF.pdf", format="pdf")
fig.savefig("AFM_PS.ps", format="ps)

Polices en SVG #

Le texte peut sortir en SVG de deux manières contrôlées par rcParams["svg.fonttype"](par défaut : 'path') :

  • comme un chemin ( 'path') dans le SVG

  • sous forme de chaîne dans le SVG avec un style de police sur l'élément ( 'none')

Lors de l'enregistrement via 'path'Matplotlib, le chemin des glyphes utilisés comme chemins vectoriels sera calculé et écrit ceux-ci dans la sortie. L'avantage est que le SVG aura le même aspect sur tous les ordinateurs, quelles que soient les polices installées. Cependant, le texte ne sera pas modifiable après coup. En revanche, l'enregistrement avec 'none'entraînera des fichiers plus petits et le texte apparaîtra directement dans le balisage. Cependant, l'apparence peut varier en fonction du visualiseur SVG et des polices disponibles.

Polices dans Agg #

Pour sortir du texte aux formats raster via Agg, Matplotlib s'appuie sur FreeType . Étant donné que le rendu exact des glyphes change entre les versions de FreeType, nous épinglons à une version spécifique pour nos tests de comparaison d'images.

Comment Matplotlib sélectionne les polices #

L'utilisation interne d'une police dans Matplotlib est un processus en trois étapes :

  1. un FontPropertiesobjet est créé (explicitement ou implicitement)

  2. en fonction de l' FontPropertiesobjet sur lequel les méthodes FontManagersont utilisées pour sélectionner la "meilleure" police la plus proche que Matplotlib connaît (sauf pour le 'none'mode SVG).

  3. le proxy Python pour l'objet font est utilisé par le code backend pour restituer le texte - les détails exacts dépendent du backend via font_manager.get_font.

L'algorithme pour sélectionner la "meilleure" police est une version modifiée de l'algorithme spécifié par les spécifications CSS1 qui est utilisé par les navigateurs Web. Cet algorithme prend en compte le nom de la famille de la police (par exemple "Arial", "Noto Sans CJK", "Hack", ...), la taille, le style et le poids. En plus des noms de famille qui correspondent directement aux polices, il existe cinq "noms de famille de polices génériques" ( serif, monospace, fantasy, cursive et sans-serif) qui seront mappés en interne à l'une des polices d'un ensemble.

Actuellement, l'API publique pour effectuer l'étape 2 est FontManager.findfont(et cette méthode sur l' FontManagerinstance globale est aliasée au niveau du module en tant que font_manager.findfont), qui ne trouvera qu'une seule police et renverra le chemin absolu vers la police sur le système de fichiers.

Police de secours #

Il n'y a pas de police qui couvre tout l'espace Unicode, il est donc possible pour les utilisateurs d'exiger un mélange de glyphes qui ne peuvent pas être satisfaits à partir d'une seule police. Bien qu'il ait été possible d'utiliser plusieurs polices dans une figure, sur des Textinstances distinctes, il n'était pas possible auparavant d'utiliser plusieurs polices dans la même Textinstance (comme le fait un navigateur Web). À partir de Matplotlib 3.6, les backends Agg, SVG, PDF et PS "se replieront" sur plusieurs polices en une seule Textinstance :

fig, ax = plt.subplots()
ax.text(
    .5, .5, "There are 几个汉字 in between!",
    family=['DejaVu Sans', 'WenQuanYi Zen Hei'],
    ha='center'
)

( Code source , png )

../../_images/fonts-1.png

La chaîne "Il y a 几个汉字 entre les deux !" rendu avec 2 polices. #

En interne, cela est implémenté en définissant la "famille de polices" sur les FontPropertiesobjets sur une liste de familles de polices. Une API (actuellement) privée extrait une liste de chemins vers toutes les polices trouvées, puis construit un ft2font.FT2Fontobjet unique qui connaît toutes les polices. Chaque glyphe de la chaîne est rendu à l'aide de la première police de la liste qui contient ce glyphe.

Une majorité de ce travail a été réalisé par Aitik Gupta soutenu par Google Summer of Code 2021.