# EasyPrint **Repository Path**: javalx/easy-print ## Basic Information - **Project Name**: EasyPrint - **Description**: Lodop + JasperReports 打印工具类 - **Primary Language**: Java - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 14 - **Created**: 2021-11-15 - **Last Updated**: 2022-05-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # EasyPrint Lodop + JasperReports 打印模块 ## 前言 ### Lodop Lodop是什么? 有人说她是Web打印控件,因为她能打印、在浏览器中以插件的形式出现,用简单一行语句就把整个网页打印出来; 有人说她是打印编程接口,因为她介于浏览器和打印设备之间,是个通道和桥梁,几乎能想到的打印控制事项都能做到; 有人说她是JavaScript的扩展,因为她所有功能就那么几个语句,和JS语法一样,一看就明白个究竟; 有人说她是报表打印工具,因为那个add_print_table语句把报表统计的那点事弄了个明明白白; 有人说她是条码打印工具,因为用了她再也不用后台生成条码图片了,前端一行指令就动态输出清晰准确的条码,一维二维都行; 有人说她是图表打印工具,因为用她能输出几乎能想象的任何图表,虽然没那么豪华,但什么饼图、折线图、柱图甚至复合图等等都不在话下; 有人说她是个小玩意,因为她体积太小了,才2M多,她所包含的其中任何一个对照工具都是她的好几倍(例如条码打印控件、图表控件等); 有人说她是套打教案,因为以Lodop+JS实现套打这种模式,在网上已被吵吵为教科书般的解决方案; 有人说她是Web打印控件的“终结者”,因为接触“她”后再不想别的“她”; 有人说她就是一个Web编程小工具,因为有了她,在BS下的打印终于像cs下那种随意而高效了; 但我们说,她是全国1000多家软件公司的智慧结晶,诞生10年了,几乎每个功能细节都蕴藏着无数开发者的期待和汗水; 她就是Lodop(读音“劳道谱”),没有别的名称,她是web开发的必选伴侣; 现在,她又添了个小兄弟,名叫C-Lodop(可编程的云打印),未来将由他开创...... 相关链接: * 官网:[跳转地址](http://www.lodop.net/index.html) ### JasperReports JasperReports是一个基于Java的开源报表工具,它可以在Java环境下像其它IDE报表工具一样来制作报表。JasperReports 支持PDF、HTML、XLS、CSV和XML文件输出格式。JasperReports是当前Java开发者最常用的报表工具。 ### LINUX系统下PDF字体异常 将 `doc/fonts` 下的字体上传至 `/usr/share/fonts` 下 ### 相关链接 * [开发手册](./doc/JasperReport) * [报表jar包](https://community.jaspersoft.com/project/jasperreports-library) * [报表设计器](https://community.jaspersoft.com/project/jaspersoft-studio) ## 项目结构 ``` EasyPrint ├─ main │ ├─ java │ │ └─ cn │ │ └─ sjx │ │ └─ print │ │ ├─ constants │ │ │ └─ PrintConstant.java 常量类 │ │ ├─ entity │ │ │ ├─ HtmlRenderData.java 模板填充数据对象 │ │ │ ├─ HtmlRenderDataSource.java 模板数据来源 枚举类 │ │ │ ├─ PaperStyle.java 纸张样式 枚举类 │ │ │ └─ PrintConfig.java 打印风格对象(如纸张样式,内容边距......) │ │ ├─ Printer.java lodop打印调用类(组装打印代码供前端使用) │ │ ├─ service │ │ │ ├─ B64ImgReplacedElementFactory.java │ │ │ ├─ BaseRenderFactoryHandler.java │ │ │ └─ customer 自定义化接口 │ │ │ ├─ impl │ │ │ │ ├─ DefaultJasperReportDataListCoverImpl.java dataList 的默认 JasperReportParametersCover实现 │ │ │ │ └─ DefaultJasperReportParametersMapCoverImpl.java parametersMap 的默认 JasperReportParametersCover实现 │ │ │ └─ JasperReportParametersCover.java jasperReport报表参数覆盖 │ │ └─ util │ │ ├─ HtmlRenderFactoryUtil.java html渲染工具类 │ │ ├─ PdfRenderFactoryUtil.java pdf渲染工具类 │ │ └─ SpringContextHolder.java │ └─ resources │ ├─ config 配置文件目录 │ │ └─ print-config.setting │ ├─ print-template 模板文件目录 │ │ ├─ demo4.html - 测试样例(beetl) │ │ └─ Test_A4.jrxml - 测试样例(JasperReport) │ └─ static │ ├─ lodop_install 打印控件下载路径 │ │ ├─ CLodop_Setup_for_Win32NT.exe │ │ ├─ install_lodop32.exe │ │ └─ install_lodop64.exe │ └─ vendors │ ├─ html2canvas html转图片插件 │ │ ├─ html2canvas.js │ │ └─ html2canvas.min.js │ └─ lodop lodop打印插件 │ ├─ LodopFuncs.js - lodop打印核心代码 │ └─ Lodop_print.js - 自定义调用方法 └─ test DEMO运行示例 ``` ## 开发教程 ### 引入jar包 ```xml cn.sjx EasyPrint 0.01-SNAPSHOT cn.hutool hutool-all ${hutool.version} com.alibaba fastjson ${fastjson.version} com.alibaba druid-spring-boot-starter ${druid.version} ``` ### 安装软件 * [JaspersoftStudio-6.12.2](https://nchc.dl.sourceforge.net/project/jasperstudio/JaspersoftStudio-6.12.2/TIB_js-studiocomm_6.12.2_windows_x86_64.zip) * [Lodop6.226](http://www.lodop.net/download/Lodop6.226_Clodop4.088.zip) *注:JaspersoftStudio使用教程请参考根目录doc文件夹下的pdf文档。iReport是JaspersoftStudio的前身,用法几乎一致。* ### JasperReport模板 在`resource`下新建`print-template`打印模板存放目录。 模板文件如下: Test_A4.jrxml ```xml <band height="60" splitType="Stretch"> <textField> <reportElement x="180" y="0" width="200" height="40" uuid="d07e7265-85f8-4e43-8050-5573734fd1e9"/> <textElement textAlignment="Center" verticalAlignment="Middle"> <font fontName="宋体" size="21"/> </textElement> <textFieldExpression><![CDATA[$P{TABLE_TITLE}]]></textFieldExpression> </textField> </band> ``` *注:* *1. 主表中每增加一个子表需要在`Parameters`下新增一个`SUB_REPORT_[0,1,2.....]`的`net.sf.jasperreports.engine.JasperReport`对象参数。* *2. 子表的数据可以定义在`Parameters`下,参数类型为`Map`或`List`* ### 后端代码 关键代码: ```java // 模板渲染 String html = HtmlRenderFactoryUtil.render("Test_A4", new HtmlRenderData(paramMaps, detailList)); System.out.println("==================== JasperReport模板渲染后的Html代码 ===================="); System.out.println(html); // 打印参数配置 PrintConfig printConfig = new PrintConfig(html); // 生成打印控件需要的代码 String str = new Printer(printConfig).print(); System.out.println("==================== Lodop打印javascript脚本语句 ===================="); System.out.println(str); ``` 举个web调用例子: ```java /** * 打印 * * @param id id 记录ID * @return {@link String} */ @PostMapping("{id}/print") public Response print(@PathVariable("id") String id) { Response response = null; try { // 获取待渲染的数据 Map dataMap = printData(id); // 生成html表格 String html = HtmlRenderFactoryUtil.render("hospitality", new HtmlRenderData( MapUtil.get(dataMap, "entertainmentExpensesMap", new TypeReference>() {}), HtmlRenderDataSource.DATA_FROM_COLLECTION_MAP ) ); // 打印配置,如横向/纵向 // 不设置纸张样式时,默认是 A4 但 长宽全是 0 PrintConfig printConfig = new PrintConfig(html); response = Response.ok(); // new Printer(printConfig).print() 该语句将生成调用Lodop控件的脚本 response.put("expression", new Printer(printConfig).print()); } catch (Exception e) { logger.error("======> 打印出错", e); response = Response.error(e.getMessage()); } return response; } ``` 详细方法用例: ```java package cn.sjx.test; import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.TimeInterval; import cn.hutool.core.lang.Console; import cn.sjx.print.Printer; import cn.sjx.print.entity.HtmlRenderData; import cn.sjx.print.entity.PrintConfig; import cn.sjx.print.service.customer.JasperReportParametersCover; import cn.sjx.print.util.HtmlRenderFactoryUtil; import cn.sjx.print.util.PdfRenderFactoryUtil; import com.forte.util.utils.MockUtil; import org.junit.Test; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 打印测试 * * @author sjx * @date 2020年05月18日 0018 15:05:05 */ public class PrintTest { /** * 示例1 - jasperReport导出html使用示例 * * @throws Exception 异常 */ @Test public void demo1() throws Exception { TimeInterval timer = DateUtil.timer(); Map paramMaps = new HashMap(1); paramMaps.put("TABLE_TITLE", "测试标题"); List> detailList = new ArrayList>(20); for (int i = 0; i < 20; i++) { Map tempMap = new HashMap(); tempMap.put("FIELD_ACCOUNT", MockUtil.title(5)); tempMap.put("FIELD_REAL_NAME", MockUtil.cname()); detailList.add(tempMap); } // 模板渲染 String html = HtmlRenderFactoryUtil.render("Test_A4", new HtmlRenderData(paramMaps, detailList), new JasperReportParametersCover>() { @Override public Map cover(Map parameterMap) throws Exception { return parameterMap; } }, new JasperReportParametersCover>>() { @Override public List> cover(List> dataList) throws Exception { return dataList; } }); System.out.println("==================== JasperReport模板渲染后的Html代码 ===================="); System.out.println(html); // 打印参数配置 PrintConfig printConfig = new PrintConfig(html); // 生成打印控件需要的代码 String str = new Printer(printConfig).print(); System.out.println("==================== Lodop打印javascript脚本语句 ===================="); System.out.println(str); System.out.println("打印运行耗时 " + timer.intervalSecond() + " 秒"); } /** * 示例2 - jasperReport导出pdf示例 * * @return * @author song_jx */ @Test public void demo2() { TimeInterval timer = DateUtil.timer(); Map paramMaps = new HashMap(1); paramMaps.put("TABLE_TITLE", "测试•标题"); List> detailList = new ArrayList>(20); for (int i = 0; i < 20; i++) { Map tempMap = new HashMap(); tempMap.put("FIELD_ACCOUNT", MockUtil.title(5) + ">"); tempMap.put("FIELD_REAL_NAME", MockUtil.cname()); detailList.add(tempMap); } // 模板渲染 File pdfFile = PdfRenderFactoryUtil.render("Test_A4", new HtmlRenderData(paramMaps, detailList)); Console.log("======> 文件路径 = {}", pdfFile.getAbsolutePath()); System.out.println("打印运行耗时 " + timer.intervalSecond() + " 秒"); } /** * 示例3 - html转pdf * * @return * @author song_jx */ @Test public void demo3() { // 字体只处理宋体!!!! String s = "
 
