使用apache poi api实现wps插入水印效果。
Office Open XML介绍
- Microsoft Office从2007版开始使用xml格式存储。zip解压一个docx的word文档后,可以看到如下结构:
- 其中document.xml代表文档的主要内容,使用vscode的XML Tools v2.5.1插件工具格式化后,可看到里面有很多种类的标签,比如w:p表示一个段落,w:pPr表示段落属性,w:r表示文本,w:rPr表示文字属性,参考微软官网(中文)(英文)查看docx文档所有标签名解释。
使用WPS插入水印
通过wps菜单【插入】-【水印】选择预设水印【保密】,可以很方便插入水印。
使用修改docx文件方式插入水印
- 使用WinMerge工具对比解压后的目录,发现插入水印后的文件,主要变化是word/header1.xml文件中多了如下片段
<w:p>
<w:pPr>
<w:pStyle w:val="3"/>
</w:pPr>
<w:r>
<w:rPr>
<w:sz w:val="18"/>
</w:rPr>
<w:pict>
<v:shape id="PowerPlusWaterMarkObject71060" o:spid="_x0000_s2049" o:spt="136" type="#_x0000_t136" style="position:absolute;left:0pt;height:89.8pt;width:497.45pt;mso-position-horizontal:center;mso-position-horizontal-relative:margin;mso-position-vertical:center;mso-position-vertical-relative:margin;rotation:-2949120f;z-index:-251657216;mso-width-relative:page;mso-height-relative:page;" fillcolor="#C0C0C0" filled="t" stroked="f" coordsize="21600,21600" adj="10800">
<v:path/>
<v:fill on="t" opacity="32768f" focussize="0,0"/>
<v:stroke on="f"/>
<v:imagedata o:title=""/>
<o:lock v:ext="edit" aspectratio="t"/>
<v:textpath on="t" fitshape="t" fitpath="t" trim="t" xscale="f" string="保密" style="font-family:思源黑体;font-size:36pt;v-same-letter-heights:f;v-text-align:center;"/>
</v:shape>
</w:pict>
</w:r>
</w:p>
- 新建一个文档new.docx,zip解压后,将刚才得到的片段,copy到new.docx的header1.xml中,然后再使用zip重新压缩,改扩展名为docx,new.docx被成功的设置了水印。
- 注意,如果解压docx得到的文件夹没有header1.xml文件,需要先用wps插入页眉,页眉里面任意输入的文字。接下来会用代码不全缺失的页眉。
使用poi编程方式插入水印
- 有了上面手动修改文件生成水印的验证基础,只要用程序实现插入上述xml片段,就可以实现用编程方式插入水印
- poi中XWPFHeaderFooterPolicy自带createWatermark方法,但它只给body章节添加了水印,没有考虑段落中的章节,导致手动插入章节的页显示不出水印
- poi版本5.2.2
- PoiWatermarkUtil.java参考XWPFHeaderFooterPolicy.getWatermarkParagraph方法
package test.poi;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.ooxml.POIXMLDocumentPart.RelationPart;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFFactory;
import org.apache.poi.xwpf.usermodel.XWPFHeader;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRelation;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.openxmlformats.schemas.officeDocument.x2006.sharedTypes.STTrueFalse;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHdrFtrRef;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPicture;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STHdrFtr;
import com.microsoft.schemas.office.office.CTLock;
import com.microsoft.schemas.vml.CTFill;
import com.microsoft.schemas.vml.CTGroup;
import com.microsoft.schemas.vml.CTShape;
import com.microsoft.schemas.vml.CTStroke;
import com.microsoft.schemas.vml.CTTextPath;
import com.microsoft.schemas.vml.STExt;
public class PoiWatermarkUtil {
public static void addWaterMark(XWPFDocument doc, String watermark) throws Exception {
List<XWPFHeader> headerList = new ArrayList<>();
headerList.addAll(doc.getHeaderList());
// 1 获取章节对象CTSectPr
List<CTSectPr> ctSectPrs = new ArrayList<>();
// 1.1 body章节
CTSectPr bodySectPr = doc.getDocument().getBody().getSectPr();
ctSectPrs.add(bodySectPr);
// 1.2 段落章节
List<XWPFParagraph> paragraphs = doc.getParagraphs();
for (XWPFParagraph xwpfParagraph : paragraphs) {
CTPPr ctpPr = xwpfParagraph.getCTPPr();
CTSectPr sectPr = ctpPr.getSectPr();
if (sectPr == null) {
continue;
}
ctSectPrs.add(sectPr);
}
// 2 补全章节中缺少的header
for (CTSectPr sectPr : ctSectPrs) {
int sizeOfHeaderReferenceArray = sectPr.sizeOfHeaderReferenceArray();
if (sizeOfHeaderReferenceArray > 0) {
continue;
}
XWPFHeader header = createHeader(doc);
headerList.add(header);
CTHdrFtrRef headerReference = sectPr.addNewHeaderReference();
headerReference.setId(doc.getRelationId(header));
headerReference.setType(STHdrFtr.DEFAULT);
}
// 3 设置水印
for (int i = 0 ; i < headerList.size() ; i++) {
int zindex = -251645952 + i;
XWPFHeader xwpfHeader = headerList.get(i);
XWPFParagraph paragraph = xwpfHeader.createParagraph();
XWPFRun run = paragraph.createRun();
CTR ctr = run.getCTR();
CTPicture pict = ctr.addNewPict();
CTGroup group = CTGroup.Factory.newInstance();
CTShape shape = group.addNewShape();
shape.setId("PowerPlusWaterMarkObject" + (i + 1));
shape.setSpid("_x0000_s206" + + (i + 1));
shape.setSpt(new Float("136"));
shape.setType("#_x0000_t136");
shape.setStyle("position:absolute;left:0pt;height:89.55pt;width:496.05pt;mso-position-horizontal:center;mso-position-horizontal-relative:margin;mso-position-vertical:center;mso-position-vertical-relative:margin;rotation:-2949120f;z-index:" + zindex + ";mso-width-relative:page;mso-height-relative:page;");
shape.setFillcolor("#C0C0C0");
shape.setFilled(STTrueFalse.T);
shape.setCoordsize("21600,21600");
shape.setAdj("10800");
CTFill fill = shape.addNewFill();
fill.setOn(STTrueFalse.T);
fill.setOpacity("32768f");
fill.setFocussize("0,0");
CTStroke stroke = shape.addNewStroke();
stroke.setOn(STTrueFalse.F);
CTLock lock = shape.addNewLock();
lock.setExt(STExt.EDIT);
lock.setAspectratio(STTrueFalse.T);
CTTextPath textPath = shape.addNewTextpath();
textPath.setOn(STTrueFalse.T);
textPath.setFitpath(STTrueFalse.T);
textPath.setTrim(STTrueFalse.T);
textPath.setXscale(STTrueFalse.F);
textPath.setString(watermark);
textPath.setStyle("font-family:思源黑体;font-size:89pt;v-same-letter-heights:f;v-text-align:center;");
pict.set(group);
}
}
private static XWPFHeader createHeader(XWPFDocument doc) {
int index = 1;
for (RelationPart rp : doc.getRelationParts()) {
if (rp.getRelationship().getRelationshipType().equals(XWPFRelation.HEADER.getRelation())) {
index++;
}
}
XWPFHeader header = (XWPFHeader) doc.createRelationship(XWPFRelation.HEADER, XWPFFactory.getInstance(), index);
header.setXWPFDocument(doc);
return header;
}
}
- 代码解释,CTPicture对象没有直接创建CTShape对象的方法,需要CTPicture先创建CTGroup,再由CTGroup创建CTShape
- 调用
public static void main(String[] args) throws Exception {
XWPFDocument doc = new XWPFDocument(new FileInputStream("D:\\tmp\\1.docx"));
addWaterMark(doc, "保密");
doc.write(new FileOutputStream("D:\\tmp\\1_wm.docx"));
doc.close();
}
- 效果