用 Java 中的 PDFbox 替换或删除 PDF 中的文本
Posted
技术标签:
【中文标题】用 Java 中的 PDFbox 替换或删除 PDF 中的文本【英文标题】:Replace or remove text from PDF with PDFbox in Java 【发布时间】:2020-08-26 06:54:33 【问题描述】:我正在尝试使用 PDFBOX 2.0 替换空白或删除文本模式(在我的情况下,我想从所有 PDF 中删除所有“[QR]”字),但我找不到任何适用于我。
我尝试了itext,但还是一样,没有任何效果。
我的 pdf 中的“[QR]”字符串在创建 PDF 后被编辑,也许这就是它们不显示为 tj 运算符的原因?
我的主要:
replaceText(documentoPDF, "[QR]", "");
我的方法(我打印了 Tj 值并且我的模式没有出现在那里):
public void replaceText(PDDocument documentoPDF, String searchString, String replacement) throws IOException
for ( PDPage page : documentoPDF.getPages())
PDFStreamParser parser = new PDFStreamParser(page);
parser.parse();
List<?> tokens = parser.getTokens();
for (int j = 0; j < tokens.size(); j++)
Object next = tokens.get(j);
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"))
// 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();
string = string.replaceFirst(searchString, replacement);
previous.setValue(string.getBytes());
else
if (op.getName().equals("TJ"))
COSArray previous = (COSArray) tokens.get(j - 1);
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();
if (j == prej)
pstring += string;
else
prej = j;
pstring = string;
System.out.println(pstring.trim());
if (searchString.equals(pstring.trim()))
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);
// now that the tokens are updated we will replace the page content stream.
PDStream updatedStream = new PDStream(documentoPDF);
OutputStream out = updatedStream.createOutputStream(COSName.FLATE_DECODE);
ContentStreamWriter tokenWriter = new ContentStreamWriter(out);
tokenWriter.writeTokens(tokens);
out.close();
page.setContents(updatedStream);
documentoPDF.save("resources\\resultado\\nuevo.pdf");
这是一个带有一些 [QR] 模式的 pdf 示例:http://www.mediafire.com/file/9w3kkc4yozwsfms/file
如果有人可以提供帮助,我将不胜感激。
如果你需要,我可以上传我的整个项目
提前致谢。
【问题讨论】:
这不起作用的原因很简单——您完全忽略了该文本字体的编码。在内容流中实际上有[( >) ( 4) ( 5) ( @) ] TJ
指令(“>”、“4”、“5”和“@”之前的“空格”实际上是零字节,0x00)。因此,显然编码是一些 16 位编码,也没有自然嵌入 ASCII。
那么,做我想做的事是不可能的?我是 pdfbox 中的菜鸟,我不知道如何使用其他编码或转换它:(
这不是不可能的。至少通常不会;存在用于文本提取的信息不完整或不正确的 PDF,这通常使您的任务无法完成。对于其他 PDF,它只是比您的方法更复杂。
那么,有什么帮助吗?我怎样才能重新编码,并能够取出并比较它的纯文本,从而替换它?
【参考方案1】:
正如 cmets 中已经提到的,您的代码不起作用的原因很简单 - 您完全忽略了该文本字体的编码。在内容流中实际上有[( >) ( 4) ( 5) ( @) ] TJ
指令(“>”、“4”、“5”和“@”之前的“空格”实际上是零字节,0x00)。因此,显然编码是一些 16 位编码,另外自然没有嵌入 ASCII。
要正确考虑字体,必须跟踪当前字体。这意味着解析整个内容流并分析文本字体设置调用、保存图形状态调用和恢复图形状态调用。然后你必须从正确的资源中检索正确的字体对象。
所有这些实际上已经由用于例如的 PDFBox 内容解析框架完成。文本提取。因此,我们可以围绕这个框架创建一个内容流编辑器。
其实这也已经做了,见this answer中的PdfContentStreamEditor
。
在您的文档中,要删除的文本块是由一个文本绘制指令绘制的,每个指令只绘制一个要删除的文本块,我们可以简单地查看当前指令绘制的文本,然后决定是否保留指令:
PDDocument document = ...;
for (PDPage page : document.getDocumentCatalog().getPages())
PdfContentStreamEditor editor = new PdfContentStreamEditor(document, page)
final StringBuilder recentChars = new StringBuilder();
@Override
protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code, Vector displacement)
throws IOException
String string = font.toUnicode(code);
if (string != null)
recentChars.append(string);
super.showGlyph(textRenderingMatrix, font, code, displacement);
@Override
protected void write(ContentStreamWriter contentStreamWriter, Operator operator, List<COSBase> operands) throws IOException
String recentText = recentChars.toString();
recentChars.setLength(0);
String operatorString = operator.getName();
if (TEXT_SHOWING_OPERATORS.contains(operatorString) && "[QR]".equals(recentText))
return;
super.write(contentStreamWriter, operator, operands);
final List<String> TEXT_SHOWING_OPERATORS = Arrays.asList("Tj", "'", "\"", "TJ");
;
editor.processPage(page);
document.save("nuevo-noQrText.pdf");
(EditPageContent 测试testRemoveQrTextNuevo
)
根据您的 PDFBox 版本,showGlyph
覆盖方法可能有第五个参数;因此,请检查您的 PDFBox 副本的showGlyph
签名,如果此代码不起作用,请进行调整。感谢@DanielNorberg 的提示!
结果二维码下方的“[QR]”文字消失了,例如
成为
【讨论】:
我尝试了这个答案中的代码和你的 PdfContentStreamEditor,它就像一个魅力,除了我必须添加 String 作为 showGlyph 的第四个参数。我正在使用 PDF 框 2.0.19,showGlyph 采用五个参数:PDFStreamEngine.showGlyph(矩阵 textRenderingMatrix,PDFont 字体,int 代码,字符串 unicode,矢量位移)。自从您写下答案后,也许该方法发生了变化?但是,在更改之后,我可以成功地从 PDF 中删除文本,而不会改变其他内容的布局。感谢您在这里分享您的知识,非常有帮助! @DanielNorberg “我使用的是 PDF box 2.0.19,showGlyph 需要五个参数” - 啊,很好找;在 3.0.0-SNAPSHOT 开发分支中,一个参数(代码中不再使用)已被删除。以上是关于用 Java 中的 PDFbox 替换或删除 PDF 中的文本的主要内容,如果未能解决你的问题,请参考以下文章
使用 PDFBox 编辑 pdf 中的内容会从 pdf 中删除最后一行