【DuodooBMS】给PDF附件加“受控”水印的完整Python实现

给PDF附件加“受控”水印的完整Python实现

功能需求

在实际工作中,许多文件需要添加水印以标识其状态,例如“受控”“机密”等。对于PDF文件,添加水印不仅可以增强文件的可识别性,还可以防止未经授权的使用。本代码的功能需求是:

  1. 修复PDF文件:在添加水印之前,确保PDF文件是完整且可读的,避免因文件损坏导致操作失败。

  2. 添加水印:在PDF的每一页上添加指定的水印图像或文字,水印可以设置位置、角度和透明度。

  3. 保存输出:将添加水印后的PDF文件保存到指定路径,并返回其二进制数据以便后续处理。

实现过程

  1. 修复PDF文件

    • 使用PyMuPDF库打开PDF文件,并尝试修复。如果文件损坏,PyMuPDF可以尝试修复并保存为一个新的二进制流。

    • 如果修复失败,则直接返回原始的PDF二进制数据。

    Python复制

    def repair_pdf(self, input_pdf_binary):
        try:
            # 使用 PyMuPDF 打开并修复 PDF
            doc = fitz.open(stream=input_pdf_binary, filetype="pdf")
            repaired_pdf_binary = BytesIO()
            doc.save(repaired_pdf_binary)
            doc.close()
            repaired_pdf_binary.seek(0)
            return repaired_pdf_binary.read()
        except Exception as e:
            print(f"Error repairing PDF: {e}")
            return input_pdf_binary
  2. 添加水印

    • 使用reportlab库创建一个临时的PDF文件作为水印。水印可以是图像或文字,支持设置位置、角度和透明度。

    • 使用PyPDF2库将水印PDF与原始PDF合并。通过merge_page方法,将水印添加到每一页。

    Python复制

    def add_watermark(self, input_pdf_binary, output_pdf, watermark_image, x_position=30, y_position=50, opacity=1):
        # 尝试修复 PDF
        repaired_pdf_binary = self.repair_pdf(input_pdf_binary)
        input_pdf_obj = PdfReader(BytesIO(repaired_pdf_binary))  # 从二进制数据中读取 PDF
        output_pdf_obj = PdfWriter()
        output_buffer = BytesIO()
    
        # 创建一个临时的 PDF 作为水印
        page_width, page_height = A4[1], A4[0]
        c = canvas.Canvas(output_buffer, pagesize=(page_width, page_height))
        try:
            c.setFillColor(colors.white)  # 将背景设置为白色
            c.setFillColor(colors.red)  # 设置字体颜色为红色
            c.setFont("Helvetica", 12)  # 设置字体和字体大小
            c.setFillAlpha(opacity)  # 设置透明度
            c.setStrokeColor(colors.transparent)  # 设置笔触颜色为透明
            img = ImageReader(watermark_image)
            if x_position is not None and y_position is not None:
                c.saveState()
                c.translate(x_position, y_position)
                c.rotate(20)
                c.drawImage(img, 0, 0, width=60, height=25)
                c.restoreState()
            else:
                x = (page_width - 60) / 2
                y = (page_height - 25) / 2
                c.saveState()
                c.translate(x, y)
                c.rotate(20)
                c.drawImage(img, 0, 0, width=60, height=25)
                c.restoreState()
        except Exception as e:
            raise ValueError(f"Error drawing image: {e}")
        c.showPage()
        c.save()
    
        # 将水印 PDF 与原始 PDF 合并
        watermark_pdf = PdfReader(output_buffer)
        for page in input_pdf_obj.pages:
            page.merge_page(watermark_pdf.pages[0])
            output_pdf_obj.add_page(page)
    
        # 保存输出 PDF
        final_output_buffer = BytesIO()
        output_pdf_obj.write(final_output_buffer)
        binary_data = final_output_buffer.getvalue()
    
        with open(output_pdf, 'wb') as f:
            output_pdf_obj.write(f)
        return binary_data
  3. 调用示例

    • 准备一个PDF文件和一个水印图像文件。

    • 调用add_watermark方法,指定输入PDF、输出路径、水印图像路径等参数。

    Python复制

    if __name__ == "__main__":
        from io import BytesIO
        from PyPDF2 import PdfReader, PdfWriter
        from reportlab.pdfgen import canvas
        from reportlab.lib.pagesizes import A4
        from reportlab.lib import colors
        from reportlab.lib.utils import ImageReader
        import fitz
    
        class WatermarkPDF:
            def repair_pdf(self, input_pdf_binary):
                try:
                    doc = fitz.open(stream=input_pdf_binary, filetype="pdf")
                    repaired_pdf_binary = BytesIO()
                    doc.save(repaired_pdf_binary)
                    doc.close()
                    repaired_pdf_binary.seek(0)
                    return repaired_pdf_binary.read()
                except Exception as e:
                    print(f"Error repairing PDF: {e}")
                    return input_pdf_binary
    
            def add_watermark(self, input_pdf_binary, output_pdf, watermark_image, x_position=30, y_position=50, opacity=1):
                repaired_pdf_binary = self.repair_pdf(input_pdf_binary)
                input_pdf_obj = PdfReader(BytesIO(repaired_pdf_binary))
                output_pdf_obj = PdfWriter()
                output_buffer = BytesIO()
    
                page_width, page_height = A4[1], A4[0]
                c = canvas.Canvas(output_buffer, pagesize=(page_width, page_height))
                try:
                    c.setFillColor(colors.white)
                    c.setFillColor(colors.red)
                    c.setFont("Helvetica", 12)
                    c.setFillAlpha(opacity)
                    c.setStrokeColor(colors.transparent)
                    img = ImageReader(watermark_image)
                    if x_position is not None and y_position is not None:
                        c.saveState()
                        c.translate(x_position, y_position)
                        c.rotate(20)
                        c.drawImage(img, 0, 0, width=60, height=25)
                        c.restoreState()
                    else:
                        x = (page_width - 60) / 2
                        y = (page_height - 25) / 2
                        c.saveState()
                        c.translate(x, y)
                        c.rotate(20)
                        c.drawImage(img, 0, 0, width=60, height=25)
                        c.restoreState()
                except Exception as e:
                    raise ValueError(f"Error drawing image: {e}")
                c.showPage()
                c.save()
    
                watermark_pdf = PdfReader(output_buffer)
                for page in input_pdf_obj.pages:
                    page.merge_page(watermark_pdf.pages[0])
                    output_pdf_obj.add_page(page)
    
                final_output_buffer = BytesIO()
                output_pdf_obj.write(final_output_buffer)
                binary_data = final_output_buffer.getvalue()
    
                with open(output_pdf, 'wb') as f:
                    output_pdf_obj.write(f)
                return binary_data
    
        # 示例调用
        watermark_pdf = WatermarkPDF()
        with open("example.pdf", "rb") as f:
            input_pdf_binary = f.read()
        watermark_image = "watermark.png"
        output_pdf = "output_with_watermark.pdf"
        watermark_pdf.add_watermark(input_pdf_binary, output_pdf, watermark_image)

