/* * Copyright (c) 2013, 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 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.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.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.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.tk.Toolkit; import com.sun.prism.j2d.PrismPrintGraphics; public class J2DPrinterJob implements PrinterJobImpl { 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; 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(); // dialog selection is a JDK 1.7 attribute. // We expect to run on 1.8 and above so this should be fine. // Don't use on Linux where it has no effect and runs into a JDK bug if (!PlatformUtil.isLinux()) { printReqAttrSet.add(DialogTypeSelection.NATIVE); } j2dPageable = new J2DPageable(); pJob2D.setPageable(j2dPageable); } public boolean showPrintDialog(Window owner) { if (jobRunning || jobDone) { return false; } if (GraphicsEnvironment.isHeadless()) { return true; } boolean rv = false; syncSettingsToAttributes(); 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(); } return rv; } private class PrintDialogRunnable implements Runnable { 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; } public boolean showPageDialog(Window owner) { if (jobRunning || jobDone) { return false; } if (GraphicsEnvironment.isHeadless()) { return true; } boolean rv = false; syncSettingsToAttributes(); 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); } if (rv) { updateSettingsFromDialog(); } return rv; } private class PageDialogRunnable implements Runnable { 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 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. } public void setPrinterImpl(PrinterImpl impl) { j2dPrinter = (J2DPrinter)impl; fxPrinter = j2dPrinter.getPrinter(); try { pJob2D.setPrintService(j2dPrinter.getService()); } catch (PrinterException pe) { } } 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(); updateCopies(); updatePageRanges(); updateSides(); updateCollation(); updatePageLayout(); updatePaperSource(); updateColor(); updatePrintQuality(); updatePrintResolution(); } private void syncSettingsToAttributes() { syncJobName(); syncCopies(); syncPageRanges(); syncSides(); syncCollation(); syncPageLayout(); syncPaperSource(); syncColor(); syncPrintQuality(); syncPrintResolution(); } private void syncJobName() { pJob2D.setJobName(settings.getJobName()); } 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; } public int print(Graphics g, PageFormat pf, int pageIndex) { if (jobError || 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 = node.impl_getPeer(); 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); } public Printable getPrintable(int pageIndex) { getPage(pageIndex); return this; } 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. */ 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 && !jobError) { synchronized (monitor) { try { monitor.wait(1000); } catch (InterruptedException e) { } } } } } } /* END J2DPageable class */ public boolean endJob() { if (jobRunning && !jobDone &&!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 false; } return jobDone; } public void cancelJob() { if (!pJob2D.isCancelled()) { pJob2D.cancel(); } jobDone = 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); } } } } }