1 /* 2 * Copyright (c) 1998, 2013, 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 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. java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Object run() { mFontProps = initProps(); String osName = System.getProperty(R); isMac = osName.startsWith(R); return null; } })348 java.security.AccessController.doPrivileged( 349 new java.security.PrivilegedAction() { 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 */ initProps()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 PSPrinterJob()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 */ printDialog()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 */ startDoc()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 = (Boolean)java.security.AccessController.doPrivileged( 626 new java.security.PrivilegedAction() { 627 public Object 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 { 664 PrinterException pex; 665 OutputStream result; 666 run()667 public Object 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 { 691 PrinterException pex; 692 handleProcessFailure(final Process failedProcess, final String[] execCmd, final int result)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 throw new IOException(sw.toString()); 712 } 713 } 714 } 715 run()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 */ abortDoc()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() { 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 */ endDoc()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 */ startPage(PageFormat pageFormat, Printable painter, int index, boolean paperChanged)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 = 829 (Boolean)java.security.AccessController.doPrivileged( 830 831 new java.security.PrivilegedAction() { 832 public Object run() { 833 try { 834 Class psClass = 835 Class.forName("sun.print.IPPPrintService"); 836 if (psClass.isInstance(pservice)) { 837 Method isPSMethod = 838 psClass.getMethod("isPostscript", 839 (Class[])null); 840 return (Boolean) 841 isPSMethod.invoke(pservice, 842 (Object[])null); 843 } 844 } catch (Throwable t) { 845 } 846 return Boolean.TRUE; 847 } 848 } 849 ); 850 851 if (isPS) { 852 mPSStream.print(" /DeferredMediaSelection true"); 853 } 854 mPSStream.println(" >> setpagedevice"); 855 } 856 mPSStream.println(PAGE_SAVE); 857 mPSStream.println(paperHeight + COORD_PREP); 858 } 859 860 /** 861 * The RastePrintJob super class calls this method 862 * at the end of each page. 863 */ endPage(PageFormat format, Printable painter, int index)864 protected void endPage(PageFormat format, Printable painter, 865 int index) 866 throws PrinterException 867 { 868 mPSStream.println(PAGE_RESTORE); 869 mPSStream.println(SHOWPAGE); 870 } 871 872 /** 873 * Convert the 24 bit BGR image buffer represented by 874 * <code>image</code> to PostScript. The image is drawn at 875 * <code>(destX, destY)</code> in device coordinates. 876 * The image is scaled into a square of size 877 * specified by <code>destWidth</code> and 878 * <code>destHeight</code>. The portion of the 879 * source image copied into that square is specified 880 * by <code>srcX</code>, <code>srcY</code>, 881 * <code>srcWidth</code>, and srcHeight. 882 */ drawImageBGR(byte[] bgrData, float destX, float destY, float destWidth, float destHeight, float srcX, float srcY, float srcWidth, float srcHeight, int srcBitMapWidth, int srcBitMapHeight)883 protected void drawImageBGR(byte[] bgrData, 884 float destX, float destY, 885 float destWidth, float destHeight, 886 float srcX, float srcY, 887 float srcWidth, float srcHeight, 888 int srcBitMapWidth, int srcBitMapHeight) { 889 890 /* We draw images at device resolution so we probably need 891 * to change the current PostScript transform. 892 */ 893 setTransform(new AffineTransform()); 894 prepDrawing(); 895 896 int intSrcWidth = (int) srcWidth; 897 int intSrcHeight = (int) srcHeight; 898 899 mPSStream.println(IMAGE_SAVE); 900 901 /* Create a PS string big enough to hold a row of pixels. 902 */ 903 int psBytesPerRow = 3 * (int) intSrcWidth; 904 while (psBytesPerRow > MAX_PSSTR) { 905 psBytesPerRow /= 2; 906 } 907 908 mPSStream.println(psBytesPerRow + IMAGE_STR); 909 910 /* Scale and translate the unit image. 911 */ 912 mPSStream.println("[" + destWidth + " 0 " 913 + "0 " + destHeight 914 + " " + destX + " " + destY 915 +"]concat"); 916 917 /* Color Image invocation. 918 */ 919 mPSStream.println(intSrcWidth + " " + intSrcHeight + " " + 8 + "[" 920 + intSrcWidth + " 0 " 921 + "0 " + intSrcHeight 922 + " 0 " + 0 + "]" 923 + "/imageSrc load false 3 colorimage"); 924 925 /* Image data. 926 */ 927 int index = 0; 928 byte[] rgbData = new byte[intSrcWidth * 3]; 929 930 try { 931 /* Skip the parts of the image that are not part 932 * of the source rectangle. 933 */ 934 index = (int) srcY * srcBitMapWidth; 935 936 for(int i = 0; i < intSrcHeight; i++) { 937 938 /* Skip the left part of the image that is not 939 * part of the source rectangle. 940 */ 941 index += (int) srcX; 942 943 index = swapBGRtoRGB(bgrData, index, rgbData); 944 byte[] encodedData = rlEncode(rgbData); 945 byte[] asciiData = ascii85Encode(encodedData); 946 mPSStream.write(asciiData); 947 mPSStream.println(""); 948 } 949 950 /* 951 * If there is an IOError we subvert it to a PrinterException. 952 * Fix: There has got to be a better way, maybe define 953 * a PrinterIOException and then throw that? 954 */ 955 } catch (IOException e) { 956 //throw new PrinterException(e.toString()); 957 } 958 959 mPSStream.println(IMAGE_RESTORE); 960 } 961 962 /** 963 * Prints the contents of the array of ints, 'data' 964 * to the current page. The band is placed at the 965 * location (x, y) in device coordinates on the 966 * page. The width and height of the band is 967 * specified by the caller. Currently the data 968 * is 24 bits per pixel in BGR format. 969 */ printBand(byte[] bgrData, int x, int y, int width, int height)970 protected void printBand(byte[] bgrData, int x, int y, 971 int width, int height) 972 throws PrinterException 973 { 974 975 mPSStream.println(IMAGE_SAVE); 976 977 /* Create a PS string big enough to hold a row of pixels. 978 */ 979 int psBytesPerRow = 3 * width; 980 while (psBytesPerRow > MAX_PSSTR) { 981 psBytesPerRow /= 2; 982 } 983 984 mPSStream.println(psBytesPerRow + IMAGE_STR); 985 986 /* Scale and translate the unit image. 987 */ 988 mPSStream.println("[" + width + " 0 " 989 + "0 " + height 990 + " " + x + " " + y 991 +"]concat"); 992 993 /* Color Image invocation. 994 */ 995 mPSStream.println(width + " " + height + " " + 8 + "[" 996 + width + " 0 " 997 + "0 " + -height 998 + " 0 " + height + "]" 999 + "/imageSrc load false 3 colorimage"); 1000 1001 /* Image data. 1002 */ 1003 int index = 0; 1004 byte[] rgbData = new byte[width*3]; 1005 1006 try { 1007 for(int i = 0; i < height; i++) { 1008 index = swapBGRtoRGB(bgrData, index, rgbData); 1009 byte[] encodedData = rlEncode(rgbData); 1010 byte[] asciiData = ascii85Encode(encodedData); 1011 mPSStream.write(asciiData); 1012 mPSStream.println(""); 1013 } 1014 1015 } catch (IOException e) { 1016 throw new PrinterIOException(e); 1017 } 1018 1019 mPSStream.println(IMAGE_RESTORE); 1020 } 1021 1022 /** 1023 * Examine the metrics captured by the 1024 * <code>PeekGraphics</code> instance and 1025 * if capable of directly converting this 1026 * print job to the printer's control language 1027 * or the native OS's graphics primitives, then 1028 * return a <code>PSPathGraphics</code> to perform 1029 * that conversion. If there is not an object 1030 * capable of the conversion then return 1031 * <code>null</code>. Returning <code>null</code> 1032 * causes the print job to be rasterized. 1033 */ 1034 createPathGraphics(PeekGraphics peekGraphics, PrinterJob printerJob, Printable painter, PageFormat pageFormat, int pageIndex)1035 protected Graphics2D createPathGraphics(PeekGraphics peekGraphics, 1036 PrinterJob printerJob, 1037 Printable painter, 1038 PageFormat pageFormat, 1039 int pageIndex) { 1040 1041 PSPathGraphics pathGraphics; 1042 PeekMetrics metrics = peekGraphics.getMetrics(); 1043 1044 /* If the application has drawn anything that 1045 * out PathGraphics class can not handle then 1046 * return a null PathGraphics. 1047 */ 1048 if (forcePDL == false && (forceRaster == true 1049 || metrics.hasNonSolidColors() 1050 || metrics.hasCompositing())) { 1051 1052 pathGraphics = null; 1053 } else { 1054 1055 BufferedImage bufferedImage = new BufferedImage(8, 8, 1056 BufferedImage.TYPE_INT_RGB); 1057 Graphics2D bufferedGraphics = bufferedImage.createGraphics(); 1058 boolean canRedraw = peekGraphics.getAWTDrawingOnly() == false; 1059 1060 pathGraphics = new PSPathGraphics(bufferedGraphics, printerJob, 1061 painter, pageFormat, pageIndex, 1062 canRedraw); 1063 } 1064 1065 return pathGraphics; 1066 } 1067 1068 /** 1069 * Intersect the gstate's current path with the 1070 * current clip and make the result the new clip. 1071 */ selectClipPath()1072 protected void selectClipPath() { 1073 1074 mPSStream.println(mClipOpStr); 1075 } 1076 setClip(Shape clip)1077 protected void setClip(Shape clip) { 1078 1079 mLastClip = clip; 1080 } 1081 setTransform(AffineTransform transform)1082 protected void setTransform(AffineTransform transform) { 1083 mLastTransform = transform; 1084 } 1085 1086 /** 1087 * Set the current PostScript font. 1088 * Taken from outFont in PSPrintStream. 1089 */ setFont(Font font)1090 protected boolean setFont(Font font) { 1091 mLastFont = font; 1092 return true; 1093 } 1094 1095 /** 1096 * Given an array of CharsetStrings that make up a run 1097 * of text, this routine converts each CharsetString to 1098 * an index into our PostScript font list. If one or more 1099 * CharsetStrings can not be represented by a PostScript 1100 * font, then this routine will return a null array. 1101 */ getPSFontIndexArray(Font font, CharsetString[] charSet)1102 private int[] getPSFontIndexArray(Font font, CharsetString[] charSet) { 1103 int[] psFont = null; 1104 1105 if (mFontProps != null) { 1106 psFont = new int[charSet.length]; 1107 } 1108 1109 for (int i = 0; i < charSet.length && psFont != null; i++){ 1110 1111 /* Get the encoding of the run of text. 1112 */ 1113 CharsetString cs = charSet[i]; 1114 1115 CharsetEncoder fontCS = cs.fontDescriptor.encoder; 1116 String charsetName = cs.fontDescriptor.getFontCharsetName(); 1117 /* 1118 * sun.awt.Symbol perhaps should return "symbol" for encoding. 1119 * Similarly X11Dingbats should return "dingbats" 1120 * Forced to check for win32 & x/unix names for these converters. 1121 */ 1122 1123 if ("Symbol".equals(charsetName)) { 1124 charsetName = "symbol"; 1125 } else if ("WingDings".equals(charsetName) || 1126 "X11Dingbats".equals(charsetName)) { 1127 charsetName = "dingbats"; 1128 } else { 1129 charsetName = makeCharsetName(charsetName, cs.charsetChars); 1130 } 1131 1132 int styleMask = font.getStyle() | 1133 FontUtilities.getFont2D(font).getStyle(); 1134 1135 String style = FontConfiguration.getStyleString(styleMask); 1136 1137 /* First we map the font name through the properties file. 1138 * This mapping provides alias names for fonts, for example, 1139 * "timesroman" is mapped to "serif". 1140 */ 1141 String fontName = font.getFamily().toLowerCase(Locale.ENGLISH); 1142 fontName = fontName.replace(' ', '_'); 1143 String name = mFontProps.getProperty(fontName, ""); 1144 1145 /* Now map the alias name, character set name, and style 1146 * to a PostScript name. 1147 */ 1148 String psName = 1149 mFontProps.getProperty(name + "." + charsetName + "." + style, 1150 null); 1151 1152 if (psName != null) { 1153 1154 /* Get the PostScript font index for the PostScript font. 1155 */ 1156 try { 1157 psFont[i] = 1158 Integer.parseInt(mFontProps.getProperty(psName)); 1159 1160 /* If there is no PostScript font for this font name, 1161 * then we want to termintate the loop and the method 1162 * indicating our failure. Setting the array to null 1163 * is used to indicate these failures. 1164 */ 1165 } catch(NumberFormatException e){ 1166 psFont = null; 1167 } 1168 1169 /* There was no PostScript name for the font, character set, 1170 * and style so give up. 1171 */ 1172 } else { 1173 psFont = null; 1174 } 1175 } 1176 1177 return psFont; 1178 } 1179 1180 escapeParens(String str)1181 private static String escapeParens(String str) { 1182 if (str.indexOf('(') == -1 && str.indexOf(')') == -1 ) { 1183 return str; 1184 } else { 1185 int count = 0; 1186 int pos = 0; 1187 while ((pos = str.indexOf('(', pos)) != -1) { 1188 count++; 1189 pos++; 1190 } 1191 pos = 0; 1192 while ((pos = str.indexOf(')', pos)) != -1) { 1193 count++; 1194 pos++; 1195 } 1196 char []inArr = str.toCharArray(); 1197 char []outArr = new char[inArr.length+count]; 1198 pos = 0; 1199 for (int i=0;i<inArr.length;i++) { 1200 if (inArr[i] == '(' || inArr[i] == ')') { 1201 outArr[pos++] = '\\'; 1202 } 1203 outArr[pos++] = inArr[i]; 1204 } 1205 return new String(outArr); 1206 1207 } 1208 } 1209 1210 /* return of 0 means unsupported. Other return indicates the number 1211 * of distinct PS fonts needed to draw this text. This saves us 1212 * doing this processing one extra time. 1213 */ platformFontCount(Font font, String str)1214 protected int platformFontCount(Font font, String str) { 1215 if (mFontProps == null) { 1216 return 0; 1217 } 1218 CharsetString[] acs = 1219 ((PlatformFont)(font.getPeer())).makeMultiCharsetString(str,false); 1220 if (acs == null) { 1221 /* AWT can't convert all chars so use 2D path */ 1222 return 0; 1223 } 1224 int[] psFonts = getPSFontIndexArray(font, acs); 1225 return (psFonts == null) ? 0 : psFonts.length; 1226 } 1227 textOut(Graphics g, String str, float x, float y, Font mLastFont, FontRenderContext frc, float width)1228 protected boolean textOut(Graphics g, String str, float x, float y, 1229 Font mLastFont, FontRenderContext frc, 1230 float width) { 1231 boolean didText = true; 1232 1233 if (mFontProps == null) { 1234 return false; 1235 } else { 1236 prepDrawing(); 1237 1238 /* On-screen drawString renders most control chars as the missing 1239 * glyph and have the non-zero advance of that glyph. 1240 * Exceptions are \t, \n and \r which are considered zero-width. 1241 * Postscript handles control chars mostly as a missing glyph. 1242 * But we use 'ashow' specifying a width for the string which 1243 * assumes zero-width for those three exceptions, and Postscript 1244 * tries to squeeze the extra char in, with the result that the 1245 * glyphs look compressed or even overlap. 1246 * So exclude those control chars from the string sent to PS. 1247 */ 1248 str = removeControlChars(str); 1249 if (str.length() == 0) { 1250 return true; 1251 } 1252 CharsetString[] acs = 1253 ((PlatformFont) 1254 (mLastFont.getPeer())).makeMultiCharsetString(str, false); 1255 if (acs == null) { 1256 /* AWT can't convert all chars so use 2D path */ 1257 return false; 1258 } 1259 /* Get an array of indices into our PostScript name 1260 * table. If all of the runs can not be converted 1261 * to PostScript fonts then null is returned and 1262 * we'll want to fall back to printing the text 1263 * as shapes. 1264 */ 1265 int[] psFonts = getPSFontIndexArray(mLastFont, acs); 1266 if (psFonts != null) { 1267 1268 for (int i = 0; i < acs.length; i++){ 1269 CharsetString cs = acs[i]; 1270 CharsetEncoder fontCS = cs.fontDescriptor.encoder; 1271 1272 StringBuffer nativeStr = new StringBuffer(); 1273 byte[] strSeg = new byte[cs.length * 2]; 1274 int len = 0; 1275 try { 1276 ByteBuffer bb = ByteBuffer.wrap(strSeg); 1277 fontCS.encode(CharBuffer.wrap(cs.charsetChars, 1278 cs.offset, 1279 cs.length), 1280 bb, true); 1281 bb.flip(); 1282 len = bb.limit(); 1283 } catch(IllegalStateException xx){ 1284 continue; 1285 } catch(CoderMalfunctionError xx){ 1286 continue; 1287 } 1288 /* The width to fit to may either be specified, 1289 * or calculated. Specifying by the caller is only 1290 * valid if the text does not need to be decomposed 1291 * into multiple calls. 1292 */ 1293 float desiredWidth; 1294 if (acs.length == 1 && width != 0f) { 1295 desiredWidth = width; 1296 } else { 1297 Rectangle2D r2d = 1298 mLastFont.getStringBounds(cs.charsetChars, 1299 cs.offset, 1300 cs.offset+cs.length, 1301 frc); 1302 desiredWidth = (float)r2d.getWidth(); 1303 } 1304 /* unprintable chars had width of 0, causing a PS error 1305 */ 1306 if (desiredWidth == 0) { 1307 return didText; 1308 } 1309 nativeStr.append('<'); 1310 for (int j = 0; j < len; j++){ 1311 byte b = strSeg[j]; 1312 // to avoid encoding conversion with println() 1313 String hexS = Integer.toHexString(b); 1314 int length = hexS.length(); 1315 if (length > 2) { 1316 hexS = hexS.substring(length - 2, length); 1317 } else if (length == 1) { 1318 hexS = "0" + hexS; 1319 } else if (length == 0) { 1320 hexS = "00"; 1321 } 1322 nativeStr.append(hexS); 1323 } 1324 nativeStr.append('>'); 1325 /* This comment costs too much in output file size */ 1326 // mPSStream.println("% Font[" + mLastFont.getName() + ", " + 1327 // FontConfiguration.getStyleString(mLastFont.getStyle()) + ", " 1328 // + mLastFont.getSize2D() + "]"); 1329 getGState().emitPSFont(psFonts[i], mLastFont.getSize2D()); 1330 1331 // out String 1332 mPSStream.println(nativeStr.toString() + " " + 1333 desiredWidth + " " + x + " " + y + " " + 1334 DrawStringName); 1335 x += desiredWidth; 1336 } 1337 } else { 1338 didText = false; 1339 } 1340 } 1341 1342 return didText; 1343 } 1344 /** 1345 * Set the current path rule to be either 1346 * <code>FILL_EVEN_ODD</code> (using the 1347 * even-odd file rule) or <code>FILL_WINDING</code> 1348 * (using the non-zero winding rule.) 1349 */ setFillMode(int fillRule)1350 protected void setFillMode(int fillRule) { 1351 1352 switch (fillRule) { 1353 1354 case FILL_EVEN_ODD: 1355 mFillOpStr = EVEN_ODD_FILL_STR; 1356 mClipOpStr = EVEN_ODD_CLIP_STR; 1357 break; 1358 1359 case FILL_WINDING: 1360 mFillOpStr = WINDING_FILL_STR; 1361 mClipOpStr = WINDING_CLIP_STR; 1362 break; 1363 1364 default: 1365 throw new IllegalArgumentException(); 1366 } 1367 1368 } 1369 1370 /** 1371 * Set the printer's current color to be that 1372 * defined by <code>color</code> 1373 */ setColor(Color color)1374 protected void setColor(Color color) { 1375 mLastColor = color; 1376 } 1377 1378 /** 1379 * Fill the current path using the current fill mode 1380 * and color. 1381 */ fillPath()1382 protected void fillPath() { 1383 1384 mPSStream.println(mFillOpStr); 1385 } 1386 1387 /** 1388 * Called to mark the start of a new path. 1389 */ beginPath()1390 protected void beginPath() { 1391 1392 prepDrawing(); 1393 mPSStream.println(NEWPATH_STR); 1394 1395 mPenX = 0; 1396 mPenY = 0; 1397 } 1398 1399 /** 1400 * Close the current subpath by appending a straight 1401 * line from the current point to the subpath's 1402 * starting point. 1403 */ closeSubpath()1404 protected void closeSubpath() { 1405 1406 mPSStream.println(CLOSEPATH_STR); 1407 1408 mPenX = mStartPathX; 1409 mPenY = mStartPathY; 1410 } 1411 1412 1413 /** 1414 * Generate PostScript to move the current pen 1415 * position to <code>(x, y)</code>. 1416 */ moveTo(float x, float y)1417 protected void moveTo(float x, float y) { 1418 1419 mPSStream.println(trunc(x) + " " + trunc(y) + MOVETO_STR); 1420 1421 /* moveto marks the start of a new subpath 1422 * and we need to remember that starting 1423 * position so that we know where the 1424 * pen returns to with a close path. 1425 */ 1426 mStartPathX = x; 1427 mStartPathY = y; 1428 1429 mPenX = x; 1430 mPenY = y; 1431 } 1432 /** 1433 * Generate PostScript to draw a line from the 1434 * current pen position to <code>(x, y)</code>. 1435 */ lineTo(float x, float y)1436 protected void lineTo(float x, float y) { 1437 1438 mPSStream.println(trunc(x) + " " + trunc(y) + LINETO_STR); 1439 1440 mPenX = x; 1441 mPenY = y; 1442 } 1443 1444 /** 1445 * Add to the current path a bezier curve formed 1446 * by the current pen position and the method parameters 1447 * which are two control points and an ending 1448 * point. 1449 */ bezierTo(float control1x, float control1y, float control2x, float control2y, float endX, float endY)1450 protected void bezierTo(float control1x, float control1y, 1451 float control2x, float control2y, 1452 float endX, float endY) { 1453 1454 // mPSStream.println(control1x + " " + control1y 1455 // + " " + control2x + " " + control2y 1456 // + " " + endX + " " + endY 1457 // + CURVETO_STR); 1458 mPSStream.println(trunc(control1x) + " " + trunc(control1y) 1459 + " " + trunc(control2x) + " " + trunc(control2y) 1460 + " " + trunc(endX) + " " + trunc(endY) 1461 + CURVETO_STR); 1462 1463 1464 mPenX = endX; 1465 mPenY = endY; 1466 } 1467 trunc(float f)1468 String trunc(float f) { 1469 float af = Math.abs(f); 1470 if (af >= 1f && af <=1000f) { 1471 f = Math.round(f*1000)/1000f; 1472 } 1473 return Float.toString(f); 1474 } 1475 1476 /** 1477 * Return the x coordinate of the pen in the 1478 * current path. 1479 */ getPenX()1480 protected float getPenX() { 1481 1482 return mPenX; 1483 } 1484 /** 1485 * Return the y coordinate of the pen in the 1486 * current path. 1487 */ getPenY()1488 protected float getPenY() { 1489 1490 return mPenY; 1491 } 1492 1493 /** 1494 * Return the x resolution of the coordinates 1495 * to be rendered. 1496 */ getXRes()1497 protected double getXRes() { 1498 return PS_XRES; 1499 } 1500 /** 1501 * Return the y resolution of the coordinates 1502 * to be rendered. 1503 */ getYRes()1504 protected double getYRes() { 1505 return PS_YRES; 1506 } 1507 1508 /** 1509 * For PostScript the origin is in the upper-left of the 1510 * paper not at the imageable area corner. 1511 */ getPhysicalPrintableX(Paper p)1512 protected double getPhysicalPrintableX(Paper p) { 1513 return 0; 1514 1515 } 1516 1517 /** 1518 * For PostScript the origin is in the upper-left of the 1519 * paper not at the imageable area corner. 1520 */ getPhysicalPrintableY(Paper p)1521 protected double getPhysicalPrintableY(Paper p) { 1522 return 0; 1523 } 1524 getPhysicalPrintableWidth(Paper p)1525 protected double getPhysicalPrintableWidth(Paper p) { 1526 return p.getImageableWidth(); 1527 } 1528 getPhysicalPrintableHeight(Paper p)1529 protected double getPhysicalPrintableHeight(Paper p) { 1530 return p.getImageableHeight(); 1531 } 1532 getPhysicalPageWidth(Paper p)1533 protected double getPhysicalPageWidth(Paper p) { 1534 return p.getWidth(); 1535 } 1536 getPhysicalPageHeight(Paper p)1537 protected double getPhysicalPageHeight(Paper p) { 1538 return p.getHeight(); 1539 } 1540 1541 /** 1542 * Returns how many times each page in the book 1543 * should be consecutively printed by PrintJob. 1544 * If the printer makes copies itself then this 1545 * method should return 1. 1546 */ getNoncollatedCopies()1547 protected int getNoncollatedCopies() { 1548 return 1; 1549 } 1550 getCollatedCopies()1551 protected int getCollatedCopies() { 1552 return 1; 1553 } 1554 printExecCmd(String printer, String options, boolean noJobSheet, String banner, int copies, String spoolFile)1555 private String[] printExecCmd(String printer, String options, 1556 boolean noJobSheet, 1557 String banner, int copies, String spoolFile) { 1558 int PRINTER = 0x1; 1559 int OPTIONS = 0x2; 1560 int BANNER = 0x4; 1561 int COPIES = 0x8; 1562 int NOSHEET = 0x10; 1563 int pFlags = 0; 1564 String execCmd[]; 1565 int ncomps = 2; // minimum number of print args 1566 int n = 0; 1567 1568 if (printer != null && !printer.equals("") && !printer.equals("lp")) { 1569 pFlags |= PRINTER; 1570 ncomps+=1; 1571 } 1572 if (options != null && !options.equals("")) { 1573 pFlags |= OPTIONS; 1574 ncomps+=1; 1575 } 1576 if (banner != null && !banner.equals("")) { 1577 pFlags |= BANNER; 1578 ncomps+=1; 1579 } 1580 if (copies > 1) { 1581 pFlags |= COPIES; 1582 ncomps+=1; 1583 } 1584 if (noJobSheet) { 1585 pFlags |= NOSHEET; 1586 ncomps+=1; 1587 } 1588 1589 String osname = System.getProperty("os.name"); 1590 if (osname.equals("Linux") || osname.endsWith("BSD") || osname.contains("OS X")) { 1591 String lprPath = "/usr/bin/lpr"; 1592 if (osname.equals("FreeBSD")) { 1593 final PrintService pservice = getPrintService(); 1594 Boolean isIPPPrinter = 1595 (Boolean)java.security.AccessController.doPrivileged( 1596 new java.security.PrivilegedAction() { 1597 public Object run() { 1598 try { 1599 Class psClass = 1600 Class.forName("sun.print.IPPPrintService"); 1601 if (psClass.isInstance(pservice)) { 1602 return Boolean.TRUE; 1603 } 1604 } catch (Throwable t) { 1605 } 1606 return Boolean.FALSE; 1607 } 1608 }); 1609 if (isIPPPrinter) { 1610 lprPath = "/usr/local/bin/lpr"; 1611 } 1612 } 1613 execCmd = new String[ncomps]; 1614 execCmd[n++] = lprPath; 1615 if ((pFlags & PRINTER) != 0) { 1616 execCmd[n++] = "-P" + printer; 1617 } 1618 if ((pFlags & BANNER) != 0) { 1619 execCmd[n++] = "-J" + banner; 1620 } 1621 if ((pFlags & COPIES) != 0) { 1622 execCmd[n++] = "-#" + copies; 1623 } 1624 if ((pFlags & NOSHEET) != 0) { 1625 execCmd[n++] = "-h"; 1626 } 1627 if ((pFlags & OPTIONS) != 0) { 1628 execCmd[n++] = new String(options); 1629 } 1630 } else { 1631 ncomps+=1; //add 1 arg for lp 1632 execCmd = new String[ncomps]; 1633 execCmd[n++] = "/usr/bin/lp"; 1634 execCmd[n++] = "-c"; // make a copy of the spool file 1635 if ((pFlags & PRINTER) != 0) { 1636 execCmd[n++] = "-d" + printer; 1637 } 1638 if ((pFlags & BANNER) != 0) { 1639 execCmd[n++] = "-t" + banner; 1640 } 1641 if ((pFlags & COPIES) != 0) { 1642 execCmd[n++] = "-n" + copies; 1643 } 1644 if ((pFlags & NOSHEET) != 0) { 1645 execCmd[n++] = "-o nobanner"; 1646 } 1647 if ((pFlags & OPTIONS) != 0) { 1648 execCmd[n++] = "-o" + options; 1649 } 1650 } 1651 execCmd[n++] = spoolFile; 1652 return execCmd; 1653 } 1654 swapBGRtoRGB(byte[] image, int index, byte[] dest)1655 private static int swapBGRtoRGB(byte[] image, int index, byte[] dest) { 1656 int destIndex = 0; 1657 while(index < image.length-2 && destIndex < dest.length-2) { 1658 dest[destIndex++] = image[index+2]; 1659 dest[destIndex++] = image[index+1]; 1660 dest[destIndex++] = image[index+0]; 1661 index+=3; 1662 } 1663 return index; 1664 } 1665 1666 /* 1667 * Currently CharToByteConverter.getCharacterEncoding() return values are 1668 * not fixed yet. These are used as the part of the key of 1669 * psfont.properties. When those name are fixed this routine can 1670 * be erased. 1671 */ makeCharsetName(String name, char[] chs)1672 private String makeCharsetName(String name, char[] chs) { 1673 if (name.equals("Cp1252") || name.equals("ISO8859_1")) { 1674 return "latin1"; 1675 } else if (name.equals("UTF8")) { 1676 // same as latin 1 if all chars < 256 1677 for (int i=0; i < chs.length; i++) { 1678 if (chs[i] > 255) { 1679 return name.toLowerCase(); 1680 } 1681 } 1682 return "latin1"; 1683 } else if (name.startsWith("ISO8859")) { 1684 // same as latin 1 if all chars < 128 1685 for (int i=0; i < chs.length; i++) { 1686 if (chs[i] > 127) { 1687 return name.toLowerCase(); 1688 } 1689 } 1690 return "latin1"; 1691 } else { 1692 return name.toLowerCase(); 1693 } 1694 } 1695 prepDrawing()1696 private void prepDrawing() { 1697 1698 /* Pop gstates until we can set the needed clip 1699 * and transform or until we are at the outer most 1700 * gstate. 1701 */ 1702 while (isOuterGState() == false 1703 && (getGState().canSetClip(mLastClip) == false 1704 || getGState().mTransform.equals(mLastTransform) == false)) { 1705 1706 1707 grestore(); 1708 } 1709 1710 /* Set the color. This can push the color to the 1711 * outer most gsave which is often a good thing. 1712 */ 1713 getGState().emitPSColor(mLastColor); 1714 1715 /* We do not want to change the outermost 1716 * transform or clip so if we are at the 1717 * outer clip the generate a gsave. 1718 */ 1719 if (isOuterGState()) { 1720 gsave(); 1721 getGState().emitTransform(mLastTransform); 1722 getGState().emitPSClip(mLastClip); 1723 } 1724 1725 /* Set the font if we have been asked to. It is 1726 * important that the font is set after the 1727 * transform in order to get the font size 1728 * correct. 1729 */ 1730 // if (g != null) { 1731 // getGState().emitPSFont(g, mLastFont); 1732 // } 1733 1734 } 1735 1736 /** 1737 * Return the GState that is currently on top 1738 * of the GState stack. There should always be 1739 * a GState on top of the stack. If there isn't 1740 * then this method will throw an IndexOutOfBounds 1741 * exception. 1742 */ getGState()1743 private GState getGState() { 1744 int count = mGStateStack.size(); 1745 return (GState) mGStateStack.get(count - 1); 1746 } 1747 1748 /** 1749 * Emit a PostScript gsave command and add a 1750 * new GState on to our stack which represents 1751 * the printer's gstate stack. 1752 */ gsave()1753 private void gsave() { 1754 GState oldGState = getGState(); 1755 mGStateStack.add(new GState(oldGState)); 1756 mPSStream.println(GSAVE_STR); 1757 } 1758 1759 /** 1760 * Emit a PostScript grestore command and remove 1761 * a GState from our stack which represents the 1762 * printer's gstate stack. 1763 */ grestore()1764 private void grestore() { 1765 int count = mGStateStack.size(); 1766 mGStateStack.remove(count - 1); 1767 mPSStream.println(GRESTORE_STR); 1768 } 1769 1770 /** 1771 * Return true if the current GState is the 1772 * outermost GState and therefore should not 1773 * be restored. 1774 */ isOuterGState()1775 private boolean isOuterGState() { 1776 return mGStateStack.size() == 1; 1777 } 1778 1779 /** 1780 * A stack of GStates is maintained to model the printer's 1781 * gstate stack. Each GState holds information about 1782 * the current graphics attributes. 1783 */ 1784 private class GState{ 1785 Color mColor; 1786 Shape mClip; 1787 Font mFont; 1788 AffineTransform mTransform; 1789 GState()1790 GState() { 1791 mColor = Color.black; 1792 mClip = null; 1793 mFont = null; 1794 mTransform = new AffineTransform(); 1795 } 1796 GState(GState copyGState)1797 GState(GState copyGState) { 1798 mColor = copyGState.mColor; 1799 mClip = copyGState.mClip; 1800 mFont = copyGState.mFont; 1801 mTransform = copyGState.mTransform; 1802 } 1803 canSetClip(Shape clip)1804 boolean canSetClip(Shape clip) { 1805 1806 return mClip == null || mClip.equals(clip); 1807 } 1808 1809 emitPSClip(Shape clip)1810 void emitPSClip(Shape clip) { 1811 if (clip != null 1812 && (mClip == null || mClip.equals(clip) == false)) { 1813 String saveFillOp = mFillOpStr; 1814 String saveClipOp = mClipOpStr; 1815 convertToPSPath(clip.getPathIterator(new AffineTransform())); 1816 selectClipPath(); 1817 mClip = clip; 1818 /* The clip is a shape and has reset the winding rule state */ 1819 mClipOpStr = saveFillOp; 1820 mFillOpStr = saveFillOp; 1821 } 1822 } 1823 emitTransform(AffineTransform transform)1824 void emitTransform(AffineTransform transform) { 1825 1826 if (transform != null && transform.equals(mTransform) == false) { 1827 double[] matrix = new double[6]; 1828 transform.getMatrix(matrix); 1829 mPSStream.println("[" + (float)matrix[0] 1830 + " " + (float)matrix[1] 1831 + " " + (float)matrix[2] 1832 + " " + (float)matrix[3] 1833 + " " + (float)matrix[4] 1834 + " " + (float)matrix[5] 1835 + "] concat"); 1836 1837 mTransform = transform; 1838 } 1839 } 1840 emitPSColor(Color color)1841 void emitPSColor(Color color) { 1842 if (color != null && color.equals(mColor) == false) { 1843 float[] rgb = color.getRGBColorComponents(null); 1844 1845 /* If the color is a gray value then use 1846 * setgray. 1847 */ 1848 if (rgb[0] == rgb[1] && rgb[1] == rgb[2]) { 1849 mPSStream.println(rgb[0] + SETGRAY_STR); 1850 1851 /* It's not gray so use setrgbcolor. 1852 */ 1853 } else { 1854 mPSStream.println(rgb[0] + " " 1855 + rgb[1] + " " 1856 + rgb[2] + " " 1857 + SETRGBCOLOR_STR); 1858 } 1859 1860 mColor = color; 1861 1862 } 1863 } 1864 emitPSFont(int psFontIndex, float fontSize)1865 void emitPSFont(int psFontIndex, float fontSize) { 1866 mPSStream.println(fontSize + " " + 1867 psFontIndex + " " + SetFontName); 1868 } 1869 } 1870 1871 /** 1872 * Given a Java2D <code>PathIterator</code> instance, 1873 * this method translates that into a PostScript path.. 1874 */ convertToPSPath(PathIterator pathIter)1875 void convertToPSPath(PathIterator pathIter) { 1876 1877 float[] segment = new float[6]; 1878 int segmentType; 1879 1880 /* Map the PathIterator's fill rule into the PostScript 1881 * fill rule. 1882 */ 1883 int fillRule; 1884 if (pathIter.getWindingRule() == PathIterator.WIND_EVEN_ODD) { 1885 fillRule = FILL_EVEN_ODD; 1886 } else { 1887 fillRule = FILL_WINDING; 1888 } 1889 1890 beginPath(); 1891 1892 setFillMode(fillRule); 1893 1894 while (pathIter.isDone() == false) { 1895 segmentType = pathIter.currentSegment(segment); 1896 1897 switch (segmentType) { 1898 case PathIterator.SEG_MOVETO: 1899 moveTo(segment[0], segment[1]); 1900 break; 1901 1902 case PathIterator.SEG_LINETO: 1903 lineTo(segment[0], segment[1]); 1904 break; 1905 1906 /* Convert the quad path to a bezier. 1907 */ 1908 case PathIterator.SEG_QUADTO: 1909 float lastX = getPenX(); 1910 float lastY = getPenY(); 1911 float c1x = lastX + (segment[0] - lastX) * 2 / 3; 1912 float c1y = lastY + (segment[1] - lastY) * 2 / 3; 1913 float c2x = segment[2] - (segment[2] - segment[0]) * 2/ 3; 1914 float c2y = segment[3] - (segment[3] - segment[1]) * 2/ 3; 1915 bezierTo(c1x, c1y, 1916 c2x, c2y, 1917 segment[2], segment[3]); 1918 break; 1919 1920 case PathIterator.SEG_CUBICTO: 1921 bezierTo(segment[0], segment[1], 1922 segment[2], segment[3], 1923 segment[4], segment[5]); 1924 break; 1925 1926 case PathIterator.SEG_CLOSE: 1927 closeSubpath(); 1928 break; 1929 } 1930 1931 1932 pathIter.next(); 1933 } 1934 } 1935 1936 /* 1937 * Fill the path defined by <code>pathIter</code> 1938 * with the specified color. 1939 * The path is provided in current user space. 1940 */ deviceFill(PathIterator pathIter, Color color, AffineTransform tx, Shape clip)1941 protected void deviceFill(PathIterator pathIter, Color color, 1942 AffineTransform tx, Shape clip) { 1943 1944 setTransform(tx); 1945 setClip(clip); 1946 setColor(color); 1947 convertToPSPath(pathIter); 1948 /* Specify the path to fill as the clip, this ensures that only 1949 * pixels which are inside the path will be filled, which is 1950 * what the Java 2D APIs specify 1951 */ 1952 mPSStream.println(GSAVE_STR); 1953 selectClipPath(); 1954 fillPath(); 1955 mPSStream.println(GRESTORE_STR + " " + NEWPATH_STR); 1956 } 1957 1958 /* 1959 * Run length encode byte array in a form suitable for decoding 1960 * by the PS Level 2 filter RunLengthDecode. 1961 * Array data to encode is inArr. Encoded data is written to outArr 1962 * outArr must be long enough to hold the encoded data but this 1963 * can't be known ahead of time. 1964 * A safe assumption is to use double the length of the input array. 1965 * This is then copied into a new array of the correct length which 1966 * is returned. 1967 * Algorithm: 1968 * Encoding is a lead byte followed by data bytes. 1969 * Lead byte of 0->127 indicates leadByte + 1 distinct bytes follow 1970 * Lead byte of 129->255 indicates 257 - leadByte is the number of times 1971 * the following byte is repeated in the source. 1972 * 128 is a special lead byte indicating end of data (EOD) and is 1973 * written as the final byte of the returned encoded data. 1974 */ rlEncode(byte[] inArr)1975 private byte[] rlEncode(byte[] inArr) { 1976 1977 int inIndex = 0; 1978 int outIndex = 0; 1979 int startIndex = 0; 1980 int runLen = 0; 1981 byte[] outArr = new byte[(inArr.length * 2) +2]; 1982 while (inIndex < inArr.length) { 1983 if (runLen == 0) { 1984 startIndex = inIndex++; 1985 runLen=1; 1986 } 1987 1988 while (runLen < 128 && inIndex < inArr.length && 1989 inArr[inIndex] == inArr[startIndex]) { 1990 runLen++; // count run of same value 1991 inIndex++; 1992 } 1993 1994 if (runLen > 1) { 1995 outArr[outIndex++] = (byte)(257 - runLen); 1996 outArr[outIndex++] = inArr[startIndex]; 1997 runLen = 0; 1998 continue; // back to top of while loop. 1999 } 2000 2001 // if reach here have a run of different values, or at the end. 2002 while (runLen < 128 && inIndex < inArr.length && 2003 inArr[inIndex] != inArr[inIndex-1]) { 2004 runLen++; // count run of different values 2005 inIndex++; 2006 } 2007 outArr[outIndex++] = (byte)(runLen - 1); 2008 for (int i = startIndex; i < startIndex+runLen; i++) { 2009 outArr[outIndex++] = inArr[i]; 2010 } 2011 runLen = 0; 2012 } 2013 outArr[outIndex++] = (byte)128; 2014 byte[] encodedData = new byte[outIndex]; 2015 System.arraycopy(outArr, 0, encodedData, 0, outIndex); 2016 2017 return encodedData; 2018 } 2019 2020 /* written acc. to Adobe Spec. "Filtered Files: ASCIIEncode Filter", 2021 * "PS Language Reference Manual, 2nd edition: Section 3.13" 2022 */ ascii85Encode(byte[] inArr)2023 private byte[] ascii85Encode(byte[] inArr) { 2024 byte[] outArr = new byte[((inArr.length+4) * 5 / 4) + 2]; 2025 long p1 = 85; 2026 long p2 = p1*p1; 2027 long p3 = p1*p2; 2028 long p4 = p1*p3; 2029 byte pling = '!'; 2030 2031 int i = 0; 2032 int olen = 0; 2033 long val, rem; 2034 2035 while (i+3 < inArr.length) { 2036 val = ((long)((inArr[i++]&0xff))<<24) + 2037 ((long)((inArr[i++]&0xff))<<16) + 2038 ((long)((inArr[i++]&0xff))<< 8) + 2039 ((long)(inArr[i++]&0xff)); 2040 if (val == 0) { 2041 outArr[olen++] = 'z'; 2042 } else { 2043 rem = val; 2044 outArr[olen++] = (byte)(rem / p4 + pling); rem = rem % p4; 2045 outArr[olen++] = (byte)(rem / p3 + pling); rem = rem % p3; 2046 outArr[olen++] = (byte)(rem / p2 + pling); rem = rem % p2; 2047 outArr[olen++] = (byte)(rem / p1 + pling); rem = rem % p1; 2048 outArr[olen++] = (byte)(rem + pling); 2049 } 2050 } 2051 // input not a multiple of 4 bytes, write partial output. 2052 if (i < inArr.length) { 2053 int n = inArr.length - i; // n bytes remain to be written 2054 2055 val = 0; 2056 while (i < inArr.length) { 2057 val = (val << 8) + (inArr[i++]&0xff); 2058 } 2059 2060 int append = 4 - n; 2061 while (append-- > 0) { 2062 val = val << 8; 2063 } 2064 byte []c = new byte[5]; 2065 rem = val; 2066 c[0] = (byte)(rem / p4 + pling); rem = rem % p4; 2067 c[1] = (byte)(rem / p3 + pling); rem = rem % p3; 2068 c[2] = (byte)(rem / p2 + pling); rem = rem % p2; 2069 c[3] = (byte)(rem / p1 + pling); rem = rem % p1; 2070 c[4] = (byte)(rem + pling); 2071 2072 for (int b = 0; b < n+1 ; b++) { 2073 outArr[olen++] = c[b]; 2074 } 2075 } 2076 2077 // write EOD marker. 2078 outArr[olen++]='~'; outArr[olen++]='>'; 2079 2080 /* The original intention was to insert a newline after every 78 bytes. 2081 * This was mainly intended for legibility but I decided against this 2082 * partially because of the (small) amount of extra space, and 2083 * partially because for line breaks either would have to hardwire 2084 * ascii 10 (newline) or calculate space in bytes to allocate for 2085 * the platform's newline byte sequence. Also need to be careful 2086 * about where its inserted: 2087 * Ascii 85 decoder ignores white space except for one special case: 2088 * you must ensure you do not split the EOD marker across lines. 2089 */ 2090 byte[] retArr = new byte[olen]; 2091 System.arraycopy(outArr, 0, retArr, 0, olen); 2092 return retArr; 2093 2094 } 2095 2096 /** 2097 * PluginPrinter generates EPSF wrapped with a header and trailer 2098 * comment. This conforms to the new requirements of Mozilla 1.7 2099 * and FireFox 1.5 and later. Earlier versions of these browsers 2100 * did not support plugin printing in the general sense (not just Java). 2101 * A notable limitation of these browsers is that they handle plugins 2102 * which would span page boundaries by scaling plugin content to fit on a 2103 * single page. This means white space is left at the bottom of the 2104 * previous page and its impossible to print these cases as they appear on 2105 * the web page. This is contrast to how the same browsers behave on 2106 * Windows where it renders as on-screen. 2107 * Cases where the content fits on a single page do work fine, and they 2108 * are the majority of cases. 2109 * The scaling that the browser specifies to make the plugin content fit 2110 * when it is larger than a single page can hold is non-uniform. It 2111 * scales the axis in which the content is too large just enough to 2112 * ensure it fits. For content which is extremely long this could lead 2113 * to noticeable distortion. However that is probably rare enough that 2114 * its not worth compensating for that here, but we can revisit that if 2115 * needed, and compensate by making the scale for the other axis the 2116 * same. 2117 */ 2118 public static class PluginPrinter implements Printable { 2119 2120 private EPSPrinter epsPrinter; 2121 private Component applet; 2122 private PrintStream stream; 2123 private String epsTitle; 2124 private int bx, by, bw, bh; 2125 private int width, height; 2126 2127 /** 2128 * This is called from the Java Plug-in to print an Applet's 2129 * contents as EPS to a postscript stream provided by the browser. 2130 * @param applet the applet component to print. 2131 * @param stream the print stream provided by the plug-in 2132 * @param x the x location of the applet panel in the browser window 2133 * @param y the y location of the applet panel in the browser window 2134 * @param w the width of the applet panel in the browser window 2135 * @param h the width of the applet panel in the browser window 2136 */ PluginPrinter(Component applet, PrintStream stream, int x, int y, int w, int h)2137 public PluginPrinter(Component applet, 2138 PrintStream stream, 2139 int x, int y, int w, int h) { 2140 2141 this.applet = applet; 2142 this.epsTitle = "Java Plugin Applet"; 2143 this.stream = stream; 2144 bx = x; 2145 by = y; 2146 bw = w; 2147 bh = h; 2148 width = applet.size().width; 2149 height = applet.size().height; 2150 epsPrinter = new EPSPrinter(this, epsTitle, stream, 2151 0, 0, width, height); 2152 } 2153 printPluginPSHeader()2154 public void printPluginPSHeader() { 2155 stream.println("%%BeginDocument: JavaPluginApplet"); 2156 } 2157 printPluginApplet()2158 public void printPluginApplet() { 2159 try { 2160 epsPrinter.print(); 2161 } catch (PrinterException e) { 2162 } 2163 } 2164 printPluginPSTrailer()2165 public void printPluginPSTrailer() { 2166 stream.println("%%EndDocument: JavaPluginApplet"); 2167 stream.flush(); 2168 } 2169 printAll()2170 public void printAll() { 2171 printPluginPSHeader(); 2172 printPluginApplet(); 2173 printPluginPSTrailer(); 2174 } 2175 print(Graphics g, PageFormat pf, int pgIndex)2176 public int print(Graphics g, PageFormat pf, int pgIndex) { 2177 if (pgIndex > 0) { 2178 return Printable.NO_SUCH_PAGE; 2179 } else { 2180 // "aware" client code can detect that its been passed a 2181 // PrinterGraphics and could theoretically print 2182 // differently. I think this is more likely useful than 2183 // a problem. 2184 applet.printAll(g); 2185 return Printable.PAGE_EXISTS; 2186 } 2187 } 2188 2189 } 2190 2191 /* 2192 * This class can take an application-client supplied printable object 2193 * and send the result to a stream. 2194 * The application does not need to send any postscript to this stream 2195 * unless it needs to specify a translation etc. 2196 * It assumes that its importing application obeys all the conventions 2197 * for importation of EPS. See Appendix H - Encapsulated Postscript File 2198 * Format - of the Adobe Postscript Language Reference Manual, 2nd edition. 2199 * This class could be used as the basis for exposing the ability to 2200 * generate EPSF from 2D graphics as a StreamPrintService. 2201 * In that case a MediaPrintableArea attribute could be used to 2202 * communicate the bounding box. 2203 */ 2204 public static class EPSPrinter implements Pageable { 2205 2206 private PageFormat pf; 2207 private PSPrinterJob job; 2208 private int llx, lly, urx, ury; 2209 private Printable printable; 2210 private PrintStream stream; 2211 private String epsTitle; 2212 EPSPrinter(Printable printable, String title, PrintStream stream, int x, int y, int wid, int hgt)2213 public EPSPrinter(Printable printable, String title, 2214 PrintStream stream, 2215 int x, int y, int wid, int hgt) { 2216 2217 this.printable = printable; 2218 this.epsTitle = title; 2219 this.stream = stream; 2220 llx = x; 2221 lly = y; 2222 urx = llx+wid; 2223 ury = lly+hgt; 2224 // construct a PageFormat with zero margins representing the 2225 // exact bounds of the applet. ie construct a theoretical 2226 // paper which happens to exactly match applet panel size. 2227 Paper p = new Paper(); 2228 p.setSize((double)wid, (double)hgt); 2229 p.setImageableArea(0.0,0.0, (double)wid, (double)hgt); 2230 pf = new PageFormat(); 2231 pf.setPaper(p); 2232 } 2233 print()2234 public void print() throws PrinterException { 2235 stream.println("%!PS-Adobe-3.0 EPSF-3.0"); 2236 stream.println("%%BoundingBox: " + 2237 llx + " " + lly + " " + urx + " " + ury); 2238 stream.println("%%Title: " + epsTitle); 2239 stream.println("%%Creator: Java Printing"); 2240 stream.println("%%CreationDate: " + new java.util.Date()); 2241 stream.println("%%EndComments"); 2242 stream.println("/pluginSave save def"); 2243 stream.println("mark"); // for restoring stack state on return 2244 2245 job = new PSPrinterJob(); 2246 job.epsPrinter = this; // modifies the behaviour of PSPrinterJob 2247 job.mPSStream = stream; 2248 job.mDestType = RasterPrinterJob.STREAM; // prevents closure 2249 2250 job.startDoc(); 2251 try { 2252 job.printPage(this, 0); 2253 } catch (Throwable t) { 2254 if (t instanceof PrinterException) { 2255 throw (PrinterException)t; 2256 } else { 2257 throw new PrinterException(t.toString()); 2258 } 2259 } finally { 2260 stream.println("cleartomark"); // restore stack state 2261 stream.println("pluginSave restore"); 2262 job.endDoc(); 2263 } 2264 stream.flush(); 2265 } 2266 getNumberOfPages()2267 public int getNumberOfPages() { 2268 return 1; 2269 } 2270 getPageFormat(int pgIndex)2271 public PageFormat getPageFormat(int pgIndex) { 2272 if (pgIndex > 0) { 2273 throw new IndexOutOfBoundsException("pgIndex"); 2274 } else { 2275 return pf; 2276 } 2277 } 2278 getPrintable(int pgIndex)2279 public Printable getPrintable(int pgIndex) { 2280 if (pgIndex > 0) { 2281 throw new IndexOutOfBoundsException("pgIndex"); 2282 } else { 2283 return printable; 2284 } 2285 } 2286 2287 } 2288 } 2289