最近被word逼疯,不仅要导出各种报告,还要附带表格,所以写了一个docx转pdf以供参考。

  • 创建docx
  • 导出表格的时候遇到的问题
  • 完整的代码



之前用XWPFDocument生成的docx在转pdf的时候总是会报java.lang.IllegalStateException: Expecting one Styles document part, but found 0。


转出来的pdf总是会损坏,给我气够呛,网上找了办法使用doc.createStyles();


然后又给我报什么文件提前结束?或者SAXParseException; Premature end of file.


一直以为是样式的问题,最后试验发现,手动创建的docx文档是可以完美转换成pdf的,我就突然有一种想法,能不能用手动创建docx的模板进行操作呢?比如说我替换掉里面的内容再导出,其实就跟我自己创建的docx没有什么区别呢?说干就干,接下来就是正文了。


该方法不限制页数,带图片的应该也可以,大家可以试试看。

创建docx

1、先自己手动创建一个docx模板,内容是什么不重要。

docx文件转pdf java_sed


2、其次读取该模板并删除模板里面的内容,所以里面是什么内容并不重要。

File file = new File("E:/模板.docx");
XWPFDocument doc = new XWPFDocument(new FileInputStream(file));
XWPFParagraph paragraph = doc.getLastParagraph();
paragraph.removeRun(0);

3、写自己需要的内容在word里。

XWPFParagraph paragraph= doc.createParagraph(); // 新建一个标题段落对象(就是一段文字)
paragraph.setVerticalAlignment(TextAlignment.CENTER);
XWPFRun titleFun = paragraph.createRun(); // 创建文本对象
titleFun.setText(titleText); //设置标题的名字
fun.setTextPosition(20); // 设置两行之间的行间距
fun.setBold(isBold); // 加粗
fun.setColor("000000");// 设置颜色
fun.setFontSize(fontSize); // 字体大小
fun.setFontFamily("宋体");//设置字体

4、导出pdf。

OutputStream os = new FileOutputStream(new File("E:/转换结果.pdf"));
PdfOptions options = PdfOptions.create();
PdfConverter.getInstance().convert(doc, os, options);

doc.write(os);
os.flush();
os.close();

导出表格的时候遇到的问题

1、第一个问题

Caused by: java.lang.NullPointerException
	at fr.opensagres.poi.xwpf.converter.core.utils.XWPFTableUtil.getGridColList(XWPFTableUtil.java:184)
	at fr.opensagres.poi.xwpf.converter.core.utils.XWPFTableUtil.computeColWidths(XWPFTableUtil.java:117)
	at fr.opensagres.poi.xwpf.converter.core.XWPFDocumentVisitor.visitTable(XWPFDocumentVisitor.java:970)
	at fr.opensagres.poi.xwpf.converter.core.XWPFDocumentVisitor.visitBodyElements(XWPFDocumentVisitor.java:267)
	at fr.opensagres.poi.xwpf.converter.core.XWPFDocumentVisitor.start(XWPFDocumentVisitor.java:215)
	at fr.opensagres.poi.xwpf.converter.pdf.PdfConverter.doConvert(PdfConverter.java:57)
	... 4 more

跟着断点知道是grid为null,所以取不到值。

docx文件转pdf java_poi_02


所以在创建表格的时候加上:

CTTblGrid grid = table.getCTTbl().getTblGrid();
if (grid == null) {
	table.getCTTbl().addNewTblGrid();
}

2、紧接着出现第二个问题

Caused by: java.lang.NullPointerException
	at fr.opensagres.poi.xwpf.converter.core.utils.XWPFTableUtil.getWidth(XWPFTableUtil.java:274)
	at fr.opensagres.poi.xwpf.converter.core.utils.XWPFTableUtil.computeColWidths(XWPFTableUtil.java:285)
	at fr.opensagres.poi.xwpf.converter.core.utils.XWPFTableUtil.computeColWidths(XWPFTableUtil.java:128)
	at fr.opensagres.poi.xwpf.converter.core.XWPFDocumentVisitor.visitTable(XWPFDocumentVisitor.java:970)
	at fr.opensagres.poi.xwpf.converter.core.XWPFDocumentVisitor.visitBodyElements(XWPFDocumentVisitor.java:267)
	at fr.opensagres.poi.xwpf.converter.core.XWPFDocumentVisitor.start(XWPFDocumentVisitor.java:215)
	at fr.opensagres.poi.xwpf.converter.pdf.PdfConverter.doConvert(PdfConverter.java:57)
	... 4 more

