# Using the PDFViewer package

Since release 2.8 the `org.faceless.pdf2.viewer2` package contains the classes required to create a Swing application to display PDFs. The design revolves around a hierarchy of `JComponent` objects which provided successively more features.

PDFViewer
  A PDFViewer is a Swing panel that contains zero or more DocumentPanel objects, and includes a toolbar, menu bar and so on. It can be run as an Application, as an Applet (via the PDFViewerApplet or your own class), or can be embedded into a larger Swing component. For most applications it is sufficient to use this class, not one of the lower-level classes.
DocumentPanel
  A DocumentPanel contains a DocumentViewport and optionally some SidePanel objects, and has the ability to run PDFActions via an ActionHandler. It's probably the class you should be instantiating if you want to view a document but don't want to use the PDFViewer.
DocumentViewport
  A DocumentViewport is used to manage the display of a PDF in a limited area of screen space. At its simplest it provides a view of a single page with scrollbars if necessary (the SinglePageDocumentViewport), but other implementations are possible. A DocumentViewport should not be used directly, but instead instantiated as part of a DocumentPanel.
PagePanel
  The PagePanel is the lowest level class - it's purpose is to display a page or a segment of a page. The section of page that's displayed is controlled via the PagePanel.setPage() method, which updates the PagePanel itself in a background thread. A PagePanel can be used directly, but is more commonly instantiated inside a DocumentViewport as part of a DocumentPanel

## Using the PDFViewer class

At its simplest a PDFViewer can be created using the `newPDFViewer()` method, which creates and displays a PDFViewer object in a frame of its own, or just by the `PDFViewer` constructor. A Collection of ViewerFeature objects may be passed in to control what features the viewer has - using this approach the viewer can be customized to be anything from a simple, single page document panel to a panel containing buttons and handling multiple documents at once, including form completion, the ability to save or print PDFs and more. It's possible to create your own `ViewerFeature` objects to create custom extensions as well as using the supplied set in the feature package.

For example, to create a new PDFViewer using a customized set of features:

    Collection features = new ArrayList(ViewerFeature.getAllFeatures());
    // Remove the MultiWindow feature
    for (Iterator i=features.iterator();i.hasNext();) {
        if (i.next() instanceof MultiWindow) {
            i.remove();
        }
    }
    features.add(new MyCustomFeature());         // Add your own custom feature
    JFrame frame = new JFrame("BFO");
    PDFViewer viewer = new PDFViewer(features);
    frame.setContentPane(viewer);
    frame.pack();
    frame.setVisible(true);

## Using the DocumentPanel class

If for some reason you don't want to use the `PDFViewer`, the DocumentPanel is the next highest level class. It can display a single document in a DocumentViewport, as well as zero or more SidePanel objects. Here's a simple example showing how to instantiate one that can navigate with internal hyperlinks in the document.

    import java.io.*;
    import javax.swing.*;
    import org.faceless.pdf2.*;
    import org.faceless.pdf2.viewer2.*;
    import org.faceless.pdf2.viewer2.feature.*;

    public static void main(String[] args) throws Exception {
        final PDF pdf = new PDF(new PDFReader(new File(args[0])));
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                DocumentPanel panel = new DocumentPanel();
                panel.addAnnotationComponentFactory(new AnnotationLinkFactory());
                panel.addActionHandler(new GoToActionHandler());
                JFrame frame = new JFrame("BFO");
                frame.setContentPane(panel);
                frame.pack();
                frame.setSize(300, 300);
                frame.setVisible(true);
                panel.setPDF(pdf);
            }
        });
    }

## Creating custom features

Custom features can easily be added by extending the ViewerFeature class, although typically you'll be extending one of its subclasses. The best place to start when extending is to look at the source code for the supplied features, as it's quite likely you can modify one to do the job. As a quick example, here's how to create a button that would stamp the current page of the active PDF.

    import org.faceless.pdf2.viewer2.*;
    import org.faceless.pdf2.*;
    import javax.swing.*;

    public class CustomStamp extends ViewerWidget {

        public ConfidentialStamp() {
            super("ConfidentialStamp");
            setButton("Stamp", "path/to/icon.png", "Stamp PDF");
        }

        public void action(final ViewerEvent event) {
            AnnotationStamp stamp = new AnnotationStamp("stamp.standard.Approved", 1);
            PDF pdf = event.getPDF();
            PDFPage page = event.getDocumentPanel().getPage();
            stamp.setRectangle(100, 500, 300, 600);
            page.getAnnotations().add(stamp);
        }

    }

