注意
转到页面末尾 下载完整示例代码。
Matplotlib 中的字体#
Matplotlib 的文本引擎需要字体才能工作,其中一些字体随安装包一起提供。默认字体是 DejaVu Sans,它涵盖了大多数欧洲书写系统。然而,用户可以配置默认字体并提供自定义字体。有关详细信息,请参阅 自定义文本属性;对于 DejaVu Sans 不支持的字形,请特别参阅 非拉丁语系字形文本。
Matplotlib 还提供了一个将文本渲染卸载到 TeX 引擎的选项(usetex=True),请参阅 使用 LaTeX 进行文本渲染。
PDF 和 PostScript 中的字体#
在计算领域,字体有着漫长(有时是不兼容的)的历史,导致不同平台支持不同类型的字体。实际上,Matplotlib 支持三种字体规范(除了本指南稍后解释的 pdf “核心字体”之外):
Type 1 (PDF 配合 usetex) |
Type 3 (PDF/PS) |
TrueType (PDF) / Type 42 (PS) |
|---|---|---|
Adobe 引入的旧字体类型。 |
Apple 引入的较新字体类型;在当今广泛使用。 |
|
PostScript 的受限子集,字符集(charstrings)以字节码形式存在。 |
完整的 PostScript 语言,允许嵌入任意代码。 |
包含一个可以执行代码的虚拟机。 |
支持字体微调(Hinting)。 |
不支持字体微调。 |
通过虚拟机支持字体微调。 |
由 |
通过外部模块 fontTools 进行子集化。 |
|
注意
Adobe 已于 2023 年 1 月停止了对 Type 1 字体创作的支持。Matplotlib 为了与 TeX 保持兼容而使用 Type 1 字体;当在 PDF 后端使用 usetex 功能时,Matplotlib 会读取 TeX 引擎使用的字体,这些字体通常是 Type 1 格式。
Matplotlib 还对 OpenType 字体提供了有限的支持,这是一种由 Adobe 和 Microsoft 联合开发的较新标准;此类字体通常包含大得多的字符集。
字体子集化#
PDF 和 PostScript 格式支持在文件中嵌入字体,使显示程序能够正确渲染文本,而不依赖于查看者电脑上安装的字体,也无需预先栅格化文本。这确保了如果输出内容被缩放或调整大小时,文本不会出现像素化。然而,在文件中嵌入完整字体可能会导致输出文件很大,特别是对于包含许多字形的字体,例如支持中日韩(CJK)语言的字体。
为了在使用矢量字体时保持合理的输出大小,Matplotlib 只嵌入文档中实际使用的字形。这被称为字体子集化。计算字体子集并写入精简后的字体都是复杂的问题,Matplotlib 大多数情况下通过使用 fontTools 库来解决这些问题。
核心字体#
除了嵌入字体的能力外,作为 PostScript 和 PDF 规范的一部分,有 14 种核心字体(Core Fonts),合规的查看器必须确保它们可用。如果您将文档限制为仅使用这些字体,则无需在文档中嵌入任何字体信息,同时仍能获得矢量文本。
这对于生成真正轻量级的文档特别有帮助。
# 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"](默认值:'path')控制,以两种方式输出到 SVG:
作为 SVG 中的路径(
'path')作为 SVG 中的字符串,且在元素上应用字体样式(
'none')
当通过 'path' 保存时,Matplotlib 会计算所用字形的矢量路径并将其写入输出。这样做的优点是,无论安装了什么字体,SVG 在所有计算机上的外观都是一样的。然而,文本事后将无法编辑。相比之下,使用 'none' 保存会导致文件更小,且文本会直接以标记形式出现。但是,外观可能会根据 SVG 查看器和可用的字体而有所不同。
Agg 中的字体#
为了通过 Agg 将文本输出为栅格格式,Matplotlib 依赖于 FreeType。由于字形的精确渲染在 FreeType 的不同版本之间会发生变化,我们在图像对比测试中锁定了特定的版本。
Matplotlib 如何选择字体#
在内部,Matplotlib 使用字体的过程分为三个步骤:
创建一个
FontProperties对象(显式或隐式地)。根据
FontProperties对象,使用FontManager的方法来选择 Matplotlib 所知的最接近的“最佳”字体(SVG 的'none'模式除外)。后端代码使用该字体对象的 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),它只会查找单个字体并返回该字体在文件系统上的绝对路径。
字体回退(Fallback)#
没有一种字体可以覆盖整个 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'
)
字符串 "There are 几个汉字 in between!" 使用 2 种字体渲染。
在内部,这是通过将 FontProperties 对象上的“字体族(font family)”设置为字体族列表来实现的。一个(目前)私有的 API 会提取找到的所有字体的路径列表,然后构建一个了解所有字体的单一 ft2font.FT2Font 对象。字符串的每个字形都使用列表中包含该字形的第一个字体进行渲染。
这项工作的大部分由 Aitik Gupta 在 2021 年 Google Summer of Code 的支持下完成。