Introduction

This developer guide will help you understand PDFControls.NET 2.0 for Winforms. It walks through the main concepts and illustrates them with code samples.

Viewing

In essence, PDFControls.NET provides a number of WinForms Viewer Controls that allow one to View PDF documents. The professional edition adds the ability to manipulate and save PDF documents. The class hierarchy for the viewer controls is as follows:

 

Viewer class hierarchy

These classes provide the following functionality:

  • DocumentViewer: this is the base class for all viewers that show PDF documents. Next to viewing, it offers basic navigation functionality such as zooming and panning.
  • ThumbnailsViewer: this is a special document viewer that shows all pages as thumbnails. If its PagesViewer property is set, it will mark the pages that are visible in the PagesViewer by means of red rectangle. In addition, clicking on a thumbnail will have the pages viewer navigate to that page.
  • PagesViewer: this is a special document viewer that adds the notion of a “current” page. In contrast to the StandardPagesViewer (see below), the PagesViewer can be used in the professional edition as a basis for a highly customized viewer. As a result, the PagesViewer is often of interest in the professional edition only.
  • StandardPagesViewer: this is special pages viewer that adds a number of convenient common features. These features resemble (and extend) the features of the version 1 PagesViewer. This makes it possible to easily obtain a very powerful viewer. In general however, it is more difficult to customize this viewer, as new features may conflict with existing ones. The latter however is only relevant for the professional edition.

Next to these classes there is also a BookMarksViewer class. This control does not show pages, but it shows the bookmarks that are defined in a PDF document.

Pages Viewer

A simple PDF document viewer can be built by including a PagesViewer control in your GUI application, connecting its Document property to a Document instance, and writing a few lines of code to open PDF files. See the SimpleViewer sample:

usingSystem;
usingSystem.ComponentModel;

using TallComponents.Interaction.WinForms;

namespaceWindowsApplication1
{
   publicpartialclassForm1 : System.Windows.Forms.Form
   {
      publicForm1()
      {
         InitializeComponent();
      }

      privatevoidopenToolStripMenuItem_Click(object sender, EventArgs e)
      {
         System.Windows.Forms.OpenFileDialog dialog = newSystem.Windows.Forms.OpenFileDialog();

         if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
         {
            document1.Open(dialog.FileName);
         }
      }
   }
}
Code sample: A simple viewer.

The code above is all that is needed to obtain a viewer that:

  • Show all pages of a PDF document
  • Allows scrolling via scroll bars
  • Allows panning by dragging the mouse
  • Supports page navigation by clicking on links
  • Allows Filling out form fields (professional only)

Save changes incrementally as an update

Saving

The standard edition does not allow changing or saving of PDF documents. In order to allow the professional version to save documents (including completed forms) one may add the following code. Also see the SimpleViewer sample of the professional edition.

private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
{
if (document1.HasDocument)
{
System.Windows.Forms.SaveFileDialog dialog = new
System.Windows.Forms.SaveFileDialog();
dialog.Filter = "PDF documents|*.pdf";
dialog.FileName = "test.pdf";

if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
using (FileStream file = new FileStream(dialog.FileName,
FileMode.OpenOrCreate, FileAccess.Write))
{
document1.Write(file);
}
}
}
}

Saving a modified document.

StandardPagesViewer

The StandardPagesViewer offers a viewer that derives from the PagesViewer class. It can be used in exactly the same way as the PagesViewer, but it offers a number of common extras:

  • A CursorMode. This mode controls how clicking and dragging operates. Next to a “normal” mode this includes a mode that allows one to select text (SelectText), various modes that allow one to zoom to a particular area (ZoomIn, ZoomOut, ZoomToSelection), and a mode that allows one to select and edit annotations (SelectAnnotations).
  • A ZoomMode that defines whether the viewer should maintain a particular zoomed view on the document. This includes FitEntirePage and FitPageWidth.
  • A TextSelectMode (when CursorMode == SelectText). This allows one to either select entire words, or separate characters.
  • A ZoomFactors property and ZoomIn()/ZoomOut() methods.
  • RotatePageLeft() and RotatePageRight() methods.
  • The EnableAnnotations property, which enables or disables all annotations at once.
  • The HighlightAnnotations property, which marks all annotations with a color when set.
  • The ShowOverflowMarker property which controls whether an overflow marker is shown when there is more text in a field than it can display.
  • Events that fire when annotations get resized (when CursorMode = SelectAnnotations).

 

 

The StandardPagesViewer implements these features via a custom LayoutManager, and – in the professional edition -, with custom interactor factories. We do not recommend changing these for the StandardPagesViewer, as this will lead to undefined behavior.

 

Thumbnails Viewer

Using a thumbnails viewer proceeds in much the same way. Note that both the PagesViewer and the ThumbnailsViewer classes derive from the same DocumentViewer class. So, one can view thumbnails by using exactly the same code as for the PagesViewer, after assigning a Document instance to the Document property of the ThumbnailsViewer.

If one also assigns a PagesViewer instance to the PagesViewer property of the thumbnails viewer, it will indicate which page is currently visible in the PagesViewer and when a user clicks on a thumbnail image, it will navigate the PagesViewer to that page.

Compile and run the ThumbnailsViewer sample to see this in action.

Screenshot Of BookMarksViewer Sample

Document Viewer

The DocumentsViewer is the base class of both the Thumbnails Viewer and the PagesViewer. It can be used as a rudimentary viewer of its own, although it lacks the notion of a “current page”, and thus it does not provide navigation to particular pages.

Bookmarks Viewer

The BookmarksViewer does not show PDF pages, but it shows the bookmark tree that is defined in the document (if any). Its use is completely analogous to that of the ThumbnailsViewer.

If one assigns a PagesViewer instance to the PagesViewer property of the BookmarksViewer, it will have the PagesViewer navigate to the right page when a user clicks in a bookmark.

Compile and run the BookmarksViewer sample to see this in action. Please note that you will have to open a PDF document with bookmarks in order for the BookmarksViewer to show anything.

Screenshot of BookMarksViewer sample

usingSystem;
usingSystem.ComponentModel;

using TallComponents.Interaction.WinForms;

namespaceWindowsApplication1
{
   publicpartialclassForm1 : System.Windows.Forms.Form
   {
      publicForm1()
      {
         InitializeComponent();
      }

      privatevoidopenToolStripMenuItem_Click(object sender, EventArgs e)
      {
         System.Windows.Forms.OpenFileDialog dialog = newSystem.Windows.Forms.OpenFileDialog();

         if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
         {
            document1.Open(dialog.FileName);
         }
      }
   }
}

Printing

PDFControls.NET supports printing of PDF documents. This can be done interactively (via a print dialog) or without any user interaction.

In order to print interactively, one can use PagesViewer.Print() without passing any additional arguments. This will pop up a print dialog that allows a user to modify a number of print settings. Printing will proceed automatically when the user presses the OK button.

Using PagesViewer Print

Next to these calls, PDFControls.NET contains a few control classes that allow one to customize interactive printing. These classes are:

  • PrintDialog: this class implements the print dialog that get shown by PagesViewer.Print().
  • PrinterListControl: this class implements a winforms control that allows one to select a printer.
  • PaperSizeListControl: this class implements a winforms control that allows one to select the paper size.
  • PrintPreviewControl: this class implements a winforms control that shows a print preview of a particular page.

We will have a closer look at these classes below.

Printing Dialog

Instead of just calling PagesViewer.Print(), the PrintDialog class allows one to show the printing dialog separately, followed by a call to Document.Print() to perform the actual printing:

TallComponents.Interaction.WinForms.Controls.PrintDialog dialog =
  new TallComponents.Interaction.WinForms.Controls.PrintDialog(pagesViewer);

dialog.ShowDialog();

if (dialog.DialogResult == DialogResult.OK)
{
  dialog.PrintSettings.QueryPagePrintSettings += new  
    EventHandler<TallComponents.PDF.QueryPagePrintSettingsEventArgs>
      (PrintSettings_QueryPagePrintSettings);

  document.Print(dialog.PrintSettings);
}

Using the print dialog in combination with explicit printing.

This allows one for example to check and modify some settings before the actual printing occurs. It also gives more control over the printing process itself.

The professional version for example, allows one to subscribe to the QueryPagePrintSettings event of the PrintSettings instance of the dialog, so that the PagePrintSettings can be changed on the fly for every page that gets printed.

Printdialog Controls

The main advantage of the PrintDialog class is that it is easy to use, and provides access to a number of common settings. In some cases however, one will want to extend or limit the settings that a user has access to. In other cases, one may want to show a print dialog in a different language.

These cases can be solved by creating your own custom printing dialog. In order to facilitate this, we have provided the PrinterListControl, the PaperSizeListControl and the PrintPreviewControl. All one needs to do, it to place these controls on a custom form, and have their PrintSettings property refer to the proper PrintSettings instance. If a user interacts with these controls the print settings will be updated accordingly. Conversely, if the print settings get changed, the controls will automatically update to reflect this.

The PrintDialog sample shows an example of this.

PrintSettings

The PrintSettings class allows one to specify print job parameters. It provides the following options:

  • Collate: Whether pages of multiple copies will be assembled in the proper order.
  • Copies: The number of copies that will be printed.
  • Duplex: Specifies whether duplex printing must be used.
  • PagePrintSettings: page specific print settings. See below.
  • PagesAsIndices: the selection of pages that will be printed, given as an array that enumerates the page indices. These indices are zero-based.
  • PagesAsString: the selection of pages that will be printed, given as a human-readable string. For example “1, 2, 5 – 10”. Note that “1” means: the page with index 0. This property can conveniently be used in user interfaces.
  • PrinterSettings: this is a reference to a System.Drawing.Printing.PrinterSettings instance. It allows selecting a printer by specifying a PrinterName. In addition, it provides detailed information about the selected printer. Please note that it is also possible to change some properties of the PrinterSettings. We do not recommend this, as the corresponding properties in the PrintSettings class (such as Copies) take precedence.
  • PrintController: a reference to a System.Drawing.Printing.PrintController instance. To a certain extent, a print controller controls the behavior of the .NET printing system. Also see “silent printing” below. This property may be null.
  • PrinterName: The name of the printer. The PrinterSettings (see below) reflect the settings of the printer that is specified here. If a different name is assigned, the PrinterSettings will be updated.
  • PrinterSettings. Contains settings that specify the characteristics of the printer. These settings are fixed and unrelated to the job.

PagePrintSettings

Page specific printing settings are specified by a PagePrintSettings instance. The settings have been separated from the PrintSettings as they can vary per page.

  • AutoRotate: when set, pages will be rotated to fit the sheet of paper best.
  • Color: specifies whether a page must be printed in color.
  • HorizontalAlignment: whether pages should be aligned left, center or right.
  • Landscape: Specifies whether landscape mode is used for the page orientation. If this is off, the .Net printing system will offer a coordinate system that has its origin at the top left of a sheet of paper. If this is on, the origin will be at the top left of a rotated (landscape) sheet of paper.
  • PaperSize: the paper size to use for printing. This should be an instance of the paper sizes that are listed in the printer settings.
  • PaperSource: the paper source to use for printing (i.e. which paper tray). This should be an instance of the sources that are listed in the printer settings.
  • RenderSettings: the render settings that will be used for printing. By default, this instance has its RenderPurpose set to Print. This has consequences for the annotations that get rendered (see the Annotation.Print property).
  • ScaleToFit: specifies whether a page must be scaled to fit on the printed sheet of paper. When set, pages may get scaled up or down, depending on whether they are too small, or too big.
  • Transform: specifies a transformation to be applied to the page before printing. . This makes it possible to fine-tune the placement of a page on a sheet of paper.
  • UsePdfPageSize: when set, the specified PaperSize property will be ignored. Instead, the page size of the PDF page determines the chosen paper size.
  • UsePrintableArea. When set, care will be taken not to print in areas that the printer cannot print on, due to physical limitations. If the page is too big, it will be shrunk so it fits in the printable area of the printer. If this flag is cleared, graphics that are placed outside the printable area will be lost. In many cases however it is best to clear this flag, as the output tends to lose it “natural size”, and the physical margins of a printer are typically much smaller than the margins of a PDF page.
  • VerticalAlignment: whether pages should be aligned top, center, or bottom.

These settings are in effect for all printed pages from the moment they are specified. The professional edition allows one to subscribe to the QueryPagePrintSettings event of the PrintSettings instance, and this allows one to change the PagePrintSettings on the fly for every page that gets printed from that point on until the next change.

PrinterSettings

The PrinterSettings class specifies the (fixed) characteristics of a printer. These setting have largely been derived from the System.Drawing.Printing.PrinterSettings class (more about that below).

  • CanDuplex: Specifies whether the printer supports duplex printing.
  • IsDefaultPrinter: Specifies whether the printer is the default printer.
  • IsPlotter: Specifies whether the printer is a plotter.
  • MaximumCopies: Specifies the maximum number of copies that the printer can handle.
  • PaperSizes: The paper sizes that are supported by this printer.
  • PaperSources: The paper trays of this printer.
  • PrinterName: The name of this printer.
  • PrinterResolutions: The resolutions that are supported by this printer.
  • SupportsColor: Specifies whether the printer supports color.
  • SystemPrinterSettings: the underlying System.Drawing.Printing.PrinterSettings class. In many cases, access to this instance is of no concern, as common print settings can be controlled by the classes above. Access to this class can be useful however if printer-specific settings need to be accessed that are not covered above. See for example the PrintDialog sample. The implementation of the properties button needs access to this instance in order show a printer-specific properties window.

Please note the following about the SystemPrinterSettings property.

The members of the System.Drawing.Printing.PrinterSettings, do not only contain the printer settings above, but also settings like Copies or DefaultPageSettings, which in turn contains many settings found in PagePrintSettings above. Basically, there is no difference between these settings. Changing them in PrintSettings or PagePrintSettings will change the corresponding settings in System.Drawing.Printing.PrinterSettings and vice versa.

Nonetheless it is better to avoid manipulating the settings in SystemPrinterSettings directly. This is not illegal in itself, but our printing controls will not automatically notice any updates in these settings, so they may no longer show the correct values.

Silent Printing

One can avoid showing a printing dialog by invoking the Print method of the Document class and passing it a PrintSettings instance. The document will then be printed immediately, using the specified settings.

By default, the PrintSettings class will use an instance of System.Drawing.Printing.StandardPrintController for its PrintController. This means that printing will proceed ‘silently’, i.e. no progress box will be shown. If you do want a progress box, please assign an instance of the System.Drawing.Printing.PrintControllerWithStatusDialog class to the PrintController property of the PrintSettings class.

Please note that the PrintController class raises a number of events that allow one to monitor progress, and execute custom code. See the relevant .NET documentation for more information.

Font Substitution

By default, PDFControls.NET will send all output to the printer as low-level graphical GDI instructions, such as lines, fills, and images. This is also the case for text. This leads to graphically accurate output, but as a downside, print jobs may become large, and thus, slow.

If this is the case, one may consider substituting the PDF fonts by system fonts, provided that system fonts are available that (approximately) resemble the PDF fonts. In this way, text will not be rendered as a collection of lines and fills, but GDI will be told to render the text. This will lead to smaller print jobs. See the chapter on Fonts for details.

Handling links

By default, the PagesViewer will navigate to link destinations for as long as they are located in the same document. It will not however automatically open URL’s or files (that would impose a security risk).

In order to deal with these, it is possible to install a custom ActionHandler. One can override the standard behavior of the PagesViewer by overriding one or more virtual methods. The following sample will pop up a message box when a link gets clicked on:

public class MyActionHandler : DefaultActionHandler
{
  public MyActionHandler( PagesViewer pagesViewer ) : base( pagesViewer )
  {
  }

  public override void GoToUri(
    string uri, bool isMap, 
    TallComponents.PDF.Actions.ActionContext context)
  {
    MessageBox.Show( string.Format( "Go to URI: {0}", uri ) );
    base.GoToUri(uri, isMap, context);
  }

  public override void GoToInternalDestination(
    TallComponents.PDF.Navigation.InternalDestination internalDestination, 
    TallComponents.PDF.Actions.ActionContext context)
  {
    MessageBox.Show(
      string.Format("Go to Page: {0}", internalDestination.Page.Index));
    base.GoToInternalDestination(internalDestination, context);
  }

