【python】pdf拆成图片,加中文,再合成pdf

前期搞了个pdf加页脚,但是搞了半天中文加不了,就换了个思路。
直接说结论,pdf拆成图片,加中文,再合成pdf,会导致pdf模糊。

import os
import fitz  # PyMuPDF
from PIL import Image, ImageDraw, ImageFont
from datetime import datetime
from typing import List, Tuple, Optional
import tempfile
import shutildef pdf_to_images(input_pdf: str, output_dir: str) -> int:"""将PDF转换为图片Args:input_pdf: 输入PDF文件路径output_dir: 输出图片目录Returns:转换后的图片数量"""os.makedirs(output_dir, exist_ok=True)try:# 打开PDF文档doc = fitz.open(input_pdf)page_count = len(doc)# 转换每一页为图片for page_num in range(page_count):page = doc[page_num]pix = page.get_pixmap()image_path = os.path.join(output_dir, f"page_{page_num + 1}.png")pix.save(image_path)doc.close()return page_countexcept Exception as e:print(f"PDF转图片时出错: {str(e)}")return 0def add_watermark_to_image(input_image: str, output_image: str, watermark_text: str,font_size: int = 10,font_color: Tuple[int, int, int] = (0, 0, 0),font_path: str = None) -> None:"""为图片添加水印Args:input_image: 输入图片路径output_image: 输出图片路径watermark_text: 水印文本font_size: 字体大小font_color: 字体颜色,RGB元组font_path: 字体文件路径,用于支持中文等特殊字符"""try:# 打开图片img = Image.open(input_image).convert("RGBA")width, height = img.size# 创建水印图层watermark = Image.new('RGBA', img.size, (0, 0, 0, 0))draw = ImageDraw.Draw(watermark)# 设置字体font = Noneif font_path:try:font = ImageFont.truetype(font_path, font_size)except Exception:print(f"无法加载指定字体: {font_path},将尝试系统字体")# 如果指定字体无法加载或未指定字体,尝试系统字体if font is None:system_fonts = {"win32": ["simhei.ttf", "msyh.ttc"],"darwin": ["/System/Library/Fonts/PingFang.ttc"],"linux": ["/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"]}for font_name in system_fonts.get(os.name, []):try:font = ImageFont.truetype(font_name, font_size)print(f"使用系统字体: {font_name}")breakexcept Exception:continue# 如果仍未找到合适的字体,使用默认字体if font is None:font = ImageFont.load_default()print("使用默认字体")# 计算水印位置(右下角,留出边距)# 兼容旧版本和新版本的 Pillow 库try:# 新版本 Pillow 使用 getbbox()bbox = draw.textbbox((0, 0), watermark_text, font=font)text_width = bbox[2] - bbox[0]text_height = bbox[3] - bbox[1]except AttributeError:# 旧版本 Pillow 使用 textsize()text_width, text_height = draw.textsize(watermark_text, font=font)margin = 10position = (width - text_width - margin, height - text_height - margin)# 添加水印draw.text(position, watermark_text, font=font, fill=font_color + (128,))  # 半透明# 合并水印图层combined = Image.alpha_composite(img.convert('RGBA'), watermark)combined = combined.convert(img.mode)  # 转换回原始模式# 保存图片combined.save(output_image)except Exception as e:print(f"图片添加水印时出错: {str(e)}")raise  # 重新抛出异常,便于调试def images_to_pdf(image_dir: str, output_pdf: str, page_count: int) -> None:"""将图片合并为PDFArgs:image_dir: 图片目录output_pdf: 输出PDF文件路径page_count: 页面数量"""try:# 创建一个新的PDF文档doc = fitz.open()# 按顺序添加图片到PDFfor i in range(1, page_count + 1):image_path = os.path.join(image_dir, f"page_{i}.png")if os.path.exists(image_path):# 打开图片img = fitz.open(image_path)rect = img[0].rectpdfbytes = img.convert_to_pdf()img.close()# 将图片PDF添加到新文档imgpdf = fitz.open("pdf", pdfbytes)page = doc.new_page(width=rect.width, height=rect.height)page.show_pdf_page(rect, imgpdf, 0)imgpdf.close()# 保存PDFdoc.save(output_pdf)doc.close()except Exception as e:print(f"图片合并为PDF时出错: {str(e)}")def add_watermark(image_path, watermark_text, output_path=None, font_size=24, opacity=80):"""给图片右下角添加文字水印参数:image_path: 图片路径watermark_text: 水印文字output_path: 输出路径,默认与原图同目录并添加"_watermarked"后缀font_size: 字体大小opacity: 水印透明度(0-100)"""# 打开图片image = Image.open(image_path)# 如果未指定输出路径,生成默认路径if output_path is None:file_name, file_ext = os.path.splitext(image_path)output_path = f"{file_name}_watermarked{file_ext}"# 创建水印图像watermark = Image.new('RGBA', image.size, (0, 0, 0, 0))draw = ImageDraw.Draw(watermark)# 设置字体font = Nonesystem_fonts = {"win32": ["simhei.ttf", "msyh.ttc"],"darwin": ["/System/Library/Fonts/PingFang.ttc"],"linux": ["/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"]}for font_name in system_fonts.get(os.name, []):try:font = ImageFont.truetype(font_name, font_size)print(f"使用系统字体: {font_name}")breakexcept Exception:continue# 如果仍未找到合适的字体,使用默认字体if font is None:font = ImageFont.load_default()print("使用默认字体")# 计算水印文字尺寸 - 兼容新旧版本try:# 新版本 Pillow 使用 getbbox()bbox = draw.textbbox((0, 0), watermark_text, font=font)text_width = bbox[2] - bbox[0]text_height = bbox[3] - bbox[1]except AttributeError:# 旧版本 Pillow 使用 textsize()text_width, text_height = draw.textsize(watermark_text, font=font)# 确定右下角位置(留出10像素边距)position = (image.width - text_width - 10, image.height - text_height - 10)# 绘制水印文字draw.text(position, watermark_text, font=font, fill=(255, 255, 255, int(opacity * 2.55)))# 合并水印到原图if image.mode != 'RGBA':image = image.convert('RGBA')watermarked_image = Image.alpha_composite(image, watermark)# 获取输出文件的扩展名,确定保存格式output_ext = os.path.splitext(output_path)[1].lower()# 根据文件扩展名处理保存逻辑if output_ext in ['.jpg', '.jpeg']:# 对于JPEG格式,移除透明度通道并转换为RGBwatermarked_image = watermarked_image.convert('RGB')watermarked_image.save(output_path, quality=95)elif output_ext == '.png':# 对于PNG格式,保留RGBA模式watermarked_image.save(output_path)else:# 其他格式,尝试转换为RGB(如果可能)try:watermarked_image.convert('RGB').save(output_path)except:watermarked_image.save(output_path)print(f"水印已添加,保存至: {output_path}")return output_pathdef add_watermark_to_pdf(input_pdf: str, output_pdf: str, watermark_text: str,font_size: int = 10,font_color: Tuple[int, int, int] = (0, 0, 0),font_path: str = None,pages_to_skip: Optional[List[int]] = None) -> None:"""为PDF文件添加水印Args:input_pdf: 输入PDF文件路径output_pdf: 输出PDF文件路径watermark_text: 水印文本font_size: 字体大小font_color: 字体颜色,RGB元组font_path: 字体文件路径,用于支持中文等特殊字符pages_to_skip: 跳过添加水印的页码列表(从0开始)"""if pages_to_skip is None:pages_to_skip = []# 创建临时目录with tempfile.TemporaryDirectory() as temp_dir:# 转换PDF为图片page_count = pdf_to_images(input_pdf, temp_dir)if page_count == 0:print(f"无法转换PDF文件: {input_pdf}")return# 为每张图片添加水印for i in range(1, page_count + 1):if (i - 1) in pages_to_skip:continueimage_path = os.path.join(temp_dir, f"page_{i}.png")temp_image_path = os.path.join(temp_dir, f"page_{i}_watermarked.png")add_watermark_to_image(image_path,temp_image_path,watermark_text,font_size,font_color,font_path)# 替换原图if os.path.exists(temp_image_path):os.replace(temp_image_path, image_path)# 将图片合并回PDFimages_to_pdf(temp_dir, output_pdf, page_count)def batch_add_watermark(input_dir: str, output_dir: str, watermark_text: str,font_size: int = 10,font_color: Tuple[int, int, int] = (0, 0, 0),font_path: str = None,pages_to_skip: Optional[List[int]] = None,overwrite: bool = False) -> None:"""批量为目录中的PDF文件添加水印,保持目录结构Args:input_dir: 输入目录路径output_dir: 输出目录路径watermark_text: 水印文本font_size: 字体大小font_color: 字体颜色,RGB元组font_path: 字体文件路径,用于支持中文等特殊字符pages_to_skip: 跳过添加水印的页码列表(从0开始)overwrite: 是否覆盖已存在的输出文件"""# 确保输出目录存在os.makedirs(output_dir, exist_ok=True)# 遍历输入目录中的所有文件和子目录for root, dirs, files in os.walk(input_dir):# 计算相对路径relative_path = os.path.relpath(root, input_dir)# 如果是根目录,相对路径会是'.',此时设为空if relative_path == '.':relative_path = ''# 计算对应的输出目录current_output_dir = os.path.join(output_dir, relative_path)# 确保输出子目录存在os.makedirs(current_output_dir, exist_ok=True)# 处理当前目录中的PDF文件for file in files:if file.lower().endswith('.pdf'):input_path = os.path.join(root, file)output_path = os.path.join(current_output_dir, file)# 检查是否需要跳过已存在的文件if os.path.exists(output_path) and not overwrite:print(f"跳过已存在的文件: {output_path}")continueprint(f"正在处理: {input_path}")add_watermark_to_pdf(input_path,output_path,watermark_text,font_size,font_color,font_path,pages_to_skip)if __name__ == "__main__":# 配置参数INPUT_DIR = "./input_pdfs"  # 输入PDF文件目录OUTPUT_DIR = "./output_pdfs"  # 输出PDF文件目录# 水印文本示例,包含当前日期WATERMARK_TEXT = '更多资料请+wx:zz34742'# 字体设置FONT_SIZE = 12FONT_COLOR = (0, 0, 0)  # 黑色# 指定支持中文的字体# Windows系统常用中文字体FONT_PATH = r"C:\Windows\Fonts\simhei.ttf"  # 黑体# FONT_PATH = r"C:\Windows\Fonts\msyh.ttc"  # 微软雅黑# macOS系统常用中文字体# FONT_PATH = "/System/Library/Fonts/PingFang.ttc"  # 苹方# Linux系统常用中文字体# FONT_PATH = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"  # Noto Sans CJK# 根据您的系统选择合适的字体# FONT_PATH = None  # 使用默认字体# 跳过添加水印的页码(从0开始)PAGES_TO_SKIP = []  # 例如: [0] 表示跳过第一页# 是否覆盖已存在的文件OVERWRITE = False# 执行批量处理batch_add_watermark(INPUT_DIR,OUTPUT_DIR,WATERMARK_TEXT,font_size=FONT_SIZE,font_color=FONT_COLOR,font_path=FONT_PATH,pages_to_skip=PAGES_TO_SKIP,overwrite=OVERWRITE)

