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