  public override void Launch(
    string path, 
    TallComponents.PDF.Navigation.WindowBehavior windowBehavior,   
    TallComponents.PDF.Actions.ActionContext context)
  {
    MessageBox.Show(string.Format("Open file: {0}", path));
    base.Launch(path, windowBehavior, context);
  }
}

For the standard edition, action handling is restricted to link handling. The professional edition contains more virtuals that can be overridden. These will be explained later.

Scrolling and zooming

Scrolling

The VisibleTop and VisibleLeft properties of the PagesViewer specify which location of the document is located at the top left corner of the viewer. This location is specified in points (a single point is defined as 1/72 inch).

A document is often opened with both VisibleTop and VisibleLeft set to zero, which means that the top left corner of the first page is at (or near, which also depends on the actual layout of the pages, and the margins that are kept around each page) the top left corner of the viewer. Programmatically, one can scroll down by increasing the VisibleTop value, and to the right by increasing the VisibleLeft value. If a user scrolls the document (e.g. via the scroll bars), the VisibleTop and VisibleLeft properties get updated automatically.

The VisibleWidth and VisibleHeight values of the viewer specify how much of the document is visible. These too are specified in points. One cannot set these values, as they get derived from the actual width and height of the control (which of course, are settable).

Zooming

The size of a PDF page is also given in points. This provides the basis for the definition of the ZoomFactor property of the DocumentViewer class. A zoom factor of 1 means that a single point in the document (1/72 inch) corresponds to a single pixel. As screens historically had a resolution of 72 pixels per inch, this is also referred to as “actual size”.

Setting the zoom factor to 2, means that the document gets “blown up” by a factor of 2 compared to its actual size. A single point then occupies a square of 2×2 pixels. Likewise, a zoom factor if 0.5 means that the document gets shrunk by a factor of 2 compared to its actual size. A single pixel then represents a square of 2×2 points.

In principle, zooming in and out can be done by specifying the appropriate zoom factor.

Without further measures however, changing the zoom factor will just keep the same document position at the top left corner of the viewer, and zoom in or out around that position. To zoom into other locations, one needs to set the VisibleTop and VisibleLeft properties of the viewer as well.

Zoom Methods

To make zooming easy, the DocumentViewer, the PagesViewer and the StandardPagesViewer define a number of zooming methods.

Document Viewer

The DocumentViewer defines the following zoom methods.

  • ZoomTo(zoomfactor, x, y): sets the zoom factor to the given value, and centers the view around the given document coordinate.
  • ZoomToRectangle(rectangle): zoom to the specified rectangle. The rectangle is given in document coordinates. The viewer will not change the width and height of the control, but it will zoom as far in as possible, in such a way that all of the rectangle will be shown.
  • ZoomToWidth: sets the zoom factor so that the width of the document fits the visible width of the viewer.

Pages-Viewer

Additionally, the PagesViewer defines a ZoomToFitPage method that will set the zoom factor and the visible area such that the current page is completely visible, and as large as possible.

StandardPagesViewer

The StandardPagesViewer defines a ZoomFactors collection, as well as a number of convenient extra zoom methods:

  • ZoomToActualSize: sets the zoom factor to 1.
  • ZoomIn(x,y): sets the zoom factor to the next higher value in the ZoomFactors collection, setting the center of the visible area at the specified document location. The zoom factor will never be set to a higher values than the MaxZoom property.
  • ZoomOut(x,y): sets the zoom factor to the next lower value in the ZoomFactors collection, setting the center of the visible area at the specified document location. The zoom factor will never be set to a lower value than the MinZoom property.

Note: The ZoomFactors collection is ordered from small to large. Adding a factor will automatically place it at the right position in this order. Inserting a factor must be done at the right position, or it will throw an exception.

Zoom Modes

Next to these zoom methods, the StandardPagesViewer offers a number of zoom modes. These are implemented in terms of the zoom methods above, and keep the pages of a document zoomed in a particular way.

Page layout

The page layout of a viewer is controlled by layout managers. PDFControls.NET defines a few standard layout managers that can be used for the most common page layouts:

Layout Manager Classes

Layout manager classes

  • LayoutManager: base layout manager class. All layout managers derive from this class.
  • GridLayoutManager: this layout manager orders all pages in a grid. Its constructor gets passed the number of columns that are required. GridLayoutManager(1) places all pages in a single column, the first page on top, and the last one on the bottom. GridLayoutManager(2) basically shows all pages in a “two-up” layout. The Indent property specifies how many columns the first page must be shifted to the right, provided that this value is smaller than the number of columns. This can be used in “two-up” layouts to make sure that facing pages are placed next to each other.
  • AutoColumnsGridLayoutManager: this layout manager is a subclass of the GridLayoutManager. Instead of using a fixed number of columns, this layout manager dynamically adapts the number of columns to the visible area of the viewer, with a minimum of 1. The ThumbnailsViewer uses this layout manager by default.

The use of these layout managers for laying out pages is fairly simple. One just needs to assign an instance of the proper layout manager to the LayoutManager property of the viewer.

Note: The Professional edition also defines a PageLayoutManager and an AnnotationLayoutManager. These are specialized layout managers that are only useful in combination with interactors. This will be explained later.

GridLayoutManager

The GridLayoutManager has the following properties:

  • ColumnCount: specifies the number of columns.
  • Spacing: the minimum space between pages. This value is given in points (i.e. the spacing in terms of pixels depends on the zoom factor).
  • Margin: the minimum space around all pages. This value is given in points.
  • Indent: specifies the number of columns that must be skipped on the first row before placing the first page.

The BookViewer sample uses the GridLayoutManager to show all pages in the way they would be when read in a book.

public Form1()
{
   InitializeComponent();

   GridLayoutManager bookLayout = new GridLayoutManager(2);
   bookLayout.Indent = 1;
   bookLayout.Spacing.Left = 0;
   bookLayout.Spacing.Right = 0;
   bookLayout.Spacing.Top = 30;

   pagesViewer1.LayoutManager = bookLayout;
   pagesViewer1.ZoomFactor = 0.5;
}

Implementing a simple “book layout”.

Book Layout

Custom layout managers

The professional edition allows the definition of custom layout managers. In order to do this, it is sufficient to override the Arrange method of the layout manager and have it place the provided interactors at the appropriate position.

Selecting and dragging text

If one sets the CursorMode of the StandardPagesViewer to SelectText, users will be able to select text on a page by dragging the mouse over it. The result of this will be that:

  • The text gets highlighted.
  • The text gets added to the current selection.
  • It becomes possible to drag the text to another interactor (pro only) or application that supports dropping text on it (WordPad for example).

See for example the Reader sample. If one clicks on the toolbar icon with the caret, one will be able to select text. The selected text can subsequently be copied via control-C, or dragged.

Copying Or Dragging Selected Text

Form Fields (professional only)

The professional edition also has support for editing form fields, including text field. The usual cut, copy and paste shortcuts work for text fields. Similar to ordinary text on a (static) page, text in form fields can also be selected and dragged. This can take various forms:

  • Text selections from other applications, or from static text on a PDF page can be dragged into a text field. A caret will be displayed at the position that the text will be inserted.
  • Text selections can be dragged within a text field. This will move the selected text. As soon as the cursor leaves the text selection during dragging, a caret will be displayed at the position that the text will be moved to.
  • Text selections can be dragged to other text fields. This will move the selected text.
  • Text selections can be moved or copied to other applications.

Accessing selected text

The current text selection can be accessed programmatically. The DocumentViewer has a read-only SelectedText property that holds the selected text. In the professional edition this can be either the selected text on a page, or the selected text in a form field.

The professional edition additionally has a Selection property. This is basically a collection that can hold various types of selections (not just text). It has the following properties that are related to text selections:

  • Text: this property returns the same value as DocumentViewer.SelectedText. In addition however, it can be used to modify the text of the current selection. At the moment, setting the text will only have effect for text selections in form fields.
  • Glyphs: this is the collection of currently selected glyphs. Each glyph contains information about a single character. Next to the character itself, it contains information about the position of the character on the page, and its font size. The professional edition also contains detailed information about the font of the glyph, as well as the pens and brushes that were used to draw it.

Searching and highlighting text

Searching

Text in a document can be searched with the Document.Find method. There also exists a Page.Find method that allows one to search text on a particular page only. These methods takes a TextFindCriteria instance as its parameter, which allows one to specify the following:

  • Backwards: set this to true if you want a text search to start at the end of the document instead of at the beginning. The default is false.
  • MatchCase: set this to true if the text search must only deliver matches that use the exact character casing as specified in the Text property.
  • MatchWholeWord: set this to true of the search must only deliver whole words.
  • Text: specifies the text to be search.

TextMatchEnumerator

As text searches may deliver many matches for large documents, searching proceeds lazily. The result of the find call is a TextMatchEnumerator instance. This allows one to obtain – and process – the matches one-by-one.

Next to these standard Enumerator features, the TextMatchEnumerator also has a Progress event. It may take some time before a MoveNext() call returns. This event will be fired every time that a new page is encountered. This makes it possible to show some progress information for example.

TextMatch

Each text match contains some information about the text that was found. Specifically:

  • TextFindCriteria: a reference to the criteria that resulted in this match.
  • Glyphs: A collection of matching glyphs.
  • Page: The page that the matching glyphs are on.

Highlighting

Once a particular text match has been found, it can be highlighted in the DocumentViewer by passing the TextMatch instance as an argument to the SelectText method. Doing so has a number of effects:

  • The viewer will scroll the text match into view.
  • The text match will be assigned to the current selection.
  • As a result of the latter, the text match will be highlighted.

Another effect of making the text the current selection is that the viewer will allow the text to be copied with ctrl-C, and that it will support dragging the text to another interactor or application that supports dropping text on it (such as Wordpad).

For an example of this, please see the Reader sample that is included in the distribution. The FindTextDialog implements text searching as described here.

Searching And Highlighting Text

Custom Glyph Comparing

In principle, text in a PDF document does not need to be stored in reading order. This means that the text search algorithm must first order all text. By default, the Find methods of our assemblies employ a heuristic that will work for relatively simple “western” texts, which consist of a single column and have a reading order from left to right, top to bottom.

Unfortunately, there is no known algorithm that is able to order all text of all possible documents in the proper reading order. Take a newspaper for example. An algorithm for ordering the text would not only need to know about reading orders, but in order to arrive at a perfect ordering it would also need to know about all sorts of layout rules, like headings, columns, captions, etc. This is beyond the scope of the PDFControls.NET.

To deal with this, we allow clients to create their own Glyph Comparer and pass it to the Find method. A Glyph comparer defines a Compare method that takes two glyphs as its arguments, and delivers a value that indicates which of the glyphs comes before the other in terms of reading order.

Fonts

The appearance of text in a PDF document is defined by its font. The PDF format allows fonts to be embedded in a document, so that text can be reproduced faithfully on any machine independently of which fonts are installed on the client machine.

By default, PDFControls.NET will interpret these definitions and use this to render text as a number of (curved) lines that are filled with a particular color. This leads to graphical output of high quality. There are two issues however that may need to be addressed by the application programmer.

  • The PDF format does not require fonts to be embedded. Fonts are sometimes not embedded, because they can add significantly to the file size. In that case, the application will have to decide how to render the text. In general this is done by selecting a font that is available on the system and that approximates the properties of the referenced font. This process is called font substitution. PDFControls.NET has its own default substitution scheme that is based on the font name. This approach will give reasonable results in many common cases, but this will not always suffice.
  • Rendering a font as a collection of curved lines leads to a large number of low level GDI drawing instructions. This often works well on screen, but during printing it may lead to large spool files and (consequently) slow printing. To deal with this, it is advisable to substitute the referenced fonts by system fonts. Note that this technique also applies to fonts that are embedded in the document.

Below, we will have a look at the mechanisms that PDFControls.NET offers for dealing with this. Central to controlling the rendering of text is the TextRenderSettings class which can be accessed through the RenderSettings.TextSettings property, which is present as a property on all viewers.

Font substitution map

The TextSettings contain a FontSubstitutionMap property. This property applies to non-embedded fonts only and it allows PDF font names to be mapped to font definition files on your file system. Often, one will refer to font files that are installed in the fonts folder of the system, but this is not mandatory. It is also possible to refer to files that are located elsewhere.

The font substitution map contains entries that map PDF font names to font files. If you open the PDF document using the Adobe PDF Reader and hit CTRL+D and then go to the Fonts tab, you may see the following fonts listed:

Font

The PDF font name is the first line of each entry above.

Font search path

As a convenience, font files can be specified relative to a font search path. This path is stored in the FontSearchPath property of the TextRenderSettings class. It contains a list of folders that are separated by semi-colons. If a relative file name is specified for a font name, the specified folders are searched from left to right until the font file is found.

The default value for the search path is “.;%FONTDIR%”. This means that we will first look in the current folder and then in the Windows fonts folder.

Default substitution font

Next to a number of explicit font mapping, the font substitution map contains a DefaultSubstitutionFont property. This indicates which font file to use when no explicit entry has been found for a particular PDF font name. The default value is “times.ttf” (relative to the font search path).

ResolveFont event

The font substitution map only plays a role for non-embedded fonts. In contrast, the ResolveFont event applies to all fonts that are referenced in a PDF document. It will be raised whenever PDFControls.NET needs to render a font.

The ResolveFont event provides information about the PDF font that PDFControls.NET has encountered, as well as the way that it is planning to render the font. The latter can be changed by the programmer, whereas the former is fixed. If the programmer does not change the event data, PDFControls.NET will use its default mechanism for rendering the font, including any substitutions that are indicated by the font substitution map.

If the programmer wants a different font to be used, he can change some of the event properties to indicate a different font definition. PDFControls.NET will then try to find the indicated font. If this succeeds, the newly found font will be used instead.

If PDFControls.NET cannot obtain the font that was indicated by the programmer, it will re-issue the ResolveFont event, allowing the programmer to provide an alternative. This process will proceed until a font definition has been found.

We will have a more detailed look at this below.

First event invocation

The first time that a particular PDF font is encountered, the system will first use its default mechanisms for resolving the font, except for consulting the default substitution as specified in the font substitution map. It will then raise the ResolveFont event to inform the client which actual font was found. The Location property of the ResolveFontEventArgs object specifies whether a font definition has been found, and if so where it has been found. At this point, it can have the following values (in order of precedence):

  • Data: the font is either embedded, or the font substitution map contains a reference to a stream with font data.
  • File: the font is not embedded, but the font substitution map contains an explicit entry to a particular font definition file. In this case the FontPath property of ResolveFontEventArgs contains the file name.
  • System: the font is not embedded, and there is no explicit entry for it in the font substitution map, but a system font has been found that matches the name of the PDF font. In this case the SystemFontName property of ResolveFontEventArgs contains the name of the system font.
  • Unresolved: no definition was found. This means that the font is not embedded, and there is no explicit entry for it in the font substitution map, and its name does not match any system font name.

In any case, the Embedded flag of the ResolveFontEventArgs specifies whether the font was embedded or not, which is in particular useful when the location is Data.

If the event handler does not change any properties of this event, the system will use the definition as indicated by the Location property. If the font has been resolved this is often the right thing to do. If the font is unresolved the system will use the default font as specified in the FontSubstitutionMap.

Specifying a different font

By changing one or more event properties, the event handler can change the font that will be chosen. Not only is it possible to map unresolved fonts to different fonts than the default, but it is also possible to map resolved fonts to different font definitions.

The following event handler maps two specific unresolved fonts to an Arial font file.

static void resolveFont(object sender, ResolveFontEventArgs args)
{
   if (args.Location == FontLocation.Unresolved)
   {
      switch (args.PdfFontName)
      {
         case "TallComponents Demo Font":
            args.FontPath = "ariali.ttf";
            break;
         case "TallComponents Demo Font Bold":
            args.FontPath = "arialbi.ttf";
            break;
      }
   }
}

Implement font substitution through the ResolveFont event

Changing the font handling for fonts that have been resolved may make sense in some cases. Fonts that have their FontRenderMode set to RenderAsCurves increase the size of print jobs. Therefore, one may choose to have these fonts rendered by an appropriate system font. This is merely a matter of changing the font render mode and setting the SystemFontName of the event to the appropriate font name. This is demonstrated in the following code sample:

