在 Python 中正确渲染具有给定字体的文本并准确检测其边界

Properly render text with a given font in Python and accurately detect its boundaries

这可能看起来非常简单,我也认为它会是,但显然不是。我一定花了一个星期的时间来完成这项工作,但看在我的份上我没能做到。

我需要的

我需要在 Python 中使用任何给定的字体(类似手写体)呈现任何给定的字符串(仅包含标准字符)。必须从 TTF 文件加载字体。我还需要能够准确地检测它的边界(获取文本的垂直和水平方向的确切开始和结束位置),最好是在绘制之前。最后,如果输出是一个我可以继续处理的数组,而不是写入光盘的图像文件,那真的会让我的生活更轻松。

我试过的

Imagemagick 绑定(即 Wand):无法弄清楚如何在设置图像大小并在其上呈现文本之前获取文本规格。

通过 Pycairo 绑定的 Pango:几乎不存在的文档,无法弄清楚如何从文件加载 TrueType 字体。

PIL(枕头):最有前途的选择。我已经设法准确计算出任何文本的高度(令人惊讶的是,这不是高度 getsize returns),但宽度对于某些字体来说似乎有问题。不仅如此,那些宽度有问题的字体也会被错误地渲染。即使图片足够大,它们也会被截断。

这里有一些例子,文字是"Puzzling":

字体:Lovers Quarrel

结果:

字体:Miss Fajardose

结果:

这是我用来生成图像的代码:

from PIL import Image, ImageDraw, ImageFont
import cv2
import numpy as np
import glob
import os

font_size = 75
font_paths = sorted(glob.glob('./fonts/*.ttf'))
text = "Puzzling"
background_color = 180
text_color = 50
color_variance = 60
cv2.namedWindow('display', 0)

for font_path in font_paths:

    font = ImageFont.truetype(font_path, font_size)
    text_width, text_height = font.getsize(text)

    ascent, descent = font.getmetrics()
    (width, baseline), (offset_x, offset_y) = font.font.getsize(text)

    # +100 added to see that text gets cut off
    PIL_image = Image.new('RGB', (text_width-offset_x+100, text_height-offset_y), color=0x888888)
    draw = ImageDraw.Draw(PIL_image)
    draw.text((-offset_x, -offset_y), text, font=font, fill=0)

    cv2.imshow('display', np.array(PIL_image))
    k = cv2.waitKey()
    if chr(k & 255) == 'q':
        break

一些问题

字体有问题吗?一些同事告诉我可能是这样,但我不这么认为,因为 Imagemagick 通过命令行正确渲染了它们。

是我的代码有问题吗?我是不是做错了什么导致文本被截断?

最后请问是PIL的bug吗?在那种情况下,您推荐我使用哪个库来解决我的问题?我应该再试一次 Pango 和 Wand 吗?

pyvips 似乎正确地做到了这一点。我试过这个:

$ python3
Python 3.7.3 (default, Apr  3 2019, 05:39:12) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyvips
>>> x = pyvips.Image.text("Puzzling", dpi=300, font="Miss Fajardose", fontfile="/home/john/pics/MissFajardose-Regular.ttf")
>>> x.write_to_file("x.png")

制作:

pyvips 文档对选项进行了快速介绍:

https://libvips.github.io/pyvips/vimage.html#pyvips.Image.text

或者 C 库文档有更多详细信息:

http://libvips.github.io/libvips/API/current/libvips-create.html#vips-text

它制作了一个 one-band 抗锯齿文本的 8 位图像,您可以将其用于进一步处理,传递给 NumPy 或 PIL 等。简介中有一节介绍如何转换 libvips 图像进入数组:

https://libvips.github.io/pyvips/intro.html#numpy-and-pil