找回密码
 立即注册
首页 业界区 安全 使用 PdfPig 处理 PDF 文档

使用 PdfPig 处理 PDF 文档

蓝娅萍 2025-6-28 14:10:07
在 使用 AI 应用模板扩展创建支持使用自定义数据进行 chat 的 .NET AI 应用 中,看到里面这个示例中使用了 PdfPig 这个 Pdf 处理库,在该示例中, 使用 PdfPig 来提取 Pdf 文档中的文字内容. 与 Word 不同, Pdf 用于排版输出, 导致我们看到的段落在 Pdf 内部并不一定是一个段落单位, PdfPig 对这些问题专门进行了处理.
以前使用过 PDFSharp,对 PdfPig 不了解,特地找时间学习了一下。
PdfPig 的 GitHub 地址:https://github.com/UglyToad/PdfPig
PdfPig 的 Wiki 地址: https://github.com/UglyToad/PdfPig/wiki
从说明中可以看到,他是从 PDFBox 移植到 .NET 中的。而 PDFBox 是 apache 旗下著名的基于 Java 技术的开源 PDF 文档处理库。PDFBox 的文档位于:https://pdfbox.apache.org/ ,记得它有一个特殊的功能是可以用来调试 PDF 文档,让我们看到 PDF 文档内部的详细结构,这个功能在学习 PDF 结构的时候,非常方便。
注意:当前它只支持 ASCII 文本
NuGet

PdfPig 可以通过 NuGet 获得: https://www.nuget.org/packages/PdfPig, 当前版本是 0.1.11-alpha-20250626-d1d79
实际上,它还没有正式发布 1.0 版本。当前 (2025/3/8) 发布的是 v0.1.10
使用 dotnet 的 add package 可以将它引用到项目中。
  1. dotnet add package PdfPig --version 0.1.11-alpha-20250626-d1d79
复制代码
快速入门

在 Wiki 中可以找到详细说明。
读取页面中的文本

在这个阶段,最简单的用法就是打开一份文档,逐页阅读文字:
  1. using (PdfDocument document = PdfDocument.Open(@"C:\Documents\document.pdf"))
  2. {
  3.         foreach (Page page in document.GetPages())
  4.         {
  5.                 string pageText = page.Text;
  6.                 foreach (Word word in page.GetWords())
  7.                 {
  8.                         Console.WriteLine(word.Text);
  9.                 }
  10.         }
  11. }
复制代码
其输出示例如下所示:
1.png

图像显示了三个单词“Write something in”,分为 2 个部分,顶部是正常的 PDF 输出,底部是相同的文本,带有 3 个粉红色的单词边界框和蓝绿色的字母边界框
对于顶部显示的 PDF 文本(“Write something in”),将检测到 3 个单词(粉红色),每个单词都包含带有字形边界框的单个字母。
创建 PDF 文档

要创建文档,请使用 PdfDocumentBuilder 类。标准 14 种字体提供了一种快速入门的方法。
在 PDF 之中有 14 种字体可以不嵌入到PDF中而直接支持,也就是说 PDF 阅读器必须支持这十四种字体,如果不支持的话,那么 PDF 中可能显示不出来正确的文字。这十四种字体为:

  • Times-Roman
  • Times-Bold
  • Time-Italic
  • Time-BoldItalic
  • Courier
  • Courier
  • Courier-Bold
  • Courier-Oblique
  • Helvetica®
  • Helvetica®-Bold
  • Helvetica®-Oblique
  • Helvetica®-BoldOblique
  • Symbol
  • ZapfDingbats
见:https://kbpdfstudio.qoppa.com/standard-14-pdf-fonts/
该示例使用的是 Helvetica 字体。
  1. PdfDocumentBuilder builder = new PdfDocumentBuilder();
  2. PdfPageBuilder page = builder.AddPage(PageSize.A4);
  3. // Fonts must be registered with the document builder prior to use to prevent duplication.
  4. // 字体必须在使用前先在文档构建器中注册,以防止重复。
  5. PdfDocumentBuilder.AddedFont font = builder.AddStandard14Font(Standard14Font.Helvetica);
  6. page.AddText("Hello World!", 12, new PdfPoint(25, 700), font);
  7. byte[] documentBytes = builder.Build();
  8. File.WriteAllBytes(@"C:\git\newPdf.pdf", documentBytes);
复制代码
输出是一个 1 页的PDF文档,文档顶部附近用 Helvetica 字体显示文本“Hello World!”
2.png

