之前使用poi-tl进行word模板生成word生成文件word之后,现在需给出需求word添加水印功能,先贴图生成后的效果,部分内容涉及公司信息打马赛克,
贴一下代码
/** * @author qiankun.hu * @version 1.0.0 * @createTime 2022年05月30日 13:57:00 * @Description 给word添加水印 */ @Slf4j @Service public class WordWaterMarkServiceImpl implements WaterMarkService { private final static String DEFAULT_WATERMARK = "XXX科技有限公司"; private final static String DEFAULT_FONT_COLOR = "#d8d8d8"; // 字体大小 private static final String FONT_SIZE = "0.5pt"; // 文本旋转角度 private static final String STYLE_ROTATION = "-30"; /** * 添加水印 临时路径返回文件 * @param is * @param waterMarkInfo * @param tempFileDirPath * @return */ @Override public String writeWithWaterMark(InputStream is, TextWaterMark waterMarkInfo, String tempFileDirPath) { if (is == null) { throw new BusinessException(new ErrorInfo("-1", "文件为空")); } File tempFileDir = new File(tempFileDirPath); if (!tempFileDir.exists()) { log.info("目录不存在,创建文件夹{}", tempFileDirPath); tempFileDir.mkdirs(); } //设置默认值 String watermark = StringUtils.isBlank(waterMarkInfo.getText()) ? DEFAULT_WATERMARK : waterMarkInfo.getText(); String color = StringUtils.isBlank(waterMarkInfo.getFontColor()) ? DEFAULT_FONT_COLOR : "#" waterMarkInfo.getFontColor(); String fontSize = (null == waterMarkInfo.getFontSize()) ? FONT_SIZE : waterMarkInfo.getFontSize() "pt"; String rotation = (null == waterMarkInfo.getRotation()) ? STYLE_ROTATION : "-" waterMarkInfo.getRotation(); //检查水印信息 去掉也行,暂时没有使用内部参数 checkTextParam(waterMarkInfo); XWPFDocument doc = null; try { doc = new XWPFDocument(is); addWaterMark(doc, watermark, color, fontSize, rotation); String filePath = tempFileDirPath File.separator System.currentTimeMillis() ".docx"; OutputStream os = new FileOutputStream(filePath); doc.write(os); return filePath; } catch (IOException e) { log.error("water mark word error: {}", e); throw new BusinessException(new ErrorInfo("-1", "添加异常水印的文件")); } finally { if (doc != null) { try { doc.close(); } catch (IOException e) { log.error("water mark word close error: {}", e); } } } } /** * 添加水印信息 * @param obj * @param watermark * @param color * @param fontSize * @param rotation */ private void addWaterMark(Object obj, String watermark, String color, String fontSize, String rotation) { if (obj instanceof XWPFDocument) { XWPFDocument doc = (XWPFDocument) obj; DocxUtil.makeFullWaterMarkByWordArt(doc, watermark, color, fontSize, rotation); } } }
service的代码
/** * @author qiankun.hu * @version 1.0.0 * @createTime 2022年05月30日 13:51:00 * @Description TODO */ public interface WaterMarkService { int DEFAULT_HORIZONTAL = 150; int DEFAULT_VERTICAL = 150; int DEFAULT_ROTATION = 30; int DEFAULT_FONT_SIZE = 50; float DEFAULT_ALPHA = 0.5f; String DEFAULT_COLOR = "d8d8d8"; String COLOR_REGEX = "^([0-9a-fA-F]{6})$"; /** * * @param is * @param waterMarkInfo * @param tempFileDirPath * @return */ String writeWithWaterMark(InputStream is, TextWaterMarkDto waterMarkInfo, String tempFileDirPath); /** * 可以去掉,没用到 */ default void checkTextParam(TextWaterMarkDto waterMark) { if (waterMark != null) { if (StringUtils.isBlank(waterMark.getText())) { throw new BusinessException(ErrorCodeEnum.PARAM_NULL.toErrorInfos(Placeholder.PARAM_WATER_TEXT.getCode())); } if (waterMark.getIntervalHorizontal() == null) { waterMark.setIntervalHorizontal(DEFAULT_HORIZONTAL); } else if (waterMark.getIntervalHorizontal() < 0) { throw new BusinessException(ErrorCodeEnum.PARAM_WATER_MARK_ERR.toErrorInfos(Placeholder.IntervalHorizontal.getCode(), " > 0")); } if (waterMark.getIntervalVertical() == null) { waterMark.setIntervalVertical(DEFAULT_VERTICAL); } else if (waterMark.getIntervalVertical() < 0) { throw new BusinessException(ErrorCodeEnum.PARAM_WATER_MARK_ERR.toErrorInfos(Placeholder.IntervalVertical.getCode(), " > 0")); } if (waterMark.getFontSize() == null) { waterMark.setFontSize(DEFAULT_FONT_SIZE);
} else if (waterMark.getFontSize() < 0) {
throw new BusinessException(ErrorCodeEnum.PARAM_WATER_MARK_ERR.toErrorInfos(Placeholder.FontSize.getCode(), "> 0"));
}
if (waterMark.getRotation() == null) {
waterMark.setRotation(DEFAULT_ROTATION);
} else if (waterMark.getRotation() > 90 || waterMark.getRotation() < 0) {
throw new BusinessException(ErrorCodeEnum.PARAM_WATER_MARK_ERR.toErrorInfos(Placeholder.Rotation.getCode(), "0-90"));
}
if (waterMark.getAlpha() == null) {
waterMark.setAlpha(DEFAULT_ALPHA);
} else if (waterMark.getAlpha() > 1 || waterMark.getAlpha() < 0) {
throw new BusinessException(ErrorCodeEnum.PARAM_WATER_MARK_ERR.toErrorInfos(Placeholder.Alpha.getCode(), "0-1"));
}
if (StringUtils.isBlank(waterMark.getFontColor())) {
waterMark.setFontColor(DEFAULT_COLOR);
} else {
Pattern p = Pattern.compile(COLOR_REGEX);
Matcher m = p.matcher(waterMark.getFontColor());
if (m.matches()) {
waterMark.setFontColor(m.group(1));
} else {
throw new BusinessException(ErrorCodeEnum.PARAM_WATER_MARK_ERR.toErrorInfos(Placeholder.FontColor.getCode(), "000000 - FFFFFF"));
}
}
}
}
}
单元测试测一下,完美输出
@Test
public void tempExport() throws Exception{
String path ="C:\\Users\\qiankun.hu\\AppData\\Local\\Temp\eTemp-1653899322026";
String name ="/1653899360909.docx";
String modelPath = path+name;//设置需要加水印的文件路径
//输入的docx文档
InputStream in = new FileInputStream(new File(modelPath));
TextWaterMarkDto waterMarkInfo = new TextWaterMarkDto();
waterMarkInfo.setText("hello");
waterMarkService.writeWithWaterMark(in,waterMarkInfo, path);
}
工具类的代码贴一下
import com.microsoft.schemas.office.office.CTLock;
import com.microsoft.schemas.vml.*;
import org.apache.poi.wp.usermodel.HeaderFooterType;
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.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import java.io.InputStream;
import java.util.stream.Stream;
/**
* @author qiankun.hu
* @version 1.0.0
* @createTime 2022年05月30日 13:57:00
* @Description word水印工具
*/
public class DocxUtil {
/**
* 水印参数
*/
// private static final String fontColor = "#D3D3D3"; // 字体颜色
/**
* 艺术字水印参数
*/
private static final String fontName = "PingFang SC"; // word字体
// private static final String fontSize = "0.5pt"; // 字体大小
private static final int widthPerWord = 10; // 一个字平均长度,单位pt,用于:计算文本占用的长度(文本总个数*单字长度)
// private static final String styleRotation = "-45"; // 文本旋转角度
/**
* 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");
}
}
/**
* 以艺术字方式加上水印(平铺)
* @param docx XWPFDocument对象
* @param customText 水印文字
*/
public static void makeFullWaterMarkByWordArt(XWPFDocument docx, String customText,String fontColor,String fontSize,String styleRotation) {
customText = customText + repeatString(" ", 16); // 水印文字之间使用8个空格分隔
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);
}
/**
* 将指定的字符串重复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);
}
/**
* 加载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;
}
/**
* 构建Shape的样式参数
* @param customText 水印文本
* @return
*/
private static String getShapeStyle(String customText, String styleTop,String styleRotation) {
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(";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();
sb.append("position: ").append("absolute"); // 文本path绘制的定位方式
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();
}
}