基于PDFBox更新PDF时的分词与字符显示问题咨询
问题描述
想咨询是否可通过PDFBox库对PDF指定位置进行更新。目前使用网上的Groovy方案,但getTokens()方法无法正确拆分词汇,导致难以定位需修改的内容。
尝试搜索“Hours worked”并替换为“Hours worked: 2”,遇到两个问题:
- 执行后日志显示token生成异常:同一行的内容被拆分为两个不同的COSArray,导致难以精准搜索特定短语;
- 找到目标短语并替换后,PDF中出现奇怪字符。
现寻求以下两个问题的解决方案:
- 如何设置解析器的token行为,将整段短语作为单个token直到遇到特殊字符?
- 如何对新PDF中的字符进行正确格式化?
现有Groovy代码
for( int i = 0; i < dataContext.getDataCount(); i++ ) { InputStream is = dataContext.getStream(i); Properties props = dataContext.getProperties(i); String searchString= "Hours worked"; String replacement = "Hours worked: 2"; File file = new File("\\****\UKDC\GFS\PRE\PREPROD\Alchemer\Template\***.pdf"); PDDocument doc = PDDocument.load(file); for ( PDPage page : doc.getPages() ) { PDFStreamParser parser = new PDFStreamParser(page); parser.parse(); List tokens = parser.getTokens(); logger.info("in Page"); for (int j = 0; j < tokens.size(); j++) { logger.info("tokens:"+tokens[j]); Object next = tokens.get(j); //logger.info("in Object"); if (next instanceof Operator) { Operator op = (Operator) next; String pstring = ""; int prej = 0; //Tj and TJ are the two operators that display strings in a PDF if (op.getName().equals("Tj")) { logger.info("in Tj"); // Tj takes one operator and that is the string to display so lets update that operator COSString previous = (COSString) tokens.get(j - 1); String string = previous.getString(); logger.info("previousString:"+string); string = string.replaceFirst(searchString, replacement); previous.setValue(string.getBytes()); } else if (op.getName().equals("TJ")) { logger.info("in TJ:"+ op.getName()); COSArray previous = (COSArray) tokens.get(j - 1); logger.info("previous:"+previous); for (int k = 0; k < previous.size(); k++) { Object arrElement = previous.getObject(k); if (arrElement instanceof COSString) { COSString cosString = (COSString) arrElement; String string = cosString.getString(); logger.info("string:"+string); if (j == prej || string.equals(" ") || string.equals(":") || string.equals("-")) { pstring += string; } else { prej = j; pstring = string; } } } logger.info("pstring:"+pstring); if (searchString.equals(pstring.trim())) { logger.info("in searchString"); COSString cosString2 = (COSString) previous.getObject(0); cosString2.setValue(replacement.getBytes()); int total = previous.size()-1; for (int k = total; k > 0; k--) { previous.remove(k); } } } } } logger.info("in updatedStream"); // now that the tokens are updated we will replace the page content stream. PDStream updatedStream = new PDStream(doc); OutputStream out = updatedStream.createOutputStream(COSName.FLATE_DECODE); ContentStreamWriter tokenWriter = new ContentStreamWriter(out); tokenWriter.writeTokens(tokens); logger.info("in tokenWriter"); out.close(); page.setContents(updatedStream); doc.save("\\***\UKDC\GFS\PRE\PREPROD\Alchemer\***1.pdf"); }
解决方案
问题1:让解析器将整段短语作为单个token
PDFBox的PDFStreamParser是严格按照PDF内容流语法拆分token的,无法直接设置让它把整段短语合并成单个token——因为PDF内容流里的文本本来就可能被拆分成多个COSString(比如为了调整字符间距、换行,或是生成PDF时的排版策略导致)。
解决思路是自己在解析时合并相邻文本token:
- 遍历token列表时,跟踪当前文本片段,把属于同一语义块的
COSString合并(比如在同一个文本对象BT/ET之间的连续文本); - 处理
TJ操作符对应的COSArray时,先过滤掉用于字符间距调整的数字,把连续的COSString拼接成完整短语后再搜索匹配; - 匹配到目标短语后,不要直接替换第一个元素并删除其他元素,而是根据原有的间距控制重新构建
COSArray,保留必要的排版信息。
问题2:解决替换后出现奇怪字符的问题
奇怪字符通常由编码不匹配或字体不支持导致,可通过以下方式解决:
- 修正编码问题:PDF文本使用的是字体编码(不一定是UTF-8),直接用
string.getBytes()会用系统默认编码,导致编码不匹配。替换代码里的cosString2.setValue(replacement.getBytes())为cosString2.setValue(replacement),利用COSString的内置方法自动处理PDF字符串编码; - 检查字体支持:确认原PDF的字体是否支持替换文本中的所有字符,若包含特殊符号,需确保字体覆盖对应字符集;
- 优化保存逻辑:原代码在循环每个页面时都调用
doc.save(),会重复保存文档,建议将doc.save()移到所有页面处理完成后执行,避免不必要的IO操作。
内容的提问来源于stack exchange,提问作者Raffaele
相关产品推荐
相关产品推荐

