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