Pdf文件导出生成斜体水印工具类方法
1、需要引入的jar包
java
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.28</version>
</dependency>
2、具体工具类(添加完水印后,需要根据实际情况返回对应的流或者上传OSS后拿到对应的带水印的下载链接)
java
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class PdfWaterMarkUtil {
@Autowired
private MessageSource messageSource;
/**
* 基础字体大小
*/
private static final int FONT_SIZE = 15;
/**
* 水平间距
*/
private static final float HORIZONTAL_SPACING = 100;
/**
* 垂直间距
*/
private static final float VERTICAL_SPACING = 100;
/**
* 透明度 (0.1-0.3效果最佳)
*/
private static final float OPACITY = 0.1f;
/**
* 旋转角度
*/
private static final float ROTATION_ANGLE = 30;
/**
* @param downloadUrl 原下载地址(不带水印)
* @param watermarkText 水印文本内容(暂不支持中文)
* @param fileName 文件名称
*/
public String addWaterMarker(String downloadUrl, String watermarkText, String fileName) {
long beginTime = System.currentTimeMillis();
try (PDDocument document = PDDocument.load(URI.create(downloadUrl).toURL().openStream());
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
// 加载字体
PDType0Font font = PDType0Font.load(document,
PdfWaterMarkUtil.class.getResourceAsStream(
"/org/apache/pdfbox/resources/ttf/LiberationSans-Regular.ttf"
));
PDExtendedGraphicsState gs = new PDExtendedGraphicsState();
gs.setNonStrokingAlphaConstant(OPACITY);
// 遍历所有页面
for (PDPage page : document.getPages()) {
PDRectangle pageSize = page.getMediaBox();
float pageWidth = pageSize.getWidth();
float pageHeight = pageSize.getHeight();
// 计算水印网格尺寸
float watermarkWidth = estimateTextWidth(watermarkText, font, FONT_SIZE);
float watermarkHeight = FONT_SIZE;
// 计算网格行列数(添加间距)
int cols = (int) (pageWidth / (watermarkWidth + HORIZONTAL_SPACING)) + 1;
int rows = (int) (pageHeight / (watermarkHeight + VERTICAL_SPACING)) + 1;
// 计算起始偏移量(居中显示)
float xOffset = (pageWidth - (cols - 1) * (watermarkWidth + HORIZONTAL_SPACING)) / 2;
float yOffset = (pageHeight - (rows - 1) * (watermarkHeight + VERTICAL_SPACING)) / 2;
// 创建内容流
try (PDPageContentStream contentStream = new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
// 设置透明度
contentStream.setGraphicsStateParameters(
new org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState());
// 浅灰色
contentStream.setNonStrokingColor(200/255.0F, 200/255.0F, 200/255.0F);
contentStream.setGraphicsStateParameters(gs);
// 绘制水印网格
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
float x = xOffset + col * (watermarkWidth + HORIZONTAL_SPACING);
float y = yOffset + row * (watermarkHeight + VERTICAL_SPACING);
// 设置旋转矩阵
Matrix matrix = Matrix.getRotateInstance(
Math.toRadians(ROTATION_ANGLE), x, y);
contentStream.beginText();
contentStream.setTextMatrix(matrix);
contentStream.setFont(font, FONT_SIZE);
// 灰色
contentStream.setNonStrokingColor(128/255.0F, 128/255.0F, 128/255.0F);
contentStream.showText(watermarkText);
contentStream.endText();
}
}
}
}
// document.save(outputPath);
//获取带水印的输入流
document.save(out);
ByteArrayInputStream inputStream = new ByteArrayInputStream(out.toByteArray());
//上传到oss,然后获取新的带水印的下载链接
StandardFileService standardFileService = fileServices.get(MideaOssAutoConfiguration.DEFAULT_BEAN_NAME);
String ossName;
if (StringUtils.isNotBlank(fileName)) {
ossName = fileName + ".pdf";
} else {
String ossId = UUID.randomUUID().toString().replaceAll("-", "");
ossName = ossId + ".pdf";
}
standardFileService.upload(inputStream, ossName);
String watermarkDownloadUrl = standardFileService.generatePresignedUrl(ossName, ossProperties.getBucketName(), 7200L,
Collections.singletonList(new MideaOssAmzUserMeta(UserUtils.getFdLoginName())));
log.info("添加水印成功!一共耗时" + (System.currentTimeMillis() - beginTime) + "毫秒");
return watermarkDownloadUrl;
} catch (Exception e) {
throw new BusinessException(ErrorCode.WATERMARK_DOWNLOAD_URL_GET_FAIL.getErrorCode(),
ErrorCode.WATERMARK_DOWNLOAD_URL_GET_FAIL.getErrorKey(),
ErrorCode.WATERMARK_DOWNLOAD_URL_GET_FAIL.getErrorMsg());
}
}
private static float estimateTextWidth(String text, PDFont font, float fontSize) throws IOException {
return font.getStringWidth(text) / 1000 * fontSize;
}
}
Docx文件导出生成斜体水印工具类方法
1、需要引入的jar包
java
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
2、具体工具类(添加完水印后,需要根据实际情况返回对应的流或者上传OSS后拿到对应的带水印的下载链接)
java
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.microsoft.schemas.office.office.CTLock;
import com.microsoft.schemas.vml.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.openxml4j.opc.OPCPackage;
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.officeDocument.x2006.sharedTypes.STTrueFalse;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URI;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Stream;
@Slf4j
@Component
public class DocxWaterMarkUtil {
/**
* word字体
*/
private static final String FONT_NAME = "宋体";
/**
* 字体大小
*/
private static final String FONT_SIZE = "0.3pt";
/**
* 字体颜色
*/
private static final String FONT_COLOR = "#C0C0C0";
/**
* 一个字平均长度,单位pt,用于:计算文本占用的长度(文本总个数*单字长度)
*/
private static final Integer WIDTH_PER_WORD = 10;
/**
* 文本旋转角度
*/
private static final String STYLE_ROTATION = "-30";
/**
* 水印透明度
*/
private static final String OPACITY = "0.2";
@Autowired
private MessageSource messageSource;
public String waterMarkDocXDocument(String downloadUrl, String watermarkText, String fileName) {
long beginTime = System.currentTimeMillis();
try (
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream in = URI.create(downloadUrl).toURL().openStream();
OPCPackage srcPackage = OPCPackage.open(in);
XWPFDocument doc = new XWPFDocument(srcPackage)
) {
// 把整页都打上水印
int i = 0;
int marginTop;
for (int lineIndex = -1; lineIndex < 6; lineIndex++) {
marginTop = 300 * lineIndex;
waterMarkDocXDocument(doc, watermarkText, i, marginTop);
i++;
}
// 输出新文档
// doc.write(out);
//TODO:获取带水印文档的输出流
doc.write(out);
//转换为字节输入流
ByteArrayInputStream inputStream = new ByteArrayInputStream(out.toByteArray());
//上传到oss,然后获取新的带水印的下载链接
StandardFileService standardFileService = fileServices.get(MideaOssAutoConfiguration.DEFAULT_BEAN_NAME);
String ossName;
if (StringUtils.isNotBlank(fileName)) {
ossName = fileName + ".docx";
} else {
String ossId = UUID.randomUUID().toString().replaceAll("-", "");
ossName = ossId + ".docx";
}
standardFileService.upload(inputStream, ossName);
String watermarkDownloadUrl = standardFileService.generatePresignedUrl(ossName, ossProperties.getBucketName(), 7200L,
Collections.singletonList(new MideaOssAmzUserMeta(UserUtils.getFdLoginName())));
log.info("添加水印成功!一共耗时" + (System.currentTimeMillis() - beginTime) + "毫秒");
return watermarkDownloadUrl;
} catch (Exception e) {
throw new BusinessException(ErrorCode.WATERMARK_DOWNLOAD_URL_GET_FAIL.getErrorCode(),
ErrorCode.WATERMARK_DOWNLOAD_URL_GET_FAIL.getErrorKey(),
ErrorCode.WATERMARK_DOWNLOAD_URL_GET_FAIL.getErrorMsg());
}
}
private static void waterMarkDocXDocument(XWPFDocument doc, String fingerText, int i, int marginTop) {
// 水印文字之间使用8个空格分隔
fingerText = fingerText + repeatString(" ", 8);
// 一行水印重复水印文字次数
fingerText = repeatString(fingerText, 5);
// 如果之前已经创建过 DEFAULT 的Header,将会复用
XWPFHeader header = doc.createHeader(HeaderFooterType.DEFAULT);
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();
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.EDIT);
CTShape shape = group.addNewShape();
shape.setId("PowerPlusWaterMarkObject" + (i + 1));
shape.setSpid("_x0000_s206" + (i + 1));
shape.setSpt(136f);
shape.setType("#_x0000_t136");
// 设置形状样式(旋转,位置,相对路径等参数)
shape.setStyle(getShapeStyle(fingerText, i, marginTop));
shape.setFillcolor(FONT_COLOR);
shape.setFilled(STTrueFalse.T);
shape.setCoordsize("21600,21600");
shape.setAdj("10800");
// 字体设置为实心
shape.setStroked(STTrueFalse.FALSE);
// 绘制文本的路径
// 设置文本字体与大小
CTTextPath shapeTextPath = shape.addNewTextpath();
shapeTextPath.setStyle("font-family:" + FONT_NAME + ";font-size:" + FONT_SIZE);
shapeTextPath.setString(fingerText);
//透明度填充
CTFill ctFill = shape.addNewFill();
ctFill.setOpacity(OPACITY);
CTPicture pict = ctr.addNewPict();
pict.set(group);
}
private static String getShapeStyle(String fingerText, int i, int marginTop) {
StringBuilder sb = new StringBuilder();
// 文本path绘制的定位方式
sb.append("position: ").append("absolute");
// 计算文本占用的长度(文本总个数*单字长度)
sb.append(";width: ").append(fingerText.length() * WIDTH_PER_WORD).append("pt");
// 字体高度
sb.append(";height: ").append("20pt");
sb.append(";z-index: ").append(-251645952 + i);
sb.append(";mso-wrap-edited: ").append("f");
// 设置水印的间隔,这是一个大坑,不能用top,必须要margin-top。
sb.append(";margin-top: ").append(marginTop);
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(STYLE_ROTATION);
return sb.toString();
}
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);
}
}
以上代码复制遇到爆红属于正常情况,需要自己根据实际情况调整为自己的业务代码。