更加灵活的使用itext绘制pdf--通过自定义注解和反射实现

1.背景

在开发中遇到一个需求,需要生成pdf数据, 刚开始想到了两个方案,根据freemarker 生成pdf(存在样式问题),填充word,然后转换成pdf(存在样式问题),后来发现了itext直接在pdf上绘制 样式

blog.csdn.net/weixin_4420…

2.设计思路

此处才是本偏文章的重点

1.定于注解

itext没有横坐标和纵坐标的概念,要想绘制回执单类型的数据,只能设置整个pdf多少列,然后依次排序,然后可以对每一个单元格(cell)设置单独的样式效果,例如,字体,字高,字色,字号,边框,水平和垂直样式等等,那么基于此分析,我们就可以采用模板类的方式来实现此功能,对于模板类定义了两个注解CellLayout和TableLayout分别用来定于模板的全局样式和单个单元格的样式

2.反射编写

2.itext使用

基本使用 :blog.csdn.net/weixin_4420…

在使用的过程中,我发现itext 的使用由一些基本的元素组成,我们需要绘制的pdf,里面是好多事动态的数据,于是我基于此,提取出了,一些基本元素,通过自定义注解和反射,实现了动态渲染

1.pom.xml

<dependency>
  <groupId>com.itextpdf</groupId>
  <artifactId>itextpdf</artifactId>
  <version>5.5.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
<dependency>
  <groupId>com.itextpdf</groupId>
  <artifactId>itext-asian</artifactId>
  <version>5.2.0</version>
</dependency>
复制代码

3.自定义实现

1.自定义注解

CellLayout

itext没有行的概念,只有单元格,所以自定义的单元格的样式

import com.itextpdf.text.pdf.PdfPCell;

import java.lang.annotation.*;

/**
* @ClassName: CellLayout
* @description: pdf单元格的样式
* @author: find me
* @create: 2022-04-13 13:42
* @Version 1.0
*/


@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME) // 运行时起作用
@Documented
public @interface CellLayout {
    // 字段的中文key
    String title() default "";
    
    // 顺序
    int index();
    
    // 水平样式 0-left, 1-center, 2-right
    int horizontalAlignment() default PdfPCell.ALIGN_LEFT;
    
    
    // 垂直样式  4-top, 5-middle, 6-bottom;
    int verticalAlignment() default PdfPCell.ALIGN_TOP;
    
    // 列合并
    int colspan() default 0;
    
    // 行合并
    int rowspan() default 0;
    
    /**
    * 外边框
    * 0-默认
    * 1-隐藏上边框
    * 2-隐藏下边框
    * 3-隐藏上、下边框
    * 4-隐藏左边框
    * 5-隐藏左、上边框
    * 6-隐藏左、下边框
    * 7-隐藏左、上、下边框
    * 8-隐藏右边框
    * 9-隐藏右、上边框
    * 10-隐藏右、下边框
    * 11-隐藏右、上、下边框
    * 12-隐藏左、右边框
    * 13-隐藏上、左、右边框
    * 14-隐藏下、左、右边框
    * 15-隐藏全部
    */
    int borderSide() default 15;
    
    // 行高
    float minimumHeight() default 17f;
    
    //    // 换行缩进比
    //    float followingIndent() default 0f;
    // 换行缩进比
    int followingIndentRatio() default 0;
    
}
复制代码

TableLayout

对挥着的表格多少列

import java.lang.annotation.*;

/**
 * @ClassName: TableLayout
 * @description: 表格的整体样式
 * @author: find me
 * @create: 2022-04-13 13:43
 * @Version 1.0
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME) // 运行时起作用
@Documented
public @interface TableLayout {

    // 每列的宽度
    float[] cols() ;

    // 字体大小
    float fontSize() default 10f;

}
复制代码

FontFactory

字体等基本样式的工厂,这里只是自己封装的工具类

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.pdf.BaseFont;

import java.io.IOException;

/**
 * @ClassName: FontFactory
 * @description:
 * @author: find me
 * @create: 2022-04-13 13:50
 * @Version 1.0
 */
