/*
 * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javafx.print;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import javafx.stage.Window;

import com.sun.javafx.print.PrinterJobImpl;
import com.sun.javafx.tk.PrintPipeline;

PrinterJob is the starting place for JavaFX scenegraph printing.

It includes

  • Printer discovery
  • Job creation
  • Job configuration based on supported printer capabilities
  • Page setup
  • Rendering of a node hierachy to a page.

Here ia a very simple example, which prints a single node.

Node node = new Circle(100, 200, 200);
PrinterJob job = PrinterJob.createPrinterJob();
if (job != null) {
   boolean success = job.printPage(node);
   if (success) {
       job.endJob();
   }
}
Points to note

In the example above the node was not added to a scene. Since most printing scenarios are printing content that's either not displayed at all, or must be prepared and formatted differently, this is perfectly acceptable.

If content that is currently part of a Scene and is being displayed, is printed, then because printing a job or even a single page of the job may span over multiple screen "pulses" or frames, it is important for the application to ensure that the node being printed is not updated during the printing process, else partial or smeared rendering is probable.

It should be apparent that the same applies even to nodes that are not displayed - updating them concurrent with printing them is not a good idea.

There is no requirement to do printing on the FX application thread. A node may be prepared for printing on any thread, the job may be invoked on any thread. However, minimising the amount of work done on the FX application thread is generally desirable, so as not to affect the responsiveness of the application UI. So the recommendation is to perform printing on a new thread and let the implementation internally schedule any tasks that need to be performed on the FX thread to be run on that thread.

Since:JavaFX 8.0
/** * PrinterJob is the starting place for JavaFX scenegraph printing. * <p> * It includes * <ul> * <li>Printer discovery * <li>Job creation * <li>Job configuration based on supported printer capabilities * <li>Page setup * <li>Rendering of a node hierachy to a page. * </ul> * <p> * Here ia a very simple example, which prints a single node. * <pre> * Node node = new Circle(100, 200, 200); * PrinterJob job = PrinterJob.createPrinterJob(); * if (job != null) { * boolean success = job.printPage(node); * if (success) { * job.endJob(); * } * } * </pre> * <b>Points to note</b> * <p> * In the example above the node was not added to a scene. * Since most printing scenarios are printing content that's either * not displayed at all, or must be prepared and formatted differently, * this is perfectly acceptable. * <p> * If content that is currently part of a Scene and is being displayed, * is printed, then because printing a job or even a single page * of the job may span over multiple screen "pulses" or frames, it is * important for the application to ensure that the node being printed * is not updated during the printing process, else partial or smeared * rendering is probable. * <p> * It should be apparent that the same applies even to nodes that are * not displayed - updating them concurrent with printing them is not * a good idea. * <p> * There is no requirement to do printing on the FX application thread. * A node may be prepared for printing on any thread, the job may * be invoked on any thread. However, minimising the amount of work * done on the FX application thread is generally desirable, * so as not to affect the responsiveness of the application UI. * So the recommendation is to perform printing on a new thread * and let the implementation internally schedule any tasks that * need to be performed on the FX thread to be run on that thread. * * @since JavaFX 8.0 */
public final class PrinterJob { // Delegating all the work keeps whatever classes // are being used out of the API packages. private PrinterJobImpl jobImpl; private ObjectProperty<Printer> printer; private JobSettings settings;
Factory method to create a job. If there are no printers available, this will return null. Some platforms may provide a pseudo printer, which creates a document. These will be enumerated here so long as the platform also enumerates them as if they are printers.
Throws:
Returns:a new PrinterJob instance, or null.
/** * Factory method to create a job. * If there are no printers available, this will return null. * Some platforms may provide a pseudo printer, which creates * a document. These will be enumerated here so long as the * platform also enumerates them as if they are printers. * @return a new PrinterJob instance, or null. * @throws SecurityException if a job does not have permission * to initiate a printer job. */
public static final PrinterJob createPrinterJob() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkPrintJobAccess(); } Printer printer = Printer.getDefaultPrinter(); if (printer == null) { return null; } else { return new PrinterJob(printer); } }
Factory method to create a job for a specified printer.

The printer argument determines the initial printer

Params:
  • printer – to use for the job. If the printer is currently unavailable (eg offline) then this may return null.
