C# 实现 PDF 转 Word/Excel:开源库选型与坐标还原实战
2026/9/14 13:16:28 网站建设 项目流程

简介:面向.NET开发者的C# PDF转换示例工程,演示如何借助Spire.PDF组件将PDF文件转换为Word和Excel格式,适用于文档管理系统、批量报表处理或办公自动化场景。资源包共45个文件,压缩包约86.24MB,内容覆盖C#源码工程、Visual Studio解决方案与项目配置、DLL依赖库、XML运行配置、程序调试缓存及文档说明等,各类型文件分工明确,便于定位代码和依赖。工程采用Windows窗体程序结构,已集成第三方PDF处理组件Spire.PDF 8.4.15,包含界面布局、程序入口及包引用管理,编译运行后可直接体验转换效果。读者可以基于此工程掌握PDF解析、文本/表格提取及格式重排的关键思路,也能直接复用其窗体逻辑与转换接口,缩短后续开发周期。目前已有370人学习/下载,适合C#入门到中级开发者作为文档处理类项目的参考模板。

1. 把 PDF 转成 Word、Excel,C# 程序员通常从哪下手

办公室里最常遇到的情况是:系统导出了几百份 PDF 报表,领导却要求改几个字段后回传 Word;或者设备日志是 PDF,财务那边非要 Excel 才能算数。很多 C# 开发第一次接到这个需求,会以为「PDF 解析库 + Word 生成库」拼起来就能跑,实际上手才发现:PDF 只是一个版面描述格式,它记录的是「哪个位置画了什么字符」,而不是「哪里是标题、哪里是表格、这一段属于哪一列」。换句话说,PDF 里没有语义结构,转 Word 和 Excel 时,需要自己从坐标、字体、间距里反推段落与表格。这篇内容面向想用 C# 做办公自动化的开发者,会依次说清库怎么选、文本怎么提、表格怎么还原、Word 与 Excel 怎么写,以及最后怎么验证转换结果是否可交付。

2. 转换前先选库:C# 处理 PDF 与 Word/Excel 的常见组合

2.1 PDF 解析库怎么选:PdfPig 与 iTextSharp 的取舍

C# 圈子里的 PDF 解析库,常见的有三个方向:PdfPig、iTextSharp(iText 7 的 .NET 版本)、Aspose.Pdf。从开源协议和可控性来说,我一般优先推荐 PdfPig,它是 MIT 协议的开源库,API 设计比较现代,能读取页面里的字符、字形和坐标信息,适合做文本提取和坐标分析。iTextSharp 的 AGPL 协议对很多企业不友好,如果公司内部工具不开源,用起来有法律风险;Aspose.Pdf 转换保真度最好,但授权费不低,适合预算充足、对格式要求极高的项目。

库名特点适合场景备注
PdfPig轻量、MIT 协议、可获取字符坐标文本提取、表格还原、数据分析不直接支持 PDF 生成
iTextSharp老牌、功能全面、支持生成和解析需要同时创建 PDF 的场景AGPL 协议需谨慎
Aspose.Pdf转换质量高、API 丰富、可转 Word/Excel格式要求高的商业项目价格高,CPU 内存占用大
PdfiumViewer基于 Google PDFium,可渲染页面为图片扫描件预处理、页面截图结合 OCR 使用

从「既能拿到文本又能拿到坐标」这个角度,PdfPig 的Word对象自带BoundingBox,后面还原表格结构时会非常有用。iTextSharp 在旧版本上虽然也能做到,但坐标提取的 API 没有 PdfPig 直观。

2.2 生成 Word 和 Excel:NPOI、EPPlus 与 ClosedXML 的边界

在 .NET 项目里生成 Word 和 Excel,开源方案有几个:NPOI 同时支持 .docx 和 .xlsx,API 模仿 Java POI,功能全但写法偏重;EPPlus 专攻 Excel,API 清爽,支持透视表、图表、条件格式,5.0 之后引入了许可证上下文,商业使用时需要付费;ClosedXML 也做 Excel,底层基于 OpenXML,上手简单,但对大文件性能一般。如果同一个项目里既要 Word 又要 Excel,我会选 NPOI 做 Word,EPPlus 做 Excel——这样每块都用各自最擅长的库,代码写起来也不别扭。

