Python使用ReportLab绘制PDF时怎么设置字体加粗

顾言
顾言
发布时间:2026-11-09| 11人浏览过
原创

全面解析在Python中使用ReportLab库绘制PDF时实现字体加粗的有效方法。深入剖析Canvas底层绘图、Platypus段落标签解析机制,详解注册中文字体族(registerFontFamily)与底层描边加粗技巧,附带完整可复现代码与避坑指南。

ReportLab 实用技巧
技术分类:Python办公自动化 / PDF报表生成 | 核心依赖:reportlab
Python使用ReportLab绘制PDF时怎么设置字体加粗
ReportLab 字体加粗主要实现方案概览

在 Python 中使用 ReportLab 绘制 PDF 时,设置字体加粗并不是像网页开发那样简单写一句 font-weight: bold 就能生效。ReportLab 依赖底层的 Adobe PostScript 与 PDF 字体度量系统:如果是内置的西文字体(如 Helvetica),必须显式指定其独立的粗体变体名称(如 Helvetica-Bold);而如果是绘制中文字体或自定义 TrueType 字体,则需要先单独注册常规与粗体字库文件,再通过 registerFontFamily 建立字族映射,才能让排版中的 <b> 标签正常工作;若缺失粗体字库,还可通过底层的文字渲染描边模式实现伪加粗。

一、为什么 ReportLab 无法自动“一键加粗”中文字体?

很多刚接触 ReportLab 的开发者在排版段落时,经常遇到这样的现象:为样式设置了中文字体,并在段落中加入 <b>重要通知</b>,运行后程序要么直接抛出 KeyError: "font 'Helvetica-Bold' not found",要么渲染出来的加粗中文变成了乱码、小黑块甚至是直接丢失。

出现这种现象的本质,在于 ReportLab 对字体的度量解析机制。ReportLab 内部的字符测量与排版引擎不会对常规矢量字形做算法层面的自动加粗合成(除非显式开启底层的描边模式)。在标准的 Platypus 排版组件中,当解析器遇到 <b> 标签时,它会向 pdfmetrics 注册表查询当前字体绑定的 bold 对应字体。如果开发者仅仅注册了一个常规中文字体(例如 SimHei),而没有为它绑定字族关系,系统就会尝试回退到默认的西文字体 Helvetica-Bold。而西文字体内部没有中文字形编码,最终导致报错或排版损坏。

Python使用ReportLab绘制PDF时怎么设置字体加粗
图 1:ReportLab 字体解析与加粗查找架构示意

二、方法一:Canvas 底层绘图设置字体加粗

如果你使用的是 reportlab.pdfgen.canvas 进行绝对坐标绘图,加粗文字主要有两种做法,取决于你使用的是西文字体还是自定义中文字体。

1. 标准西文字体:直接传入粗体变体名称

ReportLab 内置了 14 种标准的 Type 1 字体(包括 Helvetica、Times-Roman、Courier 等),它们各自附带对应的粗体与斜体变体。调用时无需注册外部文件,直接在 setFont() 方法中指定粗体名称即可:

from reportlab.pdfgen import canvas

c = canvas.Canvas("canvas_demo.pdf")

# 正确:直接传递粗体字体名称
c.setFont("Helvetica-Bold", 16)
c.drawString(100, 750, "This is Helvetica Bold text.")

# 错误写法提醒:setFont 没有 bold=True 这种参数!
# c.setFont("Helvetica", 16, bold=True)  # 会直接报错 TypeError
c.save()

2. 中文字体:注册粗体 TTF 文件后调用

标准内置字体无法渲染中文。对于中文字体,需要从操作系统或字体包中提取对应的粗体文件(例如思源黑体、微软雅黑的 Bold 版本),使用 pdfmetrics.registerFont 注册后再传给 setFont():

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

