1 /*
   2  * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.prism.j2d.print;
  27 
  28 import javafx.print.Collation;
  29 import javafx.print.JobSettings;
  30 import javafx.print.PageLayout;
  31 import javafx.print.PageOrientation;
  32 import javafx.print.PageRange;
  33 import javafx.print.Paper;
  34 import javafx.print.PaperSource;
  35 import javafx.print.PrintColor;
  36 import javafx.print.PrintResolution;
  37 import javafx.print.PrintSides;
  38 import javafx.print.Printer;
  39 import javafx.print.Printer.MarginType;
  40 import javafx.print.PrinterAttributes;
  41 import javafx.scene.Group;
  42 import javafx.scene.Node;
  43 import javafx.scene.Parent;
  44 import javafx.scene.Scene;
  45 import javafx.stage.Window;
  46 import javax.print.PrintService;
  47 import javax.print.attribute.HashPrintRequestAttributeSet;
  48 import javax.print.attribute.PrintRequestAttributeSet;
  49 import javax.print.attribute.ResolutionSyntax;
  50 import javax.print.attribute.Size2DSyntax;
  51 import javax.print.attribute.standard.Chromaticity;
  52 import javax.print.attribute.standard.Copies;
  53 import javax.print.attribute.standard.DialogTypeSelection;
  54 import javax.print.attribute.standard.Media;
  55 import javax.print.attribute.standard.MediaPrintableArea;
  56 import javax.print.attribute.standard.MediaSize;
  57 import javax.print.attribute.standard.MediaSizeName;
  58 import javax.print.attribute.standard.MediaTray;
  59 import javax.print.attribute.standard.OrientationRequested;
  60 import javax.print.attribute.standard.PageRanges;
  61 import javax.print.attribute.standard.PrintQuality;
  62 import javax.print.attribute.standard.PrinterResolution;
  63 import javax.print.attribute.standard.SheetCollate;
  64 import javax.print.attribute.standard.Sides;
  65 import java.awt.*;
  66 import java.awt.print.PageFormat;
  67 import java.awt.print.Pageable;
  68 import java.awt.print.Printable;
  69 import java.awt.print.PrinterException;
  70 import java.util.ArrayList;
  71 import java.util.Set;
  72 import com.sun.glass.ui.Application;
  73 import com.sun.javafx.PlatformUtil;
  74 import com.sun.javafx.print.PrintHelper;
  75 import com.sun.javafx.print.PrinterImpl;
  76 import com.sun.javafx.print.PrinterJobImpl;
  77 import com.sun.javafx.scene.NodeHelper;
  78 import com.sun.javafx.sg.prism.NGNode;
  79 import com.sun.javafx.tk.Toolkit;
  80 import com.sun.prism.j2d.PrismPrintGraphics;
  81 
  82 public class J2DPrinterJob implements PrinterJobImpl {
  83 
  84     javafx.print.PrinterJob fxPrinterJob;
  85     java.awt.print.PrinterJob pJob2D;
  86     javafx.print.Printer fxPrinter;
  87     J2DPrinter j2dPrinter;
  88 
  89     private JobSettings settings;
  90     private PrintRequestAttributeSet printReqAttrSet;
  91     private volatile Object elo = null;
  92 
  93     public J2DPrinterJob(javafx.print.PrinterJob fxJob) {
  94 
  95         fxPrinterJob = fxJob;
  96         fxPrinter = fxPrinterJob.getPrinter();
  97         j2dPrinter = getJ2DPrinter(fxPrinter);
  98         settings = fxPrinterJob.getJobSettings();
  99         pJob2D = java.awt.print.PrinterJob.getPrinterJob();
 100         try {
 101             pJob2D.setPrintService(j2dPrinter.getService());
 102         } catch (PrinterException pe) {
 103         }
 104         printReqAttrSet = new HashPrintRequestAttributeSet();
 105         // dialog selection is a JDK 1.7 attribute.
 106         // We expect to run on 1.8 and above so this should be fine.
 107         // Don't use on Linux where it has no effect and runs into a JDK bug
 108         if (!PlatformUtil.isLinux()) {
 109             printReqAttrSet.add(DialogTypeSelection.NATIVE);
 110         }
 111         j2dPageable = new J2DPageable();
 112         pJob2D.setPageable(j2dPageable);
 113     }
 114 
 115     public boolean showPrintDialog(Window owner) {
 116 
 117         if (jobRunning || jobDone) {
 118             return false;
 119         }
 120 
 121         if (GraphicsEnvironment.isHeadless()) {
 122             return true;
 123         }
 124 
 125         boolean rv = false;
 126         syncSettingsToAttributes();
 127         if (!Toolkit.getToolkit().isFxUserThread()) {
 128             rv = pJob2D.printDialog(printReqAttrSet);
 129         } else {
 130             // If we are on the event thread, we need to check whether we are
 131             // allowed to call a nested event handler.
 132             if (!Toolkit.getToolkit().canStartNestedEventLoop()) {
 133                 throw new IllegalStateException("Printing is not allowed during animation or layout processing");
 134             }
 135             rv = showPrintDialogWithNestedLoop(owner);
 136         }
 137         if (rv) {
 138             updateSettingsFromDialog();
 139         }
 140         return rv;
 141     }
 142 
 143     private class PrintDialogRunnable implements Runnable {
 144 
 145         public void run() {
 146             boolean rv = false;
 147             try {
 148                 rv = pJob2D.printDialog(printReqAttrSet);
 149             } catch (Exception e) {
 150             } finally {
 151                 Application.invokeLater(new ExitLoopRunnable(this, rv));
 152             }
 153         }
 154     }
 155 
 156     private boolean showPrintDialogWithNestedLoop(Window owner) {
 157         PrintDialogRunnable dr = new PrintDialogRunnable();
 158         Thread prtThread = new Thread(dr, "FX Print Dialog Thread");
 159         prtThread.start();
 160         // the nested event loop will return after the runnable exits.
 161         Object rv = Toolkit.getToolkit().enterNestedEventLoop(dr);
 162 
 163         boolean rvbool = false;
 164         try {
 165             rvbool = ((Boolean)rv).booleanValue();
 166         } catch (Exception e) {
 167         }
 168         return rvbool;
 169     }
 170 
 171     public boolean showPageDialog(Window owner) {
 172         if (jobRunning || jobDone) {
 173             return false;
 174         }
 175         if (GraphicsEnvironment.isHeadless()) {
 176             return true;
 177         }
 178         boolean rv = false;
 179         syncSettingsToAttributes();
 180         if (!Toolkit.getToolkit().isFxUserThread()) {
 181             PageFormat pf = pJob2D.pageDialog(printReqAttrSet);
 182             rv = pf != null;
 183         } else {
 184             // If we are on the event thread, we need to check whether we are
 185             // allowed to call a nested event handler.
 186             if (!Toolkit.getToolkit().canStartNestedEventLoop()) {
 187                 throw new IllegalStateException("Printing is not allowed during animation or layout processing");
 188             }
 189             rv = showPageDialogFromNestedLoop(owner);
 190         }
 191         if (rv) {
 192             updateSettingsFromDialog();
 193         }
 194         return rv;
 195     }
 196 
 197     private class PageDialogRunnable implements Runnable {
 198 
 199         public void run() {
 200             PageFormat pf = null;
 201             try {
 202                 pf = pJob2D.pageDialog(printReqAttrSet);
 203             } catch (Exception e) {
 204             } finally {
 205                 Boolean rv = Boolean.valueOf(pf != null);
 206                 Application.invokeLater(new ExitLoopRunnable(this, rv));
 207             }
 208         }
 209     }
 210 
 211     private boolean showPageDialogFromNestedLoop(Window owner) {
 212 
 213         PageDialogRunnable dr = new PageDialogRunnable();
 214         Thread prtThread = new Thread(dr, "FX Page Setup Dialog Thread");
 215         prtThread.start();
 216         // the nested event loop will return after the runnable exits.
 217         Object rv = Toolkit.getToolkit().enterNestedEventLoop(dr);
 218         boolean rvbool = false;
 219         try {
 220             rvbool = ((Boolean)rv).booleanValue();
 221         } catch (Exception e) {
 222         }
 223         return rvbool;
 224     }
 225 
 226     /*
 227      * The update-Foo methods here are only used to update the
 228      * FX JobSettings as a result of changes by user interaction
 229      * with a print dialog. The new values are stored in the
 230      * PrintRequestAttributeSet and pulled from there in to the
 231      * equivalent FX public API JobSettings.
 232      */
 233     private void updateJobName() {
 234         String name =  pJob2D.getJobName();
 235         if (!name.equals(settings.getJobName())) {
 236             settings.setJobName(name);
 237         }
 238     }
 239     private void updateCopies() {
 240         int nCopies = pJob2D.getCopies();
 241         if (settings.getCopies() != nCopies) {
 242             settings.setCopies(nCopies);
 243         }
 244     }
 245 
 246     private void updatePageRanges() {
 247         PageRanges ranges = (PageRanges)printReqAttrSet.get(PageRanges.class);
 248         // JDK sets default to 1,Integer.MAX_VALUE
 249         // So in this case I think we can just check for non-null and
 250         // only set if its non-null.
 251         if (ranges != null) {
 252             int[][] members = ranges.getMembers();
 253             if (members.length == 1) {
 254                 PageRange range = new PageRange(members[0][0], members[0][1]);
 255                 settings.setPageRanges(range);
 256             } else if (members.length > 0) {
 257                 try {
 258                     ArrayList<PageRange> prList = new ArrayList<PageRange>();
 259                     int last = 0;
 260                     for (int i=0; i<members.length;i++) {
 261                         int s = members[i][0];
 262                         int e = members[i][1];
 263                         if (s <= last || e < s) {
 264                             return;
 265                         }
 266                         last = e;
 267                         prList.add(new PageRange(s, e));
 268                     }
 269                     settings.setPageRanges(prList.toArray(new PageRange[0]));
 270                 } catch (Exception e) {
 271                 }
 272             }
 273         }
 274     }
 275 
 276     private void updateSides() {
 277         Sides sides = (Sides)printReqAttrSet.get(Sides.class);
 278         if (sides == null) {
 279             sides = (Sides)j2dPrinter.getService().
 280                 getDefaultAttributeValue(Sides.class);
 281         }
 282         if (sides == Sides.ONE_SIDED) {
 283             settings.setPrintSides(PrintSides.ONE_SIDED);
 284         } else if (sides == Sides.DUPLEX) {
 285             settings.setPrintSides(PrintSides.DUPLEX);
 286         } else if (sides == Sides.TUMBLE) {
 287             settings.setPrintSides(PrintSides.TUMBLE);
 288         }
 289     }
 290 
 291     /* If the attribute set has an explicit setting for
 292      * collation, then its been set by the user at some point,
 293      * even if the current value is the printer default.
 294      * If there is no value for collation in the attribute set,
 295      * it means that we are u  sing the printer default.
 296      */
 297     private void updateCollation() {
 298         SheetCollate collate =
 299             (SheetCollate)printReqAttrSet.get(SheetCollate.class);
 300         if (collate == null) {
 301             collate = j2dPrinter.getDefaultSheetCollate();
 302         }
 303         if (collate == SheetCollate.UNCOLLATED) {
 304             settings.setCollation(Collation.UNCOLLATED);
 305         } else {
 306             settings.setCollation(Collation.COLLATED);
 307         }
 308     }
 309 
 310     private void updateColor() {
 311         Chromaticity color =
 312             (Chromaticity)printReqAttrSet.get(Chromaticity.class);
 313         if (color == null) {
 314             color = j2dPrinter.getDefaultChromaticity();
 315         }
 316         if (color == Chromaticity.COLOR) {
 317             settings.setPrintColor(PrintColor.COLOR);
 318         } else {
 319             settings.setPrintColor(PrintColor.MONOCHROME);
 320         }
 321     }
 322 
 323     private void updatePrintQuality() {
 324         PrintQuality quality =
 325             (PrintQuality)printReqAttrSet.get(PrintQuality.class);
 326         if (quality == null) {
 327             quality = j2dPrinter.getDefaultPrintQuality();
 328         }
 329 
 330         if (quality == PrintQuality.DRAFT) {
 331             settings.
 332                 setPrintQuality(javafx.print.PrintQuality.DRAFT);
 333         } else if (quality == PrintQuality.HIGH) {
 334             settings.
 335                 setPrintQuality(javafx.print.PrintQuality.HIGH);
 336         } else {
 337             settings.
 338                 setPrintQuality(javafx.print.PrintQuality.NORMAL);
 339         }
 340     }
 341 
 342     private void updatePrintResolution() {
 343         PrinterResolution res =
 344             (PrinterResolution)printReqAttrSet.get(PrinterResolution.class);
 345         if (res == null) {
 346             res = j2dPrinter.getDefaultPrinterResolution();
 347         }
 348         int cfr = res.getCrossFeedResolution(ResolutionSyntax.DPI);
 349         int fr = res.getFeedResolution(ResolutionSyntax.DPI);
 350         settings.setPrintResolution(PrintHelper.createPrintResolution(cfr, fr));
 351     }
 352 
 353     private void updatePageLayout() {
 354         Media media = (Media)printReqAttrSet.get(Media.class);
 355         Paper paper = j2dPrinter.getPaperForMedia(media);
 356         OrientationRequested o = (OrientationRequested)
 357             printReqAttrSet.get(OrientationRequested.class);
 358         PageOrientation orient = J2DPrinter.reverseMapOrientation(o);
 359         MediaPrintableArea mpa =
 360             (MediaPrintableArea)printReqAttrSet.get(MediaPrintableArea.class);
 361         PageLayout newLayout;
 362         if (mpa == null) {
 363             newLayout = fxPrinter.createPageLayout(paper, orient,
 364                                                    MarginType.DEFAULT);
 365         } else {
 366             double pWid = paper.getWidth();
 367             double pHgt = paper.getHeight();
 368             int INCH = MediaPrintableArea.INCH;
 369             double mpaX = mpa.getX(INCH) * 72;
 370             double mpaY = mpa.getY(INCH) * 72;
 371             double mpaW = mpa.getWidth(INCH) * 72;
 372             double mpaH = mpa.getHeight(INCH) * 72;
 373             double lm=0, rm=0, tm=0, bm=0;
 374             switch (orient) {
 375             case PORTRAIT:
 376                 lm = mpaX;
 377                 rm = pWid - mpaX - mpaW;
 378                 tm = mpaY;
 379                 bm = pHgt - mpaY - mpaH;
 380                 break;
 381             case REVERSE_PORTRAIT:
 382                 lm = pWid - mpaX - mpaW;
 383                 rm = mpaX;
 384                 tm = pHgt - mpaY - mpaH;
 385                 bm = mpaY;
 386                 break;
 387             case LANDSCAPE:
 388                 lm = mpaY;
 389                 rm = pHgt - mpaY - mpaH;
 390                 tm = pWid - mpaX - mpaW;
 391                 bm = mpaX;
 392                 break;
 393             case REVERSE_LANDSCAPE:
 394                 lm = pHgt - mpaY - mpaH;
 395                 tm = mpaX;
 396                 rm = mpaY;
 397                 bm = pWid - mpaX - mpaW;
 398                 break;
 399             }
 400             if (Math.abs(lm) < 0.01) lm = 0;
 401             if (Math.abs(rm) < 0.01) rm = 0;
 402             if (Math.abs(tm) < 0.01) tm = 0;
 403             if (Math.abs(bm) < 0.01) bm = 0;
 404             newLayout = fxPrinter.createPageLayout(paper, orient,
 405                                                    lm, rm, tm, bm);
 406         }
 407         settings.setPageLayout(newLayout);
 408     }
 409 
 410     private void updatePaperSource() {
 411         Media m = (Media)printReqAttrSet.get(Media.class);
 412         if (m instanceof MediaTray) {
 413             PaperSource s = j2dPrinter.getPaperSource((MediaTray)m);
 414             if (s != null) {
 415                 settings.setPaperSource(s);
 416             }
 417         }
 418     }
 419 
 420     private Printer getFXPrinterForService(PrintService service) {
 421         Set<Printer> printerSet = Printer.getAllPrinters();
 422         for (Printer p : printerSet) {
 423             J2DPrinter p2d = (J2DPrinter)PrintHelper.getPrinterImpl(p);
 424             PrintService s = p2d.getService();
 425             if (s.equals(service)) {
 426                 return p;
 427             }
 428         }
 429         return fxPrinter; // current printer.
 430     }
 431 
 432     public void setPrinterImpl(PrinterImpl impl) {
 433         j2dPrinter = (J2DPrinter)impl;
 434         fxPrinter = j2dPrinter.getPrinter();
 435         try {
 436             pJob2D.setPrintService(j2dPrinter.getService());
 437         } catch (PrinterException pe) {
 438         }
 439     }
 440 
 441     public PrinterImpl getPrinterImpl() {
 442         return j2dPrinter;
 443     }
 444 
 445     private J2DPrinter getJ2DPrinter(Printer printer) {
 446         return (J2DPrinter)PrintHelper.getPrinterImpl(printer);
 447     }
 448 
 449     public Printer getPrinter() {
 450         return fxPrinter;
 451     }
 452 
 453     public void setPrinter(Printer printer) {
 454         fxPrinter = printer;
 455         j2dPrinter = getJ2DPrinter(printer);
 456         try {
 457             pJob2D.setPrintService(j2dPrinter.getService());
 458         } catch (PrinterException pe) {
 459         }
 460     }
 461 
 462     private void updatePrinter() {
 463         PrintService currService = j2dPrinter.getService();
 464         PrintService jobService = pJob2D.getPrintService();
 465         if (currService.equals(jobService)) {
 466             return; // no change
 467         }
 468         Printer newFXPrinter = getFXPrinterForService(jobService);
 469         // The public setPrinter call also updates the job to be valid for
 470         // the new printer. Any old values not supported will be updated
 471         // to supported values. If we do that, then apply the new user
 472         // settings, any listener will see both sets of changes.
 473         // Its best to just see the single transition.
 474         fxPrinterJob.setPrinter(newFXPrinter);
 475     }
 476 
 477     private void updateSettingsFromDialog() {
 478         updatePrinter();
 479         updateJobName();
 480         updateCopies();
 481         updatePageRanges();
 482         updateSides();
 483         updateCollation();
 484         updatePageLayout();
 485         updatePaperSource();
 486         updateColor();
 487         updatePrintQuality();
 488         updatePrintResolution();
 489     }
 490 
 491     private void syncSettingsToAttributes() {
 492         syncJobName();
 493         syncCopies();
 494         syncPageRanges();
 495         syncSides();
 496         syncCollation();
 497         syncPageLayout();
 498         syncPaperSource();
 499         syncColor();
 500         syncPrintQuality();
 501         syncPrintResolution();
 502     }
 503 
 504     private void syncJobName() {
 505         pJob2D.setJobName(settings.getJobName());
 506     }
 507 
 508     private void syncCopies() {
 509         pJob2D.setCopies(settings.getCopies());
 510         printReqAttrSet.add(new Copies(settings.getCopies()));
 511     }
 512 
 513     private void syncPageRanges() {
 514         printReqAttrSet.remove(PageRanges.class);
 515         PageRange[] prArr = settings.getPageRanges();
 516         if (prArr != null && prArr.length>0) {
 517             int len = prArr.length;
 518             int[][] ranges = new int[len][2];
 519             for (int i=0;i<len;i++) {
 520                 ranges[i][0] = prArr[i].getStartPage();
 521                 ranges[i][1] = prArr[i].getEndPage();
 522             }
 523             printReqAttrSet.add(new PageRanges(ranges));
 524         }
 525     }
 526 
 527     private void syncSides() {
 528         Sides j2dSides = Sides.ONE_SIDED;
 529         PrintSides sides = settings.getPrintSides();
 530         if (sides == PrintSides.DUPLEX) {
 531             j2dSides = Sides.DUPLEX;
 532         } else if (sides == PrintSides.TUMBLE) {
 533             j2dSides = Sides.TUMBLE;
 534         }
 535         printReqAttrSet.add(j2dSides);
 536     }
 537 
 538     private void syncCollation() {
 539         if (settings.getCollation() == Collation.UNCOLLATED) {
 540             printReqAttrSet.add(SheetCollate.UNCOLLATED);
 541         } else {
 542             printReqAttrSet.add(SheetCollate.COLLATED);
 543         }
 544 
 545     }
 546 
 547     private void syncPageLayout() {
 548         PageLayout layout = settings.getPageLayout();
 549         PageOrientation orient = layout.getPageOrientation();
 550         printReqAttrSet.add(J2DPrinter.mapOrientation(orient));
 551         double pWid = layout.getPaper().getWidth();
 552         double pHgt = layout.getPaper().getHeight();
 553         float widthInInches = (float)(pWid/72.0);
 554         float heightInInches = (float)(pHgt/72.0);
 555         MediaSizeName media = MediaSize.findMedia(widthInInches,
 556                                                   heightInInches,
 557                                                   Size2DSyntax.INCH);
 558         if (media == null) {
 559             media = MediaSizeName.NA_LETTER;
 560         }
 561         printReqAttrSet.add(media);
 562         double ix=0, iy=0, iw=pWid, ih=pHgt;
 563         switch (orient) {
 564         case PORTRAIT:
 565             ix = layout.getLeftMargin();
 566             iy = layout.getTopMargin();
 567             iw = pWid - ix - layout.getRightMargin();
 568             ih = pHgt - iy - layout.getBottomMargin();
 569             break;
 570         case REVERSE_PORTRAIT:
 571             ix = layout.getRightMargin();
 572             iy = layout.getBottomMargin();
 573             iw = pWid - ix - layout.getLeftMargin();
 574             ih = pHgt - iy - layout.getTopMargin();
 575             break;
 576         case LANDSCAPE:
 577             ix = layout.getBottomMargin();
 578             iy = layout.getLeftMargin();
 579             iw = pWid - ix - layout.getTopMargin();
 580             ih = pHgt - iy - layout.getRightMargin();
 581             break;
 582         case REVERSE_LANDSCAPE:
 583             ix = layout.getTopMargin();
 584             iy = layout.getRightMargin();
 585             iw = pWid - ix - layout.getBottomMargin();
 586             ih = pHgt - iy - layout.getLeftMargin();
 587         }
 588         ix /= 72.0;
 589         iy /= 72.0;
 590         ih /= 72.0;
 591         iw /= 72.0;
 592         MediaPrintableArea mpa =
 593             new MediaPrintableArea((float)ix, (float)iy,
 594                                    (float)iw, (float)ih,
 595                                    MediaPrintableArea.INCH);
 596         printReqAttrSet.add(mpa);
 597     }
 598 
 599     private void syncPaperSource() {
 600         Media m = (Media)printReqAttrSet.get(Media.class);
 601         if (m != null && m instanceof MediaTray) {
 602             printReqAttrSet.remove(Media.class);
 603         }
 604         PaperSource source = settings.getPaperSource();
 605         if (!source.equals(j2dPrinter.defaultPaperSource())) {
 606             MediaTray tray = j2dPrinter.getTrayForPaperSource(source);
 607             if (tray != null) {
 608                 printReqAttrSet.add(tray);
 609             }
 610         }
 611     }
 612 
 613     private void syncColor() {
 614         if (settings.getPrintColor() == PrintColor.MONOCHROME) {
 615             printReqAttrSet.add(Chromaticity.MONOCHROME);
 616         } else {
 617             printReqAttrSet.add(Chromaticity.COLOR);
 618         }
 619     }
 620 
 621     private void syncPrintQuality() {
 622         javafx.print.PrintQuality
 623             quality = settings.getPrintQuality();
 624         PrintQuality j2DQuality;
 625         if (quality == javafx.print.PrintQuality.DRAFT) {
 626             j2DQuality = PrintQuality.DRAFT;
 627         } else if (quality == javafx.print.PrintQuality.HIGH) {
 628           j2DQuality = PrintQuality.HIGH;
 629         } else {
 630             j2DQuality = PrintQuality.NORMAL;
 631         }
 632         printReqAttrSet.add(j2DQuality);
 633     }
 634 
 635     private void syncPrintResolution() {
 636         /* An unsupported resolution results in incorrect scaling by J2D, so
 637          * remove any unsupported value, and only replace with a supported value.
 638          */
 639         PrintService ps = pJob2D.getPrintService();
 640         if (!ps.isAttributeCategorySupported(PrinterResolution.class)) {
 641             printReqAttrSet.remove(PrinterResolution.class);
 642             return;
 643         }
 644         PrinterResolution pres =
 645             (PrinterResolution)printReqAttrSet.get(PrinterResolution.class);
 646         if (pres != null && !ps.isAttributeValueSupported(pres, null, null)) {
 647             printReqAttrSet.remove(PrinterResolution.class);
 648         };
 649 
 650         // Any resolution is now at least known to be supported for this device.
 651         PrintResolution res = settings.getPrintResolution();
 652         if (res == null) {
 653             return;
 654         }
 655         int cfRes = res.getCrossFeedResolution();
 656         int fRes = res.getFeedResolution();
 657         pres = new PrinterResolution(cfRes, fRes, ResolutionSyntax.DPI);
 658         if (!ps.isAttributeValueSupported(pres, null, null)) {
 659             return;
 660         }
 661         // We have validated its a supported value, so add it.
 662         printReqAttrSet.add(pres);
 663     }
 664 
 665     public PageLayout validatePageLayout(PageLayout pageLayout) {
 666         boolean needsNewLayout = false;
 667         PrinterAttributes caps = fxPrinter.getPrinterAttributes();
 668         Paper p = pageLayout.getPaper();
 669         if (!caps.getSupportedPapers().contains(p)) {
 670             needsNewLayout = true;
 671             p = caps.getDefaultPaper();
 672         }
 673         PageOrientation o = pageLayout.getPageOrientation();
 674         if (!caps.getSupportedPageOrientations().contains(o)) {
 675             needsNewLayout = true;
 676             o = caps.getDefaultPageOrientation();
 677         }
 678         if (needsNewLayout) {
 679             pageLayout = fxPrinter.createPageLayout(p, o, MarginType.DEFAULT);
 680         }
 681         return pageLayout;
 682     }
 683 
 684     private boolean jobRunning = false;
 685     private boolean jobError = false;
 686     private boolean jobDone = false;
 687     private J2DPageable j2dPageable = null;
 688 
 689     /*
 690      * Permissions were already checked when creating the job,
 691      * and when setting output file, but this is a final check
 692      * to be made before we start the underlying native job.
 693      */
 694     private void checkPermissions() {
 695         SecurityManager security = System.getSecurityManager();
 696         if (security != null) {
 697             security.checkPrintJobAccess();
 698         }
 699     }
 700 
 701     /*
 702      * 2D uses a call back model. So the 2D PrinterJob needs to run
 703      * on a different thread than the one that the FX app uses.
 704      * This gets really interesting if the FX Node is attached to a
 705      * scene, as you are only supposed to update it on the FX thread
 706      * and the PG code can only access it during sync.
 707      */
 708     public boolean print(PageLayout pageLayout, Node node) {
 709         if (Toolkit.getToolkit().isFxUserThread()) {
 710             // If we are on the event thread, we need to check whether we are
 711             // allowed to call a nested event handler.
 712             if (!Toolkit.getToolkit().canStartNestedEventLoop()) {
 713                 throw new IllegalStateException("Printing is not allowed during animation or layout processing");
 714             }
 715         }
 716 
 717         if (jobError || jobDone) {
 718             return false;
 719         }
 720 
 721         if (!jobRunning) {
 722             checkPermissions();
 723             syncSettingsToAttributes();
 724             PrintJobRunnable runnable = new PrintJobRunnable();
 725             Thread prtThread = new Thread(runnable, "Print Job Thread");
 726             prtThread.start();
 727             jobRunning = true;
 728         }
 729         try {
 730             j2dPageable.implPrintPage(pageLayout, node);
 731         } catch (Throwable t) {
 732             if (com.sun.prism.impl.PrismSettings.debug) {
 733                 System.err.println("printPage caught exception.");
 734                 t.printStackTrace();
 735             }
 736             jobError = true;
 737             jobDone = true;
 738         }
 739         return !jobError;
 740     }
 741 
 742     private class PrintJobRunnable implements Runnable {
 743 
 744         public void run() {
 745 
 746             try {
 747                 pJob2D.print(printReqAttrSet);
 748                 jobDone = true;
 749             } catch (Throwable t) { /* subsumes declared PrinterException */
 750                 if (com.sun.prism.impl.PrismSettings.debug) {
 751                     System.err.println("print caught exception.");
 752                     t.printStackTrace();
 753                 }
 754                 jobError = true;
 755                 jobDone = true;
 756             }
 757             /*
 758              * If the job ends because its reached a page range limit
 759              * rather than calling getPage() we need to exit the nested loop.
 760              */
 761             if (elo != null) {
 762                 Application.invokeLater(new ExitLoopRunnable(elo, null));
 763             }
 764         }
 765     }
 766 
 767     static class LayoutRunnable implements Runnable {
 768         PageInfo pageInfo;
 769 
 770         LayoutRunnable(PageInfo info) {
 771             pageInfo = info;
 772         }
 773 
 774         public void run() {
 775             if (pageInfo.tempScene && pageInfo.root.getScene() == null) {
 776                 new Scene(pageInfo.root);
 777             }
 778             NodeHelper.layoutNodeForPrinting(pageInfo.root);
 779         }
 780     }
 781 
 782     static class ClearSceneRunnable implements Runnable {
 783         PageInfo pageInfo;
 784 
 785         ClearSceneRunnable(PageInfo info) {
 786             pageInfo = info;
 787         }
 788 
 789         public void run() {
 790             pageInfo.clearScene();
 791         }
 792     }
 793 
 794     private static class PageInfo {
 795 
 796         private PageLayout pageLayout;
 797         private Node node;
 798         private Parent root;
 799         private Node topNode;
 800         private Group group;
 801         private boolean tempGroup;
 802         private boolean tempScene;
 803         private boolean sceneInited;
 804 
 805         PageInfo(PageLayout pageLayout, Node node) {
 806             this.pageLayout = pageLayout;
 807             this.node = node;
 808         }
 809 
 810         Node getNode() {
 811             initScene();
 812             return node;
 813         }
 814 
 815         PageLayout getPageLayout() {
 816             return pageLayout;
 817         }
 818 
 819         /*
 820          * There are 4 scenarios here.
 821          * 1. We are passed the root node of a Scene.
 822          * 2. We are passed a child node of a Scene, but not the root
 823          * 3. We are passed a root node (no parent) but its not attached
 824          * to a Scene.
 825          * 4. We are passed a child node, but its not part of a Scene.
 826          * In addition we may be called on the FX thread, or not.
 827          * The code here is trying to make all of these work without
 828          * the application needing to do anything special, and hopefully
 829          * without affecting the application.
 830          * The application should not be surprised if we request layout for it,
 831          * since we can't display or print an unlaid out hiearchy.
 832          *
 833          * If this is the FX thread, then we can do everything directly.
 834          * If not, we must add the node to a scene (if needed) and
 835          * request layout on another thread.
 836          * I am assuming here that layout will be a quick no-op if
 837          * everything is already laid out.
 838          * Eventually all of this should be able to be performed on any
 839          * thread, and without attaching to a scene, so this is largely
 840          * workaround. One part I'm not so sure about is whether it
 841          * will ever be the case that being passed a node that is part
 842          * of a hierarchy, but not its root, will be able to be laid out
 843          * directly, or if you need to traverse to the root.
 844          */
 845         void initScene() {
 846             if (sceneInited) {
 847                return;
 848             }
 849             if (node.getScene() == null) {
 850                 tempScene = true;
 851                 Node topNode = node;
 852                 while (topNode.getParent() != null) {
 853                     topNode = topNode.getParent();
 854                 }
 855                 if (topNode instanceof Group) {
 856                     group = (Group)topNode;
 857                 } else {
 858                     tempGroup = true;
 859                     group = new Group();
 860                     group.getChildren().add(topNode);
 861                 }
 862                 root = group;
 863             } else {
 864                 root = node.getScene().getRoot();
 865             }
 866             if (Toolkit.getToolkit().isFxUserThread()) {
 867                 if (tempScene && root.getScene() == null) {
 868                     new Scene(root); // don't need to keep the scene variable
 869                 }
 870                 NodeHelper.layoutNodeForPrinting(root);
 871             } else {
 872                 Application.invokeAndWait(new LayoutRunnable(this));
 873             }
 874             sceneInited = true;
 875         }
 876 
 877         private void clearScene() {
 878             if (tempGroup) {
 879                 group.getChildren().removeAll(root);
 880             }
 881             tempGroup = false;
 882             tempScene = false;
 883             root = null;
 884             group = null;
 885             topNode = null;
 886             sceneInited = false;
 887         }
 888     }
 889 
 890     private Object monitor = new Object();
 891 
 892     static class ExitLoopRunnable implements Runnable {
 893         Object elo, rv;
 894 
 895         ExitLoopRunnable(Object elo, Object rv) {
 896             this.elo = elo;
 897             this.rv = rv;
 898         }
 899 
 900         public void run() {
 901             Toolkit.getToolkit().exitNestedEventLoop(elo, rv);
 902         }
 903     }
 904 
 905     private class J2DPageable implements Pageable, Printable {
 906 
 907         private volatile boolean pageDone;
 908 
 909         private int currPageIndex = -1;
 910 
 911         private volatile PageInfo newPageInfo = null;
 912         private PageInfo currPageInfo;
 913         private PageFormat currPageFormat;
 914 
 915 
 916         private boolean waitForNextPage(int pageIndex) {
 917 
 918             if (elo != null && currPageInfo != null) {
 919                 Application.invokeLater(new ExitLoopRunnable(elo, null));
 920         }
 921 
 922             if (currPageInfo != null) {
 923                 if (Toolkit.getToolkit().isFxUserThread()) {
 924                     currPageInfo.clearScene();
 925                 } else {
 926                     Application.
 927                         invokeAndWait(new ClearSceneRunnable(currPageInfo));
 928                 }
 929             }
 930             currPageInfo = null;
 931             pageDone = true;
 932             synchronized (monitor) {
 933                 if(newPageInfo == null) {
 934                     monitor.notify(); // page is printed and no new page to print
 935                 }
 936                 while (newPageInfo == null && !jobDone && !jobError) {
 937                     try {
 938                         monitor.wait(1000);
 939                     } catch (InterruptedException e) {
 940                     }
 941                 }
 942             }
 943             if (jobDone || jobError) {
 944                 return false;
 945             }
 946             currPageInfo = newPageInfo;
 947             newPageInfo = null;
 948             currPageIndex = pageIndex;
 949             currPageFormat = getPageFormatFromLayout(currPageInfo.getPageLayout());
 950             return true;
 951         }
 952 
 953         private PageFormat getPageFormatFromLayout(PageLayout layout) {
 954             java.awt.print.Paper paper = new java.awt.print.Paper();
 955             double pWid = layout.getPaper().getWidth();
 956             double pHgt = layout.getPaper().getHeight();
 957             double ix=0, iy=0, iw=pWid, ih=pHgt;
 958             PageOrientation orient = layout.getPageOrientation();
 959             switch (orient) {
 960             case PORTRAIT:
 961                 ix = layout.getLeftMargin();
 962                 iy = layout.getTopMargin();
 963                 iw = pWid - ix - layout.getRightMargin();
 964                 ih = pHgt - iy - layout.getBottomMargin();
 965                 break;
 966             case REVERSE_PORTRAIT:
 967                 ix = layout.getRightMargin();
 968                 iy = layout.getBottomMargin();
 969                 iw = pWid - ix - layout.getLeftMargin();
 970                 ih = pHgt - iy - layout.getTopMargin();
 971                 break;
 972             case LANDSCAPE:
 973                 ix = layout.getBottomMargin();
 974                 iy = layout.getLeftMargin();
 975                 iw = pWid - ix - layout.getTopMargin();
 976                 ih = pHgt - iy - layout.getRightMargin();
 977                 break;
 978             case REVERSE_LANDSCAPE:
 979                 ix = layout.getTopMargin();
 980                 iy = layout.getRightMargin();
 981                 iw = pWid - ix - layout.getBottomMargin();
 982                 ih = pHgt - iy - layout.getLeftMargin();
 983             }
 984             paper.setSize(pWid, pHgt);
 985             paper.setImageableArea(ix, iy, iw, ih);
 986             PageFormat format = new PageFormat();
 987             format.setOrientation(J2DPrinter.getOrientID(orient));
 988             format.setPaper(paper);
 989             return format;
 990         }
 991 
 992         private boolean getPage(int pageIndex) {
 993             if (pageIndex == currPageIndex) {
 994                 return true;
 995             }
 996             boolean nextPage = false;
 997             if (pageIndex > currPageIndex) {
 998                 nextPage = waitForNextPage(pageIndex);
 999             }
1000             return nextPage;
1001         }
1002 
1003         public int print(Graphics g, PageFormat pf, int pageIndex) {
1004             if (jobError || jobDone || !getPage(pageIndex)) {
1005                 return Printable.NO_SUCH_PAGE;
1006             }
1007             int x = (int)pf.getImageableX();
1008             int y = (int)pf.getImageableY();
1009             int w = (int)pf.getImageableWidth();
1010             int h = (int)pf.getImageableHeight();
1011             Node appNode = currPageInfo.getNode();
1012             g.translate(x, y);
1013             printNode(appNode, g, w, h);
1014             return Printable.PAGE_EXISTS;
1015         }
1016 
1017         private void printNode(Node node, Graphics g, int w, int h) {
1018             PrismPrintGraphics ppg =
1019                     new PrismPrintGraphics((Graphics2D) g, w, h);
1020             NGNode pgNode = node.impl_getPeer();
1021             boolean errored = false;
1022             try {
1023                 pgNode.render(ppg);
1024             } catch (Throwable t) {
1025                 if (com.sun.prism.impl.PrismSettings.debug) {
1026                     System.err.println("printNode caught exception.");
1027                     t.printStackTrace();
1028                 }
1029                 errored = true;
1030             }
1031             ppg.getResourceFactory()
1032                     .getTextureResourcePool()
1033                     .freeDisposalRequestedAndCheckResources(errored);
1034         }
1035 
1036         public Printable getPrintable(int pageIndex) {
1037             getPage(pageIndex);
1038             return this;
1039         }
1040 
1041         public PageFormat getPageFormat(int pageIndex) {
1042             getPage(pageIndex);
1043             return currPageFormat;
1044         }
1045 
1046         /*
1047          * Since we return unknown number of pages, then
1048          * the behaviour must be that we can only signal
1049          * end of the job by returning NO_SUCH_PAGE from
1050          * the print(..) method.
1051          */
1052         public int getNumberOfPages() {
1053             return Pageable.UNKNOWN_NUMBER_OF_PAGES;
1054         }
1055 
1056         /*
1057          * Executed on the application's thread.
1058          * Messages over to the printing thread.
1059          */
1060         private void implPrintPage(PageLayout pageLayout, Node node) {
1061 
1062             /* The public API printPage() is synchronized, so we know
1063              * that the app can't call it from 2 threads at the same
1064              * time, not that this is encouraged either.
1065              * Therefore when we are in this code, we know that any
1066              * previous page rendering has completed.
1067              * We also know that this means the app can't have 'queued up'
1068              * pages.
1069              * So, when we are in here, we know that the app is providing
1070              * the info for the next page.
1071              */
1072             pageDone = false;
1073             synchronized (monitor) {
1074                 newPageInfo = new PageInfo(pageLayout, node);
1075                 monitor.notify();
1076             }
1077             if (Toolkit.getToolkit().isFxUserThread()) {
1078                 elo = new Object();
1079                 Toolkit.getToolkit().enterNestedEventLoop(elo);
1080                 elo = null;
1081             } else {
1082                 while (!pageDone) {
1083                     synchronized (monitor) {
1084                         try {
1085                             if(!pageDone) {
1086                                 monitor.wait(1000);
1087                             }
1088                         } catch (InterruptedException e) {
1089                         }
1090                     }
1091                 }
1092             }
1093         }
1094 
1095     } /* END J2DPageable class */
1096 
1097 
1098     public boolean endJob() {
1099         if (jobRunning && !jobDone &&!jobError) {
1100             jobDone = true;
1101             try {
1102                 synchronized (monitor) {
1103                     monitor.notify();
1104                     return jobDone;
1105                 }
1106             } catch (IllegalStateException e) {
1107                 if (com.sun.prism.impl.PrismSettings.debug) {
1108                     System.err.println("Internal Error " + e);
1109                 }
1110             }
1111         } else {
1112             return false;
1113         }
1114         return jobDone;
1115     }
1116 
1117     public void cancelJob() {
1118         if (!pJob2D.isCancelled()) {
1119             pJob2D.cancel();
1120         }
1121         jobDone = true;
1122         if (jobRunning) {
1123             jobRunning = false;
1124             try {
1125                 synchronized (monitor) {
1126                     monitor.notify();
1127                 }
1128             } catch (IllegalStateException e) {
1129                 if (com.sun.prism.impl.PrismSettings.debug) {
1130                     System.err.println("Internal Error " + e);
1131                 }
1132             }
1133         }
1134     }
1135 }