尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用Python和PyMuPDF自建本地PDF工具箱:合并、拆分与图片转PDF实战

用Python和PyMuPDF自建本地PDF工具箱:合并、拆分与图片转PDF实战 大家电脑里肯定都囤着一堆PDF文件下载的电子书、扫描的合同、从网页导出的资料、截图拼成的长图……真到要用的时候合并、拆分、转格式的需求就冒出来了。我之前也都是临时找在线工具但用多了之后发现两个问题一是广告和页数限制让人抓狂二是把文件传到别人的服务器上心里总是没底。所以去年下半年我干脆自己写了一个PDF工具箱覆盖合并、拆分、图片转PDF这三个最高频的场景从零到能用花了大概一个周末迭代半年多之后它已经成了我电脑里的常驻工具。这篇文章就把这个工具箱从选型、实现、踩坑到打包分享的全过程聊一遍。如果你也想自己搞一个PDF小工具或者对PDF处理背后的原理感兴趣可以按这篇文章的思路来代码量不大但每一段都经过实际使用验证直接抄作业就行。1. 为什么需要自建一个PDF工具箱个人使用场景复盘1.1 从一次帮家人整理老照片说起前年家里大扫除翻出一大堆纸质相册、老合同、手写笔记我弟提议全部拍照存档发给亲戚。手机拍了差不多两百张照片问题来了怎么把这些JPG快速变成几个PDF文件当时第一反应是搜在线工具。试了两三个问题立刻暴露出来免费版限制上传数量照片超过20张就得开会员有的工具给输出PDF加了满屏水印清晰度也被压缩得厉害最关键的是这些文件要在网页上先上传而里面有不少证件、合同这类敏感材料传上去之后总让人不踏实。折腾了一晚上最后我用Python在本地把两百多张照片转成了三个PDF整个过程也就十几秒。从那天起我就意识到很多临时需求其实用一个小脚本就能解决只是大多数时候我们习惯了去网上找现成工具没想过自己写一个。1.2 在线工具的隐性问题页数限制、广告和文件去了哪后来用在线工具的次数越多越觉得不对劲。这里不是说在线工具一无是处偶尔救急确实方便但长期用下来有几个绕不开的坎限制多免费版通常有页数限制、文件大小限制、转换次数限制高峰期还要排队。广告和诱导很多工具页面塞满广告下载按钮和真实入口长得一模一样点错就是全家桶。隐私风险PDF上传到别人服务器服务商到底存不存储、用不用来训练模型你完全不知道。转换质量不稳定同一张图不同网站的压缩算法效果差异很大有的能明显看到锯齿和色块。所以我的结论是本地处理是刚需。尤其对于经常处理合同、扫描件、资料归档的人文件不出本机这个特性本身就价值极高。1.3 工具边界只做合并、拆分、图片转PDF这三件事做工具最忌讳一上来就想做成万能的瑞士军刀。我当时定的边界非常明确第一版只做合并、拆分、图片转PDF这三件事是我和身边朋友最高频的需求而且逻辑简单、不容易出幺蛾子。合并把几十个零散的PDF合成一个用于资料归档、材料提交。 拆分从一本电子书里提取某一章从合同扫描件里抽出一页发给对方。 图片转PDF把一堆照片、扫描件变成PDF保持顺序和画质。没做OCR、没做加密解密、没做水印这些后面再说。把高频场景做顺手比做大而全重要得多这个原则一直保留到现在。2. 技术选型PyMuPDF为什么是我的最终选择2.1 四个主流库的横向对比先别急着开写我把为什么选PyMuPDF这件事说清楚因为这个决定会直接影响后面所有代码的写法。目前Python生态里处理PDF的库常用的有这么几个库合并/拆分图片渲染能力API上手难度依赖体积维护状况pypdf强弱基本不做渲染低接口直观很小活跃pypdfium2中强基于PDFium中高偏底层中等活跃PyMuPDF强强基于MuPDF低文档丰富较大非常活跃PyPDF4等中无低小基本不维护pypdf是很多教程的首选纯Python实现安装包只有几百KB合并拆分几十行就能跑通。但它的短板在于不擅长渲染如果你要做图片转PDF、提取页面缩略图、按页面尺寸精确控制这种活它就会比较别扭。pypdfium2渲染能力不错不过API风格偏底层合并拆分这种操作要写的样板代码不少。PyMuPDF是二者之间的平衡点合并拆分简单直接渲染图像又快又稳定一个库覆盖了我所有的需求。2.2 一个库搞定所有比多个库各管一段好在哪我最怕的项目是那种合并用一个库、渲染用另一个库、OCR再挂一个库的组合每个库都有自己的版本依赖和API风格每次升级都可能踩雷。PyMuPDF吸引我的地方在于它底层是C语言写的MuPDF渲染引擎非常成熟对PDF规范的支持相当完整同时上层API又做得简洁页面操作基本围绕doc和page这两个核心对象展开。包体积大是它唯一的痛点pip安装时下载几十MB是常态打包成exe之后也会明显变大。但换个角度看程序体积大总比代码逻辑复杂、依赖链混乱要好。个人工具稳定优先这几MB的差距完全可以接受。2.3 环境准备安装PyMuPDF时的几个注意点安装本身很简单pip install PyMuPDF有几个容易被坑的点值得单独提一下包名是PyMuPDF但代码里导入的是import fitz。这是历史原因PyMuPDF早期直接用MuPDF的捆绑名fitz沿用至今。很多人在这一步卡住装了包却找不到fitz模块其实只是还没import。建议在虚拟环境里安装不要直接往系统Python里塞。我自己吃过亏系统Python的site-packages被各种版本搞乱了后来一律用venv。Python版本不要太旧PyMuPDF官方会跟上新版Python老版本Python装不上新版本PyMuPDF。用3.9以上基本没坑。3. 三个核心模块的实现思路与关键代码3.1 合并PDF一个insert_pdf就能完成但排序和清理不能省合并PDF核心就是一个方法insert_pdf。它的作用是把另一个PDF文档的页面整体插入到当前文档中支持指定页码范围from_page和to_page也支持指定插入位置start_at。整个合并代码其实很短import fitz from pathlib import Path import re def natural_sort_key(name: str) - list: 自然排序10.pdf 应该排在 2.pdf 后面 而不是按字符串顺序排到前面。 return [int(t) if t.isdigit() else t.lower() for t in re.split(r(\d), name)] def merge_pdfs(pdf_paths: list[str], output_path: str): doc fitz.open() # 空文档作为合并后的容器 for path in pdf_paths: p Path(path) if not p.exists(): print(f跳过{path} 不存在) continue try: src fitz.open(str(p)) except Exception as e: print(f打开失败{path}错误{e}) continue doc.insert_pdf(src) src.close() doc.save(output_path, garbage3, deflateTrue) doc.close()代码看着简单但里面有两个容易翻车的细节。第一是排序文件管理器里的排序是按字符串来的第1章.pdf、第10章.pdf、第2章.pdf这三个文件按字符串排序会变成第1章、第10章、第2章合并出来的PDF顺序完全错乱。所以我在合并前强制做一次自然排序把文件名里的数字提取出来转成整型再比较。数字章节、带编号的扫描件这种场景特别容易踩这个坑。第二个是保存参数garbage3会清理合并过程中产生的孤立对象和未引用数据deflateTrue会压缩内部的数据流。这两个参数组合下来能避免合并后的文件莫名膨胀也能让某些原本有很多冗余资源的PDF瘦身。实测合并20个左右的文件输出体积基本等于所有源文件体积之和不会有意外放大。3.2 拆分PDF按页提取和按厚度切分的两种写法拆分比合并稍微灵活一点实际场景一般分三种提取指定页码、提取一个连续页码范围、把整本PDF按固定厚度切成多份。提取指定页面def extract_pages(input_pdf: str, start: int, end: int | None, output_pdf: str): 从第 start 页到第 end 页提取并保存。 start/end 都按人类习惯从 1 计数。 src fitz.open(input_pdf) total len(src) if start 1 or start total: print(f起始页码无效{start}文档共 {total} 页) return if end is None or end total: end total out fitz.open() # PyMuPDF 内部页码从 0 开始所以这里要 -1 out.insert_pdf(src, from_pagestart - 1, to_pageend - 1) out.save(output_pdf, garbage3, deflateTrue) out.close() src.close()按固定厚度切分比如每10页生成一个文件def split_by_chunk(input_pdf: str, chunk_size: int, output_dir: str): src fitz.open(input_pdf) total len(src) stem Path(input_pdf).stem os.makedirs(output_dir, exist_okTrue) index 1 for start in range(1, total 1, chunk_size): end min(start chunk_size - 1, total) out fitz.open() out.insert_pdf(src, from_pagestart - 1, to_pageend - 1) out_name os.path.join(output_dir, f{stem}_part_{index:02d}.pdf) out.save(out_name, garbage3, deflateTrue) out.close() index 1 src.close()拆分这块要强调页码偏移问题。人类眼里的第1页通常是文档正文第一页但很多电子书前面还带着封面、扉页、目录页实际页和PDF内部页可能会差好几页。所以我做界面的时候特意把文档总页数显示出来并在拆分前提醒用户确认页码范围这个交互细节后面还会再提。3.3 图片转PDF两种方案一套代码解决透明通道和EXIF方向图片转PDF是三个功能里最容易低估难度的一个。如果只是把图片塞进PDF用PIL几行就完事from PIL import Image, ImageOps def images_to_pdf_simple(image_paths: list[str], output_pdf: str): imgs [] for path in image_paths: img Image.open(path) img ImageOps.exif_transpose(img) # 解决手机拍摄照片方向错乱 if img.mode in (RGBA, LA, P): rgb Image.new(RGB, img.size, white) if img.mode P: img img.convert(RGB) else: rgb.paste(img, maskimg.split()[-1]) img rgb else: img img.convert(RGB) imgs.append(img) if imgs: imgs[0].save(output_pdf, save_allTrue, append_imagesimgs[1:], resolution150.0)这条路线的优点是简单PIL的save方法原生支持多页PDF输出完全不用碰PDF底层API。缺点是控制力弱页面尺寸、图片摆放位置、每页对应多大物理尺寸你说了不算。所以我的工具箱实际用的是PyMuPDF方案import fitz from PIL import Image, ImageOps A4_WIDTH 595 # 72dpi 下 A4 宽度单位是点 A4_HEIGHT 842 # 72dpi 下 A4 高度 def images_to_pdf_pymupdf(image_paths: list[str], output_pdf: str, dpi150, force_a4False): doc fitz.open() for path in image_paths: img Image.open(path) # 方向修正必须在转 RGB 之前做顺序不能反 img ImageOps.exif_transpose(img) # 统一转成 RGB避免 RGBA 出现黑底 if img.mode in (RGBA, LA, P): background Image.new(RGB, img.size, white) if A in img.mode: background.paste(img, maskimg.split()[-1]) else: background.paste(img) img background else: img img.convert(RGB) if force_a4: # 等比缩放留 5% 边距居中放置 scale min(A4_WIDTH / img.width, A4_HEIGHT / img.height) * 0.95 w img.width * scale h img.height * scale rect fitz.Rect((A4_WIDTH - w) / 2, (A4_HEIGHT - h) / 2, (A4_WIDTH w) / 2, (A4_HEIGHT h) / 2) page doc.new_page(widthA4_WIDTH, heightA4_HEIGHT) page.insert_image(rect, filenamepath, keep_proportionTrue) else: # 按图片实际尺寸和指定 DPI 换算页面大小 width_pt img.width * 72.0 / dpi height_pt img.height * 72.0 / dpi page doc.new_page(widthwidth_pt, heightheight_pt) page.insert_image(page.rect, filenamepath, keep_proportionTrue) doc.save(output_pdf, garbage3, deflateTrue) doc.close()这里我解释一下几个关键点EXIF方向问题。手机拍的JPG会内置一个旋转标记比如拍摄时手机是横着的但照片实际应该竖着显示。如果用Image.open直接读拿到的像素还是原始的直接塞进PDF就会看到照片横着。ImageOps.exif_transpose()会把旋转应用到像素本身上这一步必须在模式转换之前做。透明通道黑底问题。PDF本身不支持透明度如果你直接把带Alpha通道的PNG插进PDF透明区域渲染出来通常是黑色。解决办法是把透明像素先贴到白色背景上。我见过很多人卡在这里怎么调都调不好其实就是没做这层预处理。页面尺寸换算。PDF的内部单位是点point1英寸等于72点。一张3000像素宽的图片按150DPI显示物理宽度就是3000乘以72再除以150等于1440点。这个换算逻辑决定了图片在PDF里的实际打印尺寸。工具里我默认提供两个模式一个是按图片原始尺寸输出一个是统一压成A4扫描件和资料归档强烈建议用A4模式输出物打印出来规格统一比每页尺寸都不一样舒服得多。3.4 输出参数为什么建议带garbage和deflate细心的读者应该注意到了我每个save操作都带着garbage3, deflateTrue。这里单独说下背后的机制。PDF文件内部是一个对象系统页面、字体、图像、内容流都以对象形式存在对象之间互相引用。合并多个PDF时insert_pdf会把所有对象都复制进新文档但其中有不少是未被引用的孤立对象比如某些编辑软件留下的废弃资源。garbage参数就负责清理这类对象取值从0到4数字越大清理越激进其中garbage3是一个性能和效果平衡的选择garbage4除了清理对象还会做更多重构但速度会变慢。deflateTrue则是对文档内部的数据流做压缩包括内容流和图像数据。需要说明的是一些已经压缩过的内容流不会重复压缩所以它不会明显降低文件体积但能保证合并过程不意外产生冗余。在实际使用中我默认都用这两个参数除非遇到体积超大、保存耗时明显变长的情况才会把garbage降级到1或2。4. 界面设计先用命令行跑通再套GUI壳4.1 为什么GUI只用tkinter而不是Electron之类我的开发顺序是先写命令行版本把所有功能参数验证通过之后再考虑界面。这个习惯让我少走了很多弯路因为GUI调试会引入大量和核心逻辑无关的问题比如布局、事件循环、线程阻塞。选tkinter很简单Python自带不需要额外装包写出来的界面虽然朴素但是能在Windows、macOS、Linux上直接跑。对比一下其他方案Electron要拉一个Node.js的依赖链PyQt的学习成本和许可证问题也需要考虑我做的是给自己和朋友用的工具目的是解决问题而不是追求界面的炫酷。4.2 界面布局与操作流程界面布局我走了最朴素的三段式顶部模式选择用下拉框切换合并PDF拆分PDF图片转PDF。中部文件列表支持多选添加、单选删除、清空列表。列表上显示文件名和页面数方便确认顺序。底部输出路径选择、开始按钮、状态栏和日志区。合并模式下输出路径用asksaveasfilename让用户选拆分模式需要额外输入页码范围或每份页数图片转PDF需要选择是否强制A4。功能按钮根据当前模式动态切换避免用户填错参数。代码骨架长这样import tkinter as tk from tkinter import ttk, filedialog, messagebox import threading class PDFToolApp(tk.Tk): def __init__(self): super().__init__() self.title(PDF 工具箱) self.geometry(720x520) self.mode_var tk.StringVar(valuemerge) self.file_listbox tk.Listbox(self, selectmodetk.EXTENDED) # ... 省略布局代码 def add_files(self): files filedialog.askopenfilenames( filetypes[(PDF 和图片, *.pdf *.jpg *.jpeg *.png *.bmp *.tif *.tiff)]) for f in files: self.file_listbox.insert(tk.END, f) def on_start(self): mode self.mode_var.get() files list(self.file_listbox.get(0, tk.END)) if mode merge: if len(files) 2: messagebox.showwarning(提示, 请至少选择两个 PDF 文件) return output filedialog.asksaveasfilename( defaultextension.pdf, filetypes[(PDF 文件, *.pdf)]) if not output: return self.set_busy(True) threading.Thread( targetself.run_task, args(merge_pdfs, files, output), daemonTrue).start()这里有个交互细节值得说开始任务后立刻禁用所有按钮防止用户在后台运行时再次点击触发重复任务。日志区负责显示处理进度每个文件处理完成打印一行结果。4.3 多线程防卡死一个容易被新手忽略的问题tkinter是单线程事件模型所有UI更新必须在主线程完成。如果你在按钮回调里执行耗时操作界面会完全卡住Windows甚至会弹程序无响应的提示。解决办法是把耗时任务丢到后台线程任务完成后通过队列把结果传回主线程更新UIimport queue class PDFToolApp(tk.Tk): def __init__(self): self.msg_queue queue.Queue() # 每 100ms 检查一次队列 self.after(100, self.process_queue) def process_queue(self): try: while True: msg self.msg_queue.get_nowait() self.status_label.config(textmsg) except queue.Empty: pass self.after(100, self.process_queue) def run_task(self, func, *args): try: func(*args) self.msg_queue.put(任务完成) except Exception as e: self.msg_queue.put(f任务失败{e}) finally: self.after(0, lambda: self.set_busy(False))这种轮询队列的方式比直接跨线程调用UI方法安全得多也不会因为操作完就立刻self.destroy()这种操作引发奇怪的异常。界面这块我的原则是够用就好。文件拖拽我没有用tkinterdnd2之类的库因为增加了额外依赖还会在某些系统上遇到兼容问题现阶段的添加文件按钮已经完全满足需求。5. 实操踩坑记录编码、内存、损坏PDF和黑底图片5.1 中文路径和文件名现代库的表现与历史遗留坑中文路径是很多PDF工具翻车的高发区。PyMuPDF在这方面做得相当好在Windows上用中文路径打开、保存基本不会有问题因为底层接收的是Unicode字符串不再经过本地代码页转换。但有一个情况需要注意如果PDF是某些老软件生成的文件内部metadata可能还是GBK编码用PyMuPDF读取时能正常打开但打印文件名或标题会出现乱码。这属于PDF内部metadata不规范导致的问题不影响页面内容。我的处理方案是显示文件名时只取Path(path).name不依赖PDF内部标题如果确实需要metadata就包一层try/except读取失败时退回文件名。5.2 合并几百MB大文件时的内存控制合并多个大PDF是内存大户。insert_pdf会把源文档的所有对象复制到目标doc的内存空间合并几百MB的文件时内存占用轻松超过1GB。这个问题在32位Python下尤其致命直接MemoryError崩溃。我实测之后给自己定了几条经验规则一次合并的文件数控制在合理范围内四十个以内的普通PDF没什么问题。如果遇到单个文件特别大几百MB先单独处理这个文件不要和一堆小文件混在一起。保存时用garbage3在合并过程中同步清理孤儿对象能在一定程度上降低内存峰值。真遇到几千个文件的极端场景程序内做分批合并每批几十个生成临时文件最后再把临时文件合并成最终结果。逻辑不复杂但能显著降低峰值内存。日常使用中最稳妥的合并方式是边合并边保存不要把所有文件全部加载完再保存PyMuPDF的API设计就是逐文件insert_pdf天然支持这种流式处理你只需要注意别在doc里囤太多无用对象就行。5.3 损坏PDF的抢救思路损坏PDF是个躲不开的话题。下载了一半的PDF、某些国产软件导出的伪PDF、扫描仪生成的结构残缺文件这三种我都遇到过。PyMuPDF基于MuPDF引擎对容错性处理得不错很多pypdf打不开的文件它能打开。但真遇到打开就抛异常的情况我的抢救流程是这样的先尝试正常打开抓到异常后打印文件名不直接中断整个任务。如果打开成功但页面数据异常试试doc.tobytes(garbage4, cleanTrue)这会触发MuPDF的完整解析和重写等于把文件重新构建一遍有时候能救回来。如果PyMuPDF完全打不开最简单的保底方案是用Chrome之类的浏览器打开再通过打印功能生成一个新PDF。这个方法看起来很土但可靠性非常高因为浏览器渲染引擎对损坏PDF的容忍度通常高于PDF解析库。这个抢救逻辑我强烈建议做进工具里批量合并时遇到一个坏文件不要直接崩掉整个任务而是记录下来、继续处理其他文件最后单独提示用户哪个文件出了问题。用户感知会好很多。5.4 图片转PDF黑底问题的根源前面代码里已经写了解决方案这里再详细说下根源。PNG或者某些TIFF图片可能带Alpha通道表现为RGBA或LA模式。PDF格式不支持透明度插入图片时会有一个底色填充PyMuPDF默认填充黑色。黑色底在证件扫描件上极其难看看起来像反色一样。我的处理方案是把透明像素贴到白色背景上。具体逻辑是新建一张白色RGB图再用原图的Alpha通道作为mask把原图贴上去。这个操作要在PIL层面完成不要想着传到PDF里再处理。另外一个相关坑是调色板模式的PNG如果包含透明色先convert(RGBA)然后再走同一套白底流程顺序一定不能乱。5.5 页码错位的排查方法拆分功能遇到最多的用户反馈是拆出来页码不对。排查之后发现绝大多数情况不是程序bug而是对PDF页数理解有偏差。一本电子书通常包含封面页、扉页、版权页、目录页这些都会占页码。用户以为的第5页和PDF内部第5页可能差了两三页。我的解决方案是做拆分前先显示总页数让用户直接输入PDF内部页码并加了一行提示文字页码从1开始输入的是PDF内部页数不是书页上的印刷页码。同时在日志区输出实际提取的页范围方便用户核对。这一点不算技术难点但体现了工具设计的细心程度。很多工具不是不好用是没把用户容易误解的地方handle好。6. 打包成exe分享给朋友PyInstaller使用经验6.1 打包命令与模式选择功能稳定之后我开始把它打包成exe发给朋友用。用的PyInstaller命令很简单pip install pyinstaller pyinstaller --onefile --windowed --name PDFTool --iconapp.ico main.py--onefile会生成单个exe文件方便分发--windowed在Windows下隐藏控制台窗口不然每次运行都会弹一个黑色cmd框--name指定输出名称--icon是可选的应用图标。这里有个取舍要清楚--onefile每次启动都要把程序解压到临时目录exe体积越大启动越慢。PyMuPDF的DLL本身就大加上PIL和tkinter打包出来常常在80MB到100MB之间双击之后要等一两秒才会弹出窗口。如果实在无法接受这种启动延迟可以改用--onedir模式生成一个目录启动速度快很多缺点是分发时要压缩成zip包。6.2 体积、杀毒误报和启动速度的取舍打包完成后最头疼的就是杀毒软件误报。PyInstaller生成的exe有自解压机制启发式杀毒引擎经常把它当木马处理这是PyInstaller生态的老问题不是你的程序有问题。我的处理经验优先用--onedir模式误报概率相对低一些还能加速启动。发布前用--clean参数重新构建清掉缓存能减少一部分奇怪问题。如果还需要onefile建议把exe压缩成zip发布并附带SHA256校验值方便用户核对文件完整性。尽量用一个简单的数字签名工具签个名虽然个人签名不一定有效但能降低部分杀毒软件的拦截概率。另外一个细节是360之类国内杀毒软件对Python打包程序更敏感我自己打包的程序也被误报过好几次只能跟朋友解释加白名单。这是个人免费工具通用的命运不用太纠结。6.3 发布前的自测清单打包之后的自测一定不能省我分享一份自己的测试清单合并功能选择三个PDF文件名分别带中文和数字序号确认输出顺序正确。拆分功能输入页码范围确认输出文件页数正确、内容完整。图片转PDF用一张手机拍照的JPG注意选有EXIF方向信息的确认方向正确用一张带透明通道的PNG确认底色是白色而不是黑色。路径测试把PDF放到一个包含中文和空格的路径下确认能正常打开保存。双击测试在全新环境双击exe运行不做任何额外配置确认功能完整。这套流程我每次打包后都会跑一遍耗时不长但能避免把明显的问题发给朋友。7. 用了半年后的真实体会与下一个版本想加的功能7.1 哪个功能用得最多工具稳定跑了半年多我自己统计了一下使用频率合并用得最多几乎占了一半主要是整理电子书章节和材料归档图片转PDF大概占三成处理扫描件和照片的时候特别好用尤其是A4统一模式输出效果比很多在线工具都干净拆分用得最少但每次都是挺紧急的需求比如从合同里抽出一页发出去从资料里提取指定章节打印。这个数据和我最初设想的高频场景基本一致也验证了先做三个核心功能的方向是对的。工具不需要大而全只要解决真正高频的痛点自己就会愿意天天用它。7.2 用户反馈里最频繁的需求把工具给几个朋友用之后收集到的需求反馈集中在几类一是加密PDF的处理。朋友经常收到带打开密码的PDF文件希望工具能解密后合并或拆分。这个技术上不难PyMuPDF的doc.authenticate(password)就能处理。我计划在下一版里加一个打开密码输入框如果检测到PDF需要密码就让用户输入。二是水印需求。有人想把内部资料加上仅供预览水印再发出去。用PyMuPDF的page.insert_text就能实现逻辑也不复杂但要注意水印位置、角度、透明度这些细节。三是压缩PDF。这个需求比预想的更强烈很多人拍完照片转成PDF之后体积很大发微信不方便。压缩的思路是重新渲染低分辨率图片再生成新PDF但这样会丢失文字层更适合扫描件。文字型PDF不建议用这种方式压缩我有专门的处理方向后面再聊。7.3 基于实际需求的功能扩展计划我的规划是先解决加密PDF和水印这两个明确的高频需求因为实现成本低、收益直观。压缩功能会更谨慎打算做成独立模块提供扫描件模式和文字模式两种选项避免一刀切破坏文档质量。OCR是远期计划真正要做的话我倾向于用Tesseract或者接一个本地OCR引擎让扫描件可以全文搜索。不过这属于较大的工程短期不会动手因为合并、拆分、图片转PDF这些基础操作已经能覆盖绝大多数日常场景了。最后再分享一个小技巧如果你把这个工具箱的习惯扩展到其他常用软件上可以用Everything这类全局搜索工具快速定位文件然后把操作流程固定下来——我现在的日常路径是Everything找到文件、拖进收藏的快捷方式、运行脚本、收工。工具的价值不在于功能多炫而在于它切切实实节省了每天反复操作的时间这个PDF工具箱从写下第一行代码到现在已经帮我省了不知道多少个下午。
返回列表