首页前端开发HTMLFreemarker动态模板渲染&flyingsaucer将html转PDF(多页固定头尾)

Freemarker动态模板渲染&flyingsaucer将html转PDF(多页固定头尾)

时间2023-07-08 16:46:02发布访客分类HTML浏览1057
导读:一、序言一般正常来说,生成PDF的操作都是通过将HTML转成PDF,HTML动态渲染可以借助模板引擎,如常用的Thymeleaf或者Freemarker。HTML转PDF可以通过flyingsaucer来实现,可以参考之前博主写的一篇文章《...

一、序言


一般正常来说,生成PDF的操作都是通过将HTML转成PDF,HTML动态渲染可以借助模板引擎,如常用的Thymeleaf或者Freemarker。


HTML转PDF可以通过flyingsaucer来实现,可以参考之前博主写的一篇文章《flyingsaucer进行html文件转图片和pdf》,至于PDF样式,我们可以通过CSS打印样式来控制。


今天这篇文章主要分享模板引擎动态渲染以及结合flyingsaucer通过CSS打印样式控制PDF的内容呈现,固定每页PDF的头和尾部。


二、CSS样式控制打印模板


在PrintCSS上有一篇文章: Running Headers and Footers ,里面会介绍CSS运行时元素以及如何控制打印PDF时的头部和尾部。


这里介绍一个在线工具:PrintCSS.live,里面可以在线预览pdf打印效果,如下:



三、代码示例

1、pom.xml

dependency>
    
     groupId>
    org.springframework.boot/groupId>
    
     artifactId>
    spring-boot-starter-freemarker/artifactId>
    
/dependency>
    
dependency>
    
     groupId>
    org.xhtmlrenderer/groupId>
    
     artifactId>
    flying-saucer-pdf-itext5/artifactId>
    
     version>
    9.1.22/version>
    
 /dependency>
    


2、application.yml

spring:
  # freemarker configuration
  freemarker:
    cache: true
    suffix: .ftl
    charset: UTF-8
    template-loader-path: classpath:templates/

备注:template-loader-path.ftl模板加载路径,这里我们指定了类路径下的templates目录。

3、PdfGenerationController

import com.itextpdf.text.pdf.BaseFont;
    
import com.universe.wonderful.pojo.model.AccountProofModel;
    
import freemarker.template.Configuration;
    
import freemarker.template.Template;
    
import lombok.RequiredArgsConstructor;
    
import lombok.extern.slf4j.Slf4j;
    
import org.springframework.http.ContentDisposition;
    
import org.springframework.http.HttpHeaders;
    
import org.springframework.http.HttpStatus;
    
import org.springframework.http.MediaType;
    
import org.springframework.http.ResponseEntity;
    
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
    
import org.springframework.web.bind.annotation.RequestMapping;
    
import org.springframework.web.bind.annotation.RestController;
    
import org.xhtmlrenderer.pdf.ITextRenderer;
    

import java.io.ByteArrayOutputStream;
    
import java.nio.charset.StandardCharsets;
    
import java.time.LocalDate;


/**
 * @author Nick Liu
 * @date 2023/3/1
 */
@Slf4j
@RestController
@RequiredArgsConstructor
public class PdfGenerationController {
    

    private final Configuration configuration;
    

    @RequestMapping("/pdf/preview")
    public ResponseEntitybyte[]>
 downloadPdfWithFixedHeaderAndFooter() {
    
        AccountProofModel accountProofModel = AccountProofModel.builder()
            .generationDate(LocalDate.now().toString())
            .memberName("Nick Liu")
            .memberAddress("Nanshan District, Shenzhen city, Guangdong Province")
            .accountNo("88888888888888")
            .bankName("ICBC")
            .bankSwiftCode("ABCDEFG")
            .bankAddress("Shenzhen city of Guangdong Province")
            .countryName("China")
            .build();
    

        ByteArrayOutputStream os = new ByteArrayOutputStream();

        try {
    
            // 不建议直接创建Template实例,开销比较大,可以直接通过Configuration实例获取,有缓存机制
            Template template = configuration.getTemplate("personalAccountProof.ftl");
    
            String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, accountProofModel);
    
            ITextRenderer renderer = new ITextRenderer();
    
            // 如果内容有中文则需要添加支持中文的字体
            renderer.getFontResolver().addFont("/fonts/calibri.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
    
            renderer.setDocumentFromString(content);
    
            renderer.layout();
    
            renderer.createPDF(os);
    
            renderer.finishPDF();

        }
 catch (Exception e) {

            log.error("Fail to generate pdf: {
}
    ", e.getMessage(), e);
    
            return ResponseEntity.internalServerError().body(null);

        }
    

        HttpHeaders respHeaders = new HttpHeaders();
    
        respHeaders.setContentType(MediaType.APPLICATION_PDF);
    
        respHeaders.setContentDisposition(ContentDisposition.inline().filename("accountProof.pdf", StandardCharsets.UTF_8).build());
    
        return new ResponseEntity>
    (os.toByteArray(), respHeaders, HttpStatus.OK);

    }

}
    