测试标题
帐号真实姓名
Vtvjo席皂斋
Ltoix连雪空
Jazsj屈速
Tzans谷梁成
Eqakb寿呈
Izgmw萧菜铣
Vizmk隆噶
Nhbkv宦譬
Hhpzy郇欠放
Xyupx蔺换稍
Usczd祖玩该
Wkfkc韶芥写
Qgrvq綦毋恋
Dmkmb鲁驯
Yzlsc籍仿
Nfwkd都筒
Nsegj莘喇距
Mvnln覃知
Tkbkb谈酸
Zynak边捷
 
"; File file = PdfRenderFactoryUtil.htmlToPdf(s, Boolean.FALSE); Console.log("======> 文件路径1 = {}", file.getAbsolutePath()); // 该 html 的 style中的 font-family: \'宋体\' 经过特殊处理,需注意 String s1 = "
 
测试标题
帐号真实姓名
Vtvjo席皂斋
Ltoix连雪空
Jazsj屈速
Tzans谷梁成
Eqakb寿呈
Izgmw萧菜铣
Vizmk隆噶
Nhbkv宦譬
Hhpzy郇欠放
Xyupx蔺换稍
Usczd祖玩该
Wkfkc韶芥写
Qgrvq綦毋恋
Dmkmb鲁驯
Yzlsc籍仿
Nfwkd都筒
Nsegj莘喇距
Mvnln覃知
Tkbkb谈酸
Zynak边捷
 