public class FontFactory {
    /**
     * 获取标题样式
     *
     * @return
     * @throws DocumentException
     * @throws IOException
     */
    public static Font getTitleFont() throws DocumentException, IOException {
        BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED, true);
        Font font = new Font(bf, 22, Font.BOLD, BaseColor.BLACK);//字体
        return font;
    }

    /**
     * 获取正文样式
     *
     * @return
     * @throws DocumentException
     * @throws IOException
     */
    public static Font getBodyFont() throws DocumentException, IOException {
        BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED, true);
        Font font = new Font(bf, 22, Font.BOLD, BaseColor.BLACK);//字体
        return font;
    }

    /**
     * 获取头部样式
     *
     * @return
     * @throws DocumentException
     * @throws IOException
     */
    public static Font getHeadFont() throws DocumentException, IOException {
        BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED, true);
        Font font = new Font(bf, 22, Font.BOLD, BaseColor.BLACK);//字体
        return font;
    }

    /**
     * 获取上下文样式
     *
     * @return
     * @throws DocumentException
     * @throws IOException
     */
    public static Font getContextFont() throws DocumentException, IOException {
        BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED, true);
        Font font = new Font(bf, 10, Font.NORMAL, BaseColor.BLACK);//字体
        return font;
    }

    /**
     * 获回执信息样式
     *
     * @return
     * @throws DocumentException
     * @throws IOException
     */
    public static Font getReceiptHolderFont() throws DocumentException, IOException {
        BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED, true);
        Font font = new Font(bf, 10, Font.NORMAL, BaseColor.BLACK);//字体
        return font;
    }
}
复制代码

HeadLayout

execl类型的表格的头部数据

import com.itextpdf.text.pdf.PdfPCell;

import java.lang.annotation.*;

/**
 * @ClassName: CellLayout
 * @description: pdf单元格的样式
 * @author: find me
 * @create: 2022-04-13 13:42
 * @Version 1.0
 */


@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME) // 运行时起作用
@Documented
public @interface HeadLayout {
    // 头部标题
    String headTitle() default "";

    // 顺序
    int index();

    // 水平样式 0-left, 1-center, 2-right
    int horizontalAlignment() default PdfPCell.ALIGN_LEFT;


    // 垂直样式  4-top, 5-middle, 6-bottom;
    int verticalAlignment() default PdfPCell.ALIGN_TOP;

    // 列合并
    int colspan() default 0;

    // 行合并
    int rowspan() default 0;

    /**
     * 外边框
     * 0-默认
     * 1-隐藏上边框
     * 2-隐藏下边框
     * 3-隐藏上、下边框
     * 4-隐藏左边框
     * 5-隐藏左、上边框
     * 6-隐藏左、下边框
     * 7-隐藏左、上、下边框
     * 8-隐藏右边框
     * 9-隐藏右、上边框
     * 10-隐藏右、下边框
     * 11-隐藏右、上、下边框
     * 12-隐藏左、右边框
     * 13-隐藏上、左、右边框
     * 14-隐藏下、左、右边框
     * 15-隐藏全部
     */
    int borderSide() default 15;

    // 行高
    float minimumHeight() default 17f;

    //    // 换行缩进比
//    float followingIndent() default 0f;
    // 换行缩进比
    int followingIndentRatio() default 0;

}
复制代码

CellFactory

生成cell样式

/**
 * @ClassName: CellFactory
 * @description:
 * @author: find me
 * @create: 2022-04-13 13:50
 * @Version 1.0
 */
public class CellFactory {

    // 字体
    private Font font;
    //  水平样式 0-left, 1-center, 2-right
    private int horizontalAlignment;
    // 垂直样式  4-top, 5-middle, 6-bottom;
    private int verticalAlignment;
    // 列合并
    private int colspan;
    // 行合并
    private int rowspan;
    /**
     * 外边框
     * 0-默认
     * 1-隐藏上边框
     * 2-隐藏下边框
     * 3-隐藏上、下边框
     * 4-隐藏左边框
     * 5-隐藏左、上边框
     * 6-隐藏左、下边框
     * 7-隐藏左、上、下边框
     * 8-隐藏右边框
     * 9-隐藏右、上边框
     * 10-隐藏右、下边框
     * 11-隐藏右、上、下边框
     * 12-隐藏左、右边框
     * 13-隐藏上、左、右边框
     * 14-隐藏下、左、右边框
     * 15-隐藏全部
     */
    private int borderSide;

