package jnpf.fileStorage.dms;
|
|
import jnpf.dmsEntity.preview.model.DmsWatermarkSnapshot;
|
import jnpf.exception.DataException;
|
import lombok.RequiredArgsConstructor;
|
import org.apache.pdfbox.Loader;
|
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.graphics.image.PDImageXObject;
|
import org.apache.pdfbox.util.Matrix;
|
import org.springframework.beans.BeanUtils;
|
import org.springframework.stereotype.Service;
|
|
import java.io.ByteArrayOutputStream;
|
import java.util.ArrayList;
|
import java.util.List;
|
|
@Service
|
@RequiredArgsConstructor
|
public class DmsPdfWatermarkService {
|
private static final double POINTS_PER_MM = 72D / 25.4D;
|
private static final int MAX_PAGES = 1000;
|
private static final int MAX_OUTPUT_BYTES = 200 * 1024 * 1024;
|
|
private final DmsDocxWatermarkService rasterizer;
|
|
public byte[] apply(byte[] source, List<DmsWatermarkSnapshot> watermarks) {
|
if (watermarks == null || watermarks.isEmpty()) return source;
|
if (source == null || source.length == 0) throw new DataException("PDF 内容为空,不能生成水印");
|
try (PDDocument document = Loader.loadPDF(source);
|
ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
int totalPages = document.getNumberOfPages();
|
if (totalPages < 1 || totalPages > MAX_PAGES) {
|
throw new DataException("PDF 页数超出动态水印处理范围(1-1000 页)");
|
}
|
for (DmsWatermarkSnapshot watermark : watermarks) {
|
if (watermark == null) throw new DataException("PDF 水印配置无效");
|
if (watermark.getRenderPages() != null && watermark.getRenderPages().stream()
|
.anyMatch(page -> page == null || page < 1 || page > totalPages)) {
|
throw new DataException("PDF 水印指定页码超出文件页数");
|
}
|
}
|
for (int pageIndex = 0; pageIndex < totalPages; pageIndex++) {
|
int pageNumber = pageIndex + 1;
|
List<DmsWatermarkSnapshot> selected = forPage(watermarks, pageNumber, totalPages);
|
if (selected.isEmpty()) continue;
|
PDPage page = document.getPage(pageIndex);
|
PDRectangle box = page.getCropBox();
|
if (box == null || box.getWidth() <= 0 || box.getHeight() <= 0) {
|
throw new DataException("PDF 页面尺寸无效,不能生成水印");
|
}
|
int rotation = Math.floorMod(page.getRotation(), 360);
|
boolean quarterTurn = rotation == 90 || rotation == 270;
|
float visualWidth = quarterTurn ? box.getHeight() : box.getWidth();
|
float visualHeight = quarterTurn ? box.getWidth() : box.getHeight();
|
byte[] overlay = rasterizer.renderPdfOverlay(visualWidth / POINTS_PER_MM,
|
visualHeight / POINTS_PER_MM, selected);
|
PDImageXObject image = PDImageXObject.createFromByteArray(document, overlay,
|
"dms-watermark-page-" + pageNumber);
|
try (PDPageContentStream stream = new PDPageContentStream(document, page,
|
PDPageContentStream.AppendMode.APPEND, true, true)) {
|
stream.transform(pageTransform(box, rotation));
|
stream.drawImage(image, 0, 0, visualWidth, visualHeight);
|
}
|
}
|
document.save(output);
|
if (output.size() > MAX_OUTPUT_BYTES) {
|
throw new DataException("PDF 水印输出超过处理上限(200 MB)");
|
}
|
return output.toByteArray();
|
} catch (DataException exception) {
|
throw exception;
|
} catch (Exception exception) {
|
throw new DataException("生成 PDF 水印失败:" + exception.getMessage());
|
}
|
}
|
|
private Matrix pageTransform(PDRectangle box, int rotation) {
|
// PDF /Rotate changes the displayed page, while CropBox keeps its unrotated dimensions.
|
float x = box.getLowerLeftX();
|
float y = box.getLowerLeftY();
|
float width = box.getWidth();
|
float height = box.getHeight();
|
switch (rotation) {
|
case 0: return new Matrix(1, 0, 0, 1, x, y);
|
case 90: return new Matrix(0, 1, -1, 0, x + width, y);
|
case 180: return new Matrix(-1, 0, 0, -1, x + width, y + height);
|
case 270: return new Matrix(0, -1, 1, 0, x, y + height);
|
default: throw new DataException("PDF 页面旋转角度无效");
|
}
|
}
|
|
private List<DmsWatermarkSnapshot> forPage(List<DmsWatermarkSnapshot> watermarks,
|
int pageNumber, int totalPages) {
|
List<DmsWatermarkSnapshot> selected = new ArrayList<>();
|
for (DmsWatermarkSnapshot watermark : watermarks) {
|
if (watermark.getRenderPages() != null
|
&& !watermark.getRenderPages().contains(pageNumber)) continue;
|
DmsWatermarkSnapshot copy = new DmsWatermarkSnapshot();
|
BeanUtils.copyProperties(watermark, copy);
|
if ("TEXT".equalsIgnoreCase(copy.getWatermarkType()) && copy.getContent() != null) {
|
copy.setContent(copy.getContent().replace("{/CurrPage}", String.valueOf(pageNumber))
|
.replace("{/TotalPage}", String.valueOf(totalPages)));
|
}
|
selected.add(copy);
|
}
|
return selected;
|
}
|
}
|