PDF批注Flatten异常求助:旋转文档处理失效(iText 8.0.4/PDFBox)
解决方案:处理旋转PDF的批注扁平化问题
问题背景
需求是将PDF中所有批注转为嵌入图像,避免Mac Preview可移动批注。现有iText 8.0.4代码仅支持0度旋转文档,90/180/270度旋转文档处理后批注位置、方向异常。
方案1:修正iText 8代码
核心思路是根据页面旋转角度,转换批注坐标与外观方向,而非临时移除页面旋转。以下是兼容所有旋转角度的实现代码:
public static byte[] flattenAnnotations(byte[] data) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (PdfDocument pdfDocument = new PdfDocument(new PdfReader(new ByteArrayInputStream(data)), new PdfWriter(baos))) { for (int i = 1; i <= pdfDocument.getNumberOfPages(); i++) { PdfPage page = pdfDocument.getPage(i); int rotation = page.getRotation(); PdfCanvas canvas = new PdfCanvas(page); // 遍历批注副本,避免并发修改异常 List<PdfAnnotation> annotations = new ArrayList<>(page.getAnnotations()); for (PdfAnnotation annot : annotations) { if (annot instanceof PdfStampAnnotation) { PdfDictionary appearanceDict = annot.getAppearanceObject(PdfName.N); if (appearanceDict == null) continue; PdfFormXObject appearanceXObj = new PdfFormXObject((PdfStream) appearanceDict.clone()); Rectangle annotRect = annot.getRectangle().toRectangle(); Rectangle pageSize = page.getPageSize(); // 根据页面旋转转换批注坐标 AffineTransform transform = getTransformForRotation(rotation, pageSize); float[] transformedCoords = transform.transform( new float[]{annotRect.getLeft(), annotRect.getBottom(), annotRect.getRight(), annotRect.getTop()}, 0 ); Rectangle targetRect = new Rectangle( transformedCoords[0], transformedCoords[1], transformedCoords[2] - transformedCoords[0], transformedCoords[3] - transformedCoords[1] ); // 旋转批注外观以匹配页面方向 canvas.saveState(); if (rotation != 0) { float centerX = targetRect.getLeft() + targetRect.getWidth() / 2; float centerY = targetRect.getBottom() + targetRect.getHeight() / 2; canvas.concatMatrix(AffineTransform.getRotateInstance(Math.toRadians(rotation), centerX, centerY)); } canvas.addXObjectFittedIntoRectangle(appearanceXObj, targetRect); canvas.restoreState(); page.removeAnnotation(annot); } } } // 扁平化表单域 PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDocument, true); if (form != null) { form.flattenFields(); } } return baos.toByteArray(); } private static AffineTransform getTransformForRotation(int rotation, Rectangle pageSize) { AffineTransform transform = new AffineTransform(); switch (rotation) { case 90: transform.translate(pageSize.getHeight(), 0); transform.rotate(Math.toRadians(90)); break; case 180: transform.translate(pageSize.getWidth(), pageSize.getHeight()); transform.rotate(Math.toRadians(180)); break; case 270: transform.translate(0, pageSize.getWidth()); transform.rotate(Math.toRadians(270)); break; default: // 0度无需转换 break; } return transform; }
关键说明
- 不再跳过旋转页面,统一处理所有角度的页面
- 通过坐标转换,将批注原始矩形映射到旋转后的页面坐标系
- 以批注中心为原点旋转外观,保证显示方向与页面一致
- 绘制完成后移除原批注,避免可编辑状态
方案2:使用Apache PDFBox实现
若iText方案仍存在适配问题,可改用PDFBox实现,代码如下:
public static byte[] flattenAnnotationsWithPDFBox(byte[] data) throws IOException { try (PDDocument doc = PDDocument.load(data)) { for (PDPage page : doc.getPages()) { int rotation = page.getRotation(); PDPageContentStream contentStream = new PDPageContentStream( doc, page, PDPageContentStream.AppendMode.APPEND, true, true ); List<PDAnotation> annotations = new ArrayList<>(page.getAnnotations()); for (PDAnotation annot : annotations) { if (annot instanceof PDStampAnnotation) { PDAppearanceDictionary appearance = annot.getAppearance(); if (appearance == null) continue; PDAppearanceEntry normalAppearance = appearance.getNormalAppearance(); if (normalAppearance == null) continue; PDFormXObject xobject = normalAppearance.getAppearanceStream(); if (xobject == null) continue; // 转换批注矩形到旋转后页面坐标 PDRectangle rect = annot.getRectangle(); Rectangle2D.Float annotRect = new Rectangle2D.Float( rect.getLowerLeftX(), rect.getLowerLeftY(), rect.getWidth(), rect.getHeight() ); Rectangle2D.Float transformedRect = transformRectForRotation(annotRect, page.getMediaBox(), rotation); // 绘制并旋转批注外观 contentStream.saveGraphicsState(); if (rotation != 0) { float centerX = transformedRect.x + transformedRect.width / 2; float centerY = transformedRect.y + transformedRect.height / 2; contentStream.transform(Matrix.getRotateInstance(Math.toRadians(rotation), centerX, centerY)); } contentStream.drawForm(xobject, transformedRect.x, transformedRect.y, transformedRect.width, transformedRect.height); contentStream.restoreGraphicsState(); page.getAnnotations().remove(annot); } } contentStream.close(); } ByteArrayOutputStream baos = new ByteArrayOutputStream(); doc.save(baos); return baos.toByteArray(); } } private static Rectangle2D.Float transformRectForRotation(Rectangle2D.Float rect, PDRectangle pageSize, int rotation) { float pageWidth = pageSize.getWidth(); float pageHeight = pageSize.getHeight(); switch (rotation) { case 90: return new Rectangle2D.Float(rect.y, pageWidth - rect.x - rect.width, rect.height, rect.width); case 180: return new Rectangle2D.Float(pageWidth - rect.x - rect.width, pageHeight - rect.y - rect.height, rect.width, rect.height); case 270: return new Rectangle2D.Float(pageHeight - rect.y - rect.height, rect.x, rect.height, rect.width); default: return rect; } }
关键说明
- 基于PDFBox的坐标系统,直接转换批注矩形位置
- 通过矩阵旋转实现外观与页面方向对齐
- 以追加模式写入内容流,保留原页面内容
内容的提问来源于stack exchange,提问作者Akhil NagaSai
相关产品推荐
相关产品推荐

