使用Python合并PDF文件并添加自定义目录及页脚

如何用Python合并PDF文件并添加自定义目录及页脚

    • 步骤一:准备环境
    • 步骤二:合并PDF文件
    • 步骤三:处理特定文件
    • 步骤四:合并并添加目录与页脚
    • 步骤五:保存最终文档
    • 结语
    • 完整代码

在处理文档时,我们经常遇到需要合并多个PDF文件并添加目录及页脚的情况。本文将介绍如何使用Python,特别是PyPDF2reportlab库来实现这一功能。我们将通过一个实用的示例来演示整个过程,包括如何动态创建目录页和在每页底部添加页码。

步骤一:准备环境

首先,确保你的环境中安装了PyPDF2reportlab库。如果还没有安装,可以通过以下命令进行安装:

pip install PyPDF2 reportlab

步骤二:合并PDF文件

我们首先定义一个函数add_catalog_page(bookmarks)来创建一个包含目录的PDF页面。这个目录基于传入的书签列表动态生成,每个书签对应一个文档标题和其开始的页码。

接下来,使用create_footer_page(footer_text)函数为每个PDF页面添加自定义页脚。这个函数通过绘制一个足够大的白色矩形来覆盖原有的页码,然后在指定位置添加新的页码文本。

步骤三:处理特定文件

在合并多个PDF文件之前,我们可能需要调整它们的顺序。在本例中,我们将特定的文件(如"第一篇.pdf")移动到文件列表的开头,以确保它作为合并后PDF文档的第一个文件出现。

步骤四:合并并添加目录与页脚

遍历每个PDF文件,读取其页面,并将它们添加到一个列表中。同时,我们记录每个文件的标题和起始页码,用于生成目录页。

在所有页面都处理完毕后,我们首先将目录页添加到最终的PDF文档中,然后为每个页面添加页脚,并将它们一一加入到文档中。

步骤五:保存最终文档

完成所有页面的处理和添加后,我们将这些页面写入到一个新的PDF文件中,完成了合并、添加目录和页脚的整个过程。

结语

通过上述步骤,我们展示了如何使用Python处理PDF文件,包括合并多个PDF文件、动态创建目录页和在每页底部添加自定义页脚。这种方法不仅提高了文档处理的自动化程度,也为管理和阅读PDF文档提供了便利。希望这篇文章能帮助到需要进行PDF文档处理的读者。

完整代码