跟着断点知道TcPr是null,所以取值的时候就会报null。

docx文件转pdf java_poi_03


所以在创建表格样式的时候遍历一下列,创建出TcPr,顺便创建TcTw,因为如果不创建的话,还是会报错。

for (XWPFTableRow row : table.getRows()) {
    for(XWPFTableCell cell : row.getTableCells()) {
        CTTblWidth ctTblWidth = cell.getCTTc().addNewTcPr().addNewTcW();
        ctTblWidth.setW(new BigInteger(width));
    }
}

3、本以为这样就好了,结果又报错了。

Caused by: java.lang.NullPointerException
	at fr.opensagres.poi.xwpf.converter.core.utils.XWPFTableUtil.getTableWidth(XWPFTableUtil.java:347)
	at fr.opensagres.poi.xwpf.converter.core.utils.XWPFTableUtil.getTableWidth(XWPFTableUtil.java:331)
	at fr.opensagres.poi.xwpf.converter.core.utils.XWPFTableUtil.computeColWidths(XWPFTableUtil.java:290)
	at fr.opensagres.poi.xwpf.converter.core.utils.XWPFTableUtil.computeColWidths(XWPFTableUtil.java:128)
	at fr.opensagres.poi.xwpf.converter.core.XWPFDocumentVisitor.visitTable(XWPFDocumentVisitor.java:970)
	at fr.opensagres.poi.xwpf.converter.core.XWPFDocumentVisitor.visitBodyElements(XWPFDocumentVisitor.java:267)
	at fr.opensagres.poi.xwpf.converter.core.XWPFDocumentVisitor.start(XWPFDocumentVisitor.java:215)
	at fr.opensagres.poi.xwpf.converter.pdf.PdfConverter.doConvert(PdfConverter.java:57)
	... 4 more

再打断点看到是Type为空。

docx文件转pdf java_poi_04


在上一行代码的setW()下增加一行

ctTblWidth.setType(STTblWidth.DXA);

运行结果完成,虽然还会报一个OpenXML4JRuntimeException 但是不影响结果就不管了。。。累了。

docx文件转pdf java_sed_05

完整的代码

1、Maven依赖(里面自带poi-4.0.1)

<dependency>
    <groupId>fr.opensagres.xdocreport</groupId>
    <artifactId>fr.opensagres.poi.xwpf.converter.pdf-gae</artifactId>
    <version>2.0.2</version>
</dependency>

2、WordOutputUtil 工具类

import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

import java.math.BigInteger;
import java.util.List;

public class WordOutputUtil {

    public static void setBigTitle(XWPFDocument doc, String bigTitle, String authorName) {
        XWPFParagraph bigTitleParagraph = doc.createParagraph(); // 新建一个标题段落对象(就是一段文字)
        bigTitleParagraph.setAlignment(ParagraphAlignment.CENTER);// 样式居中
        XWPFRun titleFun = bigTitleParagraph.createRun(); // 创建文本对象
        titleFun.setText(bigTitle); //设置标题的名字
        setBigTitleFontStyle(titleFun);
        titleFun.addBreak(); // 换行
        titleFun = bigTitleParagraph.createRun(); // 创建文本对象
        titleFun.setText(authorName); //设置标题的名字
        setBigTitleTextFontStyle(titleFun);
    }

    public static void setTitleText(XWPFParagraph paragraph, String titleText) {
        XWPFRun titleFun = paragraph.createRun(); // 创建文本对象
        titleFun.setText(titleText); //设置标题的名字
        setTitleFontStyle(titleFun);
    }

    public static void setTableTitle(XWPFDocument doc, String tableTitleName) {
        XWPFParagraph bigTitleParagraph = doc.createParagraph(); // 新建一个标题段落对象(就是一段文字)
        bigTitleParagraph.setAlignment(ParagraphAlignment.CENTER);// 样式居中
        XWPFRun titleFun = bigTitleParagraph.createRun(); // 创建文本对象
        titleFun.addBreak();
        titleFun.setText(tableTitleName); //设置标题的名字
        setTableTitleFontStyle(titleFun);
    }

    public static void setGroupTitle(XWPFParagraph paragraph, String tableTitleName) {
        XWPFRun titleFun = paragraph.createRun(); // 创建文本对象
        titleFun.addBreak();
        titleFun.addTab();
        titleFun.setText(tableTitleName); //设置标题的名字
        setTitleFontStyle(titleFun);
    }


