在项目中碰见一个需求,需要将.doc的合同,转换为pdf实现打印与预览功能。
将docx模板填充数据生成doc文件
1、依赖引入
填充docx模板,只需要引入一个pom依赖即可实现。
<dependency>
<groupid>com.deepoove</groupid>
<artifactid>poi-tl</artifactid>
<version>1.5.0</version>
</dependency>2、doc文件转换docx,并标注别名
用office或者wps,创建一个001.doc文件,绘制表格,保存。
更改后缀为.docx,确定后,在指定的位置,表示数据接受变量名称。
如下图所示:

3、编写java代码实现数据填充
import com.deepoove.poi.xwpftemplate;
import org.springframework.core.io.classpathresource;
import org.springframework.core.io.resource;
import java.io.file;
import java.io.fileoutputstream;
import java.io.ioexception;
import java.io.outputstream;
import java.util.hashmap;
import java.util.map;
/**
* word 填充测试
*/
public class testword {
public static void main(string[] args) throws ioexception {
map<string, object> params = new hashmap<>();
params.put("username","xiangjiao1");
params.put("password","******");
params.put("age",22);
params.put("email","专注写bug测试中文");
resource resource = new classpathresource("templates_report/001.docx");
file file = resource.getfile();
// 数据填充
xwpftemplate template = xwpftemplate.compile(file).render(params);
string docoutpath = system.getproperty("user.dir")+file.separator+"springboot-poi"+file.separator+"pdf"+file.separator+ "1.doc";
outputstream outputstream = new fileoutputstream(docoutpath);
template.write(outputstream);
}
}运行程序,查看结果。
测试项目结构如下:

