共计 23063 个字符,预计需要花费 58 分钟才能阅读完成。
本文简介
本文次要介绍了在 SpringBoot 我的项目下,通过代码和操作步骤,具体的介绍了如何操作 PDF。心愿能够帮忙到筹备通过 JAVA 操作 PDF 的你。
我的项目框架用的 SpringBoot,但在 JAVA 中代码都是通用的。
本文波及 pdf 操作,如下:
- PDF 模板制作
- 基于 PDF 模板生成,并反对下载
- 自定义中文字体
- 齐全基于代码生成,并保留到指定目录
- 合并 PDF,并保留到指定目录
- 合并 PDF,并反对下载
基于 PDF 模板生成:实用于固定格局的 PDF 模板,基于内容进行填空,例如:合同信息生成、固定格局表格等等
齐全基于代码生成:实用于不固定的 PDF,例如:动静表格、动静增加某块内容、不确定的内容大小等不确定的场景
PDF 文件简介
PDF 是可移植文档格局,是一种电子文件格式,具备许多其余电子文档格局无奈相比的长处。PDF 文件格式能够将文字、字型、格局、色彩及独立于设施和分辨率的图形图像等封装在一个文件中。该格式文件还能够蕴含超文本链接、声音和动静影像等电子信息,反对专长文件,集成度和平安可靠性都较高。在零碎开发中通常用来生成比拟正式的报告或者合同类的电子文档。
代码实现 PDF 操作
首先须要引入咱们的依赖,这里通过 maven 治理依赖
在 pom.xml 文件增加以下依赖
<!--pdf 操作 -->
<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>${itextpdf-version}</version>
</dependency>
基于 PDF 模板生成,并下载
PDF 模板制作
- 首先在 word 或者其他软件外面制作模板,筛选你相熟的软件即可,前提是可生成 pdf。
- 将 word 文件转为 pdf 文件。
-
应用 Adobe Acrobat 软件操作 pdf,这里用的是这个软件,只有能实现这个性能,其余的软件也可~
抉择表单编辑哈,咱们要在对应的坑上增加表单占位
-
在表单上增加文本域即可,所有的格局都用文本域即可,这里只是占坑。
对应的域名称要与程序的名称对应,不便前面数据填充,不然前面须要手动解决赋值。
-
创立个简略的模板吧,要留神填充的空间要短缺,不然会呈现数据展现不全呦~
成果如下:
好了,到这里模板就生成好了,咱们保留一下,而后放在咱们的 /resources/templates
目录下
PDF 生成代码编写
在 util
包下创立 PdfUtil.java
工具类,代码如下:
package com.maple.demo.util;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import javax.servlet.ServletOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
/**
* @author 笑小枫
* @date 2022/8/15
* @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
*/
public class PdfUtil {private PdfUtil() { }
/**
* 利用模板生成 pdf
*
* @param data 写入的数据
* @param photoMap 图片信息
* @param out 自定义保留 pdf 的文件流
* @param templatePath pdf 模板门路
*/
public static void fillTemplate(Map<String, Object> data, Map<String, String> photoMap, ServletOutputStream out, String templatePath) {
PdfReader reader;
ByteArrayOutputStream bos;
PdfStamper stamper;
try {
// 读取 pdf 模板
reader = new PdfReader(templatePath);
bos = new ByteArrayOutputStream();
stamper = new PdfStamper(reader, bos);
AcroFields acroFields = stamper.getAcroFields();
// 赋值
for (String name : acroFields.getFields().keySet()) {String value = data.get(name) != null ? data.get(name).toString() : null;
acroFields.setField(name, value);
}
// 图片赋值
for (Map.Entry<String, String> entry : photoMap.entrySet()) {if (Objects.isNull(entry.getKey())) {continue;}
String key = entry.getKey();
String url = entry.getValue();
// 依据地址读取须要放入 pdf 中的图片
Image image = Image.getInstance(url);
// 设置图片在哪一页
PdfContentByte overContent = stamper.getOverContent(acroFields.getFieldPositions(key).get(0).page);
// 获取模板中图片域的大小
Rectangle signRect = acroFields.getFieldPositions(key).get(0).position;
float x = signRect.getLeft();
float y = signRect.getBottom();
// 图片等比缩放
image.scaleAbsolute(signRect.getWidth(), signRect.getHeight());
// 图片地位
image.setAbsolutePosition(x, y);
// 在该页退出图片
overContent.addImage(image);
}
// 如果为 false 那么生成的 PDF 文件还能编辑,肯定要设为 true
stamper.setFormFlattening(true);
stamper.close();
Document doc = new Document();
PdfCopy copy = new PdfCopy(doc, out);
doc.open();
PdfImportedPage importPage = copy.getImportedPage(new PdfReader(bos.toByteArray()), 1);
copy.addPage(importPage);
doc.close();
bos.close();} catch (IOException | DocumentException e) {e.printStackTrace();
}
}
}
在 controller
包下创立 TestPdfController.java
类,并 i 代码如下:
package com.maple.demo.controller;
import com.maple.demo.util.PdfUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @author 笑小枫
* @date 2022/8/15
* @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
*/
@Slf4j
@RestController
@RequestMapping("/example")
@Api(tags = "实例演示 -PDF 操作接口")
public class TestPdfController {@ApiOperation(value = "依据 PDF 模板导出 PDF")
@GetMapping("/exportPdf")
public void exportPdf(HttpServletResponse response) {Map<String, Object> dataMap = new HashMap<>(16);
dataMap.put("nickName", "笑小枫");
dataMap.put("age", 18);
dataMap.put("sex", "男");
dataMap.put("csdnUrl", "https://zhangfz.blog.csdn.net/");
dataMap.put("siteUrl", "https://www.xiaoxiaofeng.com/");
dataMap.put("desc", "大家好,我是笑小枫。");
Map<String, String> photoMap = new HashMap<>(16);
photoMap.put("logo", "https://profile.csdnimg.cn/C/9/4/2_qq_34988304");
// 设置 response 参数,能够关上下载页面
response.reset();
response.setCharacterEncoding("UTF-8");
// 定义输入类型
response.setContentType("application/PDF;charset=utf-8");
// 设置名称
response.setHeader("Content-Disposition", "attachment; filename=" + "xiaoxiaofeng.pdf");
try {ServletOutputStream out = response.getOutputStream();
// 模板门路记
PdfUtil.fillTemplate(dataMap, photoMap, out, "src/main/resources/templates/xiaoxiaofeng.pdf");
} catch (IOException e) {e.printStackTrace();
}
}
}
重启我的项目,在浏览器拜访:http://localhost:6666/example/exportPdf
导出的文件成果如下:👇
齐全基于代码生成,并保留
齐全基于代码生成 PDF 文件,这个就比拟定制化了,这里只讲常见的操作,大家能够用作参考:
自定义字体
PDF 生成的时候,有没有遇到过失落中文字体的问题呢,唉~ 解决一下
先下载字体,这里用黑体演示哈,下载地址:https://www.zitijia.com/downloadpage?itemid=281258939050380345
下载完会失去一个 Simhei.ttf
文件,对咱们就是要它
先在 util
包下创立一个字体工具类吧,代码如下:
留神:代码中相干的绝对路径要替换成本人的
package com.maple.demo.util;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPTable;
import java.io.IOException;
import java.util.List;
/**
* @author 笑小枫
* @date 2022/8/15
* @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
*/
public class PdfFontUtil {private PdfFontUtil() { }
/**
* 根底配置,能够放相对路径,这里演示绝对路径,因为字体文件过大,这里不传到我的项目外面了,须要的本人下载
* 下载地址:https://www.zitijia.com/downloadpage?itemid=281258939050380345
*/
public static final String FONT = "D:\\font/Simhei.ttf";
/**
* 根底款式
*/
public static final Font TITLE_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 20, Font.BOLD);
public static final Font NODE_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 15, Font.BOLD);
public static final Font BLOCK_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 13, Font.BOLD, BaseColor.BLACK);
public static final Font INFO_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 12, Font.NORMAL, BaseColor.BLACK);
public static final Font CONTENT_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
/**
* 段落款式获取
*/
public static Paragraph getParagraph(String content, Font font, Integer alignment) {Paragraph paragraph = new Paragraph(content, font);
if (alignment != null && alignment >= 0) {paragraph.setAlignment(alignment);
}
return paragraph;
}
/**
* 图片款式
*/
public static Image getImage(String imgPath, float width, float height) throws IOException, BadElementException {Image image = Image.getInstance(imgPath);
image.setAlignment(Image.MIDDLE);
if (width > 0 && height > 0) {image.scaleAbsolute(width, height);
}
return image;
}
/**
* 表格生成
*/
public static PdfPTable getPdfTable(int numColumns, float totalWidth) {
// 表格解决
PdfPTable table = new PdfPTable(numColumns);
// 设置表格宽度比例为 %100
table.setWidthPercentage(100);
// 设置宽度: 宽度均匀
table.setTotalWidth(totalWidth);
// 锁住宽度
table.setLockedWidth(true);
// 设置表格下面空白宽度
table.setSpacingBefore(10f);
// 设置表格上面空白宽度
table.setSpacingAfter(10f);
// 设置表格默认为无边框
table.getDefaultCell().setBorder(0);
table.setPaddingTop(50);
table.setSplitLate(false);
return table;
}
/**
* 表格内容带款式
*/
public static void addTableCell(PdfPTable dataTable, Font font, List<String> cellList) {for (String content : cellList) {dataTable.addCell(getParagraph(content, font, -1));
}
}
}
pdf 生成工具类
持续在 util
包下的 PdfUtil.java
工具类里增加,因为本文波及到的操作比拟多,这里只贴相干代码,最初对立贴一个实现的文件
留神:代码中相干的绝对路径要替换成本人的
/**
* 创立 PDF,并保留到指定地位
*
* @param filePath 保留门路
*/
public static void createPdfPage(String filePath) {
// FileOutputStream 须要敞开,开释资源
try (FileOutputStream outputStream = new FileOutputStream(filePath)) {
// 创立文档
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, outputStream);
document.open();
// 报告题目
document.add(PdfFontUtil.getParagraph("笑小枫的网站介绍", TITLE_FONT, 1));
document.add(PdfFontUtil.getParagraph("\n 网站名称:笑小枫(www.xiaoxiaofeng.com)", INFO_FONT, -1));
document.add(PdfFontUtil.getParagraph("\n 生成工夫:2022-07-02\n\n", INFO_FONT, -1));
// 报告内容
// 段落题目 + 报表图
document.add(PdfFontUtil.getParagraph("文章数据统计", NODE_FONT, -1));
document.add(PdfFontUtil.getParagraph("\n· 网站首页图 \n\n", BLOCK_FONT, -1));
// 设置图片宽高
float documentWidth = document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin();
float documentHeight = documentWidth / 580 * 320;
document.add(PdfFontUtil.getImage("D:\\xiaoxiaofeng.jpg", documentWidth - 80, documentHeight - 80));
// 数据表格
document.add(PdfFontUtil.getParagraph("\n· 数据详情 \n\n", BLOCK_FONT, -1));
// 生成 6 列的表格
PdfPTable dataTable = PdfFontUtil.getPdfTable(6, 500);
// 设置表格
List<String> tableHeadList = tableHead();
List<List<String>> tableDataList = getTableData();
PdfFontUtil.addTableCell(dataTable, CONTENT_FONT, tableHeadList);
for (List<String> tableData : tableDataList) {PdfFontUtil.addTableCell(dataTable, CONTENT_FONT, tableData);
}
document.add(dataTable);
document.add(PdfFontUtil.getParagraph("\n· 报表形容 \n\n", BLOCK_FONT, -1));
document.add(PdfFontUtil.getParagraph("数据报告能够监控每天的推广状况," +
"能够针对不同的数据体现进行剖析,以晋升推广成果。", CONTENT_FONT, -1));
document.newPage();
document.close();
writer.close();} catch (Exception e) {e.printStackTrace();
}
}
/**
* 模仿数据
*/
private static List<String> tableHead() {List<String> tableHeadList = new ArrayList<>();
tableHeadList.add("省份");
tableHeadList.add("城市");
tableHeadList.add("数量");
tableHeadList.add("百分比 1");
tableHeadList.add("百分比 2");
tableHeadList.add("百分比 3");
return tableHeadList;
}
/**
* 模仿数据
*/
private static List<List<String>> getTableData() {List<List<String>> tableDataList = new ArrayList<>();
for (int i = 0; i < 3; i++) {List<String> tableData = new ArrayList<>();
tableData.add("浙江" + i);
tableData.add("杭州" + i);
tableData.add("276" + i);
tableData.add("33.3%");
tableData.add("34.3%");
tableData.add("35.3%");
tableDataList.add(tableData);
}
return tableDataList;
}
在 controller
包下的 TestPdfController.java
类中增加代码,因为本文波及到的操作比拟多,这里只贴相干代码,最初对立贴一个实现的文件,相干代码如下:
留神:代码中相干的绝对路径要替换成本人的
@ApiOperation(value = "测试纯代码生成 PDF 到指定目录")
@GetMapping("/createPdfLocal")
public void create() {PdfUtil.createPdfPage("D:\\xxf.pdf");
}
重启我的项目,在浏览器拜访:http://localhost:6666/example/createPdfLocal
能够在 D:\\test
目录下看到 xxf.pdf
文件
咱们关上看一下成果:
合并 PDF,并保留
持续在 util
包下的 PdfUtil.java
工具类里增加,因为本文波及到的操作比拟多,这里只贴相干代码,最初对立贴一个实现的文件。
/**
* 合并 pdf 文件
*
* @param files 要合并文件数组(绝对路径如{ "D:\\test\\1.pdf", "D:\\test\\2.pdf" , "D:\\test\\3.pdf"})
* @param newFile 合并后寄存的目录 D:\\test\\xxf-merge.pdf
* @return boolean 生胜利返回 true, 否則返回 false
*/
public static boolean mergePdfFiles(String[] files, String newFile) {
boolean retValue = false;
Document document;
try (FileOutputStream fileOutputStream = new FileOutputStream(newFile)) {document = new Document(new PdfReader(files[0]).getPageSize(1));
PdfCopy copy = new PdfCopy(document, fileOutputStream);
document.open();
for (String file : files) {PdfReader reader = new PdfReader(file);
int n = reader.getNumberOfPages();
for (int j = 1; j <= n; j++) {document.newPage();
PdfImportedPage page = copy.getImportedPage(reader, j);
copy.addPage(page);
}
}
retValue = true;
document.close();} catch (Exception e) {e.printStackTrace();
}
return retValue;
}
在 controller
包下的 TestPdfController.java
类中增加代码,因为本文波及到的操作比拟多,这里只贴相干代码,最初对立贴一个实现的文件,相干代码如下:
留神:代码中相干的绝对路径要替换成本人的
@ApiOperation(value = "测试合并 PDF 到指定目录")
@GetMapping("/mergePdf")
public Boolean mergePdf() {String[] files = {"D:\\test\\1.pdf", "D:\\test\\2.pdf"};
String newFile = "D:\\test\\xxf-merge.pdf";
return PdfUtil.mergePdfFiles(files, newFile);
}
咱们首先要筹备两个文件D:\\test\\1.pdf,D:\\test\\2.pdf
,这里能够指定文件,也能够是生成的 pdf 文件。
如果是解决生成的文件,这里说下思维:程序创立一个长期目录,留神要惟一命名,而后将生成 PDF 文件保留到这个目录,而后从这个目录下拿到 pdf 进行解决,最初解决实现,保留到对应的目录下,删除这个长期目录和上面的文件。这里不做演示。《合并 PDF,并下载》外面略有波及,然而解决的单个文件,略微革新即可
重启我的项目,在浏览器拜访:http://localhost:6666/example/mergePdf
能够在 D:\\test
目录下看到 xxf-merge
文件
关上看一下成果:
合并 PDF,并下载
持续在 util
包下的 PdfUtil.java
工具类里增加,这里只贴将文件转为输入流,并删除文件的相干代码,合并相干代码见《合并 PDF,并保留》的 util 类。
/**
* 读取 PDF,读取后删除 PDF,实用于生成后须要导出 PDF,创立临时文件
*/
public static void readDeletePdf(String fileName, ServletOutputStream outputStream) {File file = new File(fileName);
if (!file.exists()) {System.out.println(fileName + "文件不存在");
}
try (InputStream in = new FileInputStream(fileName)) {IOUtils.copy(in, outputStream);
} catch (Exception e) {e.printStackTrace();
} finally {
try {Files.delete(file.toPath());
} catch (IOException e) {e.printStackTrace();
}
}
}
在 controller
包下的 TestPdfController.java
类中增加代码,因为本文波及到的操作比拟多,这里只贴相干代码,最初对立贴一个实现的文件,相干代码如下:
留神:代码中相干的绝对路径要替换成本人的
@ApiOperation(value = "测试合并 PDF 后并导出")
@GetMapping("/exportMergePdf")
public void createPdf(HttpServletResponse response) {
// 设置 response 参数,能够关上下载页面
response.reset();
response.setCharacterEncoding("UTF-8");
// 定义输入类型
response.setContentType("application/PDF;charset=utf-8");
// 设置名称
response.setHeader("Content-Disposition", "attachment; filename=" + "xiaoxiaofeng.pdf");
try (ServletOutputStream out = response.getOutputStream()) {String[] files = {"D:\\test\\1.pdf", "D:\\test\\2.pdf"};
// 生成为临时文件,转换为流后,再删除该文件
String newFile = "src\\main\\resources\\templates\\" + UUID.randomUUID() + ".pdf";
boolean isOk = PdfUtil.mergePdfFiles(files, newFile);
if (isOk) {PdfUtil.readDeletePdf(newFile, out);
}
} catch (IOException e) {e.printStackTrace();
}
}
重启我的项目,在浏览器拜访:http://localhost:6666/example/exportMergePdf
会提醒咱们下载。
残缺代码
PdfUtil.java
package com.maple.demo.util;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import org.apache.commons.compress.utils.IOUtils;
import javax.servlet.ServletOutputStream;
import java.io.*;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static com.maple.demo.util.PdfFontUtil.*;
/**
* @author 笑小枫
* @date 2022/8/15
* @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
*/
public class PdfUtil {private PdfUtil() { }
/**
* 利用模板生成 pdf
*
* @param data 写入的数据
* @param photoMap 图片信息
* @param out 自定义保留 pdf 的文件流
* @param templatePath pdf 模板门路
*/
public static void fillTemplate(Map<String, Object> data, Map<String, String> photoMap, ServletOutputStream out, String templatePath) {
PdfReader reader;
ByteArrayOutputStream bos;
PdfStamper stamper;
try {
// 读取 pdf 模板
reader = new PdfReader(templatePath);
bos = new ByteArrayOutputStream();
stamper = new PdfStamper(reader, bos);
AcroFields acroFields = stamper.getAcroFields();
// 赋值
for (String name : acroFields.getFields().keySet()) {String value = data.get(name) != null ? data.get(name).toString() : null;
acroFields.setField(name, value);
}
// 图片赋值
for (Map.Entry<String, String> entry : photoMap.entrySet()) {if (Objects.isNull(entry.getKey())) {continue;}
String key = entry.getKey();
String url = entry.getValue();
// 依据地址读取须要放入 pdf 中的图片
Image image = Image.getInstance(url);
// 设置图片在哪一页
PdfContentByte overContent = stamper.getOverContent(acroFields.getFieldPositions(key).get(0).page);
// 获取模板中图片域的大小
Rectangle signRect = acroFields.getFieldPositions(key).get(0).position;
float x = signRect.getLeft();
float y = signRect.getBottom();
// 图片等比缩放
image.scaleAbsolute(signRect.getWidth(), signRect.getHeight());
// 图片地位
image.setAbsolutePosition(x, y);
// 在该页退出图片
overContent.addImage(image);
}
// 如果为 false 那么生成的 PDF 文件还能编辑,肯定要设为 true
stamper.setFormFlattening(true);
stamper.close();
Document doc = new Document();
PdfCopy copy = new PdfCopy(doc, out);
doc.open();
PdfImportedPage importPage = copy.getImportedPage(new PdfReader(bos.toByteArray()), 1);
copy.addPage(importPage);
doc.close();
bos.close();} catch (IOException | DocumentException e) {e.printStackTrace();
}
}
/**
* 创立 PDF,并保留到指定地位
*
* @param filePath 保留门路
*/
public static void createPdfPage(String filePath) {
// FileOutputStream 须要敞开,开释资源
try (FileOutputStream outputStream = new FileOutputStream(filePath)) {
// 创立文档
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, outputStream);
document.open();
// 报告题目
document.add(PdfFontUtil.getParagraph("笑小枫的网站介绍", TITLE_FONT, 1));
document.add(PdfFontUtil.getParagraph("\n 网站名称:笑小枫(www.xiaoxiaofeng.com)", INFO_FONT, -1));
document.add(PdfFontUtil.getParagraph("\n 生成工夫:2022-07-02\n\n", INFO_FONT, -1));
// 报告内容
// 段落题目 + 报表图
document.add(PdfFontUtil.getParagraph("文章数据统计", NODE_FONT, -1));
document.add(PdfFontUtil.getParagraph("\n· 网站首页图 \n\n", BLOCK_FONT, -1));
// 设置图片宽高
float documentWidth = document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin();
float documentHeight = documentWidth / 580 * 320;
document.add(PdfFontUtil.getImage("D:\\xiaoxiaofeng.jpg", documentWidth - 80, documentHeight - 80));
// 数据表格
document.add(PdfFontUtil.getParagraph("\n· 数据详情 \n\n", BLOCK_FONT, -1));
// 生成 6 列的表格
PdfPTable dataTable = PdfFontUtil.getPdfTable(6, 500);
// 设置表格
List<String> tableHeadList = tableHead();
List<List<String>> tableDataList = getTableData();
PdfFontUtil.addTableCell(dataTable, CONTENT_FONT, tableHeadList);
for (List<String> tableData : tableDataList) {PdfFontUtil.addTableCell(dataTable, CONTENT_FONT, tableData);
}
document.add(dataTable);
document.add(PdfFontUtil.getParagraph("\n· 报表形容 \n\n", BLOCK_FONT, -1));
document.add(PdfFontUtil.getParagraph("数据报告能够监控每天的推广状况," +
"能够针对不同的数据体现进行剖析,以晋升推广成果。", CONTENT_FONT, -1));
document.newPage();
document.close();
writer.close();} catch (Exception e) {e.printStackTrace();
}
}
/**
* 模仿数据
*/
private static List<String> tableHead() {List<String> tableHeadList = new ArrayList<>();
tableHeadList.add("省份");
tableHeadList.add("城市");
tableHeadList.add("数量");
tableHeadList.add("百分比 1");
tableHeadList.add("百分比 2");
tableHeadList.add("百分比 3");
return tableHeadList;
}
/**
* 模仿数据
*/
private static List<List<String>> getTableData() {List<List<String>> tableDataList = new ArrayList<>();
for (int i = 0; i < 3; i++) {List<String> tableData = new ArrayList<>();
tableData.add("浙江" + i);
tableData.add("杭州" + i);
tableData.add("276" + i);
tableData.add("33.3%");
tableData.add("34.3%");
tableData.add("35.3%");
tableDataList.add(tableData);
}
return tableDataList;
}
/**
* 合并 pdf 文件
*
* @param files 要合并文件数组(绝对路径如{ "D:\\test\\1.pdf", "D:\\test\\2.pdf" , "D:\\test\\3.pdf"})
* @param newFile 合并后寄存的目录 D:\\test\\xxf-merge.pdf
* @return boolean 生胜利返回 true, 否則返回 false
*/
public static boolean mergePdfFiles(String[] files, String newFile) {
boolean retValue = false;
Document document;
try (FileOutputStream fileOutputStream = new FileOutputStream(newFile)) {document = new Document(new PdfReader(files[0]).getPageSize(1));
PdfCopy copy = new PdfCopy(document, fileOutputStream);
document.open();
for (String file : files) {PdfReader reader = new PdfReader(file);
int n = reader.getNumberOfPages();
for (int j = 1; j <= n; j++) {document.newPage();
PdfImportedPage page = copy.getImportedPage(reader, j);
copy.addPage(page);
}
}
retValue = true;
document.close();} catch (Exception e) {e.printStackTrace();
}
return retValue;
}
/**
* 读取 PDF,读取后删除 PDF,实用于生成后须要导出 PDF,创立临时文件
*/
public static void readDeletePdf(String fileName, ServletOutputStream outputStream) {File file = new File(fileName);
if (!file.exists()) {System.out.println(fileName + "文件不存在");
}
try (InputStream in = new FileInputStream(fileName)) {IOUtils.copy(in, outputStream);
} catch (Exception e) {e.printStackTrace();
} finally {
try {Files.delete(file.toPath());
} catch (IOException e) {e.printStackTrace();
}
}
}
}
PdfFontUtil.java
package com.maple.demo.util;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPTable;
import java.io.IOException;
import java.util.List;
/**
* @author 笑小枫
* @date 2022/8/15
* @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
*/
public class PdfFontUtil {private PdfFontUtil() { }
/**
* 根底配置,能够放相对路径,这里演示绝对路径,因为字体文件过大,这里不传到我的项目外面了,须要的本人下载
* 下载地址:https://www.zitijia.com/downloadpage?itemid=281258939050380345
*/
public static final String FONT = "D:\\font/Simhei.ttf";
/**
* 根底款式
*/
public static final Font TITLE_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 20, Font.BOLD);
public static final Font NODE_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 15, Font.BOLD);
public static final Font BLOCK_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 13, Font.BOLD, BaseColor.BLACK);
public static final Font INFO_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 12, Font.NORMAL, BaseColor.BLACK);
public static final Font CONTENT_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
/**
* 段落款式获取
*/
public static Paragraph getParagraph(String content, Font font, Integer alignment) {Paragraph paragraph = new Paragraph(content, font);
if (alignment != null && alignment >= 0) {paragraph.setAlignment(alignment);
}
return paragraph;
}
/**
* 图片款式
*/
public static Image getImage(String imgPath, float width, float height) throws IOException, BadElementException {Image image = Image.getInstance(imgPath);
image.setAlignment(Image.MIDDLE);
if (width > 0 && height > 0) {image.scaleAbsolute(width, height);
}
return image;
}
/**
* 表格生成
*/
public static PdfPTable getPdfTable(int numColumns, float totalWidth) {
// 表格解决
PdfPTable table = new PdfPTable(numColumns);
// 设置表格宽度比例为 %100
table.setWidthPercentage(100);
// 设置宽度: 宽度均匀
table.setTotalWidth(totalWidth);
// 锁住宽度
table.setLockedWidth(true);
// 设置表格下面空白宽度
table.setSpacingBefore(10f);
// 设置表格上面空白宽度
table.setSpacingAfter(10f);
// 设置表格默认为无边框
table.getDefaultCell().setBorder(0);
table.setPaddingTop(50);
table.setSplitLate(false);
return table;
}
/**
* 表格内容带款式
*/
public static void addTableCell(PdfPTable dataTable, Font font, List<String> cellList) {for (String content : cellList) {dataTable.addCell(getParagraph(content, font, -1));
}
}
}
TestPdfController.java
package com.maple.demo.controller;
import com.maple.demo.util.PdfUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author 笑小枫
* @date 2022/8/15
* @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
*/
@Slf4j
@RestController
@RequestMapping("/example")
@Api(tags = "实例演示 -PDF 操作接口")
public class TestPdfController {@ApiOperation(value = "依据 PDF 模板导出 PDF")
@GetMapping("/exportPdf")
public void exportPdf(HttpServletResponse response) {Map<String, Object> dataMap = new HashMap<>(16);
dataMap.put("nickName", "笑小枫");
dataMap.put("age", 18);
dataMap.put("sex", "男");
dataMap.put("csdnUrl", "https://zhangfz.blog.csdn.net/");
dataMap.put("siteUrl", "https://www.xiaoxiaofeng.com/");
dataMap.put("desc", "大家好,我是笑小枫。");
Map<String, String> photoMap = new HashMap<>(16);
photoMap.put("logo", "https://profile.csdnimg.cn/C/9/4/2_qq_34988304");
// 设置 response 参数,能够关上下载页面
response.reset();
response.setCharacterEncoding("UTF-8");
// 定义输入类型
response.setContentType("application/PDF;charset=utf-8");
// 设置名称
response.setHeader("Content-Disposition", "attachment; filename=" + "xiaoxiaofeng.pdf");
try {ServletOutputStream out = response.getOutputStream();
// 模板门路记
PdfUtil.fillTemplate(dataMap, photoMap, out, "src/main/resources/templates/xiaoxiaofeng.pdf");
} catch (IOException e) {e.printStackTrace();
}
}
@ApiOperation(value = "测试纯代码生成 PDF 到指定目录")
@GetMapping("/createPdfLocal")
public void create() {PdfUtil.createPdfPage("D:\\test\\xxf.pdf");
}
@ApiOperation(value = "测试合并 PDF 到指定目录")
@GetMapping("/mergePdf")
public Boolean mergePdf() {String[] files = {"D:\\test\\1.pdf", "D:\\test\\2.pdf"};
String newFile = "D:\\test\\xxf-merge.pdf";
return PdfUtil.mergePdfFiles(files, newFile);
}
@ApiOperation(value = "测试合并 PDF 后并导出")
@GetMapping("/exportMergePdf")
public void createPdf(HttpServletResponse response) {
// 设置 response 参数,能够关上下载页面
response.reset();
response.setCharacterEncoding("UTF-8");
// 定义输入类型
response.setContentType("application/PDF;charset=utf-8");
// 设置名称
response.setHeader("Content-Disposition", "attachment; filename=" + "xiaoxiaofeng.pdf");
try (ServletOutputStream out = response.getOutputStream()) {String[] files = {"D:\\test\\1.pdf", "D:\\test\\2.pdf"};
// 生成为临时文件,转换为流后,再删除该文件
String newFile = "src\\main\\resources\\templates\\" + UUID.randomUUID() + ".pdf";
boolean isOk = PdfUtil.mergePdfFiles(files, newFile);
if (isOk) {PdfUtil.readDeletePdf(newFile, out);
}
} catch (IOException e) {e.printStackTrace();
}
}
}
对于笑小枫💕
本章到这里完结了,喜爱的敌人关注一下我呦,大伙的反对,就是我保持写下去的能源。
微信公众号:笑小枫
笑小枫集体博客:https://www.xiaoxiaofeng.com
CSDN:https://zhangfz.blog.csdn.net
本文源码:https://github.com/hack-feng/maple-demo