    public static void setText(XWPFParagraph paragraph, String text) {
        XWPFRun textFun = paragraph.createRun(); // 创建文本对象

        textFun.addBreak();
        textFun.addTab();
        textFun.setText(text); //设置标题的名字
        setTextFontStyle(textFun);
    }

    public static XWPFTable createTable(XWPFDocument doc, int rows, int cols) {
        XWPFTable table = doc.createTable(rows, cols);
        // 校验一下grid是否为空,如果为空就创建。转pdf的时候如果为空会报空指针
        CTTblGrid grid = table.getCTTbl().getTblGrid();
        if (grid == null) {
            table.getCTTbl().addNewTblGrid();
        }
        setTableWidthAndHAlign(table, "8288", STJc.CENTER);
        return table;
    }

    /**
     * @Description: 设置表格总宽度与水平对齐方式
     */
    public static void setTableWidthAndHAlign(XWPFTable table, String width, STJc.Enum enumValue) {
        CTTblPr tblPr = getTableCTTblPr(table);
        // 表格宽度
        CTTblWidth tblWidth = tblPr.isSetTblW() ? tblPr.getTblW() : tblPr.addNewTblW();
        if (enumValue != null) {
            CTJc cTJc = tblPr.addNewJc();
            cTJc.setVal(enumValue);
        }
        // 设置宽度
        tblWidth.setW(new BigInteger(width));
        tblWidth.setType(STTblWidth.DXA);

        // 转换pdf的时候如果没有这个可能会报空指针
        for (XWPFTableRow row : table.getRows()) {
            for(XWPFTableCell cell : row.getTableCells()) {
                CTTblWidth ctTblWidth = cell.getCTTc().addNewTcPr().addNewTcW();
                ctTblWidth.setW(new BigInteger(width));
                ctTblWidth.setType(STTblWidth.DXA);
            }
        }
    }

    /**
     * @Description: 得到Table的CTTblPr, 不存在则新建
     */
    public static CTTblPr getTableCTTblPr(XWPFTable table) {
        CTTbl ttbl = table.getCTTbl();
        // 表格属性
        CTTblPr tblPr = ttbl.getTblPr() == null ? ttbl.addNewTblPr() : ttbl.getTblPr();
        return tblPr;
    }

    /**
     * 合并表格单元格
     *
     * @param table    表格对象
     * @param startRow 开始行
     * @param endRow   结束行
     * @param startCol 开始列
     * @param endCol   结束列
     */
    public static void mergeTableCellsAndRow(XWPFTable table, int startRow, int endRow, int startCol, int endCol) {

        for (int rowIndex = startRow; rowIndex <= endRow; rowIndex++) {
            XWPFTableRow row = table.getRow(rowIndex);
            for (int cellIndex = startCol; cellIndex <= endCol; cellIndex++) {
                XWPFTableCell cell = row.getCell(cellIndex);
                // 第一个合并单元格用重启合并值设置
                if (cellIndex == startCol) {
                    cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.RESTART);
                } else {
                    // 合并第一个单元格的单元被设置为“继续”
                    cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE);
                }
            }
        }
        for (int rowIndex = startRow; rowIndex <= endRow; rowIndex++) {
            XWPFTableRow row = table.getRow(rowIndex);
            XWPFTableCell cell = row.getCell(startCol);
            // 第一个合并单元格用重启合并值设置
            if (rowIndex == startRow) {
                cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.RESTART);
            } else {
                // 合并第一个单元格的单元被设置为“继续”
                cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.CONTINUE);
            }
        }
    }

    /**
     * 往表格中填充数据
     *
     * @param table
     * @param tableData
     * @Author Huangxiaocong 2018年12月16日
     */
    public static void setTableData(XWPFTable table, List<List<Object>> tableData) {
        List<XWPFTableRow> rowList = table.getRows();
        for (int i = 0; i < rowList.size(); i++) {
            if (i >= tableData.size())
                break;
            List<Object> list = tableData.get(i);
            List<XWPFTableCell> cellList = rowList.get(i).getTableCells();
            for (int j = 0; j < cellList.size(); j++) {
                if (j >= list.size())
                    break;
                XWPFParagraph cellParagraph = cellList.get(j).getParagraphArray(0);
                XWPFRun cellParagraphRun = cellParagraph.createRun();
                setTableTextFontStyle(cellParagraphRun);
                cellParagraphRun.setText(String.valueOf(list.get(j)));
            }
        }
    }

    public static void setBigTitleFontStyle(XWPFRun fun) {
        fun.setBold(true); // 加粗
        fun.setColor("000000");// 设置颜色
        fun.setFontSize(20); // 字体大小
        fun.setFontFamily("黑体");//设置字体
    }

    public static void setBigTitleTextFontStyle(XWPFRun fun) {
        setFontStyle(fun, 12, false);
    }

    public static void setTitleFontStyle(XWPFRun fun) {
        fun.setTextPosition(20); // 设置两行之间的行间距
        setFontStyle(fun, 12, true);
    }

    public static void setTableTitleFontStyle(XWPFRun fun) {
        fun.setBold(true); // 加粗
        fun.setColor("000000");// 设置颜色
        fun.setFontSize(12); // 字体大小
        fun.setFontFamily("黑体");//设置字体
    }

    public static void setTableTextFontStyle(XWPFRun fun) {
        fun.setBold(false); // 加粗
        fun.setColor("000000");// 设置颜色
        fun.setFontSize(10); // 字体大小
        fun.setFontFamily("仿宋");//设置字体
    }

    public static void setTextFontStyle(XWPFRun fun) {
        setFontStyle(fun, 12, false);
    }

    public static void setFontStyle(XWPFRun fun, int fontSize, boolean isBold) {
        fun.setBold(isBold); // 加粗
        fun.setColor("000000");// 设置颜色
        fun.setFontSize(fontSize); // 字体大小
        fun.setFontFamily("宋体");//设置字体
    }

