[Java 17] 无模版动态生成 PDF:图片嵌入与动态表格渲染实战

实现效果

在这里插入图片描述

引言

在企业开发中,动态生成 PDF(如出库单、发票)是常见需求。传统方法依赖模板,灵活性不足。本文将展示如何使用 Java 17 结合 iTextPDF 和 ZXing 库,实现在无模版情况下动态生成 PDF,嵌入 logo、渲染动态表格并添加二维码和水印。文章提供完整 demo 代码和开发经验分享。

技术要点

  1. 无模版生成:无需预设模板,纯代码构建。
  2. 图片嵌入:添加 logo 等图像。
  3. 动态表格渲染:根据数据动态生成多列表格。
  4. Java 17 环境:使用现代 Java 版本。
  5. 依赖管理:通过 Maven 配置 iTextPDF 和 ZXing。

实现步骤

1. 环境准备

使用 Java 17,依赖通过 Maven 管理。以下是 pom.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>untitled</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf.tool</groupId>
            <artifactId>xmlworker</artifactId>
            <version>5.5.13.3</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.3.0</version>
        </dependency>
    </dependencies>
</project>

运行 mvn clean install 安装依赖。

2. 代码实现

2.1 左上角添加 logo

添加 logo 图片到 PDF 左上角:

    private static void addLogo(Document document, PdfWriter writer) throws DocumentException, IOException {
        // 替换为实际 logo 图片路径,例如 "src/main/resources/logo.jpeg"
        Image logo = Image.getInstance("src/main/resources/logo.jpeg");
        logo.scaleToFit(100, 100); // 调整 logo 大小

        logo.setAlignment(Image.ALIGN_LEFT);
        document.add(logo); // 使用文档流插入

    }

在这里插入图片描述

	好吧,其实并不是这样的logo,往下慢慢看吧。
2.2 添加可见标题

在文档顶部添加居中标题:

    private static void addDocumentTitle(Document document) throws DocumentException, IOException {
        BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
        Font titleFont = new Font(bfChinese, 20, Font.BOLD);
        Paragraph title = new Paragraph("出库单", titleFont);
        title.setAlignment(Element.ALIGN_CENTER);
//        title.setSpacingBefore(5f);
//        title.setSpacingAfter(5f);
        document.add(title);
    }
2.3 基础单据信息

使用 4 列表格展示基础信息,动态从 API 数据填充:

// 添加基础单据信息 (4列布局,增加间距)
private static void addBasicInfo(Document document) throws DocumentException, IOException {
    PdfPTable table = new PdfPTable(4);
    table.setWidthPercentage(100);
    table.setSpacingBefore(150f);
    table.setSpacingAfter(50f);

    BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
    Font headerFont = new Font(bfChinese, 14, Font.BOLD);
    Font contentFont = new Font(bfChinese, 14);

    Map<String, String> apiData = getBaseInfoFromApi();

    List<String> keys = new ArrayList<>(fieldNameToLabelMap.keySet());
    for (int i = 0; i < keys.size(); i += 2) {
        String key1 = keys.get(i);
        String key2 = (i + 1 < keys.size()) ? keys.get(i + 1) : "";
        String label1 = fieldNameToLabelMap.get(key1);
        String value1 = apiData.getOrDefault(key1, "");
        String label2 = key2.isEmpty() ? "" : fieldNameToLabelMap.get(key2);
        String value2 = key2.isEmpty() ? "" : apiData.getOrDefault(key2, "");
        addInfoRow(table, label1, value1, label2, value2, headerFont, contentFont);
    }

    document.add(table);
}
    // 工具方法:添加单行 (4列)
private static void addInfoRow(PdfPTable table, String key1, String value1, String key2, String value2, Font keyFont, Font valueFont) {
    PdfPCell keyCell1 = new PdfPCell(new Phrase(key1, keyFont));
    keyCell1.s