blob: 4de4d57899dcf82a7ebc666a630dfd46f8e53cc0 [file] [log] [blame]
/*
* Copyright (c) 2011, 2015, 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 sun.lwawt.macosx;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.print.*;
import java.security.AccessController;
import java.security.PrivilegedAction;
import javax.print.*;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.HashPrintRequestAttributeSet;
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.PageRanges;
import sun.java2d.*;
import sun.print.*;
public final class CPrinterJob extends RasterPrinterJob {
// NOTE: This uses RasterPrinterJob as a base, but it doesn't use
// all of the RasterPrinterJob functions. RasterPrinterJob will
// break down printing to pieces that aren't necessary under MacOSX
// printing, such as controlling the # of copies and collating. These
// are handled by the native printing. RasterPrinterJob is kept for
// future compatibility and the state keeping that it handles.
private static String sShouldNotReachHere = "Should not reach here.";
private volatile SecondaryLoop printingLoop;
private boolean noDefaultPrinter = false;
private static Font defaultFont;
// This is the NSPrintInfo for this PrinterJob. Protect multi thread
// access to it. It is used by the pageDialog, jobDialog, and printLoop.
// This way the state of these items is shared across these calls.
// PageFormat data is passed in and set on the fNSPrintInfo on a per call
// basis.
private long fNSPrintInfo = -1;
private Object fNSPrintInfoLock = new Object();
static {
// AWT has to be initialized for the native code to function correctly.
Toolkit.getDefaultToolkit();
}
/**
* Presents a dialog to the user for changing the properties of
* the print job.
* This method will display a native dialog if a native print
* service is selected, and user choice of printers will be restricted
* to these native print services.
* To present the cross platform print dialog for all services,
* including native ones instead use
* <code>printDialog(PrintRequestAttributeSet)</code>.
* <p>
* PrinterJob implementations which can use PrintService's will update
* the PrintService for this PrinterJob to reflect the new service
* selected by the user.
* @return <code>true</code> if the user does not cancel the dialog;
* <code>false</code> otherwise.
* @exception HeadlessException if GraphicsEnvironment.isHeadless()
* returns true.
* @see java.awt.GraphicsEnvironment#isHeadless
*/
@Override
public boolean printDialog() throws HeadlessException {
if (GraphicsEnvironment.isHeadless()) {
throw new HeadlessException();
}
if (noDefaultPrinter) {
return false;
}
if (attributes == null) {
attributes = new HashPrintRequestAttributeSet();
}
if (getPrintService() instanceof StreamPrintService) {
return super.printDialog(attributes);
}
return jobSetup(getPageable(), checkAllowedToPrintToFile());
}
/**
* Displays a dialog that allows modification of a
* <code>PageFormat</code> instance.
* The <code>page</code> argument is used to initialize controls
* in the page setup dialog.
* If the user cancels the dialog then this method returns the
* original <code>page</code> object unmodified.
* If the user okays the dialog then this method returns a new
* <code>PageFormat</code> object with the indicated changes.
* In either case, the original <code>page</code> object is
* not modified.
* @param page the default <code>PageFormat</code> presented to the
* user for modification
* @return the original <code>page</code> object if the dialog
* is cancelled; a new <code>PageFormat</code> object
* containing the format indicated by the user if the
* dialog is acknowledged.
* @exception HeadlessException if GraphicsEnvironment.isHeadless()
* returns true.
* @see java.awt.GraphicsEnvironment#isHeadless
* @since 1.2
*/
@Override
public PageFormat pageDialog(PageFormat page) throws HeadlessException {
if (GraphicsEnvironment.isHeadless()) {
throw new HeadlessException();
}
if (noDefaultPrinter) {
return page;
}
if (getPrintService() instanceof StreamPrintService) {
return super.pageDialog(page);
}
PageFormat pageClone = (PageFormat) page.clone();
boolean doIt = pageSetup(pageClone, null);
return doIt ? pageClone : page;
}
/**
* Clones the <code>PageFormat</code> argument and alters the
* clone to describe a default page size and orientation.
* @param page the <code>PageFormat</code> to be cloned and altered
* @return clone of <code>page</code>, altered to describe a default
* <code>PageFormat</code>.
*/
@Override
public PageFormat defaultPage(PageFormat page) {
PageFormat newPage = (PageFormat)page.clone();
getDefaultPage(newPage);
return newPage;
}
@Override
protected void setAttributes(PrintRequestAttributeSet attributes) throws PrinterException {
super.setAttributes(attributes);
if (attributes == null) {
return;
}
// See if this has an NSPrintInfo in it.
NSPrintInfo nsPrintInfo = (NSPrintInfo)attributes.get(NSPrintInfo.class);
if (nsPrintInfo != null) {
fNSPrintInfo = nsPrintInfo.getValue();
}
PageRanges pageRangesAttr = (PageRanges)attributes.get(PageRanges.class);
if (isSupportedValue(pageRangesAttr, attributes)) {
SunPageSelection rangeSelect = (SunPageSelection)attributes.get(SunPageSelection.class);
// If rangeSelect is not null, we are using AWT's print dialog that has
// All, Selection, and Range radio buttons
if (rangeSelect == null || rangeSelect == SunPageSelection.RANGE) {
int[][] range = pageRangesAttr.getMembers();
// setPageRange will set firstPage and lastPage as called in getFirstPage
// and getLastPage
setPageRange(range[0][0] - 1, range[0][1] - 1);
}
}
}
volatile boolean onEventThread;
@Override
protected void cancelDoc() throws PrinterAbortException {
super.cancelDoc();
if (printingLoop != null) {
printingLoop.exit();
}
}
private void completePrintLoop() {
Runnable r = new Runnable() { public void run() {
synchronized(this) {
performingPrinting = false;
}
if (printingLoop != null) {
printingLoop.exit();
}
}};
if (onEventThread) {
try { EventQueue.invokeAndWait(r); } catch (Exception e) { e.printStackTrace(); }
} else {
r.run();
}
}
@Override
public void print(PrintRequestAttributeSet attributes) throws PrinterException {
// NOTE: Some of this code is copied from RasterPrinterJob.
// this code uses javax.print APIs
// this will make it print directly to the printer
// this will not work if the user clicks on the "Preview" button
// However if the printer is a StreamPrintService, its the right path.
PrintService psvc = getPrintService();
if (psvc == null) {
throw new PrinterException("No print service found.");
}
if (psvc instanceof StreamPrintService) {
spoolToService(psvc, attributes);
return;
}
setAttributes(attributes);
// throw exception for invalid destination
if (destinationAttr != null) {
validateDestination(destinationAttr);
}
/* Get the range of pages we are to print. If the
* last page to print is unknown, then we print to
* the end of the document. Note that firstPage
* and lastPage are 0 based page indices.
*/
int firstPage = getFirstPage();
int lastPage = getLastPage();
if(lastPage == Pageable.UNKNOWN_NUMBER_OF_PAGES) {
int totalPages = mDocument.getNumberOfPages();
if (totalPages != Pageable.UNKNOWN_NUMBER_OF_PAGES) {
lastPage = mDocument.getNumberOfPages() - 1;
}
}
try {
synchronized (this) {
performingPrinting = true;
userCancelled = false;
}
//Add support for PageRange
PageRanges pr = (attributes == null) ? null
: (PageRanges)attributes.get(PageRanges.class);
int[][] prMembers = (pr == null) ? new int[0][0] : pr.getMembers();
int loopi = 0;
do {
if (EventQueue.isDispatchThread()) {
// This is an AWT EventQueue, and this print rendering loop needs to block it.
onEventThread = true;
printingLoop = AccessController.doPrivileged(new PrivilegedAction<SecondaryLoop>() {
@Override
public SecondaryLoop run() {
return Toolkit.getDefaultToolkit()
.getSystemEventQueue()
.createSecondaryLoop();
}
});
try {
// Fire off the print rendering loop on the AppKit thread, and don't have
// it wait and block this thread.
if (printLoop(false, firstPage, lastPage)) {
// Start a secondary loop on EDT until printing operation is finished or cancelled
printingLoop.enter();
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
// Fire off the print rendering loop on the AppKit, and block this thread
// until it is done.
// But don't actually block... we need to come back here!
onEventThread = false;
try {
printLoop(true, firstPage, lastPage);
} catch (Exception e) {
e.printStackTrace();
}
}
if (++loopi < prMembers.length) {
firstPage = prMembers[loopi][0]-1;
lastPage = prMembers[loopi][1] -1;
}
} while (loopi < prMembers.length);
} finally {
synchronized (this) {
// NOTE: Native code shouldn't allow exceptions out while
// printing. They should cancel the print loop.
performingPrinting = false;
notify();
}
if (printingLoop != null) {
printingLoop.exit();
}
}
// Normalize the collated, # copies, numPages, first/last pages. Need to
// make note of pageRangesAttr.
// Set up NSPrintInfo with the java settings (PageFormat & Paper).
// Create an NSView for printing. Have knowsPageRange return YES, and give the correct
// range, or MAX? if unknown. Have rectForPage do a peekGraphics check before returning
// the rectangle. Have drawRect do the real render of the page. Have printJobTitle do
// the right thing.
// Call NSPrintOperation, it will call NSView.drawRect: for each page.
// NSView.drawRect: will create a CPrinterGraphics with the current CGContextRef, and then
// pass this Graphics onto the Printable with the appropriate PageFormat and index.
// Need to be able to cancel the NSPrintOperation (using code from RasterPrinterJob, be
// sure to initialize userCancelled and performingPrinting member variables).
// Extensions available from AppKit: Print to PDF or EPS file!
}
/**
* Returns the resolution in dots per inch across the width
* of the page.
*/
@Override
protected double getXRes() {
// NOTE: This is not used in the CPrinterJob code path.
return 0;
}
/**
* Returns the resolution in dots per inch down the height
* of the page.
*/
@Override
protected double getYRes() {
// NOTE: This is not used in the CPrinterJob code path.
return 0;
}
/**
* Must be obtained from the current printer.
* Value is in device pixels.
* Not adjusted for orientation of the paper.
*/
@Override
protected double getPhysicalPrintableX(Paper p) {
// NOTE: This is not used in the CPrinterJob code path.
return 0;
}
/**
* Must be obtained from the current printer.
* Value is in device pixels.
* Not adjusted for orientation of the paper.
*/
@Override
protected double getPhysicalPrintableY(Paper p) {
// NOTE: This is not used in the CPrinterJob code path.
return 0;
}
/**
* Must be obtained from the current printer.
* Value is in device pixels.
* Not adjusted for orientation of the paper.
*/
@Override
protected double getPhysicalPrintableWidth(Paper p) {
// NOTE: This is not used in the CPrinterJob code path.
return 0;
}
/**
* Must be obtained from the current printer.
* Value is in device pixels.
* Not adjusted for orientation of the paper.
*/
@Override
protected double getPhysicalPrintableHeight(Paper p) {
// NOTE: This is not used in the CPrinterJob code path.
return 0;
}
/**
* Must be obtained from the current printer.
* Value is in device pixels.
* Not adjusted for orientation of the paper.
*/
@Override
protected double getPhysicalPageWidth(Paper p) {
// NOTE: This is not used in the CPrinterJob code path.
return 0;
}
/**
* Must be obtained from the current printer.
* Value is in device pixels.
* Not adjusted for orientation of the paper.
*/
@Override
protected double getPhysicalPageHeight(Paper p) {
// NOTE: This is not used in the CPrinterJob code path.
return 0;
}
/**
* Begin a new page. This call's Window's
* StartPage routine.
*/
protected void startPage(PageFormat format, Printable painter, int index) throws PrinterException {
// NOTE: This is not used in the CPrinterJob code path.
throw new PrinterException(sShouldNotReachHere);
}
/**
* End a page.
*/
@Override
protected void endPage(PageFormat format, Printable painter, int index) throws PrinterException {
// NOTE: This is not used in the CPrinterJob code path.
throw new PrinterException(sShouldNotReachHere);
}
/**
* Prints the contents of the array of ints, 'data'
* to the current page. The band is placed at the
* location (x, y) in device coordinates on the
* page. The width and height of the band is
* specified by the caller.
*/
@Override
protected void printBand(byte[] data, int x, int y, int width, int height) throws PrinterException {
// NOTE: This is not used in the CPrinterJob code path.
throw new PrinterException(sShouldNotReachHere);
}
/**
* Called by the print() method at the start of
* a print job.
*/
@Override
protected void startDoc() throws PrinterException {
// NOTE: This is not used in the CPrinterJob code path.
throw new PrinterException(sShouldNotReachHere);
}
/**
* Called by the print() method at the end of
* a print job.
*/
@Override
protected void endDoc() throws PrinterException {
// NOTE: This is not used in the CPrinterJob code path.
throw new PrinterException(sShouldNotReachHere);
}
/* Called by cancelDoc */
@Override
protected native void abortDoc();
/**
* Displays the page setup dialog placing the user's
* settings into 'page'.
*/
public boolean pageSetup(PageFormat page, Printable painter) {
CPrinterDialog printerDialog = new CPrinterPageDialog(null, this, page, painter);
printerDialog.setVisible(true);
boolean result = printerDialog.getRetVal();
printerDialog.dispose();
return result;
}
/**
* Displays the print dialog and records the user's settings
* into this object. Return false if the user cancels the
* dialog.
* If the dialog is to use a set of attributes, useAttributes is true.
*/
private boolean jobSetup(Pageable doc, boolean allowPrintToFile) {
CPrinterDialog printerDialog = new CPrinterJobDialog(null, this, doc, allowPrintToFile);
printerDialog.setVisible(true);
boolean result = printerDialog.getRetVal();
printerDialog.dispose();
return result;
}
/**
* Alters the orientation and Paper to match defaults obtained
* from a printer.
*/
private native void getDefaultPage(PageFormat page);
/**
* validate the paper size against the current printer.
*/
@Override
protected native void validatePaper(Paper origPaper, Paper newPaper );
// The following methods are CPrinterJob specific.
@Override
protected void finalize() {
if (fNSPrintInfo != -1) {
dispose(fNSPrintInfo);
}
}
private native long createNSPrintInfo();
private native void dispose(long printInfo);
private long getNSPrintInfo() {
// This is called from the native side.
synchronized (fNSPrintInfoLock) {
if (fNSPrintInfo == -1) {
fNSPrintInfo = createNSPrintInfo();
}
return fNSPrintInfo;
}
}
private native boolean printLoop(boolean waitUntilDone, int firstPage, int lastPage) throws PrinterException;
private PageFormat getPageFormat(int pageIndex) {
// This is called from the native side.
PageFormat page;
try {
page = getPageable().getPageFormat(pageIndex);
} catch (Exception e) {
return null;
}
return page;
}
private Printable getPrintable(int pageIndex) {
// This is called from the native side.
Printable painter;
try {
painter = getPageable().getPrintable(pageIndex);
} catch (Exception e) {
return null;
}
return painter;
}
private String getPrinterName(){
// This is called from the native side.
PrintService service = getPrintService();
if (service == null) return null;
return service.getName();
}
private void setPrinterServiceFromNative(String printerName) {
// This is called from the native side.
PrintService[] services = PrintServiceLookup.lookupPrintServices(DocFlavor.SERVICE_FORMATTED.PAGEABLE, null);
for (int i = 0; i < services.length; i++) {
PrintService service = services[i];
if (printerName.equals(service.getName())) {
try {
setPrintService(service);
} catch (PrinterException e) {
// ignored
}
return;
}
}
}
private Rectangle2D getPageFormatArea(PageFormat page) {
Rectangle2D.Double pageFormatArea =
new Rectangle2D.Double(page.getImageableX(),
page.getImageableY(),
page.getImageableWidth(),
page.getImageableHeight());
return pageFormatArea;
}
private boolean cancelCheck() {
// This is called from the native side.
// This is used to avoid deadlock
// We would like to just call if isCancelled(),
// but that will block the AppKit thread against whomever is holding the synchronized lock
boolean cancelled = (performingPrinting && userCancelled);
if (cancelled) {
try {
LWCToolkit.invokeLater(new Runnable() { public void run() {
try {
cancelDoc();
} catch (PrinterAbortException pae) {
// no-op, let the native side handle it
}
}}, null);
} catch (java.lang.reflect.InvocationTargetException ite) {}
}
return cancelled;
}
private PeekGraphics createFirstPassGraphics(PrinterJob printerJob, PageFormat page) {
// This is called from the native side.
BufferedImage bimg = new BufferedImage((int)Math.round(page.getWidth()), (int)Math.round(page.getHeight()), BufferedImage.TYPE_INT_ARGB_PRE);
PeekGraphics peekGraphics = createPeekGraphics(bimg.createGraphics(), printerJob);
Rectangle2D pageFormatArea = getPageFormatArea(page);
initPrinterGraphics(peekGraphics, pageFormatArea);
return peekGraphics;
}
private void printToPathGraphics( final PeekGraphics graphics, // Always an actual PeekGraphics
final PrinterJob printerJob, // Always an actual CPrinterJob
final Printable painter, // Client class
final PageFormat page, // Client class
final int pageIndex,
final long context) throws PrinterException {
// This is called from the native side.
Runnable r = new Runnable() { public void run() {
try {
SurfaceData sd = CPrinterSurfaceData.createData(page, context); // Just stores page into an ivar
if (defaultFont == null) {
defaultFont = new Font("Dialog", Font.PLAIN, 12);
}
Graphics2D delegate = new SunGraphics2D(sd, Color.black, Color.white, defaultFont);
Graphics2D pathGraphics = new CPrinterGraphics(delegate, printerJob); // Just stores delegate into an ivar
Rectangle2D pageFormatArea = getPageFormatArea(page);
initPrinterGraphics(pathGraphics, pageFormatArea);
painter.print(pathGraphics, page, pageIndex);
delegate.dispose();
delegate = null;
} catch (PrinterException pe) { throw new java.lang.reflect.UndeclaredThrowableException(pe); }
}};
if (onEventThread) {
try { EventQueue.invokeAndWait(r);
} catch (java.lang.reflect.InvocationTargetException ite) {
Throwable te = (Throwable)ite.getTargetException();
if (te instanceof PrinterException) throw (PrinterException)te;
else te.printStackTrace();
} catch (Exception e) { e.printStackTrace(); }
} else {
r.run();
}
}
// Returns either 1. an array of 3 object (PageFormat, Printable, PeekGraphics) or 2. null
private Object[] getPageformatPrintablePeekgraphics(final int pageIndex) {
final Object[] ret = new Object[3];
final PrinterJob printerJob = this;
Runnable r = new Runnable() { public void run() { synchronized(ret) {
try {
Pageable pageable = getPageable();
PageFormat pageFormat = pageable.getPageFormat(pageIndex);
if (pageFormat != null) {
Printable printable = pageable.getPrintable(pageIndex);
if (printable != null) {
BufferedImage bimg = new BufferedImage((int)Math.round(pageFormat.getWidth()), (int)Math.round(pageFormat.getHeight()), BufferedImage.TYPE_INT_ARGB_PRE);
PeekGraphics peekGraphics = createPeekGraphics(bimg.createGraphics(), printerJob);
Rectangle2D pageFormatArea = getPageFormatArea(pageFormat);
initPrinterGraphics(peekGraphics, pageFormatArea);
// Do the assignment here!
ret[0] = pageFormat;
ret[1] = printable;
ret[2] = peekGraphics;
}
}
} catch (Exception e) {} // Original code bailed on any exception
}}};
if (onEventThread) {
try { EventQueue.invokeAndWait(r); } catch (Exception e) { e.printStackTrace(); }
} else {
r.run();
}
synchronized(ret) {
if (ret[2] != null)
return ret;
return null;
}
}
private Rectangle2D printAndGetPageFormatArea(final Printable printable, final Graphics graphics, final PageFormat pageFormat, final int pageIndex) {
final Rectangle2D[] ret = new Rectangle2D[1];
Runnable r = new Runnable() { public void run() { synchronized(ret) {
try {
int pageResult = printable.print(graphics, pageFormat, pageIndex);
if (pageResult != Printable.NO_SUCH_PAGE) {
ret[0] = getPageFormatArea(pageFormat);
}
} catch (Exception e) {} // Original code bailed on any exception
}}};
if (onEventThread) {
try { EventQueue.invokeAndWait(r); } catch (Exception e) { e.printStackTrace(); }
} else {
r.run();
}
synchronized(ret) { return ret[0]; }
}
// upcall from native
private static void detachPrintLoop(final long target, final long arg) {
new Thread() { public void run() {
_safePrintLoop(target, arg);
}}.start();
}
private static native void _safePrintLoop(long target, long arg);
@Override
protected void startPage(PageFormat arg0, Printable arg1, int arg2, boolean arg3) throws PrinterException {
// TODO Auto-generated method stub
}
@Override
protected MediaSize getMediaSize(Media media, PrintService service,
PageFormat page) {
if (media == null || !(media instanceof MediaSizeName)) {
return getDefaultMediaSize(page);
}
MediaSize size = MediaSize.getMediaSizeForName((MediaSizeName) media);
return size != null ? size : getDefaultMediaSize(page);
}
private MediaSize getDefaultMediaSize(PageFormat page){
final int inch = 72;
Paper paper = page.getPaper();
float width = (float) (paper.getWidth() / inch);
float height = (float) (paper.getHeight() / inch);
return new MediaSize(width, height, MediaSize.INCH);
}
@Override
protected MediaPrintableArea getDefaultPrintableArea(PageFormat page, double w, double h) {
final float dpi = 72.0f;
Paper paper = page.getPaper();
return new MediaPrintableArea(
(float) (paper.getImageableX() / dpi),
(float) (paper.getImageableY() / dpi),
(float) (paper.getImageableWidth() / dpi),
(float) (paper.getImageableHeight() / dpi),
MediaPrintableArea.INCH);
}
}