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


## 参考

- [reportlab TTFont字体回退实现解析](/docs/reportlab-ttfont-fallback-impl/) — 配套实现原理分析（来自 reportlab_enhanced）
- [reportlab-enhanced 文档 - TrueType 字体回退](https://reportlab-enhanced.tain.one/zh/ch2a_fonts/#truetype)
- [reportlab-enhanced 文档](https://reportlab-enhanced.tain.one/)
- [selcarpa/reportlab_enhanced](https://github.com/selcarpa/reportlab_enhanced)

<!--more-->

## 介绍

[reportlab_enhanced](https://github.com/selcarpa/reportlab_enhanced) 是 [reportlab](https://www.reportlab.com/) 的 fork 分支，在上游基础上进行字体功能增强。TTFont 字体回退即为其中一项核心改进。

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

本文介绍如何使用这一功能。实现原理请参考 [reportlab TTFont字体回退实现解析](/docs/reportlab-ttfont-fallback-impl/)。

## 启用功能

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

```bash
REPORTLAB_FONT_FALLBACK=1 python your_script.py
```

或在脚本开头设置：

```python
import os
os.environ['REPORTLAB_FONT_FALLBACK'] = '1'
```

## 基础用法：逐行配置

```python
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 配置：

```python
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 实例：

```python
# 方式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')]
)
```

## 检查字体字形：`hasGlyph()`

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

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

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

## 多级 Fallback

可以配置多个 fallback 字体，按顺序查找：

```python
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>` 标签：

```python
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 链的字形表，文本量较大时有一定性能影响

## 完整示例

```python
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 辅助编制*

