You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# Itext7:如何借助EditPageContent移除PDF中的短水平线

移除PDF中短水平线的解决方案(基于iText7的EditPageContent工具)

问题背景

需要移除PDF文件中的所有短水平线,已通过LineDetectionListener类成功检测出目标短水平线,现需借助iText7的EditPageContent工具完成移除操作。

检测逻辑回顾

自定义的LineDetectionListener实现了IEventListener接口,通过监听RENDER_PATH事件筛选目标线条:

  • 提取路径的起点和终点,判断是否为水平线(Y坐标相等)
  • 筛选出长度小于20的短水平线,将对应路径存入PathsToClean列表

核心移除实现步骤

使用EditPageContent工具时,需自定义IEditOperation来过滤掉待移除的路径:

  1. 初始化EditPageContent实例,关联待处理的PDF页面
  2. 实现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 };
    }
}

关键说明

  1. 路径匹配精度:由于PDF坐标可能存在微小精度误差,IsPathEqual和IsPointEqual方法使用了容差判断,可根据实际场景调整tolerance值。
  2. 页面处理范围:代码中遍历了PDF的所有页面,若只需处理特定页面,可修改循环范围。
  3. 内容保留:除目标短水平线外,其他所有页面内容(文本、图形等)都会被保留。

内容的提问来源于stack exchange,提问作者zhusp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 08:19:59