Python批量实现为PDF添加文字水印和图片印章
文档在对外发送前加盖密级标识,是很多团队的例行动作:技术方案、报价单、合同附件发给客户或供应商之前,一般要加上"内部资料 禁止外传"一类的水印,或者盖上公司标识。一两份文件手工处理没有难度,文件一多就很难保证标识样式统一、每页覆盖完整。
用 Python 配合 Free Spire.PDF for Python 可以把这套 动作固化成脚本:文字水印用 PdfTilingBrush 在整页平铺,配合图形变换控制角度与透明度;图片印章叠加到页面画布上,以正片叠底混合压住正文而不影响阅读;批量处理则是在外层套一个文件循环。下面用一份 4 页的技术方案书和一份 1 页的报价单,把整套流程实现一遍。
本文使用的方法需要用到 Free Spire.PDF for Python,可通过 pip 安装:
pip install spire.pdf.free
Free 版单份文档以 10 页为限,加载与创建均适用。
1. 准备示例文档
先准备两份贴近实际业务的文档作为处理对象:一份技术方案书(每页一个小节,共 4 页),一份报价单(明细表格与合计,共 1 页)。两份文档均使用 Microsoft YaHei 并开启字体嵌入,保证在任何 PDF 阅读器中显示一致。
from spire.pdf.common import *
from spire.pdf import *
TITLE = PdfTrueTypeFont("Microsoft YaHei", 21.0, PdfFontStyle.Bold, True)
META = PdfTrueTypeFont("Microsoft YaHei", 10.5, PdfFontStyle.Regular, True)
HEAD = PdfTrueTypeFont("Microsoft YaHei", 14.0, PdfFontStyle.Bold, True)
BODY = PdfTrueTypeFont("Microsoft YaHei", 11.0, PdfFontStyle.Regular, True)
BLACK = PdfSolidBrush(PdfRGBColor(Color.get_Black()))
GRAY = PdfSolidBrush(PdfRGBColor(Color.get_Gray()))
RULE = PdfSolidBrush(PdfRGBColor(Color.get_Silver()))
MARGIN = 60.0
def wrap(text, per_line=38):
return [text[i:i + per_line] for i in range(0, len(text), per_line)]
def draw_para(page, text, y, leading=20.0):
for line in wrap(text):
page.Canvas.DrawString(line, BODY, BLACK, MARGIN, y)
y += leading
return y + 8.0
SECTIONS = [
("一、项目背景", [
"本方案书面向星河集团数字化转型项目,围绕业务系统上云与统一运维平台建设提出整体技术设计。项目覆盖华东、华北、华南三个区域的数据中心,计划分三个阶段实施。",
"现有系统采用分散部署模式,各业务线独立采购服务器与存储资源,资源利用率长期低于 40%,跨系统数据交换依赖人工导表,运维成本居高不下。本项目通过统一云平台实现资源集中管理、弹性调度与自动化运维。"]),
("二、总体架构", [
"平台采用两地三中心部署架构:生产中心与同城灾备中心之间通过双活链路互联,异地灾备中心承担数据归档与灾难恢复职能。核心组件包括资源调度引擎、统一存储网关与运维监控平台。",
"资源调度引擎负责容器化业务的编排与弹性伸缩;统一存储网关对接现有 SAN 存储与对象存储,向业务系统提供统一的存储接口;运维监控平台采集主机、网络与应用的运行指标,支持自定义告警规则与值班编排。"]),
("三、实施计划", [
"第一阶段完成基础环境搭建与试点业务迁移,周期三个月;第二阶段完成核心业务系统迁移与数据同步机制建设,周期六个月;第三阶段开展全量业务迁移与运维体系交接,周期三个月。",
"各阶段均设置里程碑评审点,由项目组与业务部门共同确认迁移范围与回退方案。关键路径上的资源采购与机房改造需在项目启动前完成招标流程,避免影响整体进度。"]),
("四、服务保障", [
"项目交付后提供为期一年的运维支持,包含 7×24 小时故障响应、季度巡检与配置优化服务。运维团队常驻现场两名工程师,负责日常变更操作与事件处理。",
"平台上线后按季度出具运行报告,内容涵盖资源使用率、故障统计与容量预测,作为后续扩容决策的依据。"]),
]
# 技术方案书:首页含标题与第一节,其余小节各占一页
doc = PdfDocument()
page = doc.Pages.Add()
page.Canvas.DrawString("星河云平台技术方案书", TITLE, BLACK, MARGIN, 90.0)
page.Canvas.DrawString("文档编号:XH-2026-014 编制日期:2026-09-21 密级:内部", META, GRAY, MARGIN, 132.0)
page.Canvas.DrawRectangle(RULE, RectangleF(PointF(MARGIN, 152.0), SizeF(page.Canvas.ClientSize.Width - 2 * MARGIN, 1.2)))
y = 190.0
head, paras = SECTIONS[0]
page.Canvas.DrawString(head, HEAD, BLACK, MARGIN, y)
y += 30.0
for p in paras:
y = draw_para(page, p, y)
for head, paras in SECTIONS[1:]:
page = doc.Pages.Add()
y = 90.0
page.Canvas.DrawString(head, HEAD, BLACK, MARGIN, y)
y += 30.0
for p in paras:
y = draw_para(page, p, y)
doc.SaveToFile("技术方案书.pdf")
doc.Close()
# 报价单:标题、明细表与合计
doc2 = PdfDocument()
page = doc2.Pages.Add()
page.Canvas.DrawString("项目报价单", TITLE, BLACK, MARGIN, 90.0)
page.Canvas.DrawString("报价单号:XH-QT-2026-021 日期:2026-09-21", META, GRAY, MARGIN, 132.0)
page.Canvas.DrawRectangle(RULE, RectangleF(PointF(MARGIN, 152.0), SizeF(page.Canvas.ClientSize.Width - 2 * MARGIN, 1.2)))
cols = [60.0, 115.0, 340.0, 400.0, 480.0]
headers = ["序号", "设备名称", "数量", "单价(元)", "金额(元)"]
rows = [
("1", "云平台管理节点", "2", "86,000", "172,000"),
("2", "计算节点", "12", "58,000", "696,000"),
("3", "分布式存储节点", "6", "92,000", "552,000"),
("4", "网络交换机", "8", "23,000", "184,000"),
]
TH = PdfTrueTypeFont("Microsoft YaHei", 11.0, PdfFontStyle.Bold, True)
y = 195.0
for x, h in zip(cols, headers):
page.Canvas.DrawString(h, TH, BLACK, x, y)
page.Canvas.DrawRectangle(RULE, RectangleF(PointF(MARGIN, y + 18.0), SizeF(page.Canvas.ClientSize.Width - 2 * MARGIN, 1.2)))
y += 36.0
for row in rows:
for x, v in zip(cols, row):
page.Canvas.DrawString(v, BODY, BLACK, x, y)
y += 28.0
page.Canvas.DrawRectangle(RULE, RectangleF(PointF(MARGIN, y + 2.0), SizeF(page.Canvas.ClientSize.Width - 2 * MARGIN, 1.2)))
y += 20.0
page.Canvas.DrawString("合计金额", TH, BLACK, 400.0, y)
page.Canvas.DrawString("1,604,000", TH, BLACK, 480.0, y)
y += 48.0
page.Canvas.DrawString("本报价含三年原厂质保,报价有效期 30 天。", META, GRAY, MARGIN, y)
doc2.SaveToFile("报价单.pdf")
doc2.Close()画布坐标以页面左上角为原点,页面可用宽度通过 page.Canvas.ClientSize.Width 获取,正文左右各留 60pt 边距。DrawString 不自动换行,段落需要先按每行 38 个字符折行(wrap 函数),再逐行递增 y 坐标输出;小节之间的分隔线用高 1.2pt 的浅色矩形绘制。
生成的技术方案书(第 1 页)如下,报价单的结构与明细在批量处理一节中展示:

