Extending the PDF Viewer - Part 1

Extending the PDF Viewer - Part 1

The BFO PDF Viewer has a simple framework and API for adding new features. This article will show how to add a simple widget to the toolbar and then add more features that interact with the PDF document itself.

The basis of extending the PDF Viewer comes from the class org.faceless.pdf2.viewer2.ViewerFeature, but for this example we'll look at org.faceless.pdf2.viewer2.ViewerWidget, which extends ViewerFeature and adds functionality to insert menu items and/or toolbar buttons to the Viewer.

For our first example we'll add a new 'widget' to the toolbar to display the memory usage of the PDF Viewer application.

Basic widget

Our new widget will just draw a simple gauge to indicate how much memory is currently being used by the PDF Viewer. Firstly we need to create a new class based on ViewerWidget:

public class Memory extends ViewerWidget  {

  private MemoryGauge memoryUsage;
  
  /**
   * Constructor
   */
  public Memory() {
    super("Memory");
    memoryUsage = new MemoryGauge();
    memoryUsage.setPreferredSize(new Dimension(100,20));

    setComponent("MemoryUsage", memoryUsage);
  }
}

The MemoryGauge object is a simple Swing component we will look at later - the above code simply registers our widget with the name of 'Memory', creates an instance of our gauge and registers it with the PDF Viewer as a widget identified as 'MemoryUsage'. That is a basic PDF Viewer widget, although it doesn't do anything useful yet.

Reacting to events

An obvious thing a widget could need to do would be to react to events triggered by the PDF Viewer, such as documents opening or closing and other actions on pages. There are three interfaces that our widget can implement in order to be notified about such events and these are:

org.faceless.pdf2.viewer2.DocumentPanelListener org.faceless.pdf2.viewer2.PagePanelListener org.faceless.pdf2.viewer2.PagePanelInteractionListener

If we implement all three interfaces and add ourselves as listeners to the PDF Viewer then our code now becomes:

public class Memory extends ViewerWidget implements
        DocumentPanelListener, PagePanelListener,
        PagePanelInteractionListener {

  private MemoryGauge memoryUsage;
  
  /**
   * Constructor. Register us with a unique name
   */
  public Memory() {
    super("Memory");
    memoryUsage = new MemoryGauge();
    memoryUsage.setPreferredSize(new Dimension(100,20));

    setComponent("MemoryUsage", memoryUsage);
  }

  /**
   * Set things up. Register a document listener
   */
  public void initialize(PDFViewer viewer) {
    super.initialize(viewer);
    viewer.addDocumentPanelListener(this);
  }
  
  /**
   * DocumentListener.documentUpdated
   */
  public void documentUpdated(DocumentPanelEvent event) {
    if (event.getType()=="viewportChanged") {
      DocumentViewport vp = event.getDocumentPanel().getViewport();
      vp.addPagePanelInteractionListener(this);
      vp.addPagePanelListener(this);
    }
  }

  /**
   * PagePanelListener.pageUpdated
   */
  public void pageUpdated(PagePanelEvent arg0) {
  }

  /**
   * PagePanelInteraction.pageAction
   */
  public void pageAction(PagePanelInteractionEvent arg0) {    
  }
}

We don't actually need page level listeners for our example, but they are included to demonstrate how to add them to the viewport, as shown in the documentUpdated() handler.

Now that our skeleton widget is done we need to add some functionality to it. For our purposes we need to refresh our memory gauge every 10 seconds or so, and when the document is updated (e.g. opened or closed) - to do that we add the following code:

/**
 * Set things up. Register a document listener
 */
public void initialize(PDFViewer viewer) {
  super.initialize(viewer);
  viewer.addDocumentPanelListener(this);
  
  Runnable r = new Runnable() {
    public void run() {
      while (true) {
        try {
          Thread.sleep(10000); // wait 10s between refreshes
          SwingUtilities.invokeLater(new Runnable() {
            public void run() {
              memoryUsage.repaint();
            }
          });
        }
        catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  };
  new Thread(r).start();
}

/**
 * DocumentListener.documentUpdated
 */
public void documentUpdated(DocumentPanelEvent event) {
  // refresh when document loaded/closed etc
  memoryUsage.repaint();
  
  if (event.getType()=="viewportChanged") {
      DocumentViewport vp = event.getDocumentPanel().getViewport();
      vp.addPagePanelInteractionListener(this);
      vp.addPagePanelListener(this);
  }
}

The complete source code, including MemoryGauge class, is included in the finished JAR file.

You should be able to compile this class as long as you have the bfopdf.jar file in your classpath.

Adding a widget to the viewer

Once we have our widget code we need to add it to the PDF Viewer. This is done by creating a file called META-INF/services/org.faceless.pdf2.viewer2.ViewerFeature and adding it to the Class path - usually by placing it in a JAR file with the new Widget. The main bfopdf.jar has one of these files that lists all of the standard features and widgets.

To add our widget we simply need a file that has the package and name of our widget's class file in it, e.g.

# Extra widget to show PDF Viewer heap usage
org.faceless.pdf2.viewer2.extrafeatures.Memory

This will then be included in our JAR file, along with the Java code, to give us a PDF Viewer widget that we can install and deploy simply by including it on the classpath when we launch the PDF Viewer - for example:

java -cp bfopdf.jar:memoryWidget.jar org.faceless.pdf2.viewer2.PDFTool

Note that the order that the JARs appear in the classpath will determine the order in which the respective features and widgets appear in the viewer.

For now you can download the finished JAR file, including source, to try out. The next article will demonstrate some more useful viewer widgets and include a download of the full project, containing an Ant file to build and package everything.

Further Reading

The second part of this series will go into more detail, but until then the Viewer Tutorial has more information, and is included with the API documentation. The source code for the viewer is supplied with the package and is also a good place to start.