Python字体处理库:Pillow (PIL) 字体加载、FreeTypePy绑定、fontTools基础操作
做字体动画,第一步就是搞定字体文件本身。你想想看,如果连字体都加载不对,后面那些花哨的动画效果全是空中楼阁。这一章我带你过一遍Python里最常用的三个字体处理工具:Pillow、FreeTypePy和fontTools。每个都有它的脾气,但用对了都很顺手。
2.1 Pillow (PIL) 字体加载——最常用的入门方式
Pillow是Python图像处理的标配库。它加载字体其实很简单,但坑也不少。我个人习惯用ImageFont.truetype()来加载TrueType字体,这是最稳定的方式。
核心函数:ImageFont.truetype(font_path, size, encoding='unic')
- font_path:字体文件路径,支持.ttf、.otf、.ttc等格式
- size:字号大小,单位是像素
- encoding:编码方式,默认'unic'支持Unicode
from PIL import ImageFont, ImageDraw, Image
# 加载字体
font = ImageFont.truetype('path/to/font.ttf', 48)
# 创建画布
img = Image.new('RGB', (400, 200), (255, 255, 255))
draw = ImageDraw.Draw(img)
# 绘制文字
draw.text((50, 80), 'Hello 字体', font=font, fill=(0, 0, 0))
img.show()
嗯,这里要注意——Pillow的text()方法默认不支持自动换行。我在项目中遇到过需要显示长段文字的场景,结果文字直接跑出画布边界了。后来我用了textwrap模块手动分行,才解决了这个问题。
避坑指南:我曾经在Windows上加载中文字体时,发现某些字体文件路径包含中文会导致加载失败。解决方案是用os.path.abspath()转成绝对路径,或者干脆把字体文件放在项目目录下用相对路径。
2.2 FreeTypePy绑定——更底层的字体控制
Pillow虽然方便,但如果你想控制字体的渲染细节——比如字距、字形轮廓、抗锯齿级别——那就得用FreeTypePy了。它是FreeType库的Python绑定,说白了就是让你直接操作字体引擎。
为什么会需要这么底层的东西?举个例子,我在做字体逐帧动画时,需要精确知道每个字符的宽度和高度,Pillow返回的尺寸有时候不够准。FreeTypePy能给你最原始的度量数据。
import freetype
# 加载字体
face = freetype.Face('path/to/font.ttf')
face.set_char_size(48 * 64) # 字号单位是1/64像素
# 加载字符
face.load_char('A', freetype.FT_LOAD_RENDER)
glyph = face.glyph
# 获取位图
bitmap = glyph.bitmap
print(f'位图尺寸: {bitmap.width} x {bitmap.rows}')
print(f'左偏移: {glyph.bitmap_left}, 顶偏移: {glyph.bitmap_top}')
| 属性 | 说明 | 我在项目中用到的场景 |
|---|---|---|
face.glyph.linearHoriAdvance |
水平前进宽度(1/64像素) | 计算字符间距,做打字机动画 |
face.glyph.bitmap_left |
位图左边界偏移 | 对齐字符位置,避免文字抖动 |
face.glyph.bitmap_top |
位图上边界偏移 | 垂直居中文字,特别是中英文混排 |
face.ascender / descender |
字体上升/下降高度 | 计算行高,做多行文本布局 |
注意:FreeTypePy的安装在不同系统上差异很大。Linux上pip install freetype-py通常没问题,Windows上可能需要预装FreeType动态库。我曾经在Windows上折腾了整整一下午,最后用conda环境才搞定。
2.3 fontTools基础操作——字体文件的解剖刀
fontTools不是用来渲染文字的,它是用来解析和修改字体文件本身的。你可以把它想象成字体文件的瑞士军刀——读取表结构、提取字形轮廓、修改元数据,甚至合并字体。
我个人觉得fontTools最实用的功能是读取字体度量信息和提取字形轮廓。特别是做字体动画时,你需要知道每个字形的贝塞尔曲线控制点,这样才能做变形动画。
from fontTools.ttLib import TTFont
from fontTools.pens.recordingPen import RecordingPen
# 加载字体文件
font = TTFont('path/to/font.ttf')
# 获取字体名称
name_table = font['name']
for record in name_table.names:
if record.nameID == 1: # 字体家族名称
print(f'字体名称: {record.toUnicode()}')
# 提取字形轮廓
glyph_set = font.getGlyphSet()
glyph_name = 'A' # 字形名称
pen = RecordingPen()
glyph_set[glyph_name].draw(pen)
# 打印轮廓指令
for cmd, args in pen.value:
print(f'{cmd}: {args}')
font.close()
常用表结构:
cmap:字符到字形映射表,告诉你哪个Unicode对应哪个字形glyf:字形轮廓数据,包含所有贝塞尔曲线信息hmtx:水平度量表,记录每个字形的宽度和左轴承name:字体名称表,包含字体家族、样式、版权等信息OS/2:OS/2表,包含字体兼容性、度量范围等
fontTools还有一个很实用的功能——子集化。我在项目中做过一个在线字体预览工具,用户只需要预览几个字,没必要加载整个字体文件。用fontTools的子集化功能,只保留用到的字符,文件体积能缩小90%以上。
from fontTools.subset import Subsetter
# 创建子集化器
subsetter = Subsetter()
# 指定要保留的字符
subsetter.populate(unicodes=[0x0041, 0x0042, 0x0043]) # A, B, C
# 执行子集化
font = TTFont('path/to/font.ttf')
subsetter.subset(font)
font.save('subset_font.ttf')
font.close()
小技巧:fontTools的TTFont对象在操作完后一定要调用close()方法,否则文件句柄不会释放。我曾经因为这个原因在批量处理1000多个字体文件时遇到了内存泄漏,排查了半天才发现是忘了关闭文件。
2.4 三个库的对比与选择
这三个库各有侧重,你根据实际需求选就行。我一般这样用:
- 只是简单显示文字:用Pillow,够用且简单
- 需要精确控制渲染细节:用FreeTypePy,底层控制力强
- 需要解析或修改字体文件:用fontTools,功能最全
当然,实际项目中经常是组合使用。比如我用fontTools提取字形轮廓,然后用FreeTypePy渲染,最后用Pillow合成到画布上。三个库配合起来,基本能搞定所有字体相关的需求。