Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit dd90403

Browse files
committed
DOC: update and reword fonts.rst
Account for the fact that font fallback is now merged and re-organize slightly.
1 parent 6f0d7f5 commit dd90403

File tree

2 files changed

+138
-127
lines changed

2 files changed

+138
-127
lines changed

doc/users/explain/fonts.rst

Lines changed: 136 additions & 127 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+
PDF and postscript support embedded fonts in the output files allowing the
70+
display program to correctly render the text, independent of what fonts are
71+
installed on the viewer's computer, 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 a
81+
vendored fork of `ttconv <https://github.com/sandflow/ttconv>`_ and `fontTools
82+
<https://fonttools.readthedocs.io/en/latest/>`__.
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,70 +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.
125-
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.
131-
132-
This also violates the *what-you-see-is-what-you-get* feature of Matplotlib.
133-
134-
Are we reinventing the wheel?
135-
-----------------------------
136-
Internally, a feasible response to the question of 'reinventing the
137-
wheel would be, well, Yes *and No*. The font-matching algorithm used
138-
by Matplotlib has been *inspired* by web browsers, more specifically,
139-
`CSS Specifications <http://www.w3.org/TR/1998/REC-CSS2-19980512/>`_.
140-
141-
Currently, the simplest way (and the only way) to tell Matplotlib what fonts
142-
you want it to use for your document is via the **font.family** rcParam,
143-
see :doc:`Customizing text properties </tutorials/text/text_props>`.
144-
145-
This is similar to how one tells a browser to use multiple font families
146-
(specified in their order of preference) for their HTML webpages. By using
147-
**font-family** in their stylesheet, users can essentially trigger a very
148-
useful feature provided by browers, known as Font-Fallback. For example, the
149-
following snippet in an HTML markup would:
150-
151-
.. code-block:: html
152-
153-
<style>
154-
someTag {
155-
font-family: Arial, Helvetica, sans-serif;
156-
}
157-
</style>
158-
159-
<!-- somewhere in the main body -->
160-
<someTag>
161-
some text
162-
</someTag>
163-
164-
165-
For every character/glyph in *"some text"*, the browser will iterate through
166-
the whole list of font-families, and check whether that character/glyph is
167-
available in that font-family. As soon as a font is found which has the
168-
required glyph(s), the browser uses that font to render that character, and
169-
subsequently moves on to the next character.
170-
171-
How does Matplotlib achieve this?
172-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
173-
Currently, Matplotlib can't render a multi-font document. It was initially
174-
only designed to use a **single font** throughout the document, i.e., no matter
175-
how many families you pass to **font.family** rcParam, Matplotlib would use the
176-
very first font it's able to find on your system, and try to render all your
177-
characters/glyphs from that *and only that* font.
178-
179-
.. note::
180-
This is, because the internal font matching was written/adapted
181-
from a very old `CSS1 spec <http://www.w3.org/TR/1998/REC-CSS2-19980512/>`_,
182-
**written in 1998**!
183-
184-
However, allowing multiple fonts for a single document (also enabling
185-
Font-Fallback) is one of the goals for 2021's Google Summer of Code project.
186-
187-
`Read more on Matplotblog <https://matplotlib.org/matplotblog/>`_!
188112

113+
Fonts in SVG
114+
------------
115+
116+
Text can output to SVG in two ways controlled by the :rc:`svg.fonttype`
117+
rcparam:
118+
119+
- as a path (``'path'``) in the SVG
120+
- as string in the SVG with font styling on the element (``'none'``)
121+
122+
123+
When saving via ``'path'`` Matplotlib will compute the path of the glyphs used
124+
as vector paths and write those to the output. The advantage of this is that
125+
the SVG will look the same on all computers independent of what fonts are
126+
installed. However the text will not be editable after the fact.
127+
In contrast saving with ``'none'`` will result in smaller files and the
128+
text will appear directly in the markup. However, the appearance may vary
129+
based on the SVG viewer and what fonts are available.
130+
131+
Fonts in Agg
132+
------------
133+
134+
To output text to raster formats via Agg Matplotlib relies on `FreeType
135+
<https://www.freetype.org/>`_. Because the exactly rendering of the glyphs
136+
changes between FreeType versions we pin to a specific version for our image
137+
comparison tests.
138+
139+
140+
How Matplotlib selects fonts
141+
----------------------------
142+
143+
Internally using a Font in Matplotlib is a three step process:
144+
145+
1. a `.FontProperties` object is created (explicitly or implicitly)
146+
2. based on the `.FontProperties` object the methods on `.FontManager` are used
147+
to select the closest the "best" font Matplotlib is aware of (except for
148+
``'none'`` mode of SVG).
149+
3. the Python proxy for the font object is used by the backend code to render
150+
the text -- the exact details depend on the backend via `.font_manager.get_font`.
151+
152+
The algorithm to select the "best" font is a modified version of the algorithm
153+
specified by the `CSS1 Specifications
154+
<http://www.w3.org/TR/1998/REC-CSS2-19980512/>`_ which is used by web browsers.
155+
This algorithm takes into account the font family name (e.g. "Arial", "Noto
156+
Sans CJK", "Hack", ...), the size, style, and weight. In addition to family
157+
names that map directly to fonts there are five "generic font family names" (
158+
serif, monospace, fantasy, cursive, and sans-serif) that will internally be
159+
mapped to any one of a set of fonts.
160+
161+
Currently the public API for doing step 2 is `.FontManager.findfont` (and that
162+
method on the global `.FontManager` instance is aliased at the module level as
163+
`.font_manager.findfont`) will only find a single font and return the absolute
164+
path to the font on the filesystem.
165+
166+
Font Fallback
167+
-------------
168+
169+
There is no font that covers the unicode space thus it is possible for the
170+
users to require a mix of glyphs that can not be satisfied from a single font.
171+
While it has been possible to use multiple fonts within a Figure, on distinct
172+
`.Text` instances, it was not previous possible to use multiple fonts in the
173+
same `.Text` instance (as a web browser does). As of Matplotlib 3.6 the Agg,
174+
SVG, PDF, and PS backends will "fallback" through multiple fonts in a single
175+
`.Text` instance:
176+
177+
178+
.. plot::
179+
:include-source:
180+
:caption: The string "There are 几个汉字 in between!" rendered with 2 fonts.
181+
182+
fig, ax = plt.subplots()
183+
ax.text(
184+
.5, .5, "There are 几个汉字 in between!",
185+
family=['DejaVu Sans', 'WenQuanYi Zen Hei'],
186+
ha='center'
187+
)
188+
189+
190+
Internally this is implemented by setting The "font family" on
191+
`.FontProperties` objects can be specified as a list of font families. Using a
192+
(currently) private API we get a list the paths to all of the fonts found.
193+
During rendering for each glyph that list is walked until a font that has the
194+
glyph is found or all of the fonts are exhausted.
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

Lines 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)