备注:字体会从类路径下加载,底层通过ClassLoader#getResourceAsStream()读取。

字体目录和freemarker模板目录如下图:



4、Freemarker模板内容

!DOCTYPE html>
    
html>
    
head>
    
    meta charset="UTF-8" />
    
    title>
    Running Headers and Footers/title>
    
    style>

        @page {
    
            size: A4;
    
            margin: 40mm 10mm 50mm 10mm;


            @top-left {
    
                content: element(headerLeft);

            }


            @bottom-center {
    
                content: element(footerCenter);

            }

        }


        * {
    
            padding: 0;
    
            margin: 0;

        }


        body {
    
            font-family: Calibri, serif;

        }


        .headerLeft {
    
            position: running(headerLeft);

        }
    

        .titleWrapper >
 div {
    
            margin: 2px 0;

        }


        .footerCenter {
    
            text-align: center;
    
            position: running(footerCenter);

        }


        .footerTipsWrapper {
    
            color: #C1A97D;
    
            margin-top: 10px;
    
            border-top: 2px solid #EFE7DA;

        }
    

        .footerTipsWrapper >
 div {
    
            font-size: 12px;
    
            margin-top: 12px;

        }


        .contentWrapper {
    
            margin-top: -10px;

        }


        .paddingWrapper {
    
            padding: 10px;

        }


        .accountIntroduction {
    
            margin-top: 60px;
    
            background-color: #EFE7DA;
    
            border: 1px solid #EFE7DA;
    
            border-radius: 10px;

        }


        .accountDetailsWrapper {
    
            margin-top: 50px;
    
            border: 3px solid #EFE7DA;
    
            border-radius: 10px;

        }


        .subTitle {
    
            font-weight: bold;
    
            border-bottom: 2px solid #EFE7DA;
    
            padding-bottom: 10px;

        }
    

        .accountDetails >
 div {
    
            margin-top: 8px;

        }
    
    /style>
    
/head>
    
body>
    
    div class="headerLeft paddingWrapper">
    
        img src="http://localhost:8080/images/proof/head_logo.png" />
    
    /div>
    
    div class="footerCenter">
    
        div class="footerLogoWrapper">
    img src="http://localhost:8080/images/proof/footer_logo.png" alt="logo" />
    /div>
    
        div class="footerTipsWrapper">
    
            div>
    www.aletaplanet.com | account@aletaplanet.com/div>
    
            div>
    MPHK Management Company Limited | Suite 615, 6/F, Ocean Centre, Harbour City, Tsim Sha Tsui, Tsim Sha Tsui, Kowloon |br/>
    
                License No.: 21-10-03068
            /div>
    
        /div>
    
    /div>
    

    div class="contentWrapper">
    
        div class="titleWrapper paddingWrapper">
    
            div>
    b>
    Proof of Account Details/b>
    /div>
    
            div>
Generated on: ${
generationDate}
    /div>
    
        /div>
    
        div class="tips paddingWrapper">
    To whom it may concern,/div>
    
        div class="accountIntroduction paddingWrapper">
    
            div>
    b>
Personal account of ${
memberName}
    /b>
    /div>
    
            div style="margin-top: 10px;
    word-break: break-word">

                This letter confirms the below account details allow ${
memberName}
 residing at ${
memberAddress}
     to receive payments into his/ her AP-1 Account:
            /div>
    
        /div>
    
        div class="accountDetailsWrapper paddingWrapper">
    
            div class="subTitle">
    Business account details/div>
    
            div class="accountDetails">
    
                div>
Account Name: ${
memberName}
    /div>
    
                div>
Account Number: ${
accountNo}
    /div>
    
                div>
Bank Name: ${
bankName}
    /div>
    
                div>
Bank SWIFT/BIC: ${
bankSwiftCode}
    /div>
    
                div>
Bank Country: ${
countryName}
    /div>
    
                div>
Bank Address: ${
bankAddress}
    /div>
    
            /div>
    
        /div>
    
    /div>
    
/body>
    
/html>



在@page{ } 代码块中我们指定了打印页面的大小为A4、上下左右的边缘分别为40毫米、50毫米、10毫米、10毫米,同时在页面左上角指定了logo,以及在页面底部居中指定了logo和描述。


实际上@top-left和@bottom-center的效果类似于固定定位。


备注:关于@page、@top-left、@bottom-center的介绍可以参考:

https://www.w3.org/TR/css-page-3/#margin-boxes


四、展示效果


启动项目,打开浏览器,输入http://localhost:8080/pdf/preview,可以预览生成的PDF,如下:


备注:如果有多页,头部和尾部的logo也会在同样的地方显示。

声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!

前端开发

若转载请注明出处: Freemarker动态模板渲染&flyingsaucer将html转PDF(多页固定头尾)
本文地址: https://pptw.com/jishu/296322.html
网页前端学习第一次(HTML) docker镜像分析利器之dive

游客 回复需填写必要信息