iTextSharp C#提取含多对齐单元格的PDF表格文本问题求助
方案一:扩展
LocationTextExtractionStrategy保留单元格间距 默认策略会合并同一Y坐标的文本,忽略单元格间距,导致对齐不同的内容丢失。我们可以自定义策略,记录每个文本块的位置,按行分组后根据X坐标间距插入空格模拟单元格分隔:
public class TableTextExtractionStrategy : LocationTextExtractionStrategy { private List<TextChunk> _textChunks = new List<TextChunk>(); public override void RenderText(TextRenderInfo renderInfo) { base.RenderText(renderInfo); // 保存每个文本块的基线坐标与内容 var baseline = renderInfo.GetBaseline(); _textChunks.Add(new TextChunk(renderInfo.GetText(), baseline.GetStartPoint(), baseline.GetEndPoint())); } public override string GetResultantText() { // 按Y坐标(取整后)分组,模拟行;PDF Y轴向上,降序是从上到下 var rowGroups = _textChunks.GroupBy(c => Math.Round(c.StartPoint.Y, 2)) .OrderByDescending(g => g.Key); StringBuilder result = new StringBuilder(); foreach (var row in rowGroups) { // 同一行内按X坐标从左到右排序 var sortedChunks = row.OrderBy(c => c.StartPoint.X).ToList(); float lastEndX = 0; foreach (var chunk in sortedChunks) { // 文本块间距超过阈值时,插入空格模拟单元格分隔 if (lastEndX > 0 && chunk.StartPoint.X - lastEndX > 5) { // 按间距比例插入空格,可根据实际PDF调整系数 result.Append(new string(' ', (int)(chunk.StartPoint.X - lastEndX) / 2)); } result.Append(chunk.Text); lastEndX = chunk.EndPoint.X; } result.AppendLine(); } return result.ToString(); } // 辅助类存储文本块的位置与内容 private class TextChunk { public string Text { get; } public Vector StartPoint { get; } public Vector EndPoint { get; } public TextChunk(string text, Vector start, Vector end) { Text = text; StartPoint = start; EndPoint = end; } } }
使用时替换原有策略:
text = PDFTextExtractor.GetTextFromPage(reader, i, new TableTextExtractionStrategy());
方案二:检测行边框提取整行文本
如果表格有明确的水平边框线,可以先提取这些线条的Y坐标,划分行区域后逐区域提取文本,确保整行内容完整:
第一步:提取页面水平线条的Y坐标
private static List<float> GetHorizontalLineYCoordinates(PdfReader reader, int pageNum) { List<float> lineYs = new List<float>(); var pageDict = reader.GetPageN(pageNum); var resourcesDict = pageDict.GetAsDict(PdfName.RESOURCES); var xObjectDict = resourcesDict?.GetAsDict(PdfName.XOBJECT); // 处理表单XObject中的线条 if (xObjectDict != null) { foreach (PdfName name in xObjectDict.Keys) { var obj = xObjectDict.Get(name); if (obj.IsIndirect()) { var xObjDict = reader.GetPdfObject(obj.GetIndirectReference()) as PdfDictionary; if (xObjDict?.GetAsName(PdfName.SUBTYPE) == PdfName.FORM) { ExtractLinesFromContent(reader, xObjDict, lineYs); } } } } // 处理页面直接内容中的线条 ExtractLinesFromContent(reader, pageDict, lineYs); // 去重并按Y坐标降序排序(从上到下) return lineYs.Distinct().OrderByDescending(y => Math.Round(y, 2)).ToList(); } private static void ExtractLinesFromContent(PdfReader reader, PdfDictionary dict, List<float> lineYs) { var contentStream = dict.GetAsStream(PdfName.CONTENTS); if (contentStream == null) return; var parser = new PdfContentParser(reader); var tokens = new List<PdfObject>(); parser.Parse(contentStream, tokens); for (int i = 0; i < tokens.Count; i++) { // 检测PDF线条绘制指令(L操作) if (tokens[i] is PdfName && ((PdfName)tokens[i]).Equals(PdfName.L)) { // 线条坐标格式:x1 y1 x2 y2 float y1 = ((PdfNumber)tokens[i - 3]).FloatValue; float y2 = ((PdfNumber)tokens[i - 1]).FloatValue; // 判定为水平线(y1与y2差值小于容差) if (Math.Abs(y1 - y2) < 1) { lineYs.Add(Math.Round(y1, 2)); } } } }
第二步:按行区域提取文本
List<float> lineYs = GetHorizontalLineYCoordinates(reader, i); StringBuilder fullText = new StringBuilder(); // 无检测到线条时,回退到默认策略 if (lineYs.Count == 0) { fullText.Append(PDFTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy())); } else { // 添加上下边界,确保首尾行被覆盖 var pageSize = reader.GetPageSize(i); lineYs.Insert(0, pageSize.Top); lineYs.Add(pageSize.Bottom); for (int j = 0; j < lineYs.Count - 1; j++) { float topY = lineYs[j]; float bottomY = lineYs[j + 1]; // 创建当前行的区域过滤器 var rowRect = new Rectangle(0, bottomY, pageSize.Right, topY); var listener = new FilteredTextRenderListener( new LocationTextExtractionStrategy(), new RegionTextRenderFilter(rowRect) ); PDFTextExtractor.GetTextFromPage(reader, i, listener); var rowText = listener.GetResultantText().Trim(); if (!string.IsNullOrEmpty(rowText)) { fullText.AppendLine(rowText); } } } text = fullText.ToString();
注意事项
- 两个方案中的阈值(间距、线条容差)需要根据目标PDF的实际排版调整;
- 无边框表格优先用方案一,有明确边框的表格用方案二提取更准确。
内容的提问来源于stack exchange,提问作者Jaidev Khatri
相关产品推荐
相关产品推荐