这个PDF水印添加工具的核心逻辑可以概括为以下几个步骤:

1. PDF转图片

  • 读取输入PDF文件
  • 使用PyMuPDF库将PDF的每一页转换为PNG图片
  • 图片保存在临时目录中,文件名格式为page_1.png, page_2.png

2. 图片添加水印

  • 打开图片并创建一个透明的水印图层
  • 检测系统字体,优先使用支持中文的字体(如黑体、微软雅黑等)
  • 计算水印文本的大小和位置(右下角留出边距)
  • 将水印文本绘制到图层上(半透明效果)
  • 合并水印图层到原图

3. 图片合并回PDF

  • 按顺序读取带水印的图片
  • 使用PyMuPDF创建新的PDF文档
  • 将每张图片添加为PDF的一页
  • 保存为新的PDF文件

4. 批量处理

  • 遍历指定目录中的所有PDF文件
  • 保持原有的目录结构
  • 对每个PDF文件执行上述处理流程
  • 支持跳过指定页码、覆盖已处理文件等选项

关键技术细节

  1. 字体处理

    • 优先使用用户指定的字体
    • 自动检测系统中可用的中文字体
    • 回退到默认字体确保兼容性
  2. 版本兼容

    • 同时支持Pillow库的新旧API
    • 使用textbbox()(新)和textsize()(旧)方法计算文本尺寸
  3. 临时文件管理

    • 使用tempfile.TemporaryDirectory创建临时工作目录
    • 处理完成后自动清理临时文件
  4. 错误处理

    • 每层处理都有异常捕获
    • 关键步骤输出详细日志
    • 保留原始错误信息便于调试

