Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9e6afd6

Browse filesBrowse files
authored
Merge pull request #23621 from tacaswell/family-parsing
DOC: update and extend fonts explanation
2 parents 2da3401 + 4496629 commit 9e6afd6
Copy full SHA for 9e6afd6

File tree

Expand file treeCollapse file tree

2 files changed

+137
-70
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+137
-70
lines changed

‎doc/users/explain/fonts.rst

Copy file name to clipboard
+135-70Lines changed: 135 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
.. redirect-from:: /users/fonts
22

3-
Fonts in Matplotlib text engine
4-
===============================
3+
Fonts in Matplotlib
4+
===================
55

66
Matplotlib needs fonts to work with its text engine, some of which are shipped
7-
alongside the installation. However, users can configure the default fonts, or
8-
even provide their own custom fonts! For more details, see :doc:`Customizing
9-
text properties </tutorials/text/text_props>`.
7+
alongside the installation. The default font is `DejaVu Sans
8+
<https://dejavu-fonts.github.io>`_ which covers most European writing systems.
9+
However, users can configure the default fonts, and provide their own custom
10+
fonts. See :doc:`Customizing text properties </tutorials/text/text_props>` for
11+
details and :ref:`font-nonlatin` in particular for glyphs not supported by
12+
DejaVu Sans.
1013

11-
However, Matplotlib also provides an option to offload text rendering to a TeX
12-
engine (``usetex=True``),
13-
see :doc:`Text rendering with LaTeX </tutorials/text/usetex>`.
14+
Matplotlib also provides an option to offload text rendering to a TeX engine
15+
(``usetex=True``), see :doc:`Text rendering with LaTeX
16+
</tutorials/text/usetex>`.
1417

