PDF水印工具,实现平铺、左上、中上、右上、左中、正中、右中、左下、中下、右下10种水印方式。
使用itext,依赖:
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
代码:
/**
* pdf水印工具类
*
* @author : RanPengCheng
* @date : 2023/3/2 上午9:53
*/
public class PdfWaterMarkUtil2 {
private PdfWaterMarkUtil2(){}
/**
* 水印的横向间隔
*/
private static final float LEN_OFFSET = 100;
/**
* 平铺水印的,上下水印的错位比例
*/
private static final float TILE_MALPOSITION = 0.3f;
/** 平铺 */
public static final int TILE = 0;
/** 左上 */
public static final int TOP_LEFT = 1;
/** 中上 */
public static final int TOP_CENTER = 2;
/** 右上 */
public static final int TOP_RIGHT = 3;
/** 左中 */
public static final int CENTER_LEFT = 4;
/** 正中 */
public static final int CENTER = 5;
/** 右中 */
public static final int CENTER_RIGHT = 6;
/** 左下 */
public static final int BOTTOM_LEFT = 7;
/** 中下 */
public static final int BOTTOM_CENTER = 8;
/** 右下 */
public static final int BOTTOM_RIGHT = 9;
/** 字体缓存 */
private static final Map<String, BaseFont> FONT_MAP = new HashMap<>();
private static final Map<Integer, PdfGState> GS_MAP = new HashMap<>();
/** 长度(厘米)到像素(px)的换算系数 */
private static final float LEN_TO_PX = 28.347618466331845f;
public static void addWaterMark(String srcPdfFile, OutputStream out, List<MarkInfo> markInfoList) throws Exception {
addWaterMark(new PdfReader(srcPdfFile), out, markInfoList);
}
public static void addWaterMark(File pdfFile, OutputStream out, List<MarkInfo> markInfoList) throws Exception {
addWaterMark(new PdfReader(new FileInputStream(pdfFile)), out, markInfoList);
}
public static void addWaterMark(URL pdfUrl, OutputStream out, List<MarkInfo> markInfoList) throws Exception {
addWaterMark(new PdfReader(pdfUrl), out, markInfoList);
}
private static void addWaterMark(PdfReader reader, OutputStream out, List<MarkInfo> markInfoList) throws Exception {
// 加完水印的文件
PdfStamper pdfStamper = new PdfStamper(reader, out);
int pageNumber = reader.getNumberOfPages() + 1;
// 循环对每页插入水印
for (int i = 1; i < pageNumber; i++) {
Rectangle pageSize = reader.getPageSize(i);
float height = pageSize.getHeight();
float width = pageSize.getWidth();
// 水印在之前文本上
PdfContentByte content = pdfStamper.getOverContent(i);
// 开始
content.beginText();
for (MarkInfo markInfo : markInfoList) {
// 设置透明度
content.setGState(getPdfGState(markInfo.getPellucidity()));
// 设置字体及大小
content.setFontAndSize(getFont(markInfo.getFontFamily()), markInfo.getFontSize());
// 设置字体颜色
content.setColorFill(Color.get(markInfo.getFontColor()));
// 水印类型
switch (markInfo.getPositionType()) {
case TILE:
tile(markInfo, height, width, content);
break;
case TOP_LEFT:
topLeft(markInfo, height, width, content);
break;
case TOP_CENTER:
topCenter(markInfo, height, width, content);
break;
case TOP_RIGHT:
topRight(markInfo, height, width, content);
break;
case CENTER_LEFT:
centerLeft(markInfo, height, width, content);
break;
case CENTER:
center(markInfo, height, width, content);
break;
case CENTER_RIGHT:
centerRight(markInfo, height, width, content);
break;
case BOTTOM_LEFT:
bottomLeft(markInfo, height, width, content);
break;
case BOTTOM_CENTER:
bottomCenter(markInfo, height, width, content);
break;
case BOTTOM_RIGHT:
bottomRight(markInfo, height, width, content);
break;
default:
}
}
//结束
content.endText();
}
pdfStamper.close();
}
private static PdfGState getPdfGState(int pellucidity) {
PdfGState gs = GS_MAP.get(pellucidity);
if (gs == null) {
gs = new PdfGState();
gs.setFillOpacity((100 - pellucidity) / 100f);
GS_MAP.put(pellucidity, gs);
}
return gs;
}
private static void bottomRight(MarkInfo markInfo, float height, float width, PdfContentByte content) {
final String markText = markInfo.getMarkText();
final float rotation = markInfo.getRotation();
float x = getRightX(markInfo, width, content);
float y = getBottomY(markInfo, height, content);
content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
}
private static void bottomCenter(MarkInfo markInfo, float height, float width, PdfContentByte content) {
final String markText = markInfo.getMarkText();
final float rotation = markInfo.getRotation();
float x = getCenterX(markInfo, width, content);
float y = getBottomY(markInfo, height, content);
content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
}
private static void bottomLeft(MarkInfo markInfo, float height, float width, PdfContentByte content) {
final String markText = markInfo.getMarkText();
final float rotation = markInfo.getRotation();
float x = getLeftX(markInfo, width, content);
float y = getBottomY(markInfo, height, content);
content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
}
private static void centerRight(MarkInfo markInfo, float height, float width, PdfContentByte content) {
final String markText = markInfo.getMarkText();
final float rotation = markInfo.getRotation();
float x = getRightX(markInfo, width, content);
float y = getCenterY(markInfo, height, content);
content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
}
private static void centerLeft(MarkInfo markInfo, float height, float width, PdfContentByte content) {
final String markText = markInfo.getMarkText();
final float rotation = markInfo.getRotation();
float x = getLeftX(markInfo, width, content);
float y = getCenterY(markInfo, height, content);
content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
}
private static void topRight(MarkInfo markInfo, float height, float width, PdfContentByte content) {
final String markText = markInfo.getMarkText();
final float rotation = markInfo.getRotation();
float x = getRightX(markInfo, width, content);
float y = getTopY(markInfo, height, content);
content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
}
private static void topCenter(MarkInfo markInfo, float height, float width, PdfContentByte content) {
final String markText = markInfo.getMarkText();
final float rotation = markInfo.getRotation();
float x = getCenterX(markInfo, width, content);
float y = getTopY(markInfo, height, content);
content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
}
private static void topLeft(MarkInfo markInfo, float height, float width, PdfContentByte content) {
final String markText = markInfo.getMarkText();
final float rotation = markInfo.getRotation();
float x = getLeftX(markInfo, width, content);
float y = getTopY(markInfo, height, content);
content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
}
private static void center(MarkInfo markInfo, float height, float width, PdfContentByte content) {
final String markText = markInfo.getMarkText();
final float rotation = markInfo.getRotation();
float x = getCenterX(markInfo, width, content);
float y = getCenterY(markInfo, height, content);
content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
}
private static float getBottomY(MarkInfo markInfo, float height, PdfContentByte content) {
final String markText = markInfo.getMarkText();
float textLength = content.getEffectiveStringWidth(markText, false);
final float rotation = markInfo.getRotation();
float bottomMargin = markInfo.getYMargin() * LEN_TO_PX;
if (rotation >= 0) {
return bottomMargin;
}
return bottomMargin - textLength * sin(rotation);
}
private static float getCenterY(MarkInfo markInfo, float height, PdfContentByte content) {
final String markText = markInfo.getMarkText();
float textLength = content.getEffectiveStringWidth(markText, false);
final float rotation = markInfo.getRotation();
final float yLen = textLength * sin(rotation);
final float halfSize = markInfo.getFontSize() / 2f;
return (height - yLen) / 2 + halfSize * (1 - cos(rotation));
}
private static float getTopY(MarkInfo markInfo, float height, PdfContentByte content) {
final String markText = markInfo.getMarkText();
float textLength = content.getEffectiveStringWidth(markText, false);
final int fontSize = markInfo.getFontSize();
final float rotation = markInfo.getRotation();
float topMargin = markInfo.getYMargin() * LEN_TO_PX;
if (rotation >= 0) {
return height -topMargin - fontSize * cos(rotation) - textLength * sin(rotation);
}
return height - topMargin - fontSize * cos(rotation);
}
private static float getRightX(MarkInfo markInfo, float width, PdfContentByte content) {
final String markText = markInfo.getMarkText();
float textLength = content.getEffectiveStringWidth(markText, false);
final int fontSize = markInfo.getFontSize();
final float rotation = markInfo.getRotation();
final float rightMargin = markInfo.getXMargin() * LEN_TO_PX;
if (rotation >= 0) {
return width - rightMargin - textLength * cos(rotation);
}
return width - rightMargin - textLength * cos(rotation) + fontSize * sin(rotation);
}
private static float getCenterX(MarkInfo markInfo, float width, PdfContentByte content) {
final String markText = markInfo.getMarkText();
float textLength = content.getEffectiveStringWidth(markText, false);
final float halfSize = markInfo.getFontSize() / 2f;
final float rotation = markInfo.getRotation();
final float xLen = textLength * cos(rotation);
return (width - xLen) / 2 + halfSize * sin(rotation);
}
private static float getLeftX(MarkInfo markInfo, float width, PdfContentByte content) {
final int fontSize = markInfo.getFontSize();
final float rotation = markInfo.getRotation();
float leftMargin = markInfo.getXMargin() * LEN_TO_PX;
if (rotation <= 0 ) {
return leftMargin;
}
return leftMargin + fontSize * sin(rotation);
}
/**
* 平铺
* 1、先找出为旋转的时候平铺的点坐标,再绕中心点旋转
* 2、文档正中心始终有一个水印
*/
private static void tile(MarkInfo markInfo, float height, float width, PdfContentByte content) {
float textLength = content.getEffectiveStringWidth(markInfo.getMarkText(), false);
final int fontSize = markInfo.getFontSize();
// 正中心的条水印的坐标,所有其他水印的坐标由该坐标推算出来
float x = (width - textLength) / 2;
float y = (height - fontSize) / 2;
// 文档外切圆的半径
final float radius = (float) Math.sqrt(height * height + width * width) / 2f;
// 相邻水印的x坐标偏移量
final float xOffset = textLength + LEN_OFFSET;
// 相邻水印的y坐标偏移量
final float yOffset = markInfo.getLineSpacing() * fontSize;
final float centerX = width / 2;
float textCenterX = x + textLength / 2;
float relativeX = Math.abs(textCenterX - centerX);
while (relativeX < radius) {
x -= xOffset;
textCenterX = x + textLength / 2;
relativeX = Math.abs(textCenterX - centerX);
}
diffusion(x, y, 1, 1, markInfo, height, width, content);
diffusion(x - xOffset * TILE_MALPOSITION, y - yOffset, 1, -1, markInfo, height, width, content);
}
private static void diffusion(float x, float y, int xDirection, int yDirection, MarkInfo markInfo, float height, float width, PdfContentByte content) {
final float rotation = markInfo.getRotation();
final float sin = sin(rotation);
final float cos = cos(rotation);
float textLength = content.getEffectiveStringWidth(markInfo.getMarkText(), false);
final int fontSize = markInfo.getFontSize();
final float radius = (float) Math.sqrt(height * height + width * width) / 2f;
final float xOffset = textLength + LEN_OFFSET;
final float yOffset = markInfo.getLineSpacing() * fontSize;
final float centerX = width / 2;
final float centerY = height / 2;
final float xLen = textLength * cos;
final float yLen = textLength * sin;
float textCenterX = x + textLength / 2;
float textCenterY = y + fontSize / 2f;
float relativeY = Math.abs(textCenterY - centerY);
if (relativeY > radius) {
return;
}
float relativeX = Math.abs(textCenterX - centerX);
while (relativeX > radius) {
x += xOffset * xDirection;
textCenterX = x + textLength / 2;
relativeX = Math.abs(textCenterX - centerX);
}
while (relativeX < radius) {
float newX = (x - centerX) * cos - (y - centerY) * sin + centerX;
float newY = (x - centerX) * sin + (y - centerY) * cos + centerY;
x += xOffset * xDirection;
textCenterX = x + textLength / 2;
relativeX = Math.abs(textCenterX - centerX);
boolean outOfRange = (newX < 0 && newX + xLen < 0)
|| (newX > width && (newX + xLen) > width)
|| (newY < 0 && (newY + yLen < 0))
|| (newY > height && ((newY + yLen) > height));
if (outOfRange) {
continue;
}
content.showTextAligned(Element.ALIGN_LEFT, markInfo.getMarkText(), newX, newY, rotation);
}
x += xOffset * TILE_MALPOSITION * yDirection;
xDirection *= -1;
y += yOffset * yDirection;
diffusion(x, y, xDirection, yDirection, markInfo, height, width, content);
}
private static float sin(float rotation) {
return (float) Math.sin(Math.toRadians(rotation));
}
private static float cos(float rotation) {
return (float) Math.cos(Math.toRadians(rotation));
}
private static BaseFont getFont(String fontFamily) throws IOException, DocumentException {
BaseFont baseFont = FONT_MAP.get(fontFamily);
if (baseFont == null) {
String fontName = "./fonts/SimSun.ttf";
switch (fontFamily) {
case "NSimSun":
fontName = "./fonts/simsun_new.ttf";
break;
case "SimHei":
fontName = "./fonts/SIMHEI.TTF";
break;
case "KaiTi":
fontName = "./fonts/simkai.ttf";
break;
default:
}
baseFont = BaseFont.createFont(PdfWaterMarkUtil2.class.getClassLoader().getResource(fontName).getPath(), BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
FONT_MAP.put(fontFamily, baseFont);
}
return baseFont;
}
public static void main(String[] args) throws Exception {
try (final FileInputStream is = new FileInputStream(new File("/Users/ranpengcheng/Desktop/1.pdf"));
FileOutputStream out = new FileOutputStream(new File("/Users/ranpengcheng/Desktop/test444.pdf"))
) {
String s = "测试水印";
final PdfReader reader = new PdfReader(is);
MarkInfo m1 = new MarkInfo();
m1.setPellucidity(45);
m1.setPositionType(TILE);
m1.setFontColor(Color.BLUE);
m1.setFontFamily("KaiTi");
m1.setMarkText(s);
m1.setFontSize(18);
m1.setRotation(30);
m1.setXMargin(1f);
m1.setYMargin(0.1f);
List<MarkInfo> list = new ArrayList<>();
list.add(m1);
addWaterMark(reader, out, list);
}
}
public static class Color {
public static final int BLANK = 1;
public static final int RED = 2;
public static final int BLUE = 3;
public static BaseColor get(int fontColor) {
switch (fontColor) {
case BLANK:
return BaseColor.BLACK;
case RED:
return BaseColor.RED;
default:
return BaseColor.BLUE;
}
}
}
@Data
public static class MarkInfo {
/**
* 水印文字
*/
private String markText;
/**
* 字体
*/
private String fontFamily = "SimSun";
/**
* 字号
*/
private int fontSize = 12;
/**
* 字体颜色
*/
private int fontColor = 1;
/**
* 透明度(0-100,0全完不透明,100完全透明)
*/
private int pellucidity = 30;
/**
* 水印位置类型
*/
private int positionType = 0;
/**
* 倾斜度(-90至90)
*/
private float rotation = 45;
/**
* 左/右边距(厘米),最大15
*/
private float xMargin = 3;
/**
* 上/下边距(厘米),最大15
*/
private float yMargin = 2;
/**
* 行距(字体倍数)
*/
private int lineSpacing = 5;
}
}
669

被折叠的 条评论
为什么被折叠?



