Java实现生成文档并加盖公章和水印

92 阅读5分钟

java生成带水印和公章的文档

一、使用场景

需要在线生成合同文件,并加盖公章和水印,如 第三方服务平台提供服务时,给自动生成合同文件等。

本文涉及了 HTML、PDF、PNG的生成,在日常合同使用类型中基本都包括了。废话不多说,上代码

二、添加依赖

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>kernel</artifactId>
    <version>7.2.5</version>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>io</artifactId>
    <version>7.2.5</version>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>layout</artifactId>
    <version>7.2.5</version>
</dependency>

三、代码

package cn.com.juejin.utils;

import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Image;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.properties.TextAlignment;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Base64;

/**
 * 文档工具类,用于生成指定格式文档
 * */
public class DocUtil {

    //水印文字
    private static final String WATER_MARK = "掘金社区屌丝俱乐部";
    //公章图片,todo:需要先准备好
    private static final String sealPath = "src/main/resources/scripts/seal.png";
    //默认文档名称
    private static final String DEFAULT_DOC_NAME = "合同文件";
    //临时文件路径,可用于本地下载或者上传到oss
    private static final String TEMP_FILE_PATH = "src/main/resources/scripts/";
    //字体(宋体)
    private static final String FONT_PATH = "src/main/resources/font/simsun.ttc,0";



