From 71a7f956e83c9256b9e68169a839d90cfe7a5391 Mon Sep 17 00:00:00 2001 From: sschim Date: Fri, 24 Aug 2018 13:42:56 +0200 Subject: [PATCH 1/3] Support text selection Support selecting and copying text using the mouse cursor. PdfRenderer has a new CursorMode property to control whether to use panning or text selection. PdfiumViewer.Demo contains a sample implementation of a right click menu. This is not implemented in PdfViewer / PdfRenderer because of localization, but it's easy to add. PdfFile now caches the PageData for the current page so it doesn't have to reload and destroy the page data on every mouse move. --- PdfiumViewer.Demo/MainForm.Designer.cs | 48 ++- PdfiumViewer.Demo/MainForm.cs | 17 + PdfiumViewer.Demo/MainForm.resx | 3 + PdfiumViewer.Demo/PdfRangeDocument.cs | 20 + PdfiumViewer/IPdfDocument.cs | 33 ++ PdfiumViewer/NativeMethods.Pdfium.cs | 58 +++ PdfiumViewer/PanningZoomingScrollControl.cs | 11 +- PdfiumViewer/PdfDocument.cs | 47 ++ PdfiumViewer/PdfFile.cs | 450 +++++++++++--------- PdfiumViewer/PdfRenderer.cs | 347 ++++++++++++++- PdfiumViewer/PdfViewer.resx | 4 +- PdfiumViewer/PdfViewerCursorMode.cs | 12 + PdfiumViewer/PdfiumViewer.csproj | 1 + 13 files changed, 839 insertions(+), 212 deletions(-) create mode 100644 PdfiumViewer/PdfViewerCursorMode.cs diff --git a/PdfiumViewer.Demo/MainForm.Designer.cs b/PdfiumViewer.Demo/MainForm.Designer.cs index 0c2cff90..7537f774 100644 --- a/PdfiumViewer.Demo/MainForm.Designer.cs +++ b/PdfiumViewer.Demo/MainForm.Designer.cs @@ -32,12 +32,14 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); this.menuStrip1 = new System.Windows.Forms.MenuStrip(); this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.openToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator(); this.printPreviewToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.printMultiplePagesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator(); this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -86,11 +88,14 @@ private void InitializeComponent() this._pageToolStripLabel = new System.Windows.Forms.ToolStripStatusLabel(); this.toolStripStatusLabel2 = new System.Windows.Forms.ToolStripStatusLabel(); this._coordinatesToolStripLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.pdfViewerContextMenu = new System.Windows.Forms.ContextMenuStrip(this.components); + this.copyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.selectAllToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.pdfViewer1 = new PdfiumViewer.PdfViewer(); - this.printMultiplePagesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.menuStrip1.SuspendLayout(); this.toolStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout(); + this.pdfViewerContextMenu.SuspendLayout(); this.SuspendLayout(); // // menuStrip1 @@ -137,6 +142,13 @@ private void InitializeComponent() this.printPreviewToolStripMenuItem.Text = "Print Preview"; this.printPreviewToolStripMenuItem.Click += new System.EventHandler(this.printPreviewToolStripMenuItem_Click); // + // printMultiplePagesToolStripMenuItem + // + this.printMultiplePagesToolStripMenuItem.Name = "printMultiplePagesToolStripMenuItem"; + this.printMultiplePagesToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.printMultiplePagesToolStripMenuItem.Text = "Print Multiple Pages"; + this.printMultiplePagesToolStripMenuItem.Click += new System.EventHandler(this.printMultiplePagesToolStripMenuItem_Click); + // // toolStripMenuItem3 // this.toolStripMenuItem3.Name = "toolStripMenuItem3"; @@ -529,6 +541,29 @@ private void InitializeComponent() this._coordinatesToolStripLabel.Size = new System.Drawing.Size(77, 17); this._coordinatesToolStripLabel.Text = "(coordinates)"; // + // pdfViewerContextMenu + // + this.pdfViewerContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.copyToolStripMenuItem, + this.selectAllToolStripMenuItem}); + this.pdfViewerContextMenu.Name = "pdfViewerContextMenu"; + this.pdfViewerContextMenu.Size = new System.Drawing.Size(123, 48); + this.pdfViewerContextMenu.Opening += new System.ComponentModel.CancelEventHandler(this.pdfViewerContextMenu_Opening); + // + // copyToolStripMenuItem + // + this.copyToolStripMenuItem.Name = "copyToolStripMenuItem"; + this.copyToolStripMenuItem.Size = new System.Drawing.Size(122, 22); + this.copyToolStripMenuItem.Text = "&Copy"; + this.copyToolStripMenuItem.Click += new System.EventHandler(this.copyToolStripMenuItem_Click); + // + // selectAllToolStripMenuItem + // + this.selectAllToolStripMenuItem.Name = "selectAllToolStripMenuItem"; + this.selectAllToolStripMenuItem.Size = new System.Drawing.Size(122, 22); + this.selectAllToolStripMenuItem.Text = "Select &All"; + this.selectAllToolStripMenuItem.Click += new System.EventHandler(this.selectAllToolStripMenuItem_Click); + // // pdfViewer1 // this.pdfViewer1.Dock = System.Windows.Forms.DockStyle.Fill; @@ -537,13 +572,6 @@ private void InitializeComponent() this.pdfViewer1.Size = new System.Drawing.Size(1128, 524); this.pdfViewer1.TabIndex = 0; // - // printMultiplePagesToolStripMenuItem - // - this.printMultiplePagesToolStripMenuItem.Name = "printMultiplePagesToolStripMenuItem"; - this.printMultiplePagesToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.printMultiplePagesToolStripMenuItem.Text = "Print Multiple Pages"; - this.printMultiplePagesToolStripMenuItem.Click += new System.EventHandler(this.printMultiplePagesToolStripMenuItem_Click); - // // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -563,6 +591,7 @@ private void InitializeComponent() this.toolStrip1.PerformLayout(); this.statusStrip1.ResumeLayout(false); this.statusStrip1.PerformLayout(); + this.pdfViewerContextMenu.ResumeLayout(false); this.ResumeLayout(false); this.PerformLayout(); @@ -625,6 +654,9 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem findToolStripMenuItem; private System.Windows.Forms.ToolStripSeparator toolStripMenuItem7; private System.Windows.Forms.ToolStripMenuItem printMultiplePagesToolStripMenuItem; + private System.Windows.Forms.ContextMenuStrip pdfViewerContextMenu; + private System.Windows.Forms.ToolStripMenuItem copyToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem selectAllToolStripMenuItem; } } diff --git a/PdfiumViewer.Demo/MainForm.cs b/PdfiumViewer.Demo/MainForm.cs index e65cddf5..87dd7b70 100644 --- a/PdfiumViewer.Demo/MainForm.cs +++ b/PdfiumViewer.Demo/MainForm.cs @@ -19,6 +19,8 @@ public MainForm() renderToBitmapsToolStripMenuItem.Enabled = false; + pdfViewer1.Renderer.ContextMenuStrip = pdfViewerContextMenu; + pdfViewer1.Renderer.DisplayRectangleChanged += Renderer_DisplayRectangleChanged; pdfViewer1.Renderer.ZoomChanged += Renderer_ZoomChanged; @@ -378,5 +380,20 @@ private void printMultiplePagesToolStripMenuItem_Click(object sender, EventArgs form.ShowDialog(this); } } + + private void copyToolStripMenuItem_Click(object sender, EventArgs e) + { + pdfViewer1.Renderer.CopySelection(); + } + + private void selectAllToolStripMenuItem_Click(object sender, EventArgs e) + { + pdfViewer1.Renderer.SelectAll(); + } + + private void pdfViewerContextMenu_Opening(object sender, CancelEventArgs e) + { + copyToolStripMenuItem.Enabled = pdfViewer1.Renderer.IsTextSelected; + } } } diff --git a/PdfiumViewer.Demo/MainForm.resx b/PdfiumViewer.Demo/MainForm.resx index 98e3b46e..c32d9b38 100644 --- a/PdfiumViewer.Demo/MainForm.resx +++ b/PdfiumViewer.Demo/MainForm.resx @@ -307,4 +307,7 @@ 237, 17 + + 353, 17 + \ No newline at end of file diff --git a/PdfiumViewer.Demo/PdfRangeDocument.cs b/PdfiumViewer.Demo/PdfRangeDocument.cs index c56f4010..3dee0e6f 100644 --- a/PdfiumViewer.Demo/PdfRangeDocument.cs +++ b/PdfiumViewer.Demo/PdfRangeDocument.cs @@ -288,6 +288,26 @@ public Rectangle RectangleFromPdf(int page, RectangleF rect) return _document.RectangleFromPdf(TranslatePage(page), rect); } + public int GetCharacterIndexAtPosition(PdfPoint location, double xTolerance, double yTolerance) + { + return _document.GetCharacterIndexAtPosition(location, xTolerance, yTolerance); + } + + public bool GetWordAtPosition(PdfPoint location, double xTolerance, double yTolerance, out PdfTextSpan span) + { + return _document.GetWordAtPosition(location, xTolerance, yTolerance, out span); + } + + public int CountCharacters(int page) + { + return _document.CountCharacters(page); + } + + public List GetTextRectangles(int page, int startIndex, int count) + { + return _document.GetTextRectangles(page, startIndex, count); + } + private int TranslatePage(int page) { if (page < 0 || page >= PageCount) diff --git a/PdfiumViewer/IPdfDocument.cs b/PdfiumViewer/IPdfDocument.cs index 3baeebe9..92a137c9 100644 --- a/PdfiumViewer/IPdfDocument.cs +++ b/PdfiumViewer/IPdfDocument.cs @@ -252,5 +252,38 @@ public interface IPdfDocument : IDisposable /// The rectangle to convert. /// The converted rectangle. Rectangle RectangleFromPdf(int page, RectangleF rect); + + /// + /// Get the character index at or nearby a specific position. + /// + /// The location to inspect + /// An x-axis tolerance value for character hit detection, in point unit. + /// A y-axis tolerance value for character hit detection, in point unit. + /// The zero-based index of the character at, or nearby the point specified by parameter x and y. If there is no character at or nearby the point, it will return -1. + int GetCharacterIndexAtPosition(PdfPoint location, double xTolerance, double yTolerance); + + /// + /// Get the full word at or nearby a specific position + /// + /// The location to inspect + /// An x-axis tolerance value for character hit detection, in point unit. + /// A y-axis tolerance value for character hit detection, in point unit. + /// The location of the found word, if any + /// A value indicating whether a word was found at the specified location + bool GetWordAtPosition(PdfPoint location, double xTolerance, double yTolerance, out PdfTextSpan span); + + /// + /// Get number of characters in a page. + /// + /// The page to get the character count from + /// Number of characters in the page. Generated characters, like additional space characters, new line characters, are also counted. + int CountCharacters(int page); + + /// + /// Gets the rectangular areas occupied by a segment of text + /// + /// The page to get the rectangles from + /// The rectangular areas occupied by a segment of text + List GetTextRectangles(int page, int startIndex, int count); } } diff --git a/PdfiumViewer/NativeMethods.Pdfium.cs b/PdfiumViewer/NativeMethods.Pdfium.cs index 33c245ae..54aa47ba 100644 --- a/PdfiumViewer/NativeMethods.Pdfium.cs +++ b/PdfiumViewer/NativeMethods.Pdfium.cs @@ -4,6 +4,7 @@ using System.Text; using System.Windows.Forms; using System.IO; +using System.Drawing; #pragma warning disable 1591 @@ -290,6 +291,17 @@ public static void FPDFText_GetCharBox(IntPtr page, int index, out double left, } } + public static int FPDFText_GetCharIndexAtPos(IntPtr page, double x, double y, double xTolerance, double yTolerance) + { + lock (LockString) + { + var idx = Imports.FPDFText_GetCharIndexAtPos(page, x, y, xTolerance, yTolerance); + if (idx == -3) + throw new PdfException((PdfError)Imports.FPDF_GetLastError()); + return idx; + } + } + public static int FPDFText_CountChars(IntPtr page) { lock (LockString) @@ -298,6 +310,40 @@ public static int FPDFText_CountChars(IntPtr page) } } + public static List FPDFText_GetRectangles(IntPtr page, int pageIndex, int startIndex, int characterCount) + { + lock (LockString) + { + // GetRect uses internal state set by CountRects, so we should call them within the same lock + + var count = Imports.FPDFText_CountRects(page, startIndex, characterCount); + var rectangles = new List(count); + + for (int i = 0; i < count; i++) + { + if (!Imports.FPDFText_GetRect(page, i, out var left, out var top, out var right, out var bottom)) + throw new PdfException((PdfError)Imports.FPDF_GetLastError()); + + rectangles.Add(new PdfRectangle(pageIndex, new RectangleF( + (float)left, + (float)top, + (float)(right - left), + (float)(bottom - top) + ))); + } + + return rectangles; + } + } + + public static char FPDFText_GetUnicode(IntPtr page, int index) + { + lock (LockString) + { + return Imports.FPDFText_GetUnicode(page, index); + } + } + public static bool FPDFText_FindNext(IntPtr handle) { lock (LockString) @@ -696,9 +742,21 @@ private static class Imports [DllImport("pdfium.dll")] public static extern void FPDFText_GetCharBox(IntPtr page, int index, out double left, out double right, out double bottom, out double top); + [DllImport("pdfium.dll")] + public static extern int FPDFText_GetCharIndexAtPos(IntPtr page, double x, double y, double xTolerance, double yTolerance); + [DllImport("pdfium.dll")] public static extern int FPDFText_CountChars(IntPtr page); + [DllImport("pdfium.dll")] + public static extern int FPDFText_CountRects(IntPtr page, int startIndex, int count); + + [DllImport("pdfium.dll")] + public static extern bool FPDFText_GetRect(IntPtr page, int index, out double left, out double top, out double right, out double bottom); + + [DllImport("pdfium.dll", CharSet = CharSet.Unicode)] + public static extern char FPDFText_GetUnicode(IntPtr page, int index); + [DllImport("pdfium.dll")] public static extern bool FPDFText_FindNext(IntPtr handle); diff --git a/PdfiumViewer/PanningZoomingScrollControl.cs b/PdfiumViewer/PanningZoomingScrollControl.cs index ce205d5d..a87132b0 100644 --- a/PdfiumViewer/PanningZoomingScrollControl.cs +++ b/PdfiumViewer/PanningZoomingScrollControl.cs @@ -120,6 +120,8 @@ public void ZoomOut() [DefaultValue(MouseWheelMode.PanAndZoom)] public MouseWheelMode MouseWheelMode { get; set; } + protected bool MousePanningEnabled { get; set; } = true; + /// /// Raises the event. /// @@ -226,7 +228,7 @@ protected override bool IsInputKey(Keys keyData) protected override void OnSetCursor(SetCursorEventArgs e) { - if (_canPan && e.HitTest == HitTest.Client) + if (MousePanningEnabled && _canPan && e.HitTest == HitTest.Client) e.Cursor = PanCursor; base.OnSetCursor(e); @@ -243,7 +245,7 @@ protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); - if (e.Button != MouseButtons.Left || !_canPan) + if (!MousePanningEnabled || e.Button != MouseButtons.Left || !_canPan) return; Capture = true; @@ -255,7 +257,7 @@ protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); - if (!Capture) + if (!MousePanningEnabled || !Capture) return; var offset = new Point(e.Location.X - _dragStart.X, e.Location.Y - _dragStart.Y); @@ -267,7 +269,8 @@ protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); - Capture = false; + if (MousePanningEnabled) + Capture = false; } private class WheelFilter : IMessageFilter diff --git a/PdfiumViewer/PdfDocument.cs b/PdfiumViewer/PdfDocument.cs index 9541b49a..2a6412d0 100644 --- a/PdfiumViewer/PdfDocument.cs +++ b/PdfiumViewer/PdfDocument.cs @@ -504,6 +504,53 @@ public Rectangle RectangleFromPdf(int page, RectangleF rect) return _file.RectangleFromPdf(page, rect); } + /// + /// Get the character index at or nearby a specific position. + /// + /// The page to get the character index from + /// X position + /// Y position + /// An x-axis tolerance value for character hit detection, in point unit. + /// A y-axis tolerance value for character hit detection, in point unit. + /// The zero-based index of the character at, or nearby the point specified by parameter x and y. If there is no character at or nearby the point, it will return -1. + public int GetCharacterIndexAtPosition(PdfPoint location, double xTolerance, double yTolerance) + { + return _file.GetCharIndexAtPos(location, xTolerance, yTolerance); + } + + /// + /// Get the full word at or nearby a specific position + /// + /// The location to inspect + /// An x-axis tolerance value for character hit detection, in point unit. + /// A y-axis tolerance value for character hit detection, in point unit. + /// The location of the found word, if any + /// A value indicating whether a word was found at the specified location + public bool GetWordAtPosition(PdfPoint location, double xTolerance, double yTolerance, out PdfTextSpan span) + { + return _file.GetWordAtPosition(location, xTolerance, yTolerance, out span); + } + + /// + /// Get number of characters in a page. + /// + /// The page to get the character count from + /// Number of characters in the page. Generated characters, like additional space characters, new line characters, are also counted. + public int CountCharacters(int page) + { + return _file.CountChars(page); + } + + /// + /// Gets the rectangular areas occupied by a segment of text + /// + /// The page to get the rectangles from + /// The rectangular areas occupied by a segment of text + public List GetTextRectangles(int page, int startIndex, int count) + { + return _file.GetTextRectangles(page, startIndex, count); + } + /// /// Creates a for the PDF document. /// diff --git a/PdfiumViewer/PdfFile.cs b/PdfiumViewer/PdfFile.cs index 298e8198..b3ee79f9 100644 --- a/PdfiumViewer/PdfFile.cs +++ b/PdfiumViewer/PdfFile.cs @@ -21,6 +21,9 @@ internal class PdfFile : IDisposable private readonly int _id; private Stream _stream; + private PageData _currentPageData = null; + private int _currentPageDataPageNumber = -1; + public PdfFile(Stream stream, string password) { if (stream == null) @@ -45,10 +48,7 @@ public bool RenderPDFPageToDC(int pageNumber, IntPtr dc, int dpiX, int dpiY, int if (_disposed) throw new ObjectDisposedException(GetType().Name); - using (var pageData = new PageData(_document, _form, pageNumber)) - { - NativeMethods.FPDF_RenderPage(dc, pageData.Page, boundsOriginX, boundsOriginY, boundsWidth, boundsHeight, 0, flags); - } + NativeMethods.FPDF_RenderPage(dc, GetPageData(pageNumber).Page, boundsOriginX, boundsOriginY, boundsWidth, boundsHeight, 0, flags); return true; } @@ -58,15 +58,14 @@ public bool RenderPDFPageToBitmap(int pageNumber, IntPtr bitmapHandle, int dpiX, if (_disposed) throw new ObjectDisposedException(GetType().Name); - using (var pageData = new PageData(_document, _form, pageNumber)) - { - if (renderFormFill) - flags &= ~NativeMethods.FPDF.ANNOT; + var pageData = GetPageData(pageNumber); - NativeMethods.FPDF_RenderPageBitmap(bitmapHandle, pageData.Page, boundsOriginX, boundsOriginY, boundsWidth, boundsHeight, rotate, flags); + NativeMethods.FPDF_RenderPageBitmap(bitmapHandle, pageData.Page, boundsOriginX, boundsOriginY, boundsWidth, boundsHeight, rotate, flags); - if (renderFormFill) - NativeMethods.FPDF_FFLDraw(_form, bitmapHandle, pageData.Page, boundsOriginX, boundsOriginY, boundsWidth, boundsHeight, rotate, flags); + if (renderFormFill) + { + flags &= ~NativeMethods.FPDF.ANNOT; + NativeMethods.FPDF_FFLDraw(_form, bitmapHandle, pageData.Page, boundsOriginX, boundsOriginY, boundsWidth, boundsHeight, rotate, flags); } return true; @@ -79,40 +78,37 @@ public PdfPageLinks GetPageLinks(int pageNumber, Size pageSize) var links = new List(); - using (var pageData = new PageData(_document, _form, pageNumber)) - { - int link = 0; - IntPtr annotation; + int link = 0; + IntPtr annotation; - while (NativeMethods.FPDFLink_Enumerate(pageData.Page, ref link, out annotation)) - { - var destination = NativeMethods.FPDFLink_GetDest(_document, annotation); - int? target = null; - string uri = null; + while (NativeMethods.FPDFLink_Enumerate(GetPageData(pageNumber).Page, ref link, out annotation)) + { + var destination = NativeMethods.FPDFLink_GetDest(_document, annotation); + int? target = null; + string uri = null; - if (destination != IntPtr.Zero) - target = (int)NativeMethods.FPDFDest_GetPageIndex(_document, destination); + if (destination != IntPtr.Zero) + target = (int)NativeMethods.FPDFDest_GetPageIndex(_document, destination); - var action = NativeMethods.FPDFLink_GetAction(annotation); - if (action != IntPtr.Zero) - { - const uint length = 1024; - var sb = new StringBuilder(1024); - NativeMethods.FPDFAction_GetURIPath(_document, action, sb, length); + var action = NativeMethods.FPDFLink_GetAction(annotation); + if (action != IntPtr.Zero) + { + const uint length = 1024; + var sb = new StringBuilder(1024); + NativeMethods.FPDFAction_GetURIPath(_document, action, sb, length); - uri = sb.ToString(); - } + uri = sb.ToString(); + } - var rect = new NativeMethods.FS_RECTF(); + var rect = new NativeMethods.FS_RECTF(); - if (NativeMethods.FPDFLink_GetAnnotRect(annotation, rect) && (target.HasValue || uri != null)) - { - links.Add(new PdfPageLink( - new RectangleF(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top), - target, - uri - )); - } + if (NativeMethods.FPDFLink_GetAnnotRect(annotation, rect) && (target.HasValue || uri != null)) + { + links.Add(new PdfPageLink( + new RectangleF(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top), + target, + uri + )); } } @@ -241,40 +237,39 @@ public PdfMatches Search(string text, bool matchCase, bool wholeWord, int startP for (int page = startPage; page <= endPage; page++) { - using (var pageData = new PageData(_document, _form, page)) - { - NativeMethods.FPDF_SEARCH_FLAGS flags = 0; - if (matchCase) - flags |= NativeMethods.FPDF_SEARCH_FLAGS.FPDF_MATCHCASE; - if (wholeWord) - flags |= NativeMethods.FPDF_SEARCH_FLAGS.FPDF_MATCHWHOLEWORD; + var pageData = GetPageData(page); - var handle = NativeMethods.FPDFText_FindStart(pageData.TextPage, FPDFEncoding.GetBytes(text), flags, 0); + NativeMethods.FPDF_SEARCH_FLAGS flags = 0; + if (matchCase) + flags |= NativeMethods.FPDF_SEARCH_FLAGS.FPDF_MATCHCASE; + if (wholeWord) + flags |= NativeMethods.FPDF_SEARCH_FLAGS.FPDF_MATCHWHOLEWORD; - try - { - while (NativeMethods.FPDFText_FindNext(handle)) - { - int index = NativeMethods.FPDFText_GetSchResultIndex(handle); - - int matchLength = NativeMethods.FPDFText_GetSchCount(handle); - - var result = new byte[(matchLength + 1) * 2]; - NativeMethods.FPDFText_GetText(pageData.TextPage, index, matchLength, result); - string match = FPDFEncoding.GetString(result, 0, matchLength * 2); - - matches.Add(new PdfMatch( - match, - new PdfTextSpan(page, index, matchLength), - page - )); - } - } - finally + var handle = NativeMethods.FPDFText_FindStart(pageData.TextPage, FPDFEncoding.GetBytes(text), flags, 0); + + try + { + while (NativeMethods.FPDFText_FindNext(handle)) { - NativeMethods.FPDFText_FindClose(handle); + int index = NativeMethods.FPDFText_GetSchResultIndex(handle); + + int matchLength = NativeMethods.FPDFText_GetSchCount(handle); + + var result = new byte[(matchLength + 1) * 2]; + NativeMethods.FPDFText_GetText(pageData.TextPage, index, matchLength, result); + string match = FPDFEncoding.GetString(result, 0, matchLength * 2); + + matches.Add(new PdfMatch( + match, + new PdfTextSpan(page, index, matchLength), + page + )); } } + finally + { + NativeMethods.FPDFText_FindClose(handle); + } } return new PdfMatches(startPage, endPage, matches); @@ -282,130 +277,119 @@ public PdfMatches Search(string text, bool matchCase, bool wholeWord, int startP public IList GetTextBounds(PdfTextSpan textSpan) { - using (var pageData = new PageData(_document, _form, textSpan.Page)) - { - return GetTextBounds(pageData.TextPage, textSpan.Page, textSpan.Offset, textSpan.Length); - } + return GetTextBounds(GetPageData(textSpan.Page).TextPage, textSpan.Page, textSpan.Offset, textSpan.Length); } public Point PointFromPdf(int page, PointF point) { - using (var pageData = new PageData(_document, _form, page)) - { - NativeMethods.FPDF_PageToDevice( - pageData.Page, - 0, - 0, - (int)pageData.Width, - (int)pageData.Height, - 0, - point.X, - point.Y, - out var deviceX, - out var deviceY - ); - - return new Point(deviceX, deviceY); - } + var pageData = GetPageData(page); + NativeMethods.FPDF_PageToDevice( + pageData.Page, + 0, + 0, + (int)pageData.Width, + (int)pageData.Height, + 0, + point.X, + point.Y, + out var deviceX, + out var deviceY + ); + + return new Point(deviceX, deviceY); } public Rectangle RectangleFromPdf(int page, RectangleF rect) { - using (var pageData = new PageData(_document, _form, page)) - { - NativeMethods.FPDF_PageToDevice( - pageData.Page, - 0, - 0, - (int)pageData.Width, - (int)pageData.Height, - 0, - rect.Left, - rect.Top, - out var deviceX1, - out var deviceY1 - ); - - NativeMethods.FPDF_PageToDevice( - pageData.Page, - 0, - 0, - (int)pageData.Width, - (int)pageData.Height, - 0, - rect.Right, - rect.Bottom, - out var deviceX2, - out var deviceY2 - ); - - return new Rectangle( - deviceX1, - deviceY1, - deviceX2 - deviceX1, - deviceY2 - deviceY1 - ); - } + var pageData = GetPageData(page); + NativeMethods.FPDF_PageToDevice( + pageData.Page, + 0, + 0, + (int)pageData.Width, + (int)pageData.Height, + 0, + rect.Left, + rect.Top, + out var deviceX1, + out var deviceY1 + ); + + NativeMethods.FPDF_PageToDevice( + pageData.Page, + 0, + 0, + (int)pageData.Width, + (int)pageData.Height, + 0, + rect.Right, + rect.Bottom, + out var deviceX2, + out var deviceY2 + ); + + return new Rectangle( + deviceX1, + deviceY1, + deviceX2 - deviceX1, + deviceY2 - deviceY1 + ); } public PointF PointToPdf(int page, Point point) { - using (var pageData = new PageData(_document, _form, page)) - { - NativeMethods.FPDF_DeviceToPage( - pageData.Page, - 0, - 0, - (int)pageData.Width, - (int)pageData.Height, - 0, - point.X, - point.Y, - out var deviceX, - out var deviceY - ); - - return new PointF((float)deviceX, (float)deviceY); - } + var pageData = GetPageData(page); + NativeMethods.FPDF_DeviceToPage( + pageData.Page, + 0, + 0, + (int)pageData.Width, + (int)pageData.Height, + 0, + point.X, + point.Y, + out var deviceX, + out var deviceY + ); + + return new PointF((float)deviceX, (float)deviceY); } public RectangleF RectangleToPdf(int page, Rectangle rect) { - using (var pageData = new PageData(_document, _form, page)) - { - NativeMethods.FPDF_DeviceToPage( - pageData.Page, - 0, - 0, - (int)pageData.Width, - (int)pageData.Height, - 0, - rect.Left, - rect.Top, - out var deviceX1, - out var deviceY1 - ); - - NativeMethods.FPDF_DeviceToPage( - pageData.Page, - 0, - 0, - (int)pageData.Width, - (int)pageData.Height, - 0, - rect.Right, - rect.Bottom, - out var deviceX2, - out var deviceY2 - ); - - return new RectangleF( - (float)deviceX1, - (float)deviceY1, - (float)(deviceX2 - deviceX1), - (float)(deviceY2 - deviceY1) - ); - } + var pageData = GetPageData(page); + NativeMethods.FPDF_DeviceToPage( + pageData.Page, + 0, + 0, + (int)pageData.Width, + (int)pageData.Height, + 0, + rect.Left, + rect.Top, + out var deviceX1, + out var deviceY1 + ); + + NativeMethods.FPDF_DeviceToPage( + pageData.Page, + 0, + 0, + (int)pageData.Width, + (int)pageData.Height, + 0, + rect.Right, + rect.Bottom, + out var deviceX2, + out var deviceY2 + ); + + return new RectangleF( + (float)deviceX1, + (float)deviceY1, + (float)(deviceX2 - deviceX1), + (float)(deviceY2 - deviceY1) + ); } private IList GetTextBounds(IntPtr textPage, int page, int index, int matchLength) @@ -475,41 +459,108 @@ out var top public string GetPdfText(int page) { - using (var pageData = new PageData(_document, _form, page)) - { - int length = NativeMethods.FPDFText_CountChars(pageData.TextPage); - return GetPdfText(pageData, new PdfTextSpan(page, 0, length)); - } + var pageData = GetPageData(page); + int length = NativeMethods.FPDFText_CountChars(pageData.TextPage); + return GetPdfText(pageData, new PdfTextSpan(page, 0, length)); } public string GetPdfText(PdfTextSpan textSpan) { - using (var pageData = new PageData(_document, _form, textSpan.Page)) - { - return GetPdfText(pageData, textSpan); - } + return GetPdfText(GetPageData(textSpan.Page), textSpan); } private string GetPdfText(PageData pageData, PdfTextSpan textSpan) { + // NOTE: The count parameter in FPDFText_GetText seems to include the null terminator, even though the documentation does not specify this. + // So to read 40 characters, we need to allocate 82 bytes (2 for the terminator), and request 41 characters from GetText. + // The return value also includes the terminator (which is documented) var result = new byte[(textSpan.Length + 1) * 2]; - NativeMethods.FPDFText_GetText(pageData.TextPage, textSpan.Offset, textSpan.Length, result); - return FPDFEncoding.GetString(result, 0, textSpan.Length * 2); + int count = NativeMethods.FPDFText_GetText(pageData.TextPage, textSpan.Offset, textSpan.Length + 1, result); + if (count <= 0) + return string.Empty; + return FPDFEncoding.GetString(result, 0, (count - 1) * 2); } - public void DeletePage (int pageNumber) + public int GetCharIndexAtPos(PdfPoint location, double xTolerance, double yTolerance) { - NativeMethods.FPDFPage_Delete(_document, pageNumber); + return NativeMethods.FPDFText_GetCharIndexAtPos( + GetPageData(location.Page).TextPage, + location.Location.X, + location.Location.Y, + xTolerance, + yTolerance + ); } - public void RotatePage (int pageNumber, PdfRotation rotation) + public bool GetWordAtPosition(PdfPoint location, double xTolerance, double yTolerance, out PdfTextSpan span) { - using (var pageData = new PageData(_document, _form, pageNumber)) + var index = GetCharIndexAtPos(location, xTolerance, yTolerance); + if (index < 0) + { + span = default(PdfTextSpan); + return false; + } + + var baseCharacter = GetCharacter(location.Page, index); + if (IsWordSeparator(baseCharacter)) + { + span = default(PdfTextSpan); + return false; + } + + int start = index, end = index; + + for (int i = index - 1; i >= 0; i--) + { + var c = GetCharacter(location.Page, i); + if (IsWordSeparator(c)) + break; + start = i; + } + + var count = CountChars(location.Page); + for (int i = index + 1; i < count; i++) + { + var c = GetCharacter(location.Page, i); + if (IsWordSeparator(c)) + break; + end = i; + } + + span = new PdfTextSpan(location.Page, start, end - start); + return true; + + bool IsWordSeparator(char c) { - NativeMethods.FPDFPage_SetRotation(pageData.Page, rotation); + return char.IsSeparator(c) || char.IsPunctuation(c) || char.IsControl(c) || char.IsWhiteSpace(c) || c == '\r' || c == '\n'; } } + public char GetCharacter(int page, int index) + { + return NativeMethods.FPDFText_GetUnicode(GetPageData(page).TextPage, index); + } + + public int CountChars(int page) + { + return NativeMethods.FPDFText_CountChars(GetPageData(page).TextPage); + } + + public List GetTextRectangles(int page, int startIndex, int count) + { + return NativeMethods.FPDFText_GetRectangles(GetPageData(page).TextPage, page, startIndex, count); + } + + public void DeletePage(int pageNumber) + { + NativeMethods.FPDFPage_Delete(_document, pageNumber); + } + + public void RotatePage(int pageNumber, PdfRotation rotation) + { + NativeMethods.FPDFPage_SetRotation(GetPageData(pageNumber).Page, rotation); + } + public PdfInformation GetInformation() { var pdfInfo = new PdfInformation(); @@ -608,6 +659,9 @@ protected virtual void Dispose(bool disposing) { StreamManager.Unregister(_id); + _currentPageData?.Dispose(); + _currentPageData = null; + if (_form != IntPtr.Zero) { NativeMethods.FORM_DoDocumentAAction(_form, NativeMethods.FPDFDOC_AACTION.WC); @@ -634,6 +688,18 @@ protected virtual void Dispose(bool disposing) } } + private PageData GetPageData(int pageNumber) + { + if (_currentPageDataPageNumber != pageNumber) + { + _currentPageData?.Dispose(); + _currentPageData = new PageData(_document, _form, pageNumber); + _currentPageDataPageNumber = pageNumber; + } + + return _currentPageData; + } + private class PageData : IDisposable { private readonly IntPtr _form; diff --git a/PdfiumViewer/PdfRenderer.cs b/PdfiumViewer/PdfRenderer.cs index 47b4ff98..a24a17df 100644 --- a/PdfiumViewer/PdfRenderer.cs +++ b/PdfiumViewer/PdfRenderer.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Diagnostics; using System.Drawing; +using System.Text; using System.Windows.Forms; namespace PdfiumViewer @@ -13,6 +14,7 @@ namespace PdfiumViewer public class PdfRenderer : PanningZoomingScrollControl { private static readonly Padding PageMargin = new Padding(4); + private static readonly SolidBrush _textSelectionBrush = new SolidBrush(Color.FromArgb(90, Color.DodgerBlue)); private int _height; private int _maxWidth; @@ -32,6 +34,10 @@ public class PdfRenderer : PanningZoomingScrollControl private DragState _dragState; private PdfRotation _rotation; private List[] _markers; + private PdfViewerCursorMode _cursorMode = PdfViewerCursorMode.Pan; + private bool _isSelectingText = false; + private MouseState _cachedMouseState = null; + private TextSelectionState _textSelectionState = null; /// /// The associated PDF document. @@ -125,6 +131,20 @@ public PdfViewerZoomMode ZoomMode } } + /// + /// Gets or sets the way the viewer should respond to cursor input + /// + [DefaultValue(PdfViewerCursorMode.Pan)] + public PdfViewerCursorMode CursorMode + { + get { return _cursorMode; } + set + { + _cursorMode = value; + MousePanningEnabled = _cursorMode == PdfViewerCursorMode.Pan; + } + } + /// /// Gets or sets the current rotation of the PDF document. /// @@ -141,6 +161,52 @@ public PdfRotation Rotation } } + public bool IsTextSelected + { + get + { + var state = _textSelectionState?.GetNormalized(); + if (state == null) + return false; + + if (state.EndPage < 0 || state.EndIndex < 0) + return false; + + return true; + } + } + + public string SelectedText + { + get + { + var state = _textSelectionState?.GetNormalized(); + if (state == null) + return null; + + var sb = new StringBuilder(); + for (int page = state.StartPage; page <= state.EndPage; page++) + { + int start = 0, end = 0; + + if (page == state.StartPage) + start = state.StartIndex; + + if (page == state.EndPage) + end = (state.EndIndex + 1); + else + end = Document.CountCharacters(page); + + if (page != state.StartPage) + sb.AppendLine(); + + sb.Append(Document.GetPdfText(new PdfTextSpan(page, start, end - start))); + } + + return sb.ToString(); + } + } + /// /// Gets a collection with all markers. /// @@ -283,7 +349,7 @@ public Rectangle BoundsFromPdf(PdfRectangle bounds) { return BoundsFromPdf(bounds, true); } - + private Rectangle BoundsFromPdf(PdfRectangle bounds, bool translateOffset) { var offset = translateOffset ? GetScrollOffset() : Size.Empty; @@ -384,6 +450,52 @@ protected override void OnZoomChanged(EventArgs e) UpdateScrollbars(); } + /// + /// Determines whether the specified key is a regular input key or a special key that requires preprocessing. + /// + /// + /// true if the specified key is a regular input key; otherwise, false. + /// + /// One of the values. + protected override bool IsInputKey(Keys keyData) + { + switch ((keyData) & Keys.KeyCode) + { + case Keys.A: + if ((keyData & Keys.Modifiers) == Keys.Control) + SelectAll(); + return true; + + case Keys.C: + if ((keyData & Keys.Modifiers) == Keys.Control) + CopySelection(); + return true; + + default: + return base.IsInputKey(keyData); + } + } + + public void SelectAll() + { + _textSelectionState = new TextSelectionState() + { + StartPage = 0, + StartIndex = 0, + EndPage = Document.PageCount - 1, + EndIndex = Document.CountCharacters(Document.PageCount - 1) - 1 + }; + + Invalidate(); + } + + public void CopySelection() + { + var text = SelectedText; + if (text.Length > 0) + Clipboard.SetText(text); + } + /// /// Load a into the control. /// @@ -653,6 +765,10 @@ protected override void OnPaint(PaintEventArgs e) _shadeBorder.Draw(e.Graphics, pageBounds); DrawMarkers(e.Graphics, page); + + var selectionInfo = _textSelectionState; + if (selectionInfo != null) + DrawTextSelection(e.Graphics, page, selectionInfo.GetNormalized()); } } @@ -662,6 +778,37 @@ protected override void OnPaint(PaintEventArgs e) _visiblePageEnd = Document.PageCount - 1; } + private void DrawTextSelection(Graphics graphics, int page, TextSelectionState state) + { + if (state.EndPage < 0 || state.EndIndex < 0) + return; + + if (page >= state.StartPage && page <= state.EndPage) + { + int start = 0, end = 0; + + if (page == state.StartPage) + start = state.StartIndex; + + if (page == state.EndPage) + end = (state.EndIndex + 1); + else + end = Document.CountCharacters(page); + + Region region = null; + foreach (var rectangle in Document.GetTextRectangles(page, start, end - start)) + { + if (region == null) + region = new Region(BoundsFromPdf(rectangle)); + else + region.Union(BoundsFromPdf(rectangle)); + } + + if (region != null) + graphics.FillRegion(_textSelectionBrush, region); + } + } + private void DrawPageImage(Graphics graphics, int page, Rectangle pageBounds) { var pageCache = _pageCache[page]; @@ -731,6 +878,16 @@ protected override void OnSetCursor(SetCursorEventArgs e) } } } + + if (_cursorMode == PdfViewerCursorMode.TextSelection) + { + var state = GetMouseState(e.Location); + if (state.CharacterIndex >= 0) + { + e.Cursor = Cursors.IBeam; + return; + } + } } base.OnSetCursor(e); @@ -742,6 +899,50 @@ protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); + HandleMouseDownForLinks(e); + + if (_cursorMode == PdfViewerCursorMode.TextSelection) + { + HandleMouseDownForTextSelection(e); + } + } + + /// Raises the event. + /// A that contains the event data. + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + + HandleMouseUpForLinks(e); + + if (_cursorMode == PdfViewerCursorMode.TextSelection) + { + HandleMouseUpForTextSelection(e); + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (_cursorMode == PdfViewerCursorMode.TextSelection) + { + HandleMouseMoveForTextSelection(e); + } + } + + protected override void OnMouseDoubleClick(MouseEventArgs e) + { + base.OnMouseDoubleClick(e); + + if (_cursorMode == PdfViewerCursorMode.TextSelection) + { + HandleMouseDoubleClickForTextSelection(e); + } + } + + private void HandleMouseDownForLinks(MouseEventArgs e) + { _dragState = null; if (_cachedLink != null) @@ -754,12 +955,8 @@ protected override void OnMouseDown(MouseEventArgs e) } } - /// Raises the event. - /// A that contains the event data. - protected override void OnMouseUp(MouseEventArgs e) + private void HandleMouseUpForLinks(MouseEventArgs e) { - base.OnMouseUp(e); - if (_dragState == null) return; @@ -803,6 +1000,106 @@ private void HandleLinkClick(LinkClickEventArgs e) } } + private void HandleMouseDownForTextSelection(MouseEventArgs e) + { + if (e.Button != MouseButtons.Left) + return; + + var pdfLocation = PointToPdf(e.Location); + if (!pdfLocation.IsValid) + return; + + var characterIndex = Document.GetCharacterIndexAtPosition(pdfLocation, 4f, 4f); + + if (characterIndex >= 0) + { + _textSelectionState = new TextSelectionState() + { + StartPage = pdfLocation.Page, + StartIndex = characterIndex, + EndPage = -1, + EndIndex = -1 + }; + _isSelectingText = true; + Capture = true; + } + else + { + _isSelectingText = false; + Capture = false; + _textSelectionState = null; + } + } + + private void HandleMouseUpForTextSelection(MouseEventArgs e) + { + if (e.Button != MouseButtons.Left) + return; + + _isSelectingText = false; + Capture = false; + Invalidate(); + } + + private void HandleMouseMoveForTextSelection(MouseEventArgs e) + { + if (!_isSelectingText) + return; + + var mouseState = GetMouseState(e.Location); + + if (mouseState.CharacterIndex >= 0) + { + _textSelectionState.EndPage = mouseState.PdfLocation.Page; + _textSelectionState.EndIndex = mouseState.CharacterIndex; + + Invalidate(); + } + } + + private void HandleMouseDoubleClickForTextSelection(MouseEventArgs e) + { + var pdfLocation = PointToPdf(e.Location); + if (!pdfLocation.IsValid) + return; + + if (Document.GetWordAtPosition(pdfLocation, 4f, 4f, out var word)) + { + _textSelectionState = new TextSelectionState() + { + StartPage = pdfLocation.Page, + EndPage = pdfLocation.Page, + StartIndex = word.Offset, + EndIndex = word.Offset + word.Length + }; + + Invalidate(); + } + } + + private MouseState GetMouseState(Point mouseLocation) + { + // OnMouseMove and OnSetCursor get invoked a lot, often multiple times in succession for the same point. + // By just caching the mouse state for the last known position we can save a lot of work. + + var currentState = _cachedMouseState; + if (currentState?.MouseLocation == mouseLocation) + return currentState; + + _cachedMouseState = new MouseState() + { + MouseLocation = mouseLocation, + PdfLocation = PointToPdf(mouseLocation) + }; + + if (!_cachedMouseState.PdfLocation.IsValid) + return _cachedMouseState; + + _cachedMouseState.CharacterIndex = Document.GetCharacterIndexAtPosition(_cachedMouseState.PdfLocation, 4f, 4f); + + return _cachedMouseState; + } + /// /// Occurs when a link in the pdf document is clicked. /// @@ -1041,5 +1338,43 @@ private class DragState public PdfPageLink Link { get; set; } public Point Location { get; set; } } + + private class MouseState + { + public Point MouseLocation { get; set; } + public PdfPoint PdfLocation { get; set; } + public int CharacterIndex { get; set; } + } + + private class TextSelectionState + { + public int StartPage { get; set; } + public int StartIndex { get; set; } + public int EndPage { get; set; } + public int EndIndex { get; set; } + + public TextSelectionState GetNormalized() + { + if (EndPage < 0 || EndIndex < 0) // Special case when only start position is known + return this; + + if (EndPage < StartPage || + (StartPage == EndPage && EndIndex < StartIndex)) + { + // End position is before start position. + // Swap positions so start is always before end. + + return new TextSelectionState() + { + StartPage = EndPage, + StartIndex = EndIndex, + EndPage = StartPage, + EndIndex = StartIndex + }; + } + + return this; + } + } } } diff --git a/PdfiumViewer/PdfViewer.resx b/PdfiumViewer/PdfViewer.resx index e2a7b666..07595c73 100644 --- a/PdfiumViewer/PdfViewer.resx +++ b/PdfiumViewer/PdfViewer.resx @@ -205,7 +205,7 @@ _bookmarks - PdfiumViewer.NativeTreeView, PdfiumViewer, Version=2.9.0.0, Culture=neutral, PublicKeyToken=91e4789cfb0609e0 + PdfiumViewer.NativeTreeView, PdfiumViewer, Version=2.13.0.0, Culture=neutral, PublicKeyToken=91e4789cfb0609e0 _container.Panel1 @@ -244,7 +244,7 @@ _renderer - PdfiumViewer.PdfRenderer, PdfiumViewer, Version=2.9.0.0, Culture=neutral, PublicKeyToken=91e4789cfb0609e0 + PdfiumViewer.PdfRenderer, PdfiumViewer, Version=2.13.0.0, Culture=neutral, PublicKeyToken=91e4789cfb0609e0 _container.Panel2 diff --git a/PdfiumViewer/PdfViewerCursorMode.cs b/PdfiumViewer/PdfViewerCursorMode.cs new file mode 100644 index 00000000..01d55b13 --- /dev/null +++ b/PdfiumViewer/PdfViewerCursorMode.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace PdfiumViewer +{ + public enum PdfViewerCursorMode + { + Pan, + TextSelection + } +} diff --git a/PdfiumViewer/PdfiumViewer.csproj b/PdfiumViewer/PdfiumViewer.csproj index 24c8e9c9..83e95046 100644 --- a/PdfiumViewer/PdfiumViewer.csproj +++ b/PdfiumViewer/PdfiumViewer.csproj @@ -84,6 +84,7 @@ + From 92e12593da02362c384ead8354ede173407a1dec Mon Sep 17 00:00:00 2001 From: sschim Date: Fri, 31 Aug 2018 12:02:43 +0200 Subject: [PATCH 2/3] Fix control focus, clear selection state when (re)loading a document Mark the control as Selectable / UserMouse so it will take focus when clicked, and clear the selection state when (re)loading a document. --- PdfiumViewer/CustomScrollControl.cs | 3 ++- PdfiumViewer/PdfRenderer.cs | 31 ++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/PdfiumViewer/CustomScrollControl.cs b/PdfiumViewer/CustomScrollControl.cs index b8f167e9..7abd6e02 100644 --- a/PdfiumViewer/CustomScrollControl.cs +++ b/PdfiumViewer/CustomScrollControl.cs @@ -94,7 +94,8 @@ public override Rectangle DisplayRectangle public CustomScrollControl() { - SetStyle(ControlStyles.ContainerControl, true); + SetStyle(ControlStyles.Selectable, true); + SetStyle(ControlStyles.UserMouse, true); SetStyle(ControlStyles.AllPaintingInWmPaint, false); _horizontalScroll = new ScrollProperties(this, NativeMethods.SB_HORZ); diff --git a/PdfiumViewer/PdfRenderer.cs b/PdfiumViewer/PdfRenderer.cs index a24a17df..b4868df0 100644 --- a/PdfiumViewer/PdfRenderer.cs +++ b/PdfiumViewer/PdfRenderer.cs @@ -161,6 +161,9 @@ public PdfRotation Rotation } } + /// + /// Indicates whether the user currently has text selected + /// public bool IsTextSelected { get @@ -176,6 +179,9 @@ public bool IsTextSelected } } + /// + /// Gets the currently selected text + /// public string SelectedText { get @@ -450,30 +456,22 @@ protected override void OnZoomChanged(EventArgs e) UpdateScrollbars(); } - /// - /// Determines whether the specified key is a regular input key or a special key that requires preprocessing. - /// - /// - /// true if the specified key is a regular input key; otherwise, false. - /// - /// One of the values. - protected override bool IsInputKey(Keys keyData) + protected override void OnKeyDown(KeyEventArgs e) { - switch ((keyData) & Keys.KeyCode) + switch ((e.KeyData) & Keys.KeyCode) { case Keys.A: - if ((keyData & Keys.Modifiers) == Keys.Control) + if ((e.KeyData & Keys.Modifiers) == Keys.Control) SelectAll(); - return true; + break; case Keys.C: - if ((keyData & Keys.Modifiers) == Keys.Control) + if ((e.KeyData & Keys.Modifiers) == Keys.Control) CopySelection(); - return true; - - default: - return base.IsInputKey(keyData); + break; } + + base.OnKeyDown(e); } public void SelectAll() @@ -519,6 +517,7 @@ private void ReloadDocument() _height = 0; _maxWidth = 0; _maxHeight = 0; + _textSelectionState = null; foreach (var size in Document.PageSizes) { From dea22797933a9574c11ddbaffdceae161014e717 Mon Sep 17 00:00:00 2001 From: sschim Date: Fri, 31 Aug 2018 12:05:15 +0200 Subject: [PATCH 3/3] Fix indentation on previous commit --- PdfiumViewer/PdfRenderer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PdfiumViewer/PdfRenderer.cs b/PdfiumViewer/PdfRenderer.cs index b4868df0..26052a0f 100644 --- a/PdfiumViewer/PdfRenderer.cs +++ b/PdfiumViewer/PdfRenderer.cs @@ -517,7 +517,7 @@ private void ReloadDocument() _height = 0; _maxWidth = 0; _maxHeight = 0; - _textSelectionState = null; + _textSelectionState = null; foreach (var size in Document.PageSizes) {