2. 全页文字水印
平铺水印的核心是 PdfTilingBrush——一块尺寸固定的"画刷 单元",会被重复填充到目标区域。单元尺寸决定水印密度:设为页宽的一半、页高的三分之一,整页大约落入 6 处完整的文字。文字不直接画在页面上,而是先画在单元的图形对象上:把原点平移到单元中心并旋转 -30°,再以居中对齐输出文字,最后让整块单元作为画刷铺满整个页面。透明度取 0.22,屏幕上清晰可见,同时不干扰正文阅读。
PHRASE = "内部资料 禁止外传"
FONT = PdfTrueTypeFont("Microsoft YaHei", 22.0, PdfFontStyle.Regular, True)
BRUSH = PdfSolidBrush(PdfRGBColor(Color.get_DarkRed()))
def add_text_watermark(pdf_path, out_path, phrase, font, brush, opacity=0.22, angle=-30.0):
doc = PdfDocument()
doc.LoadFromFile(pdf_path)
for i in range(doc.Pages.Count):
page = doc.Pages[i]
tile = PdfTilingBrush(SizeF(page.Canvas.ClientSize.Width / 2.0, page.Canvas.ClientSize.Height / 3.0))
g = tile.Graphics
g.SetTransparency(opacity)
g.Save()
g.TranslateTransform(tile.Size.Width / 2.0, tile.Size.Height / 2.0)
g.RotateTransform(angle)
g.DrawString(phrase, font, brush, 0.0, 0.0, PdfStringFormat(PdfTextAlignment.Center))
g.Restore()
g.SetTransparency(1.0)
page.Canvas.DrawRectangle(tile, RectangleF(PointF(0.0, 0.0), page.Canvas.ClientSize))
n = doc.Pages.Count
doc.SaveToFile(out_path)
doc.Close()
return n
add_text_watermark("技术方案书.pdf", "技术方案书_文字水印.pdf", PHRASE, FONT, BRUSH)函数对文档的每一页执行同样的填充,多页文档的水印角度、间距、透明度因此完全一致,页与页之间不会错位。文案、字体、颜色都通过参数传入,同一方法可以换成其他密级表述,例如"样品 请勿传播"或"仅供评审使用"。
处理后的效果(以第 2 页为例):

