基于jdk8和flying-saucer9.1版本生成支持中日韩语言的pdf

最近被pdf中展示日文搞破防了,事情是这样的。

需求背景:业务要根据数据库表数据生成一个pdf,作为一个凭证提供给用户查询。产品用大模型画了一个原型,看上去很专业(一开始我以为是美工出的)。然后我是开发,我要用代码实现。

需求调研

头一次做pdf,经过各种调研和尝试,最终认为还是flying-saucer最符合需求。但是实现的过程也很曲折。产品给的原型,是用大模型生成的,基于css3和h5写的,我们项目用的jdk8,flying-saucer对css只支持到2,且严格遵循xhtml语法规范。我也用大模型,让他给我把他自己生成的html,给我降低到css2和xhtml。最终deepseek胜出,生成的html基本符合规范。

技术挑战

很快就遇到了技术挑战,本来pdf只包含英文,不会涉及字体的问题。但是很快业务就说,日语和韩语也要支持。注意,就是在这里,我遇到了问题。网上有很多支持flying-saucer生成pdf支持汉语的,但是支持日语和韩语的却很少。

多次尝试

我按照大模型编程助手,很快将支持宋体的demo写出来了。可是到日语的时候,大模型给了我将近10个例子,都没有将日语给渲染出来。我再次声明一下,我是用的jdk8和flying-saucer9.1.大模型是用的Intellij IDEA的copilot chatgpt5,claude也试过了。

柳暗花明又一村

昨天一个突发想法,copilot中除了chatgpt和claude外,还有gemini,要不还gemini试试?竟然可以跑通了。

总结

主要就2点吧

1、多个大模型,多试试吧,别吊死在一棵树上。就跟以前的搜索引擎一样,多试几个模型可能会有意外的收获。以前在copilot中几乎没有用过gemini,所以思想有点固化了。

2、flying-saucer对css里面的字体名称很重要,一定要和字体文件的名称完全匹配。比如我下载的NotoSansCJK-Regular.otf 字体名称是'Noto Sans CJK Regular',而不是大模型说的'Noto Sans CJK'。

下面是我用flying-saucer9.1写的一个demo,里面用的字体放代码的resource/fonts/目录下面。字体直接在网上搜,就能找到。


import org.xhtmlrenderer.pdf.ITextRenderer;
import com.lowagie.text.pdf.BaseFont;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.nio.file.Paths;

public class PdfDemoMain {

    public static void main(String[] args) throws Exception {
        // 资源字体路径(请把字体文件放在 src/main/resources/fonts/)
        String[] resourceFonts = {
                "/fonts/NotoSansCJK-Regular.otf", // 日语
        };

        // 把字体从 resources 写为临时文件并注册到 renderer
        ITextRenderer renderer = new ITextRenderer();
        for (String resPath : resourceFonts) {
            File f = copyResourceToTempFile(resPath);
            if (f == null) {
                System.err.println("Font not found: " + resPath);
                return;
            }
            // 注册字体以支持 Unicode(IDENTITY_H),并嵌入字体
            renderer.getFontResolver().addFont(f.getAbsolutePath(), BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
        }

        // HTML 内容,包含中/日/韩示例文本;CSS 指定通用字体族
        String html = "<html lang='en'>\n"
                + "<head>\n"
                + "  <meta charset='utf-8'/>\n"
                + "  <meta name='viewport' content='width=device-width,initial-scale=1'/>\n"
                + "  <style>\n"
                + "    @page { size: A4; margin: 18mm; }\n"
                + "    body { font-family: 'Noto Sans CJK Regular', sans-serif; font-size:12pt; color:#222; line-height:1.6; }\n"
                + "    header { text-align:center; margin-bottom:12mm; }\n"
                + "    h1 { font-size:18pt; margin:0; }\n"
                + "    .section { border:1px solid #e6e6e6; padding:10px 12px; border-radius:6px; margin-bottom:8mm; }\n"
                + "    .label { font-weight:600; margin-bottom:6px; display:block; }\n"
                + "    p { margin:6px 0; }\n"
                + "    .footer { font-size:9pt; color:#666; text-align:right; margin-top:14mm; }\n"
                + "  </style>\n"
                + "</head>\n"
                + "<body>\n"
                + "  <header>\n"
                + "    <h1>多语言示例 — 中文 / 日本語 / 한국어</h1>\n"
                + "  </header>\n"
                + "\n"
                + "  <div class='section'>\n"
                + "    <span class='label'>中文 (zh)</span>\n"
                + "    <p class='zh' lang='zh-CN'>中文:你好,世界!这是使用 Flying‑Saucer 生成的 PDF</p>\n"
                + "  </div>\n"
                + "\n"
                + "  <div class='section'>\n"
                + "    <span class='label'>日本語 (ja)</span>\n"
                + "    <p class='ja' lang='ja'>日本語:こんにちは、世界!これは Flying‑Saucer で生成された PDF です</p>\n"
                + "  </div>\n"
                + "\n"
                + "  <div class='section'>\n"
                + "    <span class='label'>한국어 (ko)</span>\n"
                + "    <p class='ko' lang='ko'>한국어:안녕하세요, 세계! 이것은 Flying‑Saucer로 생성된 PDF입니다. </p>\n"
                + "  </div>\n"
                + "\n"
                + "  <div class='footer'>生成时间: " + java.time.LocalDateTime.now() + "</div>\n"
                + "</body>\n"
                + "</html>";

        // 渲染并写入文件
        renderer.setDocumentFromString(html);
        renderer.layout();

        File out = Paths.get("output-multilang.pdf").toFile();
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            renderer.createPDF(baos);
            Files.write(out.toPath(), baos.toByteArray());
            System.out.println("PDF generated: " + out.getAbsolutePath());
        } catch (IOException e) {
            System.err.println("PDF writing error: " + e.getMessage());
        }
    }

    private static File copyResourceToTempFile(String resourcePath) {
        try (InputStream is = PdfDemoMain.class.getResourceAsStream(resourcePath)) {
            if (is == null) return null;
            File tmp = Files.createTempFile("font-", ".ttf").toFile();
            tmp.deleteOnExit();
            Files.copy(is, tmp.toPath(), StandardCopyOption.REPLACE_EXISTING);
            return tmp;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值