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