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