PDFBox:如何处理PDF中嵌套PDFormXObject内的图片并替换?
我正在编写代码检查PDF中的每张图片并做图像处理。先写了简单的页面解析器,能检测PDImageXObject并替换处理后的图片,简单场景下可行,但实际PDF里有PDFormXObject嵌套引用最终指向PDImageXObject的情况,得扩展代码。
我以示例代码PrintImageLocations.java为起点,但相关讨论里提到这个示例找不到嵌套在PDFormXObject里的图片。
现有解析PDF页面的代码如下:
protected void processOperator(Operator operator, List<COSBase> operands) throws IOException { // Check for "Do" then examine the previous token for the PDXImageObject and then process the image content String operation = operator.getName(); if (OperatorName.DRAW_OBJECT.equals(operation)) { COSName objectName = (COSName) operands.get( 0 ); PDXObject xobject = getResources().getXObject( objectName ); if( xobject instanceof PDImageXObject) { PDImageXObject image = (PDImageXObject)xobject; BufferedImage bi = ((PDImageXObject)xobject).getImage(); BufferedImage newBufferedImage = renderBufferedImage (bi); } } }
之前的做法是把修改后的PDImageXObject放回页面级资源,只适用于简单场景,没法处理PDFormXObject的情况(得放进关联PDFormXObject的资源里)。
想请教:是否可以确定PDImageXObject的父对象,在对应层级替换?还是需要用Map存储原对象名称和处理后的图片,等processOperator完成后统一替换?
1. 递归遍历所有嵌套XObject
要处理PDFormXObject嵌套的情况,核心是递归遍历所有XObject资源——不管是页面级的还是表单内部的。当遇到PDFormXObject时,解析它的内容流,递归处理其中的Do操作,就能触达嵌套的PDImageXObject。
修改思路:
- 在
processOperator中,判断xobject是PDFormXObject时,创建自定义的PDFStreamEngine实例,解析该表单的内容流,复用图片处理逻辑。 - 这样就能深入嵌套结构找到所有图片,同时直接获取当前表单的资源对象,把处理后的图片替换到对应表单的资源中,而非页面级资源。
补充处理PDFormXObject的代码片段:
if (xobject instanceof PDFormXObject) { PDFormXObject form = (PDFormXObject) xobject; // 递归处理表单内的内容流 MyImageProcessingEngine formProcessor = new MyImageProcessingEngine(); formProcessor.processPage(form); }
这里的MyImageProcessingEngine是你自定义的继承PDFStreamEngine的类,和处理页面的逻辑一致,确保表单内的图片能被遍历处理。
2. 直接在对应层级替换图片
不需要额外用Map存储后统一替换,递归遍历过程中可以直接获取当前图片所属的资源对象:
- 处理页面级
PDImageXObject时,用getResources()拿到页面资源,替换其中的对象; - 处理表单内
PDImageXObject时,用form.getResources()拿到表单资源,替换对应COSName指向的图片。
处理后的替换逻辑示例:
// 假设已生成处理后的BufferedImage newBufferedImage if (xobject instanceof PDImageXObject) { PDImageXObject image = (PDImageXObject)xobject; BufferedImage bi = image.getImage(); BufferedImage newBufferedImage = renderBufferedImage(bi); // 将BufferedImage转为PDImageXObject PDImageXObject newImage = PDImageXObject.createFromByteArray(document, imageToBytes(newBufferedImage), "processed-image"); // 替换当前资源中的图片 getResources().putXObject(objectName, newImage); }
如果是在表单内处理,把getResources()替换为当前表单的form.getResources()即可。
3. 注意事项
- 处理表单时,用Set记录已处理的表单对象ID,避免因循环引用导致的重复处理或死循环;
- 生成新
PDImageXObject时,要关联当前PDF文档对象,确保资源能正确写入文档。
内容的提问来源于stack exchange,提问作者Invisible Coder

