背景:
poi并没有提供直接插入水印图片的方法,目前需要再word的首页插入一张水印图片,于是就需要通过另一种方式,插入透明图片(png格式)并将图片设置为“浮于文字上方”的方式实现该需求。
所需jar:
poi-3.8.jar
poi-ooxml-3.8.jar
poi-ooxml-schemas-3.8.jar
xmlbeans-2.6.0.jar
dom4j-1.6.1.jar
示例代码:
创建CustomXWPFDocument类
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlToken;
import org.openxmlformats.schemas.drawingml.x2006.main.*;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTAnchor;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDrawing;
import java.io.IOException;
import java.io.InputStream;
/**
* 文档对象
*/
public class CustomXWPFDocument extends XWPFDocument {
public CustomXWPFDocument() {
super();
}
public CustomXWPFDocument(InputStream in) throws IOException {
super(in);
}
public CustomXWPFDocument(OPCPackage pkg) throws IOException {
super(pkg);
}
/**
* 创建一个包含图片的XWPFParagraph,将图片插入到指定或新建的段落中。
*
* @param blipId 图片在文档关系中的唯一标识(r:embed属性值),用于链接到图片数据。
* @param id 图片在文档中的唯一标识(cNvPr元素的id属性值)。
* @param width 图片的宽度,单位为像素。内部转换为EMU(English Metric Unit)单位。
* @param height 图片的高度,单位为像素。内部转换为EMU单位。
* @param paragraph 可选参数,要插入图片的XWPFParagraph。如果为null,则会创建一个新的段落。
*/
public void createPicture(String blipId, int id, int width, int height,
XWPFParagraph paragraph) {
// 定义EMU(English Metric Unit)常量,1EMU等于1/914400米,是OpenXML中使用的长度单位。
final int EMU = 9525;
// 将像素宽度和高度转换为EMU单位。
width *= EMU;
height *= EMU;
// 如果传入的段落为空,则创建一个新的段落。
if (paragraph == null) {
paragraph = createParagraph();
}
// 在段落中创建一个新的运行(Run),并在运行的底层XML结构(CTR)中添加一个新的内联图形(Inline)元素。
CTInline inline = paragraph.createRun().getCTR().addNewDrawing()
.addNewInline();
// 构建包含图片详细信息的XML字符串,遵循OpenXML DrawingML规范。
StringBuffer picXml = new StringBuffer();
// 开始图形元素
picXml.append("<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">")
// 描述图形类型为图片
.append( " <a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">")
// 开始图片元素
.append( " <pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">")
// 包含非视觉属性的图片属性(名称、ID等)
.append( " <pic:nvPicPr>" )
// 非视觉属性主元素(通用非视觉属性)设置图片ID
.append( " <pic:cNvPr id=\"")
.append(id)
.append( "\" name=\"img_")
.append(id)
.append( "\"/>")
.append( " <pic:cNvPicPr/>")
.append( " </pic:nvPicPr>")
// 填充图片的属性(图片源、缩放方式等)
.append( " <pic:blipFill>")
// 插入图片的Blip(位图)元素,引用图片数据
.append( " <a:blip r:embed=\"")
.append(blipId)
// 引用关系命名空间
.append( "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"/>")
// 设置图片填充方式为拉伸
.append( " <a:stretch>")
.append( " <a:fillRect/>")
.append( " </a:stretch>")
.append( " </pic:blipFill>")
// 图片的形状属性(位置、尺寸、旋转等)
.append( " <pic:spPr>")
// 图片的变换属性(平移、缩放)
.append( " <a:xfrm>")
// 图片相对于容器的偏移(此处为原点)
.append( " <a:off x=\"0\" y=\"0\"/>")
// 图片的扩展尺寸(宽度)
.append( " <a:ext cx=\"")
.append(width)
// 图片的扩展尺寸(高度)
.append( "\" cy=\"")
.append(height)
.append( "\"/>")
.append( " </a:xfrm>")
// 使用预定义形状(矩形)
.append( " <a:prstGeom prst=\"rect\">")
// 空的可变几何形状属性列表(保留结构完整性)
.append( " <a:avLst/>")
.append( " </a:prstGeom>")
.append( " </pic:spPr>")
.append( " </pic:pic>")
.append( " </a:graphicData>")
.append( "</a:graphic>");
// 将构造的XML字符串解析为XmlToken对象
XmlToken xmlToken = null;
try {
xmlToken = XmlToken.Factory.parse(picXml.toString());
} catch (XmlException xe) {
xe.printStackTrace();
}
// 将XmlToken对象设置到之前创建的CTInline元素中。
inline.set(xmlToken);
// 设置内联图形与周围文本的间距(顶部、底部、左侧、右侧均为0EMU)
inline.setDistT(0);
inline.setDistB(0);
inline.setDistL(0);
inline.setDistR(0);
// 设置内联图形的实际尺寸(与构造的XML中一致,可能不是必需的,因为已经包含在XML中)
CTPositiveSize2D extent = inline.addNewExtent();
extent.setCx(width);
extent.setCy(height);
// 设置非视觉文档属性(名称、ID、描述等)
CTNonVisualDrawingProps docPr = inline.addNewDocPr();
docPr.setId(id);
docPr.setName("docx_img_ " + id);
docPr.setDescr("docx Picture");
}
/**
* 创建一个带有图形对象(如图片)的CTAnchor(锚点),用于将图形放置于Word文档中特定位置,并可设定其浮于文字上方或下方。
*
* @param ctGraphicalObject 要插入的图形对象,例如CTPicture(图片)对象。
* @param deskFileName 图片的描述性文件名,用于设置锚点的描述属性。
* @param width 图形对象的宽度,单位为EMU(English Metric Unit)。
* @param height 图形对象的高度,单位为EMU。
* @param leftOffset 图形相对于所在列左侧的偏移距离,单位为EMU。
* @param topOffset 图形相对于所在段落顶部的偏移距离,单位为EMU。
* @param behind 指定图形是否应位于文字下方。如果为true,则图形位于文字下方;否则位于文字上方。
* @return 生成的CTAnchor对象,包含了指定图形对象及定位信息。
*/
public static CTAnchor getAnchorWithGraphic(CTGraphicalObject ctGraphicalObject,
String deskFileName, int width, int height,
int leftOffset, int topOffset, boolean behind) {
// 构建锚点的XML字符串,遵循WordProcessingML DrawingML规范。
String anchorXML =
// 开始wp:anchor元素,定义命名空间
"<wp:anchor xmlns:wp=\"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\" "
// 设置锚点属性(简单定位、相对高度、文字前后关系、锁定状态、布局方式、重叠允许)
+ "simplePos=\"0\" relativeHeight=\"0\" behindDoc=\"" + ((behind) ? 1 : 0) + "\" locked=\"0\" layoutInCell=\"1\" allowOverlap=\"1\">"
// 设置简单定位信息(此处为原点)
+ "<wp:simplePos x=\"0\" y=\"0\"/>"
// 设置水平位置,相对于列
+ "<wp:positionH relativeFrom=\"column\">"
// 设置水平偏移距离
+ "<wp:posOffset>" + leftOffset + "</wp:posOffset>"
+ "</wp:positionH>"
// 设置垂直位置,相对于段落
+ "<wp:positionV relativeFrom=\"paragraph\">"
+ "<wp:posOffset>" + topOffset + "</wp:posOffset>" +
"</wp:positionV>"
// 设置图形对象的尺寸
+ "<wp:extent cx=\"" + width + "\" cy=\"" + height + "\"/>"
// 设置图形边缘效果扩展距离(此处为默认值)
+ "<wp:effectExtent l=\"0\" t=\"0\" r=\"0\" b=\"3175\"/>"
// 设置无环绕模式,即图形不随文字换行而移动
+ "<wp:wrapNone/>"
// 设置文档属性(ID、名称、描述)
+ "<wp:docPr id=\"0\" name=\"Drawing 0\" descr=\"" + deskFileName + "\"/><wp:cNvGraphicFramePr/>"
+ "</wp:anchor>";
// 将构造的XML字符串解析为CTDrawing对象。
CTDrawing drawing = null;
try {
drawing = CTDrawing.Factory.parse(anchorXML);
} catch (XmlException e) {
e.printStackTrace();
}
// 从解析得到的CTDrawing对象中获取第一个(也是唯一一个)CTAnchor对象。
CTAnchor anchor = drawing.getAnchorArray(0);
// 将提供的图形对象(如CTPicture)设置到锚点中。
anchor.setGraphic(ctGraphicalObject);
return anchor;
}
}
创建poitest类进行验证:
import org.apache.poi.POIXMLDocument;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObject;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTAnchor;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDrawing;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
public class poitest {
public static void main(String[] args) throws IOException, InvalidFormatException {
insertWatermark("D:\\document_ls\\bb.docx","1231321","D:\\document_ls\\");
}
/**
* 向指定Word文档中插入水印图片,并将其设置为浮于文字之上。
*
* @param wdlj 输入Word文档路径,待插入水印的源文件路径。
* @param bgxh 水印图片背景编号或其他标识,用于生成临时输出文件的名称。
* @param parentPath 输出文件所在的父目录路径,用于保存临时文件和最终修改后的Word文档。
* @throws IOException 在文件读写过程中发生I/O错误时抛出。
* @throws InvalidFormatException 当处理的文件格式不符合预期或无法解析时抛出。
*/
private static void insertWatermark(String wdlj, String bgxh, String parentPath) throws IOException, InvalidFormatException {
FileOutputStream fos = null ;
// 定义水印图片文件路径
String pic = "D:\\document_ls\\reportPic.png";
// 生成临时输出文件路径
String outputPath = parentPath + bgxh + "_temp.docx";
// 打开并解析输入Word文档,获得OPCPackage对象和CustomXWPFDocument对象
OPCPackage pk = POIXMLDocument.openPackage(wdlj);
CustomXWPFDocument doc = new CustomXWPFDocument(pk);
// 获取文档的第一个段落
XWPFParagraph para = doc.getParagraphs().get(0);
// 将水印图片添加到文档的图片数据集中,并获取其在文档中的Blip ID
String blipId = para.getDocument().addPictureData(new FileInputStream(new File(pic)), XWPFDocument.PICTURE_TYPE_PNG);
// 在指定段落中创建图片,设置尺寸为350px x 350px,并关联到当前段落
doc.createPicture(blipId, XWPFDocument.PICTURE_TYPE_PNG, 350, 350, para);
// 从段落的第一个运行中获取对应的CTR对象,并从中找到包含图形对象的CTDrawing对象
CTDrawing drawing = para.getRuns().get(0).getCTR().getDrawingArray(0);
// 从CTDrawing对象中提取出CTGraphicalObject对象,即插入的图片
CTGraphicalObject graphicalobject = drawing.getInlineArray(0).getGraphic();
// 使用getAnchorWithGraphic方法将图形对象转换为浮于文字之上的CTAnchor对象,设置相关属性
CTAnchor anchor = doc.getAnchorWithGraphic(graphicalobject, "Generated",
Units.toEMU(350), Units.toEMU(350),
Units.toEMU(70), Units.toEMU(200), false);
// 替换CTDrawing中的内联图形对象为新创建的浮点锚点对象
drawing.setAnchorArray(new CTAnchor[]{anchor});
// 删除原有的内联图形对象,确保仅保留浮点锚点
drawing.removeInline(0);
// 创建文件输出流,将修改后的文档写入临时输出文件
fos = new FileOutputStream(new File(outputPath));
doc.write(fos);
// 关闭OPCPackage以释放资源
pk.close();
// 移动临时文件至源文件路径,替换原有文档
Files.move(Paths.get(outputPath), Paths.get(wdlj), StandardCopyOption.REPLACE_EXISTING);
}
}
效果展示:
插入前:
插入后: