Java / Spring Boot + POI 给 Word 添加水印

落魄程序员在线炒粉 2024-07-30 13:05:03 阅读 76

1、前言(瞎扯)

有个需求:整一个给 Word 加水印的demo,于是我就网上找呗~

看到那个 <code>Aspose 好像是收费的,然后就把目光转向了 POI,看到各种形形色色的也不知道哪个能用。整了一会,自己拷贝出一个比较精简的能用的 demo 了。

2、人狠话不多,上效果图

我一般都是直接上图的,先看效果图(每一页都有的):

水印的分布如果不理想,只能小伙伴们自行研究调整了~

在这里插入图片描述

3、人狠话不多,直接来代码

3.1、我的代码结构

目录结构

3.2 、直接贴代码了

<code>pom 依赖的版本不要改,修改版本可能会导致一些东西缺失

代码你们可以直接复制这里的使用

或者在码云仓库:点击这里跳转

3.2.1、pom 依赖

<?xml version="1.0" encoding="UTF-8"?>code>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"code>

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">code>

<modelVersion>4.0.0</modelVersion>

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>3.2.2</version>

<relativePath/> <!-- lookup parent from repository -->

</parent>

<groupId>com.lyk</groupId>

<artifactId>springboot-word-finger</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>springboot-word-finger</name>

<description>springboot-word-finger</description>

<properties>

<java.version>17</java.version>

</properties>

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

<dependency>

<groupId>org.jsoup</groupId>

<artifactId>jsoup</artifactId>

<version>1.11.2</version>

</dependency>

<dependency>

<groupId>org.apache.poi</groupId>

<artifactId>poi</artifactId>

<version>4.1.0</version>

</dependency>

<dependency>

<groupId>org.apache.poi</groupId>

<artifactId>poi-scratchpad</artifactId>

<version>4.1.0</version>

</dependency>

<dependency>

<groupId>org.apache.poi</groupId>

<artifactId>poi-ooxml</artifactId>

<version>4.1.0</version>

</dependency>

<dependency>

<groupId>org.apache.poi</groupId>

<artifactId>poi-ooxml-schemas</artifactId>

<version>4.1.0</version>

</dependency>

</dependencies>

<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>

</plugins>

</build>

</project>

3.2.2、处理工具类

import com.microsoft.schemas.office.office.CTLock;

import com.microsoft.schemas.vml.*;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;

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.wordprocessingml.x2006.main.*;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import java.io.*;

import java.util.stream.Stream;

/**

* @author: lyk

* @description: Word 添加水印工具类

**/

public class WatermarkUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(WatermarkUtil.class);

/** word字体 */

private static final String FONT_NAME = "宋体";

/** 字体大小 */

private static final String FONT_SIZE = "0.2pt";

/** 字体颜色 */

private static final String FONT_COLOR = "#d0d0d0";

/** 一个字平均长度,单位pt,用于:计算文本占用的长度(文本总个数*单字长度)*/

private static final Integer WIDTH_PER_WORD = 10;

/** 与顶部的间距 */

private static Integer STYLE_TOP = 0;

/** 文本旋转角度 */

private static final String STYLE_ROTATION = "30";

/**

* @param inPutPath

* @param putPutPath

* @param fingerText

* @author: lyk

* @description: 添加水印入口方法

* @date: 2024/1/25 23:42

**/

public static void waterMarkDocXDocument(String inPutPath, String putPutPath, String fingerText) {

long beginTime = System.currentTimeMillis();

try (

OutputStream out = new FileOutputStream(putPutPath);

InputStream in = new FileInputStream(inPutPath);

OPCPackage srcPackage = OPCPackage.open(in);

XWPFDocument doc = new XWPFDocument(srcPackage)

) {

// 把整页都打上水印

for (int lineIndex = -5; lineIndex < 20; lineIndex++) {

STYLE_TOP = 100 * lineIndex;

waterMarkDocXDocument(doc, fingerText);

}

// 输出新文档

doc.write(out);

LOGGER.info("添加水印成功!,一共耗时" + (System.currentTimeMillis() - beginTime) + "毫秒");

} catch (IOException e) {

throw new RuntimeException(e);

} catch (InvalidFormatException e) {

throw new RuntimeException(e);

}

}

/**

* 为文档添加水印

* @param doc 需要被处理的docx文档对象

* @param fingerText 需要添加的水印文字

*/

public static void waterMarkDocXDocument(XWPFDocument doc, String fingerText) {

// 水印文字之间使用8个空格分隔

fingerText = fingerText + repeatString(" ", 8);

// 一行水印重复水印文字次数

fingerText = repeatString(fingerText, 10);

// 如果之前已经创建过 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();

CTRPr ctrpr = ctr.addNewRPr();

ctrpr.addNewNoProof();

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.VIEW);

CTShape shape = group.addNewShape();

shape.setId("PowerPlusWaterMarkObject");

shape.setSpid("_x0000_s102");

shape.setType("#_x0000_t136");

// 设置形状样式(旋转,位置,相对路径等参数)

shape.setStyle(getShapeStyle(fingerText));

shape.setFillcolor(FONT_COLOR);

// 字体设置为实心

shape.setStroked(STTrueFalse.FALSE);

// 绘制文本的路径

CTTextPath shapeTextPath = shape.addNewTextpath();

// 设置文本字体与大小

shapeTextPath.setStyle("font-family:" + FONT_NAME + ";font-size:" + FONT_SIZE);

shapeTextPath.setString(fingerText);

CTPicture pict = ctr.addNewPict();

pict.set(group);

}

/**

* 构建Shape的样式参数

*

* @param fingerText

* @return

*/

private static String getShapeStyle(String fingerText) {

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("-251654144");

sb.append(";mso-wrap-edited: ").append("f");

// 设置水印的间隔,这是一个大坑,不能用top,必须要margin-top。

sb.append(";margin-top: ").append(STYLE_TOP);

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();

}

/**

* 将指定的字符串重复repeats次.

*/

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);

}

}

/**

* @author lyk

* @version 1.0

* @date 2024/1/25 23:16

* @description

*/

public class Main {

public static void main(String[] args) {

final String inPath = "src/main/java/com/lyk/finger/doc/aaaa.docx";

final String outPath = "src/main/java/com/lyk/finger/doc/out.docx";

// 添加水印

WatermarkUtil.waterMarkDocXDocument(inPath, outPath, "落魄程序员在线炒粉");

}

}

4、OK 完事~

拿去好好享用吧~

注意:大文档不确定是否有效,我没有大文档测试,如果那种几百页的文档请自行查看,据网友反馈大文档后面的生成不了水印(200页之后的)



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。