import io
import osfrom PyPDF2 import PdfReader
from PyPDF2 import PdfWriter
from reportlab.lib.pagesizes import letter
from reportlab.pdfbase.pdfmetrics import stringWidth
from reportlab.pdfgen import canvasdef create_footer_page(footer_text):packet = io.BytesIO()c = canvas.Canvas(packet, pagesize=letter)width, height = letter  # letter页面的宽度和高度font_name = "Helvetica"  # 使用的字体font_size = 12  # 字体大小cover_height = font_size + 4  # 覆盖区域的高度稍大于字体大小,以确保完全覆盖原有页码cover_y_position = 28  # 覆盖区域的Y位置,根据需要进行调整以确保覆盖原有页码# 计算文本宽度和起始X位置以居中文本text_width = c.stringWidth(footer_text, font_name, font_size)text_start_position = (width - text_width) / 2# 绘制一个足够大的白色矩形以覆盖原有页码c.setFillColorRGB(1, 1, 1)  # 设置填充颜色为白色c.rect(0, cover_y_position, width, cover_height, stroke=False, fill=True)# 在页脚区域居中添加文本,高度可以根据需要调整c.setFont(font_name, font_size)  # 设置字体和大小c.setFillColorRGB(0, 0, 0)  # 设置文本颜色为黑色c.drawString(text_start_position, 32, footer_text)  # 绘制居中的页脚文本c.save()packet.seek(0)return PdfReader(packet)def add_catalog_page(bookmarks):packet = io.BytesIO()c = canvas.Canvas(packet, pagesize=letter)width, height = lettertop_margin = 60  # 顶部留白增加bottom_margin = 60  # 底部留白增加y_position = height - top_margin  # 根据顶部留白调整初始y_positionc.setFont("Helvetica-Bold", 16)  # 设置标题的字体和大小c.drawString(280, y_position, "Directory")  # 在顶部绘制标题“目录”y_position -= 30  # 更新y_position以为目录项留出空间c.setFont("Helvetica", 12)  # 设置目录项的字体和大小left_margin = 72right_margin = width - 72dot_space = 5  # 点间隔可调整different_title_spacing = 25  # 不同标题之间的间隔same_title_line_spacing = 15  # 同一个标题换行的间隔split_ratio = 0.9  # 定义分割点的位置比例for title, page_number in bookmarks:available_width = right_margin - left_margin - dot_space * 2  # 计算可用宽度title_width = stringWidth(title, "Helvetica", 12)page_number_str = str(page_number)page_number_width = stringWidth(page_number_str, "Helvetica", 12)# 判断标题是否需要分割if title_width > available_width * split_ratio:# 寻找分割点split_title = titlewhile stringWidth(split_title + "-", "Helvetica", 12) > available_width * split_ratio:split_title = split_title[:-1]split_title += "-"c.drawString(left_margin, y_position, split_title)y_position -= same_title_line_spacing  # 分割后的第二行位置向下调整,间隔小一点title = title[len(split_title) - 1:]# 绘制标题c.drawString(left_margin, y_position, title)# 绘制页码c.drawRightString(right_margin, y_position, page_number_str)# 绘制点线dot_line_start = left_margin + stringWidth(title, "Helvetica", 12) + 10dot_line_end = right_margin - page_number_width - 10current_position = dot_line_startwhile current_position < dot_line_end:c.drawString(current_position, y_position, ".")current_position += dot_spacey_position -= different_title_spacing  # 移到下一个标题,间隔大一点if y_position < bottom_margin:  # 根据底部留白调整翻页判断c.showPage()y_position = height - top_margin  # 重置y_position时也考虑顶部留白c.setFont("Helvetica", 12)  # 确保新页面开始时使用正确的字体和大小c.save()packet.seek(0)return PdfReader(packet)# 读取PDF/路径下所有.pdf为后缀的文件
pdf_dir = '我的论文集/'
pdf_files = sorted([f for f in os.listdir(pdf_dir) if f.endswith('.pdf')])# 找到特定文件并将其移动到列表的开头
specific_file = "第一篇.pdf"
if specific_file in pdf_files:pdf_files.insert(0, pdf_files.pop(pdf_files.index(specific_file)))#print(pdf_files)# 存储所有处理后的页面,以便后续添加页脚
all_pages = []
bookmarks = []
total_pages = 0# 首先处理每个PDF文件,但不立即添加页脚
for filename in pdf_files:reader = PdfReader(os.path.join(pdf_dir, filename))bookmarks.append((filename.replace('.pdf', ''), total_pages + 1))for page in reader.pages:all_pages.append(page)total_pages += 1# 创建目录页并添加到最终PDF
writer = PdfWriter()
catalog_pdf = add_catalog_page(bookmarks)
for page in catalog_pdf.pages:writer.add_page(page)# 为每页添加页脚,并将页面添加到最终的PDF中
current_page_number = 1  # 从目录页之后的第一页开始计数页码
catalog_pages_count = len(catalog_pdf.pages)  # 计算目录页数量for page in all_pages:# 不再需要在页码中加上目录页的数量footer_pdf = create_footer_page(f"Page number: {current_page_number}")page.merge_page(footer_pdf.pages[0])writer.add_page(page)current_page_number += 1# 保存最终的PDF
output_pdf_path = "MergePapers.pdf"
with open(output_pdf_path, "wb") as f_out:writer.write(f_out)

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

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

相关文章

Mybaties-Plus saveBatch()、自定义批量插入、多线程批量插入性能测试和对比

一.背景 最近在做一个项目的时候&#xff0c;由于涉及到需要将一个系统的基础数据全量同步到另外一个系统中去&#xff0c;结果一看&#xff0c;基础数据有十几万条&#xff0c;作为小白的我&#xff0c;使用单元测试&#xff0c;写了一段代码&#xff0c;直接采用了MP(Mybati…

element---tree树形结构(返回的数据与官方的不一样)

项目中要用到属性结构数据&#xff0c;后端返回的数据不是官方默认的数据结构&#xff1a; <el-tree:data"treeData":filter-node-method"filterNode":props"defaultProps"node-click"handleNodeClick"></el-tree>这是文档…

SpringCloudGateway全局过滤器

文章目录 全局过滤器的作用自定义全局过滤器过滤器执行的顺序 上一篇 Gateway理论与实践 介绍的过滤器&#xff0c;网关提供了31种&#xff0c;但每一种过滤器的作用都是固定的。如果我们希望拦截请求&#xff0c;做自己的业务逻辑则没办法实现。 全局过滤器的作用 全局过滤器的…

怎样添加、移除、移动、复制、创建和查找节点?

1&#xff09;创建新节点 createDocumentFragment() //创建一个 DOM 片段createElement() //创建一个具体的元素createTextNode() //创建一个文本节点 2&#xff09;添加、移除、替换、插入 appendChild() //添加removeChild() //移除replaceChild() //替换insertBefore() /…

高级语言讲义2010计专(仅高级语言部分)

1.编写一程序&#xff0c;对输入的正整数&#xff0c;求他的约数和。 如&#xff1a;18的约数和为1236939 #include <stdio.h>int getsum(int n){int i,sum0;for(i1;i<n;i)if(n%i0)sumi;return sum; } int main(){int sum getsum(18);printf("%d",sum); …

JS直接量及其相关对象

什么是直接量 直接量是指不需要创建对象就可以直接使用的变量。ES中的直接量主要有三种类型&#xff1a;表示字符串的string类型、表示数字的number类型和表示true/false的boolean类型。当我们直接将值赋给变量后&#xff0c;ES就会自动判断其类型&#xff0c;而且当参数发生变…

