Kiva Font Management

Kiva’s FontManager code originated in the Matplotlib code base. Until version 5.1.0 it was largely unchanged. From 5.1.0 forward, it has been refactored to emphasize that it’s an internal detail which should be avoided by 3rd party code.

This document aims to describe the structure of the code circa version 5.1.0.

Overview

The basic function of FontManager is to provide a findfont() method which can be called by the Font class to resolve a font name or font file location on the system where the code is running.

To accomplish this, it:

  1. Scans the file system for files with known font file extensions (.ttf, .ttc, .otf, .afm) [scan_system_fonts()]

  2. Examines all the font files identified in the first step and extracts their metadata using fonttools. [create_font_database()]

  3. The FontManager is then ready to be used. Font instances call findfont() on the global FontManager singleton as needed.

Because scanning a system for available fonts is quite an expensive operation, FontManager stores a pickled copy of its global singleton in a cache file. And because pickle is notoriously brittle, the font manager has a __version__ attribute must be incremented any time the attributes or their classes (FontEntry, FontDatabase, etc.) change. The FontManager singleton is loaded on-demand by the first call to default_font_manager().

Font Resolution

The findfont() method uses a somewhat complex process for finding the best match to a given font query.

  1. If a directory keyword argument was passed, only fonts whose files are children of directory will be checked. If none match, a default font is returned.

  2. If a directory is not specified, the search is first narrowed by the font family (or families) designated by the query. This is possible because the match scoring algorithm gives very bad scores to fonts whose family does not match the query.

  3. A score is computed for each font using the scoring functions [score_family(), score_size(), score_stretch(), score_style(), score_variant(), and score_weight()]

  4. If the score meets a certain threshold, the matching font is returned. Otherwise a default is returned.

  5. If the query was successful, it is added to a local query cache which will avoid the scoring process if a matching query is later performed again.