void resolveFont(object sender, ResolveFontEventArgs args)
{
   if (args.FontRenderMode == FontRenderMode.RenderAsCurves)
   {
      switch (args.PdfFontName)
      {
         case "Times-Roman":
            args.FontRenderMode = FontRenderMode.RenderAsFont;
            args.SystemFontName = "Times New Roman";
            break;
         case "Times-Bold":
            args.FontRenderMode = FontRenderMode.RenderAsFont;
            args.SystemFontName = "Times New Roman";
            args.Bold = true;
            break;
         case "Times-Italic":
            args.FontRenderMode = FontRenderMode.RenderAsFont;
            args.SystemFontName = "Times New Roman";
            args.Italic = true;
            break;
      }
   }
}

Render well-known fonts as system fonts instead of as curves

Handling Failure

If the system is able to resolve the font, based on the information provided by the event handler, it will simply use that font. If for some reason the font cannot be resolved, or if it cannot be used as requested, a new ResolveFont event will be raised. Which is the case can be determined by inspecting the location:

  • If the font cannot be resolved at all, a new event will be raised with the location set to Unresolved. The FontPath or the SystemFontName will indicate which locations were tried.
  • At the moment, files can only be rendered as curves, and system fonts cannot be rendered as curves. If an unsupported render mode was specified, a new event will be raised with a corrected render mode (This means that the corrected mode will be accepted if the handler does not alter the event data).

CMaps

CMaps are used when displaying some fonts, mostly Chinese, Japanese or Korean (also known as CJK fonts). The CMaps are part of the zip archive that can be downloaded from our website. You should copy the folder \Support\CMaps to your deployment environment and tell PDFControls.NET 2 where they can be found. You can do this by assigning the path of folder CMaps to the static property TextRenderSettings.CMapFolder or by handling the event TextRenderSettings.ResolveCMap. The event allows you to provide each individual CMap as a System.IO.Stream so that you may pull the CMap from e.g. a database or load a resource from an assembly.

Font styles

Windows has the concept of font styles. A font may be regular, bold, italic or bold-italic. The PDF format does not share this concept. A PDF document may of course use bold or italic fonts, and by convention, PDF font names contain the string “bold”, “italic” or “oblique” in these cases. But other than that the font description inside the PDF document does not explicitly specify whether a font is bold or italic.

Interactors

The professional edition of PDFControls.NET 2 introduces the concept of Interactors. In short, the interactor class captures GUI elements. This not only includes PDF widgets, but also the pages that are shown by a PagesViewer instance, and even non-PDF elements like windows controls and bitmaps. The concept of an Interactor is related to that of a Control in WinForms and a GUIElement in WPF. Interactors have two main tasks:

  • They provide the appearance of a GUI element.
  • They provide the behavior of a GUI element (such as handling of mouse clicks and keystrokes).

A viewer is basically a (WinForms) control that shows a number of Interactors. Interactors are not shared among viewers. They belong to a single viewer. The figure below shows a PagesViewer and a ThumbnailsViewer that each have their own set of page interactors for showing the pages of a PDF document. Some of the pages are shown by both viewers. If so, the PDF pages are shared, but the page interactors are not.

Viewers With Interactors

Interactors allow the PDFControls 2 GUI to be customized more pervasively than was possible in PDFReaderControls 1. There are various reasons why we have introduced a separate class for GUI elements, next to the PDF-specific classes – like pages and widgets – that already existed in PDFReaderControls 1.

  • PDF-specific classes are tied to one particular document. A single document however, may have multiple simultaneous views. This is the case for example when both the thumbnails viewer and the pages viewer are showing the same document simultaneously. Both viewers will need separate Interactor instances to represent the document and its pages.
  • An Interactor forms a natural place to accommodate GUI features that go beyond the ones that have been defined in the PDF specification. An Interactor for example, defines a VisibleRectangle that tells which part is visible in the Viewer at a particular moment.
  • It is desirable to treat all GUI elements similarly, without a distinction between PDF elements like pages and widgets, and windows elements like controls and bitmaps.

Note: The main reason for having Interactors and not just WinForms controls is that WinForms controls are too limited to represent all possible PDF elements.

Hierarchy

Each Interactor can hold any number of child interactors. A viewer basically only shows a single root Interactor, from which all other interactors are reachable.

By default, when a PDF document is opened and associated with a viewer, an Interactor hierarchy gets constructed for that viewer that follows a similar structure as the PDF document itself:

  • At the top level, we have the DocumentInteractor. It offers a view on the entire document.
  • Each DocumentInteractor contains a collection of child PageInteractor instances. Each PageInteractor shows a particular PDF page.
  • Each PageInteractor in turn, contains a collection of child AnnotationInteractor instances. Subclasses of these include WidgetInteractor, and LinkInteractor. These are subclassed further into CheckBoxInteractor, ListBoxInteractor, etc.

The interactor hierarchy plays an important role for both appearance and behavior:

  • Child interactors are always drawn on top of their parent. So, the hierarchy specifies a z-order.
  • By default, if a mouse click occurs over a child interactor, it will be handled by the child, and not the parent. So basically the child hides the parent at that position, which is consistent with drawing the child on top of the parent.

The order in which the children are stored in the Interactor.Childs property plays a similar role, but this only comes into play when child interactors overlap:

  • The children are drawn in the order that they are stored in the collection. As a result, the last child is drawn on top of all the other children.
  • If a mouse event occurs over two overlapping children, it will get handled by the child that is stored after the other.

Interactors At Various Levels

It is possible to customize this hierarchy further. One may for example encapsulate a windows GUI element (like a control) in a “WinFormsControlInteractor” and include that in a DocumentInteractor or PageInteractor. The effect of this will be that the windows element will appear “next to” the pages in the document, and next to the annotations on the page respectively. This technique has been used in the CustomDateTimeInteractor sample.

Note: Often the children of an interactor are placed inside the bounds of their parent. This is not mandatory however.

Interactor Factories

Interactors are normally created automatically when a document gets opened in a viewer. By default, the viewer will create a number of standard interactors that provide common behavior.

One can see this in the Reader sample. All the behavior that it provides is basically implemented by the standard interactors that PDFControls.NET creates automatically. As a result, one will hardly have to deal with interactors if one just needs such standard behavior.

There is a second reason for creating interactors automatically. Interactors need to be created lazily. If we were to create all interactors for a large document up front, we would quickly run out of resources. We use the automatic interactor creation mechanism to only create interactors when needed, which basically means: when they must be shown on the screen.

At the same time, we also want clients to be able to create their own interactors in order to customize the GUI. In order to do this, we have introduced Interactor factories. A document viewer has three of such factories:

  • DocumentInteractorFactory: a factory that creates an instance of a DocumentInteractor when a document gets opened by the viewer.
  • PageInteractorFactory: a factory that creates instances of a PageInteractor for each page in the document.
  • AnnotationInteractorFactory: a factory that creates instances of an AnnotationInteractor for each annotation on a visible page.

Each factory has a number of virtual methods that each creates a particular type of interactor. In order to create a custom interactor, one will need to subclass the appropriate factory and override one or more of the “create” methods so that it creates the modified interactor. This factory will then need to be assigned to the proper factory property of the viewer.

For details, please see the chapter on factories. The following sections will focus on the interactors itself.

Coordinates

Each Interactor defines its own coordinate system and all contained interactors have a position that is relative to this coordinate system. As a result, if one moves an interactor, all contained interactors will move with it as if they are attached to their parent.

Each interactor has its origin at its upper left corner, with the x-axis pointing to the right, and the y-axis pointing down. This corresponds to the coordinate system that Windows uses for its controls and forms. Please note that this is also true for page interactors, even though in PDF, pages have their origin at their lower left corner, with the y-axis pointing up. In other words: even though the location of PDF widgets are specified in PDF page coordinates, the corresponding widget interactors will be placed in the coordinate system of the PageInteractor.

Dimensions

Within its own coordinate system, the dimensions of an interactor are roughly defined by the following properties:

  • Width: each concrete interactor defines a read-only width. The width is not settable, because it is normally derived from an underlying logical object (such as a PDF annotation, or a PDF page). In order to change the width, just change the dimensions of that object.
  • Height: each concrete interactor defines a read-only height. This is similar to the Width.

This does not mean that all interactors are rectangular. The values above are just basic bounds in which the interactor will be drawn.

Transforms

Interactors have a width and a height, but they do not have x- and y-properties that defined their position. Instead, the location of an interactor is defined by means of a Transform, which is applied relative to its parent.

For example, if you want to position an interactor at (10, 20) you can use the following code.

Interactor.Transform = new TranslateTransform(10, 20);

Many other transformation are possible, next to positioning an interactor. The Transform class has the following subclasses:

  • IdentityTransform: the identity transformation, i.e. this transformation has no effect.
  • TranslateTransform: this moves (translated) the interactor over a particular distance in the x and y direction.
  • ScaleTransform: this transform scales the interactor in the x and y direction.
  • RotateTransform: this transform rotates the interactor. The angle is specified in degrees. For interactors the rotation is clockwise (The direction of the rotation depends on the coordinate system. If the origin is at the top left corner – with the y-axis going down -, the rotation will be clockwise. This is the case for interactors (and WinForms/WPF). If the origin is at the bottom left corner – with the y-axis going up -, the rotation will be counter-clockwise. This is the case for PDF shapes).
  • SkewTransform: this transform skews the interactor.
  • TransformCollection: a transformation that consists of a sequence of other transformations. A TransformCollection provides access to the separate transformations that it has been constructed of.
  • MatrixTransform: a 2D transformation that is defined by a 3-by-3 affine matrix. It is possible to append transformations to an existing MatrixTransform, but in contrast to a TransformCollection, these transformations cannot be extracted from the resulting MatrixTransform. The advantage of a MatrixTransform is that it does not store all the transformations that have been appended, which makes it more efficient in situations that existing transformations get updated often.

Interactors take the same approach as WPF. A transform does not change the coordinate system, but it specifies the transformation that is applied to the interactor. If one takes a coordinate within an interactor, and applies its transform to it, one will obtain the corresponding coordinate in the coordinate system of the parent.

Please note that the order in which transformations are applied is important. In particular this should be taken into account when adding transforms to a TransformCollection, and when appending transformations to a MatrixTranform. As interactors use the same approach as WPF, the visual studio designer can be used to determine which order is correct.

Note: The Width and Height of interactors that have been derived from WinForms controls (i.e. instances of WinFormsControlInteractor) are derived from the width and height properties of these controls. WinForms controls however have their width and height specified in pixels. They cannot rotate, and resizing may lead to strange effects. To avoid problems when an interactor gets transformed, the WinForms controls on it will stay upright, and will not scale. This effect can be seen in the AdditionalControlInteractor sample. To keep things consistent, the Width and Height properties of the associated WinFormsControlInteracors will automatically adapt to any transformation so that they still show the correct width and height with respect to the parent interactor coordinate system.

Appearance

The appearance of an interactor is implemented by its OnPaint() method. This is similar to the OnPaint() method in WinForms. Any time that a viewer needs the appearance of a particular interactor, it’s OnPaint() method will be invoked automatically:

  • OnPaint(PaintEventArgs): this virtual method provides the basic appearance of the interactor. It will be invoked before drawing any child interactor. A GDI graphics instance will be provided as part of the PaintEventArgs, and it will use the coordinate system of the interactor, i.e. the origin is at the top left of the interactor. Painting will be clipped to the width and height of the interactor. If one overrides this method one can invoke base.OnPaint() to draw the base appearance and paint additional marks before or after. But it is also possible to avoid calling base and paint a completely new appearance.

Special Cases

Next to these, there are a few virtual painting methods that deal with special cases:

  • OnPaintOver(PaintEventArgs): This virtual method can be used to draw additional marks over the regular appearance of this interactor as well as over all its children. Typically, it will be used to add some highlighting, for example when an interactor gets selected. This method gets invoked after drawing all child interactors. The provided GDI graphics instance will use the same coordinate system as for OnPaint(), but painting will not be clipped.
  • OnPaintShadow(PaintEventArgs): This virtual method can be used to draw a shadow effect for the current interactor. It will be invoked before painting any other part of the interactor (The shadow of all children of some parent will be drawn before any of the children gets drawn). The provided GDI graphics instance will use a coordinate system that is slightly translated compared to the OnPaint() variant. This translation is specified by the ShadowDistance property of the interactor, and indicates a translation in the parent coordinate system (so that transformations of the interactor do not influence the location of the shadow with respect to the parent).
  • OnPaintDrag(PaintEventArgs): paints a representation of the interactor when it is being dragged. The provided graphics instance provides a coordinate system that represents the current location of the dragged interactor.

Behavior

The behavior of an interactor is controlled by means of a number of virtual methods, which resemble the methods that are present in WinForms and WPF. These are methods like “OnMouseDown”, “OnKeyDown”, etc. They will be invoked automatically when a certain interactive event occurs, and then they should handle this event. We will have a look at these methods below, grouped by the type of event (see also the chapter on events).

Please note that in order for an interactor to react to user input at all, its IsInteractive property must be set to true (also see the HideAnnotations sample).

Focusing

If an interactor has the focus, all GUI events that do not specify a location will go to that interactor. An interactor can get the focus in two ways.

  • By assigning the interactor to DocumentViewer.FocusInteractor.
  • By clicking on an (interactive) interactor. Also see the section on mouse event handling.

During a focus change, the following virtuals will be invoked.

  • OnGotFocus(EventArgs): this method will be invoked after the interactor has received the focus. This allows the interactor to prepare. Text fields for example, will typically start a blinking cursor.
  • OnLostFocus(EventArgs): this method will be invoked after an interactor has lost the focus. This allows the interactor to finalize. Text fields for example, will typically stop showing a blinking cursor.

Note: It is possible to reassign the focus during OnGotFocus or OnLostFocus. If an override calls base.OnGotFocus or base.OnLostFocus, it is advised to check afterwards whether the interactor still has (lost) the focus. If the focus gets reassigned during OnLostFocus, this new value will prevail over any focus action that might have triggered the OnLostFocus invocation.

Keyboard handling

There are two ways in which an interactor will receive keystrokes. First of all, if an interactor has the focus, all keystrokes will go to that interactor. And secondly, it is possible to send text to a particular interactor by invoking the SendText method of the interactor.

Keystrokes (including SendText) will lead to the invocation of the following virtuals:

  • OnKeyDown(KeyBoardEventArgs): invoked when a key is pressed while this interactor has the focus. If the key is held down, multiple invocations of OnKeyDown will occur.
  • OnKeyUp(KeyBoardEventArgs): invoked when a key is released while the interactor has the focus.

Please note that unlike WinForms, there is no separate KeyStroke event.

Mouse Moves

While a user is moving the mouse over a viewer the following methods will be invoked.

  • IsOver(x, y): This method will only be invoked when the mouse pointer is inside the bounds that are given by the Width and the Height of the interactor. IsOver() should return true for each coordinate within this area that is able to process mouse events. No further mouse handling methods will be invoked for any coordinate for which IsOver() returns false. Mouse handling can be turned off entirely for a particular interactor by letting IsOver() return false for the entire area. In other words: IsOver() refines the clickable area of an interactor, and together with OnPaint(), it defines its shape. By default, IsOver(x, y) returns true.
  • OnMouseEnter(EventArgs): Invoked when the mouse pointer enters the area that is defined by IsOver.
  • OnMouseLeave(EventArgs): Invoked when the mouse pointer leaves the area that is defined by IsOver().
  • OnMouseMove(MouseEventArgs): Invoked regularly when the mouse pointer moves over the interactor. The MouseEventArgs can be inspected to determine the exact location of the mouse pointer.
  • OnMouseHover(EventArgs): Invoked shortly after OnMouseEnter, except when OnMouseLeave has been invoked before that. By default, this is used as a trigger for showing tooltips.

Mouse Clicks

If a user clicks on an interactor (i.e. on the area defined by IsOver), the following methods are invoked:

  • OnMouseDown(MouseEvenArgs): Invoked at the moment that the user presses a mouse button. At this point, the mouse button is still down. The default implementation of OnMouseDown() will set the focus to this interactor if the left mouse button was pressed.
  • OnMouseUp(MouseEventArgs): Invoked at the moment that the user releases a mouse button.
  • OnMouseClick(MouseEventArgs): Invoked when the user clicks in this interactor. This will happen at the moment that the user releases the mouse button, but only if the mouse button also went down in this interactor. The latter may not be case if the mouse was dragged before releasing.
  • OnMouseClick() will be generated shortly before OnMouseUp().
  • OnMouseDoubleClick(MouseEventArgs): Invoked when the user double-clicks in this interactor. This will happen at the moment that the user presses the mouse button for the second time. This will only happen if the previous mouse-down and mouse-up also occurred in this interactor (also see OnMouseClick()). OnMouseDoubleClick() will be generated shortly after the second invocation of OnMouseDown().

