操作word 以及pdf 记录
场景如下,需求是给了一个Word 模板,word 中有一个表格,需要填充这个表格,填充完毕之后,将其转换为PDF,因为之前下载的文件是word,产品tmd让转PDF
实现的思路
问了下AI,常见的有几种方式
1)用POI操作
2)用aspose实现word转PDF
操作word 使用org.apache.poi.xwpf.usermodel.XWPFDocument
对象,填充word
模板word内容如下
填充之后内容如下
填充的代码如下
java
package com.htb.haigangpdf6.test;
import com.htb.haigangpdf6.utils.AposeWordPDFUtil;
import com.htb.haigangpdf6.utils.WordPDFUtil;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import java.io.IOException;
public class Main3 {
public static void main(String[] args) {
String templatePath = "C:\\work\\file\\写文章\\自己画的模板.docx";
String wordAfterFillPath = "C:\\work\\file\\写文章\\自己画的模板_填充内容之后.docx";
String pdfFilePath = "C:\\work\\file\\写文章\\自己画的模板_填充内容之后转PDF.pdf";
// 填充得到Word文件对象
XWPFDocument docFile = WordPDFUtil.getDocFile(templatePath);
try {
// Word文件对象 转 bytes,得到的是 Word文件的byte[]
byte[] bytes = WordPDFUtil.xwpfDocumentToBytes(docFile);
// 方式一、Word文件的byte[] 转为PDF 文件
// AposeWordPDFUtil.wordBytes2PdfFile(bytes, pdfFilePath);
// 方式二、也可以调用方法 wordBytes2PdfBytes 得到PDF 文件 byte[],方便返回文件流让前端下载
// byte[] pdfBytes = AposeWordPDFUtil.wordBytes2PdfBytes(bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
}
如果需要返回文件流给前端,只需要改造main方法中 byte[] bytes 对象,写入到response 即可,下面会提到
上面的代码是在读取word 模板,进行填充内容,得到的是填充好的 XWPFDocument 对象,(此时如果你的需求是Word 那直接就结束了,保存为文件或者是下载文件流),但是产品的最终需求是转为PDF,需要把 得到的XWPFDocument对象转化为PDF
使用下面的方式实现转化word 为PDF
使用apose实现word转PDF
参考github 工程
https://github.com/zhengqingya/file-convert-util?tab=readme-ov-file
这个项目中使用是apose 相关的技术,实现的各种文件之间的转换
参考如下的内容
先引入所需要的第三方的jar
放百度云
链接:https://pan.baidu.com/s/1Dtdz9cK5TBcMt1e9MQsUbw
提取码:ef67
aspose-cells-8.5.2.jar
aspose-words-18.6-jdk16.jar
导入工程之后,引入jar
工程结构如下
xml
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-words</artifactId>
<version>18.6</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/aspose-words-18.6-jdk16.jar</systemPath>
</dependency>
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-cells</artifactId>
<version>8.5.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/aspose-cells-8.5.2.jar</systemPath>
</dependency>
添加对应的依赖之后 转换PDF的方法
java
package com.htb.haigangpdf6.test;
import com.htb.haigangpdf6.utils.AposeWordPDFUtil;
import com.htb.haigangpdf6.utils.WordPDFUtil;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import java.io.IOException;
public class Main3 {
public static void main(String[] args) {
String templatePath = "C:\\work\\file\\写文章\\自己画的模板.docx";
String wordAfterFillPath = "C:\\work\\file\\写文章\\自己画的模板_填充内容之后.docx";
String pdfFilePath = "C:\\work\\file\\写文章\\自己画的模板_填充内容之后转PDF.pdf";
// 填充得到Word文件对象
XWPFDocument docFile = WordPDFUtil.getDocFile(templatePath);
try {
// Word文件对象 转 bytes,得到的是 Word文件的byte[]
byte[] bytes = WordPDFUtil.xwpfDocumentToBytes(docFile);
// 方式一、Word文件的byte[] 转为PDF 文件
AposeWordPDFUtil.wordBytes2PdfFile(bytes, pdfFilePath);
// 方式二、也可以调用方法 wordBytes2PdfBytes 得到PDF 文件 byte[],方便返回文件流让前端下载
// byte[] pdfBytes = AposeWordPDFUtil.wordBytes2PdfBytes(bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面的方法转换之后得到的文件如下,下面的截图是PDF文件
上面的图片转化失败,是因为图片本身的问题,换了一张图片后解决,如果遇到,可以考虑换一下图片
注意:
我找到的这个版本的Apose 转PDF 有一个弊端,不能转换超过11页的文件,如果超过,可能还要考虑PDF 拆分合并什么的 可以考虑使用 PDFBox
还有一个问题就是字体的问题,上述的截图是我在window 系统中转化的结果,使用的字体是宋体,放到服务器之后,运行在docker 容器中的时候,会有一个问题中文字都变成了 框,崩溃...我猜测是缺失字体,但是目前的镜像是基于jdk 构建的docker 镜像,没有根据操作也行构建镜像,实在不知道怎么解决,欢迎大佬提供方法
为了避免这个问题,又产生了下面的一种方式,使用itextpdf 直接操作PDF,直接绘制PDF
需要引入的依赖
xml
<properties>
<poi-ooxml.version>4.1.2</poi-ooxml.version>
<poi-ooxml-schemas.version>4.1.2</poi-ooxml-schemas.version>
</properties>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi-ooxml.version}</version>
</dependency>
<!--绘制PDF所需依赖 start-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<!--绘制PDF所需依赖 end-->
java
package com.htb.haigangpdf6.test;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import org.springframework.http.HttpHeaders;
import javax.servlet.ServletOutputStream;
import java.io.*;
import java.nio.file.Files;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
public class Main5 {
private static final String imagePath = "C:\\work\\file\\写文章\\logo.png";
private static final String pdfPath = "C:\\work\\file\\写文章\\直接绘制的.pdf";
public static void main(String[] args) throws DocumentException, IOException {
//新建一个A4大小的文档
Document document = new Document(PageSize.A4);
//创建一个ByteArrayOutputStream绑定document,后续只需要将数据流写入baos即可
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfWriter.getInstance(document, baos);
//打开文档
document.open();
// 设置中文字体
BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
//文档标题字体样式
Font titleFontChinese = new Font(bfChinese, 15, Font.BOLD);
//小标题字体样式
Font smallTitleFontChinese = new Font(bfChinese, 8, Font.BOLD);
//表头字体样式
Font headFontChinese = new Font(bfChinese, 8, Font.BOLD);
//内容字体样式
Font contentFontChinese = new Font(bfChinese, 9, Font.NORMAL);
// 画表头以上的信息
drawHead(document, titleFontChinese, contentFontChinese);
String title = "淘宝订单";
String bidNo = "no20240602xxxxx";
drawTableOne(document, contentFontChinese, title, bidNo);
drawTableTwo(document, contentFontChinese);
//关闭文档
document.close();
//将PDF内容写入响应输出流并关闭
OutputStream outputStream = new FileOutputStream(new File(pdfPath));
outputStream.write(baos.toByteArray());
outputStream.flush();
outputStream.close();
}
private static void drawHead(Document document, Font titleFontChinese, Font contentFontChinese) throws DocumentException, IOException {
//保持美观,空一行
document.add(new Paragraph(" "));
//创建一个包含两列的表格,这个表格里面左列是图片,右列是表格
PdfPTable table = new PdfPTable(2);
//设置表格宽度
table.setWidthPercentage(100);
//希望最终图片的大小小于右边的表格,需要划分两列比例
table.setWidths(new int[]{1, 2});
//第一列添加图片
PdfPCell imageCell = new PdfPCell();
File imageFile = new File(imagePath);
byte[] imageBytes = Files.readAllBytes(imageFile.toPath());
Image img = Image.getInstance(imageBytes);
img.scaleToFit(100, 52);
//设置内部表格宽度为100%
img.setWidthPercentage(100);
imageCell.addElement(img);
//设置单元格无边框
imageCell.setBorder(Rectangle.NO_BORDER);
//将带图片的单元格设置进入表格中
table.addCell(imageCell);
//第一列添加图片
PdfPCell cell2 = new PdfPCell();
Paragraph paragraph = new Paragraph("天珩集团", contentFontChinese);
paragraph.setAlignment(Element.ALIGN_RIGHT);
cell2.addElement(paragraph);
cell2.setBorder(Rectangle.NO_BORDER);
table.addCell(cell2);
document.add(table);
//这一步将step2中的titleFontChinese设置进来即可解决中文字体不显示问题
Paragraph title = new Paragraph("出价详情", titleFontChinese);
//标题居中
title.setAlignment(Element.ALIGN_CENTER);
//将标题添加到文档中
document.add(title);
}
private static void drawTableOne(Document document, Font contentFontChinese,
String title,
String bidNo) throws DocumentException {
//创建一个包含两列的表格,这个表格里面左列是图片,右列是表格
PdfPTable table = new PdfPTable(2);
//设置表格宽度
table.setWidthPercentage(100);
//希望最终图片的大小小于右边的表格,需要划分两列比例
table.setWidths(new int[]{5, 3});
PdfPCell titleCell = new PdfPCell();
titleCell.addElement(new Paragraph("采购标题: " + title, contentFontChinese));
//设置单元格无边框
titleCell.setBorder(Rectangle.NO_BORDER);
table.addCell(titleCell);
PdfPCell bidNoCell = new PdfPCell();
bidNoCell.addElement(new Paragraph("采购招标单号: " + bidNo, contentFontChinese));
//设置单元格无边框
bidNoCell.setBorder(Rectangle.NO_BORDER);
table.addCell(bidNoCell);
document.add(table);
}
private static void drawTableTwo(Document document, Font contentFontChinese) throws DocumentException {
PdfPTable table = new PdfPTable(9);
//设置表格宽度
table.setWidthPercentage(100);
// 添加表头信息
addTableHead(table, contentFontChinese);
// 获取数据
List<BidsBuyMaterial> dataList = getDataSource();
// 添加表格主体内容
addTableBody(table, contentFontChinese, dataList);
document.add(table);
String userName = "小甜甜";
addTableFoot(document, contentFontChinese, userName);
}
private static void addTableFoot(Document document, Font contentFontChinese, String userName) throws DocumentException {
PdfPTable table = new PdfPTable(3);
//设置表格宽度
table.setWidthPercentage(100);
//希望最终图片的大小小于右边的表格,需要划分两列比例
table.setWidths(new int[]{5, 2, 2});
PdfPCell cell1 = new PdfPCell();
cell1.addElement(new Paragraph("数据来源:天珩集团", contentFontChinese));
cell1.setBorder(Rectangle.NO_BORDER);
table.addCell(cell1);
PdfPCell cell2 = new PdfPCell();
cell2.addElement(new Paragraph("打印人: " + userName, contentFontChinese));
cell2.setBorder(Rectangle.NO_BORDER);
table.addCell(cell2);
PdfPCell cell3 = new PdfPCell();
Paragraph paragraphCell4 = new Paragraph("打印时间:" + localDate2DateString(LocalDate.now()), contentFontChinese);
paragraphCell4.setAlignment(Element.ALIGN_RIGHT);
cell3.addElement(paragraphCell4);
cell3.setBorder(Rectangle.NO_BORDER);
table.addCell(cell3);
document.add(table);
}
private static void addTableHead(PdfPTable table,
Font contentFontChinese) {
List<String> headList = new ArrayList<>(10);
headList.add("投标公司");
headList.add("商品名称");
headList.add("港口");
headList.add("数量");
headList.add("价格");
headList.add("成分");
headList.add("单位(%)");
headList.add("交货日期");
headList.add("附件");
for (String txt : headList) {
PdfPCell titleCell = new PdfPCell();
titleCell.addElement(new Paragraph(txt, contentFontChinese));
table.addCell(titleCell);
}
}
private static List<BidsBuyMaterial> getDataSource() {
List<BidsBuyMaterial> dataList = new ArrayList<>(10);
BidsBuyMaterial bidsBuyMaterial1 = new BidsBuyMaterial();
bidsBuyMaterial1.setBidsCompanyName("天珩集团1");
bidsBuyMaterial1.setMaterialName("冷轧板卷");
bidsBuyMaterial1.setPortLocation("矿石公司");
bidsBuyMaterial1.setBidsNum(100.11D);
bidsBuyMaterial1.setTaxInclude(1000.222D);
bidsBuyMaterial1.setMaterialAttrFe("10");
bidsBuyMaterial1.setMaterialAttrSi("20");
bidsBuyMaterial1.setMaterialAttrAi("30");
bidsBuyMaterial1.setMaterialAttrP("40");
bidsBuyMaterial1.setMaterialAttrSulfur("50");
bidsBuyMaterial1.setMaterialAttrWater("");
bidsBuyMaterial1.setMaterialAttrOther("60");
bidsBuyMaterial1.setDeliveryDateBegin(LocalDate.now());
bidsBuyMaterial1.setAcc("质量报告.pdf");
BidsBuyMaterial bidsBuyMaterial2 = new BidsBuyMaterial();
bidsBuyMaterial2.setBidsCompanyName("天珩集团2");
bidsBuyMaterial2.setMaterialName("冷轧板卷22222");
bidsBuyMaterial2.setPortLocation("矿石公司22222");
bidsBuyMaterial2.setBidsNum(100.11D);
bidsBuyMaterial2.setTaxInclude(1000.222D);
bidsBuyMaterial2.setMaterialAttrFe("10");
bidsBuyMaterial2.setMaterialAttrSi("20");
bidsBuyMaterial2.setMaterialAttrAi("30");
bidsBuyMaterial2.setMaterialAttrP("40");
bidsBuyMaterial2.setMaterialAttrSulfur("50");
bidsBuyMaterial2.setMaterialAttrWater("");
bidsBuyMaterial2.setMaterialAttrOther("60");
bidsBuyMaterial2.setDeliveryDateBegin(LocalDate.now());
bidsBuyMaterial2.setAcc("质量报告.pdf");
dataList.add(bidsBuyMaterial1);
dataList.add(bidsBuyMaterial2);
return dataList;
}
private static void addTableBody(PdfPTable table, Font contentFontChinese, List<BidsBuyMaterial> dataList) {
LinkedList<LinkedList<String>> dataToCell = getDataToCell(dataList);
for (LinkedList<String> linkedList : dataToCell) {
for (String txt : linkedList) {
PdfPCell titleCell = new PdfPCell();
titleCell.addElement(new Paragraph(txt, contentFontChinese));
table.addCell(titleCell);
}
}
}
private static LinkedList<LinkedList<String>> getDataToCell(List<BidsBuyMaterial> dataList) {
LinkedList<LinkedList<String>> result = new LinkedList<>();
for (BidsBuyMaterial bidsBuyMaterial : dataList) {
LinkedList<String> row = new LinkedList<>();
row.add(bidsBuyMaterial.getBidsCompanyName());
row.add(bidsBuyMaterial.getMaterialName());
row.add(bidsBuyMaterial.getPortLocation());
row.add(bidsBuyMaterial.getBidsNum().toString());
row.add(bidsBuyMaterial.getTaxInclude().toString());
List<String> valuesList1 = new ArrayList<>(10);
valuesList1.add("铁");
valuesList1.add("硅");
valuesList1.add("铝");
valuesList1.add("磷");
valuesList1.add("硫");
valuesList1.add("其他");
String component = String.join("\n", valuesList1);
row.add(component);
List<String> valuesList2 = new ArrayList<>(10);
valuesList2.add(Optional.ofNullable(bidsBuyMaterial.getMaterialAttrFe()).orElse(""));
valuesList2.add(Optional.ofNullable(bidsBuyMaterial.getMaterialAttrSi()).orElse(""));
valuesList2.add(Optional.ofNullable(bidsBuyMaterial.getMaterialAttrAi()).orElse(""));
valuesList2.add(Optional.ofNullable(bidsBuyMaterial.getMaterialAttrP()).orElse(""));
valuesList2.add(Optional.ofNullable(bidsBuyMaterial.getMaterialAttrSulfur()).orElse(""));
valuesList2.add(Optional.ofNullable(bidsBuyMaterial.getMaterialAttrOther()).orElse(""));
String componentNum = String.join("\n", valuesList2);
row.add(componentNum);
row.add(localDate2DateString(bidsBuyMaterial.getDeliveryDateBegin()));
row.add(bidsBuyMaterial.getAcc());
result.add(row);
}
return result;
}
private static String localDate2DateString(LocalDate localDate) {
return DateTimeFormatter.ofPattern("yyyy-MM-dd").format(localDate);
}
}
绘制PDF的效果
这种方式一步到位,且没有页数限制,直接转为PDF 文件,上面的代码略显粗糙,这是我脱敏业务构建的代码,可以直接借鉴,后续有时间再整理整理
用这种方式还可以避免缺失字体的问题,超级推荐
很久没写文章了,欢迎指正