"; File file1 = PdfRenderFactoryUtil.htmlToPdf(s1); Console.log("======> 文件路径2 = {}", file1.getAbsolutePath()); } /** * 示例4 - 使用beetl模板引擎 * * @throws Exception 异常 */ @Test public void demo4() throws Exception { TimeInterval timer = DateUtil.timer(); Map paramMaps = new HashMap(1); paramMaps.put("title", "测试标题"); paramMaps.put("date", "2020-01-01 至 2020-02-28"); List> dataList = new ArrayList>(3); for (int i = 0; i < 5; i++) { HashMap stringObjectHashMap = new HashMap<>(); stringObjectHashMap.put("orgPosition", "测试" + i); stringObjectHashMap.put("opinion", "测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试"); stringObjectHashMap.put("createByName", "姓名_" + i); stringObjectHashMap.put("createDateStr", "2020-09-30"); dataList.add(stringObjectHashMap); } paramMaps.put("dataList", dataList); // 模板渲染 String html = HtmlRenderFactoryUtil.render("demo4.html", paramMaps); System.out.println("==================== beetl模板渲染后的Html代码 ===================="); System.out.println(html); // 打印参数配置 PrintConfig printConfig = new PrintConfig(html); // 生成打印控件需要的代码 String str = new Printer(printConfig).print(); System.out.println("==================== Lodop打印javascript脚本语句 ===================="); System.out.println(str); System.out.println("打印运行耗时 " + timer.intervalSecond() + " 秒"); } /** * 示例5 - 套打 * * @throws Exception 异常 */ @Test public void demo5() throws Exception { TimeInterval timer = DateUtil.timer(); Map paramMaps = new HashMap(1); paramMaps.put("TABLE_TITLE", "测试标题"); List> detailList = new ArrayList>(20); for (int i = 0; i < 5; i++) { Map tempMap = new HashMap(); tempMap.put("FIELD_ACCOUNT", MockUtil.title(5)); tempMap.put("FIELD_REAL_NAME", MockUtil.cname()); detailList.add(tempMap); } // 模板渲染 List htmlList = HtmlRenderFactoryUtil.renderList("Test_A4", new HtmlRenderData(paramMaps, detailList), 4, new JasperReportParametersCover>() { @Override public Map cover(Map paramMap) throws Exception { paramMap.put("TABLE_TITLE", "测试标题2"); Console.log("======> {}", paramMap); return paramMap; } }, null); System.out.println("==================== JasperReport模板渲染后的Html代码 ===================="); Console.log(htmlList); // 打印参数配置 PrintConfig printConfig = new PrintConfig(htmlList); System.out.println("==================== Lodop打印javascript脚本语句 ===================="); // 生成打印控件需要的代码 String str = new Printer(printConfig).printList(); System.out.println(str); System.out.println("打印运行耗时 " + timer.intervalSecond() + " 秒"); } } ``` ### 前端代码 引入javascript插件: ```html ``` 调用方法: ```javascript // html超文本内容打印 let htmlContent = $(layero).find('iframe').eq(0).contents().find('#detail').html(); printPreviewHtml(htmlContent); // 图片打印 html2canvas($(layero).find('iframe').eq(0).contents().find('#detail').get(0)).then(canvas => { // 截图质量(0-1) printPreviewImg(canvas.toDataURL("image/png", 1), '5mm', 5, '99%', '50%'); }); // 模板渲染打印 printPreview("${adminPath}/oa/travelReimbursement/" + rowId + "/print", top.layer); ``` 提供调用的javascript脚本: ```javascript /** * 打印预览,用于后台返回数据打印 * * @param ajaxUrl ajax请求地址 * @param layerAlert layer弹窗组件对象 */ function printPreview(ajaxUrl, layerAlert) { try { // 检查是否安装lodop checkIsInstall(); let loadIndex; $.ajax({ type: "POST", url: ajaxUrl, dataType: "json", beforeSend: function() { // 开启遮罩 loadIndex = layerAlert.msg('打印模板渲染中...', { icon: 16, shade: 0.6, time: 120000 }); }, success: function(data) { // 关闭遮罩 layerAlert.close(loadIndex); if (data.code == 500) { layerAlert.alert("打印异常:" + data.msg, {icon: 2}); return; } // 打印 eval(data.expression); LODOP.PREVIEW(); } }); } catch (e) { console.log(e); } } /** * 打印预览,用于网页内容打印 * * @param htmlContent 超文本标记内容 */ function printPreviewHtml(htmlContent) { try { checkIsInstall(); LODOP.PRINT_INIT("网页内容打印"); LODOP.ADD_PRINT_HTM(0, 0, "100%", "100%", htmlContent); LODOP.PREVIEW(); } catch (e) { console.log(e); } } /** * 打印预览,用于图片打印 * * @param base64ImgStr 图片的base64码 * @param pageTop 上边距 * @param pageLeft 左边距 34px(1px=1/96英寸) * @param imgWidth 图片宽度(默认百分百),支持数值跟百分比 * @param imgHeight 图片高度(默认百分百),支持数值跟百分比 * @param zooming 1:(可变形)扩展缩放模式;2:按原图比例(不变形)缩放模式; */ function printPreviewImg(base64ImgStr, pageTop = '5mm', pageLeft = 34, imgWidth = '100%', imgHeight = '100%', zooming = 2) { try { checkIsInstall(); LODOP.PRINT_INIT("图片打印"); LODOP.ADD_PRINT_IMAGE(pageTop, pageLeft, imgWidth, imgHeight, base64ImgStr); // 按原图比例(不变形)缩放模式 LODOP.SET_PRINT_STYLEA(0, 'Stretch', zooming); LODOP.PREVIEW(); } catch (e) { console.log(e); } } ``` ## test代码执行结果 ![运行示例](./doc/image/demo.png)