Java poi api插入文字水印到docx文件

使用apache poi api实现wps插入水印效果。

Office Open XML介绍

  • Microsoft Office从2007版开始使用xml格式存储。zip解压一个docx的word文档后,可以看到如下结构:
    docx结构
  • 其中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();
	}
  • 效果
    在这里插入图片描述
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值