# 分别注册常规体与粗体
pdfmetrics.registerFont(TTFont("SourceHan-Regular", "SourceHanSansCN-Regular.ttf"))
pdfmetrics.registerFont(TTFont("SourceHan-Bold", "SourceHanSansCN-Bold.ttf"))

c = canvas.Canvas("chinese_canvas.pdf")

# 绘制常规中文
c.setFont("SourceHan-Regular", 14)
c.drawString(100, 750, "这是常规体中文内容。")

# 切换为粗体中文绘制
c.setFont("SourceHan-Bold", 14)
c.drawString(100, 720, "这是使用独立粗体字库绘制的加粗中文。")

c.save()

三、方法二:Platypus 段落标签与字体族绑定(生产推荐)

在实际生产环境中,绝大部分文档都使用 Platypus 提供的流式布局组件(如 Paragraph、Table)。在段落中,我们通常需要中英文与加粗混排(如“本月销售额达 1,500 万元”),频繁手动切换样式或切分段落极为繁琐。

实现段落内使用 <b>...</b> 标签加粗中文字体的正确方法,是使用 reportlab.pdfbase.pdfmetrics.registerFontFamily 建立字体族关系。

Python使用ReportLab绘制PDF时怎么设置字体加粗
图 2:ReportLab 中文加粗规范实施四步流程

实施步骤拆解:

  1. 准备两个 TrueType 字体文件:一个常规字重(Regular),一个粗体字重(Bold)。建议使用免费开源且字形优美的思源黑体(Source Han Sans)或阿里巴巴普惠体。
  2. 使用 TTFont 分别注册这两个文件:为它们各起一个内部标识符,如 MySans-R 和 MySans-B。
  3. 调用 registerFontFamily 绑定字体族:将一个统一的族名称(例如 MySans)与上述两个标识符关联。需要明确指定 normal 与 bold。如果不使用斜体,可将 italic 和 boldItalic 也指向对应名称,防止误用 <i> 时崩溃。
  4. 在段落样式中指定该字体族:创建 ParagraphStyle 时,fontName 填写字族名 MySans。此时在段落文本中直接写 <b>文本</b>,ReportLab 就会自动调用 MySans-B 的字形进行渲染。
核心 API 说明

registerFontFamily(family, normal=None, bold=None, italic=None, boldItalic=None)

该函数建立逻辑族名与已注册的具体字体之间的映射。只要完成了这步绑定,ReportLab 的 XML 标记解释器就能感知 <b> 应当对应哪个真实字体文件。

四、方法三:缺失粗体字库时的底层描边伪加粗技巧

在某些受限环境中,如果服务器上只有一份常规中文字体文件(例如只有 simsun.ttf,体积受限未打包粗体),而业务又强制要求在 Canvas 绘图时呈现加粗效果,可以使用 PDF 底层的文本渲染模式(Text Render Mode)实现“描边伪加粗”。

PDF 规范支持多种文本渲染模式:

  • 0:常规纯填充(Fill text,默认模式)。
  • 1:纯描边镂空(Stroke text)。
  • 2:填充并描边(Fill, then stroke text)。

通过将渲染模式切换为 2,并为文字设置一个极细的边框描边(通常为 0.3 到 0.6 磅),即可在视觉上达到类似 CSS “描边粗体”的加粗效果。

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

pdfmetrics.registerFont(TTFont("SimSun", "simsun.ttf"))

c = canvas.Canvas("pseudo_bold.pdf")
c.setFont("SimSun", 14)

# 1. 正常绘制常规字
c.drawString(80, 750, "常规字体展示:Python 报表生成")

# 2. 开启描边伪加粗
c.setTextRenderMode(2)           # 关键:模式2为同时填充并描边
c.setLineWidth(0.4)              # 描边线宽:建议在 0.3 到 0.6 之间,不可过大
c.setFillColor(colors.black)     # 文字填充颜色
c.setStrokeColor(colors.black)   # 文字描边颜色
c.drawString(80, 710, "伪加粗字体展示:Python 报表生成(通过描边加粗)")

