Matplotlib,我希望在文本之间插入多个汉字。

假设您要求 Matplotlib 渲染一个包含在英文文本之间插入多个汉字的标签的绘图。

或者反过来说,假设您使用 Matplotlib 的中文字体,但在其间有英文文本(这很常见)。

假设:中文字体没有这些英文字形,反之亦然。

通过这篇简短的文章,我将讨论 Matplotlib 中从字体优先到文本优先方法的迁移过程,理想情况下,这可以解决上述问题。

拥有字体?#

逻辑上,解决这个问题的第一步是询问您是否拥有多种字体,对吧?

Matplotlib 不附带CJK(中日韩)字体,理想情况下,这些字体包含这些汉字字形。但是,它确实尝试使用它附带的默认字体涵盖大多数情况。

因此,如果您没有字体来渲染您的汉字,请继续安装一个!Matplotlib 将找到您安装的字体(在重建缓存之后)。

解析字体#

这就是事情变得有趣的地方,也是我的上一篇文章所要讨论的…

解析整个字体族以获取给定字体属性的多个字体

FT2Font 魔法!#

让您了解 Matplotlib 的工作原理

  1. 绘制时选择单个字体(已修复:参考上一篇文章
  2. 文档中显示的每个字符仅由该字体渲染(部分修复:参考本文

FT2Font 是一个 matplotlib 到字体的模块,它提供高级 Python API 来与单个字体的操作(如读取/绘制/提取等)进行交互。

该模块是用 C++ 编写的,需要在其周围添加包装器才能使用 Python 的 C-API 转换为Python 扩展

它允许我们直接从 Python 使用 C++ 函数!

因此,无论您在库中看到哪里使用字体(库是指可读的 Python 代码库 XD),您都可以推导出这一点

FT2Font === SingleFont

但是,现在情况有点不同了…

设计多字体系统#

FT2Font 本身基本上是围绕一个名为FreeType的库的包装器,它是一个免费提供的用于渲染字体的软件库。

FT2Font Naming
FT2Font 的命名方式

在我的初始提案中…在查看 FT2Font 的结构时,我发现

Oh, looks like all we need are Faces!

如果您不知道面/字形/连字是什么,请查看为什么文本讨厌你。我可以保证您一定会喜欢一些关于文本渲染为何困难的真实案例。🥲

无论如何,如果您已经知道什么是面,您可能会想到

如果我们已经拥有来自多个字体所需的所有面(假设我们创建了 FT2Font 的子类…它只跟踪其字体的面),我们应该能够从该父 FT2Font 渲染所有内容,对吧?

正如我后来在实现此设计时发现段错误时发现的那样

Each FT2Font is linked to a single FT_Library object!

如果您尝试从不同的 FT2Font 对象加载面/字形/字符(基本上任何内容)…您将遇到严重的段错误。(因为链接到FT_Library的一个对象无法真正访问具有自身FT_Library的另一个对象)

// face is linked to FT2Font; which is
// linked to a single FT_Library object
FT_Face face = this->get_face();
FT_Get_Glyph(face->glyph, &placeholder); // works like a charm

// somehow get another FT2Font's face
FT_Face family_face = this->get_family_member()->get_face();
FT_Get_Glyph(family_face->glyph, &placeholder); // segfaults!

意识到这一点花费了相当长的时间!之后,我很快想出了一个递归方法,其中我们

  1. 在 Python 中创建一个 FT2Font 对象列表,并将其传递给 FT2Font
  2. FT2Font 将通过以下方式保存其字体的指针:
    std::vector<FT2Font *> fallback_list
  3. 查找我们想要的字符是否在当前字体中可用
    1. 如果字符可用,则使用该 FT2Font 渲染该字符
    2. 如果找不到字符,则再次转到步骤 3,但现在遍历fallback_list
  4. 就是这样!

上面代码片段的快速修改^

bool ft_get_glyph(FT_Glyph &placeholder) {
	FT_Error not_found = FT_Get_Glyph(this->get_face(), &placeholder);
	if (not_found) return False;
	else return True;
}

// within driver code
for (uint i=0; i<fallback_list.size(); i++) {
	// iterate through all FT2Font objects
	bool was_found = fallback_list[i]->ft_get_glyph(placeholder);
	if (was_found) break;
}

有了围绕此实现的想法,Agg 后端能够使用多个字体渲染文档(通过 GUI 或 PNG)!

ChineseInBetween
直接从 Matplotlib 输出的 PNG!

Python C-API 起初很难!#

我在 Python C-API 的参数文档上花费了几天时间,一开始很难获得您需要的内容,说实话。

但是,在 GSoC 社区中一些很棒的人(@srijan-paul@atharvaraykar)和很棒的导师的帮助下,障碍消失了!

那么我们完成了?#

哦,不。XD

Agg 后端工作正常,但是使用多个字体生成 PDF/PS/SVG 则是另一回事!我想我会留到以后再处理。

ThankYouDwight
如果您一直在关注目前的进展,那您真是太棒了!

注意:这篇博文也发布在我的个人网站上。#