poi word 添加水印

poi word 添加水印

依赖

        <!--poi-tl-->
        <dependency>
            <groupId>com.deepoove</groupId>
            <artifactId>poi-tl</artifactId>
            <version>1.12.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.2</version>
        </dependency>

DocxUtil

import com.microsoft.schemas.office.office.CTLock;
import com.microsoft.schemas.vml.*;
import org.apache.poi.wp.usermodel.HeaderFooterType;
import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFHeader;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

import java.io.InputStream;
import java.util.stream.Stream;

import static org.openxmlformats.schemas.officeDocument.x2006.sharedTypes.STTrueFalse.FALSE;
import static org.openxmlformats.schemas.officeDocument.x2006.sharedTypes.STTrueFalse.T;

/**
 * @author wangshuai9776
 * @date 2024/8/8 13:37
 */
public class DocxUtil {

//	public final static String DEFAULT_WATERMARK = "XXX科技有限公司"; // 后续按 系统设置获取
	public final static String DEFAULT_FONT_COLOR = "#d8d8d8";

	// 字体大小
	public static final String FONT_SIZE = "0.5pt";
	// 文本旋转角度
	public static final String STYLE_ROTATION = "-45";
	/**
	 * 水印参数
	 */
	// private static final String fontColor = "#D3D3D3"; // 字体颜色

	/**
	 * 艺术字水印参数
	 */
	private static final String fontName = "Microsoft YaHei"; // word字体
	// private static final String fontSize = "0.5pt"; // 字体大小
	private static final int widthPerWord = 10; // 一个字平均长度,单位pt,用于:计算文本占用的长度(文本总个数*单字长度)
	// private static final String styleRotation = "-45"; // 文本旋转角度
	 private static final String SHAPE_TYPE = "#_x0000_t136"; // 形状类型:多边形
	 private static final String SHAPE_SPID = "_x0000_s102";


	/**
	 * word文字水印(调用poi封装的createWatermark方法)
	 * @param doc XWPFDocument对象
	 * @param markStr 水印文字
	 */
	public static void setWordWaterMark(XWPFDocument doc, String markStr,String fontColor) {
		XWPFParagraph paragraph = doc.createParagraph();
		XWPFHeaderFooterPolicy headerFooterPolicy = doc.getHeaderFooterPolicy();
		if (headerFooterPolicy == null) {
			headerFooterPolicy = doc.createHeaderFooterPolicy();
		}
		// create default Watermark - fill color black and not rotated
		headerFooterPolicy.createWatermark(markStr);
		// get the default header
		// Note: createWatermark also sets FIRST and EVEN headers
		// but this code does not updating those other headers
		XWPFHeader header = headerFooterPolicy.getHeader(XWPFHeaderFooterPolicy.DEFAULT);
		paragraph = header.getParagraphArray(0);
//            // get com.microsoft.schemas.vml.CTShape where fill color and rotation is set
		paragraph.getCTP().newCursor();
		org.apache.xmlbeans.XmlObject[] xmlobjects = paragraph.getCTP().getRArray(0).getPictArray(0).selectChildren(
				new javax.xml.namespace.QName("urn:schemas-microsoft-com:vml", "shape"));
		if (xmlobjects.length > 0) {
			CTShape ctshape = (CTShape) xmlobjects[0];
			ctshape.setFillcolor(fontColor);
			ctshape.setStyle(ctshape.getStyle() + ";rotation:315");
		}
	}

	/**
	 * 以艺术字方式加上水印(平铺)
	 * @param docx XWPFDocument对象
	 * @param customText 水印文字
	 */
	public static void makeFullWaterMarkByWordArt(XWPFDocument docx, String customText,String fontColor,String fontSize,String styleRotation) {
		customText = customText + repeatString(" ", 16); // 水印文字之间使用8个空格分隔
		customText = repeatString(customText, 3); // 一行水印重复水印文字次数 //重复次数不能太多,内容过长会导致字体展示不出来。
		String styleTop = "0pt";  // 与顶部的间距

		if (docx == null) {
			return;
		}
		// 遍历文档,添加水印
		for (int lineIndex = -10; lineIndex < 20; lineIndex++) {
			styleTop = 200 * lineIndex + "pt";
			waterMarkDocXDocument(docx, customText, styleTop, 1,fontColor, fontSize ,styleRotation);
		}
	}

	/**
	 * 以艺术字方式加上水印(单个)
	 * @param docx XWPFDocument对象
	 * @param customText 水印文字
	 */
	public static void makeWaterMarkByWordArt(XWPFDocument docx, String customText,String fontColor,String fontSize,String rotation) {
		String styleTop = "0pt";  // 与顶部的间距

		if (docx == null) {
			return;
		}
		// 添加水印
		waterMarkDocXDocument(docx, customText, styleTop, 2,fontColor,fontSize,rotation);
	}

