Repository navigation
Viewer JavaFX RI
The icepdf JavaFX viewer is a reference implementation of a PDF viewer built on JavaFX. It comes in two layers: PdfView, an embeddable JavaFX Control for viewing, annotating and filling in PDF documents, and PdfViewer, a complete viewer assembled from it with a header bar, tool rail, side panels, dialogs, printing and signing. You can run the viewer as a stand-alone application, drop PdfViewer into your own window, or build your own interface around a bare PdfView.
Pages are rendered by the icepdf core's Java2D engine into tiles that are shared with JavaFX without copying. Everything interactive, such as text selection, search highlights, annotation handles and popups, and form field editors, is a native JavaFX node.
The JavaFX viewer requires Java 17 and JavaFX 22 or later. It lives alongside the Swing/AWT viewer, which remains available.
<dependency>
<groupId>com.github.pcorless.icepdf</groupId>
<artifactId>icepdf-core</artifactId>
<version>x.y.z</version>
</dependency>
<dependency>
<groupId>com.github.pcorless.icepdf</groupId>
<artifactId>icepdf-viewer-fx</artifactId>
<version>x.y.z</version>
</dependency>icepdf-viewer-fx holds the page view, the viewer component and the viewer application. JavaFX is not included; your application brings it (javafx-base, javafx-graphics and javafx-controls, version 22 or later).
The module name is org.icepdf.fx. It requires org.icepdf.core and javafx.controls.
From a source checkout, Gradle builds the core and starts the viewer:
./gradlew :viewer:viewer-fx:run
./gradlew :viewer:viewer-fx:run --args="/path/file.pdf" -Pxmx=4g
Every file named on the command line opens as a tab of the first window. Arguments can be file paths or file: URIs.
To build a single runnable jar with JavaFX for your platform included:
./gradlew :viewer:viewer-fx:jarWithDependencies
java -jar viewer/viewer-fx/build/libs/icepdf-viewer-fx-<version>-jar-with-dependencies.jar [file.pdf]
With Maven, install the core first, then run the viewer with the JavaFX plugin:
mvn -f core/core-awt/pom.xml install -DskipTests -Djacoco.skip=true
mvn -f viewer/viewer-fx/pom.xml javafx:run
On Linux, ./gradlew :viewer:viewer-fx:launcherLibs followed by packaging/linux/install-desktop.sh --fx installs a desktop launcher.
There are three ways to embed the viewer, from the most provided for you to the most control:
| Approach | What you get | Start here when |
|---|---|---|
PdfViewer |
The complete viewer as a single JavaFX node | You want a full-featured viewer with little code |
PdfViewer without chrome |
The viewer's document area, commands and panels, driven from your own toolbar | You want your own toolbar and layout but the viewer's behaviour |
PdfView |
The bare page view and its events | You are building your own interface from scratch |
Whichever you choose, load the system fonts once at startup so documents that don't embed their fonts render correctly:
FontSettings.apply(); // cached in ~/.icepdf/fx, scanned in the background the first timeFontSettings.apply(cacheFile, extraDirectories) lets you keep the font cache with your application's own files.
A runnable example of the first approach is in examples/javafx.
PdfViewer is a JavaFX node containing the page view, header bar, tool rail, side panels, dialogs, printing and signing:
PdfViewer viewer = PdfViewer.create(); // every tool and panel
viewer.setOnLink(getHostServices()::showDocument); // links open in the system browser
stage.setScene(new Scene(viewer, 1200, 900));
viewer.open(Path.of("file.pdf")); // loads in the background, asks for a password if needed
// ... and when the window closes:
viewer.dispose();To offer only some of the viewer's features, pass a ViewerFeatures and a UserLayout (see Configuring the viewer):
ViewerFeatures features = ViewerFeatures.builder(ViewerFeatures.Preset.ANNOTATING)
.add("signature.sign")
.remove("document.save-as", "annotation.ink")
.panels(SidePanel.THUMBNAILS, SidePanel.COMMENTS)
.build();
PdfViewer viewer = PdfViewer.create(features, UserLayout.load(settingsDir.resolve("layout.properties")));PdfViewer also has hooks for the parts an application usually owns, including setOnNewWindow, setOnExit, setOnPreferences, setFileHistory, setSigningStore, setPrintStore, setOnError and setDialogs.
Leave out the header, tool rail and side panel strip, and drive the viewer from your own controls. ActionControls.bind connects a button or menu item to a viewer command: it runs the command, disables the control when the command can't run, and keeps a toggle's selected state in sync (the current tool or view mode, for example).
PdfViewer viewer = PdfViewer.create(
ViewerFeatures.builder(ViewerFeatures.Preset.FULL).chrome().build(), // or chrome(Chrome.HEADER, ...)
UserLayout.inMemory());
ActionControls actions = viewer.getActions();
Button previous = actions.bind(new Button("Prev"), "navigation.previous");
Button next = actions.bind(new Button("Next"), "navigation.next");
ToggleButton highlight = actions.bind(new ToggleButton("Highlight"), "annotation.highlight");
MenuItem print = actions.bind(new MenuItem("Print…"), "document.print");
// the side panels are still available to place wherever you like
SplitPane split = new SplitPane(viewer.getCurrentDocument().getOutline(), viewer);Commands that depend on a missing part are left out automatically. For example, Find needs the header and the side panel commands need the panel strip.
PdfView alone, with your own toolbar, labels and dialogs. A DocumentSession owns the document: it opens files in the background, asks for passwords, saves safely and disposes the previous document. The viewer's commands work through a ViewerContext, which offers the view's own commands immediately and host commands such as Find, Open or Save once you supply a handler.
PdfView view = new PdfView();
DocumentSession session = new DocumentSession(view);
TextField find = new TextField();
ActionControls actions = new ActionControls(StandardActions.registry(),
ViewerContext.of(view).onFind(find::requestFocus));
actions.bind(previousButton, "navigation.previous");
actions.bind(nextButton, "navigation.next");
actions.bind(zoomInButton, "view.zoom-in");
find.setOnAction(e -> view.search(find.getText()));
actions.bind(nextHitButton, "search.next");
// every part of the view's state is a JavaFX property
pageLabel.textProperty().bind(Bindings.createStringBinding(
() -> (view.getCurrentPageIndex() + 1) + " of " + view.getPageCount(),
view.currentPageIndexProperty(), view.pageCountProperty()));
zoomLabel.textProperty().bind(view.zoomProperty().multiply(100).asString("%.0f%%"));
// opt in to behaviour PdfViewer would otherwise provide
AnnotationContextMenu.install(view, null); // right-click an annotation: colour, Properties…, Delete
SignatureFlow.install(session); // click or draw an empty signature field to sign it
actions.installAccelerators(scene, "search.find", "edit.copy", "edit.undo", "edit.redo");
session.open(Path.of("file.pdf"));Nothing is added to your scene unless you ask for it. When the page has focus, the view handles its own keys (Ctrl+A, Ctrl+C, Ctrl+Z/Y, Delete, arrows, Page Up/Down). installAccelerators makes the shortcuts you choose work from anywhere in your window, while your own text fields keep their normal Ctrl+C.
The view can also display a Document you manage yourself:
Document document = new Document();
document.setSecurityCallback(new PasswordPrompt(stage, "file.pdf")); // asks for the password, three tries
document.setFile("file.pdf");
view.setDocument(document); // dispose it yourself after setDocument(null)
view.setViewMode(ViewMode.CONTINUOUS); // SINGLE_PAGE, CONTINUOUS, FACING, FACING_CONTINUOUS
view.setFitMode(FitMode.WIDTH);
view.setToolMode(ToolMode.TEXT_SELECT);The viewer is configured in two tiers. ViewerFeatures decides what a product offers: which commands, side panels and page layouts exist, and which tools appear on the tool rail by default. UserLayout records how the user has arranged things within those limits. A user can never bring back something the product has removed.
Start from one of four presets and adjust:
| Preset | Includes |
|---|---|
READING |
Open, print, properties, search, view, navigation and the select and pan tools. No editing. |
ANNOTATING |
Reading, plus the annotation tools, undo/redo, delete and save. |
FORMS |
Reading, plus form filling, form reset, signing and save. |
FULL |
Everything. |
Commands are matched by id or by prefix ("annotation.*"). A removal always wins over an addition, regardless of order.
| Builder method | Description |
|---|---|
add(String...) |
Adds commands by id or pattern. |
remove(String...) |
Removes commands by id or pattern. |
panels(SidePanel...) |
The side panels offered. All panels if never called. |
viewModes(ViewMode...) |
The page layouts offered. All layouts if never called. |
defaultRail(String...) |
The tools on the rail, in order, before the user changes anything. |
railCandidates(String...) |
Which commands may be placed on the rail. Default tool.*, annotation.*, signature.*. |
userCustomisable(boolean) |
Whether users may rearrange the rail. Default true. |
multipleDocuments(boolean) |
Whether the viewer shows several documents as tabs. Default false. |
chrome(Chrome...) |
The chrome shown around the document. chrome() with no arguments shows none. |
The same choices can be read from a properties file with ViewerFeatures.fromProperties. Lists are comma separated, and missing keys keep the preset's value.
features.preset=ANNOTATING
features.add=signature.sign
features.remove=document.save-as, annotation.ink
features.panels=thumbnails, bookmarks, comments
features.viewModes=CONTINUOUS, FACING
features.rail=tool.select, tool.pan, annotation.highlight
features.customisable=true
features.multipleDocuments=true
features.chrome=none| Property | Values |
|---|---|
features.preset |
READING, ANNOTATING, FORMS, FULL (default) |
features.add, features.remove
|
Command ids or patterns |
features.panels |
thumbnails, bookmarks, comments, attachments, layers, signatures, search
|
features.viewModes |
SINGLE_PAGE, CONTINUOUS, FACING, FACING_CONTINUOUS
|
features.rail |
Command ids, in order |
features.customisable |
true / false
|
features.multipleDocuments |
true / false
|
features.chrome |
none, or any of header, tool-rail, side-panels
|
The stand-alone viewer reads features.properties from its settings directory (see below), or the file named by -Dorg.icepdf.fx.features=/path/features.properties. It turns tabs on unless the file sets features.multipleDocuments=false.
| Group | Ids |
|---|---|
| Document |
document.open, document.open-new-tab, document.new-tab, document.save, document.save-as, document.export-redacted, document.print, document.properties, document.close
|
| Application |
app.new-window, app.preferences, app.about, app.exit
|
| Edit |
edit.undo, edit.redo, edit.copy, edit.select-all, edit.delete-annotation
|
| Search |
search.find, search.panel, search.next, search.previous
|
| View |
view.side-panel, view.comments, view.full-screen, view.zoom-in, view.zoom-out, view.actual-size, view.fit-page, view.fit-width, view.rotate-clockwise, view.rotate-counterclockwise, view.mode.single-page, view.mode.continuous, view.mode.facing, view.mode.facing-continuous, view.cover-page, view.show-annotations, view.highlight-fields
|
| Navigation |
navigation.first, navigation.previous, navigation.next, navigation.last, navigation.go-to
|
| Tools |
tool.select, tool.pan
|
| Annotations |
annotation.highlight, annotation.underline, annotation.strike-out, annotation.note, annotation.free-text, annotation.ink, annotation.rectangle, annotation.ellipse, annotation.line, annotation.redact
|
| Signatures | signature.sign |
| Forms | forms.reset |
Your own commands can be added by registering them in an ActionRegistry and passing it to the PdfViewer constructor.
UserLayout holds the user's own arrangement: which tools are pinned to the rail and in what order, which are hidden, where the rail and side panel sit, and how the panel opens. Load it from a file with UserLayout.load(path) to keep it between runs, or use UserLayout.inMemory() when it shouldn't be saved.
A product that already configures the Swing viewer with ViewerPropertiesManager keys can reuse them. ViewerFeatures.fromSwingProperties(properties) maps the Swing viewer's application.* show/hide keys to command ids and panels; any key set to false removes what it hid. Swing settings with no JavaFX counterpart, such as status bar parts, preference tabs and the link and arrow tools, are ignored.
The stand-alone viewer keeps its settings in properties files in ~/.icepdf/fx, or in the directory named by -Dorg.icepdf.fx.viewer.home. These are kept separate from the Swing viewer, which uses the Java Preferences API.
| File | Contents |
|---|---|
viewer.properties |
Preferences: window placement, view, theme, search options, print defaults, annotation author and colours, signing, imaging and performance settings |
layout.properties |
The user's tool rail and panel arrangement |
features.properties |
Optional product features (see above) |
Most of these settings are edited through the Preferences dialog, which has General, Annotations, Search, Imaging, Performance and Fonts tabs.
The viewer supports SYSTEM, LIGHT, DARK, HIGH_CONTRAST and HIGH_CONTRAST_DARK modes through viewer.getTheme().modeProperty(). SYSTEM follows the desktop's light or dark setting and its high-contrast themes. The viewer is styled with CSS (viewer.css and pdf-view.css), so an application can override any part of it with its own stylesheet.
Icons are looked up by id through an IconProvider: command ids (document.print), side panels (panel.comments) and UI parts (ui.menu). The default set is a placeholder set drawn as SVG paths on a 24×24 grid, stroked rather than filled, following the same conventions as Lucide and Tabler so their paths can be dropped in.
To replace some or all of the icons, write a properties file of id=path data and chain it in front of the defaults:
IconProvider icons = SvgIcons.from(myIconsFile).orElse(SvgIcons.placeholders());
PdfViewer viewer = new PdfViewer(features, layout, StandardActions.registry(), icons);The icon colour comes from the theme (-fx-stroke on the icon-path style class).
The current tool decides what a primary-button drag on a page does. In any mode, a middle-button drag or holding Space while dragging pans, and the mouse wheel or a pinch zooms and scrolls.
| Tool | Command id | Behaviour |
|---|---|---|
TEXT_SELECT |
tool.select |
Drag to select text. Annotations can be selected, moved, resized and edited. |
PAN |
tool.pan |
Drag to pan the view. |
HIGHLIGHT |
annotation.highlight |
Select text; it is highlighted on release. |
UNDERLINE |
annotation.underline |
Select text; it is underlined on release. |
STRIKE_OUT |
annotation.strike-out |
Select text; it is struck out on release. |
NOTE |
annotation.note |
Click to place a sticky note and type in its popup. |
FREE_TEXT |
annotation.free-text |
Click to place a text box and type into it. |
INK |
annotation.ink |
Drag to draw freehand. |
RECTANGLE |
annotation.rectangle |
Drag to draw a rectangle. |
ELLIPSE |
annotation.ellipse |
Drag to draw an ellipse. |
LINE |
annotation.line |
Drag to draw a straight line. |
SIGNATURE |
signature.sign |
Drag to place a signature field, then sign it. |
REDACT |
annotation.redact |
Drag over text to mark it, or anywhere else to mark a region, for redaction. |
All annotation changes, including creating, moving, resizing, recolouring and deleting, can be undone and redone. Right-clicking an annotation opens a menu to change its colour, open its properties or delete it.
The redaction tool marks text and regions but doesn't change the document's content. Export Redacted Copy (document.export-redacted) writes a new copy with the marked content removed and reports what was removed. The viewer warns before closing a document with unsaved redaction marks.
Form fields are filled in place with native JavaFX editors, and Tab moves between fields in the document's order. From code, PdfView can read and set field values (setFieldValue), reset the form (resetForm), highlight fields (highlightFormFields) and report every change through setOnFormFieldChanged. Form edits take part in undo and redo.
Signatures are checked when a document opens, off the JavaFX thread. Signed fields show a validity badge, and the Signatures side panel lists each one. Clicking a signed field opens SignaturePropertiesDialog, which shows its status (valid, unknown identity or invalid), the individual checks and the certificate chain. From code, PdfView.getSignatures() returns a SignatureStatus for each field, and setOnSignatureClicked lets you handle the click yourself.
Choose the signature tool and drag to place a new signature field, or click an existing empty one. SignDialog collects the keystore (a PKCS#12 .p12/.pfx file or a PKCS#11 device), the certificate, signer details and the signature's appearance, including a drawn signature. The signed copy is written as an incremental update and opened in place of the original.
Each part also works on its own. DocumentSigning.prepare and saveSigned sign a document as it is saved, including from headless code.
The stand-alone viewer remembers signing settings (signing.* keys in viewer.properties). To create a self-signed PKCS#12 keystore for testing, see PKCS12 keystore setup; the same file works with both viewers.
PdfPrintDialog.show(view, null) opens a JavaFX print dialog with a page preview. view.print(PrintSettings) prints without a dialog. Pages are printed as vectors through Java2D. The stand-alone viewer remembers the last printer, paper, scaling, orientation and sides.
The view follows the document's permissions. copyAllowed, annotationEditingAllowed and formFillingAllowed report what the user may do, and toolbar buttons can be bound to them. DocumentSession asks for a password when needed; for a Document you open yourself, set a PasswordPrompt (or your own SecurityCallback) before opening it.
Actions with outside effects, such as URI links, Launch actions, form submission and JavaScript, are left to the application. With PdfViewer, setOnLink receives the links the user clicks. With a bare PdfView, handle them in setOnAnnotationAction:
view.setOnAnnotationAction(e -> { /* inspect e.getAction() */ });The stand-alone viewer opens http, https and mailto links in the system browser or mail client, and asks before following any other kind of link.
Shortcut is Ctrl on Windows and Linux, and ⌘ on macOS.
| Action | Shortcut |
|---|---|
| Open / Open in new tab | Shortcut+O / Shortcut+Shift+O |
| New tab / New window | Shortcut+T / Shortcut+N |
| Save / Save As | Shortcut+S / Shortcut+Shift+S |
| Shortcut+P | |
| Document properties | Shortcut+D |
| Close / Exit | Shortcut+W / Shortcut+Q |
| Preferences | Shortcut+, |
| Undo / Redo | Shortcut+Z / Shortcut+Shift+Z |
| Copy / Select all | Shortcut+C / Shortcut+A |
| Find / Search panel | Shortcut+F / Shortcut+Shift+F |
| Next / previous match | F3 / Shift+F3 |
| Go to page | Shortcut+G |
| First / last page | Home / End |
| Zoom in / out | Shortcut+= / Shortcut+- |
| Actual size / Fit page / Fit width | Shortcut+0 / Shortcut+1 / Shortcut+2 |
| Rotate clockwise / counterclockwise | Shortcut+Shift+Plus / Shortcut+Shift+Minus |
| Show comments | Shortcut+Shift+C |
| Toggle side panel | F4 |
| Full screen | F11 |
| Render devtools | Ctrl+Shift+I (⌘⌥I on macOS) |
With the text selection tool and a selection in place, the arrow keys move the caret through the text, Shift extends the selection, Shortcut+←/→ moves by word, and Home/End go to the start or end of the line. Esc clears the selection. The caret crosses page boundaries and skips pages with no text. Without a selection, the arrow keys scroll the view.
Every PdfView and thumbnail strip in the process renders on one shared pool of low-priority daemon threads, which exit when idle. Work for views in the focused window runs first, then visible tiles, annotation layers, page text, previews and thumbnails, in that order. The tile cache is limited to a share of the heap, with on-screen tiles kept.
| System property | Description |
|---|---|
org.icepdf.fx.render.threads |
Number of render threads. Default is half the cores, between 2 and the cap. |
org.icepdf.fx.render.threadPoolCap |
Maximum number of render threads. Default 8. |
org.icepdf.fx.devtools |
true opens the render devtools window at startup. |
org.icepdf.fx.viewer.home |
The stand-alone viewer's settings directory. Default ~/.icepdf/fx. |
org.icepdf.fx.features |
A features file for the stand-alone viewer. |
The core decodes images on its own pools, configured with org.icepdf.core.library.threadPoolSize, imageThreadPoolSize and threadPoolCap. The stand-alone viewer exposes these in the Performance tab of its Preferences dialog.
The render devtools window (Ctrl+Shift+I) shows the render pool's threads and queue, each view's cache and jobs, and the heap, live. It is useful for diagnosing performance on a user's machine.
The JavaFX viewer's text is in org/icepdf/fx/i18n/messages.properties, read through org.icepdf.fx.i18n.Messages. Translations are included for Danish, German, Spanish, Finnish, French, Italian, Dutch, Norwegian, Portuguese, Swedish, and Simplified and Traditional Chinese, covering the whole interface including dialogs and panels. A Swiss German bundle is also present but only partly translated.
The viewer uses the JVM's default locale. To try another language, start it with -Duser.language=fr (add -Duser.country=CN or TW for Chinese). Scripts for checking keys and syncing translations are in viewer/viewer-fx/tools/i18n.
| Swing/AWT viewer | JavaFX viewer |
|---|---|
SwingController + SwingViewBuilder
|
PdfViewer (complete) or PdfView (bare) |
Subclassing SwingViewBuilder to change the UI |
ViewerFeatures, the chrome options and your own controls bound with ActionControls
|
ViewerPropertiesManager / Java Preferences |
ViewerFeatures, UserLayout and properties files in ~/.icepdf/fx
|
AnnotationCallback |
setOnAnnotationAction / setOnLink
|
SecurityCallback / MyGUISecurityCallback
|
DocumentSession or PasswordPrompt
|
ICEpdfDefault.properties |
features.properties |
Some Swing viewer features are not yet in the JavaFX viewer, including outline and destination editing, text editing, and the link and arrow annotation tools.
- viewer-fx README: build commands, the smoke and UI test suites, and test helpers
- RENDERING_ANALYSIS.md: the rendering design and its measurements against the Swing viewer