如何用C#使用iTextSharp添加PDF水印

来源:网络编程作者:BIT程序员头衔:程序员
导读:本期聚焦于BIT程序员创作的《如何用C#使用iTextSharp添加PDF水印》,敬请观看详情。在C#开发中处理PDF文件时,添加水印是常见需求,iTextSharp作为成熟的PDF操作库能高效实现该功能。本文详细介绍使用iTextSharp为PDF添加文字水印和图片水印的完整流程,包括环境准备、核心API说明、不同场景下的代码实现,同时讲解水印位置调整、透明度设置、多页批量添加等实用技巧,帮助开发者快速掌握相关操作方法,解决实际项目中的PDF水印添加需求。

如何用C#使用iTextSharp添加PDF水印

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页数非常多(上千页),建议不要在循环内反复创建BaseFontImage对象,应该在循环外创建一次,重复使用。上面给出的代码已经遵循了这个原则。

六、总结

通过iTextSharp,我们可以非常方便地在C#中为PDF添加文字水印和图片水印。核心思路是利用PdfStamper获取页面画布,然后设置透明度、字体、颜色等属性,最后绘制文字或图像。理解SaveState/RestoreState和变换矩阵的使用,能让你实现更复杂的水印效果,比如多重水印、渐变水印等。希望本文能帮助你快速解决工作中的PDF水印需求,如果有其他问题,欢迎留言交流。

C#iTextSharpPDF水印PDF操作修改时间:2026-08-20 19:02:23

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。