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();
		}
	}

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

相关推荐
云烟成雨TD1 小时前
Micrometer 系列【29】源码分析:MeterRegistry 实例化流程
java·云原生·micrometer
聆风吟º1 小时前
【金仓数据库征文】Java 应用接入金仓数据库:从驱动、连接池到存储过程调试的踩坑实录
java·开发语言·数据库
不会代码的小猴2 小时前
11. 类和动态内存分配
开发语言·c++
似璟如你3 小时前
Java 开发者的 Go 语法基础:从 0 开始快速上手 Go
java·开发语言·后端·golang·go·编程语言
LccKyI3 小时前
C#学习day05(开发福彩双色球系统附思维导图)
开发语言·学习·c#
zzz_23683 小时前
TencentDB-Agent-Memory 深度解析:让多个 Agent 共享项目经验的记忆中枢
java·开发语言·jvm·人工智能·agent·memory·tencent db
gb42152873 小时前
python中unstructured库和langchain-unstructured库在解析pdf文件的时候的区别?
python·langchain·pdf
vx-程序开发3 小时前
django医院预约挂号系统---附源码23353
java·javascript·spring boot·python·eclipse·django·php
金斗潼关3 小时前
ysoserial的使用
java