在Web项目中,对于MS Ofice类型的文档有时会存在加密或权限限制的需求,除了对文档增加限制修改密码外,一般需要增加水印以达到溯源的效果。
本文基于POI完成了对常见MS Offce文档(Word/PPT/PDF/Excel)的水印添加操作。
添加文档水印的基本步骤是先根据需求生成对应的图像,再通过对应MS Office文档对象提供的方法将图片插入。
不同的文档提供的接口不同,有的自带写水印的方法,有的需要引用第三方组件,有的需要使用awt生成图像写入。
XWPFHeaderFooterPolicy 官方文档
public static void addWordWaterMark(String inputPath, String outPath, String markStr) {
// 读取原始文件
File inputFile = new File(inputPath);
XWPFDocument doc = null;
try {
doc = new XWPFDocument(new FileInputStream(inputFile));
} catch (FileNotFoundException var24) {
throw new RuntimeException("源文件不存在");
} catch (IOException var25) {
throw new RuntimeException("读取源文件IO异常");
} catch (Exception var26) {
throw new RuntimeException("不支持的文档格式");
}
// 使用自带工具类完成水印填充
XWPFHeaderFooterPolicy headerFooterPolicy = doc.getHeaderFooterPolicy();
headerFooterPolicy.createWatermark(markStr);
// 设置文档只读
doc.enforceReadonlyProtection();
// 生成输出文件
File file = new File(outPath);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException var23) {
throw new RuntimeException("创建输出文件失败");
}
}
// 打开输出流,将doc写入输出文件
OutputStream os = null;
try {
os = new FileOutputStream(outPath);
doc.write(os);
} catch (FileNotFoundException var21) {
throw new RuntimeException("创建输出文件失败");
} catch (Exception var22) {
throw new RuntimeException("添加文档水印失败");
} finally {
if (os != null) {
try {
os.close();
} catch (IOException var20) {
var20.printStackTrace();
}
}
}
}
使用了国产PPT组件:Spire-Presentation-JAVA,但是从官网下载的组件会有免费版限制,请自行决定要不要使用。
Spire-Presentation-JAVA 官网
官方教程-添加文字水印
public static void addPptWaterMark(String inputPath, String outPath, String markStr) {
//加载PPT源文档
Presentation ppt = new Presentation();
// 设置PPT密码
ppt.encrypt("Ka_ze");
try {
ppt.loadFromFile(inputPath);
ISlide slide = null;
//获取指定幻灯片
for (int o = 0; o < ppt.getSlides().getCount(); o++) {
slide = ppt.getSlides().get(o);
//设置文本水印文本宽和高
int width= 110;
int height= 80;
//起始坐标
float x = 10;
float y = 40;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
//绘制文本,设置文本格式并将其添加到第一张幻灯片
// 定义一个长方形区域
Rectangle2D.Double rect = new Rectangle2D.Double(x,y,width, height);
// 添加一个shape到定义区域
IAutoShape shape = slide.getShapes().appendShape(ShapeType.RECTANGLE, rect);
// 设置shape样式
shape.getFill().setFillType(FillFormatType.NONE);
shape.getShapeStyle().getLineColor().setColor(Color.white);
shape.setRotation(-45);
shape.getLocking().setSelectionProtection(true);
shape.getLine().setFillType(FillFormatType.NONE);
// 添加文本到shape
shape.getTextFrame().setText(markStr);
shape.setShapeArrange(ShapeAlignmentEnum.ShapeArrange.SendToBack);
// 设置文本水印样式
PortionEx textRange = shape.getTextFrame().getTextRange();
textRange.getFill().setFillType(FillFormatType.SOLID);
textRange.getFill().getSolidColor().setColor(new Color(Integer.parseInt("#C5CBCF".substring(1), 16)));
textRange.setFontHeight(20);
// 设置下一个水印的横坐标
x += (100 + ppt.getSlideSize().getSize().getWidth()/6);
}
x = 30;
// 设置下一个水印的纵坐标
y += (100 + ppt.getSlideSize().getSize().getHeight()/7) ;
}
}
//保存文档
ppt.saveToFile(outPath, FileFormat.PPTX_2013);
ppt.dispose();
} catch (Exception e) {
e.printStackTrace();
}
}
使用了开源组件iText,可以用于创建和操作 PDF 文档。
maven仓库
官方文档-itext-v7.0.0
官方Github
<dependency>
<groupId>com.itextpdfgroupId>
<artifactId>itext-asianartifactId>
<version>5.2.0version>
dependency>
<dependency>
<groupId>com.itextpdfgroupId>
<artifactId>itextpdfartifactId>
<version>5.4.3version>
dependency>
public static void addPDFWaterMark(String input, String output, String waterMarkName) {
BufferedOutputStream bos = null;
try {
// 读取文件,生成reader
com.itextpdf.text.pdf.PdfReader reader = new com.itextpdf.text.pdf.PdfReader(input);
// 生成输出文件,开启输出流
bos = new BufferedOutputStream(new FileOutputStream(new File(output)));
// 读取输出流,生成stamper(印章)
com.itextpdf.text.pdf.PdfStamper stamper = new com.itextpdf.text.pdf.PdfStamper(reader, bos);
// 设置stamper加密
stamper.setEncryption(null, "Ka_ze".getBytes(StandardCharsets.UTF_8), PdfWriter.ALLOW_PRINTING, PdfWriter.STANDARD_ENCRYPTION_128);
// 获取总页数 +1, 下面从1开始遍历
int total = reader.getNumberOfPages() + 1;
// 使用classpath下面的字体库
com.itextpdf.text.pdf.BaseFont base = null;
try {
base = com.itextpdf.text.pdf.BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", com.itextpdf.text.pdf.BaseFont.EMBEDDED);
} catch (Exception e) {
// 日志处理
e.printStackTrace();
}
// 间隔
int interval = -15;
// 获取水印文字的高度和宽度
int textH = 0, textW = 0;
JLabel label = new JLabel();
label.setText(waterMarkName);
FontMetrics metrics = label.getFontMetrics(label.getFont());
textH = metrics.getHeight();
textW = metrics.stringWidth(label.getText());
// 设置水印透明度
com.itextpdf.text.pdf.PdfGState gs = new com.itextpdf.text.pdf.PdfGState();
gs.setFillOpacity(0.2f);
gs.setStrokeOpacity(0.7f);
com.itextpdf.text.Rectangle pageSizeWithRotation = null;
PdfContentByte content = null;
for (int i = 1; i < total; i++) {
// 在内容上方加水印
content = stamper.getOverContent(i);
// 在内容下方加水印
// content = stamper.getUnderContent(i);
content.saveState();
content.setGState(gs);
// 设置字体和字体大小
content.beginText();
content.setFontAndSize(base, 20);
// 获取每一页的高度、宽度
pageSizeWithRotation = reader.getPageSizeWithRotation(i);
float pageHeight = pageSizeWithRotation.getHeight();
float pageWidth = pageSizeWithRotation.getWidth();
// 根据纸张大小多次添加, 水印文字成30度角倾斜
for (int height = interval + textH; height < pageHeight; height = height + textH * 6) {
for (int width = interval + textW; width < pageWidth + textW; width = width + textW * 2) {
content.showTextAligned(Element.ALIGN_LEFT, waterMarkName, width - textW, height - textH, 30);
}
}
content.endText();
}
// 关流
stamper.close();
reader.close();
} catch (DocumentException | IOException e) {
e.printStackTrace();
}
}
使用awt包的Graphics2D设置图像属性。
private static void addExcelWaterMark(String inputSrc, String outputSrc, String waterMarkName) {
// 读取水印文字
String[] textArray = waterMark.split("\n");
// 设置字体
Font font = new Font("microsoft-yahei", Font.PLAIN, 20);
// 单元水印图片宽高
int width = 500;
int height = 200;
// 缓冲区图片对象
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 基于特定宽高,创建图形对象
Graphics2D g = image.createGraphics();
// 基于图形对象生成半透明图片
image = g.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
// 释放画笔
g.dispose();
// 基于半透明图片,创建图形对象
g = image.createGraphics();
// 设置图形颜色
g.setColor(new Color(Integer.parseInt("#C5CBCF".substring(1), 16)));
// 设置图形字体
g.setFont(font);
// 设置图形倾斜度
g.shear(0.1, -0.26);
// 设置字体平滑
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 画出字符串
int y = 150;
for (int i = 0; i < textArray.length; i++) {
g.drawString(textArray[i], 0, y);
y = y + font.getSize();
}
// 释放画笔
g.dispose();
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
// 将内存中的图片写入至输出流
ImageIO.write(image, "png", os);
// 读取原文档
XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream(inputSrc));
// 将图片输出流写入Excel
int pictureIdx = workbook.addPicture(os.toByteArray(), Workbook.PICTURE_TYPE_PNG);
// 获取工作簿中的图片对象
POIXMLDocumentPart poixmlDocumentPart = workbook.getAllPictures().get(pictureIdx);
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {//获取每个Sheet表
XSSFSheet sheet = workbook.getSheetAt(i);
PackagePartName ppn = poixmlDocumentPart.getPackagePart().getPartName();
String relType = XSSFRelation.IMAGES.getRelation();
// 为当前Sheet添加图片关系
PackageRelationship pr = sheet.getPackagePart().addRelationship(ppn, TargetMode.INTERNAL, relType, null);
// 将图片关系添加到Sheet底层结构中
sheet.getCTWorksheet().addNewPicture().setId(pr.getId());
}
try {
workbook.write(new FileOutputStream(outputSrc));
} finally {
os.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}