Dragging

PDFControls.NET 2 supports dragging in various ways. This will be explained in a separate chapter.

Interactor layers

By subclassing an interactor, one can create a custom one. This mechanism however, cannot be used to easily extend or modify interactors of different types. In order to do this, one needs to add a Layer (This notion is related to Adorners in WPF) to the interactor. A layer will process events before they reach the interactor. If multiple layers are added to an interactor, events will be processed by the outermost layer first, which is the layer that was added last. If the outermost layer does not handle a particular event, it will be handled by the layer below it, etc.

A layer offers the same event handling virtuals as an interactor (OnMouseDown() etc.). The default implementation of the InteractorLayer class will just invoke the corresponding method of the previous layer, or of the interactor if no previous layer exists. This means that a layer is completely transparent by default, both in terms of appearance and behavior.

In order to create a useful layer, one will need to derive from the base InteractorLayer class and override one or more of its event handling methods. This override may then handle the event differently. Whether or not the handling of the underling layers/interactor are invoked is just a matter of calling base.

For example:

  • If one creates a layer that overrides OnPaint() one can change the appearance of the interactor. Marks that are painted before calling base.OnPaint() are painted below the existing appearance. Marks that are painted after calling base.Paint() are painted on top of the existing appearance.
  • If one creates a layer that overrides OnMouseClick(), one can change the behavior of an interactor. One may avoid calling base for example, and thus completely hide the mouse click for the underlying interactor. Or, one may just log the occurrence of the click and pass it on unmodified by calling base. Many variations in between are possible.

In addition to overriding the methods for defining the appearance and the behavior, there are a few additional elements that a layer allows to be overridden:

  • Rectangle: this property should specify a rectangle that encompasses the interactor and all its layers. As a convenience, the interactor also has a rectangle property that maps onto the rectangle of the outermost layer. If it has no layers this rectangle specifies the rectangle (0, 0, Interactor.Width, Interactor.Height).
  • IsOver(x, y): this allows the layer to specify a different shape for the interactor. This may be necessary if the appearance is different.
  • HoverCursor: this allows the layer to specify a different cursor when the mouse hovers over the interactors, suggesting different functionality.

Layout managers

Next to positioning the interactors explicitly, it is possible to assign a Layout Manager to the Layout property of any interactor. Each layout manager automatically positions the children of the interactor that it has been attached to.

As indicated earlier, each viewer also has a layout manager to position the pages of a document. This layout manager is actually a reference to the layout manager of the root interactor. It exists in the viewer not only as a convenience, but also because the standard edition has no access to interactors.

Please note that a layout manager will actively update the Transform properties of the child interactors. If one needs to set these explicitly, please make sure to set the layout manager to null, or use a layout manager that takes into account the explicit positioning of the children.

Standard Layout Managers

Next to the GridLayoutManager and the AutoColumnGridLayoutManager, we provide 2 special-purpose layout managers:

  • PageLayoutManager: this layout manager is typically assigned to a DocumentInteractor. It first resets the Transform of each page and then makes sure that all page interactors are rotated according to their orientation.
  • AnnotationLayoutManager: this layout manager is typically assigned to a PageInteractor. It makes sure that all annotation interactors are placed in the coordinate system of their PageInteractor, according to their location in the PDF page. So basically, this layout manager translates coordinate in PDF page space (origin at lower left corner) to PageInteractor space (origin at top left corner).

These layout managers do not take into account the existing Transform of an interactor. So, if these layout managers are attached to an interactor of which the transform was changed explicitly, the layout manager will at some point overwrite this transform.

Custom Layout Managers

The professional edition allows the construction of custom layout managers. Each layout manager has an Arrange() method that can be overridden to define a different layout. The Arrange() method gets two arguments:

  • Interactors: this is the collection of interactors that must be laid out. Their Transform contains information about their current position.
  • A layout context. This provides information about the area that the passed interactors must be placed in. Currently, it specifies a) the rectangle that the parent occupies, and b) a rectangle that indicates the visible area. Both are specified in the parent coordinate system. Future versions may include more extensive information.

If one wants to create a custom layout manager, one just needs to provide an arrange method that updates the Transform property of the interactors.

Combining Layout Managers

In some cases, it is useful to be able to construct new layout managers based on existing ones. The base LayoutManager allows this by means of a constructor that takes another layout manager for its argument. The base Arrange method will invoke the arrange method of the passed layout manager. As a convention, derived classes that support argument layout managers should first invoke base.Arrange(), and then perform their own layout. In this way, both layout steps can be combined.

The GridLayoutManager employs this. For page layouts, one will typically pass the PageLayoutManager as an argument to the GridLayoutManager. In this way, each page will first be rotated so that it conforms to the specified page orientation, and then, the resulting interactor will be placed in a grid.

Automatic Layout

Please be aware that the layout manager can be invoked at unpredictable times. It will be invoked at some point shortly after one of the following situations has occurred:

  • After invoking the Invalidate method of the layout manager.
  • After a new layout manager has been assigned.
  • After children have been added, removed, or reordered.
  • For document interactors, after a property of one of its pages changes, except when this property is known to have no influence on the size or orientation of the page.
  • For page interactors, after a property of one of its annotations changes, except when this property is known to have no influence on the size or orientation of the annotation.

The arrange method will not be invoked after changing the transform of a child interactor. In most cases, the transforms will either be managed by a layout manager, or they will be set explicitly. If you want to force a layout step after changing a transform, just call invalidate in the layout manager (and make sure that you use a layout manager that does not immediately overwrite the transform).

Note also that the arrange method will only be invoked automatically if a property of a direct child has changed. It will not be invoked when interactors lower down change, nor when the parent interactor itself changes, or any interactor higher up for that matter. The reason for this is simple: we try to avoid complexity. If we were to consider additional levels, calling arrange itself would not only cause new layout steps to be started deeper down, but also higher up. This leads to a system that is difficult to understand and control (As well as being very inefficient).

For many situations, this simple approach works well. In case one does want to arrange layout in more fancy ways, one can do so by triggering layout steps explicitly in a particular order for several levels, possibly via custom implementations of the Arrange method. In this way, the programmer remains in control of the system.

The GridLayoutManager employs this. For page layouts, one will typically pass the PageLayoutManager as an argument to the GridLayoutManager. In this way, each page will first be rotated so that it conforms to the specified page orientation, and then, the resulting interactor will be placed in a grid.

GUI Events

As we have seen above, an interaction provides its appearance and behavior by implementing virtual methods – like OnMouseDown – that get an instance of EventArgs as their argument. This is consistent with the way that WinForms and WPF basically work.

There is also an important difference. In PDFControls.NET, the base implementations of these methods will not raise any events (These are called “bubbled” events in WPF, as they appear to “bubble up”. Bubbled events are often problematic, as they allow an event to be modified by an external party during the execution of code – like OnMouseDown – that is actually handling the event already. We avoid this issue completely. Please note that it is very easy to reintroduce these events in case one does need them. One merely needs to create a custom interactor that introduces such events. In that case, the programmer also has complete knowledge over the moment that the event is raised). As a result, it is also not mandatory to call base when such methods are invoked. Not invoking base is actually a good way to turn off the corresponding base functionality.

In essence, the virtuals in the interactors are all about event handling, not about event raising.

Preview Events

This does not mean that interactors do not raise any events at all. They will raise preview events, similar to the preview (or tunneled) events in WPF.

These types of events do not originate at a particular interactor, but they arise “from outside”. In essence the user generates these events by clicking the mouse, by entering text, and by other means to interact with the computer. They subsequently end up at a viewer, which then tunnels the event down to the appropriate interactor, starting with the root interactor (the document interactor).

If a user clicks the mouse over an annotation, then:

  • The viewer will start by raising a MouseDown preview event.
  • Next, the DocumentInteractor will raise a MouseDown preview event.
  • The event will then be tunneled down to the PageInteractor in which the annotation resides. This page interactor will subsequently raise its own MouseDown preview event.
  • And finally, the event will be tunneled to the proper AnnotationInteractor. This will then raise a MouseDown preview event as well. After this event has been raised, the annotation interactor will invoke OnMouseDown in order to handle the event.

This means that preview events allow an outsider to influence the event data before it actually gets handled by the appropriate interactor.

In addition, preview events allow the event to be cancelled. If the cancel flag of a preview event gets set, the event will not be tunneled any further. Instead, the last interactor that raised an uncancelled event will get to handle it.

Using the example above: if one cancels the preview event that got raised by the AnnotationInteractor, the event will not be processed by the AnnotationInteractor, but by the PageInteractor. This means that the OnMouseDown method of the page interactor will be invoked instead.

Likewise, if one cancels the preview event that got raised by the PageInteractor, the OnMouseDown() method of the DocumentViewer will be invoked. The AnnotationInteractor will not even generate a preview event then.

Interactor Events

Given the event processing mechanism that has been described above, there are various types of events that an interactor may get:

PaintEventArgs

This type derives from System.Windows.Forms.PaintEventArgs, and extends it with a RenderSettings property. It is passed to OnPaint(), and related virtuals.

MouseEventArgs

This type derives from System.Window.Forms.MouseEventArgs. It redefines X and Y, and Location as types that are based on doubles to avoid rounding errors. This is needed because the mouse events will always be localized to the coordinate system of a particular interactor before they are passed, and this implies that integer positions are not sufficient. This event is passed to various mouse handling virtuals.

KeyboardEventArgs

This type derives from System.Windows.Forms.KeyEventArgs. It adds a Char and a String property:

  • The String property contains the culture-specific text of the keyboard event. In most cases this is the value that you will need to access as it represents the actual text that was entered. Pressing the decimal separator on the keypad for example will provide the separator of the current input language. If text is pasted, it will provide the entire text.
  • The Char property is only applicable if the KeyBoardEvent represents an actual key press (or release). It corresponds to the actual character that was entered, i.e. shift + ‘key a’ provides ‘A’ as the Char value. Basically, this corresponds to the KeyChar value of System.Windows.Forms.KeyPressEventArgs. Char holds a null value (‘\0’) for keys that do not have a character representation (such as the arrow keys) (windows does not even generate a KeyPress event for these keys), and if the KeyBoardEvent is not the result of an actual key press (e.g. when pasting some text).

DragDropEventArgs

This type derives from System.Windows.Form.DragEventArgs. It redefines X and Y as doubles for the same reason as MouseEventArgs. In addition it defines the following properties:

  • DX: the distance that an element (interactor) was dragged in the X direction, in screen coordinates.
  • DY: the distance that an element (interactor) was dragged in the Y direction, in screen coordinates.
  • DraggedInteractors: a collection of dragged interactors, if any.

DragSelectEventArgs

This event will be raised when the user drags a selection rectangle over some interactor. This type is derived from System.Windows.Forms.EventArgs, as WinForms has no specific event type with similar functionality. It defines a single property:

  • Rectangle: this is the bounding rectangle of the dragged area, specified in the coordinate system of the interactor that receives the event.

Other Events

A few other event types have been defined in the TallComponents.Interaction.WinForms.Events namespace. These events generally have to do with interaction, but they are not passed to one of the interactor virtuals that handle GUI events.

Bookmark events

There exist various bookmark events. These are only raised by the Bookmarks viewer. Please see the event description of the BookmarksViewer in the reference documentation.

Unhandled Exceptions

PDFControls.NET regularly executes code that is not invoked by user code directly, but as a result of some external event (like a mouse click) that triggers the execution of particular code.

PDFControls.NET tries to avoid throwing exceptions in such code, but it is always possible that an exception occurs that we cannot handle, for example if GDI throws some exception during the execution of OnPaint(). In order to allow clients to handle such situations, they can subscribe to the UnhandledException event of the DocumentViewer. If subscribed to, each unhandled exception will be transformed into an UnHandledException event. The handler may then choose to handle it, or throw an exception after all.

Advanced: WinForms Viewer Events

Each viewer is actually a WinForms control, and as such, it will invoke various WinForms control virtuals – like OnMouseDown -, including raising the corresponding WinForms events. As always, it is possible to attach your own handlers to these, but in order to this effectively it is important to understand the relation between these WinForms events and the interactor events that the interactors receive.

Mouse Events

In order to be able to handle mouse events, our viewer has overridden various event handlers of the base control, like for example OnMouseDown(). If a WinForms mouse event arrives at a viewer control, this event handler will get invoked. For mouse events, our viewer subclass will first call the base handler. (base.OnMouseDown()). The latter will raise the corresponding WinForms event (MouseDown). After this event has been handled, the event will be passed down the interactor tree as explained above. This leads to a number of Preview events, and finally the invocation of the event handler (Interactor.OnMouseDown).

This means that mouse events can be inspected and handled at the viewer level before they reach any of the interactors in the viewer. If it is necessary to avoid subsequent processing by the interactors, one may subscribe to one or more preview events of the viewer and cancel them. Or alternatively, one could override the viewer level event handler (OnMouseDown()) and avoid calling base so that the viewer functionality is not triggered at all (Or call base.base.OnMouseDown() so that the viewer implementation is skipped, and the WinForms event is still fired). In general however it is probably better to avoid cancelling mouse events at the viewer level, and insert modified handlers at the appropriate interactor level. It is also possible to let interactors ignore events by setting their IsInteractive flag to false.

This means that mouse events can be inspected and handled at the viewer level before they reach any of the interactors in the viewer. If it is necessary to avoid subsequent processing by the interactors, one may subscribe to one or more preview events of the viewer and cancel them. Or alternatively, one could override the viewer level event handler (OnMouseDown()) and avoid calling base so that the viewer functionality is not triggered at all1. In general however it is probably better to avoid cancelling mouse events at the viewer level, and insert modified handlers at the appropriate interactor level. It is also possible to let interactors ignore events by setting their IsInteractive flag to false.

If Preview events must be cancelled, please note that there may not be a trivial relation between the raised Preview events and the original WinForms event that triggered them:

  • MouseDown: this WinForms event will always lead to a PreviewMouseDown event.
  • MouseMove: this WinForms event will ordinarily lead to a PreviewMouseMove event. If the mouse is being dragged however, it may trigger PreviewSelectMouseDown, PreviewSelectMouseMove, and PreviewDragBegin events, depending on the DragBehavior property of the interactor. In addition, this event may trigger various Enter and Leave events.
  • MouseUp: this WinForms event may trigger a PreviewMouseUp or PreviewDragSelectMouseUp event.
  • DragDrop: this WinForms event will trigger a PreviewDragDrop event.
  • DragOver: this WinForms event will trigger a PreviewDragOver event.
  • DragEnter/DragLeave: these WinForms events do not trigger any preview events, but they are overridden by the viewer to support dragging object into or out of the viewer.

Note that the mouse preview events of the Viewer are raised after the WinForms event was raised that triggered it.

Keyboard Events

Keyboard event handling is largely similar to mouse event Handling. If a WinForms keyboard event arrives at a viewer control, first the corresponding WinForms event handler will be invoked, say OnKeyDown(), which will then first call the base handler (base.OnKeyDown()). The latter will raise the corresponding WinForms key event (KeyDown). Hereafter the viewer will dispatch the event to the interactor that has the focus.

There is also a difference with mouse event handling: after the WinForms key event has been raised, the viewer will inspect the handled flag of the event. If it has been set, it will not pass the event to the focused interactor. In this way further processing of keyboard events can easily be cancelled.

Paint Events

The paint event gets handled differently than keyboard events and mouse events. If the OnPaint method of the viewer gets invoked, it will first pass the event to the interactors, so that they get drawn. After this has completed, it will invoke the base handler (base.OnPaint()). The latter will raise the WinForms Paint event.

In this way, any subscriber will be able to easily paint additional marks over the interactors.

Interactor factories

All interactor factories derive from the generic InteractorFactory(InteractorType) class. The base class will raise a Created event whenever a new interactor has been created.

This means that it is not always necessary to create a new interactor factory in order to customize interactors. It is also possible to subscribe to the Created event of one or more existing factories and modify the interactors that they produce. It is possible for example to add interactor layers in this way.

If this does not suffice, one will need to create one or more custom interactor factories and assign these to the appropriate property of the viewer.

Document Interactor Factories

All document interactor factories derive from the WinFormsDocumentInteractorFactory class. This class provides a CreateDocumentInteractor() virtual that creates new instances of DocumentInteractor. Override this virtual if you want to create your own DocumentInteractor specialization.