使用流程

  1. 将需要添加水印的PDF文件放入input_pdfs目录
  2. 运行程序,配置水印文本、字体等参数
  3. 处理后的PDF文件会保存在output_pdfs目录中,保持原有目录结构
  4. 程序会自动检测系统并选择合适的中文字体,确保水印文本正常显示

这个工具通过将PDF转换为图片、添加水印后再转回PDF的方式,实现了对任意PDF文件添加文本水印的功能,特别适合批量处理大量文档。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/87126.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/87126.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分布式爬虫数据存储开发实战

分布式爬虫存储的核心矛盾在于:既要高吞吐又要强一致性,还要避免重复。比如Kafka虽然吞吐高但无法去重,Redis去重快但容量有限。所以我们可能低估了状态同步的复杂度——比如暂停爬虫时如何保证内存中的URL状态不丢失。 分布式爬虫的数据存储…

探秘阿里云Alibaba Cloud Linux:云时代的操作系统新宠

引言:云时代的操作系统变革 在云计算技术蓬勃发展的当下,企业的数字化转型进程被极大地加速,而作为云计算底层支撑的操作系统,也迎来了前所未有的变革与挑战。传统操作系统在应对云计算环境中的大规模资源调度、高弹性扩展以及安…

使用pyflink进行kafka实时数据消费