More complicated features that don't respond simply to a button press or menu item may need to register themselves as Listeners on the DocumentPanel or its PagePanel, so that they can be updated as events on those objects are fired. The Event model is identical in design to the Swing event model and should be very easy to pick up for any Swing programmers - and to see what's going on under the hood, you can set the `org.faceless.pdf2.viewer2.debug.Event` System property which will print out each event as it's fired. For example, here is how to create a simple widget that displays in the toolbar the PDF co-ordinates of the mouse as it moves over the page.

    import javax.swing.*;
    import java.awt.*;
    import org.faceless.pdf2.viewer2.*;

    public class Coordinates extends ViewerWidget implements DocumentPanelListener, PagePanelInteractionListener {

        private JLabel label;
        private DocumentViewport oldviewport;

        public Coordinates() {
            super("Coordinates");
            label = new JLabel();
            setComponent("Coordinates", label);
        }

        // Called when the button is first created and added to a viewer.
        // Ensure that we're registered as a DocumentPanelListener for each
        // DocumentPanel that the PDFViewer creates.
        //
        public void initialize(PDFViewer viewer) {
            super.initialize(viewer);
            viewer.addDocumentPanelListener(this);
        }

        // Whenever a DocumentPanel has its viewport set, ensure we're registerd as a
        // PagePanelInteractionListener for each PagePanel that the DocumentViewport creates.
        // We keep track of the old viewport and remove ourselves from its list when the viewport changes -
        // strictly speaking this isn't necessary as it's going to be garbage collected anyway, but it's
        // good practice.
        //
        public void documentUpdated(DocumentPanelEvent event) {
            if (event.getType().equals("viewportChanged")) {
                if (oldviewport != null) {
                    oldviewport.removePagePanelInteractionListener(this);
                }
                event.getDocumentPanel().getViewport().addPagePanelInteractionListener(this);
            }
            oldviewport = event.getDocumentPanel().getViewport();
        }

        // Whenever we receive a PagePanelInteractionEvent from the PagePanel,
        // update the co-ordinates in our JLabel to reflect the PDF co-ordinates
        //
        public void pageAction(PagePanelInteractionEvent event) {
            if (event.getType().equals("mouseMoved")) {
                Point2D p = event.getPoint();
                label.setText(((int)p.getX())+" "+((int)p.getY()));
            }
        }

    }

Here's a similar example that shows how to highlight text in a page. At the time of writing no such feature is availble by default, but although we expect to add one in a future release this example still shows it it could be done. Note how we do it - we listen for a `Redrawn` PagePanelEvent, then add a number of `JPanel` children to the PagePanel. Their positions on the page are bound to a set of PDF co-ordinates by calling the `AnnotationComponentFactory.bindComponentLocation()` method, which means as the page is zoomed or scrolled, the position of the JPanel will be updated to match.

    import org.faceless.pdf2.viewer2.*;
    import org.faceless.pdf2.*;
    import javax.swing.*;
    import java.awt.*;
    import java.util.*;

    public class Highlighter extends ViewerFeature implements DocumentPanelListener, PagePanelListener {

        private String word;
        private PDFPage page;
        private DocumentViewport oldviewport;

        public Highlighter(String word) {
            super("TextHighlighter");
            this.word = word;
        }

        // See previous example for method comments
        //
        public void initialize(PDFViewer viewer) {
            super.initialize(viewer);
            viewer.addDocumentPanelListener(this);
        }

        // See previous example for method comments
        //
        public void documentUpdated(DocumentPanelEvent event) {
            if (event.getType().equals("viewportChanged")) {
                if (oldviewport != null) {
                    oldviewport.removePagePanelInteractionListener(this);
                }
                event.getDocumentPanel().getViewport().addPagePanelListener(this);
            }
            oldviewport = event.getDocumentPanel().getViewport();
        }

        // Called when a page is updated. We catch the "redrawing" event which is
        // raised just before the page draw begins, and use it to make sure that we
        // extract text when the page is drawn. Then when the "redrawn" event fires
        // we can find the matching text and add children to the PagePanel
        // to highlight them.
        //
        public void pageUpdated(PagePanelEvent event) {
            if (event.getType().equals("redrawing")) {
                event.getPagePanel().setExtractText(true);
            } else if (event.getType().equals("redrawn")) {
                PagePanel panel = event.getPagePanel();
                if (panel.getPage() != page) {        // Page has changed, recreate highlights
                    Collection matching = panel.getPageExtractor().getMatchingText(word);
                    for (Iterator i = matching.iterator();i.hasNext();) {
                        PageExtractor.Text text = (PageExtractor.Text)i.next();
                        JPanel box = new JPanel() {
                            public void paintComponent(Graphics g) {
                                g.setColor(new Color(0x70FFFF00, true));
                                g.fillRect(0, 0, getWidth(), getHeight());
                            }
                        };
                        float[] c = text.getCorners(); // Assume text is rectangular
                        AnnotationComponentFactory.bindComponentLocation(box, c[0], c[1], c[4], c[5]);
                        panel.add(box);
                    }
                    page = panel.getPage();
                }
            }
        }

    }

For further reference, we recommend looking through the source code to the features package. We believe that most features typically required of a PDF viewing application can be created by extending the various ViewerFeature subclasses - there should be very little reason to modify the code in the viewer package itself.

Copyright © 2001-2014 Big Faceless Organization