You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用Apache POI与Pdfbox将.docx转PDF时样式丢失问题求助

解决DOCX转PDF时页边距偏移、图片丢失的问题

我之前也踩过这个坑——用Apache POI的XWPFDocument配合PDF转换器时,默认配置完全不继承DOCX的页面样式,还经常丢图片。给你拆解下问题根源和具体的解决办法:

一、页边不匹配与文本偏移问题

核心原因是默认的PdfOptions不会读取DOCX里的页面设置,直接用了转换器自带的默认边距和尺寸,自然会和原文档错位。

解决办法是手动读取DOCX的页边距和页面方向,同步到PDF选项里:

  1. XWPFDocument中提取原文档的页面参数(注意DOCX用的是twip单位,要转成PDF用的磅,比例是1twip=0.05磅)
  2. 把这些参数设置到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);
}

二、图片丢失问题

常见的两个诱因:

  1. DOCX里的图片是外部链接型而非嵌入型,XWPFDocument读取不到外部资源
  2. 图片尺寸超出页面范围被自动截断,或者转换器默认的图片处理器不兼容某些格式

对应的解决步骤:

  1. 确保DOCX里的图片是嵌入状态(保存DOCX时选择“嵌入所有图片”)
  2. PdfOptions自定义图片处理器,自动缩放图片以适应页面,同时确保图片流被正确读取
  3. 检查依赖版本,确保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

火山引擎 最新活动