Displaying pdf in JavaFX

I suggest using PDF JS javascript library.

Create a WebView and load statically the html/javascript content of this javascript pdf viewer example project. Create a function in javascript to which you can send the pdf byte array to be displayed.

This way all the logic of the pdf viewer is already there. You can even modify the viewers html to remove some features over there.

Also be careful about JPedalFX as I found it not reliable in cases where it had to render images that were added to the pdf file. In my case JPedalFX could not render a chart image that was generated with jfreechart


JPedalFX Sample Code and Usage

Sample code on using JPedalFX is provided with the JPedalFX download.

Kind of lame on my part, but I'll just paste snippets sample code here that have been copied from the sample viewer provided with the JPedalFX library. The code relies on the jpedal_lgpl.jar file included with the JPedalFX distribution being on the classpath (or the library path referenced in the manifest of your application jar).

Should you have further questions regarding usage of JPedalFX, I suggest you contact IDR solutions directly (they have been responsive to me in the past).

// get file path.
FileChooser fc = new FileChooser();
fc.setTitle("Open PDF file...");
fc.getExtensionFilters().add(new FileChooser.ExtensionFilter("PDF Files", "*.pdf"));     
File f = fc.showOpenDialog(stage.getOwner());
String filename = file.getAbsolutePath();

// open file.
PdfDecoder pdf = new PdfDecoder();
pdf.openPdfFile(filename);
showPage(1);
pdf.closePdfFile();

. . . 

/**
 * Update the GUI to show a specified page.
 * @param page 
 */
private void showPage(int page) {

    //Check in range
    if (page > pdf.getPageCount())
        return;
    if (page < 1)
        return;

    //Store
    pageNumber = page;


    //Show/hide buttons as neccessary
    if (page == pdf.getPageCount())
        next.setVisible(false);
    else
        next.setVisible(true);

    if (page == 1)
        back.setVisible(false);
    else
        back.setVisible(true);


    //Calculate scale
    int pW = pdf.getPdfPageData().getCropBoxWidth(page);
    int pH = pdf.getPdfPageData().getCropBoxHeight(page);

    Dimension s = Toolkit.getDefaultToolkit().getScreenSize();

    s.width -= 100;
    s.height -= 100;

    double xScale = (double)s.width / pW;
    double yScale = (double)s.height / pH;
    double scale = xScale < yScale ? xScale : yScale;

    //Work out target size
    pW *= scale;
    pH *= scale;

    //Get image and set
    Image i = getPageAsImage(page,pW,pH);
    imageView.setImage(i);

    //Set size of components
    imageView.setFitWidth(pW);
    imageView.setFitHeight(pH);
    stage.setWidth(imageView.getFitWidth()+2);
    stage.setHeight(imageView.getFitHeight()+2);
    stage.centerOnScreen();
}

/**
 * Wrapper for usual method since JFX has no BufferedImage support.
 * @param page
 * @param width
 * @param height
 * @return 
 */
private Image getPageAsImage(int page, int width, int height) {

    BufferedImage img;
    try {
        img = pdf.getPageAsImage(page);

        //Use deprecated method since there's no real alternative 
        //(for JavaFX 2.2+ can use SwingFXUtils instead).
        if (Image.impl_isExternalFormatSupported(BufferedImage.class))
            return javafx.scene.image.Image.impl_fromExternalImage(img);

    } catch(Exception e) {
        e.printStackTrace();
    }

    return null;
}

/**
 * ===========================================
 * Java Pdf Extraction Decoding Access Library
 * ===========================================
 *
 * Project Info:  http://www.jpedal.org
 * (C) Copyright 1997-2008, IDRsolutions and Contributors.
 *
 *  This file is part of JPedal
 *
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


 *
 * ---------------
 * JPedalFX.java
 * ---------------
 */

SwingLabs PDF Renderer

Additionaly, I used an old SwingLabs Swing based pdf renderer with JavaFX in the past for rendering pdf's for my JavaFX web browser. Although the Swing/JavaFX integration wasn't a supported feature of JavaFX at the time that I developed the browser, it still worked fine for me. Code for integration is in PDFViewer.java and BrowserWindow.java.

