Matplotlib 中的字体#

Matplotlib 需要字体才能使用其文本引擎,其中一些字体与安装一起提供。默认字体是 DejaVu Sans,它涵盖了大多数欧洲书写系统。但是,用户可以配置默认字体,并提供自己的自定义字体。有关详细信息,请参见 自定义文本属性,特别是 带有非拉丁字符的文本,用于 DejaVu Sans 不支持的字符。

Matplotlib 还提供了一个选项,可以将文本渲染卸载到 TeX 引擎 (usetex=True),请参阅 使用 LaTeX 进行文本渲染.

PDF 和 PostScript 中的字体#

字体在计算领域有着悠久(有时不兼容)的历史,导致不同的平台支持不同类型的字体。实际上,Matplotlib 支持三种字体规范(除了稍后在本指南中解释的 pdf“核心字体”)。

字体类型#

Type 1 (PDF)

Type 3 (PDF/PS)

TrueType (PDF)

最早的类型之一,由 Adobe 引入

与 Type 1 在引入方面类似

比以前的类型更新,如今广泛使用,由 Apple 引入

PostScript 的受限子集,字符字符串以字节码形式存在

完整的 PostScript 语言,允许嵌入任意代码(理论上,甚至可以在光栅化时渲染分形!)

包含一个可以执行代码的虚拟机!

这些字体支持字体提示

不支持字体提示

支持提示(虚拟机处理“提示”)

通过 Matplotlib 不进行子集化

通过外部模块 ttconv 进行子集化

通过外部模块 fontTools 进行子集化

注意

Adobe 于 2023 年 1 月 停止 支持使用 Type 1 字体进行创作。

Matplotlib 支持的其他字体规范

  • Type 42 字体 (PS)

  • OpenType 字体

    • OpenType 是数字字体的新标准,由 Adobe 和 Microsoft 共同开发

    • 通常包含更大的字符集!

    • Matplotlib 的支持有限

字体子集化#

PDF 和 PostScript 格式支持在文件中嵌入字体,允许显示程序正确渲染文本,与查看者计算机上安装的字体无关,也不需要预先光栅化文本。这确保了如果输出被缩放或调整大小,文本不会变得像素化。但是,在文件中嵌入完整的字体会导致输出文件过大,特别是对于支持 CJK(中/日/韩)的具有许多字形的字体。

解决这个问题的方法是将文档中使用的字体进行子集化,只嵌入实际使用的字形。这样既可以获得矢量文本,又能获得较小的文件大小。计算所需的字体子集和写入新的(缩减的)字体都是复杂的问题,因此 Matplotlib 依赖于 fontTools 和一个 vendored 的 ttconv 分支。

目前,Type 3、Type 42 和 TrueType 字体会被子集化。Type 1 字体不会。

核心字体#

除了嵌入字体的功能之外,作为 PostScriptPDF 规范 的一部分,有 14 种核心字体,符合规范的查看器必须确保这些字体可用。如果您将文档限制为仅使用这些字体,则无需在文档中嵌入任何字体信息,但仍然可以获得矢量文本。

这对于生成非常轻量级的文档特别有用。

# 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")

SVG 中的字体#

文本可以通过两种方式输出到 SVG,这由 rcParams["svg.fonttype"](默认值:'path')控制。

  • 作为 SVG 中的路径 ('path')

  • 作为 SVG 中的字符串,并在元素上进行字体样式设置 ('none')

当使用 'path' 保存时,Matplotlib 会将用作矢量路径的字形路径计算出来并写入输出。这样做的好处是,SVG 在所有计算机上看起来都一样,与安装的字体无关。但是,文本在保存后将无法编辑。相反,使用 'none' 保存将导致文件更小,并且文本将直接出现在标记中。但是,外观可能会根据 SVG 查看器和可用的字体而有所不同。

Agg 中的字体#

为了通过 Agg 将文本输出到光栅格式,Matplotlib 依赖于 FreeType。由于字形的精确渲染在 FreeType 版本之间有所不同,因此我们在图像比较测试中固定到特定版本。

Matplotlib 如何选择字体#

在内部,在 Matplotlib 中使用字体是一个三步过程

  1. a FontProperties 对象被创建(显式或隐式)

  2. 基于 FontProperties 对象,FontManager 上的方法用于选择 Matplotlib 知道的最近的“最佳”字体(除了 SVG 的 'none' 模式)。

  3. 字体的 Python 代理由后端代码用于渲染文本 - 确切的细节取决于后端,通过 font_manager.get_font

选择“最佳”字体的算法是 CSS1 规范中指定的算法的修改版本,该规范由 Web 浏览器使用。该算法考虑了字体系列名称(例如“Arial”、“Noto Sans CJK”、“Hack”等)、大小、样式和粗细。除了直接映射到字体的字体系列名称之外,还有五个“通用字体系列名称”(serif、monospace、fantasy、cursive 和 sans-serif),它们将在内部映射到一组字体中的任何一个。

目前,执行步骤 2 的公共 API 是 FontManager.findfont(以及全局 FontManager 实例上的该方法在模块级别别名为 font_manager.findfont),它只会找到一个字体并返回文件系统上该字体的绝对路径。

字体回退#

没有一种字体可以覆盖整个 Unicode 空间,因此用户可能需要混合使用无法从单个字体中满足的字形。虽然以前可以在一个 Figure 中使用多个字体,在不同的 Text 实例中,但以前无法在一个 Text 实例中使用多个字体(就像 Web 浏览器一样)。从 Matplotlib 3.6 开始,Agg、SVG、PDF 和 PS 后端将在单个 Text 实例中“回退”到多个字体。

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

(Source code, 2x.png, png)

字符串 "There are 几个汉字 in between!" 使用了两种字体渲染。

在内部,这是通过将 FontProperties 对象的 "font family" 设置为字体系列列表来实现的。一个(目前)私有的 API 会提取所有找到的字体的路径列表,然后构建一个包含所有字体的单个 ft2font.FT2Font 对象。字符串的每个字形都使用列表中包含该字形的第一个字体进行渲染。

这项工作的大部分是由 Aitik Gupta 完成的,并得到了 Google Summer of Code 2021 的支持。

由 Sphinx-Gallery 生成的图库