ARTICLE · 1209239
【JQUICK-PDF・柱状图】信用报告刚上线,领导指着 PDF 里的乱码问我:为什么柱状图标题全是「口口口」?
【JQUICK-PDF・柱状图】信用报告刚上线,领导指着 PDF 里的乱码问我:为什么柱状图标题全是「口口口」?
官网示例:
FORMULAS 示例页:https://www.jquick.org/jquick-pdf/#demo-biz16 github 地址:https://github.com/paohaijiao/jquick-pdf
一、故事引入
上个月,我把征信系统的「客户信用评级分布报告」从快照接口迁移到了 jquick-pdf 渲染。代码很简单,一个柱状图,一个模板绑定:
JOption option = new JOption();option.title().text("客户信用评级分布(2026-Q3)");JBar bar = new JBar();bar.name("评级户数").data(320, 580, 410, 190, 85);String svg = JChartRendererFactory.renderChart(JChartType.BAR, option, new JBar[]{bar},new JCategoryAxis().data("AAA", "AA", "A", "BBB", "BB"),new JValueAxis());JPdfConfig config = new JPdfConfig();byte[] bytes = new JQuickPdfFactory(config).bind("svg", svg).executeResource("report/credit_report_01_bar.txt");
本地跑得好好的,一上测试环境,柱状图区域里全是「口口口」。领导把截图甩我脸上:「图表标题全乱码了,评级也全是方块。」
我第一反应是字体没注册。但奇怪的是——模板里的表格中文正常,偏偏图表里的中文乱码。同一个 PDF,一半正常一半乱码,这问题邪门。
我把现场代码原封不动丢给三个 AI,问它们「为什么 SVG 绑定进模板后中文变方块」。三个 AI 的回答高度一致,而且——全都错了。
二、AI 集体翻车
AI-A 说:「这是 PDF 编码问题,把模板文件转成 UTF-8 with BOM 就好了。」
AI-B 说:「executeResource 内部用了系统默认字符集读模板,Windows 上要加 -Dfile.encoding=UTF-8 启动参数。」
AI-C 说:「SVG 字符串里有中文字体名,需要取消 SVG 里的 font-family,让它继承 PDF 默认字体。」
共同点:三个 AI 都在「字符集 / 编码」层面打转,没有一个人提到 渲染字符串型文本时,图表文本走的是哪条渲染管线。
实际上核心问题根本不是编码。SVG 本身就是 UTF-8 字符串,绑定进模板没有任何编码损失。真正的坑在于——jquick-pdf 里 数据文本和图表文本走了两条完全不同的字体解析路径。
三、底层原理拆解
原生裸框架:数据文本 vs SVG 文本,两套逻辑
先看调用链。JQuickPdfFactory.executeResource() 本质上是三件事:
从 classpath 读模板文件( credit_report_01_bar.txt),解析成 DOM 树;模板里出现 {svg}都是内存字符串操作,不存在编码损失。转 BOM 只会让<pdf>根节点多一个不可见字符。- 「file.encoding 启动参数」
:错。这是 JDK 18 之前的经典坑,但它影响的是 new String(byte[])这种隐式编码转换。绑定 SVG 走的是Map<String,Object>,与系统字符集无关。 - 「去掉 SVG 的 font-family」
:方向沾边但结论错。SVG 里原本就有字体声明,删掉声明 SVG 解析器就会用默认字体(通常是不含中文的 Helvetica),乱码依旧。 真正该辨析的 API 差异是:
bind()是纯数据注入,executeResource()是模板渲染。乱码发生在渲染阶段,跟绑定无关。修编码、修启动参数,都是在错误的层打补丁。五、延伸隐藏坑
这个坑有三个衍生版本:
- Linux 服务器上「好了」
:很多生产环境是 UTF-8 locale,某些 SVG 解析器在不同系统上字体回退行为不同,本地 Windows 复现不了,上线才发现。别用环境差异当遮羞布。 - 数字和英文永远正常
:图表里的数字标签走的是 SVG 默认字体,ASCII 字符都有兜底,所以很多报表「只坏中文」,一眼看不出是字体问题还是编码问题。 - Spring 单例工厂的并发残留
: JQuickPdfFactory内部有可变 context(变量绑定表)。Spring 单例共享一个实例时,多线程并发bind()会互相覆盖变量——这是比乱码更严重的线上隐患,后面单独写一篇。
六、推荐正确写法
原生环境,按下面这样写,一次成型:
// 1. 先配置字体,再建工厂JPdfConfig config = new JPdfConfig();config.fontConfig().registerFont("custom-mid", "STSongStd-Light","/fonts/stsong.ttf"); // 注册图表 SVG 能认到的字体// 2. 图表:SVG 文本显式指定已注册字体族JOption option = new JOption();option.title().text("客户信用评级分布(2026-Q3)").fontFamily("custom-mid"); // 关键:让 SVG 文本用注册字体option.legend().fontFamily("custom-mid");JBar bar = new JBar();bar.name("评级户数").data(320, 580, 410, 190, 85);String svg = JChartRendererFactory.renderChart(JChartType.BAR, option,new JBar[]{bar},new JCategoryAxis().data("AAA", "AA", "A", "BBB", "BB"),new JValueAxis());// 3. 模板仅做数据注入,编码全靠 JVM 内部字符串JPdfConfig renderConfig = new JPdfConfig();byte[] bytes = new JQuickPdfFactory(renderConfig).bind("svg", svg).executeResource("report/credit_report_01_bar.txt");改造思路就一条:让图表文本的字体解析路径和表格文本保持同源。不要靠「碰巧先把字体注册了」这种隐式时序,要在生成 SVG 之前就把字体显式挂进 option。
七、实战验证方案
别信嘴炮,用证据说话。给
JTemplatePdfBoxRenderer挂上日志,观察两段文本的字体解析结果:# 打开 jquick-pdf 渲染日志(logback 或 log4j 配置)<logger name="com.github.paohaijiao.visitor.render" level="DEBUG"/>日志里会看到两类记录:
resolveTextFont(text=客户信用评级分布, resolved=STSongStd-Light)—— 表格文本正确命中内置字体; resolveSvgFontFamily(family=Helvetica, glyph=客户, missing=true)—— 图表 SVG 命中默认字体、缺字形。
再看字节码层面:把乱码 PDF 用 PDFBox 打开,
PDType0Font的字形映射表里客户的码点对应.notdef,而正常 PDF 对应真实字形索引。内存地址实证:透传-Dorg.apache.pdfbox.io.RandomAccessRead断点,观察字体对象font.getFontDescriptor()的 identity 是否与注册实例一致。八、结尾感悟
这个 bug 让我重新信了一条老话:框架的「默认能用」,从来不是承诺,只是巧合的积累。表格中文能用,不代表图表中文能用——两条渲染管线,两种字体来源,中间隔着一个 SVG 解析器。
把「现象」和「层」对应清楚,比记住一百个修复技巧更值钱。
如果你也被这类「一半正常一半乱码」的报表坑过,或者踩过 jquick-pdf 别的隐性坑,欢迎点赞、在看、转发,评论区聊聊你的翻车现场。
📎 附:完整源码与模板(可直接复现)
完整 Java 测试代码
JOption option = new JOption();option.title().text("信用评级指标对比").subtext("2020-2023年度(单位:万元)");option.tooltip().trigger(JTrigger.axis);JCategoryAxis xAxis = new JCategoryAxis();xAxis.data("2020年", "2021年", "2022年", "2023年");option.xAxis(xAxis);option.yAxis(new JValueAxis());JBar revenue = new JBar();revenue.name("营业收入").data(8560, 12340, 18920, 25680);JBar profit = new JBar();profit.name("净利润").data(1240, 2150, 3890, 5420);option.series(revenue, profit);String svg = JChartRendererFactory.renderChart(JChartType.BAR, option);File dir = new File(path + "creditreport");if (!dir.exists()) {dir.mkdirs();}FileOutputStream fileOutputStream = new FileOutputStream(path + "creditreport/01_bar.pdf");JPdfConfig config = new JPdfConfig();// 开启 flex 行布局:图表与其右侧的指标说明面板在宽度允许时并排,而不是换行堆叠。config.getLayoutConfig().setFlexLayout(true);JQuickPdfFactory factory = new JQuickPdfFactory(config);factory.bind("svg", svg);byte[] bytes = factory.executeResource("report/credit_report_01_bar.txt");fileOutputStream.write(bytes);fileOutputStream.close();完整模板文件(txt)
<pdf><body><divstyle="textAlignment:center; marginBottom:5px; paddings:16px; background:#3E6B9D; color:white; borderRadius:4px"><h1style="textAlignment:center;color:white; marginBottom:8px; fontSize:20; bold:true">'企业信用评级报告'</h1><pstyle="textAlignment:center;color:rgba(255,255,255,0.9); fontSize:11; margin:2px">'报告编号: CR-2023-08975 | 评估日期: 2023年11月15日'</p><pstyle="textAlignment:center;color:rgba(255,255,255,0.9); fontSize:11; margin:2px">'评估机构: 四川省企业信用评估中心'</p></div><divstyle="marginBottom:3px;borderRadius:4px;"><divstyle="padding:12px"><h2style="color:#2c3e50; marginTop:0; marginBottom:8px; fontSize:14; bold:true">'四川省泡海椒科技有限公司'</h2><tablestyle="width:600px;verticalAlignment:center; fontSize:10"><tr><tdstyle="backgroundColor:#f8f9fa;padding:5px; textAlign:left;width:150px;">'统一信用代码'</td><tdstyle="backgroundColor:#f8f9fa;padding:5px;width:150px;">'91110108MA01XX1234'</td><tdstyle="backgroundColor:#f8f9fa; padding:5px; textAlign:left;width:150px;">'成立日期'</td><tdstyle="padding:5px; width:150px;">'2018年5月20日'</td></tr><tr><tdstyle="backgroundColor:#f8f9fa; padding:5px; textAlign:left;">'注册资本'</td><tdstyle="padding:5px;">'1000万元人民币'</td><tdstyle="backgroundColor:#f8f9fa; padding:5px; textAlign:left;">'法定代表人'</td><tdstyle="padding:5px;">'泡海椒'</td></tr><tr><tdstyle="backgroundColor:#f8f9fa; padding:5px; textAlign:left;">'所属行业'</td><tdstyle="padding:5px;">'软件和信息技术服务业'</td><tdstyle="backgroundColor:#f8f9fa; padding:5px; textAlign:left;">'企业规模'</td><tdstyle="padding:5px;">'中型(150-200人)'</td></tr></table></div></div><divstyle="margin-bottom:15px"><h2style="color:#3498db; borderBottom:1px solid #3498db; paddingBottom:4px; fontSize:13; marginBottom:10px">'历年财务数据(单位:万元)'</h2><tablestyle="width:600px; fontSize:10; marginTop:8px"><tr><thstyle="backgroundColor:#3498db; color:white; padding:6px; textAlign:center; border:1px solid #dee2e6">'财务指标'</th><thstyle="backgroundColor:#3498db; color:white; padding:6px; textAlign:center; border:1px solid #dee2e6">'2020年'</th><thstyle="backgroundColor:#3498db; color:white; padding:6px; textAlign:center; border:1px solid #dee2e6">'2021年'</th><thstyle="backgroundColor:#3498db; color:white; padding:6px; textAlign:center; border:1px solid #dee2e6">'2022年'</th><thstyle="backgroundColor:#3498db; color:white; padding:6px; textAlign:center; border:1px solid #dee2e6">'2023年'</th><thstyle="backgroundColor:#3498db; color:white; padding:6px; textAlign:center; border:1px solid #dee2e6">'增长率'</th></tr><tr><tdstyle="backgroundColor:#f8f9fa; padding:5px; textAlign:left; border:1px solid #dee2e6; bold:true">'营业收入'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'8,560'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'12,340'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'18,920'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'25,680'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6; color:#4CAF50; bold:true">'+44.2%'</td></tr><tr><tdstyle="backgroundColor:#f8f9fa; padding:5px; textAlign:left; border:1px solid #dee2e6; bold:true">'净利润'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'1,240'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'2,150'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'3,890'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'5,420'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6; color:#4CAF50; bold:true">'+63.8%'</td></tr><tr><tdstyle="backgroundColor:#f8f9fa; padding:5px; textAlign:left; border:1px solid #dee2e6; bold:true">'总资产'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'15,800'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'22,450'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'31,200'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'42,800'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6; color:#4CAF50; bold:true">'+39.5%'</td></tr><tr><tdstyle="backgroundColor:#f8f9fa; padding:5px; textAlign:left; border:1px solid #dee2e6; bold:true">'净资产'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'9,200'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'13,800'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'19,500'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'26,400'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6; color:#4CAF50; bold:true">'+42.1%'</td></tr><tr><tdstyle="backgroundColor:#f8f9fa; padding:5px; textAlign:left; border:1px solid #dee2e6; bold:true">'毛利率'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'42.5%'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'45.8%'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'48.2%'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6">'51.6%'</td><tdstyle="padding:5px; textAlign:right; border:1px solid #dee2e6; color:#4CAF50; bold:true">'+9.1%'</td></tr></table></div><divstyle="margin-bottom:15px"><h2style="color:#3498db; border-bottom:1px solid #3498db; padding-bottom:4px; font-size:13px; margin-bottom:10px">'关键经营指标'</h2><liststyle="list-style-type:none; padding-left:0; font-size:11px"><listyle="margin-bottom:8px; padding:10px; background-color:#f8f9fa; border-left:4px solid #4CAF50; border-radius:0 4px 4px 0">'营收增长率:连续三年保持40%以上增长,2023年达到25,680万元'</li><listyle="margin-bottom:8px; padding:10px; background-color:#f8f9fa; border-left:4px solid #2196F3; border-radius:0 4px 4px 0">'利润率提升:净利率从14.5%提升至21.1%,盈利能力显著增强'</li><listyle="margin-bottom:8px; padding:10px; background-color:#f8f9fa; border-left:4px solid #FF9800; border-radius:0 4px 4px 0">'研发投入:研发费用年均增长38.9%,占营收比例达13.3%'</li><listyle="margin-bottom:8px; padding:10px; background-color:#f8f9fa; border-left:4px solid #9C27B0; border-radius:0 4px 4px 0">'人才结构:技术研发人员占比42%,硕士以上学历占35%'</li><listyle="margin-bottom:8px; padding:10px; background-color:#f8f9fa; border-left:4px solid #E91E63; border-radius:0 4px 4px 0">'客户质量:服务15家上市公司,客户续约率92%,大客户满意度4.8/5.0'</li><listyle="margin-bottom:8px; padding:10px; background-color:#f8f9fa; border-left:4px solid #009688; border-radius:0 4px 4px 0">'资产质量:资产负债率维持在38%左右,流动比率2.8,偿债能力良好'</li></list></div><divstyle="margin-bottom:15px"><h2style="color:#3498db; border-bottom:1px solid #3498db; padding-bottom:4px; font-size:13px; margin-bottom:10px">'信用评级条形图'</h2><divstyle="display:flex; justify-content:space-between; margin-top:10px"><svgstyle="width:340px;height:340px">${svg}</svg><divstyle="width:290px"><divstyle="backgroundColor:#f8f9fa; padding:15px; borderRadius:4px"><h3style="color:#2c3e50; margin-top:0; fontSize:12; marginBottom:8px">'评级指标说明'</h3><liststyle="list-style-type:none; padding-left:0; font-size:10px"><listyle="margin-bottom:6px; padding:5px; border-left:3px solid #3498db"><spanstyle="bold:true; color:#2c3e50">'财务状况(90分):' </span>'营收稳定增长'</li><listyle="margin-bottom:6px; padding:5px; border-left:3px solid #2ecc71"><spanstyle="bold:true; color:#2c3e50">'经营能力(95分):' </span>'客户留存率高'</li><listyle="margin-bottom:6px; padding:5px; border-left:3px solid #e74c3c"><spanstyle="bold:true; color:#2c3e50">'偿债能力(85分):' </span>'负债率合理'</li><listyle="margin-bottom:6px; padding:5px; border-left:3px solid #f39c12"><spanstyle="bold:true; color:#2c3e50">'发展潜力(92分):' </span>'研发投入增加'</li></list></div></div></div></div><divstyle="margin-bottom:15px; background:linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%); padding:15px; border-radius:4px; border-left:4px solid #2196F3"><h2style="color:#1565c0; margin-top:0; font-size:13px; margin-bottom:8px">'综合评价'</h2><pstyle="text-align:justify; margin-bottom:8px; font-size:11px">'四川省泡海椒科技有限公司作为国家级高新技术企业和专精特新"小巨人"企业,在云计算和大数据领域表现出色。''公司财务状况健康,经营能力突出,技术创新能力强,具备良好的发展潜力。信用评级为AAA级,展望稳定。'</p><pstyle="text-align:right; font-size:8px; color:#d32f2f; margin-top:2px; margin-bottom:0">'认证通过'</p></div><divstyle="text-align:center; margin-top:15px; padding-top:12px; border-top:1px solid #dee2e6; color:#6c757d; font-size:9px"><p>'本报告依据公开信息和专业评估模型生成,仅供参考 | 报告生成时间: 2025年09月06日'</p><p>'© 2023 企业信用评估中心 版权所有 | 认证编号: CE-2023-001'</p></div></body></pdf>真实渲染效果预览
以下为上述代码 + 模板真实运行生成的 PDF 首屏预览(由 jquick-pdf 渲染输出,非示意图):

- Linux 服务器上「好了」















































