    /**
     * 创建带水印和公章的合同文件,采用默认文件名
     *
     * @param docType 文档类型(1:PDF,2:HTML, 3:PNG)
     * @param content 文档内容
     *
     * @return 临时文件全路径,
     *         根据实际情况可将该文件上传至oss或者返回给调用者
     */
    public static String createDoc(int docType, String content){
        return createDoc(docType, content, DEFAULT_DOC_NAME);
    }
    /**
     * 创建带水印和公章的合同文件
     *
     * @param docType 文档类型(1:PDF,2:HTML, 3:PNG)
     * @param content 文档内容
     * @param docName 文档名称,不带后缀 (可选) 默认值:合同文件
     *
     * @return 临时文件全路径,
     *         根据实际情况可将该文件上传至oss或者返回给调用者
     */
    public static String createDoc(int docType, String content, String docName){
        String fileName = "";
       if (docType == 1){
           fileName = TEMP_FILE_PATH+docName+".pdf";
           try (FileOutputStream fos = new FileOutputStream(fileName)) {
               DocUtil.generatePdfWithSeal(content, fos, sealPath, WATER_MARK);
               System.out.println("PDF文档生成成功!");
           } catch (IOException e) {
               System.err.println("生成PDF文档失败: " + e.getMessage());
           }
       }

       if (docType == 2){
           fileName = TEMP_FILE_PATH+docName+".html";
           //方式一:通过公章图片路径生成
//           generateHTMLWithSeal(content, sealPath, null, WATER_MARK, fileName);

           //方式二:通过公章图片Base64编码生成
           String sealBase64 = encodeImageToBase64(sealPath);
           generateHTMLWithSeal(content, null, sealBase64, WATER_MARK, fileName);
       }
       if (docType == 3){
           try {
               fileName = TEMP_FILE_PATH+docName+".png";
               generateImageDocWithSeal(content, sealPath, fileName, WATER_MARK);
           }catch (Exception e){
               System.err.println("生成图片文档失败: " + e.getMessage());
           }
       }
       return  fileName;
    }
    private static String encodeImageToBase64(String imagePath) {
        try {
            File imageFile = new File(imagePath);
            byte[] imageData = java.nio.file.Files.readAllBytes(imageFile.toPath());
            return Base64.getEncoder().encodeToString(imageData);
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 生成带公章的HTML文档
     * @param content 文档内容
     * @param sealPath 公章图片路径(可选)
     * @param sealBase64 公章图片Base64编码(可选)
     *                     sealBase64 和 sealPath 不能同时为空
     *                   如果水印文件为本地文件,可以采用base64编码
     *                   如果是云端文件,公网可访问,则可以用sealPath
     * @param waterMarkText 水印文字
     * @param fileName 带路径的文件名
     */
    private static void generateHTMLWithSeal(String content, String sealPath, String sealBase64, String waterMarkText, String fileName) {
        StringBuilder html = new StringBuilder();
        html.append("<html><head><meta charset='UTF-8'><title>Document</title></head><body>");
        // 添加水印样式
        if (waterMarkText != null && !waterMarkText.isEmpty()) {
            html.append("<style>");
            html.append(".watermark {");
            html.append("position: fixed;");
            html.append("top: 50%;");
            html.append("left: 50%;");
            html.append("transform: translate(-50%, -50%) rotate(-45deg);");
            html.append("font-size: 50px;");
            html.append("color: rgba(0, 0, 0, 0.1);");
            html.append("pointer-events: none;");
            html.append("z-index: 1000;");
            html.append("}");
            html.append("</style>");
        }

        html.append("</head><body>");

        // 添加水印
        if (waterMarkText != null && !waterMarkText.isEmpty()) {
            html.append("<div class='watermark'>").append(waterMarkText).append("</div>");
        }

        html.append("<div style='position: relative; min-height: 800px;'>");
        content = content.replace("\\n", "\n");
        html.append("<div>").append(content).append("</div>");

        // 添加公章
        if (sealPath != null && !sealPath.isEmpty()) {
            html.append("<div style='position: absolute; bottom: 100px; right: 100px;'>");
            html.append("<img src='").append(sealPath).append("' style='width: 150px; height: 150px; opacity: 0.8;' />");
            html.append("</div>");
        } else if (sealBase64 != null && !sealBase64.isEmpty()) {
            html.append("<div style='position: absolute; bottom: 100px; right: 100px;'>");
            html.append("<img src='data:image/png;base64,").append(sealBase64).append("' style='width: 150px; height: 150px; opacity: 0.8;' />");
            html.append("</div>");
        }

        html.append("</div></body></html>");
        try (FileOutputStream fos = new FileOutputStream(fileName)) {
            fos.write(html.toString().getBytes());
            fos.flush();
            fos.close();
            System.out.println("PDF文档生成成功!");
        } catch (IOException e) {
            System.err.println("生成PDF文档失败: " + e.getMessage());
        }
    }

    /**
     * 生成带公章的PDF文档(需要额外的PDF库,如iText)
     * @param content 文档内容
     * @param outputStream 输出流
     * @param sealPath 公章图片路径
     * @param waterMarkText 水印文字
     */
    private static void generatePdfWithSeal(String content, OutputStream outputStream, String sealPath, String waterMarkText) throws IOException {
        try {
            // 创建PDF文档
            PdfWriter writer = new PdfWriter(outputStream);
            PdfDocument pdfDoc = new PdfDocument(writer);
            Document document = new Document(pdfDoc);
            PdfFont font =  PdfFontFactory.createFont(FONT_PATH, PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);
            // 添加文档内容
            if (content != null && !content.isEmpty()) {
                // 处理字面量换行符
                content = content.replace("\\n", "\n");
                // 处理HTML标签,如果内容是HTML格式
//                String plainText = content.replaceAll("<[^>]*>", ""); // 简单去除HTML标签
                Paragraph paragraph = new Paragraph(content)
                        .setFont(font)
                        .setFontSize(12)
                        .setTextAlignment(TextAlignment.LEFT)
                        .setMargin(20);
                document.add(paragraph);
            }

            // 添加水印(在内容添加之后处理)
            if (waterMarkText != null && !waterMarkText.isEmpty()) {
                // 遍历所有页面添加水印
                for (int i = 1; i <= pdfDoc.getNumberOfPages(); i++) {
                    PdfPage page = pdfDoc.getPage(i);
                    Rectangle pageSize = page.getPageSize();

                    // 创建水印文本
                    Paragraph watermark = new Paragraph(waterMarkText)
                            .setFontColor(com.itextpdf.kernel.colors.ColorConstants.LIGHT_GRAY)
                            .setFont(font)
                            .setFontSize(60)
                            .setOpacity(0.3f);

                    // 添加水印到页面背景(使用canvas直接绘制)
                    com.itextpdf.kernel.pdf.canvas.PdfCanvas canvas = new com.itextpdf.kernel.pdf.canvas.PdfCanvas(page.newContentStreamBefore(), page.getResources(), pdfDoc);
                    com.itextpdf.layout.Canvas canvasLayer = new com.itextpdf.layout.Canvas(canvas, pageSize);

                    canvasLayer.showTextAligned(watermark,
                            pageSize.getWidth() / 2,
                            pageSize.getHeight() / 2,
                            i,
                            TextAlignment.CENTER,
                            com.itextpdf.layout.properties.VerticalAlignment.MIDDLE,
                            45); // 旋转45度

                    canvasLayer.close();
                }
            }

            // 如果提供了公章路径,则添加公章
            if (sealPath != null && !sealPath.isEmpty()) {
                try {
                    // 读取公章图片
                    ImageData imageData = ImageDataFactory.create(sealPath);
                    Image sealImage = new Image(imageData);

                    // 设置公章大小和位置
                    sealImage.setWidth(100);
                    sealImage.setHeight(100);

                    // 将公章定位在页面右下角
                    PdfPage lastPage = pdfDoc.getLastPage();
                    Rectangle pageSize = lastPage.getPageSize();
                    int number = pdfDoc.getPageNumber(lastPage);
                    // 设置绝对定位
                    sealImage.setFixedPosition(number,
                            pageSize.getWidth() - 120,  // 距离右边120单位
                            100  // 距离底部100单位
                    );

                    // 设置透明度
                    sealImage.setOpacity(0.8f);

                    document.add(sealImage);
                } catch (Exception e) {
                    // 如果公章加载失败,仅记录日志但不中断文档生成
                    System.err.println("警告:无法加载公章图片: " + e.getMessage());
                }
            }

            // 关闭文档
            document.close();
        } catch (Exception e) {
            throw new IOException("生成PDF文档时发生错误", e);
        }
    }

    /**
     * 生成带水印公章的图片文档
     * @param content 文档内容
     * @param sealPath 公章图片路径
     * @param outputPath 输出图片路径
     * @param waterMarkText 水印文字
     */
    private static void generateImageDocWithSeal(String content, String sealPath, String outputPath, String waterMarkText) throws Exception {
        // 创建一个BufferedImage用于绘制文档
        BufferedImage image = new BufferedImage(800, 1000, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = image.createGraphics();

        // 设置抗锯齿
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // 绘制白色背景
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, 800, 1000);

        // 添加水印
        if (waterMarkText != null && !waterMarkText.isEmpty()) {
            g2d.setColor(new Color(0, 0, 0, 30)); // 半透明黑色
            Font watermarkFont = new Font("宋体", Font.BOLD, 40);
            g2d.setFont(watermarkFont);

            // 设置旋转
            FontMetrics fm = g2d.getFontMetrics();
            int watermarkWidth = fm.stringWidth(waterMarkText);

            // 保存当前变换
            AffineTransform old = g2d.getTransform();

            // 设置旋转中心为图片中心
            g2d.rotate(Math.toRadians(-45), 400, 500);
            g2d.drawString(waterMarkText, 400 - watermarkWidth/2, 500);

            // 恢复变换
            g2d.setTransform(old);
        }

        // 绘制文本内容
        g2d.setColor(Color.BLACK);
        Font font = new Font("宋体", Font.PLAIN, 16);
        g2d.setFont(font);
        content = content.replace("\\n", "\n");
        // 简单的文本绘制(实际应用中需要更复杂的文本布局)
        String[] lines = content.split("\n");
        int y = 50;
        for (String line : lines) {
            g2d.drawString(line, 50, y);
            y += 25;
        }

        // 绘制公章
        if (sealPath != null && !sealPath.isEmpty()) {
            try {
                BufferedImage sealImage = ImageIO.read(new File(sealPath));
                if (sealImage != null) {
                    // 使用 AffineTransform 实现高质量缩放
                    BufferedImage scaledSeal = new BufferedImage(150, 150, BufferedImage.TYPE_INT_ARGB);
                    Graphics2D g2dSeal = scaledSeal.createGraphics();
                    g2dSeal.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                    g2dSeal.drawImage(sealImage, 0, 0, 150, 150, null);
                    g2dSeal.dispose();

                    // 绘制到主图中
                    g2d.drawImage(scaledSeal, 600, 800, null);
                }
            } catch (IOException e) {
                System.err.println("生成图片文档失败: " + e.getMessage());
            }
        }
        g2d.dispose();

        // 保存图片
        ImageIO.write(image, "PNG", new File(outputPath));
        System.out.println("图片文档生成成功!");
    }

    public static void main(String[] args) throws Exception {
        String content = "这是文档内容...\\n包含多行文本。\\n这是文档的最后一行。";
        createDoc(1,content);
        createDoc(2,content);
        createDoc(3,content);
    }
}