Java POI word 工具类统计及水印处理

一、下载依赖
采用免费的POI来实现Word字数统计和添加水印。
1.1、在项目依赖中添加

 <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
         <version>4.1.2</version>
    </dependency>

二、工具类(WordUtils)

 package com.gientech.jep.service.sys.util;

import com.microsoft.schemas.office.office.CTLock;
import com.microsoft.schemas.vml.*;
mport org.apache.poi.wp.usermodel.*;
import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFHeader;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.List;
import java.util.stream.Stream;

/**
 * word 工具处理类
 * @author houyinke
 * @date 2022/7/19 15:57
 */
public class WordUtils {

static Logger logger = LoggerFactory.getLogger(WordUtils.class);

/**
 * 水印字体字体
 */
private static final String fontName = "PingFang SC";

/**
 *  一个字平均长度,单位pt,用于:计算文本占用的长度(文本总个数*单字长度)
 */
private static final int widthPerWord = 10;

/**
 * word文字水印(调用poi封装的createWatermark方法)
 * @param doc XWPFDocument对象
 * @param markStr 水印文字
 */
public static void setWordWaterMark(XWPFDocument doc, String markStr,String fontColor) {
    XWPFParagraph paragraph = doc.createParagraph();
    XWPFHeaderFooterPolicy headerFooterPolicy = doc.getHeaderFooterPolicy();
    if (headerFooterPolicy == null) {
        headerFooterPolicy = doc.createHeaderFooterPolicy();
    }
    // create default Watermark - fill color black and not rotated
    headerFooterPolicy.createWatermark(markStr);
    // get the default header
    // Note: createWatermark also sets FIRST and EVEN headers
    // but this code does not updating those other headers
    XWPFHeader header = headerFooterPolicy.getHeader(XWPFHeaderFooterPolicy.DEFAULT);
    paragraph = header.getParagraphArray(0);
//            // get com.microsoft.schemas.vml.CTShape where fill color and rotation is set
    paragraph.getCTP().newCursor();
    org.apache.xmlbeans.XmlObject[] xmlobjects = paragraph.getCTP().getRArray(0).getPictArray(0).selectChildren(
            new javax.xml.namespace.QName("urn:schemas-microsoft-com:vml", "shape"));
    if (xmlobjects.length > 0) {
        CTShape ctshape = (CTShape) xmlobjects[0];
        ctshape.setFillcolor(fontColor);
        ctshape.setStyle(ctshape.getStyle() + ";rotation:315");
    }
}

/**
 * 将指定的字符串重复repeats次.
 * @param pattern 字符串
 * @param repeats 重复次数
 * @return 生成的字符串
 */
private static 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);
}

/**
 * 为文档添加水印
 * 实现参考了{@link XWPFHeaderFooterPolicy#(String, int)}
 * @param doc 需要被处理的docx文档对象
 * @param customText 水印文本
 * @param type 类型:1.平铺;2.单个
 */
private static void waterMarkDocXDocument(XWPFDocument doc, String customText, String styleTop, int type,String fontColor,String fontSize,String rotation) {
    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");

    // 平铺或单个
    if(type != 2){
        // 设置形状样式(旋转,位置,相对路径等参数)
        shape.setStyle(getShapeStyle(customText, styleTop,rotation));
    }else{
        // 设置形状样式(旋转,位置,相对路径等参数)
        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);
}

/**
 * 以艺术字方式加上水印(平铺)
 * @param docx XWPFDocument对象
 * @param customText 水印文字
 */
public static void makeFullWaterMarkByWordArt(XWPFDocument docx, String customText,String fontColor,String fontSize,String styleRotation) {
    // 水印文字之间使用8个空格分隔
    customText = customText + repeatString(" ", 16);
    // 一行水印重复水印文字次数
    customText = repeatString(customText, 10);
    // 与顶部的间距
    String styleTop = "0pt";

    if (docx == null) {
        return;
    }

    // 遍历文档,添加水印
    for (int lineIndex = -10; lineIndex < 20; lineIndex++) {
        styleTop = 200 * lineIndex + "pt";
        waterMarkDocXDocument(docx, customText, styleTop, 1,fontColor, fontSize ,styleRotation);
    }
}

/**
 * 以艺术字方式加上水印(单个)
 * @param docx XWPFDocument对象
 * @param customText 水印文字
 */