注意一点,很多 Excel 的「转换」场景其实不要求复杂公式,只需要把表格数据按行列写进去。所以选库时先确认需求:如果只是数值和文本,ClosedXML 就够了;如果要做数据透视表或图表,EPPlus 更合适;如果还要生成 Word 里的目录、页眉页脚,NPOI 能覆盖。

2.3 最小依赖组合:一个控制台项目解决 C# PDF 转换需求

准备一个最小可跑的 .NET 控制台项目,命令如下:

dotnet new console -n PdfConverter cd PdfConverter dotnet add package UglyToad.PdfPig dotnet add package NPOI dotnet add package EPPlus

说明:dotnet new console创建默认控制台模板,-n指定项目名称;dotnet add package会拉取 NuGet 上的最新稳定版,如果公司内部有私有 NuGet 源,需要自行配置。这里没有指定版本号,因为 API 在主要版本之间变化较大,建议以项目实际引用的版本为准。

3. 用 PdfPig 解析 PDF:拿到文本还要拿到坐标

3.1 从页面提取文本的两种方式:Text 属性与 GetWords()

新建一个控制台应用,把 PDF 文件路径传进来,先尝试读取整页文本:

using UglyToad.PdfPig; using (var document = PdfDocument.Open("input.pdf")) { foreach (var page in document.GetPages()) { Console.WriteLine($"Page {page.Number}"); Console.WriteLine(page.Text); } }

这段代码的关键点是:PdfDocument.Open负责打开 PDF 文件并解析内部对象;GetPages()返回页面集合,page.Text会把当前页所有字符按阅读顺序拼成一个长字符串。这样做的优点是简单,缺点也很明显:换行、分栏、表格线、单元格边界全部丢失,一个三列表格会被拼成一行一段文字。所以当目标格式是 Excel 时,page.Text只能作为粗略预览,不能直接入库。

改用GetWords()可以拿到每个词的坐标信息:

using UglyToad.PdfPig; using (var document = PdfDocument.Open("input.pdf")) { foreach (var page in document.GetPages()) { var words = page.GetWords().ToList(); foreach (var word in words) { Console.WriteLine($"Text: {word.Text}"); Console.WriteLine($"BoundingBox: {word.BoundingBox}"); Console.WriteLine($"BottomLeft: {word.BoundingBox.BottomLeft}"); Console.WriteLine($"TopRight: {word.BoundingBox.TopRight}"); } } }

这里BoundingBox返回的是一个矩形区域,包含四个角点坐标。PdfPig 的坐标系统以页面左下角为原点,X 轴向右、Y 轴向上,单位是 PDF 的点(point),1 点约等于 1/72 英寸。如果 PDF 页面是 A4 纵向,宽度约为 595 点,高度约为 842 点。理解坐标系很重要,因为后面做表格行、列聚类时,要基于这些坐标值计算行间距离和列间距离。

3.2 用坐标恢复表格结构:按 Y 分行的简单算法

拿到坐标后,最常见的需求是把一页里的词重新组织成「行 × 列」的表格结构。核心思路是:同一行里的词,它们的 Y 坐标中心点应该接近;同一列里的词,X 坐标的范围应该重叠。可以按下面步骤写一个简单版本。

public class PdfCell { public string Text { get; set; } public double X { get; set; } public double Y { get; set; } } public static List<List<PdfCell>> GroupWordsIntoRows( List<PdfCell> cells, double yTolerance) { var sorted = cells.OrderByDescending(c => c.Y).ToList(); var rows = new List<List<PdfCell>>(); List<PdfCell> currentRow = null; double currentRowY = 0; foreach (var cell in sorted) { if (currentRow == null || Math.Abs(cell.Y - currentRowY) > yTolerance) { currentRow = new List<PdfCell> { cell }; rows.Add(currentRow); currentRowY = cell.Y; } else { currentRow.Add(cell); } } foreach (var row in rows) { row.Sort((a, b) => a.X.CompareTo(b.X)); } return rows; }

逻辑说明:OrderByDescending(c => c.Y)让 Y 值大(即页面顶部)的词排在前面;yTolerance是行间容差,当两个词的 Y 中心点距离小于这个值时,认为它们属于同一行。行成型后,再按 X 坐标从左到右排序,得到列顺序。参数yTolerance需要根据实际 PDF 的行距调整,一般取 3~5 点;如果表格中有跨行单元格,这个算法会出现多余的空行,后续需要对空行做合并处理。