3. 图片印章
图片印章用于加盖公司标识一类的图形。做法很直接:加载 PNG 标识,等比缩放到页宽的 62% 并居中,叠加绘制到每一页上。关键在于透明度与混合方式——以正片叠底(Multiply)和 0.16 的透明度绘制时,标识图片中的白色区域相当于"透明",只有笔画把页面像素加深,因此印章压在正文上时两侧都保持可读,不会出现一块突兀的底色。
LOGO = "水印素材_星河科技.png"
doc = PdfDocument()
doc.LoadFromFile("技术方案书.pdf")
pdf_image = PdfImage.FromFile(LOGO)
for i in range(doc.Pages.Count):
page = doc.Pages[i]
w = page.Canvas.ClientSize.Width * 0.62
h = w * pdf_image.Height / pdf_image.Width
x = (page.Canvas.ClientSize.Width - w) / 2.0
y = (page.Canvas.ClientSize.Height - h) / 2.0
page.Canvas.Save()
page.Canvas.SetTransparency(0.16, 0.16, PdfBlendMode.Multiply)
page.Canvas.DrawImage(pdf_image, x, y, w, h)
page.Canvas.Restore()
doc.SaveToFile("技术方案书_图片印章.pdf")
doc.Close()Save / Restore 把混合设置的作用范围限制在印章绘制之内,不影响画布上的其他内容。示例标识是一张白底的 1200×420 PNG,替换成公司实际图片时只需调整缩放比例,其余代码不用改动。
叠加印章后的效果:

4. 批量处理
把单个文件的处理逻辑放进循环,即可批量处理一批文档。下面的脚本复用前面的 add_text_watermark 函数,将结果统一输出到"批量输出"目录,原始文件保持不变:
import os
OUT_DIR = "批量输出"
os.makedirs(OUT_DIR, exist_ok=True)
FILES = ["技术方案书.pdf", "报价单.pdf"]
for name in FILES:
n = add_text_watermark(name, os.path.join(OUT_DIR, name), PHRASE, FONT, BRUSH)
print("已处理: %s -> 批量输出/(%d 页)" % (name, n))
print("批量处理完成,共 %d 个文件。" % len(FILES))运行输出:
已处理: 技术方案书.pdf -> 批量输出/(4 页)
已处理: 报价单.pdf -> 批量输出/(1 页)
批量处理完成,共 2 个文件。
批量输出目录中的报价单——明细表格、合计金额与有效期说明均保持原样,水印覆盖每一页:

5. 参数与说明
透明度与颜色按实际场景微调:文字水印 0.2–0.3、图片印章 0.12–0.2 是兼顾清晰度与可读性的区间,颜色建议选深红、灰蓝一类的低饱和色,打印成黑白稿时仍有足够对比度。
字体方面,示例使用系统字体家族名加载(Microsoft YaHei),第四个参数 embed=True 会把字体子集嵌入 PDF,不受目标机器字体环境影响。改用字体文件路径加载时,需指向单体 .ttf 文件——.ttc 字体集合文件不被支持。
批量处理前建议先用 doc.Pages.Count 检查页数;超过 Free 版 10 页上限的文档需要商业版支持。
最后一点是定位问题:水印解决的是"标识与溯源",本身不提供防篡改能力。对文档真实性有要求的场景(合同、投标文件),可以在水印之外叠加数字签名与访问权限控制,两者并不冲突。
至此,从文档生成、文字水印、图片印章到批量输出已成一条完整流程,脚本除 Spire.PDF 外没有其他依赖,可以直接嵌入文件发送前或归档时的处理环节。
以上就是Python批量实现为PDF添加文字水印和图片印章的详细内容,更多关于Python PDF添加水印的资料请关注脚本之家其它相关文章!


最新评论