# 3. 恢复默认纯填充模式(避免影响后续绘制)
c.setTextRenderMode(0)
c.drawString(80, 670, "已恢复常规纯填充模式。")

c.save()
伪加粗使用注意事项

描边线宽(lineWidth)必须根据字号谨慎调整。中文字符笔画复杂、密集度高,若线宽超过 0.8,像“繁”、“囊”、“微”等复杂汉字的内部负空间会被油墨彻底填死,导致字形糊成黑块。此外,该方式仅适用于底层 Canvas 坐标绘图,无法直接作用于流式段落 Paragraph。

Python使用ReportLab绘制PDF时怎么设置字体加粗
图 3:ReportLab 字体加粗方案特性对比矩阵

五、完整实战示例:兼顾 Canvas 与 Platypus 的端到端实现

下面给出一个结构完整、可直接复现的工程级示例代码。该代码演示了如何在实际项目中同时运用 registerFontFamily 与 SimpleDocTemplate 绘制混合加粗的专业 PDF 文档。

# -*- coding: utf-8 -*-
"""
ReportLab 中文字体加粗完整工程范例
环境要求:Python 3.8+,reportlab >= 3.6
"""
import os
from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase.pdfmetrics import registerFontFamily

def setup_chinese_fonts():
    """
    配置并注册中文字体与字体族
    此处以系统常见的黑体或开源思源黑体为例
    """
    # 模拟字体路径,生产中请确保路径指向真实存在的 .ttf 文件
    reg_font_path = "SourceHanSansCN-Regular.ttf"
    bold_font_path = "SourceHanSansCN-Bold.ttf"

    # 1. 注册基础字体
    pdfmetrics.registerFont(TTFont("Hans-Regular", reg_font_path))
    pdfmetrics.registerFont(TTFont("Hans-Bold", bold_font_path))

    # 2. 建立字体族关系映射
    registerFontFamily(
        "HansFamily",
        normal="Hans-Regular",
        bold="Hans-Bold",
        italic="Hans-Regular",       # 无独立斜体时用常规体兜底
        boldItalic="Hans-Bold"       # 粗斜体用粗体兜底
    )
    print("字体族注册完成:HansFamily -> [normal: Hans-Regular, bold: Hans-Bold]")

def build_pdf(output_filename="output_report.pdf"):
    # 创建文档模板
    doc = SimpleDocTemplate(
        output_filename,
        pagesize=letter,
        rightMargin=54, leftMargin=54,
        topMargin=54, bottomMargin=54
    )
    
    # 获取基础样式库
    styles = getSampleStyleSheet()
    
    # 定义全局使用 HansFamily 字体族的自定义样式
    title_style = ParagraphStyle(
        name="ReportTitle",
        fontName="HansFamily",
        fontSize=20,
        leading=26,
        textColor=colors.HexColor("#394957"),
        spaceAfter=15
    )
    
    body_style = ParagraphStyle(
        name="ReportBody",
        fontName="HansFamily",
        fontSize=11,
        leading=18,
        textColor=colors.HexColor("#2C3E50"),
        spaceAfter=10
    )

    story = []

    # 1. 标题(使用已注册的字体族)
    story.append(Paragraph("数据分析报告月度汇总", title_style))
    story.append(Spacer(1, 10))

    # 2. 正文段落内混排加粗
    intro_text = (
        "根据财务部核算,第三季度核心营收总额达到 "
        "<b>4,820.50 万元</b>,同比增长 <b>18.4%</b>。"
        "其中云端服务板块增速最为亮眼,属于<b>重点增长业务单元</b>。"
    )
    story.append(Paragraph(intro_text, body_style))
    story.append(Spacer(1, 15))

    # 3. 表格内单元格文字加粗展示
    table_header_style = ParagraphStyle(
        name="HeaderCell",
        fontName="HansFamily",
        fontSize=10,
        leading=14,
        textColor=colors.white
    )
    
    table_cell_style = ParagraphStyle(
        name="DataCell",
        fontName="HansFamily",
        fontSize=10,
        leading=14,
        textColor=colors.HexColor("#2C3E50")
    )

    table_data = [
        [Paragraph("<b>序号</b>", table_header_style), 
         Paragraph("<b>部门名称</b>", table_header_style), 
         Paragraph("<b>达成率</b>", table_header_style)],
        [Paragraph("01", table_cell_style), 
         Paragraph("基础架构组", table_cell_style), 
         Paragraph("<b>102.5%</b> (达标)", table_cell_style)],
        [Paragraph("02", table_cell_style), 
         Paragraph("应用研发组", table_cell_style), 
         Paragraph("<b>98.2%</b>", table_cell_style)],
    ]

    t = Table(table_data, colWidths=[60, 220, 180])
    t.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor("#394957")),
        ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 6),
        ('TOPPADDING', (0, 0), (-1, -1), 6),
        ('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor("#E2DDD5")),
    ]))
    story.append(t)

    # 4. 编译输出 PDF
    doc.build(story)
    print(f"PDF 文档生成成功:{output_filename}")

