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