除了解决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次要蕴含如下几步:

@Overridepublic 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办法

@Overridepublic 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。 有两种抉择:

  1. 应用2.1.7版本
<dependency>  <groupId>com.lowagie</groupId>  <artifactId>itext</artifactId>  <version>2.1.7</version></dependency>
  1. 应用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)