java实现word文档(docx)添加水印

您所在的位置:网站首页 word2003如何增加一页 java实现word文档(docx)添加水印

java实现word文档(docx)添加水印

2024-07-12 20:50| 来源: 网络整理| 查看: 265

前言

关于本文,需要提前说明以下几点问题:

此文档实现的过程主要针对docx格式的word文档; 由于docx本身是可编辑的格式,因此处理后的文件水印也能被删除; 由于word文档可以改变页大小(A3,A4等纸张),那么内容的总页数就会不固定,因此我们无法按照具体内容有多少页,针对性地为每一页添加水印,只能通过为页眉或者页脚添加默认格式来保证页数变更的情况下,水印依旧随着页数增加和减少都生效; 由于word文档纸张大小不确定,因此水印覆盖的范围有限,所以要支持极端情况下的纸张大小,只能手动调节参数; 本文记录的实现方式不依赖windows运行环境,Linux下依旧可以正常使用。

为了实现添加水印功能,费了不少精力,考虑到项目需要运行在Linux环境下,很多网上的解决方案都是依赖windows平台,最终选择了使用Apache的POI框架,官网地址:http://poi.apache.org/。

由于POI框架对doc格式的word文档已经不再支持了,并且目前能为doc格式文档提供的API非常少,其中大多都是读类型的API,本人能力有限,暂时没有效的办法,如果有大佬有较好的方案,求发邮箱 [email protected],感激不尽!!

实现效果 依赖

依赖库主要是POI相关的库,核心依赖如下:

123456789101112131415161718192021222324252627 org.apache.poi poi 4.1.0 org.apache.poi poi-scratchpad 4.1.0 commons-codec commons-codec 1.12 org.apache.poi poi-ooxml 4.1.0 org.apache.poi poi-ooxml-schemas 4.1.0

附加的IO库(Commons-IO)依赖如下:

12345 commons-io commons-io 2.5 实现思路

水印效果的实现思路:为word文档添加艺术文字路径效果(与绘制五角星、矩形、圆形这类型的形状类似),通过编辑页眉,选中水印文字的效果如下:

水印斜纹与水印行间距实现思路: 先水平从上到下先按照特定规则生成水平的水印,其中每一行水印是按照水印文字[空格][空格][空格]水印文字[空格][空格]...这样的规则生成的重复字符串,每个字符的高度和宽度固定,具体需要绘制多长,只需要控制字符串重复的次数即可【本文是空格8个,总共一行重复10次】,最后再将水印旋转45度,示意图如下:

