Pdf和Docx文件导出生成水印工具类

Pdf文件导出生成斜体水印工具类方法

1、需要引入的jar包
java 复制代码
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox</artifactId>
            <version>2.0.28</version>
        </dependency>
2、具体工具类(添加完水印后,需要根据实际情况返回对应的流或者上传OSS后拿到对应的带水印的下载链接)
java 复制代码
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class PdfWaterMarkUtil {
    @Autowired
    private MessageSource messageSource;
    /**
     * 基础字体大小
     */
    private static final int FONT_SIZE = 15;
    /**
     * 水平间距
     */
    private static final float HORIZONTAL_SPACING = 100;
    /**
     * 垂直间距
     */
    private static final float VERTICAL_SPACING = 100;
    /**
     * 透明度 (0.1-0.3效果最佳)
     */
    private static final float OPACITY = 0.1f;
    /**
     * 旋转角度
     */
    private static final float ROTATION_ANGLE = 30;

    /**
     * @param downloadUrl 原下载地址(不带水印)
     * @param watermarkText     水印文本内容(暂不支持中文)
     * @param fileName 文件名称
     */
    public String addWaterMarker(String downloadUrl, String watermarkText, String fileName) {
        long beginTime = System.currentTimeMillis();
        try (PDDocument document = PDDocument.load(URI.create(downloadUrl).toURL().openStream());
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            // 加载字体
            PDType0Font font = PDType0Font.load(document,
                    PdfWaterMarkUtil.class.getResourceAsStream(
                            "/org/apache/pdfbox/resources/ttf/LiberationSans-Regular.ttf"
                    ));
            PDExtendedGraphicsState gs = new PDExtendedGraphicsState();
            gs.setNonStrokingAlphaConstant(OPACITY);
            // 遍历所有页面
            for (PDPage page : document.getPages()) {
                PDRectangle pageSize = page.getMediaBox();
                float pageWidth = pageSize.getWidth();
                float pageHeight = pageSize.getHeight();

                // 计算水印网格尺寸
                float watermarkWidth = estimateTextWidth(watermarkText, font, FONT_SIZE);
                float watermarkHeight = FONT_SIZE;

                // 计算网格行列数(添加间距)
                int cols = (int) (pageWidth / (watermarkWidth + HORIZONTAL_SPACING)) + 1;
                int rows = (int) (pageHeight / (watermarkHeight + VERTICAL_SPACING)) + 1;

                // 计算起始偏移量(居中显示)
                float xOffset = (pageWidth - (cols - 1) * (watermarkWidth + HORIZONTAL_SPACING)) / 2;
                float yOffset = (pageHeight - (rows - 1) * (watermarkHeight + VERTICAL_SPACING)) / 2;

                // 创建内容流
                try (PDPageContentStream contentStream = new PDPageContentStream(
                        document, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
                    // 设置透明度
                    contentStream.setGraphicsStateParameters(
                            new org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState());
                    // 浅灰色
                    contentStream.setNonStrokingColor(200/255.0F, 200/255.0F, 200/255.0F);
                    contentStream.setGraphicsStateParameters(gs);
                    // 绘制水印网格
                    for (int row = 0; row < rows; row++) {
                        for (int col = 0; col < cols; col++) {
                            float x = xOffset + col * (watermarkWidth + HORIZONTAL_SPACING);
                            float y = yOffset + row * (watermarkHeight + VERTICAL_SPACING);

                            // 设置旋转矩阵
                            Matrix matrix = Matrix.getRotateInstance(
                                    Math.toRadians(ROTATION_ANGLE), x, y);
                            contentStream.beginText();
                            contentStream.setTextMatrix(matrix);
                            contentStream.setFont(font, FONT_SIZE);
                            // 灰色
                            contentStream.setNonStrokingColor(128/255.0F, 128/255.0F, 128/255.0F);
                            contentStream.showText(watermarkText);
                            contentStream.endText();
                        }
                    }
                }
            }
//            document.save(outputPath);
            //获取带水印的输入流
            document.save(out);
            ByteArrayInputStream inputStream = new ByteArrayInputStream(out.toByteArray());
            //上传到oss,然后获取新的带水印的下载链接
            StandardFileService standardFileService = fileServices.get(MideaOssAutoConfiguration.DEFAULT_BEAN_NAME);
            String ossName;

            if (StringUtils.isNotBlank(fileName)) {
                ossName = fileName + ".pdf";
            } else {
                String ossId = UUID.randomUUID().toString().replaceAll("-", "");
                ossName = ossId + ".pdf";
            }
            standardFileService.upload(inputStream, ossName);
            String watermarkDownloadUrl = standardFileService.generatePresignedUrl(ossName, ossProperties.getBucketName(), 7200L,
                    Collections.singletonList(new MideaOssAmzUserMeta(UserUtils.getFdLoginName())));
            log.info("添加水印成功!一共耗时" + (System.currentTimeMillis() - beginTime) + "毫秒");
            return watermarkDownloadUrl;
        } catch (Exception e) {
            throw new BusinessException(ErrorCode.WATERMARK_DOWNLOAD_URL_GET_FAIL.getErrorCode(),
                    ErrorCode.WATERMARK_DOWNLOAD_URL_GET_FAIL.getErrorKey(),
                    ErrorCode.WATERMARK_DOWNLOAD_URL_GET_FAIL.getErrorMsg());
        }
    }

    private static float estimateTextWidth(String text, PDFont font, float fontSize) throws IOException {
        return font.getStringWidth(text) / 1000 * fontSize;
    }
}

Docx文件导出生成斜体水印工具类方法

1、需要引入的jar包
java 复制代码
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.3</version>
        </dependency>
2、具体工具类(添加完水印后,需要根据实际情况返回对应的流或者上传OSS后拿到对应的带水印的下载链接)
java 复制代码
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.microsoft.schemas.office.office.CTLock;
import com.microsoft.schemas.vml.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.wp.usermodel.HeaderFooterType;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFHeader;
import org.openxmlformats.schemas.officeDocument.x2006.sharedTypes.STTrueFalse;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPicture;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URI;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Stream;

@Slf4j
@Component
public class DocxWaterMarkUtil {
    /**
     * word字体
     */
    private static final String FONT_NAME = "宋体";
    /**
     * 字体大小
     */
    private static final String FONT_SIZE = "0.3pt";
    /**
     * 字体颜色
     */
    private static final String FONT_COLOR = "#C0C0C0";

    /**
     * 一个字平均长度,单位pt,用于:计算文本占用的长度(文本总个数*单字长度)
     */
    private static final Integer WIDTH_PER_WORD = 10;
    /**
     * 文本旋转角度
     */
    private static final String STYLE_ROTATION = "-30";
    /**
     * 水印透明度
     */
    private static final String OPACITY = "0.2";
    @Autowired
    private MessageSource messageSource;

    public String waterMarkDocXDocument(String downloadUrl, String watermarkText, String fileName) {

        long beginTime = System.currentTimeMillis();
        try (
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                InputStream in = URI.create(downloadUrl).toURL().openStream();
                OPCPackage srcPackage = OPCPackage.open(in);
                XWPFDocument doc = new XWPFDocument(srcPackage)
        ) {
            // 把整页都打上水印
            int i = 0;
            int marginTop;
            for (int lineIndex = -1; lineIndex < 6; lineIndex++) {
                marginTop = 300 * lineIndex;
                waterMarkDocXDocument(doc, watermarkText, i, marginTop);
                i++;
            }
            // 输出新文档
//            doc.write(out);
            //TODO:获取带水印文档的输出流
            doc.write(out);
            //转换为字节输入流
            ByteArrayInputStream inputStream = new ByteArrayInputStream(out.toByteArray());
            //上传到oss,然后获取新的带水印的下载链接
            StandardFileService standardFileService = fileServices.get(MideaOssAutoConfiguration.DEFAULT_BEAN_NAME);
            String ossName;

            if (StringUtils.isNotBlank(fileName)) {
                ossName = fileName + ".docx";
            } else {
                String ossId = UUID.randomUUID().toString().replaceAll("-", "");
                ossName = ossId + ".docx";
            }
            standardFileService.upload(inputStream, ossName);
            String watermarkDownloadUrl = standardFileService.generatePresignedUrl(ossName, ossProperties.getBucketName(), 7200L,
                    Collections.singletonList(new MideaOssAmzUserMeta(UserUtils.getFdLoginName())));
            log.info("添加水印成功!一共耗时" + (System.currentTimeMillis() - beginTime) + "毫秒");
            return watermarkDownloadUrl;
        } catch (Exception e) {
            throw new BusinessException(ErrorCode.WATERMARK_DOWNLOAD_URL_GET_FAIL.getErrorCode(),
                    ErrorCode.WATERMARK_DOWNLOAD_URL_GET_FAIL.getErrorKey(),
                    ErrorCode.WATERMARK_DOWNLOAD_URL_GET_FAIL.getErrorMsg());
        }
    }

    private static void waterMarkDocXDocument(XWPFDocument doc, String fingerText, int i, int marginTop) {
        // 水印文字之间使用8个空格分隔
        fingerText = fingerText + repeatString(" ", 8);
        // 一行水印重复水印文字次数
        fingerText = repeatString(fingerText, 5);
        // 如果之前已经创建过 DEFAULT 的Header,将会复用
        XWPFHeader header = doc.createHeader(HeaderFooterType.DEFAULT);
        int size = header.getParagraphs().size();
        if (size == 0) {
            header.createParagraph();
        }
        CTP ctp = header.getParagraphArray(0).getCTP();
        byte[] rsidr = doc.getDocument().getBody().getPArray(0).getRsidR();
        byte[] rsidrDefault = doc.getDocument().getBody().getPArray(0).getRsidRDefault();
        ctp.setRsidP(rsidr);
        ctp.setRsidRDefault(rsidrDefault);
        CTPPr ppr = ctp.addNewPPr();
        ppr.addNewPStyle().setVal("Header");
        // 开始加水印
        CTR ctr = ctp.addNewR();

        CTGroup group = CTGroup.Factory.newInstance();
        CTShapetype shapeType = group.addNewShapetype();
        CTTextPath shapeTypeTextPath = shapeType.addNewTextpath();
        shapeTypeTextPath.setOn(STTrueFalse.T);
        shapeTypeTextPath.setFitshape(STTrueFalse.T);

        CTLock lock = shapeType.addNewLock();
        lock.setExt(STExt.EDIT);

        CTShape shape = group.addNewShape();
        shape.setId("PowerPlusWaterMarkObject" + (i + 1));
        shape.setSpid("_x0000_s206" + (i + 1));
        shape.setSpt(136f);
        shape.setType("#_x0000_t136");
        // 设置形状样式(旋转,位置,相对路径等参数)
        shape.setStyle(getShapeStyle(fingerText, i, marginTop));
        shape.setFillcolor(FONT_COLOR);
        shape.setFilled(STTrueFalse.T);
        shape.setCoordsize("21600,21600");
        shape.setAdj("10800");
        // 字体设置为实心
        shape.setStroked(STTrueFalse.FALSE);

        // 绘制文本的路径
        // 设置文本字体与大小
        CTTextPath shapeTextPath = shape.addNewTextpath();
        shapeTextPath.setStyle("font-family:" + FONT_NAME + ";font-size:" + FONT_SIZE);
        shapeTextPath.setString(fingerText);
        //透明度填充
        CTFill ctFill = shape.addNewFill();
        ctFill.setOpacity(OPACITY);

        CTPicture pict = ctr.addNewPict();
        pict.set(group);
    }

    private static String getShapeStyle(String fingerText, int i, int marginTop) {
        StringBuilder sb = new StringBuilder();
        // 文本path绘制的定位方式
        sb.append("position: ").append("absolute");
        // 计算文本占用的长度(文本总个数*单字长度)
        sb.append(";width: ").append(fingerText.length() * WIDTH_PER_WORD).append("pt");
        // 字体高度
        sb.append(";height: ").append("20pt");
        sb.append(";z-index: ").append(-251645952 + i);
        sb.append(";mso-wrap-edited: ").append("f");
        // 设置水印的间隔,这是一个大坑,不能用top,必须要margin-top。
        sb.append(";margin-top: ").append(marginTop);
        sb.append(";mso-position-horizontal-relative: ").append("page");
        sb.append(";mso-position-vertical-relative: ").append("page");
        sb.append(";mso-position-vertical: ").append("left");
        sb.append(";mso-position-horizontal: ").append("center");
        sb.append(";rotation: ").append(STYLE_ROTATION);
        return sb.toString();
    }

    private static String repeatString(String pattern, int repeats) {
        StringBuilder buffer = new StringBuilder(pattern.length() * repeats);
        Stream.generate(() -> pattern).limit(repeats).forEach(buffer::append);
        return new String(buffer);
    }
}

以上代码复制遇到爆红属于正常情况,需要自己根据实际情况调整为自己的业务代码。

相关推荐
这个需求做不了18 小时前
Java实现文件格式转换(图片,视频,文档,音频)
java
愿你天黑有灯下雨有伞18 小时前
高性能Java并发编程:如何优雅地使用CompletableFuture进行异步编排
java
indexsunny18 小时前
互联网大厂Java面试实战:基于电商场景的Spring Boot与微服务技术问答
java·spring boot·微服务·面试·hibernate·电商场景·技术问答
qq_124987075318 小时前
基于Spring Boot的电影票网上购票系统的设计与实现(源码+论文+部署+安装)
java·大数据·spring boot·后端·spring·毕业设计·计算机毕业设计
无心水18 小时前
【分布式利器:腾讯TSF】6、TSF可观测性体系建设实战:Java全链路Metrics+Tracing+Logging落地
java·分布式·架构·wpf·分布式利器·腾讯tsf·分布式利器:腾讯tsf
小鸡脚来咯18 小时前
Java字符串详解
java·开发语言
麦兜*18 小时前
【Spring Boot】 接口性能优化“十板斧”:从数据库连接到 JVM 调优的全链路提升
java·大数据·数据库·spring boot·后端·spring cloud·性能优化
廋到被风吹走18 小时前
【Spring 】Spring Security深度解析:过滤器链、认证授权架构与现代集成方案
java·spring·架构
蛐蛐蜉蝣耶18 小时前
Spring Boot实现DynamicMethodMatcherPointcut示例
java·spring boot·后端