如何在C#中为PDFium添加自定义矩形绘图工具箱项及多区域标注功能?
嘿,这个需求其实是自定义控件+PDF文本提取的组合活儿,我帮你拆解成几个核心模块来实现,一步步来:
整体实现方案
核心思路是:自定义一个支持多区域绘制的Windows控件(可添加到工具箱) + PDF坐标与屏幕坐标的转换 + PDFium的区域文本提取 + 区域管理与排序。
1. 制作自定义工具箱控件(支持多颜色矩形绘制)
因为PDFium没有现成的绘制控件,咱们自己造一个:
步骤1:创建自定义控件
新建一个类继承自Panel,直接在承载PDF显示的容器上扩展绘制逻辑(避免控件层级冲突),核心是处理鼠标事件完成矩形绘制:
public class PdfRegionAnnotator : Panel { // 存储所有标注区域 public List<AnnotatedRegion> Regions { get; set; } = new List<AnnotatedRegion>(); private Rectangle? _currentDrawingRect; private Color _currentDrawColor = Color.Red; private Point _startPoint; // 设置当前绘制颜色的属性 public Color CurrentDrawColor { get => _currentDrawColor; set => _currentDrawColor = value; } public PdfRegionAnnotator() { DoubleBuffered = true; // 避免绘制闪烁 MouseDown += OnMouseDown; MouseMove += OnMouseMove; MouseUp += OnMouseUp; Paint += OnPaint; } private void OnMouseDown(object? sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { _startPoint = e.Location; _currentDrawingRect = new Rectangle(_startPoint, new Size(0, 0)); } } private void OnMouseMove(object? sender, MouseEventArgs e) { if (_currentDrawingRect.HasValue && e.Button == MouseButtons.Left) { // 更新当前绘制的矩形大小 int x = Math.Min(_startPoint.X, e.X); int y = Math.Min(_startPoint.Y, e.Y); int width = Math.Abs(e.X - _startPoint.X); int height = Math.Abs(e.Y - _startPoint.Y); _currentDrawingRect = new Rectangle(x, y, width, height); Invalidate(); // 触发重绘 } } private void OnMouseUp(object? sender, MouseEventArgs e) { if (_currentDrawingRect.HasValue && _currentDrawingRect.Value.Width > 10 && _currentDrawingRect.Value.Height > 10) { // 添加到区域列表,默认排序号为当前列表长度+1 Regions.Add(new AnnotatedRegion { ScreenRect = _currentDrawingRect.Value, DrawColor = _currentDrawColor, Order = Regions.Count + 1 }); } _currentDrawingRect = null; Invalidate(); } private void OnPaint(object? sender, PaintEventArgs e) { // 按排序顺序绘制已保存的区域 foreach (var region in Regions.OrderBy(r => r.Order)) { using var pen = new Pen(region.DrawColor, 2); e.Graphics.DrawRectangle(pen, region.ScreenRect); // 可选:绘制排序序号 e.Graphics.DrawString(region.Order.ToString(), Font, Brushes.Black, region.ScreenRect.Location); } // 绘制正在绘制的临时虚线矩形 if (_currentDrawingRect.HasValue) { using var pen = new Pen(_currentDrawColor, 2) { DashStyle = System.Drawing.Drawing2D.DashStyle.Dash }; e.Graphics.DrawRectangle(pen, _currentDrawingRect.Value); } } // 提供调整区域排序的方法 public void MoveRegionUp(AnnotatedRegion region) { if (region.Order <= 1) return; var prevRegion = Regions.First(r => r.Order == region.Order - 1); (region.Order, prevRegion.Order) = (prevRegion.Order, region.Order); Invalidate(); } public void MoveRegionDown(AnnotatedRegion region) { if (region.Order >= Regions.Count) return; var nextRegion = Regions.First(r => r.Order == region.Order + 1); (region.Order, nextRegion.Order) = (nextRegion.Order, region.Order); Invalidate(); } } // 定义区域实体类,存储关键信息 public class AnnotatedRegion { public Rectangle ScreenRect { get; set; } // 屏幕坐标矩形 public PdfRect PdfRect { get; set; } // 对应PDF页面的坐标 public Color DrawColor { get; set; } public int Order { get; set; } public string ExtractedText { get; set; } = string.Empty; } // 对应PDFium的矩形结构(单位:点) public struct PdfRect { public double Left { get; set; } public double Top { get; set; } public double Right { get; set; } public double Bottom { get; set; } }
步骤2:添加到工具箱
编译项目后,右键Visual Studio工具箱 → 选择项 → 浏览,找到你编译生成的DLL,选中后确认,这个PdfRegionAnnotator控件就会出现在工具箱里,直接拖到你的PDF阅读器窗体上即可。
2. 屏幕坐标转PDF页面坐标
这是关键!PDF的坐标原点在页面左下角,而屏幕坐标原点在左上角,还要考虑缩放和滚动偏移:
// 假设你用的是基于PDFium的PdfViewer控件,获取当前页面信息 public PdfRect ConvertScreenToPdfRect(Rectangle screenRect, PdfViewer pdfViewer) { var currentPage = pdfViewer.CurrentPage; var pageSize = currentPage.Size; // PDF页面实际尺寸(单位:点) var zoomFactor = pdfViewer.ZoomFactor; // 当前缩放比例 var scrollOffset = pdfViewer.ScrollPosition; // 滚动偏移量 // 转换屏幕X坐标到PDFX double pdfX = (screenRect.X + scrollOffset.X) / zoomFactor; // 反转Y轴:PDF页面高度 - 转换后的屏幕Y值 double pdfY = pageSize.Height - (screenRect.Y + scrollOffset.Y) / zoomFactor; double pdfWidth = screenRect.Width / zoomFactor; double pdfHeight = screenRect.Height / zoomFactor; return new PdfRect { Left = pdfX, Top = pdfY - pdfHeight, // 注意PDFium的Top/Bottom定义,测试时可调整 Right = pdfX + pdfWidth, Bottom = pdfY }; }
3. 用PDFium提取区域内文本
调用PDFium的原生API,针对转换后的PDF矩形区域提取文本:
public string ExtractTextFromPdfRect(PdfRect pdfRect, IntPtr pdfPageHandle) { var textBuffer = new StringBuilder(1024); // 调用PDFium的文本提取方法 FPDFText_GetTextByRect(pdfPageHandle, pdfRect.Left, pdfRect.Top, pdfRect.Right, pdfRect.Bottom, textBuffer); return textBuffer.ToString().Trim(); } // PDFium的P/Invoke声明(根据你使用的PDFium版本调整) [DllImport("pdfium.dll", CharSet = CharSet.Unicode)] private static extern void FPDFText_GetTextByRect(IntPtr page, double left, double top, double right, double bottom, StringBuilder text);
4. 区域排序与文本保存
排序功能
给窗体添加“上移”“下移”按钮,绑定事件实现排序调整:
// 假设用ListBox显示所有区域,选中后点击按钮调整顺序 private void btnMoveUp_Click(object sender, EventArgs e) { if (listBoxRegions.SelectedItem is AnnotatedRegion selectedRegion) { pdfRegionAnnotator.MoveRegionUp(selectedRegion); RefreshRegionList(); // 更新ListBox显示 } } private void RefreshRegionList() { listBoxRegions.Items.Clear(); foreach (var region in pdfRegionAnnotator.Regions.OrderBy(r => r.Order)) { listBoxRegions.Items.Add($"区域{region.Order} - {region.DrawColor.Name}"); } }
保存文本到文件
遍历排序后的区域,提取文本并写入文件:
private void btnSaveText_Click(object sender, EventArgs e) { using var saveFileDialog = new SaveFileDialog { Filter = "文本文件|*.txt", Title = "保存区域文本" }; if (saveFileDialog.ShowDialog() == DialogResult.OK) { using var writer = new StreamWriter(saveFileDialog.FileName); foreach (var region in pdfRegionAnnotator.Regions.OrderBy(r => r.Order)) { // 先转换坐标并提取文本 var pdfRect = ConvertScreenToPdfRect(region.ScreenRect, pdfViewer); region.ExtractedText = ExtractTextFromPdfRect(pdfRect, pdfViewer.CurrentPage.Handle); // 写入文件,包含区域信息和文本 writer.WriteLine($"=== 区域{region.Order} ({region.DrawColor.Name}) ==="); writer.WriteLine($"屏幕坐标:{region.ScreenRect}"); writer.WriteLine($"PDF坐标:左{pdfRect.Left:F2}, 上{pdfRect.Top:F2}, 右{pdfRect.Right:F2}, 下{pdfRect.Bottom:F2}"); writer.WriteLine("文本内容:"); writer.WriteLine(region.ExtractedText); writer.WriteLine(); } MessageBox.Show("文本保存成功!"); } }
5. 注意事项
- PDFium部署:确保项目中包含对应平台(x86/x64)的
pdfium.dll,并设置为“复制到输出目录”。 - 坐标验证:测试时可先提取整个页面文本,对比区域提取结果,确保坐标转换正确。
- 重绘处理:当PDF页面切换、缩放、滚动时,调用
pdfRegionAnnotator.Invalidate()重新绘制区域。 - 区域编辑:可添加删除区域功能,直接从
Regions列表移除选中项后重绘。
内容的提问来源于stack exchange,提问作者Bumba
相关产品推荐
相关产品推荐