3.3 处理中文字体与乱码:PdfPig 里字体映射的基本逻辑

PdfPig 的文本提取依赖 PDF 文件里的 ToUnicode 映射表。绝大多数由 Word、WPS、LaTeX 生成的 PDF 都带这个映射,所以中文能正常读出来。但有些 PDF 生成工具为了压缩体积,会省略 ToUnicode,此时 PdfPig 提取出来的是乱码字符,甚至是空白。遇到这种情况,可以先检查 PDF 是否属于扫描件:把页面渲染成图片,看是否是一张静态图。如果是扫描件,需要 OCR;如果不是扫描件但提取乱码,可以尝试更换 PDF 生成源,或者用 iTextSharp 做二次解析。

补充一点:如果 PDF 里使用了嵌入子集字体,字体名称会变成类似ABCDEF+SimSun这种带前缀的形式,这不影响文本提取,只影响后续字体属性的判断。

4. 把解析结果变成 Word、Excel:NPOI 与 EPPlus 的代码骨架

4.1 用 NPOI 生成 Word:段落、标题和表格的最小流程

假设已经从 PdfPig 里拿到了文本和表格行列数据,现在要生成一个 .docx。NPOI 的XWPFDocument代表 Word 文档,CreateParagraph()创建段落,CreateTable()创建表格。示例代码如下:

using NPOI.XWPF.UserModel; var doc = new XWPFDocument(); // 创建标题段落 var titlePara = doc.CreateParagraph(); titlePara.Alignment = ParagraphAlignment.CENTER; var titleRun = titlePara.CreateRun(); titleRun.SetText("季度报表"); titleRun.FontFamily = "微软雅黑"; titleRun.FontSize = 16; titleRun.SetBold(true); // 创建表格 var table = doc.CreateTable(3, 3); table.GetRow(0).GetCell(0).SetText("部门"); table.GetRow(0).GetCell(1).SetText("收入"); table.GetRow(0).GetCell(2).SetText("支出"); table.GetRow(1).GetCell(0).SetText("研发部"); table.GetRow(1).GetCell(1).SetText("120000"); table.GetRow(1).GetCell(2).SetText("80000"); using (var fs = File.Create("output.docx")) { doc.Write(fs); }

说明:CreateRun()创建文本片段,SetText()赋值,FontFamily在中文环境下必须设置,否则 Word 可能用默认字体显示中文,出现乱码或字体不一致;CreateTable(rows, cols)会生成一个带边框的表格,GetRowGetCell用来定位单元格,SetText直接写入内容。如果 PDF 里提取到了单元格的宽度信息,可以通过table.GetRow(0).GetCell(0).SetWidth("2000")设置宽度,单位是 twip(1/20 点)。

4.2 用 EPPlus 生成 Excel:单元格赋值与样式设置

Excel 部分用 EPPlus 写起来更顺手。下面的代码演示如何创建工作簿、写入表头、填充数据并保存:

