| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- import io
- import os
- import tempfile
- from reportlab.lib.pagesizes import A4
- from PyPDF2 import PdfReader, PdfWriter
- from reportlab.pdfgen import canvas
- from PIL import Image
- import logging
- import requests
- # 配置日志
- logging.basicConfig(level=logging.INFO)
- logger = logging.getLogger(__name__)
- def download_image_from_url(url, output_path):
- """从URL下载图片到本地"""
- response = requests.get(url, stream=True)
- response.raise_for_status()
-
- # 判断文件类型
- content_type = response.headers.get('content-type', '').lower()
- if 'image' in content_type:
- with open(output_path, 'wb') as f:
- for chunk in response.iter_content(chunk_size=8192):
- f.write(chunk)
- return output_path
- else:
- raise Exception(f"不支持的图片类型: {content_type}")
- def add_signature_at_position(input_pdf, output_pdf, signature_url, page_num, x, y, width=None, height=None):
- """
- 在PDF的指定位置添加签名或电子章
-
- 参数:
- - input_pdf: 输入PDF文件路径
- - output_pdf: 输出PDF文件路径
- - signature_url: 签名/电子章的URL(可以是图片)
- - page_num: 页码(从0开始)
- - x: X坐标(相对于PDF页面左下角,单位:点)
- - y: Y坐标(相对于PDF页面左下角,单位:点)
- - width: 签名宽度(可选,单位:点,默认43mm)
- - height: 签名高度(可选,单位:点,默认按比例)
- """
- try:
- # 下载签名图片
- temp_signature = os.path.join(tempfile.gettempdir(), f"temp_signature_{os.urandom(8).hex()}")
- try:
- # 尝试下载为图片
- download_image_from_url(signature_url, temp_signature)
- signature_path = temp_signature
- except Exception as e:
- logger.error(f"下载签名失败: {e}")
- raise Exception(f"无法下载签名图片: {e}")
-
- # 读取PDF
- reader = PdfReader(input_pdf)
- writer = PdfWriter()
- a4_width, a4_height = A4 # (595.2, 841.68)
-
- # 确保页码有效
- if page_num >= len(reader.pages):
- raise Exception(f"页码超出范围,PDF共有{len(reader.pages)}页")
-
- # 处理每一页
- for page_index in range(len(reader.pages)):
- page = reader.pages[page_index]
-
- # 只在指定页面添加签名
- if page_index == page_num:
- # 创建覆盖层
- packet = io.BytesIO()
- can = canvas.Canvas(packet, pagesize=A4)
-
- # 打开图片并获取尺寸
- try:
- img = Image.open(signature_path)
- img_width, img_height = img.size
- aspect_ratio = img_height / img_width
-
- # 设置签名尺寸
- if width is None:
- new_width = 43 * (72 / 25.4) # 43mm转换为点
- else:
- new_width = width
-
- if height is None:
- new_height = new_width * aspect_ratio
- else:
- new_height = height
-
- # 转换坐标:前端传入的坐标通常是相对于页面左上角,需要转换为左下角
- # y坐标需要从页面顶部翻转到底部
- pdf_x = x
- pdf_y = a4_height - y - new_height # 翻转Y坐标
-
- # 确保坐标在页面范围内
- pdf_x = max(0, min(pdf_x, a4_width - new_width))
- pdf_y = max(0, min(pdf_y, a4_height - new_height))
-
- # 绘制图片
- can.drawImage(signature_path, pdf_x, pdf_y, width=new_width, height=new_height, mask='auto')
- can.save()
-
- # 合并覆盖层到PDF页面
- packet.seek(0)
- overlay_pdf = PdfReader(packet)
- page.merge_page(overlay_pdf.pages[0])
- except Exception as e:
- logger.error(f"处理图片失败: {e}")
- raise Exception(f"处理签名图片失败: {e}")
-
- writer.add_page(page)
-
- # 保存PDF
- with open(output_pdf, "wb") as f:
- writer.write(f)
-
- # 清理临时文件
- try:
- if os.path.exists(signature_path):
- os.remove(signature_path)
- except:
- pass
-
- logger.info(f"成功添加签名到PDF: {output_pdf}")
- return True
-
- except Exception as e:
- logger.error(f"添加签名失败: {e}")
- # 清理临时文件
- try:
- if 'signature_path' in locals() and os.path.exists(signature_path):
- os.remove(signature_path)
- except:
- pass
- raise
- def add_multiple_signatures(input_pdf, output_pdf, signatures):
- """
- 在PDF的多个位置添加签名
-
- 参数:
- - input_pdf: 输入PDF文件路径
- - output_pdf: 输出PDF文件路径
- - signatures: 签名列表,每个元素为字典:
- {
- 'signature_url': '签名URL',
- 'page_num': 页码(从0开始),
- 'x': X坐标,
- 'y': Y坐标,
- 'width': 宽度(可选),
- 'height': 高度(可选)
- }
- """
- try:
- # 读取PDF
- reader = PdfReader(input_pdf)
- writer = PdfWriter()
-
- # 按页码分组签名
- signatures_by_page = {}
- for sig in signatures:
- page_num = sig['page_num']
- if page_num not in signatures_by_page:
- signatures_by_page[page_num] = []
- signatures_by_page[page_num].append(sig)
-
- # 处理每一页
- for page_index in range(len(reader.pages)):
- page = reader.pages[page_index]
-
- # 如果这一页有签名,添加所有签名
- if page_index in signatures_by_page:
- # 获取PDF页面实际尺寸(单位:点)
- page_box = page.mediabox
- page_width = float(page_box.width)
- page_height = float(page_box.height)
-
- # 创建覆盖层,使用PDF页面实际尺寸
- packet = io.BytesIO()
- can = canvas.Canvas(packet, pagesize=(page_width, page_height))
-
- for sig in signatures_by_page[page_index]:
- try:
- # 下载签名图片
- temp_signature = os.path.join(tempfile.gettempdir(), f"temp_sig_{os.urandom(8).hex()}")
- download_image_from_url(sig['signature_url'], temp_signature)
-
- # 打开图片并获取尺寸
- img = Image.open(temp_signature)
- img_width, img_height = img.size
- aspect_ratio = img_height / img_width
-
- # 设置签名尺寸(前端传入的已经是点坐标)
- new_width = sig.get('width', 43 * (72 / 25.4))
- new_height = sig.get('height', new_width * aspect_ratio)
-
- # 转换坐标:前端传入的y坐标是相对于页面左上角,需要转换为左下角
- # PDF坐标系:左下角为原点,y向上
- pdf_x = sig['x']
- pdf_y = page_height - sig['y'] - new_height
-
- # 确保坐标在页面范围内
- pdf_x = max(0, min(pdf_x, page_width - new_width))
- pdf_y = max(0, min(pdf_y, page_height - new_height))
-
- # 绘制图片
- can.drawImage(temp_signature, pdf_x, pdf_y, width=new_width, height=new_height, mask='auto')
-
- # 清理临时文件
- os.remove(temp_signature)
- except Exception as e:
- logger.error(f"添加签名失败: {e}")
- if os.path.exists(temp_signature):
- try:
- os.remove(temp_signature)
- except:
- pass
- continue
-
- can.save()
- packet.seek(0)
- overlay_pdf = PdfReader(packet)
- page.merge_page(overlay_pdf.pages[0])
-
- writer.add_page(page)
-
- # 保存PDF
- with open(output_pdf, "wb") as f:
- writer.write(f)
-
- logger.info(f"成功添加多个签名到PDF: {output_pdf}")
- return True
-
- except Exception as e:
- logger.error(f"添加多个签名失败: {e}")
- raise
|