package com.shineyue.util.sfw;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.wp.usermodel.HeaderFooterType;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import javax.xml.namespace.QName;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.math.BigInteger;
@Slf4j
public class DocxWatermarkUtil {
public static void addImageWatermark(File inputFile, File outputFile, String text, float rotation, String color)
throws Exception {
try (XWPFDocument doc = new XWPFDocument(new FileInputStream(inputFile))) {
String hz = text.substring(0, text.lastIndexOf(" ")).replaceAll(" ", "");
// 根据文字生成图片
BufferedImage image = WatermarkUtils.generateRotatedTextImage(text, Math.round(725 / (0.71F * hz.length() + 4.225F)), rotation, color);
int imaHeight = image.getHeight();
int imaWidth = image.getWidth();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "PNG", baos);
byte[] imageBytes = baos.toByteArray();
// byte[] imageBytes = processImageTransparency(text, fontSize, rotation, color);
long[] pageSize = getPageSizeInTwips(doc);
long pageWidthTwips = pageSize[0];
long pageHeightTwips = pageSize[1];
addWatermarkToAllHeaders(doc, imageBytes, pageWidthTwips, pageHeightTwips, imaWidth, imaHeight);
try (FileOutputStream out = new FileOutputStream(outputFile)) {
doc.write(out);
} catch (Exception e) {
log.info("文件添加水印=========word加水印异常" + e.getMessage());
}
}
}
private static byte[] processImageTransparency(String text, int fontSize, float rotation, String color) throws IOException {
// 读取配置文件的图片
InputStream resourceAsStream = WatermarkUtils.class.getClassLoader().getResourceAsStream("test.png");
BufferedImage original = ImageIO.read(resourceAsStream);
BufferedImage transparent = new BufferedImage(
original.getWidth(), original.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = transparent.createGraphics();
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
g.drawImage(original, 0, 0, null);
g.dispose();
// 调试:保存生成的图片
File outputImage = new File("debug_watermark.png");
ImageIO.write(transparent, "PNG", outputImage);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(transparent, "PNG", baos);
return baos.toByteArray();
}
private static long[] getPageSizeInTwips(XWPFDocument doc) {
long defaultWidth = (long) (21.0 * 567);
long defaultHeight = (long) (29.7 * 567);
CTSectPr sectPr = doc.getDocument().getBody().getSectPr();
if (sectPr != null) {
XmlObject[] pgSzNodes = sectPr.selectPath(
"declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' .//w:pgSz"
);
if (pgSzNodes.length > 0) {
long width = getXmlAttribute(pgSzNodes[0], "w", defaultWidth);
long height = getXmlAttribute(pgSzNodes[0], "h", defaultHeight);
return new long[] { width, height };
}
}
return new long[] { defaultWidth, defaultHeight };
}
private static long getXmlAttribute(XmlObject xmlObj, String attrName, long defaultValue) {
try {
XmlCursor cursor = xmlObj.newCursor();
QName qName = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main", attrName);
String value = cursor.getAttributeText(qName);
return (value != null && !value.isEmpty()) ? Long.parseLong(value) : defaultValue;
} catch (NumberFormatException e) {
return defaultValue;
}
}
private static void addWatermarkToAllHeaders(XWPFDocument doc, byte[] imageData,
long pageWidthTwips, long pageHeightTwips, int imaWidth, int imaHeight)
throws Exception {
processHeader(doc, HeaderFooterType.DEFAULT, imageData, pageWidthTwips, pageHeightTwips, imaWidth, imaHeight);
processHeader(doc, HeaderFooterType.FIRST, imageData, pageWidthTwips, pageHeightTwips, imaWidth, imaHeight);
processHeader(doc, HeaderFooterType.EVEN, imageData, pageWidthTwips, pageHeightTwips, imaWidth, imaHeight);
}
private static void processHeader(XWPFDocument doc, HeaderFooterType type,
byte[] imageData, long pageWidthTwips, long pageHeightTwips, int imaWidth, int imaHeight)
throws Exception {
XWPFHeader header = doc.createHeader(type);
String imageId = header.addPictureData(imageData, XWPFDocument.PICTURE_TYPE_PNG);
System.out.println("处理页眉类型: " + type + ", 图片ID: " + imageId);
XWPFParagraph paragraph = header.createParagraph();
configureWatermarkParagraph(paragraph);
XWPFRun run = paragraph.createRun();
embedWatermarkImage(run, imageId, pageWidthTwips, pageHeightTwips, imaWidth, imaHeight);
}
private static void configureWatermarkParagraph(XWPFParagraph paragraph) {
paragraph.setAlignment(ParagraphAlignment.CENTER);
CTP ctp = paragraph.getCTP();
CTPPr ppr = ctp.isSetPPr() ? ctp.getPPr() : ctp.addNewPPr();
CTSpacing spacing = ppr.isSetSpacing() ? ppr.getSpacing() : ppr.addNewSpacing();
spacing.setAfter(BigInteger.ZERO);
spacing.setBefore(BigInteger.ZERO);
}
private static void embedWatermarkImage(XWPFRun run, String imageId,
long pageWidthTwips, long pageHeightTwips, int imaWidth, int imaHeight) {
log.info("=======pageWidthTwips=====" + pageWidthTwips);
log.info("=======pageHeightTwips=====" + pageHeightTwips);
log.info("=======imaWidth=====" + imaWidth);
log.info("=======imaHeight=====" + imaHeight);
long min = Math.min(pageWidthTwips, pageHeightTwips);
int x = imaWidth * 10;
int y = imaHeight * 10;
log.info("=======x=====" + x);
log.info("=======y=====" + y);
XmlCursor cursor = run.getCTR().newCursor();
// 开始构建 <w:drawing> 节点
cursor.beginElement(new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "drawing"));
// 添加 <wp:inline> 节点
cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", "anchor"));
cursor.insertAttributeWithValue("relativeHeight", "999999"); // 置于最上层
cursor.insertAttributeWithValue("behindDoc", "0"); // 覆盖正文内容
cursor.insertAttributeWithValue("layoutInCell", "1");
cursor.insertAttributeWithValue("locked", "0");
cursor.insertAttributeWithValue("allowOverlap", "1"); // 允许重叠
cursor.insertAttributeWithValue("simplePos", "0"); // 使用绝对定位
/*cursor.insertAttributeWithValue(new QName("distT"), "0");
cursor.insertAttributeWithValue(new QName("distB"), "0");
cursor.insertAttributeWithValue(new QName("distL"), "0");
cursor.insertAttributeWithValue(new QName("distR"), "0");*/
// 设置图片尺寸(示例值,需动态计算)
cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", "extent"));
// cursor.insertAttributeWithValue(new QName("cx"), "5000000"); // 宽度(EMU)
// cursor.insertAttributeWithValue(new QName("cy"), "3000000"); // 高度(EMU)
cursor.insertAttributeWithValue(new QName("cx"), imaWidth * x + ""); // 宽度(EMU)
cursor.insertAttributeWithValue(new QName("cy"), imaHeight * y + ""); // 高度(EMU)
cursor.toParent(); // 回到 <wp:inline> 节点
// 添加 <a:graphic> 节点
cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/main", "graphic"));
cursor.insertAttributeWithValue(new QName("uri"), "http://schemas.openxmlformats.org/drawingml/2006/picture");
// 添加 <a:graphicData> 节点
cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/main", "graphicData"));
cursor.insertAttributeWithValue(new QName("uri"), "http://schemas.openxmlformats.org/drawingml/2006/picture");
// 添加 <pic:pic> 节点
cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/picture", "pic"));
// 添加 <pic:blipFill> 并绑定图片ID
cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/picture", "blipFill"));
cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/main", "blip"));
cursor.insertAttributeWithValue(new QName("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "embed"), imageId);
cursor.toParent(); // 回到 <pic:blipFill>
// 添加拉伸属性
cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/main", "stretch"));
cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/main", "fillRect"));
cursor.dispose(); // 关闭游标
}
}
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</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>
这是我在Java1.8 环境里用这几个依赖写的给word文档加水印的方法,我发现但文档分第一节和第二节这样多节时,只有最后一节的页面有水印,前面第一节所有页面都没有水印了,帮分析下呢
最新发布