Matplotlib 中的字体#

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

Matplotlib 还提供了将文本渲染卸载到 TeX 引擎的选项 ( usetex=True),请参阅Text rendering with LaTeX

PDF 和 PostScript 中的字体#

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

字体类型#

类型 1 (PDF)

类型 3 (PDF/PS)

TrueType (PDF)

Adobe 推出的最古老的类型之一

在介绍方面与Type 1相似

比以前的类型更新,今天普遍使用,由 Apple 推出

PostScript 的受限子集,字符字符串在字节码中

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

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

这些字体支持字体提示

不支持字体提示

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

通过 Matplotlib 进行非子集化

通过外部模块ttconv 子集

通过外部模块fonttools 子集

注意:Adobe 将在 2023 年 1 月禁用对使用 Type 1 字体进行创作的支持。在此处阅读更多信息。

Matplotlib 支持的其他字体规范:

字体子集#

PDF 和 PostScript 格式支持在文件中嵌入字体,从而使显示程序能够正确呈现文本,而与查看者计算机上安装的字体无关,并且无需对文本进行预光栅化。这确保了如果输出被缩放或调整大小,文本不会变得像素化。但是,在文件中嵌入完整字体会导致输出文件过大,尤其是具有许多字形的字体,例如支持 CJK(中文/日文/韩文)的字体。

这个问题的解决方案是对文档中使用的字体进行子集化,并且只嵌入实际使用的字形。这将获得矢量文本和小文件大小。计算所需字体的子集和编写新的(简化的)字体都是复杂的问题,因此 Matplotlib 依赖于 fontTools和ttconv的一个vendored fork 。

目前 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'

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

  • 作为 SVG 中的字符串,元素 ( 'none')上有字体样式

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

Agg 中的字体#

为了通过 Agg 将文本输出为光栅格式,Matplotlib 依赖于FreeType。因为字形的准确呈现在 FreeType 版本之间会发生变化,所以我们将其固定到特定版本以进行图像比较测试。

Matplotlib 如何选择字体#

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

  1. 创建一个FontProperties对象(显式或隐式)

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

  3. 后端代码使用字体对象的 Python 代理来呈现文本——确切的细节取决于后端通过font_manager.get_font.

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

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

字体回退#

没有一种字体可以覆盖整个 Unicode 空间,因此用户可能需要混合使用单一字体无法满足的字形。虽然可以在一个图形中使用多种字体,但在不同的 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', 'WenQuanYi Zen Hei'],
    ha='center'
)

源代码png

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

字符串“中间有几个汉字!” 用 2 种字体渲染。#

在内部,这是通过将 FontProperties对象上的“字体系列”设置为字体系列列表来实现的。一个(当前)私有 API 提取找到的所有字体的路径列表,然后构造一个ft2font.FT2Font知道所有字体的单个对象。字符串的每个字形都使用列表中包含该字形的第一个字体呈现。

这项工作的大部分由 Google Summer of Code 2021 支持的 Aitik Gupta 完成。