    // 最小高度
    private float minimumHeight;

    /**
     * @param value               文本
     * @param font                字体
     * @param horizontalAlignment 水平样式 0-left, 1-center, 2-right
     * @param verticalAlignment   垂直样式  4-top, 5-middle, 6-bottom;
     * @param colspan             列合并
     * @param rowspan             行合并
     * @param borderSide          外边框
     *                            0-默认
     *                            1-隐藏上边框
     *                            2-隐藏下边框
     *                            3-隐藏上、下边框
     *                            4-隐藏左边框
     *                            5-隐藏左、上边框
     *                            6-隐藏左、下边框
     *                            7-隐藏左、上、下边框
     *                            8-隐藏右边框
     *                            9-隐藏右、上边框
     *                            10-隐藏右、下边框
     *                            11-隐藏右、上、下边框
     *                            12-隐藏左、右边框
     *                            13-隐藏上、左、右边框
     *                            14-隐藏下、左、右边框
     *                            15-隐藏全部
     * @return
     */
    public PdfPCell setCelText(PdfPCell cell, String value) {
        cell.setPhrase(new Phrase(value, font));//存值
        return cell;
    }

    public CellFactory(Font font, int horizontalAlignment, int verticalAlignment, int colspan, int rowspan, int borderSide, float minimumHeight) {
        this.font = font;
        this.horizontalAlignment = horizontalAlignment;
        this.verticalAlignment = verticalAlignment;
        this.colspan = colspan;
        this.rowspan = rowspan;
        this.borderSide = borderSide;
        this.minimumHeight = minimumHeight;
    }

    public CellFactory() {
    }

    public PdfPCell setCellStyle(PdfPCell cell) {

        cell.setHorizontalAlignment(this.horizontalAlignment);
        cell.setMinimumHeight(this.minimumHeight);
        if (this.verticalAlignment > 0) {
            cell.setUseAscender(true);//垂直居中
        }
        cell.setVerticalAlignment(this.verticalAlignment);
        if (this.colspan > 0) {
            cell.setColspan(this.colspan);
        }
        if (this.rowspan > 0) {
            cell.setRowspan(this.rowspan);
        }
        if (this.borderSide > 0) {
            cell.disableBorderSide(this.borderSide);
        }
        return cell;
    }

    public PdfPCell generatePdfCell(String value) {
        PdfPCell cell = setCellStyle(new PdfPCell());
        cell.setPhrase(new Phrase(value, font));
        return cell;
    }

    public PdfPCell generatePdfCell(String value, float followingIndent) {
        PdfPCell cell = generatePdfCell(value);
        cell.setFollowingIndent(followingIndent);
        return cell;
    }

    public PdfPCell generatePdfCell() {
        PdfPCell cell = setCellStyle(new PdfPCell());
        return cell;
    }

    public PdfPCell generatePdfCell(float followingIndent) {
        PdfPCell cell = setCellStyle(new PdfPCell());
        cell.setFollowingIndent(followingIndent);
        return cell;
    }
}
复制代码

2.实现反射

PdfTemplate

此处是核心代码,利用反射生成itext的PdfPTable

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @ClassName: PdfTemplate
 * @description:
 * @author: find me
 * @create: 2022-04-13 13:45
 * @Version 1.0
 */
public class PdfTemplate<T> {
    public Paragraph drawTitle(String title) throws DocumentException, IOException {
        Font titleFont = FontFactory.getTitleFont();
        Paragraph paragraph = drawTitle(title, titleFont);
        return paragraph;
    }

    public Paragraph drawTitle(String title, Font font) {
        Paragraph titleParagraph = new Paragraph(title, font);
        titleParagraph.setAlignment(PdfPCell.ALIGN_RIGHT);
        //行间距
        titleParagraph.setLeading(14f);
        //设置左缩进
        //paragraph1.setIndentationLeft(12);
        //设置右缩进
        titleParagraph.setIndentationRight(100);
        //设置首行缩进
        //paragraph1.setFirstLineIndent(24);
        //设置段落上空白
        //paragraph1.setSpacingBefore(5f);
        //设置段落下空白
        titleParagraph.setSpacingAfter(10f);
        return titleParagraph;
    }