目录 背景 代码demo 踩坑记录 1、kafka连接器,kafka客户端jar包找不到 2、java模块系统访问限制 3、执行demo任务,一直报错连接kafka topic超时 总结 背景 实际项目中经常遇到source是kafka,需要实时消费kafka某个topic中的数据&#x…

软件测试理论框架与发展:分类、原则与质量保障策略

第一章 一、计算机软件的发展分类 早期软件开发的特点: 软件规模小、复杂程度低、开发过程不规范 测试的情况: 测试等同于调试 目的纠正软件的已经知道的故障 投入少,介入晚 成为一种发现软件的活动(1957) 测试不等于…

未知威胁攻击原理和架构

大家读完觉得有帮助记得关注和点赞!!! 未知威胁(Unknown Threats)指利用零日漏洞、合法工具滥用、高级逃逸技术等**绕过传统特征检测**的攻击,其核心在于**动态对抗防御体系的认知盲区**。以下从攻击原理、…

基于Netty-WebSocket构建高性能实时通信服务

引言:WebSocket在现代应用中的重要性 在当今实时交互应用盛行的时代,WebSocket协议已成为实现双向通信的核心技术。相比传统的HTTP轮询,WebSocket提供了: 真正的全双工通信极低的延迟(毫秒级)高效的连接管…

咸虾米项目总结1--const用法

在 UniApp(或 Vue 3)中,声明一个空对象可使用下面这2种写法: // 写法1 const a ref(null);// 写法2 const a ref({}); 在UniApp中,const a ref()用法概述: 用途: 创建一个响应式引用&#x…

在mac下手动编译迁移的android版webrtc组件

我原先使用的android版webrtc是在linux下编译的,现在因为某些原因需要把整个库迁移到mac下编译。 把代码迁移完后,正常是需要通过gclient sync 重新构建编译环境,但是由于网络限制等方面原因,会导致完成的比较慢。 在摸索一阵后…

Linux 命令:mkdir

Linux mkdir 命令详细教程 一、mkdir 命令的基本功能 mkdir(Make Directory)是 Linux 系统中用于创建新目录(文件夹)的基础命令。它支持一次性创建单个或多个目录,以及递归创建多层目录结构,是文件系统操…

Django 数据迁移全解析:makemigrations migrate 常见错误与解决方案

