Show PDF in your application

Long time no see! This article will explain one of the possible way for showing PDF file in your JavaFX application :


Three years ago, when I first implemented a PDF reader in my application, I did not find many options. I finally used IcePDF viewer. But this solution was not that good for several reasons :

  • IcePDF was made in Swing! Therefore I had to wrap the viewer into a SwingNode and frankly, it did not work that well.
  • The toolbar was not customizable and quite hideous.
  • It was displaying some popups when starting in order to save some cache into AppData, so I had to get the sources, disable that popup and rebuild the entire thing. Then I forgot what I did and was not able to upgrade my version.
  • It was very slow for no reason. I displayed my PDF in a side of my application, into a SplitPane, and it was taking forever to close it.
  • Recently, I tried to dig a bit more and see if new options were available. Luckily some solutions arose.

    One solution (the one I’m describing) is to use the Mozilla HTML 5 PDF viewer : pdf.js. If I manage to integrate that viewer into a WebView, I would have a stable, open-source pdf viewer with lots of functionalities!

    I decided to go with that solution because the files that we need weight less than 5Mb and basically nothing has to be done. (Ok maybe some tweaks..)

    Most of what I will say here is inspired by the comment on StackOverFlow made by user enzo : here. But the solution he wrote is not working when you actually deploy your application into a bundle or a JAR.

    Retrieve pdf.js

    So what you need is to download pdf.js dist and place it under src/main/resources.

    If you only want to display the pdf into your IDE, go to next step.

    One of the main problem with pdf.js is that we have relative links inside the javascript files. But the jar protocol does not allow for .. relative location specifiers! Thus, the viewer seems to work when you launch it in your IDE, but as soon as you will package it into a JAR and deploy it somewhere, the viewer will not work!

    So you need to take the pdf.js and pdf.worker.js files out of their build package, and place them with the other javascript files under web package.
    It should look like that :
    packages

    Once it’s done, we need to fix the broken links:
    At the top of viewer.html, replace src="../build/pdf.js" by src="pdf.js"
    In the viewer.js, replace PDFJS.workerSrc = '../build/pdf.worker.js'; by PDFJS.workerSrc = 'pdf.worker.js'; and PDFJS.cMapUrl = '../web/cmaps/'; by PDFJS.cMapUrl = 'cmaps/';.

    Now all the links should be working in the IDE and in the JAR.

    Tweaking the viewer

    To prevent pdf.js from opening demo pdf file on startup, open web/viewer.js and clear DEFAULT_URL value.

    var DEFAULT_URL = '';

    Open web/viewer.html and add script block:

    <script src="viewer.js"></script>
        <!-- CUSTOM BLOCK -->
        <script>
        var openFileFromBase64 = function(data) {
            var arr = base64ToArrayBuffer(data);
            console.log(arr);
            PDFViewerApplication.open(arr);
        }
    
        var changeLanguage = function(language) {
             var mozL10n = document.mozL10n || document.webL10n;
             mozL10n.setLanguage(language);
        }
        
        function base64ToArrayBuffer(base64) {
          var binary_string = window.atob(base64);
          var len = binary_string.length;
          var bytes = new Uint8Array( len );
          for (var i = 0; i < len; i++)        {
              bytes[i] = binary_string.charCodeAt(i);
          }
          return bytes.buffer;
        }
    </script>
    <!-- end of CUSTOM BLOCK -->
    

    The script block we added will allow us to open our PDF file and also to change the language of the PDF viewer.

    Some of pdf.js functions will not work: open file (cause pdf.js have no access to URL outside JAR), printing etc. To hide corresponding toolbar buttons you can add the following lines to a web.css file added in your resources :

    #toolbarViewerRight {
        display:none;
    }
    

    Launching the WebView and adding a PDF

    Now all you need to do is to create your WebView. Note that the WebView must be created on the FXApplicationThread otherwise an exception will be thrown :

                 WebView webView = new WebView();
                WebEngine engine = webView.getEngine();
                //Change the path according to yours.
                String url = getClass().getResource("pdfjs/web/viewer.html").toExternalForm();
                //We add our stylesheet.
                engine.setUserStyleSheetLocation(getClass().getResource("pdfjs/web.css").toExternalForm());
                engine.setJavaScriptEnabled(true);
                engine.load(url);
    

    Now the idea is to put the PDF file into a byte array. I get my PDF from a server URL so here it is :

    InputStream stream = null;
    try {
           stream = myUrl.openStream();
           //I use IOUtils from org.​apache.​commons.​io
           byte[] data = IOUtils.toByteArray(stream);
           //Base64 from java.util
           String base64 = Base64.getEncoder().encodeToString(data);
           //This must be ran on FXApplicationThread
           webView.getEngine().executeScript("openFileFromBase64('" + base64 + "')");
                            
           } catch (Exception ex) {
                e.printStackTrace();
           }finally {
                if (stream != null) {
                    try {
                        stream.close();
                    } catch (IOException ex) {
                        e.printStackTrace();
                    }
                }
            }
    

    But if you get your PDF from a file, you can get your byte array like that :

    // readFileToByteArray() comes from commons-io library
    byte[] data = FileUtils.readFileToByteArray(new File("/path/to/file"));
    

    Your PDF should be displayed inside your application!

    If you want to change the language of the viewer, simply do like that :

    public void changeLanguage(Locale locale) {
         webView.getEngine().executeScript("changeLanguage('" + locale.toLanguageTag() + "')");
    }
    

    Cheers

    10 thoughts on “Show PDF in your application”

    1. hello, nice example. i implemented it in my javafx app, but hit some issues.
      e.g
      stream = myUrl.openStream();—–> which url are you referencing?

      byte[] data = FileUtils.readFileToByteArray(new File(“/path/to/file”));–> after putting the path to my file it reads file not found or does not exist

      and occasionally, i get this error
      JSException: TypeError: undefined is not a function

      PLease help me out

      1. Hi Vernon,

        In my case, the PDF file is sent by a server, so actually I have a String like “http://server.com/?dl=MY_PDF.pdf”, and I simply build an URL out of that String.

        I cannot really help you with your File not found error. Where is located your File? On your computer or inside your project? Try several solutions in debug mode in order to see where the problem could be. Maybe a “\” instead of “/”. You can use java.nio.Paths in order to construct one and use “toFile()” also.

        Finally, I did not encounter the last error you are mentioning.. It’s definitely a Javascript exception so it should be inside PDF.js.

        Please note that I’ve been using this viewer for several months, I have noted that some complex PDF were not shown properly, for examples some big images. I’m looking on the problem but it seems that the WebView is not fully compatible with PDF.js sadly.. The quest to a perfect PDF viewer in JavaFX is still on.

        Sam’

        1. hello,
          thanks for your reply, much appreciated..

          my file is located in my computer, i have tried the file in my documents, and inside the src/web folder also..i encounter same issue..

          here i my block of code for loading the pdf

          private void LoadPDF() throws IOException {
          WebEngine engine = pdViewer.getEngine();
          String url = getClass().getResource(“/web/viewer.html”).toExternalForm();
          engine.setJavaScriptEnabled(true);
          engine.load(url);

          byte[] data = FileUtils.readFileToByteArray(new File(“C:\\Users\\VERNON\\Documents\\NetBeansProjects\\TestRun\\src\\web\\receipt.pdf”));
          String base64 = Base64.getEncoder().encodeToString(data);
          engine.executeScript(“openFileFromBase64(‘” + base64 + “‘)”);

          }

          with the above code i get the following error log:

          Caused by: netscape.javascript.JSException: TypeError: undefined is not a function
          at com.sun.webkit.dom.JSObject.fwkMakeException(JSObject.java:137)
          at com.sun.webkit.WebPage.twkExecuteScript(Native Method)
          at com.sun.webkit.WebPage.executeScript(WebPage.java:1482)
          at javafx.scene.web.WebEngine.executeScript(WebEngine.java:1003)
          at testrun.FXMLDocumentController.LoadPDF(FXMLDocumentController.java:283)
          at testrun.FXMLDocumentController.PDFLOAD(FXMLDocumentController.java:110)

          but with this code below, i get file not found or does not exist

          private void LoadPDF() throws IOException {
          WebEngine engine = pdViewer.getEngine();
          String url = getClass().getResource(“/web/viewer.html”).toExternalForm();
          engine.setJavaScriptEnabled(true);
          engine.load(url);

          byte[] data = FileUtils.readFileToByteArray(new File(“/web/receipt.pdf”));
          String base64 = Base64.getEncoder().encodeToString(data);
          engine.executeScript(“openFileFromBase64(‘” + base64 + “‘)”);

          Note that the location of the pdf is inside my project src/web folder..

          if there is anything you can tell me from my code..it will be very much appreciated

          1. Hi,

            If your File is situated in src/web folder, you should be able to reach it. Try to use the “getRessource” method just like you did for the viewer.
            byte[] data = FileUtils.readFileToByteArray(new File(getClass().getResource(“web/receipt.pdf”).toExternalForm());

            It should work somehow. In your first case, you clearly found the file and indeed, a JavaScript error is thrown after.

            It’s quite tricky to debug it just like that since I don’t have it on my side. Could you upload you application to GitHub or somewhere so that I can fork it and try to see where the error could be?

            Sam’

    2. hello,

      were you able to access my project?
      I went through your tutorial all over again, and tried debugging, cant seem to find where the problem is.

      Vernon’

          1. Allright,

            I’ve been able to launch your project. The issue is coming from the fact that you are loading the viewer.html right before loading your File. Thus I believe the pdf viewer is not yet instantiated.

            In the “loadPdf” method, if you move the four line instantiating the viewer.html in the “initialize” method, it should work.

            Please note that the receipt.pdf inside your Jar seems to be corrupted. I cannot open it with Acrobat Reader.

            In LoadPDF, I replaced the data line with that: “byte[] data = FileUtils.readFileToByteArray(new File(“C:\\Users\\MYSELF\\Desktop\\MY_WORKING_PDF.pdf”));”

            You can have a look to another of my blog post here : https://blog.samirhadzic.com/2016/09/12/change-your-application-theme-dynamically/ where I explain how to have a working code for opening files where you are launching from your IDE (FileSystem) and your JAR.

            Sam’

            1. hi

              Thanks so much for this. it actually worked…for some reason i can only read pdf files on my desktop. But am going through your link….
              when i will contact you again.
              good job and thanks again

              Vernon

    Leave a Reply

    Your email address will not be published. Required fields are marked *