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