docx文件填充数据导出pdf(web)
1、依赖引入
向docx模板中填充数据,并导出pdf类型的文件,除了上面的pom依赖之外,还需要引入其他的依赖信息,完整依赖如下所示:
<!-- docx 数据填充生成 doc文件 这个是主要 -->
<dependency>
<groupid>com.deepoove</groupid>
<artifactid>poi-tl</artifactid>
<version>1.5.0</version>
</dependency>
<!-- doc 转 pdf -->
<dependency>
<groupid>com.itextpdf</groupid>
<artifactid>itextpdf</artifactid>
<version>5.5.13</version>
</dependency>
<!-- docx4j docx2pdf -->
<dependency>
<groupid>org.docx4j</groupid>
<artifactid>docx4j</artifactid>
<version>6.1.2</version>
</dependency>
<dependency>
<groupid>org.docx4j</groupid>
<artifactid>docx4j-export-fo</artifactid>
<version>6.0.0</version>
</dependency>2、字体文件
在src\main\resources下创建一个font文件夹,其中放入simsun.ttc字体文件。
3、编写工具类
思想很简单
- 1、先使用上面的docx模板填充数据生成
临时doc文件, - 2、再将doc文件转换为pdf文件
- 3、删除临时文件
【注意:】
为了避免出现多人同时操作,导致文件误删的问题,需要尽可能地保证临时文件名称的唯一性。
import com.deepoove.poi.xwpftemplate;
import com.itextpdf.text.*;
import com.itextpdf.text.image;
import com.itextpdf.text.pdf.*;
import lombok.extern.slf4j.slf4j;
import org.apache.commons.io.fileutils;
import org.apache.commons.lang3.text.wordutils;
import org.docx4j.docx4j;
import org.docx4j.convert.out.fosettings;
import org.docx4j.fonts.identityplusmapper;
import org.docx4j.fonts.mapper;
import org.docx4j.fonts.physicalfonts;
import org.docx4j.openpackaging.packages.wordprocessingmlpackage;
import org.springframework.stereotype.component;
import java.io.*;
import java.util.map;
import java.util.uuid;
import java.util.zip.zipoutputstream;
/**
* pdf 导出工具类
*/
@component
@slf4j
public final class freemarkutils {
/**
* 根据docx模板填充数据 并生成pdf文件
*
* @param datamap 数据源
* @param docxfile docx模板的文件名
* @return 生成的文件路径
*/
public static byte[] createdocx2pdf(map<string, object> datamap, string docxfile) {
//输出word文件路径和名称 (临时文件名,本次为测试,最好使用雪花算法生成,或者用uuid)
string filename = uuid.randomuuid().tostring() + ".docx";
// word 数据填充
// 生成docx临时文件
final file temppath = new file(filename);
final file docxtempfile = gettempfile(docxfile);
xwpftemplate template = xwpftemplate.compile(docxtempfile).render(datamap);
try {
template.write(new fileoutputstream(temppath));
} catch (ioexception e) {
e.printstacktrace();
}
// word转pdf
final string pdffile = convertdocx2pdf(filename);
return getfileoutputstream(new file(pdffile)).tobytearray();
}
/**
* word(doc)转pdf
*
* @param wordpath doc 生成的临时文件路径
* @return 生成的带水印的pdf路径
*/
public static string convertdocx2pdf(string wordpath) {
outputstream os = null;
inputstream is = null;
//输出pdf文件路径和名称 (临时文件 尽可能保证文件名称的唯一性)
final string filename = uuid.randomuuid().tostring() + ".pdf";
try {
is = new fileinputstream(wordpath);
wordprocessingmlpackage mlpackage = wordprocessingmlpackage.load(is);
mapper fontmapper = new identityplusmapper();
fontmapper.put("隶书", physicalfonts.get("lisu"));
fontmapper.put("宋体", physicalfonts.get("simsun"));
fontmapper.put("微软雅黑", physicalfonts.get("microsoft yahei"));
fontmapper.put("黑体", physicalfonts.get("simhei"));
fontmapper.put("楷体", physicalfonts.get("kaiti"));
fontmapper.put("新宋体", physicalfonts.get("nsimsun"));
fontmapper.put("华文行楷", physicalfonts.get("stxingkai"));
fontmapper.put("华文仿宋", physicalfonts.get("stfangsong"));
fontmapper.put("宋体扩展", physicalfonts.get("simsun-extb"));
fontmapper.put("仿宋", physicalfonts.get("fangsong"));
fontmapper.put("仿宋_gb2312", physicalfonts.get("fangsong_gb2312"));
fontmapper.put("幼圆", physicalfonts.get("youyuan"));
fontmapper.put("华文宋体", physicalfonts.get("stsong"));
fontmapper.put("华文中宋", physicalfonts.get("stzhongsong"));
//解决宋体(正文)和宋体(标题)的乱码问题
physicalfonts.put("pmingliu", physicalfonts.get("simsun"));
physicalfonts.put("新細明體", physicalfonts.get("simsun"));
// 字体文件
physicalfonts.addphysicalfonts("simsun", wordutils.class.getresource("/font/simsun.ttc"));
mlpackage.setfontmapper(fontmapper);
os = new fileoutputstream(filename);
//docx4j docx转pdf
fosettings fosettings = docx4j.createfosettings();
fosettings.setwmlpackage(mlpackage);
docx4j.tofo(fosettings, os, docx4j.flag_export_prefer_xsl);
is.close();//关闭输入流
os.close();//关闭输出流
} catch (exception e) {
e.printstacktrace();
} finally {
// 删除docx 临时文件
file file = new file(wordpath);
if (file != null && file.isfile() && file.exists()) {
file.delete();
}
try {
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
} catch (exception ex) {
ex.printstacktrace();
}
}
return filename;
}
/**
* 文件转字节输出流
*
* @param outfile 文件
* @return
*/
public static bytearrayoutputstream getfileoutputstream(file outfile) {
// 获取生成临时文件的输出流
inputstream input = null;
bytearrayoutputstream bytestream = null;
try {
input = new fileinputstream(outfile);
bytestream = new bytearrayoutputstream();
int ch;
while ((ch = input.read()) != -1) {
bytestream.write(ch);
}
} catch (filenotfoundexception e) {
e.printstacktrace();
} catch (ioexception e) {
e.printstacktrace();
} finally {
try {
bytestream.close();
input.close();
log.info("删除临时文件");
if (outfile.exists()) {
outfile.delete();
}
} catch (ioexception e) {
e.printstacktrace();
}
}
return bytestream;
}
/**
* 获取资源文件的临时文件
* 资源文件打jar包后,不能直接获取,需要通过流获取生成临时文件
*
* @param filename 文件路径 templates/xxx.docx
* @return
*/
public static file gettempfile(string filename) {
final file tempfile = new file(filename);
inputstream fonttempstream = null;
try {
fonttempstream = freemarkutils.class.getclassloader().getresourceasstream(filename);
fileutils.copyinputstreamtofile(fonttempstream, tempfile);
} catch (exception e) {
e.printstacktrace();
} finally {
try {
if (fonttempstream != null) {
fonttempstream.close();
}
} catch (ioexception e) {
e.printstacktrace();
}
}
return tempfile;
}
/**
* 插入图片水印
* @param srcbyte 已生成pdf的字节数组(流转字节)
* @param destfile 生成有水印的临时文件 temp.pdf
* @return
*/
public static fileoutputstream addwatermark(byte[] srcbyte, string destfile) {
// 待加水印的文件
pdfreader reader = null;
// 加完水印的文件
pdfstamper stamper = null;
fileoutputstream fileoutputstream = null;
try {
reader = new pdfreader(srcbyte);
fileoutputstream = new fileoutputstream(destfile);
stamper = new pdfstamper(reader, fileoutputstream);
int total = reader.getnumberofpages() + 1;
pdfcontentbyte content;
// 设置字体
//basefont font = basefont.createfont();
// 循环对每页插入水印
for (int i = 1; i < total; i++) {
final pdfgstate gs = new pdfgstate();
// 水印的起始
content = stamper.getundercontent(i);
// 开始
content.begintext();
// 设置颜色 默认为蓝色
//content.setcolorfill(basecolor.blue);
// content.setcolorfill(color.gray);
// 设置字体及字号
//content.setfontandsize(font, 38);
// 设置起始位置
// content.settextmatrix(400, 880);
//content.settextmatrix(textwidth, textheight);
// 开始写入水印
//content.showtextaligned(element.align_left, text, textwidth, textheight, 45);
// 设置水印透明度
// 设置笔触字体不透明度为0.4f
gs.setstrokeopacity(0f);
image image = null;
image = image.getinstance("url");
// 设置坐标 绝对位置 x y 这个位置大约在 a4纸 右上角展示logo
image.setabsoluteposition(472, 785);
// 设置旋转弧度
image.setrotation(0);// 旋转 弧度
// 设置旋转角度
image.setrotationdegrees(0);// 旋转 角度
// 设置等比缩放 图片大小
image.scalepercent(4);// 依照比例缩放
// image.scaleabsolute(200,100);//自定义大小
// 设置透明度
content.setgstate(gs);
// 添加水印图片
content.addimage(image);
// 设置透明度
content.setgstate(gs);
//结束设置
content.endtext();
content.stroke();
}
} catch (ioexception e) {
e.printstacktrace();
} catch (documentexception e) {
e.printstacktrace();
} finally {
try {
stamper.close();
fileoutputstream.close();
reader.close();
} catch (documentexception e) {
e.printstacktrace();
} catch (ioexception e) {
e.printstacktrace();
}
}
return fileoutputstream;
}
}4、编写测试接口
import cn.xj.util.freemarkutils;
import org.springframework.web.bind.annotation.getmapping;
import org.springframework.web.bind.annotation.requestmapping;
import org.springframework.web.bind.annotation.restcontroller;
import javax.servlet.http.httpservletresponse;
import java.io.ioexception;
import java.util.hashmap;
import java.util.map;
import java.util.uuid;
@restcontroller
@requestmapping("/report")
public class reportcontroller {
@getmapping("/doc2pdf")
public void doc2pdf(httpservletresponse response) {
map<string, object> params = new hashmap<>();
params.put("username","xiangjiao1");
params.put("password","******");
params.put("age",22);
params.put("email","专注写bug测试中文");
final byte[] data = freemarkutils.createdocx2pdf(params, "templates_report/001.docx");
string filename = uuid.randomuuid().tostring() + "_001_test.pdf";
generatefile(response, data, filename);
}
/**
* 下载文件
* @param response 相应
* @param data 数据
* @param filename 文件名
*/
private void generatefile(httpservletresponse response, byte[] data, string filename) {
response.setheader("content-type", "application/octet-stream");
response.setcharacterencoding("utf-8");
response.setheader("content-disposition", "attachment;filename=" + filename);
try {
response.getoutputstream().write(data);
} catch (ioexception e) {
e.printstacktrace();
} finally {
try {
response.getoutputstream().close();
} catch (ioexception e) {
e.printstacktrace();
}
}
}
}请求测试
http://localhost/report/doc2pdf

docx4j 复杂docx文件转pdf碰见的坑总结
转pdf出现空格压缩、中文缩减等问题,可以考虑将半角替换成全角,将模板中的空格使用全角空格替换。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论