使用PdfStamper放大复制PDF页面时注释异常问题求解
解决方案
问题根源
- 复制页面丢失注释:
PdfImportedPage仅会导入页面的内容流数据,而PDF注释是独立存储在页面对象的/Annots数组中,不会随内容流自动迁移 - 放大后注释位置偏移:修改页面尺寸和内容流坐标变换规则后,注释的坐标仍是基于原始页面坐标系定义的,没有同步调整导致错位
具体实现代码
带注释复制页面
不要用Image.getInstance包装PdfImportedPage的实现,改用内容流复制+注释迁移的方案:
// 插入和原第1页尺寸一致的空白页作为第2页 stamper.insertPage(2, reader.getPageSize(1)); // 复制原第1页的内容流到新页面 PdfImportedPage originPage1 = stamper.getImportedPage(reader, 1); stamper.getOverContent(2).addTemplate(originPage1, 0, 0); // 迁移原第1页的所有注释到新页面 PdfDictionary originPageDict = reader.getPageN(1); PdfArray originAnnots = originPageDict.getAsArray(PdfName.ANNOTS); if (originAnnots != null && !originAnnots.isEmpty()) { PdfDictionary newPageDict = stamper.getReader().getPageN(2); PdfArray newAnnots = new PdfArray(); for (int i = 0; i < originAnnots.size(); i++) { // 深度复制注释对象,避免修改原文档数据 PdfDictionary newAnnot = new PdfDictionary(originAnnots.getAsDict(i)); newAnnots.add(newAnnot); } newPageDict.put(PdfName.ANNOTS, newAnnots); }
页面放大同步调整注释坐标
在原有放大逻辑的基础上,新增注释坐标修正逻辑:
for (int i = 1; i <= n; i++) { PdfDictionary page = reader.getPageN(i); PdfArray media = page.getAsArray(PdfName.CROPBOX); if (media == null) { media = page.getAsArray(PdfName.MEDIABOX); } float originalW = media.getAsNumber(2).floatValue(); float originalH = media.getAsNumber(3).floatValue(); // 调整页面尺寸 PdfArray crop = new PdfArray(); crop.add(new PdfNumber(0)); crop.add(new PdfNumber(0)); float newW = originalW * 2; float newH = originalH; crop.add(new PdfNumber(newW)); crop.add(new PdfNumber(newH)); page.put(PdfName.MEDIABOX, crop); page.put(PdfName.CROPBOX, crop); // 调整内容流坐标 stamper.getUnderContent(i).setLiteral(String.format("\nq 1 0 0 1 %.1f 0 cm\nq\n", originalW / 2)); stamper.getOverContent(i).setLiteral("\nQ\nQ\n"); // 同步调整所有注释的坐标 PdfArray annots = page.getAsArray(PdfName.ANNOTS); if (annots != null && !annots.isEmpty()) { for (int j = 0; j < annots.size(); j++) { PdfDictionary annot = annots.getAsDict(j); PdfArray rect = annot.getAsArray(PdfName.RECT); if (rect != null) { // 和内容流平移逻辑保持一致,所有x坐标右移originalW/2 float x1 = rect.getAsNumber(0).floatValue() + originalW / 2; float y1 = rect.getAsNumber(1).floatValue(); float x2 = rect.getAsNumber(2).floatValue() + originalW / 2; float y2 = rect.getAsNumber(3).floatValue(); rect.set(0, new PdfNumber(x1)); rect.set(1, new PdfNumber(y1)); rect.set(2, new PdfNumber(x2)); rect.set(3, new PdfNumber(y2)); // 移除原注释外观,让阅读器自动重新生成适配新坐标的外观,避免手动修改外观流的复杂度 annot.remove(PdfName.AP); } } } }
注意事项
- 以上代码适配iText 5版本,若使用iText 7,替换为
PdfDocument对应API即可 - 若存在签名注释、3D注释等特殊类型注释,需额外调整对应属性的坐标参数
- 若需要保留注释原始外观,需手动修改注释外观流的坐标变换矩阵,和页面内容流的变换规则保持一致
内容的提问来源于stack exchange,提问作者Jianfei Xiong
相关产品推荐
相关产品推荐

