SpringBoot pdf打印及预览(openhtmltopdf+freemarker)

SpringBoot pdf打印及预览(openhtmltopdf+freemarker)

  1. 添加依赖

openhtmltopdf+freemarker

  <properties>
        <openhtml.version>1.0.10</openhtml.version>
    </properties>
<!--openhtmltopdf -->
   <dependencies>
        <dependency>
            <!-- ALWAYS required, usually included transitively. -->
            <groupId>com.openhtmltopdf</groupId>
            <artifactId>openhtmltopdf-core</artifactId>
            <version>${openhtml.version}</version>
        </dependency>
        <dependency>
            <!-- Required for PDF output. -->
            <groupId>com.openhtmltopdf</groupId>
            <artifactId>openhtmltopdf-pdfbox</artifactId>
            <version>${openhtml.version}</version>
        </dependency>
        <dependency>
            <!-- Optional, leave out if you do not need logging via slf4j. -->
            <groupId>com.openhtmltopdf</groupId>
            <artifactId>openhtmltopdf-slf4j</artifactId>
            <version>${openhtml.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
  </dependencies>
  1. 在/resources/template/view/html新建模板xxx.ftl文件
<html>
<head>
    <meta charset="UTF-8"/>
    <style>
    /* 特有css样式自行参考官网 */
        @page {
            size: a4;
            @top-left {
                content: element(header-left);
            }
            @top-center {
                content: element(header-center);
            }
            @top-right {
                content: element(header-right);
            }
            @bottom-center {
                font-size: 14px;
                content: counter(page);
                font-family: 'simsun', serif;
            }
            margin: 50px;
        }

        * {
            margin: 0;
            padding: 0;
            font-family: 'simsun', serif;
        }

        #page-header-left {
            font-size: 10px;
            font-weight: bold;
            position: running(header-left);
        }

        #page-header-center {
            font-size: 10px;
            font-weight: bold;
            position: running(header-center);
        }

        #page-header-right {
            font-size: 10px;
            font-weight: bold;
            position: running(header-right);
        }

        table {
            width: 100%;
            font-size: 14px;
            border-collapse: collapse;
            font-family: 'simsun', serif;
            border-spacing: 0;
            /* The magical table pagination property. */
            /*-fs-table-paginate: paginate;*/
            /* Recommended to avoid leaving thead on a page by itself. */
            -fs-page-break-min-height: 0.5cm;
            border-left: 0.07cm solid black;
            border-right: 0.07cm solid black;
            border-bottom: 0.03cm solid black;
        }

        .checkbox {
            display: inline-block;
            position: relative;
            top: 1px;
            width: 10px;
            height: 10px;
            border: 1px solid black;

        }

        .correct {
            display: inline-block;
            position: relative;
            top: 2px;
            right: 15px;
            width: 7px;
            height: 3px;
            border-left: 1px solid black;
            border-bottom: 1px solid black;
            -webkit-transform: rotate(-45deg);
            transform: rotate(-45deg);
            margin-right: -16px;
        }

        input[type = "checkbox"] {
            width: 10px;
            height: 10px;
            border: 1px solid black;
        }

        tr, thead, tfoot {
            page-break-inside: avoid;
        }

        td, th {
            page-break-inside: avoid;
            font-family: 'simsun', serif;
            padding: 1px;
            border-top: 0.03cm solid black;
            border-left: 0.03cm solid black;
        }


        td:first-child, th:first-child {
            border-left: 0;
        }

        #table-title {
            text-align: center;
            margin-top: 0;
            margin-bottom: 5px;
            font-weight: bold;
        }  
       
    </style>
</head>
<body>
<div class="header-box">
    <div id="page-header-left">
        <span id="page-header-text">编号:<span style="font-weight: normal">${code!''}</span></span>
    </div>
    <div id="page-header-center">
    <span id="page-header-text">时间 <sup>1)</sup>:<span
                style="font-weight: normal">${time?date!''}</span></span>
    </div>
</div>
<div class="basic-box">
    <table style="border-top: 0;">
        <tr>
            <td>标题1</td>
            <td>${xx1!''}</td>
            <td>标题2</td>
            <td>${xx2!''}</td>
            <td>标题3</td>
            <td>${xx3!''}</td>
        </tr>
         <tr>
            <td style="font-weight: bold;text-align: left;">结果</td>
            <td colspan="3" style="text-align: left;">
                <div>
                    <div class="checkbox"></div>
                    <#if result??&&result == '1'>
                        <div class="correct"></div>
                    </#if>
                    <div style="display: inline-block">合格</div>
                    <div class="checkbox"></div>
                    <#if result??&&result == '0'>
                        <div class="correct"></div>
                    </#if>
                    <div style="display: inline-block">不合格</div>
                </div>
            </td>
            <td colspan="4" style="font-weight: bold;text-align: left;">签字人:</td>
        </tr>
    </table>
</div>



</body>
</html>
  1. controller接口
   @RequestMapping(value = "/print/pdf/{code}", produces = MediaType.APPLICATION_PDF_VALUE)
    public ResponseEntity<byte[]> printPdf(@PathVariable("code") String detectCode) throws IOException, TemplateException {
        Assert.hasText(code, "code不能为空");
        return detectReportPrintService.findPdf(code);
    }
  1. service实现类

📌pdf预览有中文乱码,需要重新引入simsun.ttf字体文件,放在/resources/fonts文件夹下(与代码路径一致即可),ftl文件中css样式必须font-family: 'simsun', serif;

📌在 Configuration 中可以使用下面的方法来方便建立三种模板加载。 (每种方法都会在其内部新建一个模板加载器对象,然后创建 Configuration 实例来使用它。)
1. void setDirectoryForTemplateLoading(File dir);
2. void setClassForTemplateLoading(Class cl, String prefix);
3. void setServletContextForTemplateLoading(Object servletContext, String path);
项目打包为jar包只能使用setClassForTemplateLoading方法。

    public ResponseEntity<byte[]> findPdf(String code) throws IOException, TemplateException {
        //获取ftl所需变量值的自定义方法
        Map data = this.findValue(code);
        //FreeMarker 生成 html字符串
        Configuration conf = new Configuration(Configuration.VERSION_2_3_23);
        conf.setDefaultEncoding("UTF-8");
        //加载模板文件(模板的路径)
        //有多种加载模板的方法
        //项目打包为jar包只能使用setClassForTemplateLoading方法
        //第一个参数:springboot启动类,第二个参数:ftl模板对应resources下的位置
        conf.setClassForTemplateLoading(XaWebApplication.class, "/template/view/html");
        // 加载模板
        Template template = conf.getTemplate("/xxx.ftl");
        //传入变量生成html字符串
        String htmlString = FreeMarkerTemplateUtils.processTemplateIntoString(template, data );
        //openhtmltopdf生成pdf
        @Cleanup ByteArrayOutputStream os = new ByteArrayOutputStream();
        PdfRendererBuilder builder = new PdfRendererBuilder();
        builder.withHtmlContent(htmlString, "");
        builder.useFont(new FSSupplier<InputStream>() {
            @SneakyThrows
            @Override
            public InputStream supply() {
                return resourceLoader.getResource("classpath:fonts/simsun.ttf").getInputStream();
            }
        }, "simsun");
        builder.toStream(os);
        builder.run();
        byte[] bytes = os.toByteArray();
        return new ResponseEntity<>(bytes, HttpStatus.OK);
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容