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

PDFBox如何设置文本写入的备用字体?解决中文字符无字形报错

在PDFBox中配置备用字体解决无字形(Glyph)错误

这个问题我之前也碰到过!OpenSans这类西文字体根本没有中文字形,PDFBox遇到找不到的字符就会直接抛错,解决的核心思路就是自己实现字体 fallback 逻辑——把文本拆分成主字体能处理的片段和需要用备用字体的部分,分别绘制。下面是具体的实现步骤和代码:

1. 加载主字体和备用字体

首先你需要加载你的主西文字体(比如OpenSans-Regular),再加载一款支持中文的备用字体(比如Noto Sans SC、本地的黑体/宋体都可以),注意用PDType0Font加载,它支持多语言CID字体,是PDFBox推荐的方式:

// 加载主字体(确保字体文件路径正确)
PDType0Font mainFont = PDType0Font.load(document, new File("OpenSans-Regular.ttf"));
// 加载备用中文字体(这里用Noto Sans SC举例,替换成你有的字体文件)
PDType0Font fallbackFont = PDType0Font.load(document, new File("NotoSansSC-Regular.ttf"));

2. 编写文本拆分工具方法

我们需要一个方法,把输入的文本拆分成「主字体可渲染」和「需要备用字体」的片段,每个片段对应合适的字体:

// 辅助类:存储文本片段和对应的字体
static class TextSegment {
    String text;
    PDType0Font font;

    TextSegment(String text, PDType0Font font) {
        this.text = text;
        this.font = font;
    }
}

// 拆分文本的核心方法
private List<TextSegment> splitTextByFont(String text, PDType0Font mainFont, PDType0Font fallbackFont) throws IOException {
    List<TextSegment> segments = new ArrayList<>();
    StringBuilder currentSegment = new StringBuilder();
    PDType0Font currentFont = null;

    for (char c : text.toCharArray()) {
        // 判断当前字符是否能被主字体渲染
        PDType0Font suitableFont = mainFont.hasGlyph(c) ? mainFont : fallbackFont;
        
        if (currentFont == null) {
            currentFont = suitableFont;
        } else if (currentFont != suitableFont) {
            // 切换字体时,把当前片段加入列表,重置状态
            segments.add(new TextSegment(currentSegment.toString(), currentFont));
            currentSegment.setLength(0);
            currentFont = suitableFont;
        }
        currentSegment.append(c);
    }

    // 把最后一个片段加入列表
    if (currentSegment.length() > 0) {
        segments.add(new TextSegment(currentSegment.toString(), currentFont));
    }
    return segments;
}

3. 分片段绘制文本

最后,我们遍历拆分后的文本片段,用对应的字体依次绘制,还要注意更新X坐标避免文本重叠:

float startX = 50;
float startY = 700;
float fontSize = 12;
float currentXOffset = 0;

// 拆分目标文本
List<TextSegment> textSegments = splitTextByFont("Hello 世界 笑!", mainFont, fallbackFont);

contentStream.beginText();
// 初始位置
contentStream.newLineAtOffset(startX, startY);

for (TextSegment segment : textSegments) {
    // 设置当前片段的字体
    contentStream.setFont(segment.font, fontSize);
    // 绘制文本
    contentStream.showText(segment.text);
    // 计算当前文本的宽度,更新偏移量
    float textWidth = segment.font.getStringWidth(segment.text) * fontSize / 1000;
    currentXOffset += textWidth;
    contentStream.newLineAtOffset(textWidth, 0);
}

contentStream.endText();

注意事项

  • 确保备用字体文件存在,并且支持你需要的字符集(比如如果还要支持日文,可以再加一款日文备用字体,扩展拆分逻辑即可)
  • PDType0Font会自动处理字体嵌入,避免在其他设备上无法显示的问题
  • 如果你的文本有换行需求,还需要在拆分时加入换行判断逻辑

内容的提问来源于stack exchange,提问作者V JDev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 11:10:29