1 /* 2 * Copyright (c) 1998, 2014, 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 sun.print; 27 28 import java.awt.Color; 29 import java.awt.Component; 30 import java.awt.Font; 31 import java.awt.FontMetrics; 32 import java.awt.GraphicsEnvironment; 33 import java.awt.Graphics; 34 import java.awt.Graphics2D; 35 import java.awt.HeadlessException; 36 import java.awt.Rectangle; 37 import java.awt.Shape; 38 39 import java.awt.image.BufferedImage; 40 41 import java.awt.font.FontRenderContext; 42 43 import java.awt.geom.AffineTransform; 44 import java.awt.geom.PathIterator; 45 import java.awt.geom.Rectangle2D; 46 47 import java.awt.image.BufferedImage; 48 49 import java.awt.print.Pageable; 50 import java.awt.print.PageFormat; 51 import java.awt.print.Paper; 52 import java.awt.print.Printable; 53 import java.awt.print.PrinterException; 54 import java.awt.print.PrinterIOException; 55 import java.awt.print.PrinterJob; 56 57 import javax.print.DocFlavor; 58 import javax.print.PrintService; 59 import javax.print.StreamPrintService; 60 import javax.print.attribute.HashPrintRequestAttributeSet; 61 import javax.print.attribute.PrintRequestAttributeSet; 62 import javax.print.attribute.PrintServiceAttributeSet; 63 import javax.print.attribute.standard.PrinterName; 64 import javax.print.attribute.standard.Chromaticity; 65 import javax.print.attribute.standard.Copies; 66 import javax.print.attribute.standard.Destination; 67 import javax.print.attribute.standard.DialogTypeSelection; 68 import javax.print.attribute.standard.JobName; 69 import javax.print.attribute.standard.Sides; 70 71 import java.io.BufferedInputStream; 72 import java.io.BufferedOutputStream; 73 import java.io.BufferedReader; 74 import java.io.CharConversionException; 75 import java.io.File; 76 import java.io.InputStream; 77 import java.io.InputStreamReader; 78 import java.io.IOException; 79 import java.io.FileInputStream; 80 import java.io.FileOutputStream; 81 import java.io.OutputStream; 82 import java.io.PrintStream; 83 import java.io.PrintWriter; 84 import java.io.StringWriter; 85 86 import java.util.ArrayList; 87 import java.util.Enumeration; 88 import java.util.Locale; 89 import java.util.Properties; 90 91 import sun.awt.CharsetString; 92 import sun.awt.FontConfiguration; 93 import sun.awt.FontDescriptor; 94 import sun.awt.PlatformFont; 95 import sun.awt.SunToolkit; 96 import sun.font.FontManagerFactory; 97 import sun.font.FontUtilities; 98 99 import java.nio.charset.*; 100 import java.nio.CharBuffer; 101 import java.nio.ByteBuffer; 102 import java.nio.file.Files; 103 104 //REMIND: Remove use of this class when IPPPrintService is moved to share directory. 105 import java.lang.reflect.Method; 106 107 /** 108 * A class which initiates and executes a PostScript printer job. 109 * 110 * @author Richard Blanchard 111 */ 112 public class PSPrinterJob extends RasterPrinterJob { 113 114 /* Class Constants */ 115 116 /** 117 * Passed to the <code>setFillMode</code> 118 * method this value forces fills to be 119 * done using the even-odd fill rule. 120 */ 121 protected static final int FILL_EVEN_ODD = 1; 122 123 /** 124 * Passed to the <code>setFillMode</code> 125 * method this value forces fills to be 126 * done using the non-zero winding rule. 127 */ 128 protected static final int FILL_WINDING = 2; 129 130 /* PostScript has a 64K maximum on its strings. 131 */ 132 private static final int MAX_PSSTR = (1024 * 64 - 1); 133 134 private static final int RED_MASK = 0x00ff0000; 135 private static final int GREEN_MASK = 0x0000ff00; 136 private static final int BLUE_MASK = 0x000000ff; 137 138 private static final int RED_SHIFT = 16; 139 private static final int GREEN_SHIFT = 8; 140 private static final int BLUE_SHIFT = 0; 141 142 private static final int LOWNIBBLE_MASK = 0x0000000f; 143 private static final int HINIBBLE_MASK = 0x000000f0; 144 private static final int HINIBBLE_SHIFT = 4; 145 private static final byte hexDigits[] = { 146 (byte)'0', (byte)'1', (byte)'2', (byte)'3', 147 (byte)'4', (byte)'5', (byte)'6', (byte)'7', 148 (byte)'8', (byte)'9', (byte)'A', (byte)'B', 149 (byte)'C', (byte)'D', (byte)'E', (byte)'F' 150 }; 151 152 private static final int PS_XRES = 300; 153 private static final int PS_YRES = 300; 154 155 private static final String ADOBE_PS_STR = "%!PS-Adobe-3.0"; 156 private static final String EOF_COMMENT = "%%EOF"; 157 private static final String PAGE_COMMENT = "%%Page: "; 158 159 private static final String READIMAGEPROC = "/imStr 0 def /imageSrc " + 160 "{currentfile /ASCII85Decode filter /RunLengthDecode filter " + 161 " imStr readstring pop } def"; 162 163 private static final String COPIES = "/#copies exch def"; 164 private static final String PAGE_SAVE = "/pgSave save def"; 165 private static final String PAGE_RESTORE = "pgSave restore"; 166 private static final String SHOWPAGE = "showpage"; 167 private static final String IMAGE_SAVE = "/imSave save def"; 168 private static final String IMAGE_STR = " string /imStr exch def"; 169 private static final String IMAGE_RESTORE = "imSave restore"; 170 171 private static final String COORD_PREP = " 0 exch translate " 172 + "1 -1 scale" 173 + "[72 " + PS_XRES + " div " 174 + "0 0 " 175 + "72 " + PS_YRES + " div " 176 + "0 0]concat"; 177 178 private static final String SetFontName = "F"; 179 180 private static final String DrawStringName = "S"; 181 182 /** 183 * The PostScript invocation to fill a path using the 184 * even-odd rule. (eofill) 185 */ 186 private static final String EVEN_ODD_FILL_STR = "EF"; 187 188 /** 189 * The PostScript invocation to fill a path using the 190 * non-zero winding rule. (fill) 191 */ 192 private static final String WINDING_FILL_STR = "WF"; 193 194 /** 195 * The PostScript to set the clip to be the current path 196 * using the even odd rule. (eoclip) 197 */ 198 private static final String EVEN_ODD_CLIP_STR = "EC"; 199 200 /** 201 * The PostScript to set the clip to be the current path 202 * using the non-zero winding rule. (clip) 203 */ 204 private static final String WINDING_CLIP_STR = "WC"; 205 206 /** 207 * Expecting two numbers on the PostScript stack, this 208 * invocation moves the current pen position. (moveto) 209 */ 210 private static final String MOVETO_STR = " M"; 211 /** 212 * Expecting two numbers on the PostScript stack, this 213 * invocation draws a PS line from the current pen 214 * position to the point on the stack. (lineto) 215 */ 216 private static final String LINETO_STR = " L"; 217 218 /** 219 * This PostScript operator takes two control points 220 * and an ending point and using the current pen 221 * position as a starting point adds a bezier 222 * curve to the current path. (curveto) 223 */ 224 private static final String CURVETO_STR = " C"; 225 226 /** 227 * The PostScript to pop a state off of the printer's 228 * gstate stack. (grestore) 229 */ 230 private static final String GRESTORE_STR = "R"; 231 /** 232 * The PostScript to push a state on to the printer's 233 * gstate stack. (gsave) 234 */ 235 private static final String GSAVE_STR = "G"; 236 237 /** 238 * Make the current PostScript path an empty path. (newpath) 239 */ 240 private static final String NEWPATH_STR = "N"; 241 242 /** 243 * Close the current subpath by generating a line segment 244 * from the current position to the start of the subpath. (closepath) 245 */ 246 private static final String CLOSEPATH_STR = "P"; 247 248 /** 249 * Use the three numbers on top of the PS operator 250 * stack to set the rgb color. (setrgbcolor) 251 */ 252 private static final String SETRGBCOLOR_STR = " SC"; 253 254 /** 255 * Use the top number on the stack to set the printer's 256 * current gray value. (setgray) 257 */ 258 private static final String SETGRAY_STR = " SG"; 259 260 /* Instance Variables */ 261 262 private int mDestType; 263 264 private String mDestination = "lp"; 265 266 private boolean mNoJobSheet = false; 267 268 private String mOptions; 269 270 private Font mLastFont; 271 272 private Color mLastColor; 273 274 private Shape mLastClip; 275 276 private AffineTransform mLastTransform; 277 278 /* non-null if printing EPS for Java Plugin */ 279 private EPSPrinter epsPrinter = null; 280 281 /** 282 * The metrics for the font currently set. 283 */ 284 FontMetrics mCurMetrics; 285 286 /** 287 * The output stream to which the generated PostScript 288 * is written. 289 */ 290 PrintStream mPSStream; 291 292 /* The temporary file to which we spool before sending to the printer */ 293 294 File spoolFile; 295 296 /** 297 * This string holds the PostScript operator to 298 * be used to fill a path. It can be changed 299 * by the <code>setFillMode</code> method. 300 */ 301 private String mFillOpStr = WINDING_FILL_STR; 302 303 /** 304 * This string holds the PostScript operator to 305 * be used to clip to a path. It can be changed 306 * by the <code>setFillMode</code> method. 307 */ 308 private String mClipOpStr = WINDING_CLIP_STR; 309 310 /** 311 * A stack that represents the PostScript gstate stack. 312 */ 313 ArrayList<GState> mGStateStack = new ArrayList<>(); 314 315 /** 316 * The x coordinate of the current pen position. 317 */ 318 private float mPenX; 319 320 /** 321 * The y coordinate of the current pen position. 322 */ 323 private float mPenY; 324 325 /** 326 * The x coordinate of the starting point of 327 * the current subpath. 328 */ 329 private float mStartPathX; 330 331 /** 332 * The y coordinate of the starting point of 333 * the current subpath. 334 */ 335 private float mStartPathY; 336 337 /** 338 * An optional mapping of fonts to PostScript names. 339 */ 340 private static Properties mFontProps = null; 341 342 private static boolean isMac; 343 344 /* Class static initialiser block */ 345 static { 346 //enable priviledges so initProps can access system properties, 347 // open the property file, etc. 348 java.security.AccessController.doPrivileged( 349 new java.security.PrivilegedAction<Object>() { 350 public Object run() { 351 mFontProps = initProps(); 352 String osName = System.getProperty("os.name"); 353 isMac = osName.startsWith("Mac"); 354 return null; 355 } 356 }); 357 } 358 359 /* 360 * Initialize PostScript font properties. 361 * Copied from PSPrintStream 362 */ 363 private static Properties initProps() { 364 // search psfont.properties for fonts 365 // and create and initialize fontProps if it exist. 366 367 String jhome = System.getProperty("java.home"); 368 369 if (jhome != null){ 370 String ulocale = SunToolkit.getStartupLocale().getLanguage(); 371 try { 372 373 File f = new File(jhome + File.separator + 374 "lib" + File.separator + 375 "psfontj2d.properties." + ulocale); 376 377 if (!f.canRead()){ 378 379 f = new File(jhome + File.separator + 380 "lib" + File.separator + 381 "psfont.properties." + ulocale); 382 if (!f.canRead()){ 383 384 f = new File(jhome + File.separator + "lib" + 385 File.separator + "psfontj2d.properties"); 386 387 if (!f.canRead()){ 388 389 f = new File(jhome + File.separator + "lib" + 390 File.separator + "psfont.properties"); 391 392 if (!f.canRead()){ 393 return (Properties)null; 394 } 395 } 396 } 397 } 398 399 // Load property file 400 InputStream in = 401 new BufferedInputStream(new FileInputStream(f.getPath())); 402 Properties props = new Properties(); 403 props.load(in); 404 in.close(); 405 return props; 406 } catch (Exception e){ 407 return (Properties)null; 408 } 409 } 410 return (Properties)null; 411 } 412 413 /* Constructors */ 414 415 public PSPrinterJob() 416 { 417 } 418 419 /* Instance Methods */ 420 421 /** 422 * Presents the user a dialog for changing properties of the 423 * print job interactively. 424 * @returns false if the user cancels the dialog and 425 * true otherwise. 426 * @exception HeadlessException if GraphicsEnvironment.isHeadless() 427 * returns true. 428 * @see java.awt.GraphicsEnvironment#isHeadless 429 */ 430 public boolean printDialog() throws HeadlessException { 431 432 if (GraphicsEnvironment.isHeadless()) { 433 throw new HeadlessException(); 434 } 435 436 if (attributes == null) { 437 attributes = new HashPrintRequestAttributeSet(); 438 } 439 attributes.add(new Copies(getCopies())); 440 attributes.add(new JobName(getJobName(), null)); 441 442 boolean doPrint = false; 443 DialogTypeSelection dts = 444 (DialogTypeSelection)attributes.get(DialogTypeSelection.class); 445 if (dts == DialogTypeSelection.NATIVE) { 446 // Remove DialogTypeSelection.NATIVE to prevent infinite loop in 447 // RasterPrinterJob. 448 attributes.remove(DialogTypeSelection.class); 449 doPrint = printDialog(attributes); 450 // restore attribute 451 attributes.add(DialogTypeSelection.NATIVE); 452 } else { 453 doPrint = printDialog(attributes); 454 } 455 456 if (doPrint) { 457 JobName jobName = (JobName)attributes.get(JobName.class); 458 if (jobName != null) { 459 setJobName(jobName.getValue()); 460 } 461 Copies copies = (Copies)attributes.get(Copies.class); 462 if (copies != null) { 463 setCopies(copies.getValue()); 464 } 465 466 Destination dest = (Destination)attributes.get(Destination.class); 467 468 if (dest != null) { 469 try { 470 mDestType = RasterPrinterJob.FILE; 471 mDestination = (new File(dest.getURI())).getPath(); 472 } catch (Exception e) { 473 mDestination = "out.ps"; 474 } 475 } else { 476 mDestType = RasterPrinterJob.PRINTER; 477 PrintService pServ = getPrintService(); 478 if (pServ != null) { 479 mDestination = pServ.getName(); 480 if (isMac) { 481 PrintServiceAttributeSet psaSet = pServ.getAttributes() ; 482 if (psaSet != null) { 483 mDestination = psaSet.get(PrinterName.class).toString(); 484 } 485 } 486 } 487 } 488 } 489 490 return doPrint; 491 } 492 493 /** 494 * Invoked by the RasterPrinterJob super class 495 * this method is called to mark the start of a 496 * document. 497 */ 498 protected void startDoc() throws PrinterException { 499 500 // A security check has been performed in the 501 // java.awt.print.printerJob.getPrinterJob method. 502 // We use an inner class to execute the privilged open operations. 503 // Note that we only open a file if it has been nominated by 504 // the end-user in a dialog that we ouselves put up. 505 506 OutputStream output; 507 508 if (epsPrinter == null) { 509 if (getPrintService() instanceof PSStreamPrintService) { 510 StreamPrintService sps = (StreamPrintService)getPrintService(); 511 mDestType = RasterPrinterJob.STREAM; 512 if (sps.isDisposed()) { 513 throw new PrinterException("service is disposed"); 514 } 515 output = sps.getOutputStream(); 516 if (output == null) { 517 throw new PrinterException("Null output stream"); 518 } 519 } else { 520 /* REMIND: This needs to be more maintainable */ 521 mNoJobSheet = super.noJobSheet; 522 if (super.destinationAttr != null) { 523 mDestType = RasterPrinterJob.FILE; 524 mDestination = super.destinationAttr; 525 } 526 if (mDestType == RasterPrinterJob.FILE) { 527 try { 528 spoolFile = new File(mDestination); 529 output = new FileOutputStream(spoolFile); 530 } catch (IOException ex) { 531 throw new PrinterIOException(ex); 532 } 533 } else { 534 PrinterOpener po = new PrinterOpener(); 535 java.security.AccessController.doPrivileged(po); 536 if (po.pex != null) { 537 throw po.pex; 538 } 539 output = po.result; 540 } 541 } 542 543 mPSStream = new PrintStream(new BufferedOutputStream(output)); 544 mPSStream.println(ADOBE_PS_STR); 545 } 546 547 mPSStream.println("%%BeginProlog"); 548 mPSStream.println(READIMAGEPROC); 549 mPSStream.println("/BD {bind def} bind def"); 550 mPSStream.println("/D {def} BD"); 551 mPSStream.println("/C {curveto} BD"); 552 mPSStream.println("/L {lineto} BD"); 553 mPSStream.println("/M {moveto} BD"); 554 mPSStream.println("/R {grestore} BD"); 555 mPSStream.println("/G {gsave} BD"); 556 mPSStream.println("/N {newpath} BD"); 557 mPSStream.println("/P {closepath} BD"); 558 mPSStream.println("/EC {eoclip} BD"); 559 mPSStream.println("/WC {clip} BD"); 560 mPSStream.println("/EF {eofill} BD"); 561 mPSStream.println("/WF {fill} BD"); 562 mPSStream.println("/SG {setgray} BD"); 563 mPSStream.println("/SC {setrgbcolor} BD"); 564 mPSStream.println("/ISOF {"); 565 mPSStream.println(" dup findfont dup length 1 add dict begin {"); 566 mPSStream.println(" 1 index /FID eq {pop pop} {D} ifelse"); 567 mPSStream.println(" } forall /Encoding ISOLatin1Encoding D"); 568 mPSStream.println(" currentdict end definefont"); 569 mPSStream.println("} BD"); 570 mPSStream.println("/NZ {dup 1 lt {pop 1} if} BD"); 571 /* The following procedure takes args: string, x, y, desiredWidth. 572 * It calculates using stringwidth the width of the string in the 573 * current font and subtracts it from the desiredWidth and divides 574 * this by stringLen-1. This gives us a per-glyph adjustment in 575 * the spacing needed (either +ve or -ve) to make the string 576 * print at the desiredWidth. The ashow procedure call takes this 577 * per-glyph adjustment as an argument. This is necessary for WYSIWYG 578 */ 579 mPSStream.println("/"+DrawStringName +" {"); 580 mPSStream.println(" moveto 1 index stringwidth pop NZ sub"); 581 mPSStream.println(" 1 index length 1 sub NZ div 0"); 582 mPSStream.println(" 3 2 roll ashow newpath} BD"); 583 mPSStream.println("/FL ["); 584 if (mFontProps == null){ 585 mPSStream.println(" /Helvetica ISOF"); 586 mPSStream.println(" /Helvetica-Bold ISOF"); 587 mPSStream.println(" /Helvetica-Oblique ISOF"); 588 mPSStream.println(" /Helvetica-BoldOblique ISOF"); 589 mPSStream.println(" /Times-Roman ISOF"); 590 mPSStream.println(" /Times-Bold ISOF"); 591 mPSStream.println(" /Times-Italic ISOF"); 592 mPSStream.println(" /Times-BoldItalic ISOF"); 593 mPSStream.println(" /Courier ISOF"); 594 mPSStream.println(" /Courier-Bold ISOF"); 595 mPSStream.println(" /Courier-Oblique ISOF"); 596 mPSStream.println(" /Courier-BoldOblique ISOF"); 597 } else { 598 int cnt = Integer.parseInt(mFontProps.getProperty("font.num", "9")); 599 for (int i = 0; i < cnt; i++){ 600 mPSStream.println(" /" + mFontProps.getProperty 601 ("font." + String.valueOf(i), "Courier ISOF")); 602 } 603 } 604 mPSStream.println("] D"); 605 606 mPSStream.println("/"+SetFontName +" {"); 607 mPSStream.println(" FL exch get exch scalefont"); 608 mPSStream.println(" [1 0 0 -1 0 0] makefont setfont} BD"); 609 610 mPSStream.println("%%EndProlog"); 611 612 mPSStream.println("%%BeginSetup"); 613 if (epsPrinter == null) { 614 // Set Page Size using first page's format. 615 PageFormat pageFormat = getPageable().getPageFormat(0); 616 double paperHeight = pageFormat.getPaper().getHeight(); 617 double paperWidth = pageFormat.getPaper().getWidth(); 618 619 /* PostScript printers can always generate uncollated copies. 620 */ 621 mPSStream.print("<< /PageSize [" + 622 paperWidth + " "+ paperHeight+"]"); 623 624 final PrintService pservice = getPrintService(); 625 Boolean isPS = java.security.AccessController.doPrivileged( 626 new java.security.PrivilegedAction<Boolean>() { 627 public Boolean run() { 628 try { 629 Class<?> psClass = Class.forName("sun.print.IPPPrintService"); 630 if (psClass.isInstance(pservice)) { 631 Method isPSMethod = psClass.getMethod("isPostscript", 632 (Class[])null); 633 return (Boolean)isPSMethod.invoke(pservice, (Object[])null); 634 } 635 } catch (Throwable t) { 636 } 637 return Boolean.TRUE; 638 } 639 } 640 ); 641 if (isPS) { 642 mPSStream.print(" /DeferredMediaSelection true"); 643 } 644 645 mPSStream.print(" /ImagingBBox null /ManualFeed false"); 646 mPSStream.print(isCollated() ? " /Collate true":""); 647 mPSStream.print(" /NumCopies " +getCopiesInt()); 648 649 if (sidesAttr != Sides.ONE_SIDED) { 650 if (sidesAttr == Sides.TWO_SIDED_LONG_EDGE) { 651 mPSStream.print(" /Duplex true "); 652 } else if (sidesAttr == Sides.TWO_SIDED_SHORT_EDGE) { 653 mPSStream.print(" /Duplex true /Tumble true "); 654 } 655 } 656 mPSStream.println(" >> setpagedevice "); 657 } 658 mPSStream.println("%%EndSetup"); 659 } 660 661 // Inner class to run "privileged" to open the printer output stream. 662 663 private class PrinterOpener implements java.security.PrivilegedAction<OutputStream> { 664 PrinterException pex; 665 OutputStream result; 666 667 public OutputStream run() { 668 try { 669 670 /* Write to a temporary file which will be spooled to 671 * the printer then deleted. In the case that the file 672 * is not removed for some reason, request that it is 673 * removed when the VM exits. 674 */ 675 spoolFile = Files.createTempFile("javaprint", ".ps").toFile(); 676 spoolFile.deleteOnExit(); 677 678 result = new FileOutputStream(spoolFile); 679 return result; 680 } catch (IOException ex) { 681 // If there is an IOError we subvert it to a PrinterException. 682 pex = new PrinterIOException(ex); 683 } 684 return null; 685 } 686 } 687 688 // Inner class to run "privileged" to invoke the system print command 689 690 private class PrinterSpooler implements java.security.PrivilegedAction<Object> { 691 PrinterException pex; 692 693 private void handleProcessFailure(final Process failedProcess, 694 final String[] execCmd, final int result) throws IOException { 695 try (StringWriter sw = new StringWriter(); 696 PrintWriter pw = new PrintWriter(sw)) { 697 pw.append("error=").append(Integer.toString(result)); 698 pw.append(" running:"); 699 for (String arg: execCmd) { 700 pw.append(" '").append(arg).append("'"); 701 } 702 try (InputStream is = failedProcess.getErrorStream(); 703 InputStreamReader isr = new InputStreamReader(is); 704 BufferedReader br = new BufferedReader(isr)) { 705 while (br.ready()) { 706 pw.println(); 707 pw.append("\t\t").append(br.readLine()); 708 } 709 } finally { 710 pw.flush(); 711 } 712 throw new IOException(sw.toString()); 713 } 714 } 715 716 public Object run() { 717 if (spoolFile == null || !spoolFile.exists()) { 718 pex = new PrinterException("No spool file"); 719 return null; 720 } 721 try { 722 /** 723 * Spool to the printer. 724 */ 725 String fileName = spoolFile.getAbsolutePath(); 726 String execCmd[] = printExecCmd(mDestination, mOptions, 727 mNoJobSheet, getJobNameInt(), 728 1, fileName); 729 730 Process process = Runtime.getRuntime().exec(execCmd); 731 process.waitFor(); 732 final int result = process.exitValue(); 733 if (0 != result) { 734 handleProcessFailure(process, execCmd, result); 735 } 736 } catch (IOException ex) { 737 pex = new PrinterIOException(ex); 738 } catch (InterruptedException ie) { 739 pex = new PrinterException(ie.toString()); 740 } finally { 741 spoolFile.delete(); 742 } 743 return null; 744 } 745 } 746 747 748 /** 749 * Invoked if the application cancelled the printjob. 750 */ 751 protected void abortDoc() { 752 if (mPSStream != null && mDestType != RasterPrinterJob.STREAM) { 753 mPSStream.close(); 754 } 755 java.security.AccessController.doPrivileged( 756 new java.security.PrivilegedAction<Object>() { 757 758 public Object run() { 759 if (spoolFile != null && spoolFile.exists()) { 760 spoolFile.delete(); 761 } 762 return null; 763 } 764 }); 765 } 766 767 /** 768 * Invoked by the RasterPrintJob super class 769 * this method is called after that last page 770 * has been imaged. 771 */ 772 protected void endDoc() throws PrinterException { 773 if (mPSStream != null) { 774 mPSStream.println(EOF_COMMENT); 775 mPSStream.flush(); 776 if (mDestType != RasterPrinterJob.STREAM) { 777 mPSStream.close(); 778 } 779 } 780 if (mDestType == RasterPrinterJob.PRINTER) { 781 PrintService pServ = getPrintService(); 782 if (pServ != null) { 783 mDestination = pServ.getName(); 784 if (isMac) { 785 PrintServiceAttributeSet psaSet = pServ.getAttributes(); 786 if (psaSet != null) { 787 mDestination = psaSet.get(PrinterName.class).toString() ; 788 } 789 } 790 } 791 PrinterSpooler spooler = new PrinterSpooler(); 792 java.security.AccessController.doPrivileged(spooler); 793 if (spooler.pex != null) { 794 throw spooler.pex; 795 } 796 } 797 } 798 799 /** 800 * The RasterPrintJob super class calls this method 801 * at the start of each page. 802 */ 803 protected void startPage(PageFormat pageFormat, Printable painter, 804 int index, boolean paperChanged) 805 throws PrinterException 806 { 807 double paperHeight = pageFormat.getPaper().getHeight(); 808 double paperWidth = pageFormat.getPaper().getWidth(); 809 int pageNumber = index + 1; 810 811 /* Place an initial gstate on to our gstate stack. 812 * It will have the default PostScript gstate 813 * attributes. 814 */ 815 mGStateStack = new ArrayList<>(); 816 mGStateStack.add(new GState()); 817 818 mPSStream.println(PAGE_COMMENT + pageNumber + " " + pageNumber); 819 820 /* Check current page's pageFormat against the previous pageFormat, 821 */ 822 if (index > 0 && paperChanged) { 823 824 mPSStream.print("<< /PageSize [" + 825 paperWidth + " " + paperHeight + "]"); 826 827 final PrintService pservice = getPrintService(); 828 Boolean isPS = java.security.AccessController.doPrivileged( 829 new java.security.PrivilegedAction<Boolean>() { 830 public Boolean run() { 831 try { 832 Class<?> psClass = 833 Class.forName("sun.print.IPPPrintService"); 834 if (psClass.isInstance(pservice)) { 835 Method isPSMethod = 836 psClass.getMethod("isPostscript", 837 (Class[])null); 838 return (Boolean) 839 isPSMethod.invoke(pservice, 840 (Object[])null); 841 } 842 } catch (Throwable t) { 843 } 844 return Boolean.TRUE; 845 } 846 } 847 ); 848 849 if (isPS) { 850 mPSStream.print(" /DeferredMediaSelection true"); 851 } 852 mPSStream.println(" >> setpagedevice"); 853 } 854 mPSStream.println(PAGE_SAVE); 855 mPSStream.println(paperHeight + COORD_PREP); 856 } 857 858 /** 859 * The RastePrintJob super class calls this method 860 * at the end of each page. 861 */ 862 protected void endPage(PageFormat format, Printable painter, 863 int index) 864 throws PrinterException 865 { 866 mPSStream.println(PAGE_RESTORE); 867 mPSStream.println(SHOWPAGE); 868 } 869 870 /** 871 * Convert the 24 bit BGR image buffer represented by 872 * <code>image</code> to PostScript. The image is drawn at 873 * <code>(destX, destY)</code> in device coordinates. 874 * The image is scaled into a square of size 875 * specified by <code>destWidth</code> and 876 * <code>destHeight</code>. The portion of the 877 * source image copied into that square is specified 878 * by <code>srcX</code>, <code>srcY</code>, 879 * <code>srcWidth</code>, and srcHeight. 880 */ 881 protected void drawImageBGR(byte[] bgrData, 882 float destX, float destY, 883 float destWidth, float destHeight, 884 float srcX, float srcY, 885 float srcWidth, float srcHeight, 886 int srcBitMapWidth, int srcBitMapHeight) { 887 888 /* We draw images at device resolution so we probably need 889 * to change the current PostScript transform. 890 */ 891 setTransform(new AffineTransform()); 892 prepDrawing(); 893 894 int intSrcWidth = (int) srcWidth; 895 int intSrcHeight = (int) srcHeight; 896 897 mPSStream.println(IMAGE_SAVE); 898 899 /* Create a PS string big enough to hold a row of pixels. 900 */ 901 int psBytesPerRow = 3 * intSrcWidth; 902 while (psBytesPerRow > MAX_PSSTR) { 903 psBytesPerRow /= 2; 904 } 905 906 mPSStream.println(psBytesPerRow + IMAGE_STR); 907 908 /* Scale and translate the unit image. 909 */ 910 mPSStream.println("[" + destWidth + " 0 " 911 + "0 " + destHeight 912 + " " + destX + " " + destY 913 +"]concat"); 914 915 /* Color Image invocation. 916 */ 917 mPSStream.println(intSrcWidth + " " + intSrcHeight + " " + 8 + "[" 918 + intSrcWidth + " 0 " 919 + "0 " + intSrcHeight 920 + " 0 " + 0 + "]" 921 + "/imageSrc load false 3 colorimage"); 922 923 /* Image data. 924 */ 925 int index = 0; 926 byte[] rgbData = new byte[intSrcWidth * 3]; 927 928 try { 929 /* Skip the parts of the image that are not part 930 * of the source rectangle. 931 */ 932 index = (int) srcY * srcBitMapWidth; 933 934 for(int i = 0; i < intSrcHeight; i++) { 935 936 /* Skip the left part of the image that is not 937 * part of the source rectangle. 938 */ 939 index += (int) srcX; 940 941 index = swapBGRtoRGB(bgrData, index, rgbData); 942 byte[] encodedData = rlEncode(rgbData); 943 byte[] asciiData = ascii85Encode(encodedData); 944 mPSStream.write(asciiData); 945 mPSStream.println(""); 946 } 947 948 /* 949 * If there is an IOError we subvert it to a PrinterException. 950 * Fix: There has got to be a better way, maybe define 951 * a PrinterIOException and then throw that? 952 */ 953 } catch (IOException e) { 954 //throw new PrinterException(e.toString()); 955 } 956 957 mPSStream.println(IMAGE_RESTORE); 958 } 959 960 /** 961 * Prints the contents of the array of ints, 'data' 962 * to the current page. The band is placed at the 963 * location (x, y) in device coordinates on the 964 * page. The width and height of the band is 965 * specified by the caller. Currently the data 966 * is 24 bits per pixel in BGR format. 967 */ 968 protected void printBand(byte[] bgrData, int x, int y, 969 int width, int height) 970 throws PrinterException 971 { 972 973 mPSStream.println(IMAGE_SAVE); 974 975 /* Create a PS string big enough to hold a row of pixels. 976 */ 977 int psBytesPerRow = 3 * width; 978 while (psBytesPerRow > MAX_PSSTR) { 979 psBytesPerRow /= 2; 980 } 981 982 mPSStream.println(psBytesPerRow + IMAGE_STR); 983 984 /* Scale and translate the unit image. 985 */ 986 mPSStream.println("[" + width + " 0 " 987 + "0 " + height 988 + " " + x + " " + y 989 +"]concat"); 990 991 /* Color Image invocation. 992 */ 993 mPSStream.println(width + " " + height + " " + 8 + "[" 994 + width + " 0 " 995 + "0 " + -height 996 + " 0 " + height + "]" 997 + "/imageSrc load false 3 colorimage"); 998 999 /* Image data. 1000 */ 1001 int index = 0; 1002 byte[] rgbData = new byte[width*3]; 1003 1004 try { 1005 for(int i = 0; i < height; i++) { 1006 index = swapBGRtoRGB(bgrData, index, rgbData); 1007 byte[] encodedData = rlEncode(rgbData); 1008 byte[] asciiData = ascii85Encode(encodedData); 1009 mPSStream.write(asciiData); 1010 mPSStream.println(""); 1011 } 1012 1013 } catch (IOException e) { 1014 throw new PrinterIOException(e); 1015 } 1016 1017 mPSStream.println(IMAGE_RESTORE); 1018 } 1019 1020 /** 1021 * Examine the metrics captured by the 1022 * <code>PeekGraphics</code> instance and 1023 * if capable of directly converting this 1024 * print job to the printer's control language 1025 * or the native OS's graphics primitives, then 1026 * return a <code>PSPathGraphics</code> to perform 1027 * that conversion. If there is not an object 1028 * capable of the conversion then return 1029 * <code>null</code>. Returning <code>null</code> 1030 * causes the print job to be rasterized. 1031 */ 1032 1033 protected Graphics2D createPathGraphics(PeekGraphics peekGraphics, 1034 PrinterJob printerJob, 1035 Printable painter, 1036 PageFormat pageFormat, 1037 int pageIndex) { 1038 1039 PSPathGraphics pathGraphics; 1040 PeekMetrics metrics = peekGraphics.getMetrics(); 1041 1042 /* If the application has drawn anything that 1043 * out PathGraphics class can not handle then 1044 * return a null PathGraphics. 1045 */ 1046 if (forcePDL == false && (forceRaster == true 1047 || metrics.hasNonSolidColors() 1048 || metrics.hasCompositing())) { 1049 1050 pathGraphics = null; 1051 } else { 1052 1053 BufferedImage bufferedImage = new BufferedImage(8, 8, 1054 BufferedImage.TYPE_INT_RGB); 1055 Graphics2D bufferedGraphics = bufferedImage.createGraphics(); 1056 boolean canRedraw = peekGraphics.getAWTDrawingOnly() == false; 1057 1058 pathGraphics = new PSPathGraphics(bufferedGraphics, printerJob, 1059 painter, pageFormat, pageIndex, 1060 canRedraw); 1061 } 1062 1063 return pathGraphics; 1064 } 1065 1066 /** 1067 * Intersect the gstate's current path with the 1068 * current clip and make the result the new clip. 1069 */ 1070 protected void selectClipPath() { 1071 1072 mPSStream.println(mClipOpStr); 1073 } 1074 1075 protected void setClip(Shape clip) { 1076 1077 mLastClip = clip; 1078 } 1079 1080 protected void setTransform(AffineTransform transform) { 1081 mLastTransform = transform; 1082 } 1083 1084 /** 1085 * Set the current PostScript font. 1086 * Taken from outFont in PSPrintStream. 1087 */ 1088 protected boolean setFont(Font font) { 1089 mLastFont = font; 1090 return true; 1091 } 1092 1093 /** 1094 * Given an array of CharsetStrings that make up a run 1095 * of text, this routine converts each CharsetString to 1096 * an index into our PostScript font list. If one or more 1097 * CharsetStrings can not be represented by a PostScript 1098 * font, then this routine will return a null array. 1099 */ 1100 private int[] getPSFontIndexArray(Font font, CharsetString[] charSet) { 1101 int[] psFont = null; 1102 1103 if (mFontProps != null) { 1104 psFont = new int[charSet.length]; 1105 } 1106 1107 for (int i = 0; i < charSet.length && psFont != null; i++){ 1108 1109 /* Get the encoding of the run of text. 1110 */ 1111 CharsetString cs = charSet[i]; 1112 1113 CharsetEncoder fontCS = cs.fontDescriptor.encoder; 1114 String charsetName = cs.fontDescriptor.getFontCharsetName(); 1115 /* 1116 * sun.awt.Symbol perhaps should return "symbol" for encoding. 1117 * Similarly X11Dingbats should return "dingbats" 1118 * Forced to check for win32 & x/unix names for these converters. 1119 */ 1120 1121 if ("Symbol".equals(charsetName)) { 1122 charsetName = "symbol"; 1123 } else if ("WingDings".equals(charsetName) || 1124 "X11Dingbats".equals(charsetName)) { 1125 charsetName = "dingbats"; 1126 } else { 1127 charsetName = makeCharsetName(charsetName, cs.charsetChars); 1128 } 1129 1130 int styleMask = font.getStyle() | 1131 FontUtilities.getFont2D(font).getStyle(); 1132 1133 String style = FontConfiguration.getStyleString(styleMask); 1134 1135 /* First we map the font name through the properties file. 1136 * This mapping provides alias names for fonts, for example, 1137 * "timesroman" is mapped to "serif". 1138 */ 1139 String fontName = font.getFamily().toLowerCase(Locale.ENGLISH); 1140 fontName = fontName.replace(' ', '_'); 1141 String name = mFontProps.getProperty(fontName, ""); 1142 1143 /* Now map the alias name, character set name, and style 1144 * to a PostScript name. 1145 */ 1146 String psName = 1147 mFontProps.getProperty(name + "." + charsetName + "." + style, 1148 null); 1149 1150 if (psName != null) { 1151 1152 /* Get the PostScript font index for the PostScript font. 1153 */ 1154 try { 1155 psFont[i] = 1156 Integer.parseInt(mFontProps.getProperty(psName)); 1157 1158 /* If there is no PostScript font for this font name, 1159 * then we want to termintate the loop and the method 1160 * indicating our failure. Setting the array to null 1161 * is used to indicate these failures. 1162 */ 1163 } catch(NumberFormatException e){ 1164 psFont = null; 1165 } 1166 1167 /* There was no PostScript name for the font, character set, 1168 * and style so give up. 1169 */ 1170 } else { 1171 psFont = null; 1172 } 1173 } 1174 1175 return psFont; 1176 } 1177 1178 1179 private static String escapeParens(String str) { 1180 if (str.indexOf('(') == -1 && str.indexOf(')') == -1 ) { 1181 return str; 1182 } else { 1183 int count = 0; 1184 int pos = 0; 1185 while ((pos = str.indexOf('(', pos)) != -1) { 1186 count++; 1187 pos++; 1188 } 1189 pos = 0; 1190 while ((pos = str.indexOf(')', pos)) != -1) { 1191 count++; 1192 pos++; 1193 } 1194 char []inArr = str.toCharArray(); 1195 char []outArr = new char[inArr.length+count]; 1196 pos = 0; 1197 for (int i=0;i<inArr.length;i++) { 1198 if (inArr[i] == '(' || inArr[i] == ')') { 1199 outArr[pos++] = '\\'; 1200 } 1201 outArr[pos++] = inArr[i]; 1202 } 1203 return new String(outArr); 1204 1205 } 1206 } 1207 1208 /* return of 0 means unsupported. Other return indicates the number 1209 * of distinct PS fonts needed to draw this text. This saves us 1210 * doing this processing one extra time. 1211 */ 1212 protected int platformFontCount(Font font, String str) { 1213 if (mFontProps == null) { 1214 return 0; 1215 } 1216 CharsetString[] acs = 1217 ((PlatformFont)(font.getPeer())).makeMultiCharsetString(str,false); 1218 if (acs == null) { 1219 /* AWT can't convert all chars so use 2D path */ 1220 return 0; 1221 } 1222 int[] psFonts = getPSFontIndexArray(font, acs); 1223 return (psFonts == null) ? 0 : psFonts.length; 1224 } 1225 1226 protected boolean textOut(Graphics g, String str, float x, float y, 1227 Font mLastFont, FontRenderContext frc, 1228 float width) { 1229 boolean didText = true; 1230 1231 if (mFontProps == null) { 1232 return false; 1233 } else { 1234 prepDrawing(); 1235 1236 /* On-screen drawString renders most control chars as the missing 1237 * glyph and have the non-zero advance of that glyph. 1238 * Exceptions are \t, \n and \r which are considered zero-width. 1239 * Postscript handles control chars mostly as a missing glyph. 1240 * But we use 'ashow' specifying a width for the string which 1241 * assumes zero-width for those three exceptions, and Postscript 1242 * tries to squeeze the extra char in, with the result that the 1243 * glyphs look compressed or even overlap. 1244 * So exclude those control chars from the string sent to PS. 1245 */ 1246 str = removeControlChars(str); 1247 if (str.length() == 0) { 1248 return true; 1249 } 1250 CharsetString[] acs = 1251 ((PlatformFont) 1252 (mLastFont.getPeer())).makeMultiCharsetString(str, false); 1253 if (acs == null) { 1254 /* AWT can't convert all chars so use 2D path */ 1255 return false; 1256 } 1257 /* Get an array of indices into our PostScript name 1258 * table. If all of the runs can not be converted 1259 * to PostScript fonts then null is returned and 1260 * we'll want to fall back to printing the text 1261 * as shapes. 1262 */ 1263 int[] psFonts = getPSFontIndexArray(mLastFont, acs); 1264 if (psFonts != null) { 1265 1266 for (int i = 0; i < acs.length; i++){ 1267 CharsetString cs = acs[i]; 1268 CharsetEncoder fontCS = cs.fontDescriptor.encoder; 1269 1270 StringBuffer nativeStr = new StringBuffer(); 1271 byte[] strSeg = new byte[cs.length * 2]; 1272 int len = 0; 1273 try { 1274 ByteBuffer bb = ByteBuffer.wrap(strSeg); 1275 fontCS.encode(CharBuffer.wrap(cs.charsetChars, 1276 cs.offset, 1277 cs.length), 1278 bb, true); 1279 bb.flip(); 1280 len = bb.limit(); 1281 } catch(IllegalStateException xx){ 1282 continue; 1283 } catch(CoderMalfunctionError xx){ 1284 continue; 1285 } 1286 /* The width to fit to may either be specified, 1287 * or calculated. Specifying by the caller is only 1288 * valid if the text does not need to be decomposed 1289 * into multiple calls. 1290 */ 1291 float desiredWidth; 1292 if (acs.length == 1 && width != 0f) { 1293 desiredWidth = width; 1294 } else { 1295 Rectangle2D r2d = 1296 mLastFont.getStringBounds(cs.charsetChars, 1297 cs.offset, 1298 cs.offset+cs.length, 1299 frc); 1300 desiredWidth = (float)r2d.getWidth(); 1301 } 1302 /* unprintable chars had width of 0, causing a PS error 1303 */ 1304 if (desiredWidth == 0) { 1305 return didText; 1306 } 1307 nativeStr.append('<'); 1308 for (int j = 0; j < len; j++){ 1309 byte b = strSeg[j]; 1310 // to avoid encoding conversion with println() 1311 String hexS = Integer.toHexString(b); 1312 int length = hexS.length(); 1313 if (length > 2) { 1314 hexS = hexS.substring(length - 2, length); 1315 } else if (length == 1) { 1316 hexS = "0" + hexS; 1317 } else if (length == 0) { 1318 hexS = "00"; 1319 } 1320 nativeStr.append(hexS); 1321 } 1322 nativeStr.append('>'); 1323 /* This comment costs too much in output file size */ 1324 // mPSStream.println("% Font[" + mLastFont.getName() + ", " + 1325 // FontConfiguration.getStyleString(mLastFont.getStyle()) + ", " 1326 // + mLastFont.getSize2D() + "]"); 1327 getGState().emitPSFont(psFonts[i], mLastFont.getSize2D()); 1328 1329 // out String 1330 mPSStream.println(nativeStr.toString() + " " + 1331 desiredWidth + " " + x + " " + y + " " + 1332 DrawStringName); 1333 x += desiredWidth; 1334 } 1335 } else { 1336 didText = false; 1337 } 1338 } 1339 1340 return didText; 1341 } 1342 /** 1343 * Set the current path rule to be either 1344 * <code>FILL_EVEN_ODD</code> (using the 1345 * even-odd file rule) or <code>FILL_WINDING</code> 1346 * (using the non-zero winding rule.) 1347 */ 1348 protected void setFillMode(int fillRule) { 1349 1350 switch (fillRule) { 1351 1352 case FILL_EVEN_ODD: 1353 mFillOpStr = EVEN_ODD_FILL_STR; 1354 mClipOpStr = EVEN_ODD_CLIP_STR; 1355 break; 1356 1357 case FILL_WINDING: 1358 mFillOpStr = WINDING_FILL_STR; 1359 mClipOpStr = WINDING_CLIP_STR; 1360 break; 1361 1362 default: 1363 throw new IllegalArgumentException(); 1364 } 1365 1366 } 1367 1368 /** 1369 * Set the printer's current color to be that 1370 * defined by <code>color</code> 1371 */ 1372 protected void setColor(Color color) { 1373 mLastColor = color; 1374 } 1375 1376 /** 1377 * Fill the current path using the current fill mode 1378 * and color. 1379 */ 1380 protected void fillPath() { 1381 1382 mPSStream.println(mFillOpStr); 1383 } 1384 1385 /** 1386 * Called to mark the start of a new path. 1387 */ 1388 protected void beginPath() { 1389 1390 prepDrawing(); 1391 mPSStream.println(NEWPATH_STR); 1392 1393 mPenX = 0; 1394 mPenY = 0; 1395 } 1396 1397 /** 1398 * Close the current subpath by appending a straight 1399 * line from the current point to the subpath's 1400 * starting point. 1401 */ 1402 protected void closeSubpath() { 1403 1404 mPSStream.println(CLOSEPATH_STR); 1405 1406 mPenX = mStartPathX; 1407 mPenY = mStartPathY; 1408 } 1409 1410 1411 /** 1412 * Generate PostScript to move the current pen 1413 * position to <code>(x, y)</code>. 1414 */ 1415 protected void moveTo(float x, float y) { 1416 1417 mPSStream.println(trunc(x) + " " + trunc(y) + MOVETO_STR); 1418 1419 /* moveto marks the start of a new subpath 1420 * and we need to remember that starting 1421 * position so that we know where the 1422 * pen returns to with a close path. 1423 */ 1424 mStartPathX = x; 1425 mStartPathY = y; 1426 1427 mPenX = x; 1428 mPenY = y; 1429 } 1430 /** 1431 * Generate PostScript to draw a line from the 1432 * current pen position to <code>(x, y)</code>. 1433 */ 1434 protected void lineTo(float x, float y) { 1435 1436 mPSStream.println(trunc(x) + " " + trunc(y) + LINETO_STR); 1437 1438 mPenX = x; 1439 mPenY = y; 1440 } 1441 1442 /** 1443 * Add to the current path a bezier curve formed 1444 * by the current pen position and the method parameters 1445 * which are two control points and an ending 1446 * point. 1447 */ 1448 protected void bezierTo(float control1x, float control1y, 1449 float control2x, float control2y, 1450 float endX, float endY) { 1451 1452 // mPSStream.println(control1x + " " + control1y 1453 // + " " + control2x + " " + control2y 1454 // + " " + endX + " " + endY 1455 // + CURVETO_STR); 1456 mPSStream.println(trunc(control1x) + " " + trunc(control1y) 1457 + " " + trunc(control2x) + " " + trunc(control2y) 1458 + " " + trunc(endX) + " " + trunc(endY) 1459 + CURVETO_STR); 1460 1461 1462 mPenX = endX; 1463 mPenY = endY; 1464 } 1465 1466 String trunc(float f) { 1467 float af = Math.abs(f); 1468 if (af >= 1f && af <=1000f) { 1469 f = Math.round(f*1000)/1000f; 1470 } 1471 return Float.toString(f); 1472 } 1473 1474 /** 1475 * Return the x coordinate of the pen in the 1476 * current path. 1477 */ 1478 protected float getPenX() { 1479 1480 return mPenX; 1481 } 1482 /** 1483 * Return the y coordinate of the pen in the 1484 * current path. 1485 */ 1486 protected float getPenY() { 1487 1488 return mPenY; 1489 } 1490 1491 /** 1492 * Return the x resolution of the coordinates 1493 * to be rendered. 1494 */ 1495 protected double getXRes() { 1496 return PS_XRES; 1497 } 1498 /** 1499 * Return the y resolution of the coordinates 1500 * to be rendered. 1501 */ 1502 protected double getYRes() { 1503 return PS_YRES; 1504 } 1505 1506 /** 1507 * For PostScript the origin is in the upper-left of the 1508 * paper not at the imageable area corner. 1509 */ 1510 protected double getPhysicalPrintableX(Paper p) { 1511 return 0; 1512 1513 } 1514 1515 /** 1516 * For PostScript the origin is in the upper-left of the 1517 * paper not at the imageable area corner. 1518 */ 1519 protected double getPhysicalPrintableY(Paper p) { 1520 return 0; 1521 } 1522 1523 protected double getPhysicalPrintableWidth(Paper p) { 1524 return p.getImageableWidth(); 1525 } 1526 1527 protected double getPhysicalPrintableHeight(Paper p) { 1528 return p.getImageableHeight(); 1529 } 1530 1531 protected double getPhysicalPageWidth(Paper p) { 1532 return p.getWidth(); 1533 } 1534 1535 protected double getPhysicalPageHeight(Paper p) { 1536 return p.getHeight(); 1537 } 1538 1539 /** 1540 * Returns how many times each page in the book 1541 * should be consecutively printed by PrintJob. 1542 * If the printer makes copies itself then this 1543 * method should return 1. 1544 */ 1545 protected int getNoncollatedCopies() { 1546 return 1; 1547 } 1548 1549 protected int getCollatedCopies() { 1550 return 1; 1551 } 1552 1553 private String[] printExecCmd(String printer, String options, 1554 boolean noJobSheet, 1555 String banner, int copies, String spoolFile) { 1556 int PRINTER = 0x1; 1557 int OPTIONS = 0x2; 1558 int BANNER = 0x4; 1559 int COPIES = 0x8; 1560 int NOSHEET = 0x10; 1561 int pFlags = 0; 1562 String execCmd[]; 1563 int ncomps = 2; // minimum number of print args 1564 int n = 0; 1565 1566 if (printer != null && !printer.equals("") && !printer.equals("lp")) { 1567 pFlags |= PRINTER; 1568 ncomps+=1; 1569 } 1570 if (options != null && !options.equals("")) { 1571 pFlags |= OPTIONS; 1572 ncomps+=1; 1573 } 1574 if (banner != null && !banner.equals("")) { 1575 pFlags |= BANNER; 1576 ncomps+=1; 1577 } 1578 if (copies > 1) { 1579 pFlags |= COPIES; 1580 ncomps+=1; 1581 } 1582 if (noJobSheet) { 1583 pFlags |= NOSHEET; 1584 ncomps+=1; 1585 } 1586 1587 String osname = System.getProperty("os.name"); 1588 if (osname.equals("Linux") || osname.contains("OS X")) { 1589 execCmd = new String[ncomps]; 1590 execCmd[n++] = "/usr/bin/lpr"; 1591 if ((pFlags & PRINTER) != 0) { 1592 execCmd[n++] = "-P" + printer; 1593 } 1594 if ((pFlags & BANNER) != 0) { 1595 execCmd[n++] = "-J" + banner; 1596 } 1597 if ((pFlags & COPIES) != 0) { 1598 execCmd[n++] = "-#" + copies; 1599 } 1600 if ((pFlags & NOSHEET) != 0) { 1601 execCmd[n++] = "-h"; 1602 } 1603 if ((pFlags & OPTIONS) != 0) { 1604 execCmd[n++] = new String(options); 1605 } 1606 } else { 1607 ncomps+=1; //add 1 arg for lp 1608 execCmd = new String[ncomps]; 1609 execCmd[n++] = "/usr/bin/lp"; 1610 execCmd[n++] = "-c"; // make a copy of the spool file 1611 if ((pFlags & PRINTER) != 0) { 1612 execCmd[n++] = "-d" + printer; 1613 } 1614 if ((pFlags & BANNER) != 0) { 1615 execCmd[n++] = "-t" + banner; 1616 } 1617 if ((pFlags & COPIES) != 0) { 1618 execCmd[n++] = "-n" + copies; 1619 } 1620 if ((pFlags & NOSHEET) != 0) { 1621 execCmd[n++] = "-o nobanner"; 1622 } 1623 if ((pFlags & OPTIONS) != 0) { 1624 execCmd[n++] = "-o" + options; 1625 } 1626 } 1627 execCmd[n++] = spoolFile; 1628 return execCmd; 1629 } 1630 1631 private static int swapBGRtoRGB(byte[] image, int index, byte[] dest) { 1632 int destIndex = 0; 1633 while(index < image.length-2 && destIndex < dest.length-2) { 1634 dest[destIndex++] = image[index+2]; 1635 dest[destIndex++] = image[index+1]; 1636 dest[destIndex++] = image[index+0]; 1637 index+=3; 1638 } 1639 return index; 1640 } 1641 1642 /* 1643 * Currently CharToByteConverter.getCharacterEncoding() return values are 1644 * not fixed yet. These are used as the part of the key of 1645 * psfont.properties. When those name are fixed this routine can 1646 * be erased. 1647 */ 1648 private String makeCharsetName(String name, char[] chs) { 1649 if (name.equals("Cp1252") || name.equals("ISO8859_1")) { 1650 return "latin1"; 1651 } else if (name.equals("UTF8")) { 1652 // same as latin 1 if all chars < 256 1653 for (int i=0; i < chs.length; i++) { 1654 if (chs[i] > 255) { 1655 return name.toLowerCase(); 1656 } 1657 } 1658 return "latin1"; 1659 } else if (name.startsWith("ISO8859")) { 1660 // same as latin 1 if all chars < 128 1661 for (int i=0; i < chs.length; i++) { 1662 if (chs[i] > 127) { 1663 return name.toLowerCase(); 1664 } 1665 } 1666 return "latin1"; 1667 } else { 1668 return name.toLowerCase(); 1669 } 1670 } 1671 1672 private void prepDrawing() { 1673 1674 /* Pop gstates until we can set the needed clip 1675 * and transform or until we are at the outer most 1676 * gstate. 1677 */ 1678 while (isOuterGState() == false 1679 && (getGState().canSetClip(mLastClip) == false 1680 || getGState().mTransform.equals(mLastTransform) == false)) { 1681 1682 1683 grestore(); 1684 } 1685 1686 /* Set the color. This can push the color to the 1687 * outer most gsave which is often a good thing. 1688 */ 1689 getGState().emitPSColor(mLastColor); 1690 1691 /* We do not want to change the outermost 1692 * transform or clip so if we are at the 1693 * outer clip the generate a gsave. 1694 */ 1695 if (isOuterGState()) { 1696 gsave(); 1697 getGState().emitTransform(mLastTransform); 1698 getGState().emitPSClip(mLastClip); 1699 } 1700 1701 /* Set the font if we have been asked to. It is 1702 * important that the font is set after the 1703 * transform in order to get the font size 1704 * correct. 1705 */ 1706 // if (g != null) { 1707 // getGState().emitPSFont(g, mLastFont); 1708 // } 1709 1710 } 1711 1712 /** 1713 * Return the GState that is currently on top 1714 * of the GState stack. There should always be 1715 * a GState on top of the stack. If there isn't 1716 * then this method will throw an IndexOutOfBounds 1717 * exception. 1718 */ 1719 private GState getGState() { 1720 int count = mGStateStack.size(); 1721 return mGStateStack.get(count - 1); 1722 } 1723 1724 /** 1725 * Emit a PostScript gsave command and add a 1726 * new GState on to our stack which represents 1727 * the printer's gstate stack. 1728 */ 1729 private void gsave() { 1730 GState oldGState = getGState(); 1731 mGStateStack.add(new GState(oldGState)); 1732 mPSStream.println(GSAVE_STR); 1733 } 1734 1735 /** 1736 * Emit a PostScript grestore command and remove 1737 * a GState from our stack which represents the 1738 * printer's gstate stack. 1739 */ 1740 private void grestore() { 1741 int count = mGStateStack.size(); 1742 mGStateStack.remove(count - 1); 1743 mPSStream.println(GRESTORE_STR); 1744 } 1745 1746 /** 1747 * Return true if the current GState is the 1748 * outermost GState and therefore should not 1749 * be restored. 1750 */ 1751 private boolean isOuterGState() { 1752 return mGStateStack.size() == 1; 1753 } 1754 1755 /** 1756 * A stack of GStates is maintained to model the printer's 1757 * gstate stack. Each GState holds information about 1758 * the current graphics attributes. 1759 */ 1760 private class GState{ 1761 Color mColor; 1762 Shape mClip; 1763 Font mFont; 1764 AffineTransform mTransform; 1765 1766 GState() { 1767 mColor = Color.black; 1768 mClip = null; 1769 mFont = null; 1770 mTransform = new AffineTransform(); 1771 } 1772 1773 GState(GState copyGState) { 1774 mColor = copyGState.mColor; 1775 mClip = copyGState.mClip; 1776 mFont = copyGState.mFont; 1777 mTransform = copyGState.mTransform; 1778 } 1779 1780 boolean canSetClip(Shape clip) { 1781 1782 return mClip == null || mClip.equals(clip); 1783 } 1784 1785 1786 void emitPSClip(Shape clip) { 1787 if (clip != null 1788 && (mClip == null || mClip.equals(clip) == false)) { 1789 String saveFillOp = mFillOpStr; 1790 String saveClipOp = mClipOpStr; 1791 convertToPSPath(clip.getPathIterator(new AffineTransform())); 1792 selectClipPath(); 1793 mClip = clip; 1794 /* The clip is a shape and has reset the winding rule state */ 1795 mClipOpStr = saveFillOp; 1796 mFillOpStr = saveFillOp; 1797 } 1798 } 1799 1800 void emitTransform(AffineTransform transform) { 1801 1802 if (transform != null && transform.equals(mTransform) == false) { 1803 double[] matrix = new double[6]; 1804 transform.getMatrix(matrix); 1805 mPSStream.println("[" + (float)matrix[0] 1806 + " " + (float)matrix[1] 1807 + " " + (float)matrix[2] 1808 + " " + (float)matrix[3] 1809 + " " + (float)matrix[4] 1810 + " " + (float)matrix[5] 1811 + "] concat"); 1812 1813 mTransform = transform; 1814 } 1815 } 1816 1817 void emitPSColor(Color color) { 1818 if (color != null && color.equals(mColor) == false) { 1819 float[] rgb = color.getRGBColorComponents(null); 1820 1821 /* If the color is a gray value then use 1822 * setgray. 1823 */ 1824 if (rgb[0] == rgb[1] && rgb[1] == rgb[2]) { 1825 mPSStream.println(rgb[0] + SETGRAY_STR); 1826 1827 /* It's not gray so use setrgbcolor. 1828 */ 1829 } else { 1830 mPSStream.println(rgb[0] + " " 1831 + rgb[1] + " " 1832 + rgb[2] + " " 1833 + SETRGBCOLOR_STR); 1834 } 1835 1836 mColor = color; 1837 1838 } 1839 } 1840 1841 void emitPSFont(int psFontIndex, float fontSize) { 1842 mPSStream.println(fontSize + " " + 1843 psFontIndex + " " + SetFontName); 1844 } 1845 } 1846 1847 /** 1848 * Given a Java2D <code>PathIterator</code> instance, 1849 * this method translates that into a PostScript path.. 1850 */ 1851 void convertToPSPath(PathIterator pathIter) { 1852 1853 float[] segment = new float[6]; 1854 int segmentType; 1855 1856 /* Map the PathIterator's fill rule into the PostScript 1857 * fill rule. 1858 */ 1859 int fillRule; 1860 if (pathIter.getWindingRule() == PathIterator.WIND_EVEN_ODD) { 1861 fillRule = FILL_EVEN_ODD; 1862 } else { 1863 fillRule = FILL_WINDING; 1864 } 1865 1866 beginPath(); 1867 1868 setFillMode(fillRule); 1869 1870 while (pathIter.isDone() == false) { 1871 segmentType = pathIter.currentSegment(segment); 1872 1873 switch (segmentType) { 1874 case PathIterator.SEG_MOVETO: 1875 moveTo(segment[0], segment[1]); 1876 break; 1877 1878 case PathIterator.SEG_LINETO: 1879 lineTo(segment[0], segment[1]); 1880 break; 1881 1882 /* Convert the quad path to a bezier. 1883 */ 1884 case PathIterator.SEG_QUADTO: 1885 float lastX = getPenX(); 1886 float lastY = getPenY(); 1887 float c1x = lastX + (segment[0] - lastX) * 2 / 3; 1888 float c1y = lastY + (segment[1] - lastY) * 2 / 3; 1889 float c2x = segment[2] - (segment[2] - segment[0]) * 2/ 3; 1890 float c2y = segment[3] - (segment[3] - segment[1]) * 2/ 3; 1891 bezierTo(c1x, c1y, 1892 c2x, c2y, 1893 segment[2], segment[3]); 1894 break; 1895 1896 case PathIterator.SEG_CUBICTO: 1897 bezierTo(segment[0], segment[1], 1898 segment[2], segment[3], 1899 segment[4], segment[5]); 1900 break; 1901 1902 case PathIterator.SEG_CLOSE: 1903 closeSubpath(); 1904 break; 1905 } 1906 1907 1908 pathIter.next(); 1909 } 1910 } 1911 1912 /* 1913 * Fill the path defined by <code>pathIter</code> 1914 * with the specified color. 1915 * The path is provided in current user space. 1916 */ 1917 protected void deviceFill(PathIterator pathIter, Color color, 1918 AffineTransform tx, Shape clip) { 1919 1920 setTransform(tx); 1921 setClip(clip); 1922 setColor(color); 1923 convertToPSPath(pathIter); 1924 /* Specify the path to fill as the clip, this ensures that only 1925 * pixels which are inside the path will be filled, which is 1926 * what the Java 2D APIs specify 1927 */ 1928 mPSStream.println(GSAVE_STR); 1929 selectClipPath(); 1930 fillPath(); 1931 mPSStream.println(GRESTORE_STR + " " + NEWPATH_STR); 1932 } 1933 1934 /* 1935 * Run length encode byte array in a form suitable for decoding 1936 * by the PS Level 2 filter RunLengthDecode. 1937 * Array data to encode is inArr. Encoded data is written to outArr 1938 * outArr must be long enough to hold the encoded data but this 1939 * can't be known ahead of time. 1940 * A safe assumption is to use double the length of the input array. 1941 * This is then copied into a new array of the correct length which 1942 * is returned. 1943 * Algorithm: 1944 * Encoding is a lead byte followed by data bytes. 1945 * Lead byte of 0->127 indicates leadByte + 1 distinct bytes follow 1946 * Lead byte of 129->255 indicates 257 - leadByte is the number of times 1947 * the following byte is repeated in the source. 1948 * 128 is a special lead byte indicating end of data (EOD) and is 1949 * written as the final byte of the returned encoded data. 1950 */ 1951 private byte[] rlEncode(byte[] inArr) { 1952 1953 int inIndex = 0; 1954 int outIndex = 0; 1955 int startIndex = 0; 1956 int runLen = 0; 1957 byte[] outArr = new byte[(inArr.length * 2) +2]; 1958 while (inIndex < inArr.length) { 1959 if (runLen == 0) { 1960 startIndex = inIndex++; 1961 runLen=1; 1962 } 1963 1964 while (runLen < 128 && inIndex < inArr.length && 1965 inArr[inIndex] == inArr[startIndex]) { 1966 runLen++; // count run of same value 1967 inIndex++; 1968 } 1969 1970 if (runLen > 1) { 1971 outArr[outIndex++] = (byte)(257 - runLen); 1972 outArr[outIndex++] = inArr[startIndex]; 1973 runLen = 0; 1974 continue; // back to top of while loop. 1975 } 1976 1977 // if reach here have a run of different values, or at the end. 1978 while (runLen < 128 && inIndex < inArr.length && 1979 inArr[inIndex] != inArr[inIndex-1]) { 1980 runLen++; // count run of different values 1981 inIndex++; 1982 } 1983 outArr[outIndex++] = (byte)(runLen - 1); 1984 for (int i = startIndex; i < startIndex+runLen; i++) { 1985 outArr[outIndex++] = inArr[i]; 1986 } 1987 runLen = 0; 1988 } 1989 outArr[outIndex++] = (byte)128; 1990 byte[] encodedData = new byte[outIndex]; 1991 System.arraycopy(outArr, 0, encodedData, 0, outIndex); 1992 1993 return encodedData; 1994 } 1995 1996 /* written acc. to Adobe Spec. "Filtered Files: ASCIIEncode Filter", 1997 * "PS Language Reference Manual, 2nd edition: Section 3.13" 1998 */ 1999 private byte[] ascii85Encode(byte[] inArr) { 2000 byte[] outArr = new byte[((inArr.length+4) * 5 / 4) + 2]; 2001 long p1 = 85; 2002 long p2 = p1*p1; 2003 long p3 = p1*p2; 2004 long p4 = p1*p3; 2005 byte pling = '!'; 2006 2007 int i = 0; 2008 int olen = 0; 2009 long val, rem; 2010 2011 while (i+3 < inArr.length) { 2012 val = ((long)((inArr[i++]&0xff))<<24) + 2013 ((long)((inArr[i++]&0xff))<<16) + 2014 ((long)((inArr[i++]&0xff))<< 8) + 2015 ((long)(inArr[i++]&0xff)); 2016 if (val == 0) { 2017 outArr[olen++] = 'z'; 2018 } else { 2019 rem = val; 2020 outArr[olen++] = (byte)(rem / p4 + pling); rem = rem % p4; 2021 outArr[olen++] = (byte)(rem / p3 + pling); rem = rem % p3; 2022 outArr[olen++] = (byte)(rem / p2 + pling); rem = rem % p2; 2023 outArr[olen++] = (byte)(rem / p1 + pling); rem = rem % p1; 2024 outArr[olen++] = (byte)(rem + pling); 2025 } 2026 } 2027 // input not a multiple of 4 bytes, write partial output. 2028 if (i < inArr.length) { 2029 int n = inArr.length - i; // n bytes remain to be written 2030 2031 val = 0; 2032 while (i < inArr.length) { 2033 val = (val << 8) + (inArr[i++]&0xff); 2034 } 2035 2036 int append = 4 - n; 2037 while (append-- > 0) { 2038 val = val << 8; 2039 } 2040 byte []c = new byte[5]; 2041 rem = val; 2042 c[0] = (byte)(rem / p4 + pling); rem = rem % p4; 2043 c[1] = (byte)(rem / p3 + pling); rem = rem % p3; 2044 c[2] = (byte)(rem / p2 + pling); rem = rem % p2; 2045 c[3] = (byte)(rem / p1 + pling); rem = rem % p1; 2046 c[4] = (byte)(rem + pling); 2047 2048 for (int b = 0; b < n+1 ; b++) { 2049 outArr[olen++] = c[b]; 2050 } 2051 } 2052 2053 // write EOD marker. 2054 outArr[olen++]='~'; outArr[olen++]='>'; 2055 2056 /* The original intention was to insert a newline after every 78 bytes. 2057 * This was mainly intended for legibility but I decided against this 2058 * partially because of the (small) amount of extra space, and 2059 * partially because for line breaks either would have to hardwire 2060 * ascii 10 (newline) or calculate space in bytes to allocate for 2061 * the platform's newline byte sequence. Also need to be careful 2062 * about where its inserted: 2063 * Ascii 85 decoder ignores white space except for one special case: 2064 * you must ensure you do not split the EOD marker across lines. 2065 */ 2066 byte[] retArr = new byte[olen]; 2067 System.arraycopy(outArr, 0, retArr, 0, olen); 2068 return retArr; 2069 2070 } 2071 2072 /** 2073 * PluginPrinter generates EPSF wrapped with a header and trailer 2074 * comment. This conforms to the new requirements of Mozilla 1.7 2075 * and FireFox 1.5 and later. Earlier versions of these browsers 2076 * did not support plugin printing in the general sense (not just Java). 2077 * A notable limitation of these browsers is that they handle plugins 2078 * which would span page boundaries by scaling plugin content to fit on a 2079 * single page. This means white space is left at the bottom of the 2080 * previous page and its impossible to print these cases as they appear on 2081 * the web page. This is contrast to how the same browsers behave on 2082 * Windows where it renders as on-screen. 2083 * Cases where the content fits on a single page do work fine, and they 2084 * are the majority of cases. 2085 * The scaling that the browser specifies to make the plugin content fit 2086 * when it is larger than a single page can hold is non-uniform. It 2087 * scales the axis in which the content is too large just enough to 2088 * ensure it fits. For content which is extremely long this could lead 2089 * to noticeable distortion. However that is probably rare enough that 2090 * its not worth compensating for that here, but we can revisit that if 2091 * needed, and compensate by making the scale for the other axis the 2092 * same. 2093 */ 2094 public static class PluginPrinter implements Printable { 2095 2096 private EPSPrinter epsPrinter; 2097 private Component applet; 2098 private PrintStream stream; 2099 private String epsTitle; 2100 private int bx, by, bw, bh; 2101 private int width, height; 2102 2103 /** 2104 * This is called from the Java Plug-in to print an Applet's 2105 * contents as EPS to a postscript stream provided by the browser. 2106 * @param applet the applet component to print. 2107 * @param stream the print stream provided by the plug-in 2108 * @param x the x location of the applet panel in the browser window 2109 * @param y the y location of the applet panel in the browser window 2110 * @param w the width of the applet panel in the browser window 2111 * @param h the width of the applet panel in the browser window 2112 */ 2113 public PluginPrinter(Component applet, 2114 PrintStream stream, 2115 int x, int y, int w, int h) { 2116 2117 this.applet = applet; 2118 this.epsTitle = "Java Plugin Applet"; 2119 this.stream = stream; 2120 bx = x; 2121 by = y; 2122 bw = w; 2123 bh = h; 2124 width = applet.size().width; 2125 height = applet.size().height; 2126 epsPrinter = new EPSPrinter(this, epsTitle, stream, 2127 0, 0, width, height); 2128 } 2129 2130 public void printPluginPSHeader() { 2131 stream.println("%%BeginDocument: JavaPluginApplet"); 2132 } 2133 2134 public void printPluginApplet() { 2135 try { 2136 epsPrinter.print(); 2137 } catch (PrinterException e) { 2138 } 2139 } 2140 2141 public void printPluginPSTrailer() { 2142 stream.println("%%EndDocument: JavaPluginApplet"); 2143 stream.flush(); 2144 } 2145 2146 public void printAll() { 2147 printPluginPSHeader(); 2148 printPluginApplet(); 2149 printPluginPSTrailer(); 2150 } 2151 2152 public int print(Graphics g, PageFormat pf, int pgIndex) { 2153 if (pgIndex > 0) { 2154 return Printable.NO_SUCH_PAGE; 2155 } else { 2156 // "aware" client code can detect that its been passed a 2157 // PrinterGraphics and could theoretically print 2158 // differently. I think this is more likely useful than 2159 // a problem. 2160 applet.printAll(g); 2161 return Printable.PAGE_EXISTS; 2162 } 2163 } 2164 2165 } 2166 2167 /* 2168 * This class can take an application-client supplied printable object 2169 * and send the result to a stream. 2170 * The application does not need to send any postscript to this stream 2171 * unless it needs to specify a translation etc. 2172 * It assumes that its importing application obeys all the conventions 2173 * for importation of EPS. See Appendix H - Encapsulated Postscript File 2174 * Format - of the Adobe Postscript Language Reference Manual, 2nd edition. 2175 * This class could be used as the basis for exposing the ability to 2176 * generate EPSF from 2D graphics as a StreamPrintService. 2177 * In that case a MediaPrintableArea attribute could be used to 2178 * communicate the bounding box. 2179 */ 2180 public static class EPSPrinter implements Pageable { 2181 2182 private PageFormat pf; 2183 private PSPrinterJob job; 2184 private int llx, lly, urx, ury; 2185 private Printable printable; 2186 private PrintStream stream; 2187 private String epsTitle; 2188 2189 public EPSPrinter(Printable printable, String title, 2190 PrintStream stream, 2191 int x, int y, int wid, int hgt) { 2192 2193 this.printable = printable; 2194 this.epsTitle = title; 2195 this.stream = stream; 2196 llx = x; 2197 lly = y; 2198 urx = llx+wid; 2199 ury = lly+hgt; 2200 // construct a PageFormat with zero margins representing the 2201 // exact bounds of the applet. ie construct a theoretical 2202 // paper which happens to exactly match applet panel size. 2203 Paper p = new Paper(); 2204 p.setSize((double)wid, (double)hgt); 2205 p.setImageableArea(0.0,0.0, (double)wid, (double)hgt); 2206 pf = new PageFormat(); 2207 pf.setPaper(p); 2208 } 2209 2210 public void print() throws PrinterException { 2211 stream.println("%!PS-Adobe-3.0 EPSF-3.0"); 2212 stream.println("%%BoundingBox: " + 2213 llx + " " + lly + " " + urx + " " + ury); 2214 stream.println("%%Title: " + epsTitle); 2215 stream.println("%%Creator: Java Printing"); 2216 stream.println("%%CreationDate: " + new java.util.Date()); 2217 stream.println("%%EndComments"); 2218 stream.println("/pluginSave save def"); 2219 stream.println("mark"); // for restoring stack state on return 2220 2221 job = new PSPrinterJob(); 2222 job.epsPrinter = this; // modifies the behaviour of PSPrinterJob 2223 job.mPSStream = stream; 2224 job.mDestType = RasterPrinterJob.STREAM; // prevents closure 2225 2226 job.startDoc(); 2227 try { 2228 job.printPage(this, 0); 2229 } catch (Throwable t) { 2230 if (t instanceof PrinterException) { 2231 throw (PrinterException)t; 2232 } else { 2233 throw new PrinterException(t.toString()); 2234 } 2235 } finally { 2236 stream.println("cleartomark"); // restore stack state 2237 stream.println("pluginSave restore"); 2238 job.endDoc(); 2239 } 2240 stream.flush(); 2241 } 2242 2243 public int getNumberOfPages() { 2244 return 1; 2245 } 2246 2247 public PageFormat getPageFormat(int pgIndex) { 2248 if (pgIndex > 0) { 2249 throw new IndexOutOfBoundsException("pgIndex"); 2250 } else { 2251 return pf; 2252 } 2253 } 2254 2255 public Printable getPrintable(int pgIndex) { 2256 if (pgIndex > 0) { 2257 throw new IndexOutOfBoundsException("pgIndex"); 2258 } else { 2259 return printable; 2260 } 2261 } 2262 2263 } 2264 }