docx转pdf
程序员文章站
2024-01-18 23:03:10
...
最近被word逼疯,不仅要导出各种报告,还要附带表格,所以写了一个docx转pdf以供参考。
之前用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模板,内容是什么不重要。
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,所以取不到值。
所以在创建表格的时候加上:
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。
所以在创建表格样式的时候遍历一下列,创建出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为空。
在上一行代码的setW()下增加一行
ctTblWidth.setType(STTblWidth.DXA);
运行结果完成,虽然还会报一个OpenXML4JRuntimeException 但是不影响结果就不管了。。。累了。
完整的代码
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();
}
}
}
需要注意的是,表格合并的时候,如果是横向合并的话,字数太多或者列数太多会出现段落不齐的情况,竖向合并不会,所以请自行选择是否横向合并。
上一篇: Vue的单文件组件