15-
Font specifications
16-
-------------------
17-
Fonts have a long and sometimes incompatible history in computing, leading to
18-
different platforms supporting different types of fonts. In practice, there are
19-
3 types of font specifications Matplotlib supports (in addition to 'core
20-
fonts', more about which is explained later in the guide):
18+
Fonts in PDF and PostScript
19+
---------------------------
20+
21+
Fonts have a long (and sometimes incompatible) history in computing, leading to
22+
different platforms supporting different types of fonts. In practice, there
23+
are 3 types of font specifications Matplotlib supports (in addition to 'core
24+
fonts' in pdf which is explained later in the guide):
2125

2226
.. list-table:: Type of Fonts
2327
:header-rows: 1
@@ -37,20 +41,19 @@ fonts', more about which is explained later in the guide):
3741
- Hinting supported (virtual machine processes the "hints")
3842
* - Non-subsetted through Matplotlib
3943
- Subsetted via external module `ttconv <https://github.com/sandflow/ttconv>`_
40-
- Subsetted via external module `fonttools <https://github.com/fonttools/fonttools>`_
44+
- Subsetted via external module `fonttools <https://github.com/fonttools/fonttools>`__
4145

4246
NOTE: Adobe will disable support for authoring with Type 1 fonts in
4347
January 2023. `Read more here. <https://helpx.adobe.com/fonts/kb/postscript-type-1-fonts-end-of-support.html>`_
4448

45-
Special mentions
46-
^^^^^^^^^^^^^^^^
49+
4750
Other font specifications which Matplotlib supports:
4851

4952
- Type 42 fonts (PS):
5053

5154
- PostScript wrapper around TrueType fonts
5255
- 42 is the `Answer to Life, the Universe, and Everything! <https://en.wikipedia.org/wiki/Answer_to_Life,_the_Universe,_and_Everything>`_
53-
- Matplotlib uses an external library called `fonttools <https://github.com/fonttools/fonttools>`_
56+
- Matplotlib uses an external library called `fonttools <https://github.com/fonttools/fonttools>`__
5457
to subset these types of fonts
5558

5659
- OpenType fonts:
@@ -60,50 +63,37 @@ Other font specifications which Matplotlib supports:
6063
- Generally contain a much larger character set!
6164
- Limited Support with Matplotlib
6265

63-
Subsetting
64-
----------
65-
Matplotlib is able to generate documents in multiple different formats. Some of
66-
those formats (for example, PDF, PS/EPS, SVG) allow embedding font data in such
67-
a way that when these documents are visually scaled, the text does not appear
68-
pixelated.
69-
70-
This can be achieved by embedding the *whole* font file within the
71-
output document. However, this can lead to very large documents, as some
72-
fonts (for instance, CJK - Chinese/Japanese/Korean fonts) can contain a large
73-
number of glyphs, and thus their embedded size can be quite huge.
74-
75-
Font Subsetting can be used before generating documents, to embed only the
76-
*required* glyphs within the documents. Fonts can be considered as a collection
77-
of glyphs, so ultimately the goal is to find out *which* glyphs are required
78-
for a certain array of characters, and embed only those within the output.
79-
80-
.. note::
81-
The role of subsetter really shines when we encounter characters like **ä**
82-
(composed by calling subprograms for **a** and **¨**); since the subsetter
83-
has to find out *all* such subprograms being called by every glyph included
84-
in the subset, this is a generally difficult problem!
85-
86-
Luckily, Matplotlib uses a fork of an external dependency called
87-
`ttconv <https://github.com/sandflow/ttconv>`_, which helps in embedding and
88-
subsetting font data. (however, recent versions have moved away from ttconv to
89-
pure Python for certain types: for more details visit
90-
`these <https://github.com/matplotlib/matplotlib/pull/18370>`_, `links <https://github.com/matplotlib/matplotlib/pull/18181>`_)
91-
92-
| *Type 1 fonts are still non-subsetted* through Matplotlib. (though one will encounter these mostly via *usetex*/*dviread* in PDF backend)
93-
| **Type 3 and Type 42 fonts are subsetted**, with a fair amount of exceptions and bugs for the latter.
94-
95-
What to use?
96-
------------
97-
Practically, most fonts that are readily available on most operating systems or
98-
are readily available on the internet to download include *TrueType fonts* and
99-
its "extensions" such as MacOS-resource fork fonts and the newer OpenType
100-
fonts.
66+
Font Subsetting
67+
~~~~~~~~~~~~~~~
68+
69+
The PDF and PostScript formats support embedding fonts in files allowing the
70+
display program to correctly render the text, independent of what fonts are
71+
installed on the viewer's computer and without the need to pre-rasterize the text.
72+
This ensures that if the output is zoomed or resized the text does not become
73+
pixelated. However, embedding full fonts in the file can lead to large output
74+
files, particularly with fonts with many glyphs such as those that support CJK
75+
(Chinese/Japanese/Korean).
76+
77+
The solution to this problem is to subset the fonts used in the document and
78+
only embed the glyphs actually used. This gets both vector text and small
79+
files sizes. Computing the subset of the font required and writing the new
80+
(reduced) font are both complex problem and thus Matplotlib relies on
81+
`fontTools <https://fonttools.readthedocs.io/en/latest/>`__ and a vendored fork
82+
of `ttconv <https://github.com/sandflow/ttconv>`_.
83+
84+
Currently Type 3, Type 42, and TrueType fonts are subseted. Type 1 fonts are not.
85+
86+
87+
Core Fonts
88+
~~~~~~~~~~
10189

102-
PS and PDF backends provide support for yet another type of fonts, which remove
103-
the need of subsetting altogether! These are called **Core Fonts**, and
104-
Matplotlib calls them via the keyword **AFM**; all that is supplied from
105-
Matplotlib to such documents are font metrics (specified in AFM format), and it
106-
is the job of the viewer applications to supply the glyph definitions.
90+
In addition to the ability to embed fonts, as part of the `PostScript
91+
<https://en.wikipedia.org/wiki/PostScript_fonts#Core_Font_Set>`_ and `PDF
92+
specification
93+
<https://docs.oracle.com/cd/E96927_01/TSG/FAQ/What%20are%20the%2014%20base%20fonts%20distributed%20with%20Acroba.html>`_
94+
there are 14 Core Font that compliant viewers must ensure are available. If
95+
you restrict your document to only these fonts you do not have to embed any
96+
font information in the document but still get vector text.
10797

10898
This is especially helpful to generate *really lightweight* documents.::
10999

@@ -119,14 +109,89 @@ This is especially helpful to generate *really lightweight* documents.::
119109
fig.savefig("AFM_PDF.pdf", format="pdf")
120110
fig.savefig("AFM_PS.ps", format="ps)
121111

122-
.. note::
123-
These core fonts are limited to PDF and PS backends only; they can not be
124-
rendered in other backends.
125112

126-
Another downside to this is that while the font metrics are standardized,
127-
different PDF viewer applications will have different fonts to render these
128-
metrics. In other words, the **output might look different on different
129-
viewers**, as well as (let's say) Windows and Linux, if Linux tools included
130-
free versions of the proprietary fonts.
113+
Fonts in SVG
114+
------------
115+
116+
Text can output to SVG in two ways controlled by :rc:`svg.fonttype`:
117+
118+
- as a path (``'path'``) in the SVG
119+
- as string in the SVG with font styling on the element (``'none'``)
120+
121+
122+
When saving via ``'path'`` Matplotlib will compute the path of the glyphs used
123+
as vector paths and write those to the output. The advantage of this is that
124+
the SVG will look the same on all computers independent of what fonts are
125+
installed. However the text will not be editable after the fact.
126+
In contrast saving with ``'none'`` will result in smaller files and the
127+
text will appear directly in the markup. However, the appearance may vary
128+
based on the SVG viewer and what fonts are available.
129+
130+
Fonts in Agg
131+
------------
131132

132-
This also violates the *what-you-see-is-what-you-get* feature of Matplotlib.
133+
To output text to raster formats via Agg, Matplotlib relies on `FreeType
134+
<https://www.freetype.org/>`_. Because the exact rendering of the glyphs
135+
changes between FreeType versions we pin to a specific version for our image
136+
comparison tests.
137+
138+
139+
How Matplotlib selects fonts
140+
----------------------------
141+
142+
Internally using a Font in Matplotlib is a three step process:
143+
144+
1. a `.FontProperties` object is created (explicitly or implicitly)
145+
2. based on the `.FontProperties` object the methods on `.FontManager` are used
146+
to select the closest "best" font Matplotlib is aware of (except for
147+
``'none'`` mode of SVG).
148+
3. the Python proxy for the font object is used by the backend code to render
149+
the text -- the exact details depend on the backend via `.font_manager.get_font`.
150+
151+
The algorithm to select the "best" font is a modified version of the algorithm
152+
specified by the `CSS1 Specifications
153+
<http://www.w3.org/TR/1998/REC-CSS2-19980512/>`_ which is used by web browsers.
154+
This algorithm takes into account the font family name (e.g. "Arial", "Noto
155+
Sans CJK", "Hack", ...), the size, style, and weight. In addition to family
156+
names that map directly to fonts there are five "generic font family names" (
157+
serif, monospace, fantasy, cursive, and sans-serif) that will internally be
158+
mapped to any one of a set of fonts.
159+
160+
Currently the public API for doing step 2 is `.FontManager.findfont` (and that
161+
method on the global `.FontManager` instance is aliased at the module level as
162+
`.font_manager.findfont`), which will only find a single font and return the absolute
163+
path to the font on the filesystem.
164+
165+
Font Fallback
166+
-------------
167+
168+
There is no font that covers the entire Unicode space thus it is possible for the
169+
users to require a mix of glyphs that can not be satisfied from a single font.
170+
While it has been possible to use multiple fonts within a Figure, on distinct
171+
`.Text` instances, it was not previous possible to use multiple fonts in the
172+
same `.Text` instance (as a web browser does). As of Matplotlib 3.6 the Agg,
173+
SVG, PDF, and PS backends will "fallback" through multiple fonts in a single
174+
`.Text` instance:
175+
176+
177+
.. plot::
178+
:include-source:
179+
:caption: The string "There are 几个汉字 in between!" rendered with 2 fonts.
180+
181+
fig, ax = plt.subplots()
182+
ax.text(
183+
.5, .5, "There are 几个汉字 in between!",
184+
family=['DejaVu Sans', 'WenQuanYi Zen Hei'],
185+
ha='center'
186+
)
187+
188+
189+
Internally this is implemented by setting The "font family" on
190+
`.FontProperties` objects to a list of font families. A (currently)
191+
private API extracts a list of paths to all of the fonts found and then
192+
constructs a single `.ft2font.FT2Font` object that is aware of all of the fonts.
193+
Each glyph of the string is rendered using the first font in the list that
194+
contains that glyph.
195+
196+
A majority of this work was done by Aitik Gupta supported by Google Summer of
197+
Code 2021.

‎tutorials/text/text_props.py

Copy file name to clipboardExpand all lines: tutorials/text/text_props.py
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@
215215
# matplotlib.rcParams['font.family'] = ['Family1', 'SerifFamily1', 'SerifFamily2', 'Family2']
216216
#
217217
#
218+
# .. _font-nonlatin:
219+
#
218220
# Text with non-latin glyphs
219221
# ==========================
220222
#

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.