Contents

How to Use reportlab's TTFont Font Fallback

reportlab_enhanced is a fork of reportlab that enhances font capabilities on top of the upstream. TTFont font fallback is one of its core improvements.

In the original reportlab, TTFont has always lacked a font fallback mechanism. When processing multilingual text (e.g., Latin + Chinese, Japanese, etc.), if the main font lacks glyphs for certain characters, the rendering is poor—displaying tofu boxes at best, or missing content at worst. reportlab_enhanced adds Type1-level fallback support for TTFont by setting the environment variable REPORTLAB_FONT_FALLBACK=1.

This article explains how to use this feature. For implementation details, see reportlab TTFont Font Fallback Implementation Analysis.

The feature is off by default and requires an environment variable:

REPORTLAB_FONT_FALLBACK=1 python your_script.py

Or set it at the start of your script:

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

# Register primary font (missing some glyphs)
latin_font = TTFont('NotoSans', 'NotoSans-Regular.ttf')

# Register fallback font (contains glyphs missing from primary)
cjk_font = TTFont('NotoSansCJK', 'NotoSansCJK-Regular.ttf')

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

# Set fallback
latin_font.substitutionFonts = [cjk_font]

# Usage
canvas.setFont('NotoSans', 12)
canvas.drawString(100, 700, 'Hello 你好 World')  # Chinese chars auto-use NotoSansCJK

registerFontWithFallback handles registration and fallback setup in one step:

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

font = pdfmetrics.registerFontWithFallback(
    'NotoSans',
    'NotoSans-Regular.ttf',
    fallbackFonts=[
        TTFont('NotoSansCJK', 'NotoSansCJK-Regular.ttf')
    ]
)

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

The fallbackFonts parameter accepts font name strings or TTFont instances:

# Method 1: Font name strings
font = pdfmetrics.registerFontWithFallback(
    'NotoSans', 'NotoSans-Regular.ttf',
    fallbackFonts=['NotoSansCJK']  # Must be pre-registered via getFont()
)

# Method 2: TTFont instances
font = pdfmetrics.registerFontWithFallback(
    'NotoSans', 'NotoSans-Regular.ttf',
    fallbackFonts=[TTFont('NotoSansCJK', 'NotoSansCJK-Regular.ttf')]
)

To manually check if a character exists in a font:

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

font.hasGlyph('A')       # True, Latin letters usually present
font.hasGlyph('你')       # False, primary font lacks CJK glyphs
font.hasGlyph(0x4F60)     # False, Unicode code point
font.hasGlyph(ord('你'))  # Same as above

Multiple fallback fonts can be configured in order:

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')  # CJK → NotoSansCJK, emoji → NotoEmoji

The <font> tag is also supported:

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)

Note: Explicitly specify the font name (configured with fallback) in the style or tag.

  1. Feature from reportlab_enhanced — This is a font enhancement from reportlab_enhanced on top of upstream reportlab. Disabled by default; requires REPORTLAB_FONT_FALLBACK=1.
  2. Performance cost — Each text rendering checks the fallback chain for glyphs missing from the primary font. May impact performance with large text volumes.
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')
    ]
)

c = canvas.Canvas('mixed_lang.pdf')
c.setFont('NotoSans', 16)
c.drawString(100, 800, 'ReportLab: Hello 你好 World 世界')
c.save()

In the generated PDF, Latin characters use NotoSans and CJK characters automatically use NotoSansCJK—no manual segmentation needed.

AI-assisted article.

This article is translated by deepseek-v4-flash (model: deepseek/deepseek-v4-flash).