在项目中碰见一个需求,需要将.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出现空格压缩、中文缩减等问题,可以考虑将半角替换成全角,将模板中的空格使用全角空格替换。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论