public static void makeWaterMarkByWordArt(XWPFDocument docx, String customText,String fontColor,String fontSize,String rotation) {
    // 与顶部的间距
    String styleTop = "0pt";
    // 判断文档是否为空
    if (docx == null) {
        return;
    }

    // 添加水印
    waterMarkDocXDocument(docx, customText, styleTop, 2,fontColor,fontSize,rotation);
}

/**
 * 构建Shape的样式参数
 * @param customText 水印文本
 * @return
 */
private static String getShapeStyle(String customText, String styleTop,String styleRotation) {
    StringBuilder sb = new StringBuilder();
    // 文本path绘制的定位方式
    sb.append("position: ").append("absolute");
    // 计算文本占用的长度(文本总个数*单字长度)
    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(";margin-top: ").append(styleTop);
    sb.append(";mso-position-horizontal-relative: ").append("margin");
    sb.append(";mso-position-vertical-relative: ").append("margin");
    sb.append(";mso-position-vertical: ").append("left");
    sb.append(";mso-position-horizontal: ").append("center");
    sb.append(";rotation: ").append(styleRotation);
    return sb.toString();
}

/**
 * 构建Shape的样式参数
 * @return
 */
private static String getShapeStyle() {
    StringBuilder sb = new StringBuilder();
    // 文本path绘制的定位方式
    sb.append("position: ").append("absolute");
    sb.append(";left: ").append("opt");
    // 计算文本占用的长度(文本总个数*单字长度)
    sb.append(";width: ").append("500pt");
    // 字体高度
    sb.append(";height: ").append("150pt");
    sb.append(";z-index: ").append("-251654144");
    sb.append(";mso-wrap-edited: ").append("f");
    sb.append(";margin-left: ").append("-50pt");
    sb.append(";margin-top: ").append("270pt");
    sb.append(";mso-position-horizontal-relative: ").append("margin");
    sb.append(";mso-position-vertical-relative: ").append("margin");
    sb.append(";mso-width-relative: ").append("page");
    sb.append(";mso-height-relative: ").append("page");
    sb.append(";rotation: ").append("-2949120f");
    return sb.toString();
}

/**
 * word 字数统计
 * @param xwpfDocument doc文档
 * @return
 */
public static int count(XWPFDocument xwpfDocument){
    List<XWPFParagraph> paragraphs = xwpfDocument.getParagraphs();
    // 行数,字数
    int rowCount = 1, count = 0;

    try {
        // 循环读取字数
        for (XWPFParagraph xwpfParagraph : paragraphs) {
            int linLength = 0;
            String lineStr = "";
            List<XWPFRun> xwpfRuns = xwpfParagraph.getRuns();
            for (XWPFRun xwpfRun : xwpfRuns) {
                linLength += xwpfRun.toString().trim().length();
                lineStr += xwpfRun.toString();
                count += xwpfRun.toString().trim().length();
            }

            logger.info("第" + rowCount + "行内容,长度:" + linLength);
            rowCount ++;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

    return count;
}

/**
 * 加载docx格式的word文档
 * @param inputStream
 * @return
 */
private static XWPFDocument loadDocXDocument(InputStream inputStream) {
    XWPFDocument doc;
    try {
        doc = new XWPFDocument(inputStream);
    } catch (Exception e) {
        throw new RuntimeException("文档加载失败!!");
    }
    return doc;
}

public static void main(String[] args) throws IOException {
    XWPFDocument xwpfDocument = null;
    try {
        //输入的docx文档
        InputStream in = new FileInputStream(new File("/Users/houyinke/Desktop/doc/4.doc"));
        xwpfDocument = WordUtils.loadDocXDocument(in);

        // 统计字数
        int count = WordUtils.count(xwpfDocument);
        System.out.println("word字数:" + count);

        // 添加水印
        WordUtils.makeFullWaterMarkByWordArt(xwpfDocument, "中电金信", "#888888", "0.7pt","-30");

        // 写出加水印后的文档
        String filePath = "/Users/houyinke/Desktop/doc/3.docx";
        OutputStream os = new FileOutputStream(filePath);
        xwpfDocument.write(os);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(xwpfDocument != null){
            xwpfDocument.close();
        }
    }
}

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容