How to enable users to highlight text in PDFControls.NET

UI
1/10/2011

Downloads

This article decribes how PDFControls.NET can be used to enable users to highlight text in a PDF document. The sample code below allows users to first select text on a page via the standard text selection features of PDFControls.NET. They can then highlight the selected text via a context menu. These two steps can be repeated to selected multiple text areas. These highlights can be persisted simply by saving the document.

1 Introduction

The techniques that are described here are used in the HighlightText sample that it attached to this article. It can also be found in the professional edition of PDFControls.NET. See the "folder Professional Edition\Code Samples - GUI\CS" of the distribution.

2 Text selection.

As a first step, please have your application use the StandardPagesViewer class and turn on text selection. This can be done as follows.

1 pagesViewer.CursorMode = CursorMode.SelectText;

3 Create a context menu.

After a user has selected some text, we want them to be able to right-click on the page and highlight this text via a context menu item. We first add a layer to each page:

1 pagesViewer.PageInteractorCreated += 2 new EventHandler<TallComponents.Interaction.WinForms.Events.InteractorEventArgs> 3 (pagesViewer_PageInteractorCreated); 4 5 ... 6 7 void pagesViewer_PageInteractorCreated( 8 object sender, TallComponents.Interaction.WinForms.Events.InteractorEventArgs e) 9 { 10 PageInteractor pageInteractor = e.Element as PageInteractor; 11 if (pageInteractor != null) 12 { 13 pageInteractor.Layers.Add(new MyPageLayer(pageInteractor.Page)); 14 } 15 } 16 17 ... 18 19 public class MyPageLayer : WinFormsInteractorLayer 20 { 21 public MyPageLayer(Page page) 22 { 23 _page = page; 24 } 25 26 ... 27 28 Page _page; 29 System.Windows.Forms.ContextMenu _contextMenu = new System.Windows.Forms.ContextMenu(); 30 }

Within the layer we override OnMouseDown, so that it shows a context menu when clicked:

1 protected override void OnMouseDown(MouseEventArgs args) 2 { 3 if (args.Button == System.Windows.Forms.MouseButtons.Right) 4 { 5 _contextMenu.MenuItems.Clear(); 6 7 PagesViewer pagesViewer = Interactor.Viewer as PagesViewer; 8 if (pagesViewer != null) 9 { 10 GlyphCollection glyphs = pagesViewer.Selection.Glyphs; 11 if (glyphs != null && glyphs.Count > 0) 12 { 13 // Do not call base: we do not want more handling than this. 14 15 System.Windows.Forms.MenuItem highlightItem = new System.Windows.Forms.MenuItem( 16 "Highlight selected text", new EventHandler(highlightHandler)); 17 _contextMenu.MenuItems.Add(highlightItem); 18 } 19 Point viewerLocation = new Point(new Point(args.X, args.Y), Interactor.ViewerTransform); 20 _contextMenu.Show(pagesViewer, 21 new System.Drawing.Point((int)viewerLocation.X, (int)viewerLocation.Y)); 22 } 23 } 24 else 25 { 26 base.OnMouseDown(args); 27 } 28 }

The highlightTextHandler will perform the actual job of adding the highlights.

3 Create highlighted text

The glyphs in a text selection contain a number of page coordinates: BottomLeft, BottomRight, TopLeft, and TopRight. The terms Bottom, Top, Left and Right are relative to the glyphs itself. So, BottomLeft indicates the page coordinate of the bottom-left of the glyph. Please note that if the glyph is rotated, BottomLeft may not contain smaller coordinate values than say, TopRight.

Text markup annotations also have page coordinates. The code below will generate a single markup annotation for any sequence of glyphs that has the same "bottom" y coordinate. Each markup annotation is created via a call to createTextMarkup().

1 private void highlightHandler(object sender, EventArgs args) 2 { 3 PagesViewer pagesViewer = Interactor.Viewer as PagesViewer; 4 GlyphCollection glyphs = pagesViewer.Selection.Glyphs; 5 if (glyphs != null && glyphs.Count > 0) 6 { 7 // we add a text markup annotation for each sequence of glyphs that has the same y position. 8 9 Glyph first = null; 10 Glyph last = null; 11 foreach (Glyph glyph in glyphs) 12 { 13 if (first == null) 14 { 15 // no first yet, this glyph is the first and last character on a line. 16 first = glyph; 17 last = glyph; 18 } 19 else 20 { 21 // Determine the base Y position of this glyph, and the first glyph. 22 double firstY = Math.Min(Math.Min(first.BottomLeft.Y, first.BottomRight.Y), 23 Math.Min(first.TopLeft.Y, first.TopRight.Y)); 24 double currentY = Math.Min(Math.Min(glyph.BottomLeft.Y, glyph.BottomRight.Y), 25 Math.Min(glyph.TopLeft.Y, glyph.TopRight.Y)); 26 27 if (firstY != currentY) 28 { 29 // current character has a different Y, we create a markup annotation 30 // from first to last. 31 32 createTextMarkup(first, last); 33 34 // The current glyph will introduce a new markup annotation. 35 first = glyph; 36 last = glyph; 37 } 38 else 39 { 40 // The current glyph is part of this line. 41 last = glyph; 42 } 43 } 44 } 45 46 // Generate markup for the remaining glyphs. 47 createTextMarkup(first, last); 48 } 49 }

The createTextMarkup method will create a text markup annotation that runs from the first argument glyph to the last. It will simply compute a bounding box for these, and place an annotation at that position on the page.

1 private void createTextMarkup(Glyph first, Glyph last) 2 { 3 if (first != null && last != null) 4 { 5 double left = Math.Min(Math.Min(first.BottomLeft.X, first.BottomRight.X), 6 Math.Min(first.TopLeft.X, first.TopRight.X)); 7 double right = Math.Max(Math.Max(last.BottomLeft.X, last.BottomRight.X), 8 Math.Max(last.TopLeft.X, last.TopRight.X)); 9 double top = Math.Max(Math.Max(last.BottomLeft.Y, last.BottomRight.Y), 10 Math.Max(last.TopLeft.Y, last.TopRight.Y)); 11 double bottom = Math.Min(Math.Min(first.BottomLeft.Y, first.BottomRight.Y), 12 Math.Min(first.TopLeft.Y, first.TopRight.Y)); 13 14 TextMarkup markup = new TextMarkup(left, bottom, right - left, top - bottom); 15 markup.BorderColor = new TallComponents.PDF.Colors.RgbColor(System.Drawing.Color.DarkMagenta); 16 _page.Markups.Add(markup); 17 } 18 }

Saving

The text markup annotations will automatically be saved when the document is written to a file (or stream). Other applications (like Adobe Acrobat) will recognize these annotations as separate elements, so it remains possible to remove or edit them later.