Note: The StandardPagesViewer will have its DocumentInteractorFactory property set to an instance of WinFormsStandardDocumentInteractoryFactory. This factory will create new instances of StandardDocumentInteractor. The latter implements the zoom modes of the StandardPagesViewer, and it may be used to implement future extensions of the StandardPagesViewer. We do not recommend assigning a different DocumentInteractorFactory in this case, as this may lead to undefined behavior.

Page Interactor Factories

All page interactor factories derive from the WinFormsPageInteractorFactory class. This class provides several virtuals for creating a customized page interactor:

  • CreatePageInteractor(): Override this virtual if you want to create your own PageInteractor specialization. By default, WinFormsPageInteractorFactory class creates an instance of PageInteractor.
  • CreateBackGroundLayer(): Adds a background layer to a page. By default it adds a layer that just clears the entire page with the BackColor setting of the RenderSettings. Override this method if you want to provide a different background.
  • CreateUnderLayLayer(): Adds an underlay on top of the Background layer. By default, this layer will draw the graphics that are present in the underlay of the page.
  • CreateOverLayLayer(): The factory will always create a PageContentLayer on top of the underlay. The PageContentLayer is responsible for painting the page content. The CreateOverlayLayer() method will add an overlay layer on top of the content layer. By default this layer will draw the graphics that are present in the overlay of the page.
  • CreateForegroundLayer(): Adds a foreground layer to a page. By default it adds a BorderLayer that draws a thin black border around the page.

All virtuals that create layers may also return null. If so, the layer will not be created.

Note: The StandardPagesViewer will have its PageInteractorFactory property set to an instance of WinFormsStandardPageInteractoryFactory. This factory will create new instances of StandardPageInteractor. The latter implements the text selection and annotation selection features of the StandardPagesViewer, and it may be used to implement future extensions of the StandardPagesViewer. We do not recommend assigning a different PageInteractorFactory in this case, as this may lead to undefined behavior.

Annotation Interactor Factories

All annotation interactor factories derive from the WinFormsAnnotationInteractorFactory class. This class provides a large number of virtuals for creating customized annotation interactors. The sections below will treat these in alphabetical order.

  • CreateCheckBoxInteractor: This method will be invoked for each CheckBoxWidget. By default, it will create an instance of the CheckBoxInteractor class.
  • CreateDateTimeTextBoxInteractor: This method will be invoked for each Widget that refers to a DateTimeField. By default it will create an instance of a DateTimeTextBoxInteractor.
  • CreateDropDownListInteractor: This method will be invoked for each Widget that refers to a DropDownListField. By default it will create an instance of a DropDownListInteractor.
  • CreateLinkInteractor: This method will be invoked for each Link. By default it will create an instance of a LinkInteractor.
  • CreateListBoxInteractor: This method will be invoked for each Widget that refers to a ListBoxField. By default it will create an instance of a ListBoxInteractor.
  • CreateMultiLineTextBoxInteractor: This method will be invoked for each Widget that refers to a TextField that has the Multiline property set to true. By default it will create an instance of a MultiLineTextBoxInteractor.
  • CreateNoteInteractor: This method will be invoked for each Note markup. By default it will create an instance of a NoteInteractor.
  • CreateNumericTextBoxInteractor: This method will be invoked for each Widget that refers to a NumericField. By default it will create an instance of a NumericTextBoxInteractor.
  • CreatePasswordTextBoxInteractor: This method will be invoked for each Widget that refers to a PasswordField. By default it will create an instance of a PasswordTextBoxInteractor.
  • CreatePopupInteractor: This method will be invoked for each Markup element that has a Popup, at the moment that the popup is opened. By default it will create an AnnotationInteractor, as there is no default implementation yet for popups.
  • CreatePushButtonInteractor: This method will be invoked for each PushButtonWidget. By default it will create an instance of a PushButtonInteractor.
  • CreateRadioButtonInteractor: This method will be invoked for each RadioButtonWidget. By default it will create an instance of a RadioButtonInteractor.
  • CreateSignatureInteractor: This method will be invoked for each SignatureWidget. By default it will create an instance of a PasswordTextBoxInteractor.
  • CreateSingleLineTextBoxInteractor: This method will be invoked for each Widget that refers to a TextField that has the Multiline property set to false. By default it will create an instance of a SingleLineTextBoxInteractor.
  • CreateStampMarkupInteractor: This method will be invoked for each Stamp markup. By default it will create an instance of a StampInteractor.
  • CreateTextMarkupInteractor: This method will be invoked for each Text markup. By default it will create an instance of a TextMarkupInteractor.
  • CreateUnknownMarkupInteractor: This method will be invoked for each markup element that is not an instance of any of the above. By default this method will create an instance of an UnknownMarkupInteractor.

Note: Although at this moment the StandardPagesViewer does not use a custom AnnotationInteractorFactory it is not recommended to assign a custom one, as future versions may use the AnnotationInteractorFactory to implement new features.

Dragging

Interactors support various types of dragging behavior. Dragging is a more complex operation than ordinary “clicking” as dragging often involves multiple interactors.

Drag Behavior

When a user starts dragging the mouse over a certain interactor, the DragBehavior property of this interactor determines what will happen next. It can have the following values:

  • DragOver: This is the default behavior. Dragging will result in a number of MouseMove events, followed by a MouseClick event and finally, a MouseUp event. These events will all be directed to the interactor in which the mouse button was originally pressed, even if the mouse moves outside the interactor or over one of its children. This behavior is useful in many cases, including selection of text or resizing. Basically, it allows the interactor to “follow” the mouse regardless of where it moves to.
  • DragDrop: This indicates that the interactor can be dragged. In this case, multiple interactors are involved. The source interactor is the one that is being dragged, and the target interactor is the one that it is currently hovering over. Both will received a number of special “Drag” events during dragging. We will have a closer look at this below.
  • DragSelect: If an interactor specifies this behavior, the viewer will drag a “selection rectangle” during dragging. The interactor will first receive a DragSelectMouseDown event, followed by a number of DragSelectMouseMove events, and finally a DragSelectMouseUp event. These events are similar to the ordinary DragOver events, except that they will pass the location and size of the dragged rectangle.

The DragOver behavior is fairly straightforward, so we will not discuss it further. Below, we will have a closer look at the other behavior.

DragDrop

Once a user starts dragging an interactor with DragDrop behavior, a number of event handlers will be invoked for this source interactor, as well as for any target interactor that it gets dragged over. We will consider these handlers below.

Source Events

The following event handlers will get invoked for the source interactor:

  • OnDragBegin: This will be invoked when the user starts dragging this interactor. Dragging can be cancelled by removing the interactor from the DraggedInteractors collection of the event. The AllowedEffect property of the event will be set to All. OnDragBegin may change the Effect property to limit this.
  • OnDragEnd: This event will be invoked when the user releases the mouse button. The effect of the Drag can be cancelled by removing the interactor from the DraggedInteractors collection of the event. The Effect property indicates the effect(s) that the target interactor allows.
  • OnPaint: This method will be invoked to draw the original, undragged interactor. By default, this is the same as the normal representation.
  • OnPaintDrag: This method will be invoked to draw a dragged representation of the interactor. The coordinate system will have its origin translated by the same amount as the user has dragged the interactor. By default, this method draws a transparent “ghost” image of the interactor.

Target Events

The following event handlers will get invoked for any target interactor that the source interactor gets dragged over.

  • OnDragEnter: This will be invoked when the mouse pointer enters the target interactor. This is comparable to OnMouseEnter. The AllowedEffect property of the event contains the value as indicated by the source interactor via OnDragBegin. The Effect property has the value None at this point. If the interactor supports dropping the source interactor within the AllowedEffect possibilities, it should indicate this by modifying the Effects property accordingly. If it fails to do this, it will not be possible to drop the source here.
  • OnDragOver: This will be invoked regularly while the mouse pointer hovers over the target interactor. This is comparable to OnMouseMove. The Effects property will have the value that was left by the last OnDragEnter call, or the previous OnDragOver invocation. OnDragOver may modify this value.
  • OnDragLeave: This will be invoked when the mouse pointer leaves the target interactor. This is comparable to OnMouseLeave.
  • OnDragDrop: This will be invoked when the source interactor gets dropped on this interactor. It will be invoked just after OnDragEnd has been invoked for the source. Normally, OnDragDrop will perform the actual drag-and-drop action.

Please note that a DragDropEvent contains a collection of dragged source interactors. Normally, if a user starts dragging an interactor, it will contain only this interactor. If however the dragged interactor is part of the current selection, all interactors in it will be dragged. Also see the Selection property of the viewer.

DragSelect

When a user starts dragging over an interactor with DragSelect behavior, the viewer will automatically draw a selection rectangle. While this rectangle gets dragged, the following event handlers may be invoked for the interactor in which dragging started.

  • DragSelectMouseDown: This will be invoked at the moment that the mouse starts being dragged. This event will not occur if the mouse just gets clicked. The mouse must actually move while the left mouse button is kept down.
  • DragSelectMouseMove: This will be invoked any time the size of the dragged rectangle changes. The DragSelectEvent will pass a rectangle that provides the size of the selection rectangle.
  • DragSelectMouseUp: This will be invoked when the mouse button gets released.

JavaScript

The professional edition of PDFControls.NET supports executing JavaScript. This will work out-of-the-box for many simple scripts, but some JavaScript objects need to be customized by the application that they run in, in order to provide the correct functionality.

The way that this works, is basically by registering a custom JavaScript factory that creates custom JavaScript objects. The code below for example registers ‘MyJavaScriptFactory’ and has it create a custom App object.

JavaScriptFactory.Register(new MyJavaScriptFactory());

…

private class MyJavaScriptFactory : JavaScriptFactory
{
  protected override App CreateApp()
  {
    return new MyApp();
  }
}

The following sections will discuss the objects that we allow to be customized (such as App). For details about these objects please see the Acrobat JavaScript reference, which can be found at Adobe.

Global

The Global object allows data to be shared between documents and have data persisted across “sessions”.

In order to provide this functionality, the application will need to provide a mechanism for persisting data. PDFControls.NET does not implement this by default, as this would require write access to some storage medium.

The TallComponents.PDF.Javascript.Scripting.Global object allows the following methods to be overridden:

  • GetPersistedNames(): returns the collection of variable names that have been persisted during all previous sessions. By default, this method returns an empty collection.
  • GetPersistedValue(name): returns the value of a previously persisted variable. By default, this method returns null for all passed names.
  • Initialize(): initializes global storage. This method will be called once, before executing any JavaScript. Override this method to add custom initialization code. By default this method defines a set of standard JavaScript methods and objects. Make sure to call base in your implementation.
  • SetPersistedValue(name, value): persists the given variable name with the given value. The type of the variable must be Boolean, Number or String. The latter is a requirement of the Acrobat JavaScript reference. By default this method has no effect.

App

The App object has the same lifetime as the application, and amongst other things, it defines a number of GUI-related functions. The TallComponents.PDF.Javascript.Scripting.App class allows the following methods to be overridden:

  • Alert(message, icon, buttons, title, document, checkbox): this method implements the App.alert functionality. It should display an alert dialog on the screen. See the Acrobat JavaScript reference for details. By default this method does nothing, and returns AlertResult.OK.
  • LaunchURL(url, newFrame): this method implements the App.launchURL functionality. It should launch a browser window with the given URL. See the Acrobat JavaScript reference for details. By default this method does nothing.

Doc

A Doc object will be created for each document, and it has the same lifetime. It offers the interface between JavaScript and the document. The TallComponents.PDF.Javascript.Scripting.Doc class allows the following methods to be overridden:

  • ExportAsFDF(path, formData): this method implements the doc.exportAsFDF functionality. See the Acrobat JavaScript reference for details. Except for the path, all other JavaScript parameters have been accounted for in the formData object, for as far as supported (see the PDFControls.NET reference for details). This method should write the provided form data to disk. By default, this method does nothing, as this would require write access to the system.
  • ImportAnFDF(path): this method implements the doc.importAnFDF functionality. See the Acrobat JavaScript reference for details. This method should create an FdfFormData instance from the given path and return it as its result. The default implementation will try to do this. It will return null if this fails. In order to provide an implementation that adheres to the PDF specification, one should override this method and invoke base.ImportAnFDF(path) first. If this returns null, one should allow the user to select a file.
  • ImportAnXFDF(path): this method implements the doc.importAnXFDF functionality. See the Acrobat JavaScript reference for details. This method should create an XfdfFormData instance from the given path and return it as its result. The default implementation will try to do this. It will return null if this fails. In order to provide an implementation that adheres to the PDF specification, one should override this method and invoke base.ImportAnXFDF(path) first. If this returns null, one should allow the user to select a file.
  • ImportTextData(path): this method implements the doc.importTextData functionality. See the Acrobat JavaScript reference for details. This method should populate the fields of the document as indicated in the file. By default this method does nothing, and returns ImportTextDataResult.CannotOpenFile.
  • SubmitForm(options): this method implements the doc.submitForm functionality. See the Acrobat JavaScript reference for details. The parameters of this method are passed in a SubmitFormOptions instance, for as far as supported. See the PDFControls.NET reference for details. This method has a default implementation for posting FDF to the URL that is specified in the options. Other variants have no default implementation yet, but this may be provided in future updates.

Note that SubmitForm has a default implementation. This means that form data will be submitted to the specified server by default. This may be a privacy concern. If so, please override this method and provide a custom implementation.

Console

The console object gives access to a console window. This allows debug information to be printed for example. PDFControls.NET provides no default implementation. The TallComponents.PDF.Javascript.Scripting.Console class allows the following methods to be overridden:

  • Clear(): implements the console.clear functionality. See the Acrobat JavaScript reference for details. This method has no default implementation.
  • Hide(): implements the console.hide functionality. See the Acrobat JavaScript reference for details. This method has no default implementation.
  • PrintLn(): implements the console.printLn functionality. See the Acrobat JavaScript reference for details. This method has no default implementation.
  • Show(): implements the console.show functionality. See the Acrobat JavaScript reference for details. This method has no default implementation.

Messages

This object is not present in the Acrobat JavaScript object model. PDFControls.NET defines it, so that it becomes possible to provide localized message strings. The TallComponents.PDF.Javascript.Scripting.Messages class allows the following methods to be overridden:

  • ClockAm: Override this string to provide a localized version of the string “am”.
  • ClockPm: Override this string to provide a localized version of the string “pm”.
  • DoesNotMatchFieldFormat: Indicates that a field value does not match its format. Override this string to provide a localized version of the string “The value entered does not match the format of the field”.
  • InvalidDateForField: Indicates that an invalid date is being used for a particular field. Override this string to provide a localized version of the string “Invalid date/time: please ensure that the date/time exists. Field “. Please note that the field name will be printed after this string.
  • InvalidMonth: Indicates that an invalid month index is being used. Override this string to provide a localized version of the string “** Invalid **”
  • MonthsFull: Override this string array to provide an array of localized full names for all months, e.g. [“January”, “February”, …]. The array must have size 12.
  • MonthsShort: Override this string array to provide an array of localized short names for all months, e.g. [“Jan”, “Feb”, …]. The array must have size 12.
  • MustBeBetweenOrEqual: Indicates that a value falls outside particular bounds. Override this string to provide a localized version of the string “Invalid value: must be greater than or equal to %s and less than or equal to %s.”. Please note that the bounds are provided as an argument to this string and will be inserted at the locations of ‘%s”.
  • MustBeGreaterThanOrEqual: Indicates that a value is too small. Override this string to provide a localized version of the string “Invalid value: must be greater than or equal to %s.”. Please note that the lower bound is provided as an argument to this string and will be inserted at the location of ‘%s”.
  • MustBeLessThanOrEqual: Indicates that a value is too big. Override this string to provide a localized version of the string “Invalid value: must be less than or equal to %s.”. Please note that the upper bound is provided as an argument to this string and will be inserted at the location of ‘%s”.
  • MustMatchFieldFormat: Indicates that a field value does not match a particular format. Override this string to provide a localized version of the string “should match format “. Please note that the required format will be printed directly after this string.

Please note that these are JavaScript strings. Some of these strings have escape codes that must be present in order for the message text to be constructed correctly.

Document Structure

The top-level structure of a PDF document is shown below.

Document Structure

You can either open an existing document or create a new document as shown in the following code sample:

using System;
using System.IO;
using TallComponents.PDF;