    public PdfPTable drawPdfContext(Object obj) throws DocumentException, IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Font contextFont = FontFactory.getContextFont();
        PdfPTable table = drawPdfBody(obj, contextFont);
        return table;
    }

    public PdfPTable drawPdfReceiptHolder(Object obj) throws DocumentException, IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Font receiptHolderFont = FontFactory.getReceiptHolderFont();
        PdfPTable table = drawPdfBody(obj, receiptHolderFont);
        return table;
    }

    public PdfPTable drawPdfBody(Object obj, Font font) throws NoSuchMethodException, InvocationTargetException,
            IllegalAccessException {
        Class aClass = obj.getClass();

        TableLayout tableLayout = (TableLayout) aClass.getAnnotation(TableLayout.class);
        Field[] declaredFields = aClass.getDeclaredFields();
        // 对于有 CellLayout 的字段 按照index排序,并且
        LinkedList<Field> collect = Arrays.stream(declaredFields).filter(item -> item.getAnnotation(CellLayout.class) != null).sorted(Comparator.comparingInt(field -> field.getAnnotation(CellLayout.class).index())).collect(Collectors.toCollection(LinkedList::new));
        // 设置样式
        PdfPTable contextTable = new PdfPTable(tableLayout.cols());
        for (Field field : collect) {
            CellLayout cellLayout = field.getAnnotation(CellLayout.class);
            CellFactory cellFactory = new CellFactory(font, cellLayout.horizontalAlignment(), cellLayout.verticalAlignment(), cellLayout.colspan(), cellLayout.rowspan(), cellLayout.borderSide(), cellLayout.minimumHeight());
            String name = field.getName();
            name = name.substring(0, 1).toUpperCase() + name.substring(1);
            Method m = aClass.getMethod("get" + name);
            PdfPCell cell = cellFactory.generatePdfCell(cellLayout.title() + ((m.invoke(obj) != null) ? m.invoke(obj) :
                    ""));
            cell.setFollowingIndent(tableLayout.fontSize() * cellLayout.followingIndentRatio());
            contextTable.addCell(cell);
        }
        return contextTable;
    }


    public PdfPTable drawPdfExcel(List<T> objs, Class aClass, Font font) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        TableLayout tableLayout = (TableLayout) aClass.getAnnotation(TableLayout.class);

        PdfPTable excelTable = new PdfPTable(tableLayout.cols());
        Field[] declaredFields = aClass.getDeclaredFields();


        LinkedList<Field> collect = Arrays.stream(declaredFields).filter(item -> item.getAnnotation(HeadLayout.class) != null).sorted(Comparator.comparingInt(field -> field.getAnnotation(HeadLayout.class).index())).collect(Collectors.toCollection(LinkedList::new));
        for (Field field : collect) {
            HeadLayout headLayout = field.getAnnotation(HeadLayout.class);
            CellFactory cellFactory = new CellFactory(font, headLayout.horizontalAlignment(), headLayout.verticalAlignment(), headLayout.colspan(), headLayout.rowspan(), headLayout.borderSide(), headLayout.minimumHeight());
            PdfPCell cell = cellFactory.generatePdfCell(headLayout.headTitle());
            excelTable.addCell(cell);
        }


        for (T obj : objs) {
             Class<?> obj1 = obj.getClass();
//            Class obj1 = (Class) obj;
            Field[] qq = aClass.getDeclaredFields();
            // 对于有 CellLayout 的字段 按照index排序,并且
            LinkedList<Field> collect1 =
                    Arrays.stream(qq).filter(item -> item.getAnnotation(CellLayout.class) != null).sorted(Comparator.comparingInt(field -> field.getAnnotation(CellLayout.class).index())).collect(Collectors.toCollection(LinkedList::new));
            // 设置样式
            for (Field field : collect1) {
                CellLayout cellLayout = field.getAnnotation(CellLayout.class);
                CellFactory cellFactory = new CellFactory(font, cellLayout.horizontalAlignment(), cellLayout.verticalAlignment(), cellLayout.colspan(), cellLayout.rowspan(), cellLayout.borderSide(), cellLayout.minimumHeight());
                String name = field.getName();
                name = name.substring(0, 1).toUpperCase() + name.substring(1);
                Method m = obj1.getMethod("get" + name);
                PdfPCell cell = cellFactory.generatePdfCell(cellLayout.title() + ((m.invoke(obj) != null) ? m.invoke(obj) :
                        ""));
                cell.setFollowingIndent(tableLayout.fontSize() * cellLayout.followingIndentRatio());
                excelTable.addCell(cell);
            }
        }
        return excelTable;
    }


}
复制代码

