骄阳似火的六月天,被迫在办公室吹着空调瑟瑟发抖。脑海中不停的考虑,什么才是程序员真正的宿命。
这次咱们要实现一个导出pdf文件的简略需要,基于Spring Boot 2.2.5.RELEASE。
咱们用到的依赖如下
<dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.13.2</version> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-asian</artifactId> <version>5.2.0</version> </dependency>
spring mvc中曾经存在了一个AbstractPdfView
的抽象类,然而应用到的依赖被网友吐槽太过老旧,所以咱们本人写一个,这个抽象类没什么特地,就是将原有的包都换成itext的包。
package com.jason.cloud.view;import com.itextpdf.text.Document;import com.itextpdf.text.DocumentException;import com.itextpdf.text.PageSize;import com.itextpdf.text.pdf.PdfWriter;import org.springframework.web.servlet.view.AbstractView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.ByteArrayOutputStream;import java.io.OutputStream;import java.util.Map;/** * 重写抽象类,将原有的AbstractPdfView由itext低版本替换为高版本 * create by Jason * Date: 2021/6/2 * Time: 11:52 */public abstract class AbstractTextPdfView extends AbstractView { public AbstractTextPdfView() { this.setContentType("application/pdf"); } protected boolean generatesDownloadContent() { return true; } protected final void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { ByteArrayOutputStream baos = this.createTemporaryOutputStream(); Document document = this.newDocument(); PdfWriter writer = this.newWriter(document, baos); this.prepareWriter(model, writer, request); this.buildPdfMetadata(model, document, request); this.buildPdfDocument(model, document, writer, request, response); this.writeToResponse(response, baos); } protected Document newDocument() { return new Document(PageSize.A4); } protected PdfWriter newWriter(Document document, OutputStream os) throws DocumentException { return PdfWriter.getInstance(document, os); } protected void prepareWriter(Map<String, Object> model, PdfWriter writer, HttpServletRequest request) throws DocumentException { writer.setViewerPreferences(this.getViewerPreferences()); } protected int getViewerPreferences() { return PdfWriter.ALLOW_PRINTING | PdfWriter.PageLayoutSinglePage; } protected void buildPdfMetadata(Map<String, Object> model, Document document, HttpServletRequest request) { } protected abstract void buildPdfDocument(Map<String, Object> var1, Document var2, PdfWriter var3, HttpServletRequest var4, HttpServletResponse var5) throws Exception;}
咱们写一个实现了本人AbstractTextPdfView
的类PdfView。
package com.jason.cloud.view;import com.itextpdf.text.*;import com.itextpdf.text.pdf.*;import org.springframework.core.io.ClassPathResource;import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.net.URLEncoder;import java.util.Map;public class PdfView extends AbstractTextPdfView { // 大题目字号 private final float FONT_SIZE_TITLE = 16f; // 小标题字号 private final float FONT_SIZE_HEADLINE = 14f; // 默认字号 private final float FONT_SIZE_DEFAULT = 12f; // 题目行间距 private final float LEADING_HEADLINE = 24f; // 默认行间距 private final float LEADING_DEFAULT = 14f; // 段落间距 private final float SPACING_PARAGRAPH = 18f; // 题目与内容间距 private final float SPACING_CONTENT = 12f; // 左侧缩进 private final float INDENTATION_LEFT = 50f; @Override protected void buildPdfDocument(Map<String, Object> map, Document document, PdfWriter pdfWriter, HttpServletRequest request, HttpServletResponse response) throws Exception { String name = (String) map.get("fileName"); if (StringUtils.isEmpty(name)) { name = "pdf"; } String fileName = name + ".pdf"; // 解决火狐浏览器乱码 String agent = request.getHeader("User-Agent"); if (agent != null) { if ("firefox".contains(agent.toLowerCase())) { response.setHeader("content-disposition", String.format("attachment;filename*=utf-8'zh_cn'%s", URLEncoder.encode(fileName,"utf-8"))); } else { response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf-8")); } } response.setCharacterEncoding("UTF-8"); response.setContentType("Application/pdf;charset=utf-8"); // 这个设置对我来说不论用,应用了下一行的字体设置// BaseFont baseFont = BaseFont.createFont("STSong-Light","UniGB-UCS2-H",true); // 字体 宋体(这里的设置看文末的阐明) BaseFont SimSunFont = BaseFont.createFont(new ClassPathResource("/font/SimSun.ttf").getPath(), BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);// BaseFont SimSunFont = BaseFont.createFont(new ClassPathResource("/font/simsun.ttc").getPath() + ",1", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); // 设置默认字号 Font defaultFont = new Font(SimSunFont, FONT_SIZE_DEFAULT); // 设置题目字号 Font headlineFont = new Font(SimSunFont, FONT_SIZE_HEADLINE); // 默认彩色// defaultFont.setColor(BaseColor.BLACK); // 默认A4// document.setPageSize(PageSize.A4); document.addTitle((String) map.get("title")); // 打开文档 document.open(); // 设置题目内容,字号 Paragraph title = new Paragraph(new Chunk("需要审核申请单", new Font(SimSunFont, FONT_SIZE_TITLE))); // 设置居中 title.setAlignment(Element.ALIGN_CENTER); // 设置下间距 title.setSpacingAfter(LEADING_HEADLINE); document.add(title); // 章节,带序号// Chapter chapter = new Chapter(title, 1);// document.add(chapter); /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ // 一级题目 Paragraph headlineApply = new Paragraph(new Chunk("申请单信息", headlineFont)); // 设置题目与内容间距 headlineApply.setSpacingAfter(SPACING_CONTENT); document.add(headlineApply); // 创立申请单信息表格,5列 PdfPTable applyInfoTable = new PdfPTable(5); // 设置表格占比 applyInfoTable.setWidthPercentage(100f); // 表格宽度占比 applyInfoTable.setWidths(new int[]{25, 35, 15, 10, 15}); // 表头 final String[] applyInfoColumns = new String[] {"申请单号", "申请事项", "所属单位", "申请人", "手机号"}; for (String infoColumn : applyInfoColumns) { // 建设单元格 PdfPCell cell = new PdfPCell(new Phrase(infoColumn, defaultFont)); // 设置单元格高度(不要瞎设置,高度不够会影响换行)// cell.setFixedHeight(TABLE_CELL_HEIGHT); // 设置单元格背景色 cell.setBackgroundColor(new BaseColor(231,230,230)); applyInfoTable.addCell(cell); } // 依照表头程序填入数据 PdfPCell cell; cell = new PdfPCell(new Phrase("124389759016789", defaultFont)); applyInfoTable.addCell(cell); cell = new PdfPCell(new Phrase("我要申请六十个存档", defaultFont)); applyInfoTable.addCell(cell); cell = new PdfPCell(new Phrase("浣熊市警察局", defaultFont)); applyInfoTable.addCell(cell); cell = new PdfPCell(new Phrase("里昂", defaultFont)); applyInfoTable.addCell(cell); cell = new PdfPCell(new Phrase("18876543210", defaultFont)); applyInfoTable.addCell(cell); // 将数据表退出文档 document.add(applyInfoTable); /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ // 一级题目 Paragraph headlineDemand = new Paragraph(new Chunk("需要详情", headlineFont));// headlineDemand.setSpacingBefore(SPACING_PARAGRAPH); document.add(headlineDemand); /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ // 退出list List list = new List(); list.setSymbolIndent(12f); // 设置item款式 list.setListSymbol("\u2022"); // 多写几行,使pdf变成两页 for (int i = 1; i <= 60; i++) { list.add(new ListItem(new Phrase("第" + i + "项", defaultFont))); } document.add(list); document.close(); }}
那最初咱们在将前端控制器补上
@GetMapping("/pdf") public ModelAndView exportPDF() { Map<String, Object> params = new HashMap<>(16); params.put("fileName", "pdf文档导出"); params.put("title", "这是一个神奇的PDF"); return new ModelAndView(new PdfView(), params); }
这里着重阐明一点,最后我也是应用上面这一段代码,导出的pdf汉字不会显示(应用预览的时候,岂但能看到汉字,还能复制文字,就很奇怪)。这里有可能就是字符集不匹配造成的。
// 这段代码酌情应用BaseFont.createFont("STSong-Light","UniGB-UCS2-H",BaseFont.NOT_EMBEDDED);
解决办法如下:
在C:\Windows\Fonts
中找到新宋体 惯例
(当然后面的宋体惯例
也能够),复制到工程的/resources/font
下,会变成simsun.ttc
,并且在代码中获取门路后需加上",1
"(据说ttc是多个ttf的聚合,1相似于数组下标),并且逗号和1之间不能有空格。
或者网上下载了SimSun.ttf
(我下载的10M)。