// create a new empty document
Document document1 = new Document();

// open an existing document
using ( FileStream file = new FileStream(
    "report.pdf",
       FileMode.Open, FileAccess.Read ) )
{            
Document document2 = new Document( file );
}

Code sample: Create a new document or open an existing document

Pages

The property Pages of class Document allows you to enumerate, remove and insert pages. Chapter Actions discusses pages in more detail.

Fields

The property Fields of class Document allows you to enumerate, remove and insert form fields. Fields are discussed in more detail in Forms.

Bookmarks

A PDF document has a bookmarks tree which is also known as the document outline or table of contents. The top-level bookmarks can be accessed through the Document.Bookmarks property. From here you can enumerate the entire tree. You can also add, insert and remove bookmarks and modify bookmark properties. Bookmarks are discussed in more detail in Navigation.

Viewer Preferences

The viewer preferences define how a PDF reader should display a PDF document initially. Viewer preferences are discussed in more detail in Navigation.

JavaScript

Just like you can embed JavaScript in an HTML page for things like input validation and dynamically changing element properties, you can embed JavaScript in a PDF document for similar purposes. In fact, if you set the input mask on a text field, some JavaScript will be inserted on the fly to make this happen.

Document Info

The document info consists of simple properties such as Author, Creator and Subject. The following snippet dumps part of the document info of a given document to the Console:

// open file 
using ( FileStream file = new FileStream(
  "my.pdf", FileMode.Open, FileAccess.Read ) )
{
  // open PDF document
  Document document = new Document( file );
  // dump info to console
  Console.WriteLine("author: {0}", document.DocumentInfo.Author );
  Console.WriteLine("subject: {0}", document.DocumentInfo.Subject );
  Console.WriteLine("title: {0}", document.DocumentInfo.Title );
}

Code sample: Dump the PDF document info to the console (DumpInfo)

Metadata

Metadata schemas are embedded as XML documents. This data is often inserted by PDF producers to preserve metadata such as high-level layout information. A metadata schema is a collection of name-value pairs. A value is of type MetadataValue which is an abstract base class. The only concrete specializations are MetadataString and UnsupportedMetadataValue. We will add support for other types than string in future updates.

Security

The Document.Security property gives you access to the security settings of the document. The property is of type Security which is an abstract base class. The only concrete derived class is PasswordSecurity. We anticipate to provide more and allow you to write custom security handlers – hence this approach. You can assign securty settings like this:

// open file 
using ( FileStream fileIn = new FileStream( 
  "guide.pdf",
  FileMode.Open, FileAccess.Read ) )
{
  // open source PDF
  Document document = new Document( fileIn );

  // assign owner and user passwords
  PasswordSecurity security = new PasswordSecurity();
  security.OwnerPassword = "1234";
  security.UserPassword = "GoodNews";
  document.Security = security;

  // save as a new PDF document
  using ( FileStream fileOut = new FileStream( 
    "guide_protected.pdf", 
    FileMode.Create, FileAccess.Write ) )
  {
    document.Write( fileOut );
  }
}

Code sample: Save PDF document with passwords (AssignPassword)

Navigation

In addition to the standard navigation means of a PDF reader application, PDF allows you to include navigation elements such as bookmarks and links. Central to navigation is the Destination concept. A Destination encapsulates all information that a reader application needs to jump to a location inside or outside a PDF document.

Destination

A destination can either be internal or remote. An internal destination is a location inside the current PDF document. A remote destination points to a location inside another PDF document. Finally, a destination can be named. A named destination can be resolved to an internal destination through the Document.NamedDestinations property. This is a collection that maps names to internal destinations. Destination is an abstract base class. InternalDestination, RemoteDestination and NamedDestination are concrete specializations. See class hierarchy below.

Destination Class Hierarchy

Internal destination

The following code snippet adds a link to the first page that points to the third page.

// create the internal destination object:
InternalDestination destination = new InternalDestination();
destination.Page = document.Pages[2];
destination.PageDisplay = PageDisplay.FitEntire;
// add the destination to a link via a GoToAction:
GoToAction action = new GoToAction( destination );
Link link = new Link( left, top, width, height );
link.MouseUpActions.Add( action );
// add the link to the first page:
document.Pages[0].Links.Add( link );

Code sample: Add a cross-reference link inside the same document (InternalDest)

Remote destination

The following code snippet adds a link to the first page that points to the third page of another PDF document. It also instructs the PDF reader to open the document in a new window.

// create the remote destination:
RemoteDestination destination = new RemoteDestination(); 
destination.FileSpecification = @"c:\temp\Upgrading to PDFKit.NET 2.0.pdf";
destination.PageIndex = 2; // third page
destination.PageDisplay = PageDisplay.FitEntire;
destination. WindowBehavior = WindowBehavior.NewWindow;
// add the destination to a link via a GoToAction:
GoToAction action = new GoToAction( destination );
Link link = new Link( left, top, width, height );
link.MouseUpActions.Add( action );
// add the link to the first page:
document.Pages[0].Links.Add( link );

Code sample: Add a link to an external PDF document (RemoteDest)

Named destination

The following code snippet dumps all named destination in a given document and reports to what page the associated internal destination points.

foreach ( string name in document.NamedDestinations.Names )
{
   // resolve name to internal destination
   InternalDestination destination = document.NamedDestinations[ name ];
   Console.WriteLine( "{0} points to page {1}",
      name, destination.Page.Index );
}

Code sample: Dump named destinations

Links are areas that navigate the PDF reader to another location. In PDF, links are implemented as Link annotations. A link annotation occupies a rectangle on a page and when clicked a sequence of PDF actions is executed. Although this can be any sequence of any actions, it is most often a single GoToAction that points to a given Destination. Code sample Add a cross-reference link inside the same document and Code sample Add a link to an external PDF document demonstrate how to create a link and associate it with a destination.

Bookmarks

A PDF document has a bookmarks tree which is also known as the document outline or table of contents. See image to the right. The top-level bookmarks can be accessed through the Document.Bookmarks property. From here you can enumerate the entire tree. You can also add, insert and remove bookmarks and modify bookmark properties.

The following code splits a document into parts according to its top-level bookmarks. Each part is named according to the title of the bookmark.

Bookmarks

BookmarkCollection bookmarks = document.Bookmarks;
for ( int index=0, fromPage=0; index<bookmarks.Count; index++ )
{
   // determine last page of next part
   int toPage = -1;
   Bookmark bookmark = null;
   if ( index == bookmarks.Count-1 )
   {
      // last bookmark - append all remaining pages
      toPage = document.Pages.Count;
   }
   else
   {
      // not the last bookmark - append up to the next bookmark
      bookmark = bookmarks[index+1];
      GoToAction action = bookmark.Actions[0] as GoToAction;
      if ( null != action )
      {
         InternalDestination destination = action.Destination as
            InternalDestination;
         if ( null != destination )
         {
            toPage = destination.Page.Index;
         }
      }
   }

   // create a new part
   if ( -1 != toPage && null != bookmark )
   {
      Document part = new Document();
      for ( int pageIndex=fromPage; pageIndex<toPage; pageIndex++ )
      {
         part.Pages.Add( document.Pages[pageIndex].Clone() );
      }
      using ( FileStream fileOut = new FileStream( 
         bookmark.Title + ".pdf", FileMode.Create, FileAccess.Write ) )
      {
         part.Write( fileOut );
      }
      fromPage = toPage;
   }
}

Code sample: Split a document according to its top-level bookmarks (SplitByBookmark)

Viewer Preferences

The viewer preferences define how a PDF reader should display a PDF document initially. The following code snippet shows you how to open, change and save a PDF so that the next time you open it, it will be displayed without menu and toolbar.

// open file 
using ( FileStream fileIn = new FileStream( 
  "guide.pdf",
  FileMode.Open, FileAccess.Read ) )
{
  // open source PDF
  Document document = new Document( fileIn );

  // assign custom viewer preferences
  ViewerPreferences preferences = new ViewerPreferences();
  preferences.HideMenubar = true;
  preferences.HideToolbar = true;
  document.ViewerPreferences = preferences;

  // save as a new PDF document
  using ( FileStream fileOut = new FileStream( 
    "guide_nomenunotoolbar.pdf", 
    FileMode.Create, FileAccess.Write ) )
  {
    document.Write( fileOut );
  }
}

Code sample: Save PDF document with modified viewer preferences (ViewerPrefs)

Annotations

An annotation is a rectangular area on a PDF page that the user can interact with. Examples are ‘links’, ‘sticky notes’ and ‘widgets’. All annotation classes and related types live in the TallComponents.PDF.Annotations namespace and nested namespaces.

Annotation

Annotation is the abstract base class of all annotation classes. The figure below shows the class hierarchy of the annotations that we currently support:

Annotation Class Hierarchy

Class Annotation has the following members that are common to all annotation types:

Annotation members

Public properties
PageThe page that contains this annotation. Read-only.
Left, Bottom, Right, Top, Width and HeightThe position and size in page space. Right and Top are read-only.
BorderWidthWidth of the border. 0 means no border.
BorderColorColor of the border (must be RgbColor for non-widget annotation).
BorderStyleStyle of the border (Solid, Dashed, etc.)
InvisibleThe annotation is not visible on screen.
LockedWhether the annotation is locked in the viewer application.
PrintThe annotation is printed.
Public methods
FlattenThe page that contains this annotation. Read-only.
AcceptSupports the Visitor pattern.

Widget

As a special case, a widget annotation (or widget) is the visual representation of a field that provides interactive access to the field value. In theory, a field can have multiple widgets. Widgets will be discussed in more detail in Forms.

The link annotation is a clickable area that executes a sequence of actions. To jump to a destination you add a single action of type GoToAction. To jump to a URL you add a single action of type UriAction. Of course you can add any number of actions of any type.

In addition to the members inherited from Annotation, Link has the following specific members:

Members specific to Link

Public properties
HighlightStyleThe visual effect that is used when the mouse is pressed inside the annotation area.
MouseUpActionsThe sequence of actions to execute when the mouse button is released in the Link area.

Code sample Add a cross-reference link inside the same document shows how to add a link to a page that points to another page. Code sample Add a link to an external PDF document shows how to add a link to a page that points to an external PDF document.

Markup

Markup annotations are used to mark-up a document. In a typical mark-up scenario, the author of a document asks a peer to review the document. The peer will add comments at specific location in the document, e.g. to mark-up a typo or to mark-up a figure that is not clear enough. Mark-ups can take several visual forms of which a simple textual note is the most common one.

Markup is the abstract base class of all markup annotations. It has a number of properties that are common to all markup annotations.

Members specific to Markup

Public properties
Replies, InReplyToRespectively, all mark-ups that reply to this mark-up and the mark-up to which this mark-up replies. These two properties allow you to create a complete thread of mark-ups. InReplyTo is read-only.
TextThe text of this mark-up. This text may contain simple formatting tags such as bold.
PopupThe pop-up that displays the text of this mark-up.
OpacityThe opacity of this mark-up (0 means fully transparent, 255 means fully opaque).
Author, CreationDate and SubjectThe author, creation date and subject of this mark-up.

Note

A note is the most common mark-up. The class Note is a concrete specialization of the abstract base class Markup. It has the following specific members:

Members specific to Note

Public properties
IconNameViewer applications have predefined icons for at least the following names: Comment, Key, Note, Help, NewParagraph, Paragraph and Insert.
StateModelState model of this mark-up. Existing state models:

  • Marked: according to this model, a mark-up is marked or not.
  • Review: according to this model, a mark-up has one of the following states: None, Accepted, Rejected, Cancelled or Completed.
  • Migration: according to this model, a mark-up has one of the following states: None, Confirmed or NotConfirmed.
  • None. This mark-up has no state model.
MarkedMeaningful if state model is Marked.
ReviewStateMeaningful if state model is Review.
MigrationStateMeaningful if state model is Migration.

This is a special annotation that displays the text of a markup as a pop-up message. The pop-up allows the user to edit the text. A pop-up is associated with a markup through its Markup property. A markup is associated with a popup through its Popup property. Note that the displayed text is a property of the markup, not of the pop-up.

In addition to the members inherited from Annotation, Popup has the following specific members:

Members specific to Popup

Public properties
OpenWhether the pop-up should initially be displayed open.
MarkupThe Markup to which this pop-up belongs. Read-only.

The following code sample creates a new document with a single page. The page has a note with an open pop-up noting that the page is empty.

// create a new document with a single empty page
Document document = new Document();
Page page = new Page( PageSize.Letter );
document.Pages.Add( page );

// add a new note
Note note = new Note( 300, 300, 10, 10 );
note.Text = "This page is empty.";
note.IconName = "Note";
page.Markups.Add( note );

// attach an open popup to the note
Popup popup = new Popup( 320, 350, 200, 100 );
popup.Open = true;
note.Popup = popup;

// save the new document
sing ( FileStream file = new FileStream( 
   @"..\..\note.pdf", 
   FileMode.Create, FileAccess.Write ) )
{
   document.Write( file );
}

Code sample: Create a new document with a single page, a note and a popup (AddNote)

Created From Code Sample

Created from Code sample Create a new document with a single page, a note and a popup

Forms

PDF documents that include fillable fields are referred to as PDF forms. A typical example is an IRS tax form such as the W-4.

Fields

In general, a PDF form has different types of fields. E.g. text fields to enter you first and last name, a group of radio buttons to select your marital status and a checkbox to indicate whether you filed the same tax form last year.

The class TallComponents.PDF.Forms.Fields.Field represent a PDF form field. It is an abstract base class that has the following inheritance hierarchy:

Fields Hierarchy

Fields hierarchy (UnknowmField and UnknowBarcodeField omitted)

All fields in a PDF document can be accessed through the Document.Fields property. It returns an instance of type TallComponents.PDF.Forms.Fields.FieldCollection. You may be surprised that the collection of fields is contained by a Document instead of by a Page. This is explained in the next section.

Widgets

A widget is an annotation that provides the visual representation of a field. It also allows the user to interact with the field, e.g. to enter a text or select a date from a calendar control. In general, a field can have multiple widgets but in most cases it only has one. A field is contained by a document. A widget is contained by a page.

Document Fields Pages And Widgets

Document, fields, pages and widgets

The following diagram shows the Widget inheritance hierarchy.

Widget Inheritance Hierarchy

Widget inheritance hierarchy

As said, a field is associated with one or more widgets. The following table shows what type of field corresponds to what type of widget.

Correspondence between Field and Widget type

FieldWidgetRemarks
SignatureFieldSignatureWidgetThe SignatureWidget has additional properties to customize the appearance, e.g. display text only, image only, both. Display the date or not, display the reaon or not, etc.
PushButtonFieldPushButtonWidgetThe PushButtonWidget has additional properties that allow you to specify a label, an icon and how the label and the icon should laid out (label above icon, icon only, label only, etc.).
CheckBoxFieldCheckBoxWidgetThe CheckBoxWidget has an additional appearance to specify the check mark (diamond, check, start, etc.).
RadioButtonFieldRadioButtonWidgetJust like the CheckBoxWidget, the RadioButtonWidget has a property to specify the appearance of the chek mark. In addition, it has a property Option that represents one of the radio button options that can be selected.
DateTimeField, DropDownListField, ImageField, ListBoxField, NumericField, PasswordField, TextField, Code128BarcodeField, Code2of5InterleavedBarcodeField, Code3of9BarcodeFieldWidgetThese fields are all visually represented by the same Widget class.

All widget types have a common base class Widget. Widget in turn inherits from Annotation, see Annotations. The table below lists the members that are specific to Widget annotations.

Public Widget members

Public properties
FieldThe field that is associated with this widget. Read-only.
Font, FontSize, TextColor, HorizontalAlignment, VerticalAlignmentProperties that specify how text is displayed.
Invisible, Orientation, BackgroundColorAppearance properties.
PersistencyAllows you to specify how a widget is persisted when the document is written. You can leave a widget unchanged, you can remove it and finally it can be flattened. This means that after writing the document, the widget is replaced by static, non-interactive content.
GotFocusActions, LostFocusActionsActions to be executed when the widget gets or loses keyboard focus.
MouseDownActions, MouseUpActions, MouseEnterActions, MouseExitActionsActions to be executed per mouse event.

The following code snippet creates new document with a single page and a text field:

// create new document
Document document = new Document();
Page page = new Page( PageSize.Letter );
document.Pages.Add( page );

