近期在项目种遇到了实时生成复杂 PDF 的需求,经过一番调研和测试,最终选择了采用 Thymeleaf 和 iText7 来实现需求,本文将详细介绍实现过程。
Maven 引入依赖;
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>com.itextpdfgroupId>
<artifactId>html2pdfartifactId>
<version>5.0.0version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.8.21version>
dependency>
编写 Thymeleaf 模板 resources/templates/demo.html
;
DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PDF Demotitle>
<style>
body {
padding-top: 50px;
padding-left: 60px;
padding-right: 60px;
font-family: 'KaiTi', serif;
}
.title {
color: red;
text-align: center;
margin-bottom: 50px;
}
table {
width: 100%;
border: 1px solid black;
border-spacing: 0;
}
th {
border: 1px solid black;
background-color: rgb(128, 128, 128);
}
td {
border: 1px solid black;
}
style>
head>
<body>
<h1 class="title" th:text="${title}">h1>
<table>
<thead>
<tr>
<th>序号th>
<th>姓名th>
<th>年龄th>
<th>性别th>
tr>
thead>
<tbody th:each="student, studentStat : ${students}">
<tr>
<td th:text="${studentStat.count}">td>
<td th:text="${student.name}">td>
<td th:text="${student.age}">td>
<td th:text="${student.sex}">td>
tr>
tbody>
table>
body>
html>
添加中文字体资源 resources/fonts/simkai.ttf
;
编写 PDF 页码事件处理 handler/PageEventHandler
;
package com.xiaoqqya.itextpdf.handler;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.properties.TextAlignment;
/**
* 页码事件处理.
*
* @author xiaoQQya
* @since 2023/08/29
*/
public class PageEventHandler implements IEventHandler {
@Override
public void handleEvent(Event event) {
PdfDocumentEvent documentEvent = (PdfDocumentEvent) event;
PdfDocument document = documentEvent.getDocument();
PdfPage page = documentEvent.getPage();
Rectangle pageSize = page.getPageSize();
PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), document);
Canvas canvas = new Canvas(pdfCanvas, pageSize);
float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
float y = pageSize.getBottom() + 15;
Paragraph paragraph = new Paragraph("-- " + document.getPageNumber(page) + " --")
.setFontSize(10);
canvas.showTextAligned(paragraph, x, y, TextAlignment.CENTER);
canvas.close();
}
}
编写 Student 实体类 model/domain/Student
;
package com.xiaoqqya.itextpdf.model.domain;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 学生.
*
* @author xiaoQQya
* @since 2023/08/29
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Student {
/**
* 姓名
*/
private String name;
/**
* 、
* 年龄
*/
private Integer age;
/**
* 性别
*/
private String sex;
}
编写 Service service/PdfService
生成 PDF;
package com.xiaoqqya.itextpdf.service.impl;
import cn.hutool.core.io.resource.ResourceUtil;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.html2pdf.resolver.font.DefaultFontProvider;
import com.itextpdf.io.font.FontProgramFactory;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.font.FontProvider;
import com.xiaoqqya.itextpdf.exception.CustomException;
import com.xiaoqqya.itextpdf.handler.PageEventHandler;
import com.xiaoqqya.itextpdf.model.domain.Student;
import com.xiaoqqya.itextpdf.service.PdfService;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* PDF Service.
*
* @author xiaoQQya
* @since 2023/08/29
*/
@Service
public class PdfServiceImpl implements PdfService {
@Resource
private TemplateEngine templateEngine;
/**
* 生成 PDF.
*
* @param outputStream 输出流
*/
@Override
public void generatePdf(OutputStream outputStream) {
// 模拟数据
List<Student> students = new ArrayList<>();
students.add(Student.builder().name("小红").age(18).sex("女").build());
students.add(Student.builder().name("小强").age(21).sex("男").build());
students.add(Student.builder().name("熊大").age(19).sex("男").build());
// 生成 Thymeleaf 上下文
Context context = new Context();
context.setVariable("title", "PDF Demo");
context.setVariable("students", students);
String demo = templateEngine.process("demo", context);
// 生成 PDF, 并添加页码
try (PdfWriter pdfWriter = new PdfWriter(outputStream); PdfDocument pdfDocument = new PdfDocument(pdfWriter)) {
pdfDocument.setDefaultPageSize(PageSize.A4);
pdfDocument.addEventHandler(PdfDocumentEvent.INSERT_PAGE, new PageEventHandler());
ConverterProperties converterProperties = new ConverterProperties();
FontProvider fontProvider = new DefaultFontProvider(true, true, false);
fontProvider.addFont(FontProgramFactory.createFont(ResourceUtil.readBytes("fonts/simkai.ttf")));
converterProperties.setFontProvider(fontProvider);
HtmlConverter.convertToPdf(demo, pdfDocument, converterProperties);
} catch (IOException e) {
throw new CustomException(e.getMessage());
}
}
}
编写 Controller controller/PdfController
返回给前端浏览器展示;
package com.xiaoqqya.itextpdf.controller;
import com.xiaoqqya.itextpdf.service.PdfService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* PDF Controller.
*
* @author xiaoQQya
* @since 2023/08/29
*/
@RestController
@RequestMapping(value = "/pdf")
public class PdfController {
@Resource
private PdfService pdfService;
/**
* 生成 PDF.
*/
@GetMapping
public void generatePdf(HttpServletResponse response) throws IOException {
pdfService.generatePdf(response.getOutputStream());
}
}
浏览器访问 http://localhost:8080/pdf
查看效果。
参考文章: