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.