Spring Boot 使用Itext绘制并导出PDF
最终效果
其实可以加分页,但是没有那么精细的需求,所以我最后就没有加,有兴趣的可以尝试下。
项目依赖
<!-- Spring Boot 版本有点老 -->
<spring-boot.version>2.3.12.RELEASE</spring-boot.version><!-- 依赖 -->
<dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.13</version>
</dependency><dependency><groupId>com.itextpdf</groupId><artifactId>itext-asian</artifactId><version>5.2.0</version>
</dependency>
实现代码
出奇的简单,就一个核心的方法
核心实现方法
package com.an.pdfhandle.service;import com.an.pdfhandle.entity.Member;
import com.an.pdfhandle.entity.Team;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;import java.io.OutputStream;
import java.net.URL;
import java.util.List;public class PdfExportService {// 样式化导出为类似画布的横向排版:iText5 实现核心逻辑(支持多个团队)public void exportTeamCanvasStyle(List<Team> teams, OutputStream outputStream) throws Exception {Document document = new Document(PageSize.A4, 36, 36, 36, 36); // A4 纵向页面PdfWriter.getInstance(document, outputStream);document.open();BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);Font nameFont = new Font(baseFont, 12);Font labelFont = new Font(baseFont, 12, Font.BOLD);Font titleFont = new Font(baseFont, 16, Font.BOLD);for (Team team : teams) {// --- 信息页(第一页) ---document.newPage();document.add(new Paragraph("参 赛 队:" + team.getName(), titleFont));document.add(Chunk.NEWLINE);// 绘制基本代表队基本信息Paragraph p1 = new Paragraph();p1.setFont(nameFont);p1.add("领 队:" + team.getLeaderName() + " 联系电话:" + team.getLeaderPhone() + "\n");p1.add("主训教练:" + team.getCoachName() + " 队 医:" + team.getDoctorName() + "\n");p1.add("助理教练:" + team.getAssistantCoachName() + " 联系电话:" + team.getCoachPhone());document.add(p1);document.add(Chunk.NEWLINE);// 绘制TablePdfPTable infoTable = new PdfPTable(8);infoTable.setWidthPercentage(100);infoTable.setWidths(new float[]{1f, 2f, 2f, 1f, 2.5f, 2f, 4f, 2f});String[] headers = {"序号", "身份", "姓名", "性别", "身高/体重", "比赛号码", "身份证号", "服装号码"};for (String h : headers) {PdfPCell cell = new PdfPCell(new Phrase(h, labelFont));cell.setHorizontalAlignment(Element.ALIGN_CENTER);cell.setVerticalAlignment(Element.ALIGN_MIDDLE);cell.setPaddingTop(7f); // 4cm ≈ 113pt,每边加大间距cell.setPaddingBottom(7f);infoTable.addCell(cell);}int index = 1;for (Member m : team.getMembers()) {PdfPCell[] cells = new PdfPCell[] {new PdfPCell(new Phrase(String.valueOf(index++), nameFont)),new PdfPCell(new Phrase(m.getRole(), nameFont)),new PdfPCell(new Phrase(m.getName(), nameFont)),new PdfPCell(new Phrase(m.getGender(), nameFont)),new PdfPCell(new Phrase(m.getHeightWeight(), nameFont)),new PdfPCell(new Phrase(m.getCode(), nameFont)),new PdfPCell(new Phrase(m.getIdCard(), nameFont)),new PdfPCell(new Phrase(m.getClothingSize(), nameFont))};for (PdfPCell cell : cells) {cell.setHorizontalAlignment(Element.ALIGN_CENTER);cell.setVerticalAlignment(Element.ALIGN_MIDDLE);cell.setPaddingTop(7f); // 增加内边距cell.setPaddingBottom(7f);infoTable.addCell(cell);}}document.add(infoTable);// --- 画布页(第二页) ---document.newPage();// 代表队名称Paragraph title = new Paragraph("队名:" + team.getName(), titleFont);title.setAlignment(Element.ALIGN_LEFT);document.add(title);document.add(Chunk.NEWLINE);// 底部头像(其实也是一个表格,但是没有边框)PdfPTable table = new PdfPTable(3); // 每行最多3个头像(适合纵向)table.setWidthPercentage(100);table.getDefaultCell().setBorder(Rectangle.NO_BORDER);for (Member member : team.getMembers()) {PdfPCell cell = new PdfPCell();cell.setBorder(Rectangle.NO_BORDER);// 图片if (member.getImageUrl() != null) {try {Image img = Image.getInstance(new URL(member.getImageUrl()));img.scaleToFit(100, 120);cell.addElement(img);} catch (Exception e) {// 可添加默认图}}// 图片下的文字信息Paragraph info = new Paragraph();info.setLeading(14);info.add(new Chunk(member.getRole() + ":", labelFont));info.add(new Chunk(member.getName() + "\n", nameFont));info.add(new Chunk(member.getIdCard() + "\n", nameFont));info.add(new Chunk("号码:" + member.getCode(), nameFont));cell.addElement(info);table.addCell(cell);}int remainder = team.getMembers().size() % 3;if (remainder != 0) {for (int i = 0; i < 3 - remainder; i++) {PdfPCell empty = new PdfPCell();empty.setBorder(Rectangle.NO_BORDER);table.addCell(empty);}}document.add(table);}document.close();}}
调用Controller
package com.an.pdfhandle.demos.web;import com.an.pdfhandle.entity.Member;
import com.an.pdfhandle.entity.Team;
import com.an.pdfhandle.service.PdfExportService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;@RestController
public class PdfController {@GetMapping("/exportCanvasStyle")public void exportPdfCanvasStyle(HttpServletResponse response) throws Exception {// 模拟数据List<Team> teams = Arrays.asList(new Team("测试1代表队","大袋","15635748705","张三","李四","张三","15635748705", Arrays.asList(new Member("张三", "运动员", "10","男","178/70KG","2XL", "110101199001010011", "http://101.37.161.72:8888/an/M00/00/00/rBoEmGRy2TuARDgsAADin4gdP7Q119.jpg"),new Member("李四", "运动员", "11", "男","178/70KG","2XL", "110101199202020022", "http://101.37.161.72:8888/an/M00/00/00/rBoEmGRy2TuARDgsAADin4gdP7Q119.jpg"))),new Team("测试2代表队","大袋","15635748705","张三","李四","张三","15635748705", Arrays.asList(new Member("王五", "运动员", "21", "男","178/70KG","2XL", "110101199303030033", "http://101.37.161.72:8888/an/M00/00/00/rBoEmGRy2TuARDgsAADin4gdP7Q119.jpg"),new Member("赵六", "运动员", "22", "男","178/80KG","3XL", "110101199303030033", "http://101.37.161.72:8888/an/M00/00/00/rBoEmGRy2TuARDgsAADin4gdP7Q119.jpg"))));response.setContentType("application/pdf");response.setHeader("Content-Disposition", "attachment; filename=teams.pdf");new PdfExportService().exportTeamCanvasStyle(teams, response.getOutputStream());}
}
到此结束,其实生成的比较粗糙,这里需要反思下,我的后端的排版布局能力还是比较差的,哈哈,有待提高。
使用前端直接用DOM元素往出导PDF的话会美观点,毕竟CSS用的更加顺手点, 使用VUE导出可以看我另一篇博客