一、介绍

上一篇文章(java使用freemarker操作word(携带动态表格,图片))https://blog.csdn.net/weixin_45853881/article/details/129298494

紧跟上文,现在不仅需要word,而且还要pdf
在这里插入图片描述

二、准备

导入依赖,这里使用iText 5没有使用iText 7的,追求快点写,至于iText 7,下次一定

<!-- iText 5核心库-->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.2.0</version>
</dependency>
<!--字体-->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>

三、实现效果

所有图片均来源于网络,两个表格使用不同的方法进行写的,水印透明度和颜色可以调节,水印页码也可以调整
刚加入了水印设置,懒得截图了也,代码里面有
在这里插入图片描述
在这里插入图片描述

四、代码

这里把代码都贴上来了,就把一下操作隐藏了,替换为了手敲,先上代码,后展示样式

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

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class demo1 {
    public static void main(String[] args) {
        try {
            generatePDF();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

	// 生成 pdf
    public static void generatePDF() throws Exception{
        //临时文件目录
        String name = "检查报告-" + UUID.randomUUID();
        FileOutputStream fos = new FileOutputStream("C:\\Users\\admin\\Downloads\\" + name + ".pdf");

        // 服务器对客户端的HTTP请求的响应对象
        // HttpServletResponse response
        // ServletOutputStream os = response.getOutputStream();

        //新建文档
        Document document = new Document();
        // 将内存中的 PDF 文档内容转换为二进制数据,处理流
        PdfWriter writer = PdfWriter.getInstance(document, fos);
        writer.setViewerPreferences(PdfWriter.PageModeUseThumbs);
        writer.setPageSize(PageSize.A4);
        // 注册水印,页码事件
        writer.setPageEvent(new PageEvent());
        // 打开文档
        document.open();

        // 首页
        part1(document,writer);
        // 总览页(表格1)
        document.newPage();
        part2(document);
        //内容第一页(表格2)
        document.newPage();
        part3(document);
        // 查看页码设置生效了没
        document.newPage();
        Paragraph con = new Paragraph(" ");
        document.add(con);
        // 关闭文档
        document.close();
    }

    // 首页 封面
    public static void part1(Document document, PdfWriter writer) throws Exception{
        // pdf首页据上方默认无限边界,所有先设置一个空的 好调整位置,不设置空的也可以,使用时间那里的绝对定位进行设置
        // 获取页面尺寸信息(A4默认宽595f,高842f,可根据实际调整)
        float pageWidth = document.getPageSize().getWidth();
        float pageHeight = document.getPageSize().getHeight();
        // 计算居中的坐标(x为页面水平中心)
        float centerX = pageWidth / 2;

        // 空白占据
        Paragraph blank = new Paragraph(" "); //有空格,证明有内容,才可以进行设置间距
        blank.setSpacingBefore(10f); // 上方留出10磅的间距
        blank.setSpacingAfter(10f); // 下方留出10磅的间距
        document.add(blank);

        //logo
        Paragraph logo = new Paragraph();
        Image image;
        try {
            image = Image.getInstance("https://img-home.csdnimg.cn/images/20201124032511.png");
        } catch (Exception e) {
            image = createBlankImage();
        }
        image.scaleToFit(100, 100); // 调整图片大小
        // 方法作用是创建一个包含图像的文本块 最后一个参数:是否调整行高以适应图像, 设置为ture之后上一步设置的scaleToFit需要等比设置才生效(例如image.scaleToFit(100, 100);会生效100,image.scaleToFit(200, 100);也会生效100)
        logo.add(new Chunk(image, 18, 0, true));
        Chunk textChunk = new Chunk("CSND", getFont(12, Font.BOLD));
        textChunk.setCharacterSpacing(-2f); //字符间距
        logo.add(textChunk);
        logo.setSpacingBefore(2f);
        logo.setIndentationLeft(19f); //左方缩进
        document.add(logo);

        //标题
        Paragraph title = new Paragraph();
        title.add(new Chunk("XXX用户", getFont(28,Font.BOLD)));
        title.add(Chunk.NEWLINE);
        title.add(new Chunk("文章检测报告", getFont(28,Font.BOLD)));
        title.setLeading(0, 2.8f);
        title.setAlignment(Element.ALIGN_CENTER);
        title.setSpacingBefore(49f); // 上方留出49磅的间距
        document.add(title);


        // 底部时间
        float bottomY = 130f;
        ColumnText ct = new ColumnText(writer.getDirectContent());
        Paragraph time = new Paragraph();
        time.add(new Chunk("官方检测", getFont(18,Font.BOLD)));
        time.add(Chunk.NEWLINE);
        time.add(new Chunk("二〇二五年七月二十九日", getFont(18,Font.BOLD)));
        time.setLeading(0, 1.8f);
        time.setAlignment(Element.ALIGN_CENTER);
        ct.setSimpleColumn(
                centerX - 200f, //左侧
                bottomY, //下方
                centerX + 200f, //右边
                bottomY + 100f //上边
        );
        ct.addElement(time);
        ct.go();
    }

    // 总览页
    public static void part2(Document document) throws Exception {
        Paragraph blank = new Paragraph(" ");
        blank.setSpacingAfter(10f);
        document.add(blank);

        Paragraph title = new Paragraph();
        title.add(new Chunk("xxx用户", getFont(18, Font.BOLD)));
        title.add(new Chunk("检测认证", getFont(18, Font.BOLD)));
        title.setAlignment(Element.ALIGN_CENTER);
        title.setSpacingAfter(10f);
        document.add(title);

        Paragraph content = new Paragraph();
        content.add(new Chunk("受xxx委托,xxx官方对xxx用户信息进行审核。",getFont(16, Font.NORMAL)));
        content.setLeading(0, 2.4f); //设置段落的行间距
        // 设置首行缩进(约2个中文字符,假设16号字体下1个字符≈16磅)
        content.setFirstLineIndent(32f);
        content.setSpacingAfter(10f);
        document.add(content);

        // 创建一个2列的表格
        PdfPTable table = new PdfPTable(2);
        table.setWidthPercentage(90); // 可以减少宽度以避免边缘挤压,根据实际需要设置
        // 定义表格各行的单元格内容,按顺序添加
        String[][] cellTexts = {
                {"检查时间:2025.5.29"},
                {"检查渠道:", "CSDN"}, // 第二行第二列将合并到第三行
                {"检查官方:", ""},  // 第三行第二列留空(已被合并)
                {"图片1:",""}, // 合并单元格,使用单元格的值,在进行附加
                {"",""},
        };

        for (int i = 0; i < cellTexts.length; i++) {
            String[] rowTexts = cellTexts[i];
            for (int j = 0; j < rowTexts.length; j++) {
                String cellText = rowTexts[j];
                // 跳过已被合并的单元格 第三行第二列  第四行第二列
                if ((i == 2 || i == 3) && j == 1) {
                    continue;
                }

                PdfPCell cell;
                // 处理需要插入图片的单元格
                if (i == 3  || i == 4) { // 第4行和第5行
                    try {
                        Paragraph paragraph = new Paragraph();
                        Image image;
                        if (i == 3) { //第四行合并了
                            paragraph.add(new Chunk(cellText, getFont(14, Font.NORMAL)));
                            paragraph.add(Chunk.NEWLINE);
                            try {
                                image = Image.getInstance("https://pics2.baidu.com/feed/e824b899a9014c08c4754f93412bd5037bf4f412.jpeg");
                            } catch (Exception e) {
                                image = createBlankImage();
                            }
                            image.scaleToFit(100, 100); // 调整图片大小
                            paragraph.add(new Chunk(image, 0, 0, true));
                            cell = new PdfPCell();
                            cell.addElement(paragraph);
                        } else { //第五行有两个
                            if (j == 0) {
                                paragraph.add(new Chunk("图片2:", getFont(14, Font.NORMAL)));
                                paragraph.add(Chunk.NEWLINE);
                                try {
                                    image = Image.getInstance("https://pics2.baidu.com/feed/7e3e6709c93d70cf8e1b7905b58c060bbba12bb5.jpeg");
                                } catch (Exception e) {
                                    image = createBlankImage();
                                }
                                // 设置图片属性
                                image.scaleToFit(100, 100); // 缩放图片以适应单元格
                                cell = new PdfPCell();
                                cell.addElement(paragraph);
                                paragraph.add(new Chunk(image, 0, 0, true));
                            } else {
                                try {
                                    image = Image.getInstance("https://pics6.baidu.com/feed/d833c895d143ad4b000f8b7dce528da4a50f06c2.jpeg");
                                } catch (Exception e) {
                                    image = createBlankImage();
                                }
                                // 设置图片属性
                                image.scaleToFit(100, 100); // 缩放图片以适应单元格
                                image.setAlignment(Image.ALIGN_CENTER); // 图片居中
                                // 创建包含图片的单元格
                                cell = new PdfPCell(image, true); // 第二个参数true表示拉伸图片以填满单元格
                            }
                        }
                    } catch (Exception e) {
                        // 图片加载失败时,使用文本替代
                        cell = new PdfPCell(new Phrase("图片加载失败", getFont(14, Font.ITALIC)));
                        e.printStackTrace();
                    }
                } else {
                    // 普通文本单元格
                    cell = new PdfPCell(new Phrase(cellText, getFont(14, Font.NORMAL)));
                }

                // 第一行合并两列
                if (i == 0) {
                    cell.setColspan(2); // 合并两列
                    cell.setHorizontalAlignment(Element.ALIGN_LEFT); // 左对齐
                }
                // 第二行第二列:合并两行
                if (i == 1 && j == 1) {
                    cell.setRowspan(2); // 合并下面一行
                }
                if (i == 3) {
                    cell.setColspan(2); // 合并两列
                    cell.setHorizontalAlignment(Element.ALIGN_LEFT); // 左对齐
                }

                // 设置单元格内边距
                cell.setPadding(5);
                // 设置单元格内容垂直居中
                cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
                cell.setHorizontalAlignment(Element.ALIGN_CENTER);
                table.addCell(cell);
            }
        }
        // 将表格添加到文档
        document.add(table);
    }

    // 内容页
    public static void part3(Document document) throws Exception{
        Paragraph title = new Paragraph();
        title.add(new Chunk("情况汇总表", getFont(18,Font.BOLD)));
        title.setAlignment(Element.ALIGN_CENTER);
        title.setSpacingAfter(20f);
        document.add(title);

        // 创建一个5列的表格
        PdfPTable table = new PdfPTable(5);
        table.setWidthPercentage(100); // 可以减少宽度以避免边缘挤压,根据实际需要设置
        table.setHorizontalAlignment(Element.ALIGN_CENTER); //表格水平居中
        // 设置表格列宽比例
        float[] columnWidths = {1f, 4f, 3f,4f,3f};
        table.setWidths(columnWidths);

        // 添加表头
        String[] headers = {"序号", "姓名", "年龄","职位","图片"};
        for (String header : headers) {
            PdfPCell cell = new PdfPCell(new Phrase(header, getFont(12,null)));
            cell.setHorizontalAlignment(Element.ALIGN_CENTER); //表头水平居中
            cell.setPadding(5);
            table.addCell(cell);
        }
        // 后续替换为接口对接查询
        List<Staff> list = new ArrayList<>();
        list.add(new Staff("1","张三","30","软件工程师","https://n.sinaimg.cn/sinakd20106/384/w2048h1536/20250517/64b2-bd7d404c74b6c162844f652af698d98e.jpg"));
        list.add(new Staff("2","李四","28","产品经理","https://img1.baidu.com/it/u=1520272912,1660814288&fm=253&fmt=auto&app=138&f=JPEG"));
        list.add(new Staff("3","王五","26","UI设计师","https://www.vipyl.com/user/webimg/2018123/20181231718374.jpg"));
        for (Staff staff : list) {
            addDataCell(table, staff.getId(), getFont(12,null));
            addDataCell(table, staff.getName(), getFont(12,null));
            addDataCell(table, staff.getAge(), getFont(12,null));
            addDataCell(table, staff.getWork(), getFont(12,null));
            Image image;
            try {
                image = Image.getInstance(staff.getPic());
            } catch (Exception e) {
                image = createBlankImage();
            }
            image.scaleToFit(100, 100);
            PdfPCell imageCell = new PdfPCell(image, true);
            imageCell.setHorizontalAlignment(Element.ALIGN_CENTER); // 图片水平居中
            imageCell.setVerticalAlignment(Element.ALIGN_MIDDLE); // 图片垂直居中
            imageCell.setPadding(5);
            table.addCell(imageCell);
        }
        // 将表格添加到文档
        document.add(table);
    }

    // 添加数据单元格的辅助方法
    private static void addDataCell(PdfPTable table, String data, Font font) {
        PdfPCell cell = new PdfPCell(new Phrase(data, font));
        cell.setHorizontalAlignment(Element.ALIGN_CENTER); // 水平居中
        cell.setVerticalAlignment(Element.ALIGN_MIDDLE); // 垂直居中
        cell.setPadding(5);
        table.addCell(cell);
    }

    // 图片获取失败处理 创建空白图像
    private static Image createBlankImage() {
        try {
            BufferedImage blankImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(blankImage, "PNG", baos);
            return Image.getInstance(baos.toByteArray());
        } catch (Exception e) {
            throw new RuntimeException("创建空白图像失败", e);
        }
    }

    // 全局静态字体
    private static BaseFont baseFont;
    static {
        try {
            baseFont = BaseFont.createFont(
                    "STSong-Light",     	 // 字体名称(宋体)
                    "UniGB-UCS2-H", 		 // 字符编码(简体中文)
                    BaseFont.NOT_EMBEDDED    // 不嵌入字体(依赖系统字体)
            );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 字体设置
    public static Font getFont(Integer size,Integer type) {
        if (size == null) size = 12;
        if (type == null) type = Font.NORMAL;
        return new Font(baseFont, size, type);
    }

    // 水印,页码 事件类
    public static class PageEvent extends PdfPageEventHelper {
        @Override
        public void onEndPage(PdfWriter writer, Document document) {
            try {
                int pageNum = writer.getPageNumber();
                /*********************************************画水印*********************************************/
                // 封面不加水印(页码为1时不显示水印)
                if (pageNum != 1) {
                    PdfContentByte canvas = writer.getDirectContentUnder();
                    // 设置水印字体
                    Font font = getFont(12, Font.BOLD);
                    // 设置水印文本
                    String watermarkText = "CSDN检测报告";
                    // 获取页面尺寸
                    Rectangle pageSize = document.getPageSize();
                    float width = pageSize.getWidth();
                    float height = pageSize.getHeight();

                    // 设置水印颜色和透明度
                    canvas.saveState();
                    // 创建 PdfGState 对象并设置透明度
                    PdfGState gs = new PdfGState();
                    gs.setFillOpacity(0.5f); // 填充透明度(0-1之间,0完全透明,1完全不透明)
                    canvas.setGState(gs); // 将透明度应用到画布
                    canvas.setRGBColorFill(200, 200, 200); // 灰色
                    canvas.setFontAndSize(font.getBaseFont(), font.getSize());

                    // 设置水印旋转角度
                    float angle = 30;
                    // 计算水印位置并添加多个水印
                    for (float y = -height / 2; y <= height * 1.5; y += 420) {
                        for (float x = -width / 2; x <= width * 1.5; x += 300) {
                            canvas.showTextAlignedKerned(
                                    Element.ALIGN_CENTER,
                                    watermarkText,
                                    x + width / 2,
                                    y + height / 2,
                                    angle
                            );
                        }
                    }
                    canvas.restoreState();
                }

                /*********************************************画页码*********************************************/
                if (pageNum != 1 && pageNum != 2) {
                    PdfContentByte cb = writer.getDirectContent();
                    // 设置字体,使用支持中文的字体(仍需设置,避免其他内容乱码)
                    Font font = getFont(10, Font.NORMAL);
                    // 设置字体到内容字节
                    cb.setFontAndSize(font.getBaseFont(), font.getSize());

                    // 计算页码位置(底部居中)
                    // 从第3页开始,显示实际页码-2(如第3页显示1,第4页显示2)
                    String pageNumber = String.valueOf(writer.getPageNumber() - 2);
                    float x = (document.left() + document.right()) / 2;
                    float y = document.bottom() - 15; // 设置底部
//                float y = document.top(); // 设置顶部

                    // 添加页码
                    cb.beginText();
                    cb.setTextMatrix(x, y);
                    cb.showTextAligned(Element.ALIGN_CENTER, pageNumber, x, y, 0);
                    cb.endText();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


}

实体类,水点字,就不写在同一个类下了

public class Staff {
    private String id;
    private String name;
    private String age;
    private String work;
    private String pic;

    // 无参构造方法
    public Staff() {
    }

    // 有参构造方法
    public Staff(String id, String name, String age, String work, String pic) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.work = work;
        this.pic = pic;
    }

    // Getter 和 Setter 方法
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getWork() {
        return work;
    }

    public void setWork(String work) {
        this.work = work;
    }

    public String getPic() {
        return pic;
    }

    public void setPic(String pic) {
        this.pic = pic;
    }

    // 重写 toString 方法
    @Override
    public String toString() {
        return "Study{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", age='" + age + '\'' +
                ", work='" + work + '\'' +
                ", pic='" + pic + '\'' +
                '}';
    }
}
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