com.sun.prism.j2d.print.J2DPrinterJob Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2013, 2022, 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 com.sun.prism.j2d.print;
import javafx.print.Collation;
import javafx.print.JobSettings;
import javafx.print.PageLayout;
import javafx.print.PageOrientation;
import javafx.print.PageRange;
import javafx.print.Paper;
import javafx.print.PaperSource;
import javafx.print.PrintColor;
import javafx.print.PrintResolution;
import javafx.print.PrintSides;
import javafx.print.Printer;
import javafx.print.Printer.MarginType;
import javafx.print.PrinterAttributes;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Window;
import javax.print.PrintService;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttribute;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.ResolutionSyntax;
import javax.print.attribute.Size2DSyntax;
import javax.print.attribute.standard.Chromaticity;
import javax.print.attribute.standard.Copies;
import javax.print.attribute.standard.Destination;
import javax.print.attribute.standard.DialogTypeSelection;
import javax.print.attribute.standard.Media;
import javax.print.attribute.standard.MediaPrintableArea;
import javax.print.attribute.standard.MediaSize;
import javax.print.attribute.standard.MediaSizeName;
import javax.print.attribute.standard.MediaTray;
import javax.print.attribute.standard.OrientationRequested;
import javax.print.attribute.standard.PageRanges;
import javax.print.attribute.standard.PrintQuality;
import javax.print.attribute.standard.PrinterResolution;
import javax.print.attribute.standard.SheetCollate;
import javax.print.attribute.standard.Sides;
import java.awt.*;
import java.awt.print.PageFormat;
import java.awt.print.Pageable;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.Set;
import com.sun.glass.ui.Application;
import com.sun.javafx.PlatformUtil;
import com.sun.javafx.print.PrintHelper;
import com.sun.javafx.print.PrinterImpl;
import com.sun.javafx.print.PrinterJobImpl;
import com.sun.javafx.scene.NodeHelper;
import com.sun.javafx.sg.prism.NGNode;
import com.sun.javafx.stage.WindowHelper;
import com.sun.javafx.tk.TKStage;
import com.sun.javafx.tk.Toolkit;
import com.sun.glass.utils.NativeLibLoader;
import com.sun.prism.impl.PrismSettings;
import com.sun.prism.j2d.PrismPrintGraphics;
import java.lang.reflect.Constructor;
import java.security.AccessController;
import java.security.PrivilegedAction;
public class J2DPrinterJob implements PrinterJobImpl {
static {
@SuppressWarnings("removal")
var dummy = AccessController.doPrivileged((PrivilegedAction) () -> {
String libName = "prism_common";
if (PrismSettings.verbose) {
System.out.println("Loading Prism common native library ...");
}
NativeLibLoader.loadLibrary(libName);
if (PrismSettings.verbose) {
System.out.println("\tsucceeded.");
}
return null;
});
}
javafx.print.PrinterJob fxPrinterJob;
java.awt.print.PrinterJob pJob2D;
javafx.print.Printer fxPrinter;
J2DPrinter j2dPrinter;
private JobSettings settings;
private PrintRequestAttributeSet printReqAttrSet;
private volatile Object elo = null;
private static Class onTopClass = null;
@SuppressWarnings("removal")
PrintRequestAttribute getAlwaysOnTop(final long id) {
return AccessController.doPrivileged(
(PrivilegedAction) () -> {
PrintRequestAttribute alwaysOnTop = null;
try {
if (onTopClass == null) {
onTopClass =
Class.forName("javax.print.attribute.standard.DialogOwner");
}
if (id == 0) {
Constructor
cons = onTopClass.getConstructor();
alwaysOnTop = cons.newInstance();
} else {
alwaysOnTop = getAlwaysOnTop(onTopClass, id);
}
} catch (Throwable t) {
}
return alwaysOnTop;
});
}
private static native
PrintRequestAttribute getAlwaysOnTop(Class onTopClass, long id);
public J2DPrinterJob(javafx.print.PrinterJob fxJob) {
fxPrinterJob = fxJob;
fxPrinter = fxPrinterJob.getPrinter();
j2dPrinter = getJ2DPrinter(fxPrinter);
settings = fxPrinterJob.getJobSettings();
pJob2D = java.awt.print.PrinterJob.getPrinterJob();
try {
pJob2D.setPrintService(j2dPrinter.getService());
} catch (PrinterException pe) {
}
printReqAttrSet = new HashPrintRequestAttributeSet();
printReqAttrSet.add(DialogTypeSelection.NATIVE);
j2dPageable = new J2DPageable();
pJob2D.setPageable(j2dPageable);
}
private void setEnabledState(Window owner, boolean state) {
if (owner == null) {
return;
}
final TKStage stage = WindowHelper.getPeer(owner);
if (stage == null) { // just in case.
return;
}
Application.invokeAndWait(() -> stage.setEnabled(state));
}
@Override
public boolean showPrintDialog(Window owner) {
if (jobRunning || jobDone || jobCanceled) {
return false;
}
if (GraphicsEnvironment.isHeadless()) {
return true;
}
if (onTopClass != null) {
printReqAttrSet.remove(onTopClass);
}
if (owner != null) {
long id = 0L;
if (PlatformUtil.isWindows()) {
id = WindowHelper.getPeer(owner).getRawHandle();
}
PrintRequestAttribute alwaysOnTop = getAlwaysOnTop(id);
if (alwaysOnTop != null) {
printReqAttrSet.add(alwaysOnTop);
}
}
boolean rv = false;
syncSettingsToAttributes();
try {
setEnabledState(owner, false);
if (!Toolkit.getToolkit().isFxUserThread()) {
rv = pJob2D.printDialog(printReqAttrSet);
} else {
// If we are on the event thread, we need to check whether
// we are allowed to call a nested event handler.
if (!Toolkit.getToolkit().canStartNestedEventLoop()) {
throw new IllegalStateException(
"Printing is not allowed during animation or layout processing");
}
rv = showPrintDialogWithNestedLoop(owner);
}
if (rv) {
updateSettingsFromDialog();
}
} finally {
setEnabledState(owner, true);
}
return rv;
}
private class PrintDialogRunnable implements Runnable {
@Override
public void run() {
boolean rv = false;
try {
rv = pJob2D.printDialog(printReqAttrSet);
} catch (Exception e) {
} finally {
Application.invokeLater(new ExitLoopRunnable(this, rv));
}
}
}
private boolean showPrintDialogWithNestedLoop(Window owner) {
PrintDialogRunnable dr = new PrintDialogRunnable();
Thread prtThread = new Thread(dr, "FX Print Dialog Thread");
prtThread.start();
// the nested event loop will return after the runnable exits.
Object rv = Toolkit.getToolkit().enterNestedEventLoop(dr);
boolean rvbool = false;
try {
rvbool = ((Boolean)rv).booleanValue();
} catch (Exception e) {
}
return rvbool;
}
@Override
public boolean showPageDialog(Window owner) {
if (jobRunning || jobDone || jobCanceled) {
return false;
}
if (GraphicsEnvironment.isHeadless()) {
return true;
}
if (onTopClass != null) {
printReqAttrSet.remove(onTopClass);
}
if (owner != null) {
long id = 0L;
if (PlatformUtil.isWindows()) {
id = WindowHelper.getPeer(owner).getRawHandle();
}
PrintRequestAttribute alwaysOnTop = getAlwaysOnTop(id);
if (alwaysOnTop != null) {
printReqAttrSet.add(alwaysOnTop);
}
}
boolean rv = false;
syncSettingsToAttributes();
try {
setEnabledState(owner, false);
if (!Toolkit.getToolkit().isFxUserThread()) {
PageFormat pf = pJob2D.pageDialog(printReqAttrSet);
rv = pf != null;
} else {
// If we are on the event thread, we need to check whether
// we are allowed to call a nested event handler.
if (!Toolkit.getToolkit().canStartNestedEventLoop()) {
throw new IllegalStateException(
"Printing is not allowed during animation or layout processing");
}
rv = showPageDialogFromNestedLoop(owner);
}
} finally {
setEnabledState(owner, true);
}
if (rv) {
updateSettingsFromDialog();
}
return rv;
}
private class PageDialogRunnable implements Runnable {
@Override
public void run() {
PageFormat pf = null;
try {
pf = pJob2D.pageDialog(printReqAttrSet);
} catch (Exception e) {
} finally {
Boolean rv = Boolean.valueOf(pf != null);
Application.invokeLater(new ExitLoopRunnable(this, rv));
}
}
}
private boolean showPageDialogFromNestedLoop(Window owner) {
PageDialogRunnable dr = new PageDialogRunnable();
Thread prtThread = new Thread(dr, "FX Page Setup Dialog Thread");
prtThread.start();
// the nested event loop will return after the runnable exits.
Object rv = Toolkit.getToolkit().enterNestedEventLoop(dr);
boolean rvbool = false;
try {
rvbool = ((Boolean)rv).booleanValue();
} catch (Exception e) {
}
return rvbool;
}
/*
* The update-Foo methods here are only used to update the
* FX JobSettings as a result of changes by user interaction
* with a print dialog. The new values are stored in the
* PrintRequestAttributeSet and pulled from there in to the
* equivalent FX public API JobSettings.
*/
private void updateJobName() {
String name = pJob2D.getJobName();
if (!name.equals(settings.getJobName())) {
settings.setJobName(name);
}
}
private void updateOutputFile() {
Destination dest =
(Destination)printReqAttrSet.get(Destination.class);
if (dest != null) {
settings.setOutputFile(dest.getURI().getPath());
} else {
settings.setOutputFile("");
}
}
private void updateCopies() {
int nCopies = pJob2D.getCopies();
if (settings.getCopies() != nCopies) {
settings.setCopies(nCopies);
}
}
private void updatePageRanges() {
PageRanges ranges = (PageRanges)printReqAttrSet.get(PageRanges.class);
// JDK sets default to 1,Integer.MAX_VALUE
// So in this case I think we can just check for non-null and
// only set if its non-null.
if (ranges != null) {
int[][] members = ranges.getMembers();
if (members.length == 1) {
PageRange range = new PageRange(members[0][0], members[0][1]);
settings.setPageRanges(range);
} else if (members.length > 0) {
try {
ArrayList prList = new ArrayList<>();
int last = 0;
for (int i=0; i printerSet = Printer.getAllPrinters();
for (Printer p : printerSet) {
J2DPrinter p2d = (J2DPrinter)PrintHelper.getPrinterImpl(p);
PrintService s = p2d.getService();
if (s.equals(service)) {
return p;
}
}
return fxPrinter; // current printer.
}
@Override
public void setPrinterImpl(PrinterImpl impl) {
j2dPrinter = (J2DPrinter)impl;
fxPrinter = j2dPrinter.getPrinter();
try {
pJob2D.setPrintService(j2dPrinter.getService());
} catch (PrinterException pe) {
}
}
@Override
public PrinterImpl getPrinterImpl() {
return j2dPrinter;
}
private J2DPrinter getJ2DPrinter(Printer printer) {
return (J2DPrinter)PrintHelper.getPrinterImpl(printer);
}
public Printer getPrinter() {
return fxPrinter;
}
public void setPrinter(Printer printer) {
fxPrinter = printer;
j2dPrinter = getJ2DPrinter(printer);
try {
pJob2D.setPrintService(j2dPrinter.getService());
} catch (PrinterException pe) {
}
}
private void updatePrinter() {
PrintService currService = j2dPrinter.getService();
PrintService jobService = pJob2D.getPrintService();
if (currService.equals(jobService)) {
return; // no change
}
Printer newFXPrinter = getFXPrinterForService(jobService);
// The public setPrinter call also updates the job to be valid for
// the new printer. Any old values not supported will be updated
// to supported values. If we do that, then apply the new user
// settings, any listener will see both sets of changes.
// Its best to just see the single transition.
fxPrinterJob.setPrinter(newFXPrinter);
}
private void updateSettingsFromDialog() {
updatePrinter();
updateJobName();
updateOutputFile();
updateCopies();
updatePageRanges();
updateSides();
updateCollation();
updatePageLayout();
updatePaperSource();
updateColor();
updatePrintQuality();
updatePrintResolution();
}
private void syncSettingsToAttributes() {
syncJobName();
syncOutputFile();
syncCopies();
syncPageRanges();
syncSides();
syncCollation();
syncPageLayout();
syncPaperSource();
syncColor();
syncPrintQuality();
syncPrintResolution();
}
private void syncJobName() {
pJob2D.setJobName(settings.getJobName());
}
private void syncOutputFile() {
printReqAttrSet.remove(Destination.class);
String file = settings.getOutputFile();
if (file != null && !file.isEmpty()) {
// check SE, check access ?
URI uri = (new File(file)).toURI();
Destination d = new Destination(uri);
printReqAttrSet.add(d);
}
}
private void syncCopies() {
pJob2D.setCopies(settings.getCopies());
printReqAttrSet.add(new Copies(settings.getCopies()));
}
private void syncPageRanges() {
printReqAttrSet.remove(PageRanges.class);
PageRange[] prArr = settings.getPageRanges();
if (prArr != null && prArr.length>0) {
int len = prArr.length;
int[][] ranges = new int[len][2];
for (int i=0;i currPageIndex) {
nextPage = waitForNextPage(pageIndex);
}
return nextPage;
}
@Override
public int print(Graphics g, PageFormat pf, int pageIndex) {
if (jobError || jobCanceled || jobDone && !getPage(pageIndex)) {
return Printable.NO_SUCH_PAGE;
}
int x = (int)pf.getImageableX();
int y = (int)pf.getImageableY();
int w = (int)pf.getImageableWidth();
int h = (int)pf.getImageableHeight();
Node appNode = currPageInfo.getNode();
g.translate(x, y);
printNode(appNode, g, w, h);
return Printable.PAGE_EXISTS;
}
private void printNode(Node node, Graphics g, int w, int h) {
PrismPrintGraphics ppg =
new PrismPrintGraphics((Graphics2D) g, w, h);
NGNode pgNode = NodeHelper.getPeer(node);
boolean errored = false;
try {
pgNode.render(ppg);
} catch (Throwable t) {
if (com.sun.prism.impl.PrismSettings.debug) {
System.err.println("printNode caught exception.");
t.printStackTrace();
}
errored = true;
}
ppg.getResourceFactory()
.getTextureResourcePool()
.freeDisposalRequestedAndCheckResources(errored);
}
@Override
public Printable getPrintable(int pageIndex) {
getPage(pageIndex);
return this;
}
@Override
public PageFormat getPageFormat(int pageIndex) {
getPage(pageIndex);
return currPageFormat;
}
/*
* Since we return unknown number of pages, then
* the behaviour must be that we can only signal
* end of the job by returning NO_SUCH_PAGE from
* the print(..) method.
*/
@Override
public int getNumberOfPages() {
return Pageable.UNKNOWN_NUMBER_OF_PAGES;
}
/*
* Executed on the application's thread.
* Messages over to the printing thread.
*/
private void implPrintPage(PageLayout pageLayout, Node node) {
/* The public API printPage() is synchronized, so we know
* that the app can't call it from 2 threads at the same
* time, not that this is encouraged either.
* Therefore when we are in this code, we know that any
* previous page rendering has completed.
* We also know that this means the app can't have 'queued up'
* pages.
* So, when we are in here, we know that the app is providing
* the info for the next page.
*/
pageDone = false;
synchronized (monitor) {
newPageInfo = new PageInfo(pageLayout, node);
monitor.notify();
}
if (Toolkit.getToolkit().isFxUserThread()) {
elo = new Object();
Toolkit.getToolkit().enterNestedEventLoop(elo);
elo = null;
} else {
while (!pageDone && !jobDone && !jobCanceled && !jobError) {
synchronized (monitor) {
try {
if (!pageDone) {
monitor.wait(1000);
}
} catch (InterruptedException e) {
}
}
}
}
}
} /* END J2DPageable class */
@Override
public boolean endJob() {
if (jobRunning && !jobDone && !jobCanceled && !jobError) {
jobDone = true;
try {
synchronized (monitor) {
monitor.notify();
return jobDone;
}
} catch (IllegalStateException e) {
if (com.sun.prism.impl.PrismSettings.debug) {
System.err.println("Internal Error " + e);
}
}
} else {
return jobDone && !jobError;
}
return jobDone;
}
@Override
public void cancelJob() {
if (!pJob2D.isCancelled()) {
pJob2D.cancel();
}
jobCanceled = true;
if (jobRunning) {
jobRunning = false;
try {
synchronized (monitor) {
monitor.notify();
}
} catch (IllegalStateException e) {
if (com.sun.prism.impl.PrismSettings.debug) {
System.err.println("Internal Error " + e);
}
}
}
}
}