Spring MVC 、IText 生成PDF文件

882 阅读4分钟

1. 前言

2. Maven引入

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.4.3</version>
</dependency>

3. 具体代码

import com.itextpdf.text.*;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfWriter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.OutputStream;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 将数据写入pdf的工具类
 * Created by @author fuxj on 2020-4-15 17:22
 */
@Slf4j
public class ExportPdfUtils {

    /**
     * 设置字体大小
     */
    private static Font headfont;
    private static Font keyfont;
    private static Font textfont;

    static {
        BaseFont bfChinese;
        try {
            bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            headfont = new Font(bfChinese, 14, Font.BOLD);
            keyfont = new Font(bfChinese, 12, Font.NORMAL);
            textfont = new Font(bfChinese, 12, Font.NORMAL);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 将数据写入PDF
     *
     * @param data 数据源
     * @param out  输出
     * @throws Exception
     */
    public static void writePdf(Map<String, Object> data, OutputStream out) throws Exception {
        // 第一个参数是页面大小。接下来的参数分别是左、右、上和下页边距。
        Document document = new Document(PageSize.A4, 50, 50, 50, 50);
        // 建立一个书写器(Writer)与document对象关联,通过书写器(Writer)可以将文档写入到磁盘中。
        // 创建 PdfWriter 对象 第一个参数是对文档对象的引用,第二个参数是文件的实际名称,在该名称中还会给出其输出路径。
        PdfWriter writer = PdfWriter.getInstance(document, out);
        // 添加页码
         // 添加事件
        PdfPageEvent pageEvent = new PdfPageEvent();
        writer.setPageEvent(pageEvent);
        // 打开文档
        document.open();
        // 向文档中添加内容 来添加文本。可以用文本及其默认的字体、颜色、大小等等设置来创建一个默认段落
        // 将标题写进去
        Paragraph pt = new Paragraph("标题");
        // 设置文字居中 0靠左 1,居中 2,靠右
        pt.setAlignment(1);
        document.add(pt);
        // 添加段落分隔符 换行
        document.add(new Paragraph("\n"));
        document.add(new Paragraph("文档内容", textfont));
        // 写入图片, 网络图片或本地图片,网络图片使用new URL()
        Image image = Image.getInstance("e:\\e.jpg");
        // 设置图片大小
        image.scaleToFit(220, 180);
        document.add(image);
        // 关闭文档
        document.close();
        writer.close();
    }

}

4. 事件


import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;

import java.io.IOException;

/**
 * Created by @author fuxj on 2020-4-16 15:01
 */
public class PdfPageEvent extends PdfPageEventHelper {

    public String header = "";

    /**
     * 文档字体大小,页脚页眉最好和文本大小一致
     */
    public int presentFontSize = 12;

    /**
     * 文档页面大小,最好前面传入,否则默认为A4纸张
     */
    public Rectangle pageSize = PageSize.A4;

    /**
     * 模板
     */
    public PdfTemplate total;

    /**
     * 基础字体对象
     */
    public BaseFont bf = null;

    /**
     * 利用基础字体生成的字体对象,一般用于生成中文文字
     */
    public Font fontDetail = null;

    public PdfPageEvent() {
    }

    public PdfPageEvent(String yeMei, int presentFontSize, Rectangle pageSize) {
        this.header = yeMei;
        this.presentFontSize = presentFontSize;
        this.pageSize = pageSize;
    }

    public void setHeader(String header) {
        this.header = header;
    }

    public void setPresentFontSize(int presentFontSize) {
        this.presentFontSize = presentFontSize;
    }


    @Override
    public void onOpenDocument(PdfWriter writer, Document document) {
        total = writer.getDirectContent().createTemplate(50, 50);
    }

    @Override
    public void onEndPage(PdfWriter writer, Document document) {
        // 分页
        this.addPage(writer, document);
        // 水印
//        this.addWatermark(writer);
    }

    /**
     * 分页
     * @param writer
     * @param document
     */
    public void addPage(PdfWriter writer, Document document){
        try {
            if (bf == null) {
                bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", false);
            }
            if (fontDetail == null) {
                // 数据体字体
                fontDetail = new Font(bf, presentFontSize, Font.NORMAL);
            }
        } catch (DocumentException | IOException e) {
            e.printStackTrace();
        }

        // 1.写入页眉
        ColumnText.showTextAligned(writer.getDirectContent(),
                Element.ALIGN_LEFT, new Phrase(header, fontDetail),
                document.left(), document.top() + 20, 0);
        // 2.写入前半部分的 第 X页/共
        int pageS = writer.getPageNumber();
        String foot1 = "第 " + pageS + " 页 /共";
        Phrase footer = new Phrase(foot1, fontDetail);

        // 3.计算前半部分的foot1的长度,后面好定位最后一部分的'Y页'这俩字的x轴坐标,字体长度也要计算进去 = len
        float len = bf.getWidthPoint(foot1, presentFontSize);

        // 4.拿到当前的PdfContentByte
        PdfContentByte cb = writer.getDirectContent();

        // 5.写入页脚1,x轴就是(右margin+左margin + right() -left()- len)/2.0F
        // 再给偏移20F适合人类视觉感受,否则肉眼看上去就太偏左了
        // ,y轴就是底边界-20,否则就贴边重叠到数据体里了就不是页脚了;注意Y轴是从下往上累加的,最上方的Top值是大于Bottom好几百开外的。
        ColumnText
                .showTextAligned(
                        cb,
                        Element.ALIGN_CENTER,
                        footer,
                        (document.rightMargin() + document.right()
                                + document.leftMargin() - document.left() - len) / 2.0F + 20F,
                        document.bottom() - 20, 0);

        // 6.写入页脚2的模板(就是页脚的Y页这俩字)添加到文档中,计算模板的和Y轴,X=(右边界-左边界 - 前半部分的len值)/2.0F +
        // len , y 轴和之前的保持一致,底边界-20
        // 调节模版显示的位置
        cb.addTemplate(total, (document.rightMargin() + document.right()
                        + document.leftMargin() - document.left()) / 2.0F + 20F,
                document.bottom() - 20);

    }

   /**
     * 水印
     *
     * @param writer
     */
    public void addWatermark(PdfWriter writer) {
        // 水印图片
        Image image;
        try {
            // 图片地址
            image = Image.getInstance("e:\\e.jpg");
            PdfContentByte content = writer.getDirectContentUnder();
            content.beginText();
            // 开始写入水印
            for (int k = 0; k < 5; k++) {
                for (int j = 0; j < 4; j++) {
                    image.setAbsolutePosition(150 * j, 170 * k);
                    content.addImage(image);
                }
            }
            content.endText();
        } catch (IOException | DocumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


    @Override
    public void onCloseDocument(PdfWriter writer, Document document) {
        // 7.最后一步了,就是关闭文档的时候,将模板替换成实际的 Y 值,至此,page x of y 制作完毕,完美兼容各种文档size。
        total.beginText();
        // 生成的模版的字体、颜色
        total.setFontAndSize(bf, presentFontSize);
        String foot2 = " " + (writer.getPageNumber()-1) + " 页";
        // 模版显示的内容
        total.showText(foot2);
        total.endText();
        total.closePath();
    }
}

5. 使用

OutputStream outputStream = null;
try {
    String fileName = System.currentTimeMillis() + ".pdf";
    String userAgent = request.getHeader("User-Agent");
    if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
        fileName = java.net.URLEncoder.encode(fileName, "UTF-8");
    } else {
        // 非IE浏览器的处理:
        fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
    }
    response.reset();
    response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
    response.setContentType("application/octet-stream;charset=UTF-8");
    outputStream = new BufferedOutputStream(response.getOutputStream());
    // 调用工具类写到pdf
    ExportPdfUtils.writePdf(map, outputStream);
    outputStream.flush();
} catch (Exception e) {
    e.printStackTrace();
} finally {
    try {
        if (outputStream != null) {
            outputStream.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

6. 注意事项