使用Apache POI与Pdfbox将.docx转PDF时样式丢失问题求助
解决DOCX转PDF时页边距偏移、图片丢失的问题
我之前也踩过这个坑——用Apache POI的XWPFDocument配合PDF转换器时,默认配置完全不继承DOCX的页面样式,还经常丢图片。给你拆解下问题根源和具体的解决办法:
一、页边不匹配与文本偏移问题
核心原因是默认的PdfOptions不会读取DOCX里的页面设置,直接用了转换器自带的默认边距和尺寸,自然会和原文档错位。
解决办法是手动读取DOCX的页边距和页面方向,同步到PDF选项里:
- 从
XWPFDocument中提取原文档的页面参数(注意DOCX用的是twip单位,要转成PDF用的磅,比例是1twip=0.05磅) - 把这些参数设置到
PdfOptions中
代码示例片段:
// 读取DOCX的页面设置 XWPFDocument document = new XWPFDocument(doc); CTPageSz pageSz = document.getDocument().getBody().getSectPr().getPgSz(); CTPageMar pageMar = document.getDocument().getBody().getSectPr().getPgMar(); // 创建自定义PDF选项,转换单位并设置边距 PdfOptions options = PdfOptions.create(); float leftMargin = pageMar.getLeft().intValue() / 20f; float rightMargin = pageMar.getRight().intValue() / 20f; float topMargin = pageMar.getTop().intValue() / 20f; float bottomMargin = pageMar.getBottom().intValue() / 20f; options.marginLeft(leftMargin) .marginRight(rightMargin) .marginTop(topMargin) .marginBottom(bottomMargin); // 同步页面方向(横版/竖版) if (pageSz.getOrient() == STPageOrientation.LANDSCAPE) { options.pageSize(PageSize.A4.rotate()); } else { options.pageSize(PageSize.A4); }
二、图片丢失问题
常见的两个诱因:
- DOCX里的图片是外部链接型而非嵌入型,XWPFDocument读取不到外部资源
- 图片尺寸超出页面范围被自动截断,或者转换器默认的图片处理器不兼容某些格式
对应的解决步骤:
- 确保DOCX里的图片是嵌入状态(保存DOCX时选择“嵌入所有图片”)
- 给
PdfOptions自定义图片处理器,自动缩放图片以适应页面,同时确保图片流被正确读取 - 检查依赖版本,确保POI和PDF转换器的版本完全一致(版本不兼容是隐形坑)
图片处理器的代码示例:
options.imageProvider(new ImageProvider() { @Override public BufferedInputStream getImage(ImageEntity imageEntity) throws IOException { // 确保图片流被正确读取 return new BufferedInputStream(imageEntity.getInputStream()); } @Override public float getImageScale(ImageEntity imageEntity) { // 自动缩放图片到页面宽度以内 float pageWidth = options.getPageSize().getWidth() - leftMargin - rightMargin; float imageWidth = imageEntity.getWidth(); return imageWidth > pageWidth ? pageWidth / imageWidth : 1.0f; } });
另外,字体缺失也会间接导致文本偏移甚至图片错位,建议给PdfOptions设置字体 fallback:
options.fontProvider((familyName, encoding, size, style, color) -> { try { return FontFactory.getFont(familyName, encoding, size, style, color); } catch (DocumentException e) { // 找不到对应字体时用Helvetica兜底 return FontFactory.getFont(FontFactory.HELVETICA, size, style, color); } });
完整修正后的代码
把上面的逻辑整合起来,完整代码如下:
InputStream doc = new FileInputStream(new File(sourcePath + name)); XWPFDocument document = new XWPFDocument(doc); // 读取DOCX页面设置 CTPageSz pageSz = document.getDocument().getBody().getSectPr().getPgSz(); CTPageMar pageMar = document.getDocument().getBody().getSectPr().getPgMar(); // 创建自定义PDF选项 PdfOptions options = PdfOptions.create(); float leftMargin = pageMar.getLeft().intValue() / 20f; float rightMargin = pageMar.getRight().intValue() / 20f; float topMargin = pageMar.getTop().intValue() / 20f; float bottomMargin = pageMar.getBottom().intValue() / 20f; options.marginLeft(leftMargin) .marginRight(rightMargin) .marginTop(topMargin) .marginBottom(bottomMargin); // 同步页面方向 if (pageSz.getOrient() == STPageOrientation.LANDSCAPE) { options.pageSize(PageSize.A4.rotate()); } else { options.pageSize(PageSize.A4); } // 设置字体兜底 options.fontProvider((familyName, encoding, size, style, color) -> { try { return FontFactory.getFont(familyName, encoding, size, style, color); } catch (DocumentException e) { return FontFactory.getFont(FontFactory.HELVETICA, size, style, color); } }); // 设置图片处理器 options.imageProvider(new ImageProvider() { @Override public BufferedInputStream getImage(ImageEntity imageEntity) throws IOException { return new BufferedInputStream(imageEntity.getInputStream()); } @Override public float getImageScale(ImageEntity imageEntity) { float pageWidth = options.getPageSize().getWidth() - leftMargin - rightMargin; float imageWidth = imageEntity.getWidth(); return imageWidth > pageWidth ? pageWidth / imageWidth : 1.0f; } }); // 执行转换并关闭流 String pdfFileName = name.replace("." + MimeUtil2.getExtension(name), ".pdf"); OutputStream out = new FileOutputStream(new File(destinationPath + pdfFileName)); PdfConverter.getInstance().convert(document, out, options); out.close(); doc.close();
额外注意事项
- 依赖版本必须统一:比如用POI 4.1.2的话,
poi-ooxml-converter-pdf也要用4.1.2,版本不兼容会出现各种奇怪的渲染问题 - 如果DOCX里有复杂样式(比如分栏、嵌套表格),可能需要额外自定义处理器,但大部分常规文档用上面的配置就能解决问题
- 确保运行环境有DOCX中用到的字体,否则会用默认字体替代,可能导致布局偏移
内容的提问来源于stack exchange,提问作者Miguel NoTeimporta