if __name__ == "__main__":
    # 模拟在存在对应字体的环境下调用
    # setup_chinese_fonts()
    # build_pdf()
    pass

六、常见报错与生产排查指南

在排版测试过程中,如果字体加粗未能如预期呈现,请依次按照以下清单排查:

Python使用ReportLab绘制PDF时怎么设置字体加粗
图 4:ReportLab 加粗常见误区与生产排查清单

1. 报错 KeyError: "font '...' not found"

原因排查:在 Paragraph 内部使用了 <b> 标签,但当前样式绑定的 fontName 仅仅是通过 registerFont(TTFont('MyFont', '...')) 注册的单一常规字体,并没有通过 registerFontFamily 将 MyFont 声明为一个字族。ReportLab 内部无法解析 bold 对应项,触发了默认回退策略。

解决方法:确保注册了对应的 Bold 字体文件,并调用 registerFontFamily("MyFamily", normal="MyFont-R", bold="MyFont-B"),将样式的 fontName 改为 MyFamily。

2. 报错 TypeError: setFont() got an unexpected keyword argument 'bold'

原因排查:开发者混淆了某些 GUI 框架(如 Tkinter、PyQt)的字体参数,误以为 canvas.setFont() 带有 bold=True 属性。

解决方法:ReportLab 的 setFont(psfontname, size, leading=None) 只能接收具体字体名和字号。请直接传入注册好的粗体字体名称字符串。

3. 加粗后中文变成方块或空白字符

原因排查:虽然调用了 registerFontFamily,但是 bold 参数指向的字体实际上是一个西文字体(例如默认的 Helvetica-Bold),或者该字体缺少对应的汉字 Unicode 编码段。

解决方法:核对粗体文件是否为正规完整的中文字体(如 SourceHanSansSC-Bold.ttf)。建议在注册前通过 fonttools 库或操作系统字体查看器确认其中文字符集是否齐全。

七、总结

在 ReportLab 中实现字体加粗的关键,是理解并顺应其“字形按名称注册、字族按映射绑定”的底层逻辑:

  • 对于原生英文字符,直接使用 Helvetica-Bold 等内置粗体名。
  • 对于中文流式段落(Platypus),务必采用“注册两个 TTF + 绑定 registerFontFamily”的标准四步法,即可在 Paragraph 中自由使用 <b> 标签混排。
  • 在仅有常规字库且用于底层 Canvas 绘图的特殊场景下,可借助 setTextRenderMode(2) 进行适度描边伪加粗。

采用规范的字体族注册流程,不仅能杜绝各类字体缺失崩溃,还能保证生成的 PDF 具有出色的排版保真度与跨平台兼容性。

相关专题:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。