	/**
	 * 将指定的字符串重复repeats次.
	 * @param pattern 字符串
	 * @param repeats 重复次数
	 * @return 生成的字符串
	 */
	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);
	}

	/**
	 * 为文档添加水印
	 * 实现参考了{@link XWPFHeaderFooterPolicy#(String, int)}
	 * @param doc 需要被处理的docx文档对象
	 * @param customText 水印文本
	 * @param type 类型:1.平铺;2.单个
	 */
	private static void waterMarkDocXDocument(XWPFDocument doc, String customText, String styleTop, int type,String fontColor,String fontSize,String rotation) {
		XWPFHeader header = doc.createHeader(HeaderFooterType.DEFAULT); // 如果之前已经创建过 DEFAULT 的Header,将会复用之
		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");
		for (int i = 0; i < 3; i++) {
			// 开始加水印
			CTR ctr = ctp.addNewR();
			CTRPr ctrpr = ctr.addNewRPr();
			ctrpr.addNewNoProof();
			CTGroup group = CTGroup.Factory.newInstance();
			CTShape shape = group.addNewShape();
			shape.setSpid(SHAPE_SPID);
			shape.setType(SHAPE_TYPE);
			if(type != 2){
				shape.setStyle(getShapeStyle(customText, styleTop,rotation)); // 设置形状样式(旋转,位置,相对路径等参数)
			}else{
				shape.setStyle(getShapeStyle()); // 设置形状样式(旋转,位置,相对路径等参数)
			}
			shape.setFillcolor(fontColor);
			shape.setStroked(FALSE); // 字体设置为实心

			CTLock lock = shape.addNewLock();
			lock.setExt(STExt.VIEW);

			CTTextPath shapeTextPath = shape.addNewTextpath(); // 绘制文本的路径
			shapeTextPath.setOn(T);
			shapeTextPath.setFitshape(T);
			shapeTextPath.setStyle("font-family:" + fontName + ";font-size:" + fontSize); // 设置文本字体与大小
			shapeTextPath.setString(customText+i);
			CTPicture pict = ctr.addNewPict();
			pict.set(group);
		}
	}

	/**
	 * 加载docx格式的word文档
	 * @param inputStream
	 * @return
	 */
	private static XWPFDocument loadDocXDocument(InputStream inputStream) {
		XWPFDocument doc;
		try {
			doc = new XWPFDocument(inputStream);
		} catch (Exception e) {
			throw new RuntimeException("文档加载失败!!");
		}
		return doc;
	}

	/**
	 * 构建Shape的样式参数
	 * @param customText 水印文本
	 * @return
	 */
	private static String getShapeStyle(String customText, String styleTop,String styleRotation) {
		StringBuilder sb = new StringBuilder();
		sb.append("position: ").append("absolute"); // 文本path绘制的定位方式
		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");
		sb.append(";margin-top: ").append(styleTop);
		sb.append(";mso-position-horizontal-relative: ").append("margin");
		sb.append(";mso-position-horizontal: ").append("center");
		sb.append(";mso-position-vertical-relative: ").append("margin");
		sb.append(";mso-position-vertical: ").append("left");
		sb.append(";rotation: ").append(styleRotation);
		return sb.toString();
	}

	/**
	 * 构建Shape的样式参数
	 * @return
	 */
	private static String getShapeStyle() {
		StringBuilder sb = new StringBuilder();
		sb.append("position: ").append("absolute"); // 文本path绘制的定位方式
		sb.append(";left: ").append("opt");
		sb.append(";width: ").append("500pt"); // 计算文本占用的长度(文本总个数*单字长度)
		sb.append(";height: ").append("150pt"); // 字体高度
		sb.append(";z-index: ").append("-251654144");
		sb.append(";mso-wrap-edited: ").append("f");
		sb.append(";margin-left: ").append("-50pt");
		sb.append(";margin-top: ").append("270pt");
		sb.append(";mso-position-horizontal-relative: ").append("margin");
		sb.append(";mso-position-vertical-relative: ").append("margin");
		sb.append(";mso-width-relative: ").append("page");
		sb.append(";mso-height-relative: ").append("page");
		sb.append(";rotation: ").append("-2949120f");
		return sb.toString();
	}

}

调用

DocxUtil.makeFullWaterMarkByWordArt(doc, watermark, DocxUtil.DEFAULT_FONT_COLOR, DocxUtil.FONT_SIZE, DocxUtil.STYLE_ROTATION);

遇到的问题

部分客户给的word无法添加水印

  • 在选取模式下 有水印
  • 编辑模式下就看不见水印了

水印文案 过长会导致字变小变形 超过一定长度就会显示异常。消失等情况

相关推荐
星河梦瑾37 分钟前
SpringBoot相关漏洞学习资料
java·经验分享·spring boot·安全
黄名富40 分钟前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
love静思冥想42 分钟前
JMeter 使用详解
java·jmeter
言、雲1 小时前
从tryLock()源码来出发,解析Redisson的重试机制和看门狗机制
java·开发语言·数据库
TT哇1 小时前
【数据结构练习题】链表与LinkedList
java·数据结构·链表
Yvemil71 小时前
《开启微服务之旅:Spring Boot 从入门到实践》(三)
java
Anna。。1 小时前
Java入门2-idea 第五章:IO流(java.io包中)
java·开发语言·intellij-idea
.生产的驴2 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
爱上语文2 小时前
宠物管理系统:Dao层
java·开发语言·宠物
王ASC2 小时前
SpringMVC的URL组成,以及URI中对/斜杠的处理,解决IllegalStateException: Ambiguous mapping
java·mvc·springboot·web