使用iText7 C#读写PDF标注/BSIColumnData属性值的实现与报错排查
问题根因定位
你现有代码运行报错,本质是踩了三个固定的实现坑:
- 节点位置匹配错误:Bluebeam存储的自定义标记属性
BSIColumnData,不是直接挂载在注释根字典上的,而是嵌套在注释字典的EP(扩展属性)子字典内部,直接在注释根层级搜索,根本定位不到这个属性节点。 - 键对象引用错误:iText7内置的
PdfName类只预定义了PDF国际标准规范里的键名,根本没有为Bluebeam自定义的BSIColumnData提供静态常量实例,你代码里直接写PdfName.BSIColumnData的逻辑本身就过不了编译,就算强行绕过编译环节,也会因为键值不匹配取不到实际内容。 - 数组解析逻辑疏漏:手动遍历页面的
Annots数组时,碰到以间接引用形式存储的注释对象,GetAsDictionary(x)会直接取不到实际的字典结构,最终触发空引用异常或者漏读注释内容。
正确实现方案
读取BSIColumnData
修正后的完整读取逻辑如下,自动处理注释间接引用、定位正确的EP扩展节点:
public void ReadBSIColumnData() { string pdfPath = @"C:\test PDF.pdf"; using var pdfReader = new iText.Kernel.Pdf.PdfReader(pdfPath); using var pdfDoc = new iText.Kernel.Pdf.PdfDocument(pdfReader); int numberOfPages = pdfDoc.GetNumberOfPages(); for (int pageIndex = 1; pageIndex <= numberOfPages; pageIndex++) { var page = pdfDoc.GetPage(pageIndex); // 直接调用框架封装的注释获取方法,自动处理间接引用、嵌套注释结构 var pageAnnotations = page.GetAnnotations(); foreach (var annot in pageAnnotations) { var annotDict = annot.GetPdfObject(); // 先定位注释下的EP扩展属性子字典 var epDict = annotDict.GetAsDictionary(iText.Kernel.Pdf.PdfName.EP); if (epDict == null) continue; // 手动创建自定义键对象,替代不存在的内置PdfName常量 var bsiKey = new iText.Kernel.Pdf.PdfName("BSIColumnData"); var bsiValue = epDict.GetAsString(bsiKey); if (bsiValue != null) { MessageBox.Show($"第{pageIndex}页注释的BSIColumnData值:{bsiValue.ToString()}"); } } } }
实现注意点:
- 不要再手动解析页面的
Annots数组,iText7封装的Page.GetAnnotations()方法已经做了全类型的对象解析,能避免大部分间接引用导致的空引用问题。 - 所有Bluebeam的自定义键,都需要手动通过
new PdfName("键名")创建,不要尝试找内置的静态常量,根本不存在。
写入BSIColumnData
写入操作必须搭配读写双模式,不能只用只读的PdfReader打开文档,具体实现如下:
public void UpdateBSIColumnData(string newValue) { string srcPath = @"C:\test PDF.pdf"; string destPath = @"C:\test PDF_updated.pdf"; // 必须同时绑定Reader和Writer,才能完成修改写入 using var pdfReader = new iText.Kernel.Pdf.PdfReader(srcPath); using var pdfWriter = new iText.Kernel.Pdf.PdfWriter(destPath); using var pdfDoc = new iText.Kernel.Pdf.PdfDocument(pdfReader, pdfWriter); int numberOfPages = pdfDoc.GetNumberOfPages(); for (int pageIndex = 1; pageIndex <= numberOfPages; pageIndex++) { var page = pdfDoc.GetPage(pageIndex); var pageAnnotations = page.GetAnnotations(); foreach (var annot in pageAnnotations) { var annotDict = annot.GetPdfObject(); // 获取EP扩展字典,不存在就新建 var epDict = annotDict.GetAsDictionary(iText.Kernel.Pdf.PdfName.EP); if (epDict == null) { epDict = new iText.Kernel.Pdf.PdfDictionary(); annotDict.Put(iText.Kernel.Pdf.PdfName.EP, epDict); } // 写入BSIColumnData值 var bsiKey = new iText.Kernel.Pdf.PdfName("BSIColumnData"); epDict.Put(bsiKey, new iText.Kernel.Pdf.PdfString(newValue)); // 标记字典为已修改,确保内容能被持久化到输出PDF epDict.SetModified(); } } }
写入注意点:
- 绝对不要把
BSIColumnData直接存在注释的根字典上,Bluebeam读取自定义属性时会严格从EP子字典下查找,放在根节点的属性会被软件直接忽略。 - 如果碰到原PDF有加密的情况,需要在初始化PdfReader的时候传入对应的解密密码,否则会出现文档打开失败的问题。
内容的提问来源于stack exchange,提问作者Dragon
相关产品推荐
相关产品推荐

