共计 11386 个字符,预计需要花费 29 分钟才能阅读完成。
除了解决 word, excel 等文件外,最为常见的就是 PDF 的导出了。在 java 技术栈中,PDF 创立和操作最为罕用的 itext 了,然而应用 itext 肯定要理解其版本历史和 License 问题,在早前版本应用的是 MPL 和 LGPL 双许可协定,在 5.x 以上版本中应用的是 AGPLv3(这个协定意味着,只有集体用处和开源的我的项目能力应用 itext 这个库,否则是须要免费的)。本文次要介绍通过 SpringBoot 集成 itextpdf 实现 PDF 导出性能。@pdai
-
SpringBoot 集成文件 – 集成 itextpdf 之导出 PDF
-
常识筹备
- 什么是 itext
- itext 的历史版本和 License 问题
- 规范的 itextpdf 导出的步骤
-
实现案例
- Pom 依赖
- 导出 PDF
- 增加页眉页脚和水印
-
进一步了解
- 遇到 license 问题怎么办
- 为何增加页眉页脚和水印是通过 PdfPageEvent 来实现
- 示例源码
- 参考文章
- 更多内容
-
常识筹备
须要理解 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(这个协定意味着,<mark> 只有集体用处和开源的我的项目能力应用 itext 这个库,否则是须要免费的 </mark>)
-
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/t…
参考文章
https://itextpdf.com
https://blog.csdn.net/u012397…
更多内容
辞别碎片化学习,无套路一站式体系化学习后端开发: Java 全栈常识体系 (https://pdai.tech)