SpringBoot集成文件 - 如何集成itextpdf导出PDF?itext的变迁?
lipiwang 2024-11-21 17:40 4 浏览 0 评论
SpringBoot集成文件 - 如何集成itextpdf导出PDF?itext的变迁?
除了处理word, excel等文件外,最为常见的就是PDF的导出了。在java技术栈中,PDF创建和操作最为常用的itext了,但是使用itext一定要了解其版本历史和License问题,在早前版本使用的是MPL和LGPL双许可协议,在5.x以上版本中使用的是AGPLv3(这个协议意味着,只有个人用途和开源的项目才能使用itext这个库,否则是需要收费的)。本文主要介绍通过SpringBoot集成itextpdf实现PDF导出功能。@pdai
知识准备
需要了解itext,以及itext历史版本变迁,以及license的问题。
什么是itext
来源于百度百科:iText是著名的开放源码的站点sourceforge一个项目(由Bruno Lowagie编写),是一个用Java和.NET语言写的库,用来创建和修改PDF文件。通过iText不仅可以生成PDF或rtf的文档,而且可以将XML、Html文件转化为PDF文件。 iText的安装非常方便,下载iText.jar文件后,只需要在系统的CLASSPATH中加入iText.jar的路径,在程序中就可以使用iText类库了。
iText提供除了基本的创建、修改PDF文件外的其他高级的PDF特性,例如基于PKI的签名,40位和128位加密,颜色校正,带标签的PDF,PDF表单(AcroForms),PDF/X,通过ICC配置文件和条形码进行颜色管理。这些特性被一些产品和服务中使用,包括Eclipse BIRT,Jasper Reports,JBoss Seam,Windward Reports和pdftk。
一般情况下,iText使用在有以下一个要求的项目中:
- 内容无法提前利用:取决于用户的输入或实时的数据库信息。
- 由于内容,页面过多,PDF文档不能手动生成。
- 文档需在无人参与,批处理模式下自动创建。
- 内容被定制或个性化;例如,终端客户的名字需要标记在大量的页面上。
itext的历史版本和License问题
使用itext一定要了解其版本历史,和License问题,在早前版本使用的是MPL和LGPL双许可协议,在5.x以上版本中使用的是AGPLv3(这个协议意味着,只有个人用途和开源的项目才能使用itext这个库,否则是需要收费的)
- iText 0.x-2.x/iTextSharp 3.x-4.x 更新时间是2000-2009使用的是MPL和LGPL双许可协议最近的更新是2009年,版本号是iText 2.1.7/iTextSharp 4.1.6.0此时引入包的GAV版本如下:
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>2.1.7</version>
</dependency>
- iText 5.x和iTextSharp 5.x 更新时间是2009-2016, 公司化运作,并标准化和提高性能开始使用**AGPLv3协议** 只有个人用途和开源的项目才能使用itext这个库,否则是需要收费的 iTextSharp被设计成iText库的.NET版本,并且与iText版本号同步,iText 5.0.0和iTextSharp5.0.0同时发布新功能不在这里面增加,但是官方会修复重要的bug此时引入包的GAV版本如下:
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
- iText 7.x 更新时间是2016到现在AGPLv3协议完全重写,重点关注可扩展性和模块化不适用iTextSharp这个名称,都统称为iText,有Java和.Net版本JDK 1.7+此时引入包的GAV版本如下:
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>7.2.2</version>
<type>pom</type>
</dependency>
注:iText变化后,GitHub上有团队基于4.x版本(MPL和LGPL双许可协议)fork了一个分支成为OpenPDF,并继续维护该项目。
标准的itextpdf导出的步骤
itextpdf导出pdf主要包含如下几步:
@Override
public Document generateItextPdfDocument(OutputStream os) throws Exception {
// 1. 创建文档
Document document = new Document(PageSize.A4);
// 2. 绑定输出流(通过pdfwriter)
PdfWriter.getInstance(document, os);
// 3. 打开文档
document.open();
// 4. 往文档中添加内容
document.add(xxx);
// 5. 关闭文档
document.close();
return document;
}
document中添加的Element有哪些呢?
需要说明下如下概念之前的差别:
- Chunk:文档的文本的最小块单位
- Phrase:一系列以特定间距(两行之间的距离)作为参数的块
- Paragraph:段落是一系列块和(或)短句。同短句一样,段落有确定的间距。用户还可以指定缩排;在边和(或)右边保留一定空白,段落可以左对齐、右对齐和居中对齐。添加到文档中的每一个段落将自动另起一行。
(其它从字面上就可以看出,所以这里具体就不做解释了)
实现案例
这里展示SpringBoot集成itext5导出PDF的例子。
Pom依赖
引入poi的依赖包
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
导出PDF
UserController中导出的方法
package tech.pdai.springboot.file.word.poi.controller;
import java.io.OutputStream;
import javax.servlet.http.HttpServletResponse;
import io.swagger.annotations.ApiOperation;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.pdai.springboot.file.word.poi.service.IUserService;
/**
* @author pdai
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
@ApiOperation("Download Word")
@GetMapping("/word/download")
public void download(HttpServletResponse response) {
try {
XWPFDocument document = userService.generateWordXWPFDocument();
response.reset();
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-disposition",
"attachment;filename=user_world_" + System.currentTimeMillis() + ".docx");
OutputStream os = response.getOutputStream();
document.write(os);
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
UserServiceImple中导出PDF方法
@Override
public Document generateItextPdfDocument(OutputStream os) throws Exception {
// document
Document document = new Document(PageSize.A4);
PdfWriter.getInstance(document, os);
// open
document.open();
// add content - pdf meta information
document.addAuthor("pdai");
document.addCreationDate();
document.addTitle("pdai-pdf-itextpdf");
document.addKeywords("pdf-pdai-keyword");
document.addCreator("pdai");
// add content - page content
// Title
document.add(createTitle("Java 全栈知识体系"));
// Chapter 1
document.add(createChapterH1("1. 知识准备"));
document.add(createChapterH2("1.1 什么是POI"));
document.add(createParagraph("Apache POI 是创建和维护操作各种符合Office Open XML(OOXML)标准和微软的OLE 2复合文档格式(OLE2)的Java API。用它可以使用Java读取和创建,修改MS Excel文件.而且,还可以使用Java读取和创建MS Word和MSPowerPoint文件。更多请参考[官方文档](https://poi.apache.org/index.html)"));
document.add(createChapterH2("1.2 POI中基础概念"));
document.add(createParagraph("生成xls和xlsx有什么区别?POI对Excel中的对象的封装对应关系?"));
// Chapter 2
document.add(createChapterH1("2. 实现案例"));
document.add(createChapterH2("2.1 用户列表示例"));
document.add(createParagraph("以导出用户列表为例"));
// 表格
List<User> userList = getUserList();
PdfPTable table = new PdfPTable(new float[]{20, 40, 50, 40, 40});
table.setTotalWidth(500);
table.setLockedWidth(true);
table.setHorizontalAlignment(Element.ALIGN_CENTER);
table.getDefaultCell().setBorder(1);
for (int i = 0; i < userList.size(); i++) {
table.addCell(createCell(userList.get(i).getId() + ""));
table.addCell(createCell(userList.get(i).getUserName()));
table.addCell(createCell(userList.get(i).getEmail()));
table.addCell(createCell(userList.get(i).getPhoneNumber() + ""));
table.addCell(createCell(userList.get(i).getDescription()));
}
document.add(table);
document.add(createChapterH2("2.2 图片导出示例"));
document.add(createParagraph("以导出图片为例"));
// 图片
Resource resource = new ClassPathResource("pdai-guli.png");
Image image = Image.getInstance(resource.getURL());
// Image image = Image.getInstance("/Users/pdai/pdai/www/tech-pdai-spring-demos/481-springboot-demo-file-pdf-itextpdf/src/main/resources/pdai-guli.png");
image.setAlignment(Element.ALIGN_CENTER);
image.scalePercent(60); // 缩放
document.add(image);
// close
document.close();
return document;
}
private List<User> getUserList() {
List<User> userList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
userList.add(User.builder()
.id(Long.parseLong(i + "")).userName("pdai" + i).email("pdai@pdai.tech" + i).phoneNumber(121231231231L)
.description("hello world" + i)
.build());
}
return userList;
}
在实现时可以将如下创建文档内容的方法封装到Util工具类中
private Paragraph createTitle(String content) throws IOException, DocumentException {
Font font = new Font(getBaseFont(), 24, Font.BOLD);
Paragraph paragraph = new Paragraph(content, font);
paragraph.setAlignment(Element.ALIGN_CENTER);
return paragraph;
}
private Paragraph createChapterH1(String content) throws IOException, DocumentException {
Font font = new Font(getBaseFont(), 22, Font.BOLD);
Paragraph paragraph = new Paragraph(content, font);
paragraph.setAlignment(Element.ALIGN_LEFT);
return paragraph;
}
private Paragraph createChapterH2(String content) throws IOException, DocumentException {
Font font = new Font(getBaseFont(), 18, Font.BOLD);
Paragraph paragraph = new Paragraph(content, font);
paragraph.setAlignment(Element.ALIGN_LEFT);
return paragraph;
}
private Paragraph createParagraph(String content) throws IOException, DocumentException {
Font font = new Font(getBaseFont(), 12, Font.NORMAL);
Paragraph paragraph = new Paragraph(content, font);
paragraph.setAlignment(Element.ALIGN_LEFT);
paragraph.setIndentationLeft(12); //设置左缩进
paragraph.setIndentationRight(12); //设置右缩进
paragraph.setFirstLineIndent(24); //设置首行缩进
paragraph.setLeading(20f); //行间距
paragraph.setSpacingBefore(5f); //设置段落上空白
paragraph.setSpacingAfter(10f); //设置段落下空白
return paragraph;
}
public PdfPCell createCell(String content) throws IOException, DocumentException {
PdfPCell cell = new PdfPCell();
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
Font font = new Font(getBaseFont(), 12, Font.NORMAL);
cell.setPhrase(new Phrase(content, font));
return cell;
}
private BaseFont getBaseFont() throws IOException, DocumentException {
return BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
}
导出后的PDF
添加页眉页脚和水印
在itextpdf 5.x 中可以利用PdfPageEvent来完成页眉页脚和水印。
package tech.pdai.springboot.file.pdf.itextpdf.pdf;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.Element;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfGState;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
/**
* @author pdai
*/
public class MyHeaderFooterPageEventHelper extends PdfPageEventHelper {
private String headLeftTitle;
private String headRightTitle;
private String footerLeft;
private String waterMark;
private PdfTemplate total;
public MyHeaderFooterPageEventHelper(String headLeftTitle, String headRightTitle, String footerLeft, String waterMark) {
this.headLeftTitle = headLeftTitle;
this.headRightTitle = headRightTitle;
this.footerLeft = footerLeft;
this.waterMark = waterMark;
}
@Override
public void onOpenDocument(PdfWriter writer, Document document) {
total = writer.getDirectContent().createTemplate(30, 16);
}
@Override
public void onEndPage(PdfWriter writer, Document document) {
BaseFont bf = null;
try {
bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} catch (Exception e) {
e.printStackTrace();
}
// page header and footer
addPageHeaderAndFooter(writer, document, bf);
// watermark
if (waterMark!=null) {
addWaterMark(writer, document, bf);
}
}
private void addPageHeaderAndFooter(PdfWriter writer, Document document, BaseFont bf) {
PdfContentByte cb = writer.getDirectContent();
cb.saveState();
cb.beginText();
cb.setColorFill(BaseColor.GRAY);
cb.setFontAndSize(bf, 10);
// header
float x = document.top(-10);
cb.showTextAligned(PdfContentByte.ALIGN_LEFT,
headLeftTitle,
document.left(), x, 0);
cb.showTextAligned(PdfContentByte.ALIGN_RIGHT,
headRightTitle,
document.right(), x, 0);
// footer
float y = document.bottom(-10);
cb.showTextAligned(PdfContentByte.ALIGN_LEFT,
footerLeft,
document.left(), y, 0);
cb.showTextAligned(PdfContentByte.ALIGN_CENTER,
String.format("- %d -", writer.getPageNumber()),
(document.right() + document.left()) / 2,
y, 0);
cb.endText();
cb.restoreState();
}
private void addWaterMark(PdfWriter writer, Document document, BaseFont bf) {
for (int i = 1; i < 7; i++) {
for (int j = 1; j < 10; j++) {
PdfContentByte cb = writer.getDirectContent();
cb.saveState();
cb.beginText();
cb.setColorFill(BaseColor.GRAY);
PdfGState gs = new PdfGState();
gs.setFillOpacity(0.1f);
cb.setGState(gs);
cb.setFontAndSize(bf, 12);
cb.showTextAligned(Element.ALIGN_MIDDLE, waterMark, 75 * i,
80 * j, 30);
cb.endText();
cb.restoreState();
}
}
}
@Override
public void onCloseDocument(PdfWriter writer, Document document) {
ColumnText.showTextAligned(total, Element.ALIGN_LEFT, new Phrase(String.valueOf(writer.getPageNumber() - 1)), 2,
2, 0);
}
}
添加水印后导出后的PDF
进一步理解
通过如下几个问题进一步理解itextpdf。
遇到license问题怎么办
如前文所述,使用itext一定要了解其版本历史和License问题,在早前版本使用的是MPL和LGPL双许可协议,在5.x以上版本中使用的是AGPLv3。 有两种选择:
- 使用2.1.7版本
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>2.1.7</version>
</dependency>
- 使用OpenPDF
GitHub上有团队基于itext 4.x版本(MPL和LGPL双许可协议)fork了一个分支成为OpenPDF,并继续维护该项目。
为何添加页眉页脚和水印是通过PdfPageEvent来完成
为何添加页眉页脚和水印是通过PdfPageEvent来完成?
举个例子,如果我们在上述例子中需要在页脚中显示 “Page 1 of 3", 即总页数怎么办呢?而itext是流模式的写入内容,只有写到最后,才能知道有多少页,那么显示总页数必须在内容写完之后(或者关闭之前)确定;这就是为什么在onEndPage方法时才会写每页的页眉页脚。
iText仅在调用释放模板方法后才将PdfTemplate写入到OutputStream中,否则对象将一直保存在内存中,直到关闭文档。所以我们可以在最后关闭文档前,使用PdfTemplate写入总页码。可以理解成先写个占位符,然后统一替换。
示例源码
https://github.com/realpdai/tech-pdai-spring-demos
参考文章
https://itextpdf.com
https://blog.csdn.net/u012397189/article/details/80196974
更多内容
告别碎片化学习,无套路一站式体系化学习后端开发: Java 全栈知识体系(https://pdai.tech)
相关推荐
- 想减少Windows 11内存占用?请取消固定Teams
-
如果你想要提高Windows11系统的运行速度,那么可以禁用某些默认启用的功能和设置。如果你的Windows11是安装在已经停止支持的设备或者内存容量不高的旧设备,那么应该立即限制或禁用固...
- Windows查看端口占用、查看PID对应的进程、并终止进程
-
Windows下:查看端口占用netstat-ano|findstr"端口号"获取到pid查看PID对应的进程tasklist|findstr"进程ID"...
- 计算机组成原理(36): 分时之一——进程
-
建立一个虚拟机VM目标:给每个程序一个自己的虚拟机“VirtualMachine”,程序并不知道其他的虚拟机。1.1进程(Process)为了捕获正在运行的程序,我们创建一个称为“进程(Proce...
- window系统如何停止端口被占用的进程(高手版)
-
如上图1,作为开发人员是不是经常遇到这个问题?(Webserverfailedtostart.Port9527wasalreadyinuse.)当然,如果在你知道确实有某个进程正占...
- 电脑的文件无法删除咋回事?你需要这款神兵利器
-
很多朋友用电脑的时候,都遇到过文件无法删除的情况。这往往是由于文件被某个软件、进程所调用所引发的——在Windows中,某个文件如果被使用,这个文件可能就没法进行删除、重命名之类的操作了。想要进一步操...
- Windows日志分析(windows 日志文件)
-
1.Windows日志文件简介1.1Windows日志核心分类1.系统日志系统日志包含由Windows系统组件记录的事件,记录系统进程和设备驱动程序的活动。由它审核的系统事件包括启动失败的设备驱动程...
- 电脑软件崩溃、闪退不用慌!DJS Tech 教你几招轻松解决
-
当你正全神贯注用电脑处理重要文件、沉浸在精彩的游戏世界,或是观看喜欢的视频时,软件突然崩溃、闪退,那一刻的烦躁简直难以言喻。别着急,DJSTech作为深耕计算机领域多年的专业团队,为你带来一系列超...
- 微软Win11推进淘汰控制面板,时间服务器配置迁移至设置应用
-
IT之家5月29日消息,科技媒体Winaero昨日(5月28日)发布博文,报道称微软在Windows11系统中,继续推进“淘汰控制面板”进程,配置时间服务器地址选项迁移到设置应...
- 微软 PowerToys更新,可帮你找出 Win11上哪些进程正在占用该文件
-
IT之家11月3日消息,微软针对Windows11和Windows10的PowerToys已经更新到了最新的0.64.0版本,并上线了一个名为“文件锁匠FileLock...
- Windows基础操作 认识任务管理器(windows任务管理器的使用)
-
Windows基础操作:认识任务管理器任务管理器(TaskManager)是Windows系统中一个功能强大的实用工具,它为用户提供了实时监控系统资源、管理正在运行的程序和服务的能力。掌握任务管理器...
- windows——netstat过滤(终止进程)
-
windows——netstat过滤(终止进程)在Windows操作系统中,使用netstat命令可以查看网络连接的状态。要过滤特定协议或端口的连接,可以使用以下命令:查看所有连接:netstat-...
- 只要这么做 Windows Defender与第三方就能和平共存啦
-
无论大家是否喜欢WindowsDefender,伴随着Windows10的不断升级,它已经成为系统的底层必备组件之一。虽然我们有各种各样的方法去关闭它,换用顺手的第三方,但只要更新打补丁,噩梦就来...
- Win10如何彻底关闭wsappx进程(win10 wsappx怎么关闭)
-
win10如何彻底关闭wsappx进程?wsappx进程是什么?wsappx进程是Windows10系统的一部分,这个进程是WindowsStore和微软通用应用程序平台(UWP)的依赖进程。...
- Windows环境黑客入侵应急与排查(黑客入侵电脑原理)
-
1文件分析1.1临时目录排查黑客往往可能将病毒放在临时目录(tmp/temp),或者将病毒相关文件释放到临时目录,因此需要检查临时目录是否存在异常文件。假设系统盘在C盘,则通常情况下的临时目录如下...
- Windows 11 24H2 KB5044384出现大面积安装失败、任务管理器0进程等问题
-
Windows11KB5044384更新由于出现大量错误而无法在Windows1124H2上安装、其中包括一个奇怪的错误,即由于0x800f0922、0x800736b3和0x8...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)