1. 迁移机制与底层原理 在 Django 中,ORM(Object-Relational Mapping)是连接模型(Model)和数据库结构的桥梁。Django 鼓励开发者通过编写 Python 类(模型)来定义业务数据结构,而不是…

SuperGlue:使用图神经网络学习特征匹配

摘要 本文提出了 SuperGlue,一种神经网络,用于通过联合寻找对应关系并排除不可匹配点来匹配两组局部特征。匹配结果通过求解一个可微的最优传输问题来估计,该问题的代价由一个图神经网络预测。我们引入了一种基于注意力的灵活上下文聚合机制…

ssh -T git@github.com失败后解决方案

这个错误表示你的 SSH 连接无法到达 GitHub 服务器。以下是详细解决方案,按照优先级排序: 首选解决方案:使用 SSH over HTTPS(端口 443) 这是最有效的解决方案,因为许多网络会阻止 22 端口: …

从苹果事件看 ARM PC市场的未来走向

最近,苹果宣布部分搭载 Intel 处理器的 Mac 不再支持最新的 macOS 系统更新,这一消息犹如一颗石子投入平静湖面,激起层层涟漪。它不仅让 Intel 芯片在 Mac 产品线上彻底成为历史,也促使我们重新审视 PC 行业的发展脉络&#xff0c…

vue + element ui 实现超出宽度展示..,鼠标移入显示完整内容

vue element ui 实现超出宽度展示…&#xff0c;鼠标移入显示完整内容 代码理念&#xff1a; 当高度大于对应行数的高度 则说明需要展示"…" 子组件 <template><div class"tooltip"><div ref"tooltipRef" :class"[tooltip…

HarmonyOSNext应用无响应全解析:从机制到实战的卡死问题排查

HarmonyOSNext应用无响应全解析&#xff1a;从机制到实战的卡死问题排查 ##Harmony OS Next ##Ark Ts ##教育 本文适用于教育科普行业进行学习&#xff0c;有错误之处请指出我会修改。 喂喂喂&#xff01;应用卡成PPT了&#xff1f;点啥都没反应&#xff1f;别慌&#xff01…

git 迁移之获取原库所有分支

以下是一个安全的 Bash 脚本&#xff0c;用于将远程 Git 仓库的所有分支检出到本地&#xff08;自动跳过已存在的分支&#xff09;&#xff1a; #!/bin/bash# 获取所有远程分支&#xff08;排除 HEAD&#xff09; remote_branches$(git branch -r | grep -v HEAD\|->)# 循环…

设计模式 | 适配器模式

适配器模式&#xff08;Adapter Pattern&#xff09; 是结构型设计模式中的连接器大师&#xff0c;它允许不兼容接口的类能够协同工作。本文将深入探索适配器模式的核心思想、实现技巧以及在C中的高效实践&#xff0c;解决现实开发中的接口兼容性问题。 为什么需要适配器模式 …

RTL 级机器人电机控制器的 FPGA 设计

借助Verilog&#xff0c;在FPGA中实现了带编码器的两台电机的电机控制系统的RTL级设计。 介绍 借助硬件描述语言 (HDL) Verilog 和 AMD Vivado 设计套件&#xff0c;在 AMD Spartan-7 FPGA 中实现带编码器的两个电机的控制器系统的 RTL 设计。 在这个项目中&#xff0c;使用了搭…

4_Flink CEP

Flink CEP 1、何为CEP&#xff1f; CEP&#xff0c;全称为复杂事件处理&#xff08;Complex Event Processing&#xff09;&#xff0c;是一种用于实时监测和分析数据流的技术。 CEP详细讲解&#xff1a; CEP是基于动态环境的事件流的分析技术&#xff0c;事件是状态变化&am…

容器基础知识2-K8s 和 Docker 的关系与管理逻辑详解

K8s 和 Docker 的关系与管理逻辑详解 一、先搞懂&#xff1a;Docker 和 K8s 分别是做什么的&#xff1f; Docker&#xff08;容器工具&#xff09;&#xff1a;好比「集装箱工厂」&#xff0c;负责把应用和依赖打包成标准化容器&#xff08;类似集装箱&#xff09;&#xff0…