图像显示了在 Google Chrome 的PDF查看器中的 PDF 文档。可以看到文本 “Hello World!”,每种字体必须在使用之前向 PdfDocumentBuilder 注册,以便使页面能够共享字体资源。仅支持标准 14 种字体和TrueType字体(.ttf)。高级文档提取在这个例子中,执行了更高级的文档提取。使用PdfDocumentBuilder创建一个带有调试信息(边界框和阅读顺序)添加的 pdf 副本。
页面分割器 (Page Segmenter) 负责在页面中查找文本块。它们返回一个可视为段落的 TextBlock 列表。每个 TextBlock 包含属于它的行 (TextLine) 列表。反过来,每个 TextLine 包含属于它的 Words 列表。每个元素都有自己的边界框和文本。
高级示例
  1. //using UglyToad.PdfPig;
  2. //using UglyToad.PdfPig.DocumentLayoutAnalysis.PageSegmenter;
  3. //using UglyToad.PdfPig.DocumentLayoutAnalysis.ReadingOrderDetector;
  4. //using UglyToad.PdfPig.DocumentLayoutAnalysis.WordExtractor;
  5. //using UglyToad.PdfPig.Fonts.Standard14Fonts;
  6. //using UglyToad.PdfPig.Writer;
  7. var sourcePdfPath = "";
  8. var outputPath = "";
  9. var pageNumber = 1;
  10. using (var document = PdfDocument.Open(sourcePdfPath))
  11. {
  12.     var builder = new PdfDocumentBuilder { };
  13.     PdfDocumentBuilder.AddedFont font = builder.AddStandard14Font(Standard14Font.Helvetica);
  14.     var pageBuilder = builder.AddPage(document, pageNumber);
  15.     pageBuilder.SetStrokeColor(0, 255, 0);
  16.     var page = document.GetPage(pageNumber);
  17.     var letters = page.Letters; // no preprocessing
  18.     // 1. Extract words 单词提取器
  19.     var wordExtractor = NearestNeighbourWordExtractor.Instance;
  20.     var words = wordExtractor.GetWords(letters);
  21.     // 2. Segment page 页面分割器, 用来将页面分割为多个文本块
  22.     var pageSegmenter = DocstrumBoundingBoxes.Instance;
  23.     var textBlocks = pageSegmenter.GetBlocks(words);
  24.     // 3. Postprocessing 后处理
  25.     var readingOrder = UnsupervisedReadingOrderDetector.Instance;
  26.     var orderedTextBlocks = readingOrder.Get(textBlocks);
  27.     // 4. Add debug info - Bounding boxes and reading order
  28.    //     添加调试信息 - 为文本块加上边框和顺序编号
  29.     foreach (var block in orderedTextBlocks)
  30.     {
  31.         var bbox = block.BoundingBox;
  32.         pageBuilder.DrawRectangle(bbox.BottomLeft, bbox.Width, bbox.Height);
  33.         pageBuilder.AddText(block.ReadingOrder.ToString(), 8, bbox.TopLeft, font);
  34.     }
  35.     // 5. Write result to a file 将结果写回文件中
  36.     byte[] fileBytes = builder.Build();
  37.     File.WriteAllBytes(outputPath, fileBytes); // save to file 保存文件
  38. }
复制代码
3.png

图像显示由上述代码块创建的PDF文档,其中文字的边界框和阅读顺序被展示。
有关 高级文档分析 的更多信息,请参见文档布局分析。有关更高级工具以分析文档布局,请参见 导出。
使用方式

PdfDocument

PdfDocument 类提供了访问从文件加载或作为字节传递的文档内容的方法。要从文件打开,请使用 PdfDocument.Open 静态方法:
  1. using UglyToad.PdfPig;
  2. using UglyToad.PdfPig.Content;
  3. using (PdfDocument document = PdfDocument.Open(@"C:\my-file.pdf"))
  4. {
  5.         int pageCount = document.NumberOfPages;
  6.         // Page number starts from 1, not 0.
  7.         Page page = document.GetPage(1);
  8.         decimal widthInPoints = page.Width;
  9.         decimal heightInPoints = page.Height;
  10.         string text = page.Text;
  11. }
复制代码
PdfDocument 只能在 using 语句中使用,因为它实现了 IDisposable 接口(除非消费者在其他地方处置它)。加密文档可以通过 PdfPig 打开。要提供所有者或用户密码,请在调用 Open 方法时提供可选的 ParsingOptions,并定义 Password 属性。例如:
  1. using (PdfDocument document
  2.         = PdfDocument.Open(
  3.               @"C:\my-file.pdf",  
  4.               new ParsingOptions { Password = "password here" }))
复制代码
您还可以提供一个密码列表尝试:
  1. using (PdfDocument document = PdfDocument.Open(@"C:\file.pdf", new ParsingOptions
  2. {
  3.         Passwords = new List<string> { "One", "Two" }
  4. }))
