C# Itext7:如何借助EditPageContent移除PDF中的短水平线
移除PDF中短水平线的解决方案(基于iText7的EditPageContent工具)
问题背景
需要移除PDF文件中的所有短水平线,已通过LineDetectionListener类成功检测出目标短水平线,现需借助iText7的EditPageContent工具完成移除操作。
检测逻辑回顾
自定义的LineDetectionListener实现了IEventListener接口,通过监听RENDER_PATH事件筛选目标线条:
- 提取路径的起点和终点,判断是否为水平线(Y坐标相等)
- 筛选出长度小于20的短水平线,将对应路径存入
PathsToClean列表
核心移除实现步骤
使用EditPageContent工具时,需自定义IEditOperation来过滤掉待移除的路径:
- 初始化
EditPageContent实例,关联待处理的PDF页面 - 实现
IEditOperation接口,在ProcessContent方法中,对每个PathRenderInfo进行判断:- 若当前路径存在于
PathsToClean列表中,则跳过该路径(不写入新内容) - 若不存在,则保留该路径,继续处理
- 若当前路径存在于
完整代码示例
using iText.Kernel.Pdf; using iText.Kernel.Pdf.Canvas.Parser; using iText.Kernel.Pdf.Canvas.Parser.Data; using iText.Kernel.Pdf.Canvas.Parser.Listener; using iText.Kernel.Pdf.Canvas.Parser.Util; using iText.Kernel.Geom; using System.Collections.Generic; using System.Linq; public class ShortLineRemover { public static void RemoveShortHorizontalLines(string inputPath, string outputPath) { // 第一步:检测待移除的短水平线路径 List<Path> pathsToRemove = new List<Path>(); using (PdfDocument pdfDoc = new PdfDocument(new PdfReader(inputPath))) { LineDetectionListener detectionListener = new LineDetectionListener(); PdfCanvasProcessor processor = new PdfCanvasProcessor(detectionListener); for (int i = 1; i <= pdfDoc.GetNumberOfPages(); i++) { processor.ProcessPageContent(pdfDoc.GetPage(i)); } pathsToRemove = detectionListener.PathsToClean.ToList(); } // 第二步:使用EditPageContent移除目标路径 using (PdfDocument pdfDoc = new PdfDocument(new PdfReader(inputPath), new PdfWriter(outputPath))) { for (int i = 1; i <= pdfDoc.GetNumberOfPages(); i++) { PdfPage page = pdfDoc.GetPage(i); EditPageContent editContent = new EditPageContent(page); editContent.AddEditOperation(new IEditOperation() { public void ProcessContent(IEventData data, EventType type, IContentOperator operator_) { if (type == EventType.RENDER_PATH) { PathRenderInfo pathInfo = (PathRenderInfo)data; Path currentPath = pathInfo.GetPath(); // 判断当前路径是否在待移除列表中(考虑坐标精度误差) bool isTargetPath = pathsToRemove.Any(p => IsPathEqual(p, currentPath)); if (!isTargetPath) { // 保留非目标路径,执行原操作 operator_.Invoke(page.GetLastContentStream(), data); } // 目标路径则跳过,不执行操作 } else { // 非路径内容直接保留 operator_.Invoke(page.GetLastContentStream(), data); } } }); editContent.ApplyChanges(); } } } // 辅助方法:比较两个路径是否相等 private static bool IsPathEqual(Path path1, Path path2) { if (path1.GetSubpaths().Count != path2.GetSubpaths().Count) return false; for (int i = 0; i < path1.GetSubpaths().Count; i++) { Subpath sub1 = path1.GetSubpaths()[i]; Subpath sub2 = path2.GetSubpaths()[i]; if (!IsPointEqual(sub1.GetStartPoint(), sub2.GetStartPoint())) return false; if (sub1.GetSegments().Count != sub2.GetSegments().Count) return false; for (int j = 0; j < sub1.GetSegments().Count; j++) { Point[] points1 = sub1.GetSegments()[j].GetBasePoints(); Point[] points2 = sub2.GetSegments()[j].GetBasePoints(); if (points1.Length != points2.Length) return false; for (int k = 0; k < points1.Length; k++) { if (!IsPointEqual(points1[k], points2[k])) return false; } } } return true; } // 辅助方法:比较两个坐标点是否相等(考虑精度误差) private static bool IsPointEqual(Point p1, Point p2) { const float tolerance = 0.001f; return System.Math.Abs(p1.GetX() - p2.GetX()) < tolerance && System.Math.Abs(p1.GetY() - p2.GetY()) < tolerance; } } // 复用原检测类 public class LineDetectionListener : IEventListener { public List<Path> PathsToClean { get; } = new List<Path>(); public List<Point[]> PointsToClean { get; } = new List<Point[]>(); public void EventOccurred(IEventData data, EventType type) { PathRenderInfo pathRenderInfo = (PathRenderInfo)data; var points = GetLinePoints(pathRenderInfo); if (points != default && points[0].GetY() == points[1].GetY()) { if (points[1].GetX() - points[0].GetX() < 20f) { PathsToClean.Add(pathRenderInfo.GetPath()); PointsToClean.Add(points); } } } private Point[] GetLinePoints(PathRenderInfo pathData) { var subpaths = pathData.GetPath().GetSubpaths(); foreach (var subpath in subpaths) { var startPoint = subpath.GetStartPoint(); var subpathSegments = subpath.GetSegments(); if (subpathSegments.Count == 1) { var destPoint = subpathSegments[0].GetBasePoints()[1]; return new Point[] { startPoint, destPoint }; } } return default; } public ICollection<EventType> GetSupportedEvents() { return new[] { EventType.RENDER_PATH }; } }
关键说明
- 路径匹配精度:由于PDF坐标可能存在微小精度误差,
IsPathEqual和IsPointEqual方法使用了容差判断,可根据实际场景调整tolerance值。 - 页面处理范围:代码中遍历了PDF的所有页面,若只需处理特定页面,可修改循环范围。
- 内容保留:除目标短水平线外,其他所有页面内容(文本、图形等)都会被保留。
内容的提问来源于stack exchange,提问作者zhusp
相关产品推荐
相关产品推荐

