
C#使用iTextSharp给PDF添加水印:文字水印与图片水印完整教程
在很多业务场景中,PDF文件都需要加上水印来保护版权、标明状态或防止泄密。比如合同文件加盖“内部资料 禁止外传”、报表文件打上“草稿”字样、或者在企业宣传资料上叠加公司Logo。iTextSharp作为一款成熟的.NET PDF处理库,提供了非常灵活的方式来操作PDF内容。本文将手把手教你如何在C#项目中利用iTextSharp实现文字水印和图片水印的添加,并深入解析每个参数的含义,让你不仅能复制代码,还能理解背后的原理。
一、环境准备与基础概念
1.1 安装iTextSharp库
iTextSharp是iText的.NET移植版本,支持创建、编辑和读取PDF文件。你可以通过NuGet包管理器安装,在Visual Studio中右键项目 -> 管理NuGet程序包 -> 搜索“iTextSharp” -> 点击安装。也可以手动下载DLL文件添加到项目引用。安装完成后,在代码文件顶部引入必要的命名空间:
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.IO;这三个命名空间分别提供了基础类型(如字体、颜色)、PDF操作核心类(如PdfReader、PdfStamper)以及文件流操作。
1.2 理解核心对象:PdfReader、PdfStamper与PdfContentByte
- PdfReader:用来读取已有的PDF文件,获取页面数量、页面尺寸等信息。
- PdfStamper:相当于一个“印章”,可以在不改变原PDF结构的情况下,在现有页面上添加额外内容(水印、注释、表单等)。它有两种工作模式:
GetOverContent()在原有内容之上添加(覆盖模式),GetUnderContent()在原有内容之下添加(底层模式)。通常水印放在上层更合适。 - PdfContentByte:代表一个页面的图形上下文,所有绘图操作(文字、图像、线条)都在这个对象上进行。
了解这三个对象的关系后,添加水印的基本流程就清晰了:用PdfReader打开源文件,用PdfStamper创建一个输出文件,然后遍历每一页,通过GetOverContent获取当前页的画布,在画布上绘制水印,最后关闭所有资源。
二、添加文字水印的详细实现
文字水印是最常用的类型,可以自定义内容、字体、大小、颜色、旋转角度和透明度。下面我们一步步实现一个倾斜45度、半透明的“内部资料 禁止外传”水印。
2.1 核心代码
public void AddTextWatermark(string sourcePdfPath, string outputPdfPath, string watermarkText)
{
// 1. 读取原始PDF
PdfReader reader = new PdfReader(sourcePdfPath);
// 2. 创建输出文件流
using (FileStream fs = new FileStream(outputPdfPath, FileMode.Create))
{
// 3. 创建PdfStamper
PdfStamper stamper = new PdfStamper(reader, fs);
// 4. 设置水印字体(这里使用Windows宋体,注意路径和编码)
BaseFont baseFont = BaseFont.CreateFont(
@"C:\Windows\Fonts\simsun.ttc,0",
BaseFont.IDENTITY_H,
BaseFont.EMBEDDED);
// 5. 获取总页数
int pageCount = reader.NumberOfPages;
// 6. 遍历每一页
for (int i = 1; i <= pageCount; i++)
{
// 获取当前页的覆盖层画布
PdfContentByte canvas = stamper.GetOverContent(i);
// 7. 设置透明度
PdfGState gState = new PdfGState();
gState.FillOpacity = 0.3f; // 0完全透明,1完全不透明
canvas.SetGState(gState);
// 8. 设置字体和字号
canvas.SetFontAndSize(baseFont, 36);
// 9. 设置颜色(浅灰色)
canvas.SetColorFill(BaseColor.LIGHT_GRAY);
// 10. 保存当前画布状态(便于后续恢复)
canvas.SaveState();
// 11. 将坐标系原点移动到页面中心
float pageWidth = reader.GetPageSize(i).Width;
float pageHeight = reader.GetPageSize(i).Height;
canvas.ConcatCTM(1f, 0f, 0f, 1f, pageWidth / 2, pageHeight / 2);
// 12. 旋转坐标系45度(旋转矩阵)
float cos = (float)Math.Cos(Math.PI / 4); // 约0.707
float sin = (float)Math.Sin(Math.PI / 4); // 约0.707
canvas.ConcatCTM(cos, sin, -sin, cos, 0, 0);
// 13. 绘制水印文字(居中对齐)
canvas.BeginText();
canvas.ShowTextAligned(Element.ALIGN_CENTER, watermarkText, 0, 0, 0);
canvas.EndText();
// 14. 恢复画布状态
canvas.RestoreState();
}
// 15. 关闭stamper(会自动刷新内容到输出流)
stamper.Close();
}
// 注意:PdfReader在stamper.Close()之后会被自动关闭,无需手动关闭
}2.2 代码逐段解析
为什么要用PdfGState设置透明度?
直接绘制的文字默认是不透明的。通过PdfGState对象可以单独控制填充透明度(FillOpacity)和描边透明度(StrokeOpacity)。这里只设置了填充透明度为0.3,水印就会呈现半透明效果,不会遮挡底下的正文内容。
字体路径和编码为何这样写?
BaseFont.CreateFont的第一个参数是字体文件路径。Windows系统中宋体文件位于C:\Windows\Fonts\simsun.ttc,后面的,0表示使用字体集合中的第一个字体(因为.ttc是TrueType集合)。第二个参数BaseFont.IDENTITY_H表示使用Unicode编码(支持中文),第三个参数BaseFont.EMBEDDED表示将字体嵌入PDF中,这样即使对方电脑没有安装宋体也能正常显示。如果你希望减小文件体积,可以选择BaseFont.NOT_EMBEDDED,但需确保目标环境有该字体。
SaveState和RestoreState的作用是什么?
画布的状态包括当前的变换矩阵(平移、旋转、缩放)、颜色、字体等。SaveState会把当前状态压入栈中,之后做的任何变换(如平移、旋转)都不会影响到后续页面的绘制。当RestoreState被调用时,画布恢复到之前保存的状态。如果不这么做,第一页旋转后的坐标系会影响第二页,导致水印位置错乱。
旋转矩阵的原理
ConcatCTM方法用于拼接一个2x3的变换矩阵。标准的旋转变换公式为:
[cosθ sinθ 0]
[-sinθ cosθ 0]
[tx ty 1]
这里我们先将原点移到页面中心,然后应用旋转矩阵(θ=45°),这样水印就以页面中心为轴旋转了45度。你也可以改成其他角度,比如30度或60度。
ShowTextAligned的参数含义
ShowTextAligned(alignment, text, x, y, rotation):alignment指定对齐方式(左、中、右),这里用Element.ALIGN_CENTER;x和y是相对于当前坐标系的坐标(由于我们已经将原点移到了页面中心,所以(0,0)就是页面中心);rotation是额外的旋转角度,这里设为0,因为我们已经在前面通过矩阵旋转过了。
三、添加图片水印的详细实现
有时我们需要在PDF上添加公司Logo或特定图案作为水印。图片水印的实现逻辑与文字水印类似,但需要注意图片的缩放、定位和透明度设置。
3.1 核心代码
public void AddImageWatermark(string sourcePdfPath, string outputPdfPath, string imagePath)
{
PdfReader reader = new PdfReader(sourcePdfPath);
using (FileStream fs = new FileStream(outputPdfPath, FileMode.Create))
{
PdfStamper stamper = new PdfStamper(reader, fs);
// 1. 读取图片并设置缩放
Image img = Image.GetInstance(imagePath);
img.ScaleToFit(200f, 200f); // 宽度或高度不超过200像素,保持比例
// 2. 设置透明度
PdfGState gState = new PdfGState();
gState.FillOpacity = 0.25f;
int pageCount = reader.NumberOfPages;
for (int i = 1; i <= pageCount; i++)
{
PdfContentByte canvas = stamper.GetOverContent(i);
canvas.SetGState(gState);
// 3. 计算居中位置
float pageWidth = reader.GetPageSize(i).Width;
float pageHeight = reader.GetPageSize(i).Height;
float x = (pageWidth - img.ScaledWidth) / 2;
float y = (pageHeight - img.ScaledHeight) / 2;
img.SetAbsolutePosition(x, y);
// 4. 将图片添加到画布
canvas.AddImage(img);
}
stamper.Close();
}
}3.2 关键点说明
ScaleToFit与SetAbsolutePosition
ScaleToFit(width, height)会在保持图片宽高比的前提下,将图片缩放到指定的最大尺寸内。这里设为200×200,如果你的Logo很大,它会自动缩小。SetAbsolutePosition(x,y)设置图片左下角在页面上的坐标。通过计算让图片居中显示。
为什么图片水印也需要PdfGState?
图片本身也有透明度属性,但AddImage方法不支持直接设置透明度。通过canvas.SetGState(gState),后续所有绘制操作(包括添加图片)都会受到透明度影响。注意这里的gState只设置了填充透明度,但对于图片来说,它的效果等同于整体透明度。
重复使用同一Image对象
注意代码中我们在循环外部创建了img对象,然后在循环内多次调用canvas.AddImage(img)。这是可以的,因为每次添加只是引用同一个图片资源,并不会重复加载。但要注意,SetAbsolutePosition必须在每次添加前重新设置,因为位置是针对当前页面的。
四、方法调用与完整示例
将上面两个方法封装到一个类中(例如PdfWatermarkHelper),然后在主程序中调用:
class Program
{
static void Main(string[] args)
{
string src = @"D:\test.pdf";
string textOut = @"D:\test_text_watermark.pdf";
string imgOut = @"D:\test_image_watermark.pdf";
string logo = @"D:\logo.png";
// 添加文字水印
AddTextWatermark(src, textOut, "内部资料 禁止外传");
// 添加图片水印
AddImageWatermark(src, imgOut, logo);
Console.WriteLine("水印添加完成!");
}
}运行前请确保源PDF文件存在且未被其他程序打开,输出目录有写入权限。
五、注意事项与常见问题
5.1 文件占用与资源释放
使用PdfStamper时,它会锁定输入文件直到Close()被调用。因此务必使用using语句或try-finally确保资源释放。上面的代码中使用了using包裹FileStream,并且stamper.Close()会在using块结束前执行。注意:PdfReader不需要显式关闭,因为PdfStamper.Close()会自动关闭与之关联的PdfReader。
5.2 中文乱码问题
如果水印文字出现乱码,通常是因为字体未正确加载或编码不对。请确认:
- 字体路径正确,且字体文件支持中文(如宋体、微软雅黑)。
- 使用
BaseFont.IDENTITY_H编码(支持Unicode)。 - 如果不想嵌入字体(减少PDF体积),可将第三个参数改为
BaseFont.NOT_EMBEDDED,但必须确保查看PDF的设备上有该字体。
5.3 水印位置与角度的灵活调整
- 如果想在页面四个角都加上水印,可以在循环中多次调用绘制方法,每次设置不同的平移坐标。
- 如果想实现平铺效果,可以用嵌套循环,按固定间距重复绘制。
- 旋转角度可以任意设定,比如改成30度:
float angle = 30 * Math.PI / 180;。
5.4 性能考虑
如果PDF页数非常多(上千页),建议不要在循环内反复创建BaseFont和Image对象,应该在循环外创建一次,重复使用。上面给出的代码已经遵循了这个原则。
六、总结
通过iTextSharp,我们可以非常方便地在C#中为PDF添加文字水印和图片水印。核心思路是利用PdfStamper获取页面画布,然后设置透明度、字体、颜色等属性,最后绘制文字或图像。理解SaveState/RestoreState和变换矩阵的使用,能让你实现更复杂的水印效果,比如多重水印、渐变水印等。希望本文能帮助你快速解决工作中的PDF水印需求,如果有其他问题,欢迎留言交流。
C#iTextSharpPDF水印PDF操作修改时间:2026-08-20 19:02:23