One of the challenges I had to face a couple of years ago was to migrate a Java desktop application from 32 bits to 64 bits architecture, and what may have been as simple as using the right JDK, switching to the proper external libraries versions and getting rid of the deprecated code actually had an extra issue. The application embeded a 32 bits PDF reader on a panel, and that limitation had been stopping this migration for a while.
One of the challenges I had to face a couple of years ago was to migrate a Java desktop application from 32 bits to 64 bits architecture, and what may have been as simple as using the right JDK, switching to the proper external libraries versions and getting rid of the deprecated code actually had an extra issue. The application embeded a 32 bits PDF reader on a panel, and that limitation had been stopping this migration for a while.
Going into the code itself, it was using the XULRunner, one of the Firefox engine components, so what it actually did was “using a 32 bits Firefox browser embedded”. It also used Adobe Acrobat generated interactive forms, which actually required Adobe Acrobat Reader itself to be rendered properly and this took Apache PDFBox out of the picture to do this task.
When I looked into the philosophy of running an embed browser I tried to take what happened in my laptop: when I was surfing the net and opening a PDF file my Firefox 32 bits browser, I didn’t use some browser code: I delegated this in the Adobe Acrobat Reader X Plugin for the browser. So, why should not we take this idea one step further, and use the same plugin through an integration? Adobe itself provides this system, so there would be no incompatibility issue, I just had to setup this through the operating system configuration to get a workaround and get the proper version for the machine architecture in which we are currently running the program.
So let’s meet DJNativeSwing, a library based in SWT (Standard Widget Toolkit) which allows us to have our very own embedded browser in our code. SWT is highly recommended library due to its portability, as it accesses to the Native Operating System GUIs, and that is exactly what we require for this kind of issue. It is also the next wrapping level to Java Swing, and actually lighter and faster, and I had already used it back in the day to avoid problems when I had to deal with Macromedia Flash plugin integrations.
As an example of how to configure this, we are going to follow 2 basic steps:
Set up a browser tab on a Java swing component.
Be able to open a PDF on that said browser tab.
Process
1. Previous set up: the Maven dependecies required
Let’s start with the basics: first of all we will get the maven dependencies.
❕This was the stable version when the post was originally written.
Right after that, we will define the basic window frame, with a button the select the file (via FileChooser) and a panel to show the results.
❕Setting up all the text strings as constants makes them easier to spot them in order to replace them if needed. It’s not really necessary, but doing it improves reusability.
publicclassDemoPDFRenderLauncher { privatestaticfinalStringTITLE="PDF Renderer demo"; privatestaticfinalStringNO_OUTPUT_MESSAGE="No output available"; privatestaticfinalStringNO_DATA_MESSAGE="There is no data available from form"; privatestaticfinalintLENGTH=800; privatestaticfinalintWIDTH=600; privatestaticfinal String FILTER_FILES "PDF files"; privatestaticfinal String FILE_EXTENSION "pdf";
/** * The main app window */ private JFrame window; /** * The path of the file we will open */ private String path; /** * Button for open file function */ private JButton buttonOpen; /** * A browser panel */ private BrowserPanel browserPanel;
/** * Initializes the browser and sets a value in the URL storage * @param path the URL value o file path to open */ publicvoidnavigate(String path) { webBrowser.setVisible(true); webBrowser.navigate(path); } /** * Makes the browser retrieve and render the content from the path previously stored */ public String getAddress(){ return webBrowser.getHTMLContent(); } /** * Hides the browser controls (forward, back, home buttons...) */ publicvoidhideContent() { webBrowser.setVisible(false); } }
This way we are getting a fully functional web browser, very similar to the browser in the Eclipse IDE, but with too many unnecessary functions for what we are trying to do here. Since we are only doing the rendering process by delegating it on Adobe, we can remove the GUI extra elements from this whole custom browsing system and leave the bare panel.
/** * Allows to launch a JFrame containing an embedded browser. */ publicclassBrowserFrameLauncher {
/** * Renders the file content on a browser via DJ Native Swing * * @param path * the file url if we pass as a parameter any webpage URL, * the system would try to render it * @return a JPanel with a native browser, to render the file content */ private Component createBrowserPanel(String path) { JWebBrowser.useXULRunnerRuntime(); JPanelfileBrowserPanel=newJPanel(newBorderLayout()); finalJWebBrowserfileBrowser=newJWebBrowser(); fileBrowser.setBarsVisible(false); fileBrowser.setStatusBarVisible(false); fileBrowser.navigate(path); fileBrowserPanel.add(fileBrowser, BorderLayout.CENTER); return fileBrowserPanel; }
}
4. Setting the last piece of the puzzle: getting the PDF itself
Finally let’s make the final touches to open a PDF. What we are actually doing is getting the PDF file path into the browser, so in the end we have a new layer over our old friend XULRunner, but this provides us a way to integrate the plugins via the “right architecture version” SWT library. So as a conclussion we are able to connect to the “right architecture version” plugin, fixing our rendering problem and making us independent from the 32 bits plaform once and for all.
privatestaticfinalStringCLOSING_MESSAGE="Do you really want to close the file?"; privatestaticfinalStringRENDERED_TITLE="PDF Renderer demo - Embed Browser";
/** * Opens a file and shows it content in a JFrame. * * @param path * the url of the file to open in a JFrame */ publicstaticvoidopenPDF(final String path) { NativeInterface.open(); SwingUtilities.invokeLater(newRunnable() { publicvoidrun() { finalJFrameframe=newJFrame(RENDERED_TITLE); frame.setLocation(0, 0); //we may set up a default size for this test //frame.setSize(800, 600); frame.setVisible(true); frame.add(createBrowserPanel(path)); //a window listener would allow us to control the closing actions frame.addWindowListener(newWindowAdapter() { publicvoidwindowClosing(WindowEvent e) { inti= JOptionPane.showConfirmDialog(frame,CLOSING_MESSAGE); if (i == 0) { NativeInterface.close(); } } }); } }); }
❗️ Please notice the NativeInterface.open() line to make sure about getting the components correctly booted, and the threading of this component in order to avoid other processes interfering with the rendering.