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, which is enabled by default starting from version 0.1.0.

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

Starting with reportlab_enhanced 0.1.0, TTFont font fallback is enabled by default and requires no manual configuration.

For earlier versions, set the environment variable REPORTLAB_FONT_FALLBACK=1 to enable this feature.

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. Enabled by default starting from version 0.1.0.
  2. Performance cost — Each text rendering checks the fallback chain for glyphs missing from the primary font. May impact performance with large text volumes.
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).