// add a new text field and add it to the new document
TextField textField = new TextField( "text1" );
textField.Value = "Hello!";
document.Fields.Add( textField );

// add a new widget and connect the widget to the text field and the page
Widget widget = new Widget( 200, 400, 200, 50 );
widget.TextColor = RgbColor.Red;
widget.BorderColor = RgbColor.Black;
widget.BorderStyle = BorderStyle.Solid;
widget.BorderWidth = 2;
textField.Widgets.Add( widget );
page.Widgets.Add( widget );

// save the new document
using ( FileStream file = new FileStream( 
   @"..\..\textfield.pdf", 
   FileMode.Create, FileAccess.Write ) )
{
   document.Write( file );
}

Code sample: Create a new document with a single page and a text field (AddTextField)

Text Field As Created By Code Sample

Textfield as created by Code sample Create a new document with a single page and a text field

Fill Fields

Filling a form field is as simple as setting the Value property of a field. Depending on the type of field, special XxxValue properties exist. Here are some code samples that show how to fill form fields:

using ( FileStream fileIn = new FileStream( 
   @"fields.pdf", FileMode.Open, FileAccess.Read ) )
{
   Document document = new Document( fileIn );

   TextField textField = document.Fields[ "Text1" ] as TextField;
   textField.Value = "Hello";

   CheckBoxField checkBox = document.Fields[ "Check Box2" ] as CheckBoxField;
   checkBox.CheckBoxValue = CheckState.On;

   RadioButtonField radioButton = document.Fields[ "Radio Button4" ] 
      as RadioButtonField;
   radioButton.RadioButtonValue = radioButton.Options[1]; // second option

   ListBoxField listBox = document.Fields[ "List Box7" ] as ListBoxField;
   listBox.ListBoxValue = 
      new ListOption[] { listBox.Options[1] }; // second option

   DropDownListField dropDown = document.Fields[ "Combo Box8" ] 
      as DropDownListField;
   dropDown.DropDownListValue = dropDown.Options[1]; // second option

   using ( FileStream fileOut = new FileStream( 
      @"out.pdf", FileMode.Create, FileAccess.Write ) )
   {
      document.Write( fileOut );
   }
}

Code sample: Fill fields (FillFields)

Form Data

At each point a field has a value. This can be text entered into a text field or the signature state of a signature field. The collection of all values of all fields of a document is referred to as form data. Form data can be persisted in different formats (e.g The Acrobat JavaScript Scripting Reference mentions a form data format called ‘XFD’. We have no idea what this format is and we suspect that it is a typo (from XFDF which is included as well)), the most known being Form Data Format (FDF). The class FormData is the abstract base class of all classes that each represent one format of persisted form data. The following inheritance hierarchy shows the different FormData classes.

FormData Inheritance Hierarchy

FormData inheritance hierarchy

Export

Exporting form data in any of the supported format is really easy. Simply call the method Document.Export and specify what format you want:

using ( FileStream pdfFile = new FileStream( 
   "fw4.pdf", FileMode.Open, FileAccess.Read ) )
{
   Document document = new Document( pdfFile );
   // fill two fields
   TextField firstName = document.Fields["f1_09(0)"] as TextField;
   firstName.Value = "Chris";
   TextField lastName = document.Fields["f1_10(0)"] as TextField;
   lastName.Value = "Sharp";
   // export as FDF
   FdfFormData fdf = document.Export( FormDataFormat.Fdf ) as FdfFormData;
   fdf.Path = "fw4.pdf";
   // save FDF
   using ( FileStream fdfFile = new FileStream( 
      "fw4.fdf", FileMode.Create, FileAccess.Write ) )
   {
      fdf.Write( fdfFile );
   }
}

Code sample: Export form data as FDF (ExportFdf)

Import

Importing existing form data into a PDF document is just as easy. Simply create a FormData instance from persisted form data (can be in memory) and pass this instance to Document.Import:

