关于笑小枫💕
Hello,我是笑小枫,欢迎来到我的世界,喜欢的朋友关注一下我呦,大伙的支持,就是我坚持写下去的动力。
笑小枫个人博客:https://www.xiaoxiaofeng.com
本文简介
本文主要介绍了在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>5.5.6</version></dependency><!-- 这个主要用来设置样式什么的 --><dependency><groupId>com.itextpdf</groupId><artifactId>itext-asian</artifactId><version>5.2.0</version></dependency>
基于PDF模板生成,并下载
PDF模板制作
- 首先在word或者其他软件里面制作模板,挑选你熟悉的软件即可,前提是可生成pdf。
- 将word文件转为pdf文件。
- 使用Adobe Acrobat软件操作pdf,这里用的是这个软件,只要能实现这个功能,其他的软件也可~选择表单编辑哈,我们要在对应的坑上添加表单占位
- 在表单上添加文本域即可,所有的格式都用文本域即可,这里只是占坑。对应的域名称要与程序的名称对应,方便后面数据填充,不然后面需要手动处理赋值。
- 创建个简单的模板吧,要注意填充的空间要充足,不然会出现数据展示不全呦~效果如下:
好了,到这里模板就生成好了,我们保存一下,然后放在我们的
/resources/templates
目录下
PDF生成代码编写
在
util
包下创建
PdfUtil.java
工具类,代码如下:
packagecom.maple.demo.util;importcom.itextpdf.text.Document;importcom.itextpdf.text.DocumentException;importcom.itextpdf.text.Image;importcom.itextpdf.text.Rectangle;importcom.itextpdf.text.pdf.*;importjavax.servlet.ServletOutputStream;importjava.io.ByteArrayOutputStream;importjava.io.IOException;importjava.util.Map;importjava.util.Objects;/**
* @author 笑小枫
* @date 2022/8/15
* @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
*/publicclassPdfUtil{privatePdfUtil(){}/**
* 利用模板生成pdf
*
* @param data 写入的数据
* @param photoMap 图片信息
* @param out 自定义保存pdf的文件流
* @param templatePath pdf模板路径
*/publicstaticvoidfillTemplate(Map<String,Object> data,Map<String,String> photoMap,ServletOutputStream out,String templatePath){PdfReader reader;ByteArrayOutputStream bos;PdfStamper stamper;try{// 读取pdf模板
reader =newPdfReader(templatePath);
bos =newByteArrayOutputStream();
stamper =newPdfStamper(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 =newDocument();PdfCopy copy =newPdfCopy(doc, out);
doc.open();PdfImportedPage importPage = copy.getImportedPage(newPdfReader(bos.toByteArray()),1);
copy.addPage(importPage);
doc.close();
bos.close();}catch(IOException|DocumentException e){
e.printStackTrace();}}}
在
controller
包下创建
TestPdfController.java
类,并i代码如下:
packagecom.maple.demo.controller;importcom.maple.demo.util.PdfUtil;importio.swagger.annotations.Api;importio.swagger.annotations.ApiOperation;importlombok.extern.slf4j.Slf4j;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjavax.servlet.ServletOutputStream;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.util.HashMap;importjava.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操作接口")publicclassTestPdfController{@ApiOperation(value ="根据PDF模板导出PDF")@GetMapping("/exportPdf")publicvoidexportPdf(HttpServletResponse response){Map<String,Object> dataMap =newHashMap<>(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 =newHashMap<>(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
包下创建一个字体工具类吧,代码如下:
注意:代码中相关的绝对路径要替换成自己的
packagecom.maple.demo.util;importcom.itextpdf.text.*;importcom.itextpdf.text.pdf.BaseFont;importcom.itextpdf.text.pdf.PdfPTable;importjava.io.IOException;importjava.util.List;/**
* @author 笑小枫
* @date 2022/8/15
* @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
*/publicclassPdfFontUtil{privatePdfFontUtil(){}/**
* 基础配置,可以放相对路径,这里演示绝对路径,因为字体文件过大,这里不传到项目里面了,需要的自己下载
* 下载地址:https://www.zitijia.com/downloadpage?itemid=281258939050380345
*/publicstaticfinalString FONT ="D:\\font/Simhei.ttf";/**
* 基础样式
*/publicstaticfinalFont TITLE_FONT =FontFactory.getFont(FONT,BaseFont.IDENTITY_H,20,Font.BOLD);publicstaticfinalFont NODE_FONT =FontFactory.getFont(FONT,BaseFont.IDENTITY_H,15,Font.BOLD);publicstaticfinalFont BLOCK_FONT =FontFactory.getFont(FONT,BaseFont.IDENTITY_H,13,Font.BOLD,BaseColor.BLACK);publicstaticfinalFont INFO_FONT =FontFactory.getFont(FONT,BaseFont.IDENTITY_H,12,Font.NORMAL,BaseColor.BLACK);publicstaticfinalFont CONTENT_FONT =FontFactory.getFont(FONT,BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);/**
* 段落样式获取
*/publicstaticParagraphgetParagraph(String content,Font font,Integer alignment){Paragraph paragraph =newParagraph(content, font);if(alignment !=null&& alignment >=0){
paragraph.setAlignment(alignment);}return paragraph;}/**
* 图片样式
*/publicstaticImagegetImage(String imgPath,float width,float height)throwsIOException,BadElementException{Image image =Image.getInstance(imgPath);
image.setAlignment(Image.MIDDLE);if(width >0&& height >0){
image.scaleAbsolute(width, height);}return image;}/**
* 表格生成
*/publicstaticPdfPTablegetPdfTable(int numColumns,float totalWidth){// 表格处理PdfPTable table =newPdfPTable(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;}/**
* 表格内容带样式
*/publicstaticvoidaddTableCell(PdfPTable dataTable,Font font,List<String> cellList){for(String content : cellList){
dataTable.addCell(getParagraph(content, font,-1));}}}
pdf生成工具类
继续在
util
包下的
PdfUtil.java
工具类里添加,因为本文涉及到的操作比较多,这里只贴相关代码,最后统一贴一个完成的文件
注意:代码中相关的绝对路径要替换成自己的
/**
* 创建PDF,并保存到指定位置
*
* @param filePath 保存路径
*/publicstaticvoidcreatePdfPage(String filePath){// FileOutputStream 需要关闭,释放资源try(FileOutputStream outputStream =newFileOutputStream(filePath)){// 创建文档Document document =newDocument();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();}}/**
* 模拟数据
*/privatestaticList<String>tableHead(){List<String> tableHeadList =newArrayList<>();
tableHeadList.add("省份");
tableHeadList.add("城市");
tableHeadList.add("数量");
tableHeadList.add("百分比1");
tableHeadList.add("百分比2");
tableHeadList.add("百分比3");return tableHeadList;}/**
* 模拟数据
*/privatestaticList<List<String>>getTableData(){List<List<String>> tableDataList =newArrayList<>();for(int i =0; i <3; i++){List<String> tableData =newArrayList<>();
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")publicvoidcreate(){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
*/publicstaticbooleanmergePdfFiles(String[] files,String newFile){boolean retValue =false;Document document;try(FileOutputStream fileOutputStream =newFileOutputStream(newFile)){
document =newDocument(newPdfReader(files[0]).getPageSize(1));PdfCopy copy =newPdfCopy(document, fileOutputStream);
document.open();for(String file : files){PdfReader reader =newPdfReader(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")publicBooleanmergePdf(){String[] files ={"D:\\test\\1.pdf","D:\\test\\2.pdf"};String newFile ="D:\\test\\xxf-merge.pdf";returnPdfUtil.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,创建临时文件
*/publicstaticvoidreadDeletePdf(String fileName,ServletOutputStream outputStream){File file =newFile(fileName);if(!file.exists()){System.out.println(fileName +"文件不存在");}try(InputStream in =newFileInputStream(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")publicvoidcreatePdf(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
packagecom.maple.demo.util;importcom.itextpdf.text.Document;importcom.itextpdf.text.DocumentException;importcom.itextpdf.text.Image;importcom.itextpdf.text.Rectangle;importcom.itextpdf.text.pdf.*;importorg.apache.commons.compress.utils.IOUtils;importjavax.servlet.ServletOutputStream;importjava.io.*;importjava.nio.file.Files;importjava.util.ArrayList;importjava.util.List;importjava.util.Map;importjava.util.Objects;importstaticcom.maple.demo.util.PdfFontUtil.*;/**
* @author 笑小枫
* @date 2022/8/15
* @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
*/publicclassPdfUtil{privatePdfUtil(){}/**
* 利用模板生成pdf
*
* @param data 写入的数据
* @param photoMap 图片信息
* @param out 自定义保存pdf的文件流
* @param templatePath pdf模板路径
*/publicstaticvoidfillTemplate(Map<String,Object> data,Map<String,String> photoMap,ServletOutputStream out,String templatePath){PdfReader reader;ByteArrayOutputStream bos;PdfStamper stamper;try{// 读取pdf模板
reader =newPdfReader(templatePath);
bos =newByteArrayOutputStream();
stamper =newPdfStamper(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 =newDocument();PdfCopy copy =newPdfCopy(doc, out);
doc.open();PdfImportedPage importPage = copy.getImportedPage(newPdfReader(bos.toByteArray()),1);
copy.addPage(importPage);
doc.close();
bos.close();}catch(IOException|DocumentException e){
e.printStackTrace();}}/**
* 创建PDF,并保存到指定位置
*
* @param filePath 保存路径
*/publicstaticvoidcreatePdfPage(String filePath){// FileOutputStream 需要关闭,释放资源try(FileOutputStream outputStream =newFileOutputStream(filePath)){// 创建文档Document document =newDocument();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();}}/**
* 模拟数据
*/privatestaticList<String>tableHead(){List<String> tableHeadList =newArrayList<>();
tableHeadList.add("省份");
tableHeadList.add("城市");
tableHeadList.add("数量");
tableHeadList.add("百分比1");
tableHeadList.add("百分比2");
tableHeadList.add("百分比3");return tableHeadList;}/**
* 模拟数据
*/privatestaticList<List<String>>getTableData(){List<List<String>> tableDataList =newArrayList<>();for(int i =0; i <3; i++){List<String> tableData =newArrayList<>();
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
*/publicstaticbooleanmergePdfFiles(String[] files,String newFile){boolean retValue =false;Document document;try(FileOutputStream fileOutputStream =newFileOutputStream(newFile)){
document =newDocument(newPdfReader(files[0]).getPageSize(1));PdfCopy copy =newPdfCopy(document, fileOutputStream);
document.open();for(String file : files){PdfReader reader =newPdfReader(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,创建临时文件
*/publicstaticvoidreadDeletePdf(String fileName,ServletOutputStream outputStream){File file =newFile(fileName);if(!file.exists()){System.out.println(fileName +"文件不存在");}try(InputStream in =newFileInputStream(fileName)){IOUtils.copy(in, outputStream);}catch(Exception e){
e.printStackTrace();}finally{try{Files.delete(file.toPath());}catch(IOException e){
e.printStackTrace();}}}}
PdfFontUtil.java
packagecom.maple.demo.util;importcom.itextpdf.text.*;importcom.itextpdf.text.pdf.BaseFont;importcom.itextpdf.text.pdf.PdfPTable;importjava.io.IOException;importjava.util.List;/**
* @author 笑小枫
* @date 2022/8/15
* @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
*/publicclassPdfFontUtil{privatePdfFontUtil(){}/**
* 基础配置,可以放相对路径,这里演示绝对路径,因为字体文件过大,这里不传到项目里面了,需要的自己下载
* 下载地址:https://www.zitijia.com/downloadpage?itemid=281258939050380345
*/publicstaticfinalString FONT ="D:\\font/Simhei.ttf";/**
* 基础样式
*/publicstaticfinalFont TITLE_FONT =FontFactory.getFont(FONT,BaseFont.IDENTITY_H,20,Font.BOLD);publicstaticfinalFont NODE_FONT =FontFactory.getFont(FONT,BaseFont.IDENTITY_H,15,Font.BOLD);publicstaticfinalFont BLOCK_FONT =FontFactory.getFont(FONT,BaseFont.IDENTITY_H,13,Font.BOLD,BaseColor.BLACK);publicstaticfinalFont INFO_FONT =FontFactory.getFont(FONT,BaseFont.IDENTITY_H,12,Font.NORMAL,BaseColor.BLACK);publicstaticfinalFont CONTENT_FONT =FontFactory.getFont(FONT,BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);/**
* 段落样式获取
*/publicstaticParagraphgetParagraph(String content,Font font,Integer alignment){Paragraph paragraph =newParagraph(content, font);if(alignment !=null&& alignment >=0){
paragraph.setAlignment(alignment);}return paragraph;}/**
* 图片样式
*/publicstaticImagegetImage(String imgPath,float width,float height)throwsIOException,BadElementException{Image image =Image.getInstance(imgPath);
image.setAlignment(Image.MIDDLE);if(width >0&& height >0){
image.scaleAbsolute(width, height);}return image;}/**
* 表格生成
*/publicstaticPdfPTablegetPdfTable(int numColumns,float totalWidth){// 表格处理PdfPTable table =newPdfPTable(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;}/**
* 表格内容带样式
*/publicstaticvoidaddTableCell(PdfPTable dataTable,Font font,List<String> cellList){for(String content : cellList){
dataTable.addCell(getParagraph(content, font,-1));}}}
TestPdfController.java
packagecom.maple.demo.controller;importcom.maple.demo.util.PdfUtil;importio.swagger.annotations.Api;importio.swagger.annotations.ApiOperation;importlombok.extern.slf4j.Slf4j;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjavax.servlet.ServletOutputStream;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.util.HashMap;importjava.util.Map;importjava.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操作接口")publicclassTestPdfController{@ApiOperation(value ="根据PDF模板导出PDF")@GetMapping("/exportPdf")publicvoidexportPdf(HttpServletResponse response){Map<String,Object> dataMap =newHashMap<>(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 =newHashMap<>(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")publicvoidcreate(){PdfUtil.createPdfPage("D:\\test\\xxf.pdf");}@ApiOperation(value ="测试合并PDF到指定目录")@GetMapping("/mergePdf")publicBooleanmergePdf(){String[] files ={"D:\\test\\1.pdf","D:\\test\\2.pdf"};String newFile ="D:\\test\\xxf-merge.pdf";returnPdfUtil.mergePdfFiles(files, newFile);}@ApiOperation(value ="测试合并PDF后并导出")@GetMapping("/exportMergePdf")publicvoidcreatePdf(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();}}}
本系列其它文章
本系列的源码已同步在Github:https://github.com/hack-feng/maple-demo
- SpringBoot项目创建
- SpringBoot配置基于swagger2的knife4j接口文档
- SpringBoot集成Mybatis Plus
- SpringBoot返回统一结果包装
- SpringBoot返回统一异常处理
- SpringBoot日志打印Logback详解
- SpringBoot控制台自定义banner
- SpringBoot集成Redis
- SpringBoot用户登录拦截器
- SpringBoot处理请求跨域问题
- SpringBoot接口日志信息统一记录
- SpringBoot导入Excel
- SpringBoot导出Excel
- SpringBoot发送邮件
- SpringBoot根据模板生成Word
- SpringBoot生成PDF
- SpringBoot文件上传下载
- SpringBoot中的Properties配置
版权归原作者 笑小枫 所有, 如有侵权,请联系我们删除。