实现总结

本代码通过PyMuPDF修复PDF文件,使用reportlab创建水印PDF,并通过PyPDF2将水印合并到原始PDF中。整个过程支持自定义水印的位置、角度和透明度,能够灵活地满足不同场景的需求。代码结构清晰,易于扩展和维护,适合在实际项目中使用。

 

让转型不迷航——邹工转型手札

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

linux的三剑客和进程处理

Linux三剑客: grep:查找 sed:编辑 awk:分析 grep - 正则表达式 [rootlocalhost ~]# grep ^a hello.txt abc grep - 忽略大小写,还有一些场景需要查询出来对应字符串所在的行号,方便我们快速在文件中定位字…

ASUS/华硕飞行堡垒9 FX506H FX706H 原厂Win10系统 工厂文件 带ASUS Recovery恢复

华硕工厂文件恢复系统 ,安装结束后带隐藏分区,带一键恢复,以及机器所有的驱动和软件。 支持型号:FX506HC, FX506HE, FX506HM, FX706HC, FX706HE, FX706HM, FX506HHR, FX706HMB, FX706HEB, FX706HCB, FX506HMB, FX506HEB, FX506HC…

13.StringTable

String的基本特性 String:字符串,使用一对 ”” 引起来表示 String s1 "mogublog" ; // 字面量的定义方式String s2 new String("moxi"); string声明为final的,不可被继承String实现了Serializable接口:表…

JavaSE基本知识补充 -Map集合

目录 Map(key,value键值对呈现) 1.1 Map的映射的特点 1. 2.HashMap (键值对的业务偏多,而且hashmap在jdk1.7和1.8之间有所不同,性能做了提升,面试高频考点) 1.3 Map接口的方法 方法 HashMap遍…

JAVA学习第二天

ArryList的构造方法和添加方法 01。构造方法的<>里面可以放数据类型 02. add&#xff08;&#xff09;可以直接在后面加入数据&#xff0c;也可以指定下标的插入元素。 ArrayList的常用方法 ArrayList存储对象 在Java中&#xff0c;System.out.println()可以打印基本数据…

基于窄带物联网的矿车追踪定位系统(论文+源码+实物)

1.功能设计 鉴于智能物联网的大趋势&#xff0c;本次基于窄带物联网的矿车追踪定位系统应具备以下功能&#xff1a; &#xff08;1&#xff09;实现实时定位&#xff0c;真正实现矿车随时随地定位; &#xff08;2&#xff09;定位精度高&#xff0c;采用该系统可以实现矿车在…

如何把邮件批量导出到本地

最近遇到邮箱满了的问题&#xff0c;需要把邮件批量导出到本地&#xff0c;然后清空邮箱。 问题是这个邮箱的官网&#xff0c;没有批量导出按钮&#xff0c;比较麻烦&#xff1b;总不能一封一封下载到本地&#xff0c;上万的。 找到了一个好用的工具&#xff0c;Mozilla Thun…

