C#使用PdfPig等免费库逐行提取PDF文本的问题求解
C# 免费方案:基于PdfPig实现PDF文本逐行拆分
原代码失效原因
- 直接按
BoundingBox.Bottom精确值分组无法覆盖所有行:PDF排版中同一行的文字会受字体、字号、上下标、对齐偏移影响,Y轴坐标存在小幅浮动,精确匹配必然出现漏判、错判。 - 直接提取
\r\n拆分的思路不成立:PDF是排版格式而非流式文档格式,不存在内置的硬换行标记,所有换行都需要通过文字坐标计算得到,GetWords()本身不会返回换行符。
修复方案
核心逻辑是引入行高容差做Y坐标聚类,而非精确匹配坐标值:
- 单页内先计算所有文字的平均高度,取平均字高的20%~40%作为同一条行的判定容差
- 按Y坐标从页面顶部到底部遍历所有单词,Y轴差值在容差范围内的单词判定为同一行
- 同一行内的单词按X坐标从左到右排序后拼接,避免文字顺序错乱
- 所有页面处理完成后统一写入纯文本文件
完整实现代码
using UglyToad.PdfPig; using UglyToad.PdfPig.Content; using System.Collections.Generic; using System.IO; using System.Linq; using System; // 路径配置 const string pdfInputPath = @"fileName.pdf"; const string txtOutputPath = @"extracted_text.txt"; // 行容差系数:数值越大越容易把相邻行合并,数值越小越容易把同一行拆分,常规文档取0.3即可 const double lineToleranceRatio = 0.3; List<string> allExtractedLines = new List<string>(); using (PdfDocument document = PdfDocument.Open(pdfInputPath)) { foreach (Page page in document.GetPages()) { IReadOnlyList<Word> pageWords = page.GetWords(); if (pageWords.Count == 0) { allExtractedLines.Add(string.Empty); continue; } // 动态计算当前页的行判定容差 double avgWordHeight = pageWords.Average(w => w.BoundingBox.Height); double allowedYDeviation = avgWordHeight * lineToleranceRatio; // 第一步:按Y坐标聚类拆分出行 List<List<Word>> lineClusters = new List<List<Word>>(); // PDF坐标原点在页面左下角,Y值越大位置越靠上,因此按Y降序遍历 List<Word> wordsSortedByY = pageWords.OrderByDescending(w => w.BoundingBox.Bottom).ToList(); List<Word> currentLineWords = new List<Word> { wordsSortedByY[0] }; double currentLineBaseY = wordsSortedByY[0].BoundingBox.Bottom; for (int i = 1; i < wordsSortedByY.Count; i++) { Word currentWord = wordsSortedByY[i]; double yDifference = Math.Abs(currentWord.BoundingBox.Bottom - currentLineBaseY); if (yDifference <= allowedYDeviation) { currentLineWords.Add(currentWord); } else { lineClusters.Add(currentLineWords); currentLineWords = new List<Word> { currentWord }; currentLineBaseY = currentWord.BoundingBox.Bottom; } } lineClusters.Add(currentLineWords); // 第二步:行内按X坐标从左到右排序,拼接为文本 foreach (List<Word> line in lineClusters) { string lineContent = string.Join(" ", line .OrderBy(w => w.BoundingBox.Left) .Select(w => w.Text)); allExtractedLines.Add(lineContent.Trim()); } } } // 写入纯文本文件 File.WriteAllLines(txtOutputPath, allExtractedLines);
调参说明
如果提取结果存在行拆分错误,可微调
lineToleranceRatio参数:
- 出现同一行被拆为多条:适当调大参数,最高不超过0.5
- 出现上下相邻行被合并为一条:适当调小参数,最低不小于0.1
该方案仅适用于可编辑文本PDF,扫描版图片PDF需要先接入OCR能力再做行拆分。
内容的提问来源于stack exchange,提问作者Ben.S
相关产品推荐
相关产品推荐

