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