Note that embedding JavaFX in a Swing app is supported in Java 2.2 and embedding a Swing app in JavaFX is supported in Java 8.


Ok, here is my 50 cents. In addition to @ALabrosik and @ReneEnriquez answers.

Download pdf.js dist and place it under src/main/resources

├── pom.xml
├── src
│   └── main
│       ├── java
│       │   └── me
│       │       └── example
│       │           ├── JSLogListener.java
│       │           ├── Launcher.java
│       │           └── WebController.java
│       └── resources
│           ├── build
│           │   ├── pdf.js
│           │   └── pdf.worker.js
│           ├── main.fxml
│           ├── web
│           │   ├── cmaps
│           │   ├── compatibility.js
│           │   ├── debugger.js
│           │   ├── images
│           │   ├── l10n.js
│           │   ├── locale
│           │   ├── viewer.css
│           │   ├── viewer.html
│           │   └── viewer.js

Create the following fxml file (you should wrap WebView in TabPane or similar container to avoid problems with scrolling support)

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.web.WebView?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="576.0" prefWidth="1024.0" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" fx:controller="me.example.WebController">
    <center>
      <TabPane>
         <tabs>
            <Tab text="PDF test">
               <content>
                    <WebView fx:id="web" minHeight="-1.0" minWidth="-1.0" />
               </content>
            </Tab>
         </tabs>
      </TabPane>
    </center>
   <bottom>
      <Button fx:id="btn" mnemonicParsing="false" text="Open another file" BorderPane.alignment="CENTER" />
   </bottom>
</BorderPane>

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:

<head>

<!-- ... -->
<script src="viewer.js"></script>

<!-- CUSTOM BLOCK -->
<script>
    var openFileFromBase64 = function(data) {
        var arr = base64ToArrayBuffer(data);
        console.log(arr);
        PDFViewerApplication.open(arr);
    }

    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 -->

</head>

Now the controller (see code comments for explanation).

 public class WebController implements Initializable {

    @FXML
    private WebView web;

    @FXML
    private Button btn;

    public void initialize(URL location, ResourceBundle resources) {
        WebEngine engine = web.getEngine();
        String url = getClass().getResource("/web/viewer.html").toExternalForm();

        // connect CSS styles to customize pdf.js appearance
        engine.setUserStyleSheetLocation(getClass().getResource("/web.css").toExternalForm());

        engine.setJavaScriptEnabled(true);
        engine.load(url);

        engine.getLoadWorker()
                .stateProperty()
                .addListener((observable, oldValue, newValue) -> {
                    // to debug JS code by showing console.log() calls in IDE console
                    JSObject window = (JSObject) engine.executeScript("window");
                    window.setMember("java", new JSLogListener());
                    engine.executeScript("console.log = function(message){ java.log(message); };");

                    // this pdf file will be opened on application startup
                    if (newValue == Worker.State.SUCCEEDED) {
                        try {
                            // readFileToByteArray() comes from commons-io library
                            byte[] data = FileUtils.readFileToByteArray(new File("/path/to/file"));
                            String base64 = Base64.getEncoder().encodeToString(data);
                            // call JS function from Java code
                            engine.executeScript("openFileFromBase64('" + base64 + "')");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });

        // this file will be opened on button click
        btn.setOnAction(actionEvent -> {
            try {
                byte[] data = FileUtils.readFileToByteArray(new File("/path/to/another/file"));
                String base64 = Base64.getEncoder().encodeToString(data);
                engine.executeScript("openFileFromBase64('" + base64 + "')");
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}

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 web.css:

#toolbarViewerRight {
    display:none;
}

That's all. The rest of the code is trivial.

public class JSLogListener {

    public void log(String text) {
        System.out.println(text);
    }
}

public class Launcher extends Application {

    public static void main(String[] args) {
        Application.launch();
    }

    public void start(Stage primaryStage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("/main.fxml"));
        primaryStage.setTitle("PDF test app");
        primaryStage.setScene(new Scene(root, 1280, 576));
        primaryStage.show();
    }
}

Hope this helps to someone.

Tags:

Java

Javafx