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 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 }