using OfficeOpenXml; ExcelPackage.LicenseContext = LicenseContext.NonCommercial; using (var pkg = new ExcelPackage()) { var ws = pkg.Workbook.Worksheets.Add("Sheet1"); // 写表头 ws.Cells[1, 1].Value = "部门"; ws.Cells[1, 2].Value = "收入"; ws.Cells[1, 3].Value = "支出"; // 表头加粗 using (var range = ws.Cells["A1:C1"]) { range.Style.Font.Bold = true; range.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid; range.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGray); } // 填充数据 var data = new List<object[]> { new object[] { "研发部", 120000, 80000 }, new object[] { "市场部", 200000, 150000 } }; for (int i = 0; i < data.Count; i++) { ws.Cells[i + 2, 1].Value = data[i][0]; ws.Cells[i + 2, 2].Value = data[i][1]; ws.Cells[i + 2, 3].Value = data[i][2]; } ws.Cells.AutoFitColumns(); pkg.SaveAs(new FileInfo("output.xlsx")); }

ExcelPackage.LicenseContext = LicenseContext.NonCommercial在 EPPlus 5.0 以上是必须写的,否则运行时会抛异常;Cells[row, col]从 1 开始计数;AutoFitColumns()会根据内容长度自动调整列宽,但如果内容里有中英文混排,自动列宽偶尔会偏窄,可以手动指定ws.Column(1).Width = 20。如果 PDF 表格有合并单元格需求,使用ws.Cells["A1:B2"].Merge = true

4.3 样式与格式之间如何取舍:什么时候直接复制 PDF 版式,什么时候放弃

PDF 转 Word,最理想的结果是每一段文字、每一个表格边框、每一种字体大小都原样复现。但现实是:PDF 里的空心文字、艺术字、特殊排版、跨页表格,用开源方案很难完美还原。我的做法是:文字段落尽量保留标题层级和字体;表格保留行列数据,但不强制要求单元格合并逻辑 100% 一致;图片单独提取后按原位置重新插入,位置用坐标换算。如果客户说「必须一模一样」,那通常要考虑 Aspose 这类商业库,或者把 PDF 渲染成图片直接嵌入 Word,而不是做文本转换。

5. 落地技巧:命令批处理、日志和逐个文件的验证方法

5.1 写一个可批量的转换脚本:通配符与输出目录

实际项目里很少只转一个文件。控制台程序的Main函数接收参数,用输入目录作为第一个参数,输出目录作为第二个参数,然后遍历所有 PDF 文件:

static void Main(string[] args) { if (args.Length < 2) { Console.WriteLine("用法: PdfConverter <输入目录> <输出目录>"); return; } var inputDir = args[0]; var outputDir = args[1]; Directory.CreateDirectory(outputDir); var files = Directory.GetFiles(inputDir, "*.pdf"); foreach (var file in files) { try { ConvertPdf(file, outputDir); Console.WriteLine($"[OK] {Path.GetFileName(file)}"); } catch (Exception ex) { Console.WriteLine($"[FAIL] {Path.GetFileName(file)}: {ex.Message}"); } } }

这段代码强调了批处理中的两条原则:单个文件失败不能中断整个任务,所以要 catch 异常并记日志;输出目录最好按日期或批次创建子目录,避免同名文件互相覆盖。

5.2 转换质量如何验证:看文本顺序、表格行数、字符统计

转换完成后,手动打开几个样本文件检查是一种方法,但数量多了就不现实。我一般会在程序里嵌入一个简单的验证步骤:用 PdfPig 读取原文的字符总数,再读取 Word/Excel 里的文本总量,计算差值。如果两者差异超过 10%,说明有内容丢失;如果 Excel 的行数和原 PDF 表格行数不一致,说明行列聚类参数有问题。还可以用正则去匹配关键字段,比如合同编号、日期、金额,确认这些字段在转换结果里出现的位置。

验证项方法通过标准
文本完整性对比原文与转换后字符总数差值小于 10%
表格行数按坐标聚类行数与 Excel 行数对比完全一致
关键字段正则匹配合同编号、日期等至少出现一次
图片数量统计 PDF 内嵌图片与 Word 内嵌图片数量一致

5.3 遇到乱码与歪斜文本时的三招:改容差、换库、转图片

乱码的常见原因有三个:PDF 没有 ToUnicode 映射、字体子集损坏、文本被转成曲线。第一个原因可以尝试用PdfDocument.Open之后的document.GetLetters()手动查看字符编码,如果能拿到 Unicode 码点但显示乱码,说明控制台编码问题,改用文件输出就能解决;第二个原因只能换原件;第三个原因的本质是文字已经变成矢量图形,PdfPig 无能为力,需要将页面渲染成图片再走 OCR。歪斜文本在大批量扫描件里很常见,解决方法是调大yTolerance,或者根据BoundingBox的旋转角度做坐标矫正。角度信息可以从word.BoundingBox.BottomLeftBottomRight的 Y 值差异计算,差异为 0 说明文本水平。

处理扫描件 PDF 的一个常见路线是:先用 PdfiumViewer 把页面转成 PNG,再接入 Tesseract OCR 引擎,用简体中文语言包识别,最后把识别文本按原坐标写入 Excel。这样虽然不能保证 100% 准确,但对无法提供电子版原件的场景,已经是成本最低的方案。最后再提醒一句:任何转换方案上线前,必须拿三类样本测试标准文档、扫描档、带复杂表格的 PDF,不要拿单一文件跑通就交付。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询