PDF模板填充,基于IText5(改进版)

原版:PDF模板填充,基于IText5-CSDN博客

java 复制代码
\import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

/**
 * PDF模板填充,基于itext-pdf-v5
 */
public class PDFTemplateFiller {

    /**
     * 模板
     */
    private transient PdfReader reader;

    /**
     * 字体
     */
    private BaseFont baseFont;

    /**
     * 是否添加页码
     */
    private boolean addPageNumber;

    /**
     * 页码字体大小
     * {@link #addPageNumber}为true时有效
     */
    private float pageNumberFontSize = 14F;

    /**
     * 页码左边样式字符
     * {@link #addPageNumber}为true时有效
     */
    private String pageNumberLeft = "";

    /**
     * 页码邮编样式字符
     * {@link #addPageNumber}为true时有效
     */
    private String pageNumberRight = "";

    /**
     * 填充模板,返回填充后文件
     *
     * @param fillData - 待填充数据
     * @return - 填充后文件字节数据
     */
    public byte[] fill(Map<String, Object> fillData) throws DocumentException, IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        PdfStamper stamper = new PdfStamper(reader, out);
        AcroFields form = stamper.getAcroFields();
        // 设置字体
        form.addSubstitutionFont(baseFont);

        // 执行填充
        doFill(stamper, form, fillData);

        // 清除表单域可编辑状态
        stamper.setFormFlattening(true);
        stamper.close();
        reader.close();

        // 是否需要添加页码,则需要重新将填充后的PDF文档加载进reader中
        byte[] filedData = out.toByteArray();
        if (addPageNumber) {
            this.reader = new PdfReader(filedData);
            return pageNumber();
        }
        return filedData;
    }

    /**
     * 添加页码
     *
     * @return
     * @throws DocumentException
     */
    public byte[] pageNumber() throws DocumentException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Document document = new Document();
        PdfWriter writer = PdfWriter.getInstance(document, out);
        document.open();
        int pages = reader.getNumberOfPages();
        for (int i = 1; i <= pages; i++) {
            document.newPage();
            doAadPageNumber(writer, i);
            PdfImportedPage page = writer.getImportedPage(reader, i);
            writer.getDirectContent().addTemplate(page, 0, 0);
        }
        document.close();
        reader.close();
        return out.toByteArray();
    }


    /**
     * 是否添加页码(默认:否)
     *
     * @param isAddPageNumber - true-添加/false-不添加
     */
    public PDFTemplateFiller pageNumber(boolean isAddPageNumber) {
        this.addPageNumber = isAddPageNumber;
        return this;
    }

    /**
     * <pre>
     * 设置字体(默认:STSong-Light)
     * 通过 {@link com.itextpdf.text.pdf.BaseFont#createFont(java.lang.String, java.lang.String, boolean)}创建
     * </pre>
     *
     * @param baseFont - 待设值字体
     */
    public PDFTemplateFiller font(BaseFont baseFont) {
        this.baseFont = baseFont;
        return this;
    }

    /**
     * 页码样式设置
     * <p>
     * {@link #addPageNumber}为true时有效
     *
     * @param pageNumberLeft  - 页码左边样式字符
     * @param pageNumberRight - 页码邮编样式字符
     */
    public PDFTemplateFiller pageNumberStyle(String pageNumberLeft, String pageNumberRight) {
        this.pageNumberLeft = pageNumberLeft;
        this.pageNumberRight = pageNumberRight;
        return this;
    }

    /**
     * 根据表单域字段名查找其对应所在页码
     *
     * @param form      - 表单对象
     * @param fieldName - 表单域属性名
     * @return - 找到返回具体页码(起始页为1);否则返回-1
     */
    private int findPageNumber(AcroFields form, String fieldName) {
        List<AcroFields.FieldPosition> positions = form.getFieldPositions(fieldName);
        if (positions == null || positions.isEmpty()) {
            return -1;
        }
        return positions.get(0).page;
    }

    /**
     * 填充文本
     */
    private void doFill(PdfStamper stamper, AcroFields form, Map<String, Object> fillData) throws DocumentException, IOException {
        for (Map.Entry<String, Object> entry : fillData.entrySet()) {
            if (entry.getValue() instanceof byte[]) {
                doFillImage(stamper, entry.getKey(), (byte[]) entry.getValue());
            } else {
                form.setField(entry.getKey(), String.valueOf(entry.getValue()));
            }
        }
    }

    /**
     * 填充图片
     */
    private void doFillImage(PdfStamper stamper, String fieldName, byte[] image) throws DocumentException, IOException {
        AcroFields form = stamper.getAcroFields();
        List<AcroFields.FieldPosition> positions = form.getFieldPositions(fieldName);
        if (positions != null && !positions.isEmpty()) {
            AcroFields.FieldPosition position = positions.get(0);
            Rectangle rectangle = position.position;
            com.itextpdf.text.Image img = com.itextpdf.text.Image.getInstance(image);
            // 根据域的大小缩放图片
            img.scaleToFit(rectangle.getWidth(), rectangle.getHeight());
            img.setAbsolutePosition(rectangle.getLeft(), rectangle.getBottom());
            stamper.getOverContent(position.page).addImage(img);
        }
    }

    /**
     * 添加页码
     */
    private void doAadPageNumber(PdfWriter writer, int pageNumber) {
        PdfContentByte contentByte = writer.getDirectContent();
        contentByte.beginText();
        contentByte.setFontAndSize(baseFont, pageNumberFontSize);
        Rectangle rectangle = writer.getPageSize();
        // 页码的 横轴 坐标 居中
        float x = (rectangle.getLeft() + rectangle.getRight()) / 2;
        contentByte.showTextAligned(Element.ALIGN_CENTER, String.format("%s%d%s", pageNumberLeft, pageNumber, pageNumberRight), x, 20, 0);
        contentByte.endText();
    }

    /**
     * 合并多个PDF文件到一个PDF中
     *
     * @param pdfs - 待合并的PDF 文件
     * @return byte - 合并后的PDF流
     */
    public static byte[] merge(File... pdfs) throws Exception {
        PdfReader[] array = new PdfReader[pdfs.length];
        for (int i = 0; i < pdfs.length; i++) {
            array[i] = new PdfReader(pdfs[i].getAbsolutePath());
        }
        return merge(array);
    }

    /**
     * 合并多个PDF文件到一个PDF中
     *
     * @param pdfs - 待合并的PDF 文件
     * @return byte - 合并后的PDF流
     */
    public static byte[] merge(byte[]... pdfs) throws Exception {
        PdfReader[] array = new PdfReader[pdfs.length];
        for (int i = 0; i < pdfs.length; i++) {
            array[i] = new PdfReader(pdfs[i]);
        }
        return merge(array);
    }

    /**
     * 合并多个PDF文件到一个PDF中
     *
     * @param pdfs - 待合并的PDF 文件
     * @return byte - 合并后的PDF流
     */
    public static byte[] merge(PdfReader... pdfs) throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Document document = new Document();
        PdfCopy copy = new PdfCopy(document, out);
        document.open();
        for (PdfReader reader : pdfs) {
            for (int i = 1, pages = reader.getNumberOfPages(); i <= pages; i++) {
                copy.addPage(copy.getImportedPage(reader, i));
            }
            reader.close();
        }
        copy.close();
        document.close();
        return out.toByteArray();
    }

    public static PDFTemplateFiller load(InputStream pdfTemplate) throws IOException, DocumentException {
        return new PDFTemplateFiller(new PdfReader(pdfTemplate));
    }

    public static PDFTemplateFiller load(File pdfTemplate) throws IOException, DocumentException {
        return new PDFTemplateFiller(new PdfReader(pdfTemplate.getAbsolutePath()));
    }

    public static PDFTemplateFiller load(byte[] pdfTemplate) throws IOException, DocumentException {
        return new PDFTemplateFiller(new PdfReader(pdfTemplate));
    }

    public static PDFTemplateFiller load(ByteArrayOutputStream pdfTemplate) throws IOException, DocumentException {
        return load(pdfTemplate.toByteArray());
    }

    private PDFTemplateFiller(PdfReader pdfReader) throws DocumentException, IOException {
        this.reader = pdfReader;
        this.baseFont = getDefaultFont();
        this.addPageNumber = false;
    }

    /**
     * 获取默认的字体(宋体)
     */
    private BaseFont getDefaultFont() throws DocumentException, IOException {
        return BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
    }

}