Throws:
Returns:a new PrinterJob, or null.
/** * Factory method to create a job for a specified printer. * <p> * The <code>printer</code> argument determines the initial printer * @param printer to use for the job. If the printer is currently * unavailable (eg offline) then this may return null. * @return a new PrinterJob, or null. * @throws SecurityException if a job does not have permission * to initiate a printer job. */
public static final PrinterJob createPrinterJob(Printer printer) { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkPrintJobAccess(); } return new PrinterJob(printer); } private PrinterJob(Printer printer) { this.printer = createPrinterProperty(printer); settings = printer.getDefaultJobSettings(); settings.setPrinterJob(this); createImplJob(printer, settings); } synchronized private PrinterJobImpl createImplJob(Printer printer, JobSettings settings) { if (jobImpl == null) { jobImpl = PrintPipeline.getPrintPipeline().createPrinterJob(this); } return jobImpl; }
Updating settings or printer is only allowed on a new job, meaning before you start printing or cancel etc. The implementation needs to check this wherever job state updates are received.
/** * Updating settings or printer is only allowed on a new job, * meaning before you start printing or cancel etc. * The implementation needs to check this wherever job state * updates are received. */
boolean isJobNew() { return getJobStatus() == JobStatus.NOT_STARTED; } private ObjectProperty<Printer> createPrinterProperty(Printer printer) { return new SimpleObjectProperty<Printer>(printer) { @Override public void set(Printer value) { if (value == get() || !isJobNew()) { return; } if (value == null) { value = Printer.getDefaultPrinter(); } super.set(value); jobImpl.setPrinterImpl(value.getPrinterImpl()); settings.updateForPrinter(value); } @Override public void bind(ObservableValue<? extends Printer> rawObservable) { throw new RuntimeException("Printer property cannot be bound"); } @Override public void bindBidirectional(Property<Printer> other) { throw new RuntimeException("Printer property cannot be bound"); } @Override public Object getBean() { return PrinterJob.this; } @Override public String getName() { return "printer"; } }; }
Property representing the Printer for this job.
Returns:the Printer for this job
/** * Property representing the * <code>Printer</code> for this job. * @return the <code>Printer</code> for this job */
public final ObjectProperty<Printer> printerProperty() { /* The PrinterJob constructor always creates this property, * so it can be returned directly. */ return printer; }
Gets the printer currently associated with this job.
Returns:printer for the job.
/** * Gets the printer currently associated with this job. * @return printer for the job. */
public synchronized Printer getPrinter() { return printerProperty().get(); }
Change the printer for this job. If the new printer does not support the current job settings, (for example if DUPLEX printing is requested but the new printer does not support this), then the values are reset to the default for the new printer, or in some cases a similar value. For example this might mean REVERSE_LANDSCAPE is updated to LANDSCAPE, however this implementation optimisation is allowed, but not required.

The above applies whether the printer is changed by directly calling this method, or as a side-effect of user interaction with a print dialog.

Setting a null value for printer will install the default printer. Setting the current printer has no effect.

Params:
  • printer – to be used for this print job.
/** * Change the printer for this job. * If the new printer does not support the current job settings, * (for example if DUPLEX printing is requested but the new printer * does not support this), then the values are reset to the default * for the new printer, or in some cases a similar value. For example * this might mean REVERSE_LANDSCAPE is updated to LANDSCAPE, however * this implementation optimisation is allowed, but not required. * <p> * The above applies whether the printer is changed by directly calling * this method, or as a side-effect of user interaction with a print * dialog. * <p> * Setting a null value for printer will install the default printer. * Setting the current printer has no effect. * @param printer to be used for this print job. */
public synchronized void setPrinter(Printer printer) { printerProperty().set(printer); }
The JobSettings encapsulates all the API supported job configuration options such as number of copies, collation option, duplex option, etc. The initial values are based on the current settings for the initial printer.
Returns:current job settings.
/** * The <code>JobSettings</code> encapsulates all the API supported job * configuration options such as number of copies, * collation option, duplex option, etc. * The initial values are based on the current settings for * the initial printer. * @return current job settings. */
public synchronized JobSettings getJobSettings() { return settings; }
Displays a Print Dialog. Allow the user to update job state such as printer and settings. These changes will be available in the appropriate properties after the print dialog has returned. The print dialog is also typically used to confirm the user wants to proceed with printing. This is not binding on the application but generally should be obeyed.

In the case that there is no UI available then this method returns true, with no options changed, as if the user had confirmed to proceed with printing.

If the job is not in a state to display the dialog, such as already printing, cancelled or done, then the dialog will not be displayed and the method will return false.