3、测试代码

import com.top.ckdemo.ck.common.WordOutputUtil;
import fr.opensagres.poi.xwpf.converter.pdf.PdfConverter;
import fr.opensagres.poi.xwpf.converter.pdf.PdfOptions;
import org.apache.poi.xwpf.usermodel.*;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * docx转pdf
 */
public class Docx2Pdf {
    public static void main(String[] args) {
        String modelPath = "E:/模板.docx";
        String outPath = "E:/转换结果.pdf";
        docx2Pdf(modelPath, outPath);
    }

    public static void docx2Pdf(String modelPath, String outPath) {
        try {
            File file = new File(modelPath);
            XWPFDocument doc = new XWPFDocument(new FileInputStream(file));
            XWPFParagraph paragraph = doc.getLastParagraph();
            paragraph.removeRun(0);

            String bigTitle = "这是大标题";
            String authorName = "这是作者小标题";
            WordOutputUtil.setBigTitle(doc, bigTitle, authorName); // 大标题

            XWPFParagraph paragraph1 = doc.createParagraph(); // 新建一个标题段落对象(就是一段文字)
            paragraph1.setVerticalAlignment(TextAlignment.CENTER);
            XWPFParagraph paragraph2 = doc.createParagraph(); // 新建一个标题段落对象(就是一段文字)
            paragraph2.setVerticalAlignment(TextAlignment.CENTER);
            XWPFParagraph paragraph3 = doc.createParagraph(); // 新建一个标题段落对象(就是一段文字)
            paragraph3.setVerticalAlignment(TextAlignment.CENTER);
            String titleText = "一、内容1";
            WordOutputUtil.setTitleText(paragraph1, titleText); // 段落标题
            titleText = "二、内容2";
            WordOutputUtil.setTitleText(paragraph2, titleText); // 段落标题
            titleText = "三、内容3";
            WordOutputUtil.setTitleText(paragraph3, titleText); // 段落标题

            List<List<Object>> list = new ArrayList<List<Object>>();
            List<Object> rowList = new ArrayList<>();
            rowList.add("1");
            rowList.add("2");
            rowList.add("3");
            list.add(rowList);
            // 创建表格,
            WordOutputUtil.setTableTitle(doc, "表1-1");
            XWPFTable table = WordOutputUtil.createTable(doc, 3, 10); // 数据行数+1行标题,列数
            WordOutputUtil.setTableData(table, list);

            OutputStream os = new FileOutputStream(new File(outPath));

            PdfOptions options = PdfOptions.create();
            PdfConverter.getInstance().convert(doc, os, options);

            doc.write(os);
            os.flush();
            os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

需要注意的是,表格合并的时候,如果是横向合并的话,字数太多或者列数太多会出现段落不齐的情况,竖向合并不会,所以请自行选择是否横向合并。