核心实现代码来源于POI某段源代码(但是该源代码的水印实现不是很好使,修改后完整的代码见后文),该代码代码位于org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy类的getWatermarkParagraph(String text, int idx)的实现,如下:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768/** * This is the default Watermark paragraph; the only variable is the text message * TODO: manage all the other variables */private XWPFParagraph getWatermarkParagraph(String text, int idx) { CTP p = CTP.Factory.newInstance(); byte[] rsidr = doc.getDocument().getBody().getPArray(0).getRsidR(); byte[] rsidrdefault = doc.getDocument().getBody().getPArray(0).getRsidRDefault(); p.setRsidP(rsidr); p.setRsidRDefault(rsidrdefault); CTPPr pPr = p.addNewPPr(); pPr.addNewPStyle().setVal("Header"); // start watermark paragraph CTR r = p.addNewR(); CTRPr rPr = r.addNewRPr(); rPr.addNewNoProof(); CTPicture pict = r.addNewPict(); CTGroup group = CTGroup.Factory.newInstance(); CTShapetype shapetype = group.addNewShapetype(); shapetype.setId("_x0000_t136"); shapetype.setCoordsize("1600,21600"); shapetype.setSpt(136); shapetype.setAdj("10800"); shapetype.setPath2("m@7,0l@8,0m@5,21600l@6,21600e"); CTFormulas formulas = shapetype.addNewFormulas(); formulas.addNewF().setEqn("sum #0 0 10800"); formulas.addNewF().setEqn("prod #0 2 1"); formulas.addNewF().setEqn("sum 21600 0 @1"); formulas.addNewF().setEqn("sum 0 0 @2"); formulas.addNewF().setEqn("sum 21600 0 @3"); formulas.addNewF().setEqn("if @0 @3 0"); formulas.addNewF().setEqn("if @0 21600 @1"); formulas.addNewF().setEqn("if @0 0 @2"); formulas.addNewF().setEqn("if @0 @4 21600"); formulas.addNewF().setEqn("mid @5 @6"); formulas.addNewF().setEqn("mid @8 @5"); formulas.addNewF().setEqn("mid @7 @8"); formulas.addNewF().setEqn("mid @6 @7"); formulas.addNewF().setEqn("sum @6 0 @5"); CTPath path = shapetype.addNewPath(); path.setTextpathok(STTrueFalse.T); path.setConnecttype(STConnectType.CUSTOM); path.setConnectlocs("@9,0;@10,10800;@11,21600;@12,10800"); path.setConnectangles("270,180,90,0"); CTTextPath shapeTypeTextPath = shapetype.addNewTextpath(); shapeTypeTextPath.setOn(STTrueFalse.T); shapeTypeTextPath.setFitshape(STTrueFalse.T); CTHandles handles = shapetype.addNewHandles(); CTH h = handles.addNewH(); h.setPosition("#0,bottomRight"); h.setXrange("6629,14971"); CTLock lock = shapetype.addNewLock(); lock.setExt(STExt.EDIT); CTShape shape = group.addNewShape(); shape.setId("PowerPlusWaterMarkObject" + idx); shape.setSpid("_x0000_s102" + (4 + idx)); shape.setType("#_x0000_t136"); shape.setStyle("position:absolute;margin-left:0;margin-top:0;width:415pt;height:207.5pt;z-index:-251654144;mso-wrap-edited:f;mso-position-horizontal:center;mso-position-horizontal-relative:margin;mso-position-vertical:center;mso-position-vertical-relative:margin"); shape.setWrapcoords("616 5068 390 16297 39 16921 -39 17155 7265 17545 7186 17467 -39 17467 18904 17467 10507 17467 8710 17545 18904 17077 18787 16843 18358 16297 18279 12554 19178 12476 20701 11774 20779 11228 21131 10059 21248 8811 21248 7563 20975 6316 20935 5380 19490 5146 14022 5068 2616 5068"); shape.setFillcolor("black"); shape.setStroked(STTrueFalse.FALSE); CTTextPath shapeTextPath = shape.addNewTextpath(); shapeTextPath.setStyle("font-family:;Cambria;;font-size:1pt"); shapeTextPath.setString(text); pict.set(group); // end watermark paragraph return new XWPFParagraph(p, doc);} 源代码

说明:

水印字体是否显示正常取决于宿主机,如果该机器没安装指定字体会导致水印字体效果可能不同; 字体大小与word软件一致,使用 pt 作为单位; 使用方式,只需要通过构造函数传递水印文字,最后调用makeSlopeWaterMark方法,传入目标docx的输入流和指定的输出流即可。

实现源代码如下:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184import com.microsoft.schemas.office.office.CTLock;import com.microsoft.schemas.vml.*;import org.apache.commons.io.IOUtils;import org.apache.poi.wp.usermodel.HeaderFooterType;import org.apache.poi.xwpf.usermodel.XWPFDocument;import org.apache.poi.xwpf.usermodel.XWPFHeader;import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;import java.io.*;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.util.stream.Stream;/** * 微软office Word 水印机. */public class WordWaterMarker { private String customText; // 水印文字 private String fontName = "华文行楷"; // word字体 private String fontSize = "0.5pt"; // 字体大小 private String fontColor = "#d0d0d0"; // 字体颜色 private int widthPerWord = 10; // 一个字平均长度,单位pt,用于:计算文本占用的长度(文本总个数*单字长度) private String styleTop = "0"; // 与顶部的间距 private String styleRotation = "45"; // 文本旋转角度 public WordWaterMarker(String customText) { customText = customText + repeatString(" ", 8); // 水印文字之间使用8个空格分隔 this.customText = repeatString(customText, 10); // 一行水印重复水印文字次数 } /** * 【核心方法】将输入流中的docx文档加载添加水印后输出到输出流中. * * @param inputStream docx文档输入流 * @param outputStream 添加水印后docx文档的输出流 */ public void makeSlopeWaterMark(InputStream inputStream, OutputStream outputStream) { Path tempFile = createTempFile(inputStream); if (tempFile == null) { return; } try (BufferedInputStream buffIn = new BufferedInputStream(Files.newInputStream(tempFile))) { XWPFDocument doc = loadDocXDocument(buffIn, outputStream); if (doc == null) { return; } // 遍历文档,添加水印 for (int lineIndex = -10; lineIndex < 10; lineIndex++) { styleTop = 200 * lineIndex + " "; waterMarkDocXDocument(doc); } try { doc.write(outputStream); // 写出添加水印后的文档 } finally { IOUtils.closeQuietly(doc); } } catch (Exception exception) { throw new RuntimeException("水印添加失败!"); } finally { deleteFile(tempFile); } } /** * 为文档添加水印 * 实现参考了{@link org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy#getWatermarkParagraph(String, int)} * * @param doc 需要被处理的docx文档对象 */ private void waterMarkDocXDocument(XWPFDocument doc) { 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"); // 开始加水印 CTR ctr = ctp.addNewR(); CTRPr ctrpr = ctr.addNewRPr(); ctrpr.addNewNoProof(); CTGroup group = CTGroup.Factory.newInstance(); CTShapetype shapetype = group.addNewShapetype(); CTTextPath shapeTypeTextPath = shapetype.addNewTextpath(); shapeTypeTextPath.setOn(STTrueFalse.T); shapeTypeTextPath.setFitshape(STTrueFalse.T); CTLock lock = shapetype.addNewLock(); lock.setExt(STExt.VIEW); CTShape shape = group.addNewShape(); shape.setId("PowerPlusWaterMarkObject"); shape.setSpid("_x0000_s102"); shape.setType("#_x0000_t136"); shape.setStyle(getShapeStyle()); // 设置形状样式(旋转,位置,相对路径等参数) shape.setFillcolor(fontColor); shape.setStroked(STTrueFalse.FALSE); // 字体设置为实心 CTTextPath shapeTextPath = shape.addNewTextpath(); // 绘制文本的路径 shapeTextPath.setStyle("font-family:" + fontName + ";font-size:" + fontSize); // 设置文本字体与大小 shapeTextPath.setString(customText); CTPicture pict = ctr.addNewPict(); pict.set(group); } // 构建Shape的样式参数 private String getShapeStyle() { 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(";top: ").append(styleTop); sb.append(";mso-position-horizontal-relative: ").append("page"); sb.append(";mso-position-vertical-relative: ").append("page"); sb.append(";mso-position-vertical: ").append("left"); sb.append(";mso-position-horizontal: ").append("center"); sb.append(";rotation: ").append(styleRotation); return sb.toString(); } // 加载docx格式的word文档 private XWPFDocument loadDocXDocument(InputStream inputStream, OutputStream outputStream) { XWPFDocument doc; try { doc = new XWPFDocument(inputStream); } catch (Exception e) { throw new RuntimeException("文档加载失败!!"); } return doc; } /** * 创建临时文件. * * @param inputStream docx文档输入流 */ private Path createTempFile(InputStream inputStream) { Path tempFilePath; inputStream = (inputStream == null) ? new ByteArrayInputStream(new byte[0]) : inputStream; // 如果传入了null输入流,转换成空数组流 BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); bufferedInputStream.mark(0); // 输入流头部打上Mark(方便重读) // 创建临时文件 try { tempFilePath = Files.createTempFile("lanting", ".docx"); // 向临时文件写入数据 try (OutputStream tempout = Files.newOutputStream(tempFilePath)) { IOUtils.copy(bufferedInputStream, tempout); } catch (Exception e) { // 如果拷贝异常,删除临时文件 deleteFile(tempFilePath); } } catch (Exception e) { // 这里表示创建临时文件失败 tempFilePath = null; } return tempFilePath; } // 删除指定path的文件 private void deleteFile(Path path) { if (path != null) { try { Files.deleteIfExists(path); } catch (IOException e) { throw new RuntimeException(e); } } } /** * 将指定的字符串重复repeats次. */ private 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); }} 赏

谢谢您对蓝亭的支持!

支付宝


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3