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.
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);
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); } }); }
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.