1、前言(瞎扯)
事情是这样子的:我好哥们所在的公司实在是太忙了,多线程都开起来了还是忙不完,只能开了子线程叫我帮他整一个给 Word 加水印的,于是我就网上找呗~
看到那个Aspose
好像是收费的,然后就把目光转向了 POI,看到各种形形色色的也不知道哪个能用。整了一会,自己拷贝出一个比较精简的能用的 demo 了。
2、人狠话不多,上效果图
我一般都是直接上图的,先看效果图(每一页都有的):
只是好像右上角分布比较少,只能小伙伴们自行研究调整了~
3、人狠话不多,直接来代码
3.1、我的代码结构
3.2 、直接贴代码了
pom 依赖的版本不要改,修改会导致一些东西缺失
3.2.1、pom 依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<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、处理工具类
package com.lyk.finger.util;
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 java.io.*;
import java.util.stream.Stream;
public class WatermarkUtil {
// word字体
private static final String fontName = "宋体";
// 字体大小
private static final String fontSize = "0.2pt";
// 字体颜色
private static final String fontColor = "#d0d0d0";
// 一个字平均长度,单位pt,用于:计算文本占用的长度(文本总个数*单字长度)
private static final Integer widthPerWord = 10;
// 与顶部的间距
private static Integer styleTop = 0;
// 文本旋转角度
private static final String styleRotation = "45";
/**
* @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++) {
styleTop = 100 * lineIndex;
waterMarkDocXDocument(doc, fingerText);
}
// 输出
doc.write(out);
System.out.println("生成成功!,一共耗时" + (System.currentTimeMillis() - beginTime) + "毫秒");
} catch (IOException e) {
throw new RuntimeException(e);
} catch (InvalidFormatException e) {
throw new RuntimeException(e);
}
}
/**
* 为文档添加水印
* @param doc 需要被处理的docx文档对象
* @param customText 需要添加的水印文字
*/
public static void waterMarkDocXDocument(XWPFDocument doc, String customText) {
// 水印文字之间使用8个空格分隔
customText = customText + repeatString(" ", 8);
// 一行水印重复水印文字次数
customText = repeatString(customText, 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(customText));
shape.setFillcolor(fontColor);
// 字体设置为实心
shape.setStroked(STTrueFalse.FALSE);
// 绘制文本的路径
CTTextPath shapeTextPath = shape.addNewTextpath();
// 设置文本字体与大小
shapeTextPath.setStyle("font-family:" + fontName + ";font-size:" + fontSize);
shapeTextPath.setString(customText);
CTPicture pict = ctr.addNewPict();
pict.set(group);
}
/**
* 构建Shape的样式参数
*
* @param customText
* @return
*/
private static String getShapeStyle(String customText) {
StringBuilder sb = new StringBuilder();
// 文本path绘制的定位方式
sb.append("position: ").append("absolute");
// 计算文本占用的长度(文本总个数*单字长度)
sb.append(";width: ").append(customText.length() * widthPerWord).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(styleTop);
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(styleRotation);
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、OJBK 完事~
拿去好好享用吧~