骄阳似火的六月天,被迫在办公室吹着空调瑟瑟发抖。脑海中不停的考虑,什么才是程序员真正的宿命。
这次咱们要实现一个导出 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)。