使用pdfbox从单独的pdf(不同页面大小)添加页面作为图层

Add page as layer from separate pdf(different page size) using pdfbox

如果页面大小不同,如何将页面从外部 pdf 文档添加到目标 pdf? 这是我想要完成的:

我尝试使用 LayerUtility(如本例 PDFBox LayerUtility - Importing layers into existing PDF),但是一旦我从外部 pdf 导入页面,过程就会挂起:

PDDocument destinationPdfDoc = PDDocument.load(fileInputStream);
PDDocument externalPdf = PDDocument.load(EXTERNAL PDF);

List<PDPage> destinationPages = destinationPdfDoc.getDocumentCatalog().getAllPages();

LayerUtility layerUtility = new LayerUtility(destinationPdfDoc);

// process hangs here
PDXObjectForm firstForm = layerUtility.importPageAsForm(externalPdf, 0);

AffineTransform affineTransform = new AffineTransform();
layerUtility.appendFormAsLayer(destinationPages.get(0), firstForm, affineTransform, "external page");


destinationPdfDoc.save(resultTempFile);

destinationPdfDoc.close();
externalPdf.close();

我做错了什么?

PDFBox 依赖项

主要问题是 PDFBox 具有三个核心组件和一个必需的依赖项。缺少一个核心组件。

OP 在评论中澄清说

Actually process doesn't hangs, the file is just not created at all.

由于这听起来可能存在异常或错误,因此在聊天中提议 将代码封装为 try { ... } catch (Throwable t) { t.printStackTrace(); }。事实上,

java.lang.NoClassDefFoundError: org/apache/fontbox/util/BoundingBox 
    at org.apache.pdfbox.util.LayerUtility.importPageAsForm(LayerUtility.java:203) 
    at org.apache.pdfbox.util.LayerUtility.importPageAsForm(LayerUtility.java:135) 
    at ...

事实证明,OP 的设置中缺少 fontbox.jar。

PDFBox 版本 1.8.x 描述了依赖关系 here。尤其是pdfboxfontboxjempbox这三个核心组件都应该出现在相同的版本,并且有所需的依赖项 commons-logging.

一旦添加了缺失的组件,样本就可以正常工作。

正在定位导入的页面

导入的页面可以通过AffineTransform参数中的翻译定位到目标页面。此参数还允许进行其他转换,例如缩放、旋转、镜像、倾斜...*

对于原始示例文件,此 PDF 页面

已添加到此页面

导致此页面

OP 然后想知道

how to position the imported layer

layerUtility.appendFormAsLayer 调用中的参数是 AffineTransform affineTransform。 OP 在此处使用 new AffineTransform() 创建单位矩阵,进而导致源页面添加到坐标系的原点,在本例中为底部。

通过使用翻译而不是身份,例如

PDRectangle destCrop = destinationPages.get(0).findCropBox();
PDRectangle sourceBox = firstForm.getBBox();
AffineTransform affineTransform = AffineTransform.getTranslateInstance(0, destCrop.getUpperRightY() - sourceBox.getHeight());

可以将源页面放在别处,例如在顶部:

PDFBox LayerUtility 的期望

不幸的是 layerUtility.appendFormAsLayer 没有重置图形上下文就将表单附加到页面。

layerUtility.appendFormAsLayer 使用此代码添加额外的内容流:

PDPageContentStream contentStream = new PDPageContentStream(
        targetDoc, targetPage, true, !DEBUG);

不幸的是,此构造函数生成的内容流继承了目标页面现有内容末尾的图形状态。这尤其意味着用户 space 坐标系可能不再处于默认状态。一些软件,例如镜像坐标系使 y 坐标向下增加。

如果相反

PDPageContentStream contentStream = new PDPageContentStream(
        targetDoc, targetPage, true, !DEBUG, true);

已被使用,图形状态将被重置为默认状态,因此是已知的。

因此,就其本身而言,此方法不能以受控方式用于任意输入。

幸运的是,LayerUtility 也有一种方法 wrapInSaveRestore(PDPage) 可以通过操纵给定页面的内容使末尾具有默认图形状态来克服此弱点。

因此,应该替换

layerUtility.appendFormAsLayer(destinationPages.get(0), firstForm, affineTransform, "external page");

来自

PDPage destPage = destinationPages.get(0);
layerUtility.wrapInSaveRestore(destPage);
layerUtility.appendFormAsLayer(destPage, firstForm, affineTransform, "external page");