复制代码
该文档包含其遵循的PDF规范的版本,通过 document.Version 访问。
  1. decimal version = document.Version;
复制代码
创建 PDF 文档

PdfDocumentBuilder 创建一个没有页面或内容的新文档。对于文本内容,必须先在构建器中注册字体。此库默认支持 Adobe 提供的标准 14 种字体和 TrueType 格式字体。要添加标准 14 字体,请使用:
  1. public AddedFont AddStandard14Font(Standard14Font type)
复制代码
或者,对于 TrueType 字体这样使用:
  1. AddedFont AddTrueTypeFont(IReadOnlyList<byte> fontFileBytes)
复制代码
传递 TrueType 文件 (.ttf) 的字节。您可以使用以下方法检查 TrueType 文件嵌入到 PDF 文档中的适用性:
  1. bool CanUseTrueTypeFont(IReadOnlyList<byte> fontFileBytes, out IReadOnlyList<string> reasons)
复制代码
如果检查失败,则提供了不能使用该字体的原因列表。您应该在使用之前检查 TrueType 字体的许可证,因为压缩字体文件嵌入在结果文档中并与之一起分发。AddedFont 类表示存储在文档构建器上的字体的关键。在向页面添加文本内容时必须提供此键。要向文档添加页面,请使用:
  1. PdfPageBuilder AddPage(PageSize size, bool isPortrait = true)
复制代码
这创建了一个具有指定大小的新 PdfPageBuilder。第一个添加的页面是第 1 页,然后是第 2 页,接着是第 3 页,依此类推。页面构建器支持添加文本、绘制线条和矩形,并在绘制之前测量文本的大小。要绘制线条和矩形,请使用以下方法:
  1. void DrawLine(PdfPoint from, PdfPoint to, decimal lineWidth = 1)
  2. void DrawRectangle(PdfPoint position, decimal width, decimal height, decimal lineWidth = 1)
复制代码
线宽可以变化,默认为 1。矩形是未填充的,目前填充颜色无法更改。要在页面上写入文本,您必须拥有来自 PdfDocumentBuilder 上上述方法的AddedFont 的引用。然后,您可以使用以下方法将文本绘制到页面上:
  1. IReadOnlyList<Letter> AddText(string text, decimal fontSize, PdfPoint position, PdfDocumentBuilder.AddedFont font)
复制代码
位置是绘制文本的基线。目前仅支持 ASCII 文本。您还可以使用以下方法在绘制之前测量文本的结果大小:
  1. IReadOnlyList<Letter> MeasureText(string text, decimal fontSize, PdfPoint position, PdfDocumentBuilder.AddedFont font)
复制代码
这不会改变页面的状态,不像 AddText。支持使用以下方式更改文本、线条和矩形的 RGB 颜色:
  1. void SetStrokeColor(byte r, byte g, byte b)
  2. void SetTextAndFillColor(byte r, byte g, byte b)
复制代码
RGB 值在 0 到 255 之间。该颜色将在调用这些方法后,所有后续操作中保持有效,直到使用以下方式重置:
  1. void ResetColor()
复制代码
这将重置描边、填充和文本绘制的颜色为黑色。
文档信息

PdfDocument 提供访问 PDF 文件中定义的 DocumentInformation 文档元数据。这些信息通常不会提供,因此大多数条目将为空:
  1. PdfDocument document = PdfDocument.Open(fileName);
  2. // The name of the program used to convert this document to PDF.
  3. string producer = document.Information.Producer;
  4. // The title given to the document
  5. string title = document.Information.Title;
  6. // etc...
复制代码
文档结构

该文档现在具有一个 Structure 成员:
  1. UglyToad.PdfPig.Structure structure = document.Structure;
复制代码
这提供了对标记化 PDF 文档内容的访问:
  1. Catalog catalog = structure.Catalog;
  2. DictionaryToken pagesDictionary = catalog.PagesDictionary;
复制代码
页面字典 PageDictionary 是 PDF 文档内部页面树的根。该结构还暴露了一个 GetObject(IndirectReference reference) 方法,允许随机访问 PDF 中的任何对象,只要其标识符编号是已知的。这是一个形式为 69 0 R 的标识符,其中 69 是对象编号,0 是代数。
页面

页面包含以点为单位的页面宽度和高度,以及映射到 PageSize 枚举:
  1. PageSize size = Page.Size;
  2. bool isA4 = size == PageSize.A4;
复制代码
Page 提供对其中文本的访问支持:
  1. string text = page.Text;
复制代码
有一种方法可以访问单词 Word。默认方法使用基本启发式算法。对于高级情况,您还可以实现自己的 IWordExtractor,或使用 NearestNeighbourWordExtractor:
  1. IEnumerable<Word> words = page.GetWords();
