基于 JasperReports 的Web模板打印功能

简介

JasperReports是一个基于Java的开源报表工具,它可以在Java环境下像其它IDE报表工具一样来制作报表。JasperReports 支持PDF、HTML、XLS、CSV和XML文件输出格式。JasperReports是当前Java开发者最常用的报表工具。

准备工作

官网
报表模板设计器下载
iReport报表制作参考
本例使用的JasperReports版本为6.3.0,通过其报表设计器(iReport是其前身)设计好打印模板,使用核心包按照指定的模板.jasper文件生成用于打印的HTML文件。

集成方式

添加Maven依赖

  <properties>
    ......
    <!-- 报表核心包 -->
    <jasperreports.version>6.3.0</jasperreports.version>
    <!--如果打印模板用到条形码需要依赖以下两种条形码生成的jar包-->
    <barcode4j.version>2.1</barcode4j.version>
    <barbecue.version>1.5-beta1</barbecue.version>
    <!--将.jrxml打印模板定义编译成.jasper文件-->
    <groovy.version>2.4.5</groovy.version>
    <!--解析.jrxml-->
    <commons-digester3.version>3.2</commons-digester3.version>
    <!--SVG文档操作相关-->
    <batik-bridge.version>1.8</batik-bridge.version>
     <!--XML与图形进行转换-->
    <xmlgraphics-commons.version>2.1</xmlgraphics-commons.version>
    ......
  </properties>

 <dependencyManagement>
    <dependencies>
      ......
      <dependency>
        <groupId>net.sf.jasperreports</groupId>
        <artifactId>jasperreports</artifactId>
        <version>${jasperreports.version}</version>
      </dependency>
      <dependency>
        <groupId>org.codehaus.groovy</groupId>
        <artifactId>groovy-all</artifactId>
        <version>${groovy.version}</version>
      </dependency>
      <dependency>
        <groupId>net.sf.barcode4j</groupId>
        <artifactId>barcode4j</artifactId>
        <version>${barcode4j.version}</version>
      </dependency>
      <dependency>
        <groupId>net.sourceforge.barbecue</groupId>
        <artifactId>barbecue</artifactId>
        <version>${barbecue.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.xmlgraphics</groupId>
        <artifactId>batik-bridge</artifactId>
        <version>${batik-bridge.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.xmlgraphics</groupId>
        <artifactId>xmlgraphics-commons</artifactId>
        <version>${xmlgraphics-commons.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-digester3</artifactId>
        <version>${commons-digester3.version}</version>
      </dependency>
    </dependencies>
   <!--此处只列出本人过程中会用到的相关功能的依赖包,不代表JasperReports完整功能依赖,大家根据需要引入即可-->
  ......
 </dependencyManagement>

修改配置

在web工程src/main/resources路径下新建jasperreports.properties,在此文件中配置覆盖JasperReports的相关属性的默认值。

由于生成条形码默认格式为svg,本人使用的web端打印插件不支持此图片格式,因此只用到以下配置修改。

# barcode4j 生成图片格式默认为svg,改用普通图片格式
net.sf.jasperreports.components.barcode4j.image.producer=image
# 图片dpi,默认值为72,Lodop打印使用的是96px,因此需要覆盖
net.sf.jasperreports.image.dpi=96
# HTML单位长度单位,默认为px,由于jasperReports的像素是按照1dpi=72px设计,和Lodop打印插件冲突,此处改为1dpi=72pt即可和Lodop的单位换算一致
net.sf.jasperreports.export.html.size.unit=pt

覆盖HtmlExporter

1、HtmlExporter生成的html文件,最终内容和实际设计器中的预览结果有差异,左右两边多了一些间距,通过修改代码去除不必要的间距。

2、由于本人使用的打印插件分页是根据Html实际内容高度进行的,因此需要保证每个分页页面的宽度、高度和设计器指定的纸张宽高(此处使用pt作为单位)一致。

  • 重写HtmlExporter类,类路径自己定义。
  • 修改内容如下
# 增加以下方法
//获取每个分页的宽度
  private String getPageWidth() {
    List<ExporterInputItem> items = exporterInput.getItems();
    if (items.isEmpty()) {
      return "";
    }

    JasperPrint jasperPrint = items.get(0).getJasperPrint();
    return jasperPrint.getPageWidth() + "pt";//此处单位保持一致使用pt
  }

# 修改exportReportToWriter方法,固定好宽度同时去除左右两边的间距。
protected void exportReportToWriter() throws JRException, IOException {
    HtmlExporterConfiguration configuration = getCurrentConfiguration();
    String htmlHeader = configuration.getHtmlHeader();
    String betweenPagesHtml = configuration.getBetweenPagesHtml();
    String htmlFooter = configuration.getHtmlFooter();
    boolean flushOutput = configuration.isFlushOutput();// FIXMEEXPORT maybe
                                                        // move flush flag to
                                                        // output

    if (htmlHeader == null) {
      String encoding = getExporterOutput().getEncoding();

      writer
          .write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n");
      writer.write("<html>\n");
      writer.write("<head>\n");
      writer.write("  <title></title>\n");
      writer.write("  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=" + encoding
          + "\"/>\n");
      writer.write("  <style type=\"text/css\">\n");
      writer.write("    a {text-decoration: none}\n");
      writer.write("  </style>\n");
      writer.write("</head>\n");
      writer
          .write("<body text=\"#000000\" link=\"#000000\" alink=\"#000000\" vlink=\"#000000\" style=\"margin:0;width:"
              + getPageWidth() + "\">\n"); //此处有修改,固定宽度
      writer.write("<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n");
      writer.write("<tr><td>\n");//此处有修改,去除左边占比50%的空td块
      writer.write("\n");
    } else {
      writer.write(htmlHeader);
    }

    List<ExporterInputItem> items = exporterInput.getItems();

    for (reportIndex = 0; reportIndex < items.size(); reportIndex++) {
      ExporterInputItem item = items.get(reportIndex);

      setCurrentExporterInputItem(item);

      List<JRPrintPage> pages = jasperPrint.getPages();
      if (pages != null && pages.size() > 0) {
        PageRange pageRange = getPageRange();
        int startPageIndex = (pageRange == null || pageRange.getStartPageIndex() == null) ? 0
            : pageRange.getStartPageIndex();
        int endPageIndex = (pageRange == null || pageRange.getEndPageIndex() == null) ? (pages
            .size() - 1) : pageRange.getEndPageIndex();

        JRPrintPage page = null;
        for (pageIndex = startPageIndex; pageIndex <= endPageIndex; pageIndex++) {
          if (Thread.interrupted()) {
            throw new ExportInterruptedException();
          }

          page = pages.get(pageIndex);

          writer.write("<a name=\"" + JR_PAGE_ANCHOR_PREFIX + reportIndex + "_" + (pageIndex + 1)
              + "\"></a>\n");

          /*   */
          exportPage(page);

          if (reportIndex < items.size() - 1 || pageIndex < endPageIndex) {
            if (betweenPagesHtml == null) {
              // writer.write("<br/>\n<br/>\n"); 此处有修改,去除多余的换行符
            } else {
              writer.write(betweenPagesHtml);
            }
          }

          writer.write("\n");
        }
      }
    }

    ReportContext reportContext = getReportContext();
    if (fontsToProcess != null && fontsToProcess.size() > 0)// when no
                                                            // resourceHandler,
                                                            // fonts are not
                                                            // processed
    {
      if (reportContext == null) {
        @SuppressWarnings("deprecation")
        HtmlResourceHandler resourceHandler = getExporterOutput().getResourceHandler() == null ? getResourceHandler()
            : getExporterOutput().getResourceHandler();

        for (HtmlFontFamily htmlFontFamily : fontsToProcess.values()) {
          writer.write("<link class=\"jrWebFont\" rel=\"stylesheet\" href=\""
              + resourceHandler.getResourcePath(htmlFontFamily.getId()) + "\">\n");
        }

        // generate script tag on static export only
        writer.write("<!--[if IE]>\n");
        writer.write("<script>\n");
        writer.write("var links = document.querySelectorAll('link.jrWebFont');\n");
        writer
            .write("setTimeout(function(){ if (links) { for (var i = 0; i < links.length; i++) { links.item(i).href = links.item(i).href; } } }, 0);\n");
        writer.write("</script>\n");
        writer.write("<![endif]-->\n");
      } else {
        reportContext.setParameterValue(JsonExporter.REPORT_CONTEXT_PARAMETER_WEB_FONTS,
            fontsToProcess);
      }
    }

    // place hyperlinksData on reportContext
    if (hyperlinksData.size() > 0) {
      // for sure reportContext is not null, because otherwise there would be no
      // item in the hyperilnkData
      reportContext.setParameterValue("net.sf.jasperreports.html.hyperlinks", hyperlinksData);
    }

    if (htmlFooter == null) {
      writer.write("</td></tr>\n"); //此处有修改,去除右边占比50%的空td块
      writer.write("</table>\n");
      writer.write("</body>\n");
      writer.write("</html>\n");
    } else {
      writer.write(htmlFooter);
    }

    if (flushOutput) {
      writer.flush();
    }
  }

根据报表模板生成HTML文件

public PrintPageConfig genPrintHtmlFile(String tmplFileUrl, String targetUrl,
      Map<String, String> params) throws DemoServiceException {
    Connection connection = null;
    try {
      DruidDataSource ds = ApplicationContextUtils
          .getBean(DemoConstants.DEFAULT_DATASOURCE_CONTEXT_ID, DruidDataSource.class);
      connection = ds.getConnection();

      //传入模板.jasper文件内容
      InputStream inputFile = new FileInputStream(tmplFileUrl);
      JasperReport jasperReport = (JasperReport) JRLoader.loadObject(inputFile);
      //指定数据源,并传入对应报表的执行参数
      JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, (Map) params,
          connection);

       //生成最终结果的HTML文件,注意此处引用自己重写的HtmlExporter
      HtmlExporter exporter = new HtmlExporter(DefaultJasperReportsContext.getInstance());
      exporter.setExporterInput(new SimpleExporterInput(jasperPrint));
      exporter.setExporterOutput(new SimpleHtmlExporterOutput(targetUrl));
      exporter.exportReport();

      return new PrintPageConfig().setWidth(jasperPrint.getPageWidth())
          .setHeight(jasperPrint.getPageHeight());
    } catch (Exception e) {
      throw new DemoServiceException(e.getMessage());
    } finally {
      if (connection != null) {
        try {
          connection.close();
        } catch (SQLException e) {
          throw new DemoServiceException(e.getMessage());
        }
      }
    }

  }

使用Lodop打印生成的html内容

本例使用Lodop综合版本:Lodop6.216_CLodop2.093。
具体集成方式见官网说明,此处只介绍如何配合JasperReports打印出来的html文件进行不失真打印。Lodop官网

/**
 * 根据已有的html url进行打印
 * @param type
 * 是打印还是打印预览:preview-打印预览;其它值为打印。
 * @param config
 * 打印内容配置:
 * 1、为字符串时,表示打印的html所在的远程url地址。
 * 2、为对象时:{
 * url:打印的html所在的远程url地址,
 * width:html内容的实际宽度(单位像素)
 * height:html内容的实际高度(单位像素)
 * }
 *
 * 说明:
 * JasperReports设计模板的像素和实际纸张的尺寸之比为72pt=1英寸(in)。
 * Lodop打印的内容是按照一个dpi占96px=72pt=1in=2.54cm(厘米)=25.4mm(毫米)打印,JasperReports的html结果内容是按照一个dpi占72pt生成。
 *
 */
function doPrintByHtml(type, config) {
    if (!window.LODOP) {
        alert('打印插件不可用,请先到首页进行下载安装,然后刷新浏览器!');
        return;
    }
    if (window.top && window.top.waltz && window.top.waltz.app && window.top.waltz.app.globalOption) {
        var globalOption = window.top.waltz.app.globalOption;
        if (globalOption.lodopLicenseAuthor) {
            LODOP.SET_LICENSES(globalOption.lodopLicenseAuthor, globalOption.lodopLicenseKey, "", "");
        }
    }
    LODOP.PRINT_INIT("printTask");

    var url;
    if ((typeof config == 'string') && config.constructor == String) {
        url = config;
    } else {
        url = config.url;
        var width = Math.round((config.width / 72 ) * 25.4) * 10,//1 inch=25.4mm,Lodop默认单位为0.1mm
            height = Math.round((config.height / 72) * 25.4) * 10;
        //设置纸张大小,设置后不允许用户手动修改纸张大小
        LODOP.SET_PRINT_PAGESIZE(0, width, height, "");
    }
    // 指定按实际宽度缩放比例
    LODOP.SET_PRINT_MODE('PRINT_PAGE_PERCENT', "Full-Width");
    LODOP.ADD_PRINT_HTM(0, 0, '100%', '100%', 'URL:' + url);
    LODOP.SET_PRINT_STYLE("Stretch", 2);
    if (type == 'preview') {
        LODOP.PREVIEW();
    } else {
        LODOP.PRINT();
    }
};

注意事项

使用JasperReports根据打印模板生成打印结果文件时,对应服务器必须安装打印模板中用到的字体,比如:打印内容包含中文,linux服务器需要安装中文的语言包,否则执行生成结果文件时报错。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,590评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,409评论 25 707
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,726评论 6 342
  • 林辰等了半个多小时,突然听到“biu~biu~~biu~”mk416的枪声,随后“哒哒哒”的ak枪声也响了起来...
    生活爱问人阅读 294评论 0 1
  • 自己选的,遇到再难的事也要坚持,哭过就算了,毕竟谁都不容易。自己确实没做好。对不起越越,妈妈才忙好,妈妈的最爱
    日落绚阅读 195评论 0 1