java实现pdf加水印+签名

bash 复制代码
	<dependency>
			<groupId>com.itextpdf</groupId>
			<artifactId>itextpdf</artifactId>
			<version>5.5.13.3</version>
		</dependency>
java 复制代码
	/**
	 * 合同签订测试
	 * @param request
	 * @param response
	 * @return
	 */
	@RequestMapping(value = "/contract/contractSignTest", method = RequestMethod.POST)
	void contractSignTest(HttpServletRequest request, HttpServletResponse response, MultipartFile file,Long contractId);
java 复制代码
private String downloadAndSignPdf(String pdfUrl, String imageUrl,MultipartFile file) throws Exception {
			// 下载PDF和图片
			byte[] pdfBytes = downloadFromUrl(pdfUrl);
			byte[] imageBytes;
			if (file != null && !file.isEmpty()) {
				imageBytes = file.getBytes();
			}else {
				imageBytes = downloadFromUrl(imageUrl);
			}

			try {
				// 使用iText将图片添加到PDF
				ByteArrayOutputStream pdfOutput = new ByteArrayOutputStream();
				com.itextpdf.text.pdf.PdfReader reader = new com.itextpdf.text.pdf.PdfReader(pdfBytes);
				com.itextpdf.text.pdf.PdfStamper stamper = new com.itextpdf.text.pdf.PdfStamper(reader, pdfOutput);

				// 获取最后一页
				int lastPage = reader.getNumberOfPages();
				com.itextpdf.text.Rectangle pageSize = reader.getPageSize(lastPage);

				// 创建图片对象并设置位置(右下角)
				com.itextpdf.text.Image image = com.itextpdf.text.Image.getInstance(imageBytes);
				image.setAbsolutePosition(pageSize.getWidth() * 0.3f, pageSize.getHeight() * 0.3f);
				image.scaleToFit(pageSize.getWidth() * 0.2f, pageSize.getHeight() * 0.1f);

				// 将图片添加到PDF
				stamper.getOverContent(lastPage).addImage(image);

				// ========== 添加平铺水印开始 ==========
				com.itextpdf.text.pdf.PdfGState gs = new com.itextpdf.text.pdf.PdfGState();
				gs.setFillOpacity(0.25f);//透明度

				for (int i = 1; i <= reader.getNumberOfPages(); i++) {
					PdfContentByte canvas = stamper.getOverContent(i);
					canvas.setGState(gs);

					com.itextpdf.text.Rectangle page_size = reader.getPageSize(i);

					// 动态计算水印参数
					String watermarkText = "测试pdf水印";
					int fontSize = 60;  // 基础字号
					int padding = 40;   // 边距

					// 先用临时Graphics2D计算文字实际宽度
					java.awt.image.BufferedImage tempImg = new java.awt.image.BufferedImage(1, 1, java.awt.image.BufferedImage.TYPE_INT_ARGB);
					java.awt.Graphics2D tempG2d = tempImg.createGraphics();
					tempG2d.setFont(new java.awt.Font("宋体", java.awt.Font.BOLD, fontSize));
					java.awt.FontMetrics fm = tempG2d.getFontMetrics();
					int textWidth = fm.stringWidth(watermarkText);
					int textHeight = fm.getHeight();
					tempG2d.dispose();

					// 根据文字尺寸计算图片大小(考虑旋转后需要的空间)
					double angleRad = Math.toRadians(30);
					double cos = Math.abs(Math.cos(angleRad));
					double sin = Math.abs(Math.sin(angleRad));

					int imgWidth = (int)(textWidth * cos + textHeight * sin) + padding * 2;
					int imgHeight = (int)(textWidth * sin + textHeight * cos) + padding * 2;

					// 创建实际水印图片
					java.awt.image.BufferedImage watermarkImg = new java.awt.image.BufferedImage(
							imgWidth, imgHeight, java.awt.image.BufferedImage.TYPE_INT_ARGB);

					java.awt.Graphics2D g2d = watermarkImg.createGraphics();
					g2d.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING,
							java.awt.RenderingHints.VALUE_ANTIALIAS_ON);
					g2d.setRenderingHint(java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,
							java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

					g2d.setFont(new java.awt.Font("宋体", java.awt.Font.BOLD, fontSize));
					g2d.setColor(new java.awt.Color(180, 180, 180, 150));

					// 旋转并绘制
					g2d.rotate(Math.toRadians(30), imgWidth / 2.0, imgHeight / 2.0);
					g2d.drawString(watermarkText, (imgWidth - textWidth) / 2, (imgHeight + textHeight) / 2 - fm.getDescent());
					g2d.dispose();

					com.itextpdf.text.Image watermarkImage = com.itextpdf.text.Image.getInstance(
							watermarkImg, null);

					// 计算平铺数量(根据页面大小)
					int cols = Math.max(2, (int)(page_size.getWidth() / imgWidth) - 1);
					int rows = Math.max(2, (int)(page_size.getHeight() / imgHeight) - 1);

					for (int row = 0; row < rows; row++) {
						for (int col = 0; col < cols; col++) {
							float x = page_size.getWidth() * (col + 1) / (cols + 1) - imgWidth / 2;
							float y = page_size.getHeight() * (row + 1) / (rows + 1) - imgHeight / 2;
							watermarkImage.setAbsolutePosition(x, y);
							canvas.addImage(watermarkImage);
						}
					}
				}
				// ========== 添加平铺水印结束 ==========
				stamper.close();
				reader.close();
				byte[] signedPdfBytes = pdfOutput.toByteArray();
				Result<UploadResult> upload = storageService.upload(
						"contract_signed_" + System.currentTimeMillis() + ".pdf",
						signedPdfBytes,
						null,
						null
				);//上传到文件服务器
				String path = upload.getData().getPath();
				System.out.println(domainUrl+path);

				return path;
			} catch (Exception e) {
				log.warn("图片处理失败,返回原PDF: " + e.getMessage());
				throw new Exception(e.getMessage());
			}
	}

	private byte[] downloadFromUrl(String urlString) throws Exception {
		URL url = new URL(urlString);
		HttpURLConnection connection = (HttpURLConnection) url.openConnection();
		connection.setRequestMethod("GET");
		connection.setConnectTimeout(10000);
		connection.setReadTimeout(10000);

		try (InputStream inputStream = connection.getInputStream();
			 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
			byte[] buffer = new byte[4096];
			int bytesRead;
			while ((bytesRead = inputStream.read(buffer)) != -1) {
				byteArrayOutputStream.write(buffer, 0, bytesRead);
			}
			connection.disconnect();
			return byteArrayOutputStream.toByteArray();
		}
	}