测试demo

java 复制代码
public static void main(String[] args) throws Exception {
        String templatePath = "/path/模板.pdf";
        byte[] avatar = IOUtils.toByteArray(Files.newInputStream(Paths.get("/path/avatar.jpg")));

        Map<String, Object> fillData = new HashMap<>();
        fillData.put("name", "Ian");
        fillData.put("gender", "男");
        fillData.put("addr", "贵州省贵阳市");
        fillData.put("avatar", avatar);
        fillData.put("avatar1", avatar);

        String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        byte[] bytes = PDFTemplateFiller.load(Files.newInputStream(Paths.get(templatePath)))
                .pageNumber(true)
                .pageNumberStyle("-< ", " >-")
                .fill(fillData);
        write(bytes, "/path/" + date + ".pdf");

        File dir = new File("/path/pdfdata/");
        File[] files = dir.listFiles((file, name) -> name.endsWith(".pdf"));
        assert files != null;
        byte[] bytes1 = PDFTemplateFiller.merge(files);
        write(bytes1, "/path/merged" + date +".pdf");
    }

    static void write(byte[] bytes, String dest) throws IOException {
        IOUtils.write(bytes, Files.newOutputStream(Paths.get(dest)));
    }
相关推荐
啊松同学3 分钟前
【Java】设计模式——工厂模式
java·后端·设计模式
枫叶_v31 分钟前
【SpringBoot】20 同步调用、异步调用、异步回调
java·spring boot·后端
鸣弦artha38 分钟前
蓝桥杯——杨辉三角
java·算法·蓝桥杯·eclipse
大波V51 小时前
设计模式-参考的雷丰阳老师直播课
java·开发语言·设计模式
计算机-秋大田1 小时前
基于微信小程序的平安驾校预约平台的设计与实现(源码+LW++远程调试+代码讲解等)
java·spring boot·微信小程序·小程序·vue·课程设计
《源码好优多》1 小时前
基于Java Springboot旅游信息推荐系统
java·spring boot·旅游
岁月无声code1 小时前
Spring Boot 牛刀小试 org.springframework.boot:spring-boot-maven-plugin:找不到类错误
java·spring boot·github
不爱学习的YY酱1 小时前
【计网不挂科】计算机网络第二章< 物理层 >习题库(含答案)
java·数据库·计算机网络
南城花随雪。2 小时前
Spring框架之装饰者模式 (Decorator Pattern)
java·开发语言·装饰器模式
编程、小哥哥2 小时前
设计模式之装饰器模式(SSO单点登录功能扩展,增加拦截用户访问方法范围场景)
java·设计模式·装饰器模式