/*
* 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: - SecurityException – if a job does not have permission
to initiate a printer job.
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: - SecurityException – if a job does not have permission
to initiate a printer job.
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: - IllegalStateException – if this method is called during
animation or layout processing.
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: - IllegalStateException – if this method is called during
animation or layout processing.
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: - NullPointerException – if the pageLayout parameter is null.
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: - NullPointerException – if either parameter is null.
- IllegalStateException – if this method is called during
animation or layout processing.
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: - NullPointerException – if the node parameter is null.
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();
}
}