ICLR 2025 oral|用nuPlan + 200h物流小车数据集测试!SOTA扩散模型轨迹规划器来了

导读&#xff1a; 本文介绍了清华大学联合毫末智行、自动化所、港中文、上海交大、上海人工智能实验室最新研究成果《Diffusion-based Planning for Autonomous Driving with Flexible Guidance》——荣获ICLR 2025 Oral Presentation(仅1.8%接受率)。 该算法创新性地设计了基…

dify.ai 怎么配置链接火山引擎等云厂商的deepseek模型

要将 dify.ai 配置链接到火山引擎等云厂商的 DeepSeek 模型. 申请火山引擎的key&#xff0c;创建endpoint 添加模型 测试模型

SAP-ABAP:dialog界面中的数据块Event Block详解举例

在SAP的Dialog程序开发中&#xff0c;Event Block&#xff08;事件块&#xff09;是屏幕流逻辑&#xff08;Flow Logic&#xff09;中的关键部分&#xff0c;用于定义屏幕在特定事件触发时执行的逻辑。Event Block通常与ABAP模块&#xff08;Module&#xff09;结合使用&#x…

2025年怎么选择SEO发布工具

在如今竞争激烈的互联网时代&#xff0c;网站的流量和曝光率直接决定着一个品牌或企业的市场影响力。无论是个人博客&#xff0c;还是企业官网&#xff0c;能够有效提升SEO&#xff08;搜索引擎优化&#xff09;排名的工具&#xff0c;已成为许多网站管理者和营销人员的必备良器…

Java 进阶day14XML Dom4j 工厂模式 Base64

目录 知识点1、XML 概念XML约束 知识点2、XML解析 Dom4j&#xff08;Dom for java&#xff09;XPath 知识点3、工厂模式知识点4、Base64 知识点1、XML 概念 XML的全称为&#xff08;eXtensible Markup Language&#xff09;&#xff0c;是一种可扩展的标记语言。 XML的作用…

数据结构实验——排序算法的实现与分析

前言 到目前为止&#xff0c;8个数据结构实验在这里就全部更完啦&#xff08;撒花&#xff09;&#xff01;我那一段难忘的周二晚课时光也告一段落&#xff0c;整体来说&#xff0c;有赶课的折腾&#xff0c;有调错的崩溃&#xff0c;也有故意迟到五分钟的惬意&#xff0c;用G…

【Antv G2 5.x】饼图添加点击事件,获取当前坐标数据

// 监听 tooltip:show 事件this.chart.on(tooltip:show, (event) => {this.currentShowTooltipName = event.data.items[0].name})// 监听绘图区plot的点击事件this.chart.on(interval:click, ev => {this.$emit(chartClick, this.currentShowTooltipName);})// 监听绘图…

Oracle常用导元数据方法

1 说明 前两天领导发邮件要求导出O库一批表和索引的ddl语句做国产化测试&#xff0c;涉及6个系统&#xff0c;6千多张表&#xff0c;还好涉及的用户并不多&#xff0c;要不然很麻烦。 如此大费周折原因&#xff0c;是某国产库无法做元数据迁移。。。额&#xff0c;只能我手动导…

anolis os 8.9安装jenkins

一、系统版本 # cat /etc/anolis-release Anolis OS release 8.9 二、安装 # dnf install -y epel-release # wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo # rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.…

Python办公自动化之PDF

python版本&#xff1a;3.13.1 开发工具&#xff1a;pycharm 安装三方库&#xff1a;pypdf2 、pdfplumber、pymupdf 一、从PDF中提取文字 用Python从PDF中提取文字-CSDN博客 二、从PDF中提取表格 用Python从PDF中提取表格-CSDN博客 三、拆分和合并PDF文件 用Python拆…

变化检测相关论文可读list

一些用得上的&#xff1a; 遥感变化检测常见数据集https://github.com/rsdler/Remote-Sensing-Change-Detection-Dataset/ 代码解读&#xff1a;代码解读 | 极简代码遥感语义分割&#xff0c;结合GDAL从零实现&#xff0c;以U-Net和建筑物提取为例 NeurIPS2024: https://mp.w…

ASP.NET Core SignalR案例:导入英汉词典

Ecdict 下载词典文件stardict.7z&#xff0c;解压&#xff0c;stardict.csv是一个CSV格式的文本文件&#xff0c;文件的第一行是表头&#xff0c;除第一行外&#xff0c;其他每行文本是一个单词的相关信息&#xff0c;用逗号分隔的就是各个列的值。英汉词典ECDICT中导入单词到…

元宵佳节,我的创作纪念日:技术之路的回顾与展望

今天是元宵节&#xff0c;一个象征着团圆与美好的节日。巧合的是&#xff0c;今天也是我作为技术博客博主的创作纪念日。在这个特别的日子里&#xff0c;我想和大家分享我的创作故事&#xff0c;回顾初心、总结收获、展望未来&#xff0c;同时也希望能为正在技术道路上探索的你…