Android14之编译输出system/product/vendor/odm分区(一百九十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

机器学习流程—数据预处理 Encoding

机器学习流程—数据预处理 Encoding 在机器学习中,我们经常会遇到分类变量,这些分量变量往往机器学习模型没有办法从中学习,往往有两种,一种是字符型,一种是数值型。通常需要对分类型变量做一些处理,常用的方法有两种:label encoding和one hot encoding。 例如,假设数…

「AI工程师」数据处理与分析-工作指导

工作指导书 一、工作职责 负责数据的收集、清洗、整合和处理,确保数据质量和准确性。进行数据分析和挖掘,提取有价值的信息,为业务决策提供支持。构建和维护数据处理和分析的流程和工具,提高数据处理效率。与其他团队成员合作,共同解决数据处理和分析过程中遇到的问题。二…

深度学习相关概念及术语总结

目录 1.CNN2.RNN3.LSTM4.NLP5.CV6.正向传播7.反向传播8.sigmoid 函数9.ReLU函数10.假设函数11.损失函数12.代价函数 1.CNN CNN 是卷积神经网络&#xff08;Convolutional Neural Network&#xff09;的缩写。卷积神经网络是一种深度学习模型&#xff0c;专门用于处理具有网格状…

kotlin筑基(2)

文章目录 一、internal二、infix三、object四、open五、operator六、sealed class1. 用法一2. 用法二 七、typealias八、when1. 带参数2. 不带参数 一、internal internal 修饰方法&#xff0c;表示这个方法只适合当前 module 使用&#xff0c;同理修饰 class 也是一样。 Syn…

mysql的time_zone导致性能下降

mysql的 time_zone有哪些 MySQL 的 time_zone 变量可以设置为多种不同的时区值。这些值通常由字母和数字组成&#xff0c;代表世界上不同的地理区域和它们的标准时间。以下是一些常见的 time_zone 设置选项&#xff1a; UTC&#xff1a;协调世界时&#xff0c;是国际标准时间…

2024年3月ZZUACM 招新赛题解

2024年3月ZZUACM 招新赛 题号题目A区间次大值B上课签到C魔法森林&#xff08;一&#xff09;D魔法森林&#xff08;二&#xff09;ELOPF跳格子G猜数字H抽卡记录I安达的二维矩阵J安达的数字手术K跳楼梯L前缀和 A 区间次大值—循环/签到题 题目描述 给定一个 n n n的全排列 a i…

顶顶通呼叫中心中间件(基于FreeSWITCH)对接mrcp asr 和 tts配置

介绍 顶顶通呼叫中心中间件通过asrproxy程序对接各种asr和tts,本文主要讲解 asrproxy程序 对接 mrcp v1 和 mrcp v2的配置。 asrproxy.json配置 asr配置 "asr":{"interface": {"mrcp": {"count": 0,"fault_threshold": 0…

外包干了10天,技术退步明显···

先说一下自己的情况&#xff0c;本科生&#xff0c;通过校招进入杭州某软件公司&#xff0c;干了接近3年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试&…

【鸿蒙开发】第十七章 Web组件(一)

1 Web概述 Web组件用于在应用程序中显示Web页面内容&#xff0c;为开发者提供页面加载、页面交互、页面调试等能力。 页面加载&#xff1a;Web组件提供基础的前端页面加载的能力&#xff0c;包括&#xff1a;加载网络页面、本地页面、html格式文本数据。 页面交互&#xff1a…

Java必须掌握的clear【】(含面试大厂题和源码)

面试题&#xff1a;“如何在Java中清除一个数组中的所有元素&#xff1f;” 在Java中&#xff0c;清除一个数组中的所有元素并不意味着逐个删除数组的每个元素&#xff0c;因为这在数组中是不可能做到的。数组的大小在初始化之后是固定的&#xff0c;无法改变。因此&#xff0…

IOS面试题object-c 1-10

1、简述Object-C的理解与特性&#xff1f; OC 作为一门 面向对象 的语言&#xff0c;自然具有面向对象的语言特性&#xff1a;封装、继承、多态。 它既具有 静态语言的特性&#xff08;如C&#xff09;&#xff0c;又有 动态语言的效率&#xff08;动态绑定、动态加载等&#…

JAVA使用的工具类-Hutool

文章目录 Hutool工具类简介1&#xff1a;身份证工具类相关方法1.1 身份证脱敏处理 字符串补零处理(此处是JAVA类的方法&#xff0c;并无引用StrUtil)springboot前后端分离&#xff0c;后端返回json字符串带斜杠问题处理 在这里整理一下博主常用的工具类方法-hutool工具类,这里囊…

制定一份完美的测试计划,让您的产品质量更上一层楼!

大家好&#xff0c;我是彭于晏。今天学习测试计划如何书写。 虽然很多人日常工作中都知道测试计划是什么&#xff0c;但是写好测试计划&#xff0c;其实并不容易。今天就来一起学习下测试计划如何书写。 什么是测试计划&#xff1f; 测试计划是一份为软件产品所准备的详细文档…