JAVA导出PDF,Freemarket+itext.
最近客户有个需求,针对企业的信息整理出一个电子版的报告,要求以pdf格式展示,接到需求后分析了一下需求,整体方案如下,
1.通过freemarket,将html模板渲染成完整的html页面;
2.再通过itext将html页面渲染成pdf.
直接上代码:
- 准备html 模板.
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> <title>信用评价下载</title> <style> body{ width: 100%; height: auto; } .title{ font-family: -apple-system; font-size: 36px; font-weight: 400; text-align: center; } table { width: 100%; font-family: verdana,arial,sans-serif; font-size:16px; color:#333333; border-width: 1px; border-color: #ebeef5; border-collapse: collapse; text-align: center; } table th { border-width: 1px; padding: 8px; border-style: solid; border-color: #666666; background-color: #dedede; } table td { border-width: 1px; padding: 8px; border-style: solid; border-color: #ebeef5; /*background-color: #ffffff;*/ } table .tr1{ background-color: #e4dbd0; font-weight: bold; } </style> </head> <body > <div > <div class="title" style="width: 100%"> <span>企业信用报告</span> </div> <div style="height: 400px" > </div> <div style="text-align: center;font-size: 24px"> (${(baseInfo.entName)!'--'}) </div> <div style="height: 500px"> </div> <div style="text-align: center"> 报告生成时间: ${createTime} </div> <br></br> <!--企业基本信息--> <div> <div style="font-weight: bold"> <p>一:企业基本信息</p> </div> <div> <table> <tbody> <tr> <td>企业名称</td> <td colspan="3">${(baseInfo.entName)!'--'}</td> </tr> <tr> <td>统一社会信用代码</td> <td colspan="3">${(baseInfo.uniscid)!'--'}</td> </tr> <tr> <td>经营场所</td> <td colspan="3">${(baseInfo.dom)!'--'}</td> </tr> <tr> <td style="width: 25%">法定代表人</td> <td style="width: 25%">${(baseInfo.name)!'--'}</td> <td style="width: 20%">成立日期</td> <td>${(baseInfo.estDate)!'--'}</td> </tr> <tr> <td>注册资本(万)</td> <td >${(baseInfo.regcap)!'--'}</td> <td>经营状态</td> <td>${(baseInfo.regCtateCn)!'--'}</td> </tr> <tr> <td>登记机关</td> <td >${(baseInfo.regorgCn)!'--'}</td> <td>公司类型</td> <td>${(baseInfo.entTypeCn)!'--'}</td> </tr> <tr> <td>经营范围</td> <td colspan="3"> <div > ${(baseInfo.opScope)!'--'} </div> </td> </tr> </tbody> </table> </div> </div> <!--高管信息--> <br></br> <br></br> <br></br> <!--高管信息--> <div> <div style="font-weight: bold"> <p>二:高管信息</p> </div> <div> <table> <tbody> <tr class="tr1"> <td >序号</td> <td >职位</td> <td >人员</td> </tr> <#if managerInfo?? && (managerInfo?size > 0)> <#list managerInfo as value> <tr> <td style="width: 5%">${value_index?if_exists+1}</td> <td style="width: 50%">${(value.position)!'--'}</td> <td style="width: 45%">${(value.name)!'--'}</td> </tr> </#list> </#if> <tr> <td colspan="2" style="text-align: left">共计[${managerInfo?size}]条</td> </tr> </tbody> </table> </div> </div> <!--股东信息--> <br></br> <br></br> <br></br> <!--股东信息--> <div> <div style="font-weight: bold"> <p>三:股东信息</p> </div> <div> <table> <tbody> <tr class="tr1"> <td style="width: 5%" >序号</td> <td >股东名称</td> <td >股东类型</td> <td >出资比例</td> <td >出资额度</td> <td >出资时间</td> <td >货种</td> </tr> <#if enterpriseShareInfo?? && (enterpriseShareInfo?size > 0)> <#list enterpriseShareInfo as value> <tr> <td style="width: 2%">${value_index?if_exists+1}</td> <td>${(value.shareName)!'--'}</td> <td >${(value.shareType)!'--'}</td> <td >${(value.moneyPre)!'--'}</td> <td >${(value.moneyTotal)!'--'}</td> <td >${(value.moneyTime)!'--'}</td> <td >${(value.costType)!'--'}</td> </tr> </#list> </#if> <tr> <td colspan="6" style="text-align: left">共计[${enterpriseShareInfo?size}]条</td> </tr> </tbody> </table> </div> </div> <!--工商变更--> <br></br> <br></br> <br></br> <!--工商变更--> <div> <div style="font-weight: bold"> <p>四:工商变更</p> </div> <div> <table> <tbody> <tr class="tr1"> <td style="width: 5%" >序号</td> <td style="width: 15%">变更日期</td> <td style="width: 20%">变更事项</td> <td style="width: 35%">变更前</td> <td style="width: 35%">变更后</td> </tr> <#if enterpriseChange?? && (enterpriseChange?size > 0)> <#list enterpriseChange as value> <tr> <td style="width: 5%">${value_index?if_exists+1}</td> <td>${(value.changeDate)!'--'}</td> <td >${(value.changeItem)!'--'}</td> <td >${(value.beforeChange)!'--'}</td> <td >${(value.afterChange)!'--'}</td> </tr> </#list> </#if> <tr> <td colspan="4" style="text-align: left">共计[${enterpriseChange?size}]条</td> </tr> </tbody> </table> </div> </div> <!--商标--> <!--<br></br> <br></br> <br></br> <!–商标–> <div> <div style="font-weight: bold"> <p>五:商标</p> </div> <div> <table> <tbody> <tr class="tr1"> <td >序号</td> <td >申请日期</td> <td >注册号</td> <td >状态</td> <td >商标名</td> <td >国际分类</td> </tr> <tr> <td colspan="6" style="text-align: left">共计[0]条</td> </tr> </tbody> </table> </div> </div>--> <!--专利--> <br></br> <br></br> <br></br> <!--专利--> <div> <div style="font-weight: bold"> <p>五:专利</p> </div> <div> <table> <tbody> <tr class="tr1"> <td >序号</td> <td style="width: 20%">专利名称</td> <td style="width: 10%">公开/公布号</td> <td style="width: 16%">类型</td> <td style="width: 16%">法律状态</td> <td style="width: 16%">公开/公布日期</td> </tr> <#if enterprisePatent?? && (enterprisePatent?size > 0)> <#list enterprisePatent as value> <tr> <td>${value_index?if_exists+1}</td> <td>${(value.patentName)!'--'}</td> <td >${(value.authorizeNum)!'--'}</td> <td >${(value.typeName)!'--'}</td> <td >${(value.lastStatus)!'--'}</td> <td >${(value.authorizeDate)!'--'}</td> </tr> </#list> </#if> <tr> <td colspan="5" style="text-align: left">共计[${enterprisePatent?size}]条</td> </tr> </tbody> </table> </div> </div> <br></br> <br></br> <br></br> <!--软件著作权--> <div> <div style="font-weight: bold"> <p>六:软件著作权</p> </div> <div> <table> <tbody> <tr class="tr1"> <td >序号</td> <td >登记号</td> <td >软件全称</td> <td >类型</td> <td >版本号</td> <td >著作权人</td> <td >登记批准日期</td> </tr> <#if enterpriseCopyRight?? && (enterpriseCopyRight?size > 0)> <#list enterpriseCopyRight as value> <tr> <td>${value_index?if_exists+1}</td> <td>${(value.number)!'--'}</td> <td >${(value.name)!'--'}</td> <td >${(value.typeName)!'--'}</td> <td >${(value.version)!'--'}</td> <td >${(value.company)!'--'}</td> <td >${(value.approvalDate)!'--'}</td> </tr> </#list> </#if> <tr> <td colspan="6" style="text-align: left">共计[${enterpriseCopyRight?size}]条</td> </tr> </tbody> </table> </div> </div> <!--招投标--> <br></br> <br></br> <br></br> <!--招投标--> <div> <div style="font-weight: bold"> <p>七:招投标</p> </div> <div> <table> <tbody> <tr class="tr1"> <td >序号</td> <td >企业名称</td> <td >角色</td> <td >标题</td> <td >所在地区</td> <td >招标代理</td> <td >发布时间</td> <td >状态</td> </tr> <#if enterpriseBiddingsall?? && (enterpriseBiddingsall?size > 0)> <#list enterpriseBiddingsall as value> <tr> <td style="width: 5%">${value_index?if_exists+1}</td> <td style="width: 10%">${(value.ename)!'--'}</td> <td style="width: 15%">${(value.role)!'--'}</td> <td style="width: 15%">${(value.title)!'--'}</td> <td style="width: 15%">${(value.areaName)!'--'}</td> <td style="width: 15%">${(value.tendeAgent)!'--'}</td> <td style="width: 10%">${(value.date)!'--'}</td> <td style="width: 15%">${(value.status)!'--'}</td> </tr> </#list> </#if> <tr> <td colspan="7" style="text-align: left">共计[${enterpriseBiddingsall?size}]条</td> </tr> </tbody> </table> </div> </div> <br></br> <br></br> <br></br> <div style="margin-bottom: 0px ;color:#626675"> <span>注:</span> <span>各维度数据均按时间排序获取前50条!</span> </div> <!--分割线--> </div> </body> </html>
2. 准备html需要填充的数据
Map map = new HashMap<>(); //报告创建时间. map.put("createTime",DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now())); //获取基本信息. map.put("baseInfo",this.queryEnterpriseBaseInfo(uniscid)); //获取高管信息. map.put("managerInfo",this.queryEnterpriseManagerInfo(uniscid)); //获取股东信息 map.put("enterpriseShareInfo",this.queryEnterpriseShareInfo(uniscid)); //获取工商变更信息 map.put("enterpriseChange",this.queryEnterpriseChangeInfoInfo(uniscid)); //获取专利信息 map.put("enterprisePatent",this.queryEnterprisePatentInfo(uniscid)); //获取著作信息 map.put("enterpriseCopyRight",this.queryEnterpriseCopyRightInfo(uniscid)); //获取招标信息 map.put("enterpriseBiddingsall",this.queryEnterpriseBiddingsallInfo(uniscid)); //生成报告 PDFUtil.htmlToPdf(map,uniscid,"dea.html",resp);
3.加载html模板,渲染html模板,渲染pdf.
package com.audaque.credit; import com.audaque.component.core.AudaqueContexts; import com.itextpdf.text.*; import com.itextpdf.text.Font; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.*; import com.itextpdf.tool.xml.XMLWorkerFontProvider; import com.itextpdf.tool.xml.XMLWorkerHelper; import freemarker.template.Configuration; import freemarker.template.Template; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URLEncoder; import java.nio.charset.Charset; /** * @ClassName 导出pdf工具类. * @Descrription TODO * @Author mxk * @Date 2021/5/28 下午3:30 * @Version 1.0 */ public class PDFUtil { private static final String FONT = AudaqueContexts.getProperty("pdffreemarket.templete")+File.separator+"font"+File.separator+"simsun.ttf"; private static Configuration freemarkerCfg = null; static { String templetePath = AudaqueContexts.getProperty("pdffreemarket.templete"); freemarkerCfg =new Configuration(); //freemarker的模板目录 try { freemarkerCfg.setDirectoryForTemplateLoading(new File(templetePath)); } catch (IOException e) { e.printStackTrace(); } } /** * 将freemark渲染为html,转换成pdf * @param t 动态数据 * @param * @param <T> */ public static <T> void htmlToPdf(T t, String fileName, String template, HttpServletResponse resp) { // 渲染html内容 String content = PDFUtil.freeMarkerRender(t, template); // System.out.println("解析之后的数据=>>>>"+content); Rectangle rectangle = new Rectangle(PageSize.A4); Document document = new Document(rectangle); try { resp.setCharacterEncoding("UTF-8"); resp.setHeader("content-Type", "application/pdf"); resp.setHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(fileName + ".pdf", "UTF-8")); // 建立书写器 PdfWriter writer = PdfWriter.getInstance(document, resp.getOutputStream()); writer.setPageEvent(new TextWaterMarkPdfPageEvent()); document.open(); XMLWorkerFontProvider fontImp = new MyFontsProvider(); fontImp.register(FONT); XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(content.getBytes("utf-8")), null, Charset.forName("UTF-8"), fontImp); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } finally { document.close(); } } /** * 设置字体 */ public static class TextWaterMarkPdfPageEvent extends PdfPageEventHelper { protected PdfTemplate total; protected BaseFont helv; public void onOpenDocument(PdfWriter writer, Document document) { total = writer.getDirectContent().createTemplate(100, 100); total.setBoundingBox(new Rectangle(-20, -20, 100, 100)); try { helv = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); } catch (Exception e) { throw new ExceptionConverter(e); } } @Override public void onEndPage(PdfWriter writer, Document document) { PdfContentByte cb = writer.getDirectContent(); cb.beginText(); try { // 设置水印字体参数及大小 (字体参数,字体编码格式,是否将字体信息嵌入到pdf中(一般不需要嵌入),字体大小) cb.setFontAndSize(helv, 50); PdfGState gs = new PdfGState(); gs.setFillOpacity(0.3f); gs.setStrokeOpacity(0.3f); // 设置透明度 cb.setGState(gs); // 设置水印对齐方式 水印内容 X坐标 Y坐标 旋转角度 cb.showTextAligned(Element.ALIGN_RIGHT, AudaqueContexts.getProperty("pdffreemarket.waterMarketText"), 385, 500, 45); // 设置水印颜色(灰色) cb.setColorFill(BaseColor.GRAY); //结束 cb.endText(); } catch (Exception e) { e.printStackTrace(); } super.onEndPage(writer, document); } } /** * 重写 字符设置方法,解决中文乱码问题 */ public static class MyFontsProvider extends XMLWorkerFontProvider { public Font getFont(final String fontname, final String encoding, final boolean embedded, final float size, final int style, final BaseColor color) { BaseFont bf = null; try { bf = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); } catch (Exception e) { } Font font = new Font(bf, size, style, color); font.setColor(color); return font; } } /** * freemarker渲染html */ public static <T> String freeMarkerRender(T data, String htmlTmp) { Writer out = new StringWriter(); try { freemarkerCfg.setDefaultEncoding("UTF-8"); // 获取模板,并设置编码方式 Template template = freemarkerCfg.getTemplate(htmlTmp); template.setEncoding("UTF-8"); // 合并数据模型与模板,将合并后的数据和模板写入到流中,这里使用的字符流 template.process(data, out); out.flush(); return out.toString(); } catch (Exception e) { e.printStackTrace(); } finally { try { out.close(); } catch (IOException ex) { ex.printStackTrace(); } } return null; } }
写在最后,网络上关于java导出pdf 的教程与样例代码太多了,但是基本上都是一个板子刻出来的.我想要加个通过freemarket渲染后(结构完美), 再在itext渲染的过程中增加水印,网络大神们都建议采用两步走.先将渲染后的html存储本地,然后读取出来
再用itext渲染,这不是托裤子放屁么,就没个完整的方案,于是就去翻itext api 发现了一个神奇的类PdfPageEventHelper 发现这个类是在itext渲染html 到pdf过程中,每一次分页的过程中均调用,那我为什不能在这里添加水印呢,于是乎一个很完美的方案
出来了,自定义一个类继承PdfPageEventHelper,然后将这个类注册到书写器中,这样在渲染每一页之后再增加一个水印.,最后附上我生成的一个效果,晚上又可以加个鸡腿了.
道生一,一生二,二生三,三生万物。