The window owner may be null, but if it is a visible Window, it will be used as the parent.

This method may be called from any thread. If it is called from the JavaFX application thread, then it must either be called from an input event handler or from the run method of a Runnable passed to Platform.runLater. It must not be called during animation or layout processing.

Params:
  • owner – to which to block input, or null.
Throws:
Returns:false if the user opts to cancel printing, or the job is not in the new state. That is if it has already started, has failed, or has been cancelled, or ended.
/** * Displays a Print Dialog. * Allow the user to update job state such as printer and settings. * These changes will be available in the appropriate properties * after the print dialog has returned. * The print dialog is also typically used to confirm the user * wants to proceed with printing. This is not binding on the * application but generally should be obeyed. * <p> * In the case that there is no UI available then this method * returns true, with no options changed, as if the user had * confirmed to proceed with printing. * <p> * If the job is not in a state to display the dialog, such * as already printing, cancelled or done, then the dialog will * not be displayed and the method will return false. * <p> * The window <code>owner</code> may be null, but * if it is a visible Window, it will be used as the parent. * <p> * This method may be called from any thread. If it is called from the * JavaFX application thread, then it must either be called from an input * event handler or from the run method of a Runnable passed to * {@link javafx.application.Platform#runLater Platform.runLater}. * It must not be called during animation or layout processing. * * @param owner to which to block input, or null. * @return false if the user opts to cancel printing, or the job * is not in the new state. That is if it has already started, * has failed, or has been cancelled, or ended. * @throws IllegalStateException if this method is called during * animation or layout processing. */
public synchronized boolean showPrintDialog(Window owner) { // TBD handle owner if (!isJobNew()) { return false; } else { return jobImpl.showPrintDialog(owner); } }
Displays a Page Setup dialog. A page set up dialog is primarily to allow an end user to configure the layout of a page. Paper size and orientation are the most common and most important components of this.

This will display the most appropriate available dialog for this purpose. However there may be still be access to other settings, including changing the current printer. Therefore a side effect of this dialog display method may be to update that and any other current job settings. The method returns true if the user confirmed the dialog whether or not any changes are made.

If the job is not in a state to display the dialog, such as already printing, cancelled or done, then the dialog will not be displayed and the method will return false.

The window owner may be null, but if it is a visible Window, it will be used as the parent.

This method may be called from any thread. If it is called from the FX application thread, then it must either be called from an input event handler or from the run method of a Runnable passed to Platform.runLater. It must not be called during animation or layout processing.

Params:
  • owner – to block input, or null.
Throws:
Returns:false if the user opts to cancel the dialog, or the job is not in the new state. That is if it has already started, has failed, or has been cancelled, or ended.
/** * Displays a Page Setup dialog. * A page set up dialog is primarily to allow an end user * to configure the layout of a page. Paper size and orientation * are the most common and most important components of this. * <p> * This will display the most appropriate available dialog for * this purpose. * However there may be still be access to other settings, * including changing the current printer. * Therefore a side effect of this dialog display method may be to * update that and any other current job settings. * The method returns true if the user confirmed the dialog whether or * not any changes are made. * <p> * If the job is not in a state to display the dialog, such * as already printing, cancelled or done, then the dialog will * not be displayed and the method will return false. * <p> * The window <code>owner</code> may be null, but * if it is a visible Window, it will be used as the parent. * <p> * This method may be called from any thread. If it is called from the FX * application thread, then it must either be called from an input event * handler or from the run method of a Runnable passed to * {@link javafx.application.Platform#runLater Platform.runLater}. * It must not be called during animation or layout processing. * * @param owner to block input, or null. * @return false if the user opts to cancel the dialog, or the job * is not in the new state. That is if it has already started, * has failed, or has been cancelled, or ended. * @throws IllegalStateException if this method is called during * animation or layout processing. */
public synchronized boolean showPageSetupDialog(Window owner) { // TBD handle owner if (!isJobNew()) { return false; } else { return jobImpl.showPageDialog(owner); } }
This method can be used to check if a page configuration is possible in the current job configuration. For example if the specified paper size is supported. If the original PageLayout is supported it will be returned. If not, a new PageLayout will be returned that attempts to honour the supplied PageLayout, but adjusted to match the current job configuration.

This method does not update the job configuration.

Params:
  • pageLayout – to be validated
Throws:
Returns:a PageLayout that is supported in the current job configuration.
/** * This method can be used to check if a page configuration * is possible in the current job configuration. For example * if the specified paper size is supported. If the original * PageLayout is supported it will be returned. If not, a new PageLayout * will be returned that attempts to honour the supplied * PageLayout, but adjusted to match the current job configuration. * <p> * This method does not update the job configuration. * @param pageLayout to be validated * @return a <code>PageLayout</code> that is supported in the * current job configuration. * @throws NullPointerException if the pageLayout parameter is null. */
synchronized PageLayout validatePageLayout(PageLayout pageLayout) { if (pageLayout == null) { throw new NullPointerException("pageLayout cannot be null"); } return jobImpl.validatePageLayout(pageLayout); }
Print the specified node using the specified page layout. The page layout will override the job default for this page only. If the job state is CANCELED, ERROR or DONE, this method will return false.

This method may be called from any thread. If it is called from the FX application thread, then it must either be called from an input event handler or from the run method of a Runnable passed to Platform.runLater. It must not be called during animation or layout processing.

Params:
  • pageLayout – Layout for this page.
  • node – The node to print.
Throws:
Returns:whether rendering was successful.
/** * Print the specified node using the specified page layout. * The page layout will override the job default for this page only. * If the job state is CANCELED, ERROR or DONE, this method will * return false. * <p> * This method may be called from any thread. If it is called from the FX * application thread, then it must either be called from an input event * handler or from the run method of a Runnable passed to * {@link javafx.application.Platform#runLater Platform.runLater}. * It must not be called during animation or layout processing. * * @param pageLayout Layout for this page. * @param node The node to print. * @return whether rendering was successful. * @throws NullPointerException if either parameter is null. * @throws IllegalStateException if this method is called during * animation or layout processing. */
public synchronized boolean printPage(PageLayout pageLayout, Node node) { if (jobStatus.get().ordinal() > JobStatus.PRINTING.ordinal()) { return false; } if (jobStatus.get() == JobStatus.NOT_STARTED) { jobStatus.set(JobStatus.PRINTING); } if (pageLayout == null || node == null) { jobStatus.set(JobStatus.ERROR); throw new NullPointerException("Parameters cannot be null"); } boolean rv = jobImpl.print(pageLayout, node); if (!rv) { jobStatus.set(JobStatus.ERROR); } return rv; }
Print the specified node. The page layout is the job default. If the job state is CANCELED, ERROR or DONE, this method will return false.
Params:
  • node – The node to print.
Throws:
Returns:whether rendering was successful.
/** * Print the specified node. The page layout is the job default. * If the job state is CANCELED, ERROR or DONE, this method will * return false. * @param node The node to print. * @return whether rendering was successful. * @throws NullPointerException if the node parameter is null. */
public synchronized boolean printPage(Node node) { return printPage(settings.getPageLayout(), node); }
An enum class used in reporting status of a print job. Applications can listen to the job status via the jobStatus property, or may query it directly using getJobStatus().

The typical life cycle of a job is as follows :

  • job will be created with status NOT_STARTED and will stay there during configuration via dialogs etc.
  • job will enter state PRINTING when the first page is printed.
  • job will enter state DONE once the job is successfully completed without being cancelled or encountering an error. The job is now completed.
  • A job that encounters an ERROR or is CANCELED is also considered completed.

A job may not revert to an earlier status in its life cycle and the current job state affects operations that may be performed. For example a job may not begin printing again if it has previously passed that state and entered any of the termination states.

Since:JavaFX 8.0
/** * An enum class used in reporting status of a print job. * Applications can listen to the job status via the * {@link #jobStatusProperty() jobStatus} property, or may query it directly * using {@link PrinterJob#getJobStatus() getJobStatus()}. * <p> * The typical life cycle of a job is as follows : * <ul> * <li>job will be created with status <code>NOT_STARTED</code> and * will stay there during configuration via dialogs etc. * <li>job will enter state <code>PRINTING</code> when the first page * is printed. * <li>job will enter state <code>DONE</code> once the job is * successfully completed without being cancelled or encountering * an error. The job is now completed. * <li>A job that encounters an <code>ERROR</code> or is * <code>CANCELED</code> is also considered completed. * </ul> * <p> * A job may not revert to an earlier status in its life cycle and * the current job state affects operations that may be performed. * For example a job may not begin printing again if it has previously * passed that state and entered any of the termination states. * * @since JavaFX 8.0 */
public static enum JobStatus {
The new job status. May display print dialogs and configure the job and initiate printing.
/** * The new job status. May display print dialogs and * configure the job and initiate printing. */
NOT_STARTED,
The job has requested to print at least one page, and has not terminated printing. May no longer display print dialogs.
/** * The job has requested to print at least one page, * and has not terminated printing. May no longer * display print dialogs. */
PRINTING,
The job has been cancelled by the application. May not display dialogs or initiate printing. Job should be discarded. There is no need to call endJob().
/** * The job has been cancelled by the application. * May not display dialogs or initiate printing. * Job should be discarded. There is no need to * call endJob(). */
CANCELED,
The job encountered an error. Job should be discarded. There is no need to call endJob().
/** * The job encountered an error. * Job should be discarded. There is no need to * call endJob(). */
ERROR,
The job initiated printing and later called endJob() which reported success. The job can be discarded as it cannot be re-used.
/** * The job initiated printing and later called endJob() * which reported success. The job can be discarded * as it cannot be re-used. */
DONE }; private ReadOnlyObjectWrapper<JobStatus> jobStatus = new ReadOnlyObjectWrapper(JobStatus.NOT_STARTED);
A read only object property representing the current JobStatus
Returns:the current JobStatus
/** * A read only object property representing the current * <code>JobStatus</code> * @return the current <code>JobStatus</code> */
public ReadOnlyObjectProperty<JobStatus> jobStatusProperty() { return jobStatus.getReadOnlyProperty(); }
Obtain the current status of the job.
Returns:the current JobStatus
/** * Obtain the current status of the job. * @return the current <code>JobStatus</code> */
public JobStatus getJobStatus() { return jobStatus.get(); }
Cancel the underlying print job at the earliest opportunity. It may return immediately without waiting for the job cancellation to be complete in case this would block the FX user thread for any period of time. If printing is in process at that time, then typically this means cancellation is after the current page is rendered. The job status is updated to CANCELED only once that has happened. Thus determining that the job is CANCELED requires monitoring the job status.

The call has no effect if the job has already been requested to be CANCELED, or is in the state ERROR or DONE. For example it will not de-queue from the printer a job that has already been spooled for printing. Once a job is cancelled, it is not valid to call any methods which render new content or change job state.

/** * Cancel the underlying print job at the earliest opportunity. * It may return immediately without waiting for the job cancellation * to be complete in case this would block the FX user thread * for any period of time. * If printing is in process at that time, then typically * this means cancellation is after the current page is rendered. * The job status is updated to CANCELED only once that has happened. * Thus determining that the job is CANCELED requires monitoring * the job status. * <p> * The call has no effect if the job has already been requested * to be CANCELED, or is in the state ERROR or DONE. * For example it will not de-queue from the printer a job that * has already been spooled for printing. * Once a job is cancelled, it is not valid to call any methods * which render new content or change job state. */
public void cancelJob() { if (jobStatus.get().ordinal() <= JobStatus.PRINTING.ordinal()) { jobStatus.set(JobStatus.CANCELED); jobImpl.cancelJob(); } }
If the job can be successfully spooled to the printer queue this will return true. Note : this does not mean the job already printed as that could entail waiting for minutes or longer, even where implementable.

A return value of false means the job could not be spooled, or was already completed.

Successful completion will also update job status to DONE, at which point the job can no longer be used.

Calling endJob() on a job for which no pages have been printed is equivalent to calling {code cancelJob()}.

Returns:true if job is spooled, false if its not, or the job was already in a completed state.
/** * If the job can be successfully spooled to the printer queue * this will return true. Note : this does not mean the job already * printed as that could entail waiting for minutes or longer, * even where implementable. * <p> * A return value of false means the job could not be spooled, * or was already completed. * <p> * Successful completion will also update job status to <code>DONE</code>, * at which point the job can no longer be used. * <p> * Calling endJob() on a job for which no pages have been printed * is equivalent to calling {code cancelJob()}. * @return true if job is spooled, false if its not, or the job * was already in a completed state. */
public synchronized boolean endJob() { if (jobStatus.get() == JobStatus.NOT_STARTED) { cancelJob(); return false; } else if (jobStatus.get() == JobStatus.PRINTING) { boolean rv = jobImpl.endJob(); jobStatus.set(rv ? JobStatus.DONE : JobStatus.ERROR); return rv; } else { return false; } } @Override public String toString() { return "JavaFX PrinterJob " + getPrinter() + "\n" + getJobSettings() + "\n" + "Job Status = " + getJobStatus(); } }