前端需自行上传签字的图片文件

相关推荐
学长毕业设计21 分钟前
基于springboot的云南中草药知识普及管理网站的设计与开发(源码+文档+讲解视频)
java·spring boot·后端
大模型码小白22 分钟前
AI 提示词专栏:使用系统指令(System Prompt)实现全局约束
java·大数据·运维·开发语言·人工智能·python·prompt
cxoptics28 分钟前
蓝宝石的轴向在窗片和偏振片中起的作用
java
HugoStudio_SWAN38 分钟前
C++ CMD 互动动画:按键触发爆炸效果
开发语言·c++·学习·程序人生
wuminyu40 分钟前
Java FFM处理网络读写事件源码剖析
java·linux·c语言·jvm·c++
互联网中的一颗神经元42 分钟前
10. Large 对象分配:直接路径
开发语言·golang
小江的记录本1 小时前
【AI Agent】《2026年9月 AI Agent全栈开发技术选型专项面试宝典》
java·spring boot·python·spring·ai·面试·ai编程
redfred1 小时前
Stirling-PDF 使用教程:开源 PDF 工具箱 50+ 工具 PDF转Word/合并/压缩/OCR 桌面版 Docker 部署详解
pdf·开源·word
梦梦代码精1 小时前
《LikeShop全产品技术硬核拆解:ThinkPHP8+Vue3+UniApp架构,私有化部署与二次开发实战指南》
java·低代码·系统架构·php·开源软件
一木 之林1 小时前
C/C++ 面向对象编程(OOP)(进阶)
java·c语言·c++