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