// open PDF file
using ( FileStream fileIn = new FileStream( 
   "fw4.pdf", FileMode.Open, FileAccess.Read ) )
{
   Document document = new Document( fileIn );

   // open FDF file
   using ( FileStream fdfFile = new FileStream( 
      "fw4.fdf", FileMode.Open, FileAccess.Read ) )
   {
      FdfFormData fdfData = new FdfFormData( fdfFile );
      // import FDF data
      document.Import( fdfData );

   // save modified PDF file 
   using ( FileStream fileOut = new FileStream( 
      "fw4_afterimport.pdf", FileMode.Create, FileAccess.Write ) )
   {
      document.Write( fileOut );
   }
}

Code sample: Import form data from FDF (ImportFdf)

Adobe LiveCycle Designer Support

Dynamic XFA documents are not supported. If you open such a document with, an UnsupportPdfException is thrown. Static XFA documents are supported.

Numeric field, DateTime field, Image field and Barcode field are represented by the corresponding classes in the TallComponents.PDF.Forms.Fields namespace. These field types cannot be created. It is not possible to add these field types to an existing non-XFA (classic) PDF document. It is not possible to add new fields/widgets to static XFA document.

The property Document.DocumentType reflects the type of PDF document. Possible values are: Classic, StatcXfa and DynamicXfa. The enum value DynamicXfa will never be returned because an exception is already thrown at construction time.

To summarize, we support Adobe LiveCycle Designer document with the following restrictions:

  • We support static XFA only (no dynamic XFA).
  • We do not support repeated subforms. See ‘repeat subform for each data item’ flag in Binding properties.
  • You cannot add new fields/widgets.
  • You cannot remove fields by calling the FieldCollection.Remove() method. You can however remove fields by setting widget.Persistency to WidgetPersistency.Flatten or WidgetPersistency.Remove.
  • When adding a page from a XFA document to an other document, the XFA fields are automatically converted to classic types (e.g. NumericField will be TextField).
  • We do not support data-bindings. The default binding must be ‘Normal’. You can set field values through property ValueField.Value.
  • We do not support events and script objects/variables.

The following table describes the relationship between the different versions of the Adobe LiveCycle Designer product, the different versions of the XFA format and the different versions of the Adobe PDF Reader and which ones are supported by PDFKit.NET 2.0 and 3.0.

Adobe (LiveCycle) DesignerProduces XFA versionRequires Adobe PDF ReaderRequires PDFKit.NET
6.02.06.022.0
7.02.16.022.0
7.0 BETA2.27.052.0
7.12.47.052.0
8.02.58.02.0
8.12.68.13.0
8.1.22.78.23.0
ES 8.22.89.03.0
No public release3.09.1Not supported yet

Digital Signatures

Digitally signed PDF documents (or messages in general) include two parties: the sender and the recipient. The sender applies a digital signature using a private key. It is assumed that only the sender has access to the private key and therefore it represents the identity of the sender. Using the public key, the recipient can confirm with high confidence that the signature was applied with the sender’s private key and so by the only person who has access to the private key. The recipient can also be confident that the document has not been modified since it was signed.

Signature Encodings

This application allows you to sign, verify and validate signature fields using all standard signature encodings. These encodings are:

  • adbe.x509.rsa_sha1 (PKCS #1)
  • adbe.pkcs7.detached (PKCS #7)
  • adbe.pkcs7.sha1 (PKCS #7)

Verification

Verification is the process of determining whether the data that has been signed, has not been changed after it has been signed. If the signed data has not been modified after signing, the signature is said to be verified. The following code opens a PDF document and dumps the verification status of each signature field:

using ( FileStream file = new FileStream(
   "f1040a_signed.pdf", FileMode.Open, FileAccess.Read ) )
{
   Document document = new Document( file );
   foreach ( Field field in document.Fields )
   {
      // retrieve signature field
      SignatureField signature = field as SignatureField;
      if ( null != signature  && signature.IsSigned )
      {
         // verifiy signature using standard signature handler
         bool verified = signature.Verify();
         Console.WriteLine( "signature {0} {1} been verified",
            signature.FullName,
            verified ? "has" : "has NOT" );
      }
   }
}

Code sample: Verify all fields (Verify)

You may wonder why we chose a parameterless method Verify, instead of a more elegant getter Verified. The reason for having a method is that we offer 2 more overloads that let you pass a custom signature handler or a signature handler factory.

Updates

It is possible to change a PDF document, e.g. by filling out fields, and to save the changes incrementally. If the document was signed prior to saving the incremental changes, then the signature field still verifies successfully because the data that has been signed has not changed. The property SignatureField.DocumentModifiedAfterSigning lets you check whether incremental changes have been added to the document as an update.

Save Changes Incrementally As An Update

Save changes incrementally as an update

Figure 6 -10 shows how changes are saved incrementally as an update. Update 1 remains unchanged while Update 2 includes all changes. If Update 1 was signed, that signature will still verify successfully after saving because the update has not changed. After saving, the property SignatureField.DocumentModifiedAfterSigning returns false, while it returns true prior to saving.

Validation

While verification is an objective process, validation is not because it involves trust. After verification succeeds, the next step is to decide whether the signer can be trusted (or whether the identity is known). If so, the signature is said to be validated, otherwise it is not. This step is to be executed by your own code by inspecting the SignatureField.Certificates property.

Signing

The following code sample shows how to digitally sign a PDF document. The signature field is named “SignHere”. The certificate is stored inside the file “ChrisSharp.pfx”. This file was exported from the Windows key store. The password of the key store is “Sample”.

  // open PDF form with signature field      
using ( FileStream inFile = new FileStream( 
   "f1040a.pdf", FileMode.Open, FileAccess.Read ) )
{
   Document document = new Document(inFile);

   // open certicate store.
   Pkcs12Store store = null;
   using (FileStream file = new FileStream( 
      "ChrisSharp.pfx", FileMode.Open, FileAccess.Read))
   {
      store = new Pkcs12Store( file, "Sample" );
   }

   // let the class factory decide which type should be used.
   SignatureHandler handler = StandardSignatureHandler.Create( store );

   // sign signature field
   SignatureField field = document.Fields[ "SignHere" ] as SignatureField;
   field.SignatureHandler = handler;

   // set optional info.
   field.ContactInfo = "+31 (0)77 4748677";
   field.Location = "The Netherlands";
   field.Reason = "I fully agree!";

   // write signed document to disk. signing requires read-write file access
   using ( FileStream outFile = new FileStream( 
      "f1040a_signed.pdf", FileMode.Create, FileAccess.ReadWrite ) )
   {
      document.Write( outFile );
   }
}

Code sample: Digitally sign a PDF document (Sign)

Signature Field States

A signature field can have different states. These are shown in the next figures.

IsSigned FalseIsSigned == false;

IsSigned True Verify FalseIsSigned == true; Verify() == false;

IsSigned True Verify True DocumentModifiedAfterSigning False Not TrustedIsSigned == true; Verify() == true;

IsSigned True Verify True DocumentModifiedAfterSigning True Not TrustedIsSigned == true; Verify() == true;

IsSigned True Verify True DocumentModifiedAfterSigning False TrustedIsSigned == true; Verify() == true;

IsSigned True Verify True DocumentModifiedAfterSigning True TrustedIsSigned == true; Verify() == true; DocumentModifiedAfterSigning ==true; trusted

Actions

PDF allows you to associate actions with events. These actions are provided through the Action class and its specializations.

The following actions are supported:

  • FormAction: Submit or clear form data
  • GoToAction: Jump to a destination in the same document or to a page in another PDF document
  • HideAction: Show or hide an annotation
  • ImportDataAction: Import an FDF file
  • JavaScriptAction: Perform scripted operations against the document (Professional edition only)
  • LaunchAction: Launch an application
  • NamedAction: Execute a named action
  • UriAction: Jump to a URI

Each action type is discussed in detail below.

Events

It is the responsibility of the PDF reader to execute the actions that are associated with an event whenever the event occurs. The following properties are of type Action. The properties correspond to events.

  • Document.OpenActions: These actions are executed after the document has been opened
  • Document.AfterPrint: These actions are executed after the document has been printed
  • Document.AfterSave: These actions are executed after the document has been saved
  • Document.BeforeClose: These actions are executed before the document is closed
  • Document.BeforePrint: These actions are executed before the document is printed
  • Document.BeforeSave: These actions are executed before the document has been saved
  • Link.MouseUpActions: These actions are executed when the mouse button is released while the pointer is inside the link
  • Widget.GotFocusActions: These actions are executed when the widget receives keyboard focus
  • Widget.LostFocusActions: These actions are executed when the widget loses keyboard focus
  • Widget.MouseDownActions: These actions are executed when the mouse button is pressed while the pointer is inside the widget
  • Widget.MouseEntrerActions: These actions are executed when the mouse pointer enters the widget
  • Widget.MouseExitActions: These actions are executed when the mouse pointer leaves the widget
  • Widget.MouseUpActions: These actions are executed when the mouse button is released while the pointer is inside the widget
  • Bookmark.Actions: These actions are executed when the bookmark is selected

Note that all properties are of type ActionCollection. All actions in the collection are executed in order.

FormAction

FormAction is the abstract base class of the following actions:

  • ResetFormAction: reset all form fields in the document to their default value
  • SubmitFormAction: Submits the form data to a given destination. The URL and submit options such as the format can be specified as properties of this action.

Note that all properties are of type ActionCollection. All actions in the collection are executed in order.

GoToAction

The GoToAction jumps to a destination. The destination is set by assigning the GoToAction.Destination property.

The following types of destinations exist.

Internal destination

The InternalDestination points to a page inside the same document. It is possible to specify the exact location of the viewer, the zoom factor and the way that the page is displayed.

Remote destination

The RemoteDestination points to a page in an external PDF document. It is specified by the path of the PDF document and a page index. You can also specify additional settings such as zoom factor and the exact scroll position after jumping to the location.

Named destination

Class Document has a property called NamedDestinations which is a collection of name-destination pairs. The NamedDestination class lets you specify a destination by specifying the name of a destination in this collection.

HideAction

This action hides or shows one or more annotations or fields.

ImportDataAction

This action lets you import an FDF file by specifying the path.

JavaScriptAction

This action executes JavaScript against the current document. A PDF document is fully scriptable on the client side. Typical usage includes setting a text field value after clicking a button or showing/hiding a field after entering/leaving a form field. Here is the full JavaScript reference.

The following code sample generates a PDF document so that a text field is filled with the current date just before it is printed. This way, you can always see on the print out when it was printed. (How to add fields to a PDF document is discussed in Section Field Shapes.)

Document document = new Document();

JavaScriptAction action = new JavaScriptAction( 
  string.Format( 
    "this.getField('printed').value = 'Printed: {0}'", DateTime.Now));
document.BeforePrintAction = action;

using (FileStream file = new FileStream(
   @"..\..\out.pdf", FileMode.Create, FileAccess.Write))
{
   document.Write(file);
}

Code sample: Add a BeforePrintAction to a document in C#

Document Level JavaScript

The property Document.JavaScripts lets you declare document level JavaScript. This typically contains common functions and constants that can be reused from other JavaScript actions. See JavaScript actions for a more detailed discussion. The following code sample shows how to add a JavaScript function at document level:

JavaScript javaScript = new JavaScript("function foo() { return 10; }");
document.JavaScripts.Add( javaScript );

Code sample: Add a document-level JavaScript function in C#

The document level javascript will need to be run at least once before the definitions in it become available to other javascript code. Existing document level javascript will be run automatically when the document gets opened (if allowed by the ScriptBehavior property), so existing definitions will be available automatically. If you add new definitions however, and you want to use these immediately without saving and reopening the document, you will need to run the document level javascript code explicitly. In order to do so, please execute the following code before executing any other javascript code that relies on it.

foreach (JavaScript javaScript in document.JavaScripts)
{           
  javaScript.Run(document);
}

Code sample: Explicitly running document level javascript in C# in order to declare new functions.

LaunchAction

This action lets you launch an application. The path property of this class may point either to the application itself or to a document which is then opened by the associated application.

LaunchAction

Adobe has specified a number of named actions. You can specify these through this class by setting the NamedAction.Name property to any of the following values:

  • NextPage: go to the next page
  • PrevPage: go to the previous page
  • FirstPage: go to the first page
  • LastPage: go to the last page

UriAction

The UriAction jumps to a given URI such as http://www.tallcomponents.com.

Appending, Splitting and Imposition

Using this application it is possible to construct new documents from pages from existing documents. This way you can split existing documents into multiple new document, you assemble new documents from multiple existing documents and you can create new pages that are composed of multiple new or existing pages.

Append

Appending pages to a document is extremely simple. The following code shows how a new output document is created from an assortment of new and existing pages. Note that 3 different methods for adding a range of pages are shown.

using ( FileStream file1 = new FileStream( 
   @"..\..\..\f1040a.pdf", FileMode.Open, FileAccess.Read ) )
using ( FileStream file2 = new FileStream( 
   @"..\..\..\fw4.pdf", FileMode.Open, FileAccess.Read ) )
using ( FileStream file3 = new FileStream( 
   @"..\..\..\ResellerGuide.pdf", FileMode.Open, FileAccess.Read ) )
{
   Document document = new Document();

   // add all pages from f1040a.pdf (using foreach)
   Document document1 = new Document( file1 );
   foreach ( Page page in document1.Pages )
   {
      document.Pages.Add( page.Clone() );
   }

   // add all pages from fw4.pdf (using AddRange)
   Document document2 = new Document( file2 );
   document.Pages.AddRange( document2.Pages.CloneToArray() );

   // add all pages from ResellerGuide.pdf (using page index)
   Document document3 = new Document( file3 );
   for ( int i=0; i<document3.Pages.Count; i++ )
   {
      document.Pages.Add( document3.Pages[i].Clone() );
   }

   // save new document with all pages
   using ( FileStream file = new FileStream( 
      @"..\..\out.pdf", FileMode.Create, FileAccess.Write ) )
   {
      document.Write( file );
   }
}

Code sample: Combine all pages of multiple documents in one document (Append)

Split

Splitting a document into multiple documents really boils down to the same kind of code that you use to append pages to a document. The following code shows how a document is split in chunks of 5 or less pages:

using ( FileStream fileIn = new FileStream( 
   @"ResellerGuide.pdf", FileMode.Open, FileAccess.Read ) )
{
   // open source document
   Document documentIn = new Document( fileIn );
   int n = documentIn.Pages.Count;
   for ( int i=0; i<n; i+=5 )
   {
      // create a page array of the next 5 (or less) pages
      Page[] pages = new Page[ Math.Min( i+5, n ) - i ];
      for ( int j=0; j<pages.Length; j++ )
      {
         pages[j] = documentIn.Pages[i+j].Clone();
      }
      // create a new document and add the range of pages
      Document document = new Document();
      document.Pages.AddRange( pages );
      using ( FileStream fileOut = new FileStream( 
         string.Format( @"..\..\out{0}-{1}.pdf", i+1, i+pages.Length ),
         FileMode.Create, FileAccess.Write ) )
      {
         // save next document
         document.Write( fileOut );
      }
   }
}

Code sample: Split a document in chunks of 5 or less pages (Split)

Imposition

Figure below shows a typical imposition scenario known as 2-up.

Typical Imposition Scenario 2 Up

Typical imposition scenario 2-up

The following code sample shows how to implement the 2-up scenario.

using ( FileStream fileIn = new FileStream( 
   "ResellerGuide.pdf", FileMode.Open, FileAccess.Read ) )
{
   // open source document
   Document documentIn = new Document( fileIn );
   PageCollection pages = documentIn.Pages;

   // set new width and height
   double height = pages[0].Width;
   double width = pages[0].Height;

   // create new document
   Document document = new Document();
   for ( int i=0; i<pages.Count; i+=2 )
   {
      Page page = new Page( width, height );    

      // add left page as page shape
      PageShape pageShape1 = new PageShape( 
         pages[i], 0, 0, width / 2, height, true, 0,
         PageBoundary.MediaBox );
      page.Overlay.Add( pageShape1 );

      if ( i+1 == pages.Count ) break;

      // add right page as page shape
      PageShape pageShape2 = new PageShape(
         pages[i+1], width / 2, 0, width / 2, height, true, 0,
         PageBoundary.MediaBox );
      page.Overlay.Add( pageShape2 );

      document.Pages.Add( page );
   }

   // save new document
   using ( FileStream fileOut = new FileStream( 
      "out.pdf", FileMode.Create, FileAccess.Write ) )
   {
      document.Write( fileOut );
   }
}

Code sample: Create 2-up document (2up)

Drawing with Shapes

In this application you can add new graphics to a new or existing PDF page. You do this by adding Shape objects to a Canvas object that is associated with a Page. Shape is an abstract base class and we offer a collection of concrete specializations such as TextShape, ImageShape and PageShape.

Canvas

A canvas is the top-level container of shape objects. Each page has 4 canvas objects that are accessed through the following Page properties: Overlay, VisualOverlay, Underlay and VisualUnderlay.

When you add shapes to Overlay or VisualOverlay, they appear on top of the existing content. In other words, shapes in these canvasses may occlude the existing content.

When you add shapes to Underlay or VisualUnderlay, they appear behind the existing content. In other words, shapes in these canvasses may be occluded by the existing content. There is a catch that the existing content of a page has an opaque white area spanning the entire page. In this case you will not see any shapes in Underlay or VisualUnderlay. You may be surprised by this because you assume that the white opaque area is actually empty page area.

The distinction between VisualOverlay and Overlay proper lies in the fact that VisualOverlay accounts for any cropping or rotation of the page. The same holds for VisualUnderlay and Underlay. This is shown is the figure below. The bounding box in visual page space is the intersection of the media box and the crop box.

Page Space Versus Visual Page Space

Page space versus Visual page space

Base class Shape

All shapes inherit directly or indirectly from Shape.

Partial Shapes Class Hierarchy

Partial shapes class hierarchy

ContentShape

ContentShape adds the following properties to specify transformation, blend mode and opacity.

Public properties specific to ContentShape

Public properties
Opacity0 means fully transparent. 255 means fully opaque.
BlendModeDefines how this content shape blends with its background. Default is Inherit (meaning: the same as its parent).
TransformThe geometric transformation applied to this content shape. Default is no transformation.

Destination

The ContentShape.Transform property lets you apply any geometric transform to a ContentShape. It is modelled after the WPF equivalent. This is described in more detail in Section Transforming content shapes.

TextShape

TextShape lets you draw single-line text. The X and Y properties that are inherited from Shape, correspond to the lower-left corner. The following table lists the public properties of TextShape.

Public properties specific to TextShape

Public properties
TextThe text that is displayed.
MeasuredWidth, MeasuredHeightRead-only properties that reflect the size of the text with the current settings.
Pen, Brush, Font, FontSize, Italic, BoldAppearance properties.
ReadDirectionRead direction (left-to-right or right-to-left).
BoundingBoxThe bounding box after rotation is applied.

MultilineTextShape

MultilineTextShape lets you draw multi-line text with mixed formatting.

The content of the multi-line text shape is specified as a collection of fragments. Each fragment has properties that specify both content and appearance. All fragments will be displayed concatenated and broken across lines as needed.

As opposed to the single-line TextShape you can set the Width. This determines where lines break. In addition you can set the Height property but this is only useful if the multi-line text shape is auto-sized. This is the case if and only if there is exactly one fragment that has a zero font size.

Barcodes

Currently we offer 3 barcode shapes: Code128BarcodeShape, Code2of5BarcodeShape and Code3of9BarcodeShape. They all inherit from the abstract base class OneDimensionalBarcodeShape, which in turn inherits from the abstract base class BarcodeShape, which in turn inherits from Shape. We anticipate adding 2-dimensional barcode shapes, hence this hierarchy.

ShapeCollection

There is a special shape called ShapeCollection which is a group of shape objects. Because a ShapeCollection is a Shape, it allows nesting of shapes to any depth. The purpose of ShapeCollection becomes clear if we look at the members that are specific to ShapeCollection.

Public properties specific to ShapeCollection

Public properties (typical collection members omitted)
Width, HeightWidth and Height of the shape collection with respect to the parent coordinate space. This parent is either a ShapeCollection or a Canvas.
VirtualWidth, VirtualHeightThese are the width and height of the shape collection as seen by the child shapes. If these are different than Width and Height, a scaling is applied. VirtualWidth and VirtualHeight decouple the dimensions of the child shapes from the actual size of the parent shape collection.
ClipClip the child shapes to the Width and Height.

PathShape

PathShape is the abstract base class of all shapes that involve stroking and filling. The table below shows the public members that are specific to PathShape.

Public properties specific to PathShape

Public properties
PenThis Pen is used to stroke this path shape.
BrushThis Brush is used to fill the closed region of this path shape.

Below is the class hierarchy starting with the PathShape.

PathShape And Derived Classes

PathShape and derived classes

FreeHandShape

The FreeHandShape allow you to build an arbitray curve composed of straight lines and bezier curves. The table below shows the members that are specific to FreeHandShape.

Public properties of FreeHandShape

Public properties
PathsA collection of FreeHandPath objects. You build a freehand shape by adding paths to this collection.
FillRuleDefines according to which fill rule the interior is filled. Can be either FillRule.NonzeroWindingNumber or FillRule.EvenOdd.

The following code shows a typical usage of the FreeHandShape:

FreeHandShape freeHandShape = new FreeHandShape();
page.Overlay.Add(freeHandShape);

freeHandShape.Pen = new Pen(GrayColor.Black);
freeHandShape.Brush = new SolidBrush(RgbColor.Red);

FreeHandPath path = new FreeHandPath();
path.Closed = true;
freeHandShape.Paths.Add(path);

path.Segments.Add(new FreeHandStartSegment(150, 150));
path.Segments.Add(new FreeHandLineSegment(400, 150));
path.Segments.Add(new FreeHandBezierSegment(250, 200, 300, 550, 400, 400));

Code sample: FreeHandShape

The result looks like this:

Sample

Docking

Sometimes you do not want to or you cannot specify exact positions. Instead you want shapes to be stacked or docked in some direction. The top-level class Shape has a property Dock of type DockStyle. The default value is DockStyle.None meaning that the shape is positioned at exact coordinates. Setting the Dock property to any of the other values turns on docking behavior that is similar to the how Winforms controls can be docked.

Transforming content shape

The ContentShape.Transform property lets you apply a geomatric transformation to a ContentShape object. It lets you position, rotate, scale and skew content shapes or apply any combination of these.

The following code draws an untransformed RectangleShape. The grid has a 30 pts unit.

RectangleShape rect1 = new RectangleShape(60, 120);
rect1.Pen = new Pen(RgbColor.Blue, 3);
rect1.Brush = new AxialGradientBrush(
   RgbColor.Red, RgbColor.Green, 0, 0, 0, rect1.Height);
shapes.Add(rect1);

Code sample: Draw an untransformed rectangle

This looks as follows:

RectangleShape1

The following code adds a translation:

RectangleShape rect2 = new RectangleShape(60, 120);
rect2.Transform = new TranslateTransform(30, 60);
rect2.Pen = new Pen(RgbColor.Blue, 3);
rect2.Brush = new AxialGradientBrush(
   RgbColor.Red, RgbColor.Green, 0, 0, 0, rect2.Height);
shapes.Add(rect2);

Code sample: Translate a rectangle shape

This looks as follows:

RectangleShape2

The following code adds a rotation:

RectangleShape rect3 = new RectangleShape(60, 120);
TransformCollection transforms = new TransformCollection();
rect3.Transform = transforms;
transforms.Add(new TranslateTransform(30, 60));
transforms.Add(new RotateTransform(30));
rect3.Pen = new Pen(RgbColor.Blue, 3);
rect3.Brush = new AxialGradientBrush(
   RgbColor.Red, RgbColor.Green, 0, 0, 0, rect3.Height);
shapes.Add(rect3);

Code sample: Translate and then rotate a rectangle

RectangleShape3

Note that the rectangle is rotated around the origin of the parent, not around its own origin. If you want to rotate it such that the lower-left corner of the rotated rectangle lies at [30,60], then you would need to inverse the transformation order and use the following code:

 

RectangleShape rect4 = new RectangleShape(60, 120);
TransformCollection transforms = new TransformCollection();
rect4.Transform = transforms;
transforms.Add(new RotateTransform(30));
transforms.Add(new TranslateTransform(30, 60));
rect4.Pen = new Pen(RgbColor.Blue, 3);
rect4.Brush = new AxialGradientBrush(
   RgbColor.Red, RgbColor.Green, 0, 0, 0, rect4.Height);
shapes.Add(rect4);

Code sample: Rotate and then translate a rectangle

RectangleShape4

Clipping

Clipping is done through the ClipShape property. You add it to a shape collection just like the other shapes. It only clips shapes that are added to the same collection after the ClipShape. You can have multiple clip shapes in a single collection. The ClipShape is defined just like the FreeHandShape using a FreeHandPath.

The following code creates a clipping path and clips an image:

ImageShape image = new ImageShape(@"..\..\flowers.jpg");

Page page = new Page(image.Width, image.Height);
document.Pages.Add(page);

ShapeCollection shapes = new ShapeCollection(page.Width, page.Height);
page.Overlay.Add(shapes);

ClipShape clip = new ClipShape();
FreeHandPath path = new FreeHandPath();
clip.Paths.Add(path);

path.Segments.Add(new FreeHandStartSegment(150, 150));
path.Segments.Add(new FreeHandLineSegment(400, 150));
path.Segments.Add(new FreeHandBezierSegment(250, 200, 300, 550, 400, 400));

shapes.Add(clip);
shapes.Add(image);

Code sample: Clipping an image using a ClipShape

It looks as follows:

Image Clipped By ClipShape

ShapeCollection.Clip

If this property is set, then everything outside the boundaries of the ShapeCollection is clipped.

Extract Graphics

As of version 3.0, it is possible to extract the existing graphics on a page as a collection of ContentShape objects. This collection and its items can be navigated, inspected, modified and persisted. This allows you to actually change the PDF content.

Page.CreateShapes

The central method to extracting graphics is Page.CreateShapes. The following code copies all content except the images from an existing document to a new document:

static void Main(string[] args)
{
   using (FileStream fileIn = new FileStream(
      "in.pdf", FileMode.Open, FileAccess.Read))
   {
      Document pdfIn = new Document(fileIn);
      Document pdfOut = new Document();

      foreach ( Page page in pdfIn.Pages)
         pdfOut.Pages.Add(copy(page));

      using (FileStream fileOut = new FileStream(
         "out.pdf", FileMode.Create, FileAccess.Write))
      {
         pdfOut.Write(fileOut);
      }
   }
}

static Page copy(Page page)
{
   Page newPage = new Page(page.Width, page.Height);

   ShapeCollection shapes = page.CreateShapes();
   purge(shapes);
   newPage.Overlay.Add(shapes);

   return newPage;
}

static void purge(ShapeCollection shapes)
{
   for(int i=0; i<shapes.Count; i++)
   {
      Shape shape = shapes[i];
      if (shape is ImageShape) { shapes.RemoveAt(i); i--; }
      if (shape is ShapeCollection) purge(shape as ShapeCollection);
   }
}

Code sample: Copy content except images from an existing document to a new document

Shape types

Only the following shape types are returned:

  • Structure shapes: ShapeCollection, ClipShape and LayerShape
  • Path shapes: FreeHandShape and LineShape
  • Graphics shapes: TextShape and ImageShape

Note that multiline text is not converted to a MultilineTextShape object and that paths are not converted to EllipseShape or RectangleShape objects.

Transformations

The Transform property of each ContentShape is set to an object of type TransformCollection. The items inside this collection are always of type MatrixTransform. Each transformation inside a PDF document is preserved as a single MatrixTransform so they are not collapsed.

Restrictions

The following features are not preserved while extracting graphics since they are not yet supported by the Shape object model:

  • Complex transparencies will be reduced to a single opacity value of type double (ContentShape.Opacity).
  • When modifying the TextShape.Text property, you need to be aware that if it uses a subsetted font, it may not be able to display the new text. This is the case if characters are used for which no representation exists in the subsetted font.
  • Shading types FunctionBasedShading (Type1), FreeFormGouraudShaded (Type4), LatticeFormGouraudShaded (type5), CoonsPatchMeshes (type6) and TensorProductPatchMeshes (type7) shadings are not supported. These are mapped to no fill.
  • The functions of shading types Axial Shading (type2) and RadialShading(type3) are converted to an array of color stops to achieve a similar visual effect.
  • Not all PDF color spaces are supported. Especially CIE based color spaces, DeviceN, Indexed and Pattern. These are converted to RGB to achieve a similar visual effect.
Download PDFControls.NET 2.0
We will send you a download link
Why do we ask your email address?
We send tips that speed up your evaluation
We let you know about bug fixes
You can always unsubscribe with one click
We never share your address with a 3rd party
Thank you for your download

We have sent an email with a download link.