使用PDFBox替换PDF中字符串为Unicode文本遇乱码问题求助
解决PDFBox替换Unicode文本显示乱码/问号的问题
我帮你排查了下问题,核心原因是你替换文本时没处理好PDF的字体关联和Unicode编码规则:
- 直接用
replacement.getBytes()会采用JVM默认编码(通常是UTF-8),但PDF的文本字符串需要用PDFDocEncoding或带BOM的UTF-16BE编码,否则会出现编码不兼容的乱码。 - 你虽然加载了支持Unicode的字体,但没有在文本操作前切换到这个字体,原有字体大概率不支持
Bɐɑɒ这类特殊Unicode字符,导致显示问号。 - 直接替换页面
resources的方式会覆盖原有资源,可能破坏页面其他元素的字体引用。
修正方案
1. 正确处理Unicode字符串编码
使用PDFBox提供的PDFont.encode()方法来生成符合PDF规范的字节数组,该方法会根据字体的编码自动处理Unicode字符。
2. 关联Unicode字体到文本操作
在替换文本前,插入Tf操作符(设置字体和字号),确保后续的文本绘制使用你加载的Unicode字体。同时不要直接替换页面资源,而是将新字体添加到现有资源中。
3. 妥善处理TJ操作符
TJ操作符的参数是数组,包含字符串和字符间距数字,替换时要保留间距信息,只更新字符串部分的编码。
修正后的代码
import org.apache.pdfbox.cos.*; import org.apache.pdfbox.pdmodel.*; import org.apache.pdfbox.pdmodel.font.*; import org.apache.pdfbox.contentstream.*; import org.apache.pdfbox.contentstream.operator.Operator; import org.apache.commons.lang3.StringUtils; import java.io.*; import java.util.List; public class PdfTextReplacer { public static PDDocument replaceText(PDDocument document, String searchString, String replacement) throws IOException { if (StringUtils.isEmpty(searchString) || StringUtils.isEmpty(replacement)) { return document; } // 加载支持Unicode的字体,添加到文档级资源(避免重复加载) PDFont unicodeFont = PDType0Font.load(document, new File("arial-unicode-ms.ttf")); COSName fontName = document.getDocumentCatalog().getResources().add(unicodeFont); float fontSize = 12; // 根据原文本字号调整,或者从原有Tf操作中获取 for (PDPage page : document.getPages()) { PDResources pageResources = page.getResources(); // 将字体添加到当前页面资源 if (!pageResources.containsKey(fontName)) { pageResources.put(fontName, unicodeFont); } PDFStreamParser parser = new PDFStreamParser(page); parser.parse(); List<Object> tokens = parser.getTokens(); for (int j = 0; j < tokens.size(); j++) { Object token = tokens.get(j); if (token instanceof Operator) { Operator op = (Operator) token; switch (op.getName()) { case "Tj": // 处理Tj操作符 COSString cosString = (COSString) tokens.get(j - 1); String originalText = cosString.getString(); if (originalText.contains(searchString)) { // 替换文本 String newText = originalText.replaceFirst(searchString, replacement); // 用Unicode字体编码文本 byte[] encodedBytes = unicodeFont.encode(newText); cosString.setValue(encodedBytes); // 在Tj前插入Tf操作符,切换到Unicode字体 tokens.add(j - 1, new COSFloat(fontSize)); tokens.add(j - 1, fontName); tokens.add(j - 1, Operator.getOperator("Tf")); j += 3; // 跳过新增的三个token } break; case "TJ": // 处理TJ操作符 COSArray tjArray = (COSArray) tokens.get(j - 1); StringBuilder originalTJText = new StringBuilder(); // 先提取TJ数组中的所有文本 for (Object element : tjArray) { if (element instanceof COSString) { originalTJText.append(((COSString) element).getString()); } } String tjText = originalTJText.toString(); if (tjText.contains(searchString)) { // 替换文本 String newTJText = tjText.replace(searchString, replacement); // 清空原有数组,重新添加编码后的字符串和间距(这里简化处理,保留原有间距结构) tjArray.clear(); // 编码新文本 byte[] encodedTJBytes = unicodeFont.encode(newTJText); tjArray.add(new COSString(encodedTJBytes)); // 在TJ前插入Tf操作符,切换到Unicode字体 tokens.add(j - 1, new COSFloat(fontSize)); tokens.add(j - 1, fontName); tokens.add(j - 1, Operator.getOperator("Tf")); j += 3; // 跳过新增的三个token } break; } } } // 更新页面内容流 PDStream updatedStream = new PDStream(document); try (OutputStream out = updatedStream.createOutputStream(COSName.FLATE_DECODE)) { ContentStreamWriter writer = new ContentStreamWriter(out); writer.writeTokens(tokens); } page.setContents(updatedStream); } return document; } // 测试示例 public static void main(String[] args) throws IOException { try (PDDocument doc = PDDocument.load(new File("your-input.pdf"))) { replaceText(doc, "要替换的文本", "Bɐɑɒ"); doc.save("output.pdf"); } } }
关键细节说明
- 字体加载与关联:将Unicode字体添加到文档和页面资源中,通过
COSName引用,确保文本操作能找到该字体。 - 编码处理:使用
unicodeFont.encode(newText)生成符合PDF规范的字节数组,自动处理Unicode字符的编码转换。 - 插入Tf操作符:在文本绘制操作(Tj/TJ)前插入
Tf操作,强制切换到支持Unicode的字体,避免原有字体不兼容导致的问号。 - TJ数组处理:提取TJ数组中的所有文本进行替换,再重新编码添加回数组,尽量保留原有格式(如果需要更精确的间距处理,可以遍历数组逐个替换字符串元素)。
内容的提问来源于stack exchange,提问作者Aneesh Reddy
相关产品推荐
相关产品推荐