复制代码
您还可以访问用于在页面的内容流中绘制图形和内容的原始操作:
  1. IReadOnlyList<IGraphicsStateOperation> operations = page.Operations;
复制代码
请参阅 PDF 规范以了解各个操作符的含义。
还有一个 API 可以检索每个页面的 PDF 图像对象:
  1. IEnumerable<XObjectImage> images = page.GetImages();
复制代码
请阅读关于图像的 wiki。
字符

由于 PDF 内部结构的方式,页面文本可能不是文档中文本的可读表示形式。由于 PDF 是一种展示格式,文本可以以任何顺序绘制,而不一定是阅读顺序。这意味着可能缺少空格或单词可能在文本中处于意想不到的位置。
为了帮助用户解决页面上实际文本的顺序,页面文件提供了对字母列表的访问:
  1. IReadOnlyList<Letter> letters = page.Letters;
复制代码
这些字母包含:

  • 字母的文本:letter.Value。
  • 字母的左下角位置:letter.Location。
  • 字母的宽度:letter.Width。
  • 以未缩放的相对文本单位表示的字体大小(这些大小是PDF内部的,并且不对应于像素、点或其他单位的大小):letter.FontSize。
  • 如果存在的话,渲染字母的字体名称:letter.FontName。
  • 一个矩形,这是完全包含字母/字形可见区域的最小矩形:letter.GlyphRectangle。
  • 基线开始和结束点 StartBaseLine 和 EndBaseLine 表示字母是否旋转。TextDirection 指示这是否是常用旋转或自定义旋转。
字母位置在 PDF 坐标中测量,原点是页面的左下角。因此,较高的Y值意味着更靠近页面的顶部。
注解

使用以下方法检索每一页上的注释:
  1. page.GetAnnotations()
复制代码
此调用未被缓存,并且在使用之前文档不得被丢弃。
书签

文档的书签(大纲)可以在文档级别检索:
  1. bool hasBookmarks = document.TryGetBookmarks(out Bookmarks bookmarks);
复制代码
如果文档未定义任何书签,则将返回 false。
表单

可以使用以下方法检索交互式表单(AcroForms)的表单字段:
  1. bool hasForm = document.TryGetForm(out AcroForm form);
复制代码
如果文档不包含表单,则返回 false。可以使用 AcroForm 的 Fields 属性访问字段。由于表单是在文档级别定义的,因此这将返回文档中所有页面的字段。字段的类型由枚举 AcroFieldType 定义,例如 PushButton、Checkbox、Text 等。
请注意,这些表单是只读的,无法使用 PdfPig 更改或添加值。
超级链接

页面有一个方法来提取超链接(链接类型的注释):
  1. IReadOnlyList<UglyToad.PdfPig.Content.Hyperlink> hyperlinks = page.GetHyperlinks();
复制代码
TrueType 字体

用于处理 PDF 文件中 TrueType 字体的类可以供公开使用。给定一个输入文件:
  1. using UglyToad.PdfPig.Fonts.TrueType;
  2. using UglyToad.PdfPig.Fonts.TrueType.Parser;
  3. byte[] fontBytes = System.IO.File.ReadAllBytes(@"C:\font.ttf");
  4. TrueTypeDataBytes input = new TrueTypeDataBytes(fontBytes);
  5. TrueTypeFont font = TrueTypeFontParser.Parse(input);
复制代码
解析后的字体可以进行检查。
嵌入文件

PDF 文件可能包含完全嵌入在其中的其他文件用于文档注释。可以访问嵌入文件及其字节内容的列表:
  1. if (document.Advanced.TryGetEmbeddedFiles(out IReadOnlyList<EmbeddedFile> files)
  2.     && files.Count > 0)
  3. {
  4.     var firstFile = files[0];
  5.     string name = firstFile.Name;
  6.     IReadOnlyList<byte> bytes = firstFile.Bytes;
  7. }
复制代码
合并 PDF 文件

您可以使用 PdfMerger 类合并两个或更多现有的 PDF 文件:
  1. var resultFileBytes = PdfMerger.Merge(filePath1, filePath2);
  2. File.WriteAllBytes(@"C:\pdfs\outputfilename.pdf", resultFileBytes);
复制代码
API 参考资料

如果您希望生成 doxygen 文档,请运行 doxygen doxygen-docs,然后打开 docs/doxygen/html/index.html。
有关API部分的详细文档,请参见 Wiki。
问题

如果您遇到 bug,请 务必 提交问题。但是为了让我们能够协助您,您必须提供导致该问题的文件。请将其托管在一个公开可用的地方。感谢

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册