Matplotlib 中的字体#

Matplotlib 需要字体才能配合其文本引擎工作,其中一些字体随安装包一起提供.默认字体是 DejaVu Sans ,它涵盖了大多数欧洲书写系统.但是,用户可以配置默认字体,并提供他们自己的自定义字体.有关详细信息,请参阅 Customizing text properties ,特别是 包含非拉丁字符的文本 ,了解 DejaVu Sans 不支持的字形.

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

PDF 和 PostScript 中的字体#

字体在计算领域有着悠久(有时是不兼容的)历史,导致不同的平台支持不同类型的字体.实际上,Matplotlib 支持三种字体规范(除了 pdf 'core fonts',这将在本指南后面解释):

字体类型#

Type 1 (PDF)

Type 3 (PDF/PS)

TrueType (PDF)

最古老的类型之一,由 Adobe 引入

在引入方面与 Type 1 类似

比以前的类型更新,今天常用,由 Apple 引入

PostScript 的受限子集,charstrings 采用字节码

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

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

这些字体支持字体微调

不支持字体微调

支持微调(虚拟机处理"提示")

非通过 Matplotlib 进行子集化

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

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

备注

Adobe 在 2023 年 1 月禁用了__对使用 Type 1 字体进行创作的支持.

Matplotlib 支持的其他字体规范:

  • Type 42 字体 (PS):

  • OpenType 字体:

    • OpenType 是 Adobe 和 Microsoft 联合开发的一种新的数字字体标准

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

    • Matplotlib 的支持有限

字体子集化#

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

解决此问题的方法是子集化文档中使用的字体,并且仅嵌入实际使用的字形.这样既可以获得矢量文本,又可以获得小的文件大小.计算所需的字体子集并写入新的(缩小的)字体都是复杂的问题,因此 Matplotlib 依赖于 fontTools 和 ttconv 的供应商分支.

当前,Type 3,Type 42 和 TrueType 字体被子集化.Type 1 字体未被子集化.

核心字体#

除了嵌入字体的能力之外,作为 PostScriptPDF specification 的一部分,有 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 中的字体#

文本可以通过 rcParams["svg.fonttype"] (default: 'path') 控制的两种方式输出到 SVG:

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

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

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

Agg 中的字体#

为了通过 Agg 将文本输出为栅格格式,Matplotlib 依赖于 FreeType .因为字形的精确渲染在 FreeType 版本之间会发生变化,所以我们将图像比较测试固定到特定版本.

Matplotlib 如何选择字体#

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

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

  2. 基于 FontProperties 对象,使用 FontManager 上的方法来选择 Matplotlib 知道的 closest "best" 字体(除了 SVG 的 'none' 模式).

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

选择 "best" 字体的算法是由 CSS1 Specifications 指定的算法的修改版本,该算法被 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, png)

The string "There are 几个汉字 in between!" rendered with 2 fonts.

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

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

Gallery generated by Sphinx-Gallery