You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django中xhtml2pdf转换HTML到PDF时UTF-8特殊字符显示为方框的问题求助

解决xhtml2pdf转换PDF时特殊字符显示为方框的问题

这个方框问题本质很简单:你用的Josefin Slab字体不支持这些特殊字符——不管是匈牙利语的带重音字母(Ő、Ű、ő、ű),还是Emoji(✅),都需要字体包含对应的字形才能正常显示。xhtml2pdf默认依赖系统字体,如果你的环境里的Josefin Slab没覆盖这些字符,就会用方框替代。

下面是几个靠谱的解决方法:

方法1:更换支持宽字符集的系统字体

直接换成原生支持多语言和Emoji的字体,比如Google的Noto Sans(几乎覆盖所有语言和常用符号),修改你的模板CSS:

body {
    font-family: 'Noto Sans', sans-serif;
    font-size: large;
    background-color: beige;
}

如果是服务器环境,要先安装对应字体:

  • Ubuntu/Debian:sudo apt install fonts-noto
  • CentOS/RHEL:sudo yum install google-noto-sans-fonts

方法2:嵌入自定义字体到PDF(最可靠,避免环境依赖)

如果不想依赖系统字体,直接把字体文件打包到项目里,让PDF嵌入字体,这样不管部署到什么环境都能正常显示:

  1. 准备字体文件:下载支持宽字符集的字体(比如Noto Sans的.ttf文件),放到Django项目的static/fonts/目录下
  2. 修改模板CSS:用@font-face引入字体
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<head>
    {% load static %} <!-- 记得加载static标签 -->
    <style>
        @font-face {
            font-family: 'Noto Sans';
            src: url('{% static "fonts/NotoSans-Regular.ttf" %}') format('truetype');
            font-weight: normal;
            font-style: normal;
        }
        body {
            font-family: 'Noto Sans', sans-serif;
            font-size: large;
            background-color: beige;
        }
    </style>
    <!-- 其他head内容 -->
</head>
<body>
    <h2 class="utf">This is myŐ, Ű, ő or ű✅✅ pdf file with special char</h2>
</body>
</html>
  1. 修改pdf.py代码:添加link_callback参数,让xhtml2pdf能正确找到静态文件路径
import os
from django.conf import settings
from django.contrib.staticfiles import finders
from io import BytesIO
from django.http import HttpResponse
from django.template.loader import get_template
from xhtml2pdf import pisa

def link_callback(uri, rel):
    # 处理静态资源路径的回调函数
    result = finders.find(uri)
    if result:
        if not isinstance(result, (list, tuple)):
            result = [result]
        path = list(os.path.realpath(p) for p in result)[0]
    else:
        # 区分静态文件和媒体文件路径
        if uri.startswith(settings.MEDIA_URL):
            path = os.path.join(settings.MEDIA_ROOT, uri.replace(settings.MEDIA_URL, ""))
        elif uri.startswith(settings.STATIC_URL):
            path = os.path.join(settings.STATIC_ROOT, uri.replace(settings.STATIC_URL, ""))
        else:
            return uri  # 处理绝对URL

    if not os.path.isfile(path):
        raise Exception(f"无法找到资源文件: {uri}")
    return path

def html2pdf(template_source, context_dict={}):
    template = get_template(template_source)
    html = template.render(context_dict)
    result = BytesIO()
    # 传入link_callback处理静态字体文件
    pdf = pisa.CreatePDF(BytesIO(html.encode('utf-8')), result, link_callback=link_callback)
    if not pdf.err:
        return HttpResponse(result.getvalue(), content_type="application/pdf")
    return None

额外注意:Emoji的特殊处理

有些基础字体(比如Noto Sans Regular)可能不包含Emoji,这时候可以在字体栈里加上专门的Emoji字体:

body {
    font-family: 'Noto Sans', 'Noto Sans Emoji', sans-serif;
}

这样渲染器会自动用Noto Sans Emoji显示✅这类符号。

核心思路就是:确保你用的字体包含所有需要显示的字符的字形,要么依赖系统已安装的字体,要么直接嵌入字体到PDF里,就能解决方框问题了。

内容的提问来源于stack exchange,提问作者divyanshu mishra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 18:20:59