Việc chèn watermark vào tài liệu PDF là nhu cầu phổ biến nhằm bảo vệ bản quyền hoặc đánh dấu tài liệu nội bộ. Thay vì sử dụng công cụ trả phí, bạn có thể tự triển khai giải pháp miễn phí dựa trên thư viện mã nguồn mở.
Thiết lập phụ thuộc Maven
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
Cấu hình tham số watermark
private static final String FONT_PATH = "fonts/simfang.ttf";
private static final float ALPHA = 0.25f; // Độ trong suốt
private static final int FONT_PT = 28; // Kích thước chữ
private static final float MARGIN = 35f; // Lề văn bản
private static final float LINE_GAP = 45f; // Khoảng cách dòng
private static final int DEGREE = -40; // Góc xoay (ngược chiều kim đồng hồ)
Xử lý font chữ
private static BaseFont loadFont() throws DocumentException, IOException {
return BaseFont.createFont(
WriteWatermarkUtil.class.getResource(FONT_PATH).getPath(),
BaseFont.IDENTITY_H,
BaseFont.EMBEDDED
);
}
Thêm watermark cho một file PDF
public static void addWatermark(String srcPath, String destPath, String mark)
throws IOException, DocumentException {
if (srcPath == null || srcPath.isBlank() || destPath == null || destPath.isBlank()) {
throw new IllegalArgumentException("Đường dẫn không hợp lệ");
}
File destFile = new File(destPath);
destFile.getParentFile().mkdirs();
try (PdfReader reader = new PdfReader(srcPath);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(destFile))) {
PdfGState state = new PdfGState();
state.setFillOpacity(ALPHA);
BaseFont bf = loadFont();
JLabel tempLabel = new JLabel(mark);
FontMetrics fm = tempLabel.getFontMetrics(tempLabel.getFont());
int charHeight = fm.getHeight() * 2;
int charWidth = fm.stringWidth(mark) * 2;
for (int pageNum = 1; pageNum <= reader.getNumberOfPages(); pageNum++) {
PdfContentByte canvas = stamper.getOverContent(pageNum);
canvas.beginText();
canvas.setGState(state);
canvas.setColorFill(BaseColor.GRAY);
canvas.setFontAndSize(bf, FONT_PT);
float pageWidth = reader.getPageSize(pageNum).getWidth();
float pageHeight = reader.getPageSize(pageNum).getHeight();
for (float y = MARGIN; y < pageHeight; y += charHeight * 3.5) {
for (float x = MARGIN; x < pageWidth; x += charWidth + LINE_GAP) {
canvas.showTextAligned(Element.ALIGN_LEFT, mark, x, y, DEGREE);
}
}
canvas.endText();
}
}
}
Xử lý hàng loạt file PDF
public static void batchProcess(String inputFolder, String outputFolder, String mark)
throws IOException, DocumentException {
File sourceDir = new File(inputFolder);
if (!sourceDir.exists() || !sourceDir.isDirectory()) {
throw new IllegalArgumentException("Thư mục nguồn không tồn tại");
}
File targetDir = new File(outputFolder);
targetDir.mkdirs();
File[] pdfFiles = sourceDir.listFiles((dir, name) ->
name.toLowerCase().endsWith(".pdf")
);
if (pdfFiles != null) {
for (File pdf : pdfFiles) {
String outputPath = outputFolder + File.separator + "WM_" + pdf.getName();
addWatermark(pdf.getAbsolutePath(), outputPath, mark);
}
}
}
Lớp tiện ích hoàn chỉnh
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.pdf.*;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class WriteWatermarkUtil {
private static final String FONT_PATH = "fonts/simfang.ttf";
private static final float ALPHA = 0.25f;
private static final int FONT_PT = 28;
private static final float MARGIN = 35f;
private static final float LINE_GAP = 45f;
private static final int DEGREE = -40;
public static void main(String[] args) throws Exception {
addWatermark("input/sample.pdf", "output/watermarked.pdf", "Bản quyền © 2025");
// batchProcess("input/", "output/", "Nội bộ - Không tiết lộ");
}
private static BaseFont loadFont() throws DocumentException, IOException {
return BaseFont.createFont(
WriteWatermarkUtil.class.getResource(FONT_PATH).getPath(),
BaseFont.IDENTITY_H,
BaseFont.EMBEDDED
);
}
public static void addWatermark(String srcPath, String destPath, String mark)
throws IOException, DocumentException {
// ... (triển khai như trên)
}
public static void batchProcess(String inputFolder, String outputFolder, String mark)
throws IOException, DocumentException {
// ... (triển khai như trên)
}
}