目录

如何使用reportlab的TTFont字体回退功能

reportlab_enhancedreportlab 的 fork 分支,在上游基础上进行字体功能增强。TTFont 字体回退即为其中一项核心改进。

在原生 reportlab 中,TTFont 一直缺少字体回退机制。这导致处理多语言混合文本(如拉丁字母 + 中文、日文等)时,如果主字体缺少某些字符的字形,显示效果会非常糟糕——轻则方块,重则缺失。reportlab_enhanced 通过设置环境变量 REPORTLAB_FONT_FALLBACK=1,为 TTFont 添加了与 Type1 字体同等能力的 fallback 支持。

本文介绍如何使用这一功能。实现原理请参考 reportlab TTFont字体回退实现解析

必须设置环境变量才能启用,默认关闭:

REPORTLAB_FONT_FALLBACK=1 python your_script.py

或在脚本开头设置:

import os
os.environ['REPORTLAB_FONT_FALLBACK'] = '1'
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

# 注册主字体(缺少某些字形)
latin_font = TTFont('NotoSans', 'NotoSans-Regular.ttf')

# 注册 fallback 字体(包含主字体缺失的字形)
cjk_font = TTFont('NotoSansCJK', 'NotoSansCJK-Regular.ttf')

pdfmetrics.registerFont(latin_font)
pdfmetrics.registerFont(cjk_font)

# 设置 fallback
latin_font.substitutionFonts = [cjk_font]

# 使用
canvas.setFont('NotoSans', 12)
canvas.drawString(100, 700, 'Hello 你好 World')  # 中文字符自动使用 NotoSansCJK

registerFontWithFallback 可以一步完成注册和 fallback 配置:

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

# 传入 fallback 字体
font = pdfmetrics.registerFontWithFallback(
    'NotoSans',
    'NotoSans-Regular.ttf',
    fallbackFonts=[
        TTFont('NotoSansCJK', 'NotoSansCJK-Regular.ttf')
    ]
)

canvas.setFont('NotoSans', 12)
canvas.drawString(100, 700, 'Hello 你好 World')

fallbackFonts 参数支持字体名称字符串或 TTFont 实例:

# 方式1:传入字体名称字符串
font = pdfmetrics.registerFontWithFallback(
    'NotoSans', 'NotoSans-Regular.ttf',
    fallbackFonts=['NotoSansCJK']  # 需要预先通过 getFont() 注册
)

# 方式2:传入 TTFont 实例
font = pdfmetrics.registerFontWithFallback(
    'NotoSans', 'NotoSans-Regular.ttf',
    fallbackFonts=[TTFont('NotoSansCJK', 'NotoSansCJK-Regular.ttf')]
)

如果需要手动检查某个字符是否在字体中:

font = TTFont('NotoSans', 'NotoSans-Regular.ttf')

font.hasGlyph('A')       # True,拉丁字母通常有
font.hasGlyph('你')       # False,主字体没有中文字形
font.hasGlyph(0x4F60)     # False,Unicode 码点方式
font.hasGlyph(ord('你'))  # 同上

可以配置多个 fallback 字体,按顺序查找:

latin_font = TTFont('NotoSans', 'NotoSans-Regular.ttf')
cjk_font = TTFont('NotoSansCJK', 'NotoSansCJK-Regular.ttf')
emoji_font = TTFont('NotoEmoji', 'NotoEmoji.ttf')

latin_font.substitutionFonts = [cjk_font, emoji_font]

canvas.setFont('NotoSans', 12)
canvas.drawString(100, 700, 'Hello 你好 😄 World')  # 中文字符 → NotoSansCJK,表情符号 → NotoEmoji

同样支持 <font> 标签:

from reportlab.platypus import Paragraph, SimpleDocTemplate

doc = SimpleDocTemplate('output.pdf')
story = [
    Paragraph(
        '<font name="NotoSans">Hello 你好 World 世界</font>',
        style=ParagraphStyle(fontName='NotoSans', fontSize=14)
    )
]
doc.build(story)

注意:需要在样式或标签中显式指定字体名称为已配置 fallback 的字体。

  1. 功能来自 reportlab_enhanced — 这是 reportlab_enhanced 对上游 reportlab 的字体增强,默认关闭,需要设置 REPORTLAB_FONT_FALLBACK=1 环境变量才生效
  2. 性能开销 — 每次文本渲染时,若主字体缺失某字符,会依次查询 fallback 链的字形表,文本量较大时有一定性能影响
import os
os.environ['REPORTLAB_FONT_FALLBACK'] = '1'

from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

# 注册字体
font = pdfmetrics.registerFontWithFallback(
    'NotoSans', 'NotoSans-Regular.ttf',
    fallbackFonts=[
        TTFont('NotoSansCJK', 'NotoSansCJK-Regular.ttf')
    ]
)

# 创建 PDF
c = canvas.Canvas('mixed_lang.pdf')
c.setFont('NotoSans', 16)

# 绘制混合文本
c.drawString(100, 800, 'ReportLab: Hello 你好 World 世界')

c.save()

生成的 PDF 中,拉丁字符使用 NotoSans,中文字符自动切换到 NotoSansCJK,无需手动分段处理。

本文由 AI 辅助编制