基于 JasperReports 的Web模板打印功能

簡介

JasperReports是一個基于Java的開源報表工具,它可以在Java環(huán)境下像其它IDE報表工具一樣來制作報表。JasperReports 支持PDF钉寝、HTML、XLS、CSV和XML文件輸出格式。JasperReports是當前Java開發(fā)者最常用的報表工具伟众。

準備工作

官網(wǎng)
報表模板設(shè)計器下載
iReport報表制作參考
本例使用的JasperReports版本為6.3.0,通過其報表設(shè)計器(iReport是其前身)設(shè)計好打印模板召廷,使用核心包按照指定的模板.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文檔操作相關(guān)-->
    <batik-bridge.version>1.8</batik-bridge.version>
     <!--XML與圖形進行轉(zhuǎn)換-->
    <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>
   <!--此處只列出本人過程中會用到的相關(guān)功能的依賴包账胧,不代表JasperReports完整功能依賴,大家根據(jù)需要引入即可-->
  ......
 </dependencyManagement>

修改配置

在web工程src/main/resources路徑下新建jasperreports.properties先紫,在此文件中配置覆蓋JasperReports的相關(guān)屬性的默認值找爱。

由于生成條形碼默認格式為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設(shè)計,和Lodop打印插件沖突疮胖,此處改為1dpi=72pt即可和Lodop的單位換算一致
net.sf.jasperreports.export.html.size.unit=pt

覆蓋HtmlExporter

1环戈、HtmlExporter生成的html文件,最終內(nèi)容和實際設(shè)計器中的預覽結(jié)果有差異澎灸,左右兩邊多了一些間距院塞,通過修改代碼去除不必要的間距。

2性昭、由于本人使用的打印插件分頁是根據(jù)Html實際內(nèi)容高度進行的拦止,因此需要保證每個分頁頁面的寬度、高度和設(shè)計器指定的紙張寬高(此處使用pt作為單位)一致糜颠。

  • 重寫HtmlExporter類汹族,類路徑自己定義。
  • 修改內(nèi)容如下
# 增加以下方法
//獲取每個分頁的寬度
  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();
    }
  }

根據(jù)報表模板生成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文件內(nèi)容
      InputStream inputFile = new FileInputStream(tmplFileUrl);
      JasperReport jasperReport = (JasperReport) JRLoader.loadObject(inputFile);
      //指定數(shù)據(jù)源法绵,并傳入對應(yīng)報表的執(zhí)行參數(shù)
      JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, (Map) params,
          connection);

       //生成最終結(jié)果的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內(nèi)容

本例使用Lodop綜合版本:Lodop6.216_CLodop2.093。
具體集成方式見官網(wǎng)說明朋譬,此處只介紹如何配合JasperReports打印出來的html文件進行不失真打印盐茎。Lodop官網(wǎng)

/**
 * 根據(jù)已有的html url進行打印
 * @param type
 * 是打印還是打印預覽:preview-打印預覽;其它值為打印徙赢。
 * @param config
 * 打印內(nèi)容配置:
 * 1字柠、為字符串時探越,表示打印的html所在的遠程url地址。
 * 2窑业、為對象時:{
 * url:打印的html所在的遠程url地址,
 * width:html內(nèi)容的實際寬度(單位像素)
 * height:html內(nèi)容的實際高度(單位像素)
 * }
 *
 * 說明:
 * JasperReports設(shè)計模板的像素和實際紙張的尺寸之比為72pt=1英寸(in)钦幔。
 * Lodop打印的內(nèi)容是按照一個dpi占96px=72pt=1in=2.54cm(厘米)=25.4mm(毫米)打印,JasperReports的html結(jié)果內(nèi)容是按照一個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;
        //設(shè)置紙張大小,設(shè)置后不允許用戶手動修改紙張大小
        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根據(jù)打印模板生成打印結(jié)果文件時喷市,對應(yīng)服務(wù)器必須安裝打印模板中用到的字體相种,比如:打印內(nèi)容包含中文,linux服務(wù)器需要安裝中文的語言包品姓,否則執(zhí)行生成結(jié)果文件時報錯寝并。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市腹备,隨后出現(xiàn)的幾起案子衬潦,更是在濱河造成了極大的恐慌,老刑警劉巖馏谨,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件别渔,死亡現(xiàn)場離奇詭異,居然都是意外死亡惧互,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門喇伯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喊儡,“玉大人,你說我怎么就攤上這事稻据“拢” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵捻悯,是天一觀的道長匆赃。 經(jīng)常有香客問我,道長今缚,這世上最難降的妖魔是什么算柳? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮姓言,結(jié)果婚禮上瞬项,老公的妹妹穿的比我還像新娘蔗蹋。我一直安慰自己,他們只是感情好囱淋,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布猪杭。 她就那樣靜靜地躺著,像睡著了一般妥衣。 火紅的嫁衣襯著肌膚如雪皂吮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天税手,我揣著相機與錄音涮较,去河邊找鬼。 笑死冈止,一個胖子當著我的面吹牛狂票,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播熙暴,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼闺属,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了周霉?” 一聲冷哼從身側(cè)響起掂器,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎俱箱,沒想到半個月后国瓮,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡狞谱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年乃摹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跟衅。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡孵睬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伶跷,到底是詐尸還是另有隱情掰读,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布叭莫,位于F島的核電站蹈集,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏雇初。R本人自食惡果不足惜拢肆,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧善榛,春花似錦辩蛋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至咒循,卻和暖如春据途,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背叙甸。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工颖医, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人裆蒸。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓熔萧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親僚祷。 傳聞我的和親對象是個殘疾皇子佛致,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)辙谜,斷路器俺榆,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,111評論 25 707
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,810評論 6 342
  • 林辰等了半個多小時,突然聽到“biu~biu~~biu~”mk416的槍聲装哆,隨后“噠噠噠”的ak槍聲也響了起來...
    生活愛問人閱讀 301評論 0 1
  • 自己選的罐脊,遇到再難的事也要堅持,哭過就算了蜕琴,畢竟誰都不容易萍桌。自己確實沒做好。對不起越越奸绷,媽媽才忙好梗夸,媽媽的最愛
    日落絢閱讀 200評論 0 1