GeneratePdf

调用方法

import com.google.common.io.ByteArrayDataOutput;
import com.itextpdf.text.Document;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import org.springframework.lang.NonNull;

import java.io.*;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * @ClassName: GeneratePdf
 * @description:
 * @author: find me
 * @create: 2022-04-13 13:44
 * @Version 1.0
 */
public class GeneratePdf<T> {

    /**
     * @param document      文档
     * @param title         title
     * @param context       凭证主题数据
     * @param receiptHolder 回执留存方
     */
    public OutputStream generatePDFVoucher(Document document, String title, Object context, Object receiptHolder) throws Exception {
        PdfTemplate pdfTemplate = new PdfTemplate();

        try (OutputStream out = new ByteArrayOutputStream()) {
            SecureRandom random = new SecureRandom();
            int i = random.nextInt(1000000);
            File file = new File("D:\" + i + ".pdf");
            //生成目标文件
//            PdfWriter writer2 = PdfWriter.getInstance(document, new FileOutputStream(file));
//            PdfWriter writer = PdfWriter.getInstance(document, out);
            PdfWriter.getInstance(document, out);

            document.open();
            Paragraph paragraphTitle = pdfTemplate.drawTitle(title);
            document.add(paragraphTitle);
            if (context != null) {
                PdfPTable contextTable = pdfTemplate.drawPdfContext(context);
                document.add(contextTable);
            }
            if (receiptHolder != null) {
                PdfPTable receiptHolderTable = pdfTemplate.drawPdfReceiptHolder(receiptHolder);
                document.add(receiptHolderTable);
            }
            return out;
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception(e);
        } finally {
            // 5.关闭文档
            if (document != null) {
                document.close();
            }
        }
    }

    /**
     * 详情
     *
     * @param document      文档
     * @param title         标题
     * @param head          头部数据
     * @param excelContext  表格具体数据
     * @param excelTail     表格尾部备注
     * @param receiptHolder 回执人
     * @return
     */
    public OutputStream generatePDFVoucherDetail(Document document, String title, @NonNull Object head,
                                                 @NonNull List<T> excelContext, @NonNull Object excelTail,
                                                 @NonNull Object receiptHolder, Class aClass) throws Exception {

        PdfTemplate pdfTemplate = new PdfTemplate<T>();

        try (OutputStream out = new ByteArrayOutputStream()) {
            PdfWriter writer = PdfWriter.getInstance(document, out);
//            SecureRandom random = new SecureRandom();
//            int i = random.nextInt(1000000);
//            File file = new File("D:\" + i + ".pdf");//生成目标文件
//            file.createNewFile();
//            PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(file));
            document.open();
            // 标题
            Paragraph paragraphTitle = pdfTemplate.drawTitle(title);
            document.add(paragraphTitle);
            if (head != null) {
                PdfPTable headTable = pdfTemplate.drawPdfContext(head);
                document.add(headTable);
            }

            if (excelContext != null) {
                PdfPTable excelTable = pdfTemplate.drawPdfExcel(excelContext, aClass, FontFactory.getContextFont());
                document.add(excelTable);
            }
            if (excelTail != null) {
                PdfPTable tailTable = pdfTemplate.drawPdfContext(excelTail);
                document.add(tailTable);
            }
            if (receiptHolder != null) {
                PdfPTable receiptInfoTable = pdfTemplate.drawPdfReceiptHolder(receiptHolder);
                document.add(receiptInfoTable);
            }

            return out;
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception(e);
        } finally {
            // 5.关闭文档
            if (document != null) {
                document.close();
            }
        }


    }

}
复制代码

猜你喜欢

转载自juejin.im/post/7104471057385390088