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.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. java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Object>() { public Object run() { mFontProps = initProps(); String osName = System.getProperty(R); isMac = osName.startsWith(R); return null; } })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 */ initProps()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 PSPrinterJob()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 */ printDialog()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 setAttributes(PrintRequestAttributeSet attributes)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 */ startDoc()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 run()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 handleProcessFailure(final Process failedProcess, final String[] execCmd, final int result)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 run()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 */ abortDoc()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 */ endDoc()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 getCoordPrep()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 */ startPage(PageFormat pageFormat, Printable painter, int index, boolean paperChanged)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 */ endPage(PageFormat format, Printable painter, int index)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 */ drawImageBGR(byte[] bgrData, float destX, float destY, float destWidth, float destHeight, float srcX, float srcY, float srcWidth, float srcHeight, int srcBitMapWidth, int srcBitMapHeight)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 */ printBand(byte[] bgrData, int x, int y, int width, int height)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 createPathGraphics(PeekGraphics peekGraphics, PrinterJob printerJob, Printable painter, PageFormat pageFormat, int pageIndex)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 */ selectClipPath()1092 protected void selectClipPath() { 1093 1094 mPSStream.println(mClipOpStr); 1095 } 1096 setClip(Shape clip)1097 protected void setClip(Shape clip) { 1098 1099 mLastClip = clip; 1100 } 1101 setTransform(AffineTransform transform)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 */ setFont(Font font)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 */ getPSFontIndexArray(Font font, CharsetString[] charSet)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 escapeParens(String str)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 */ platformFontCount(Font font, String str)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 textOut(Graphics g, String str, float x, float y, Font mLastFont, FontRenderContext frc, float width)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 */ setFillMode(int fillRule)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 */ setColor(Color color)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 */ fillPath()1403 protected void fillPath() { 1404 1405 mPSStream.println(mFillOpStr); 1406 } 1407 1408 /** 1409 * Called to mark the start of a new path. 1410 */ beginPath()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 */ closeSubpath()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 */ moveTo(float x, float y)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 */ lineTo(float x, float y)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 */ bezierTo(float control1x, float control1y, float control2x, float control2y, float endX, float endY)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 trunc(float f)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 */ getPenX()1501 protected float getPenX() { 1502 1503 return mPenX; 1504 } 1505 /** 1506 * Return the y coordinate of the pen in the 1507 * current path. 1508 */ getPenY()1509 protected float getPenY() { 1510 1511 return mPenY; 1512 } 1513 1514 /** 1515 * Return the x resolution of the coordinates 1516 * to be rendered. 1517 */ getXRes()1518 protected double getXRes() { 1519 return xres; 1520 } 1521 /** 1522 * Return the y resolution of the coordinates 1523 * to be rendered. 1524 */ getYRes()1525 protected double getYRes() { 1526 return yres; 1527 } 1528 1529 /** 1530 * Set the resolution at which to print. 1531 */ setXYRes(double x, double y)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 */ getPhysicalPrintableX(Paper p)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 */ getPhysicalPrintableY(Paper p)1550 protected double getPhysicalPrintableY(Paper p) { 1551 return 0; 1552 } 1553 getPhysicalPrintableWidth(Paper p)1554 protected double getPhysicalPrintableWidth(Paper p) { 1555 return p.getImageableWidth(); 1556 } 1557 getPhysicalPrintableHeight(Paper p)1558 protected double getPhysicalPrintableHeight(Paper p) { 1559 return p.getImageableHeight(); 1560 } 1561 getPhysicalPageWidth(Paper p)1562 protected double getPhysicalPageWidth(Paper p) { 1563 return p.getWidth(); 1564 } 1565 getPhysicalPageHeight(Paper p)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 */ getNoncollatedCopies()1576 protected int getNoncollatedCopies() { 1577 return 1; 1578 } 1579 getCollatedCopies()1580 protected int getCollatedCopies() { 1581 return 1; 1582 } 1583 printExecCmd(String printer, String options, boolean noJobSheet, String jobTitle, int copies, String spoolFile)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") || osname.endsWith("BSD")) { 1623 String lprPath = "/usr/bin/lpr"; 1624 if (osname.equals("FreeBSD")) { 1625 final PrintService pservice = getPrintService(); 1626 Boolean isIPPPrinter = java.security.AccessController.doPrivileged( 1627 new java.security.PrivilegedAction<Boolean>() { 1628 public Boolean run() { 1629 try { 1630 Class<?> psClass = 1631 Class.forName("sun.print.IPPPrintService"); 1632 if (psClass.isInstance(pservice)) { 1633 return Boolean.TRUE; 1634 } 1635 } catch (Throwable t) { 1636 } 1637 return Boolean.FALSE; 1638 } 1639 } 1640 ); 1641 if (isIPPPrinter) { 1642 lprPath = "/usr/local/bin/lpr"; 1643 } 1644 } 1645 execCmd = new String[ncomps]; 1646 execCmd[n++] = lprPath; 1647 if ((pFlags & PRINTER) != 0) { 1648 execCmd[n++] = "-P" + printer; 1649 } 1650 if ((pFlags & JOBTITLE) != 0) { 1651 execCmd[n++] = "-J" + jobTitle; 1652 } 1653 if ((pFlags & COPIES) != 0) { 1654 execCmd[n++] = "-#" + copies; 1655 } 1656 if ((pFlags & NOSHEET) != 0) { 1657 execCmd[n++] = "-h"; 1658 } else if (getPrintService(). 1659 isAttributeCategorySupported(JobSheets.class)) { 1660 execCmd[n++] = "-o job-sheets=standard"; 1661 } 1662 if ((pFlags & OPTIONS) != 0) { 1663 execCmd[n++] = "-o" + options; 1664 } 1665 } else { 1666 ncomps+=1; //add 1 arg for lp 1667 execCmd = new String[ncomps]; 1668 execCmd[n++] = "/usr/bin/lp"; 1669 execCmd[n++] = "-c"; // make a copy of the spool file 1670 if ((pFlags & PRINTER) != 0) { 1671 execCmd[n++] = "-d" + printer; 1672 } 1673 if ((pFlags & JOBTITLE) != 0) { 1674 execCmd[n++] = "-t" + jobTitle; 1675 } 1676 if ((pFlags & COPIES) != 0) { 1677 execCmd[n++] = "-n" + copies; 1678 } 1679 if ((pFlags & NOSHEET) != 0) { 1680 execCmd[n++] = "-o nobanner"; 1681 } else if (getPrintService(). 1682 isAttributeCategorySupported(JobSheets.class)) { 1683 execCmd[n++] = "-o job-sheets=standard"; 1684 } 1685 if ((pFlags & OPTIONS) != 0) { 1686 execCmd[n++] = "-o" + options; 1687 } 1688 } 1689 execCmd[n++] = spoolFile; 1690 return execCmd; 1691 } 1692 swapBGRtoRGB(byte[] image, int index, byte[] dest)1693 private static int swapBGRtoRGB(byte[] image, int index, byte[] dest) { 1694 int destIndex = 0; 1695 while(index < image.length-2 && destIndex < dest.length-2) { 1696 dest[destIndex++] = image[index+2]; 1697 dest[destIndex++] = image[index+1]; 1698 dest[destIndex++] = image[index+0]; 1699 index+=3; 1700 } 1701 return index; 1702 } 1703 1704 /* 1705 * Currently CharToByteConverter.getCharacterEncoding() return values are 1706 * not fixed yet. These are used as the part of the key of 1707 * psfont.properties. When those name are fixed this routine can 1708 * be erased. 1709 */ makeCharsetName(String name, char[] chs)1710 private String makeCharsetName(String name, char[] chs) { 1711 if (name.equals("Cp1252") || name.equals("ISO8859_1")) { 1712 return "latin1"; 1713 } else if (name.equals("UTF8")) { 1714 // same as latin 1 if all chars < 256 1715 for (int i=0; i < chs.length; i++) { 1716 if (chs[i] > 255) { 1717 return name.toLowerCase(); 1718 } 1719 } 1720 return "latin1"; 1721 } else if (name.startsWith("ISO8859")) { 1722 // same as latin 1 if all chars < 128 1723 for (int i=0; i < chs.length; i++) { 1724 if (chs[i] > 127) { 1725 return name.toLowerCase(); 1726 } 1727 } 1728 return "latin1"; 1729 } else { 1730 return name.toLowerCase(); 1731 } 1732 } 1733 prepDrawing()1734 private void prepDrawing() { 1735 1736 /* Pop gstates until we can set the needed clip 1737 * and transform or until we are at the outer most 1738 * gstate. 1739 */ 1740 while (isOuterGState() == false 1741 && (getGState().canSetClip(mLastClip) == false 1742 || getGState().mTransform.equals(mLastTransform) == false)) { 1743 1744 1745 grestore(); 1746 } 1747 1748 /* Set the color. This can push the color to the 1749 * outer most gsave which is often a good thing. 1750 */ 1751 getGState().emitPSColor(mLastColor); 1752 1753 /* We do not want to change the outermost 1754 * transform or clip so if we are at the 1755 * outer clip the generate a gsave. 1756 */ 1757 if (isOuterGState()) { 1758 gsave(); 1759 getGState().emitTransform(mLastTransform); 1760 getGState().emitPSClip(mLastClip); 1761 } 1762 1763 /* Set the font if we have been asked to. It is 1764 * important that the font is set after the 1765 * transform in order to get the font size 1766 * correct. 1767 */ 1768 // if (g != null) { 1769 // getGState().emitPSFont(g, mLastFont); 1770 // } 1771 1772 } 1773 1774 /** 1775 * Return the GState that is currently on top 1776 * of the GState stack. There should always be 1777 * a GState on top of the stack. If there isn't 1778 * then this method will throw an IndexOutOfBounds 1779 * exception. 1780 */ getGState()1781 private GState getGState() { 1782 int count = mGStateStack.size(); 1783 return mGStateStack.get(count - 1); 1784 } 1785 1786 /** 1787 * Emit a PostScript gsave command and add a 1788 * new GState on to our stack which represents 1789 * the printer's gstate stack. 1790 */ gsave()1791 private void gsave() { 1792 GState oldGState = getGState(); 1793 mGStateStack.add(new GState(oldGState)); 1794 mPSStream.println(GSAVE_STR); 1795 } 1796 1797 /** 1798 * Emit a PostScript grestore command and remove 1799 * a GState from our stack which represents the 1800 * printer's gstate stack. 1801 */ grestore()1802 private void grestore() { 1803 int count = mGStateStack.size(); 1804 mGStateStack.remove(count - 1); 1805 mPSStream.println(GRESTORE_STR); 1806 } 1807 1808 /** 1809 * Return true if the current GState is the 1810 * outermost GState and therefore should not 1811 * be restored. 1812 */ isOuterGState()1813 private boolean isOuterGState() { 1814 return mGStateStack.size() == 1; 1815 } 1816 1817 /** 1818 * A stack of GStates is maintained to model the printer's 1819 * gstate stack. Each GState holds information about 1820 * the current graphics attributes. 1821 */ 1822 private class GState{ 1823 Color mColor; 1824 Shape mClip; 1825 Font mFont; 1826 AffineTransform mTransform; 1827 GState()1828 GState() { 1829 mColor = Color.black; 1830 mClip = null; 1831 mFont = null; 1832 mTransform = new AffineTransform(); 1833 } 1834 GState(GState copyGState)1835 GState(GState copyGState) { 1836 mColor = copyGState.mColor; 1837 mClip = copyGState.mClip; 1838 mFont = copyGState.mFont; 1839 mTransform = copyGState.mTransform; 1840 } 1841 canSetClip(Shape clip)1842 boolean canSetClip(Shape clip) { 1843 1844 return mClip == null || mClip.equals(clip); 1845 } 1846 1847 emitPSClip(Shape clip)1848 void emitPSClip(Shape clip) { 1849 if (clip != null 1850 && (mClip == null || mClip.equals(clip) == false)) { 1851 String saveFillOp = mFillOpStr; 1852 String saveClipOp = mClipOpStr; 1853 convertToPSPath(clip.getPathIterator(new AffineTransform())); 1854 selectClipPath(); 1855 mClip = clip; 1856 /* The clip is a shape and has reset the winding rule state */ 1857 mClipOpStr = saveFillOp; 1858 mFillOpStr = saveFillOp; 1859 } 1860 } 1861 emitTransform(AffineTransform transform)1862 void emitTransform(AffineTransform transform) { 1863 1864 if (transform != null && transform.equals(mTransform) == false) { 1865 double[] matrix = new double[6]; 1866 transform.getMatrix(matrix); 1867 mPSStream.println("[" + (float)matrix[0] 1868 + " " + (float)matrix[1] 1869 + " " + (float)matrix[2] 1870 + " " + (float)matrix[3] 1871 + " " + (float)matrix[4] 1872 + " " + (float)matrix[5] 1873 + "] concat"); 1874 1875 mTransform = transform; 1876 } 1877 } 1878 emitPSColor(Color color)1879 void emitPSColor(Color color) { 1880 if (color != null && color.equals(mColor) == false) { 1881 float[] rgb = color.getRGBColorComponents(null); 1882 1883 /* If the color is a gray value then use 1884 * setgray. 1885 */ 1886 if (rgb[0] == rgb[1] && rgb[1] == rgb[2]) { 1887 mPSStream.println(rgb[0] + SETGRAY_STR); 1888 1889 /* It's not gray so use setrgbcolor. 1890 */ 1891 } else { 1892 mPSStream.println(rgb[0] + " " 1893 + rgb[1] + " " 1894 + rgb[2] + " " 1895 + SETRGBCOLOR_STR); 1896 } 1897 1898 mColor = color; 1899 1900 } 1901 } 1902 emitPSFont(int psFontIndex, float fontSize)1903 void emitPSFont(int psFontIndex, float fontSize) { 1904 mPSStream.println(fontSize + " " + 1905 psFontIndex + " " + SetFontName); 1906 } 1907 } 1908 1909 /** 1910 * Given a Java2D {@code PathIterator} instance, 1911 * this method translates that into a PostScript path.. 1912 */ convertToPSPath(PathIterator pathIter)1913 void convertToPSPath(PathIterator pathIter) { 1914 1915 float[] segment = new float[6]; 1916 int segmentType; 1917 1918 /* Map the PathIterator's fill rule into the PostScript 1919 * fill rule. 1920 */ 1921 int fillRule; 1922 if (pathIter.getWindingRule() == PathIterator.WIND_EVEN_ODD) { 1923 fillRule = FILL_EVEN_ODD; 1924 } else { 1925 fillRule = FILL_WINDING; 1926 } 1927 1928 beginPath(); 1929 1930 setFillMode(fillRule); 1931 1932 while (pathIter.isDone() == false) { 1933 segmentType = pathIter.currentSegment(segment); 1934 1935 switch (segmentType) { 1936 case PathIterator.SEG_MOVETO: 1937 moveTo(segment[0], segment[1]); 1938 break; 1939 1940 case PathIterator.SEG_LINETO: 1941 lineTo(segment[0], segment[1]); 1942 break; 1943 1944 /* Convert the quad path to a bezier. 1945 */ 1946 case PathIterator.SEG_QUADTO: 1947 float lastX = getPenX(); 1948 float lastY = getPenY(); 1949 float c1x = lastX + (segment[0] - lastX) * 2 / 3; 1950 float c1y = lastY + (segment[1] - lastY) * 2 / 3; 1951 float c2x = segment[2] - (segment[2] - segment[0]) * 2/ 3; 1952 float c2y = segment[3] - (segment[3] - segment[1]) * 2/ 3; 1953 bezierTo(c1x, c1y, 1954 c2x, c2y, 1955 segment[2], segment[3]); 1956 break; 1957 1958 case PathIterator.SEG_CUBICTO: 1959 bezierTo(segment[0], segment[1], 1960 segment[2], segment[3], 1961 segment[4], segment[5]); 1962 break; 1963 1964 case PathIterator.SEG_CLOSE: 1965 closeSubpath(); 1966 break; 1967 } 1968 1969 1970 pathIter.next(); 1971 } 1972 } 1973 1974 /* 1975 * Fill the path defined by {@code pathIter} 1976 * with the specified color. 1977 * The path is provided in current user space. 1978 */ deviceFill(PathIterator pathIter, Color color, AffineTransform tx, Shape clip)1979 protected void deviceFill(PathIterator pathIter, Color color, 1980 AffineTransform tx, Shape clip) { 1981 1982 if (Double.isNaN(tx.getScaleX()) || 1983 Double.isNaN(tx.getScaleY()) || 1984 Double.isNaN(tx.getShearX()) || 1985 Double.isNaN(tx.getShearY()) || 1986 Double.isNaN(tx.getTranslateX()) || 1987 Double.isNaN(tx.getTranslateY())) { 1988 return; 1989 } 1990 setTransform(tx); 1991 setClip(clip); 1992 setColor(color); 1993 convertToPSPath(pathIter); 1994 /* Specify the path to fill as the clip, this ensures that only 1995 * pixels which are inside the path will be filled, which is 1996 * what the Java 2D APIs specify 1997 */ 1998 mPSStream.println(GSAVE_STR); 1999 selectClipPath(); 2000 fillPath(); 2001 mPSStream.println(GRESTORE_STR + " " + NEWPATH_STR); 2002 } 2003 2004 /* 2005 * Run length encode byte array in a form suitable for decoding 2006 * by the PS Level 2 filter RunLengthDecode. 2007 * Array data to encode is inArr. Encoded data is written to outArr 2008 * outArr must be long enough to hold the encoded data but this 2009 * can't be known ahead of time. 2010 * A safe assumption is to use double the length of the input array. 2011 * This is then copied into a new array of the correct length which 2012 * is returned. 2013 * Algorithm: 2014 * Encoding is a lead byte followed by data bytes. 2015 * Lead byte of 0->127 indicates leadByte + 1 distinct bytes follow 2016 * Lead byte of 129->255 indicates 257 - leadByte is the number of times 2017 * the following byte is repeated in the source. 2018 * 128 is a special lead byte indicating end of data (EOD) and is 2019 * written as the final byte of the returned encoded data. 2020 */ rlEncode(byte[] inArr)2021 private byte[] rlEncode(byte[] inArr) { 2022 2023 int inIndex = 0; 2024 int outIndex = 0; 2025 int startIndex = 0; 2026 int runLen = 0; 2027 byte[] outArr = new byte[(inArr.length * 2) +2]; 2028 while (inIndex < inArr.length) { 2029 if (runLen == 0) { 2030 startIndex = inIndex++; 2031 runLen=1; 2032 } 2033 2034 while (runLen < 128 && inIndex < inArr.length && 2035 inArr[inIndex] == inArr[startIndex]) { 2036 runLen++; // count run of same value 2037 inIndex++; 2038 } 2039 2040 if (runLen > 1) { 2041 outArr[outIndex++] = (byte)(257 - runLen); 2042 outArr[outIndex++] = inArr[startIndex]; 2043 runLen = 0; 2044 continue; // back to top of while loop. 2045 } 2046 2047 // if reach here have a run of different values, or at the end. 2048 while (runLen < 128 && inIndex < inArr.length && 2049 inArr[inIndex] != inArr[inIndex-1]) { 2050 runLen++; // count run of different values 2051 inIndex++; 2052 } 2053 outArr[outIndex++] = (byte)(runLen - 1); 2054 for (int i = startIndex; i < startIndex+runLen; i++) { 2055 outArr[outIndex++] = inArr[i]; 2056 } 2057 runLen = 0; 2058 } 2059 outArr[outIndex++] = (byte)128; 2060 byte[] encodedData = new byte[outIndex]; 2061 System.arraycopy(outArr, 0, encodedData, 0, outIndex); 2062 2063 return encodedData; 2064 } 2065 2066 /* written acc. to Adobe Spec. "Filtered Files: ASCIIEncode Filter", 2067 * "PS Language Reference Manual, 2nd edition: Section 3.13" 2068 */ ascii85Encode(byte[] inArr)2069 private byte[] ascii85Encode(byte[] inArr) { 2070 byte[] outArr = new byte[((inArr.length+4) * 5 / 4) + 2]; 2071 long p1 = 85; 2072 long p2 = p1*p1; 2073 long p3 = p1*p2; 2074 long p4 = p1*p3; 2075 byte pling = '!'; 2076 2077 int i = 0; 2078 int olen = 0; 2079 long val, rem; 2080 2081 while (i+3 < inArr.length) { 2082 val = ((long)((inArr[i++]&0xff))<<24) + 2083 ((long)((inArr[i++]&0xff))<<16) + 2084 ((long)((inArr[i++]&0xff))<< 8) + 2085 ((long)(inArr[i++]&0xff)); 2086 if (val == 0) { 2087 outArr[olen++] = 'z'; 2088 } else { 2089 rem = val; 2090 outArr[olen++] = (byte)(rem / p4 + pling); rem = rem % p4; 2091 outArr[olen++] = (byte)(rem / p3 + pling); rem = rem % p3; 2092 outArr[olen++] = (byte)(rem / p2 + pling); rem = rem % p2; 2093 outArr[olen++] = (byte)(rem / p1 + pling); rem = rem % p1; 2094 outArr[olen++] = (byte)(rem + pling); 2095 } 2096 } 2097 // input not a multiple of 4 bytes, write partial output. 2098 if (i < inArr.length) { 2099 int n = inArr.length - i; // n bytes remain to be written 2100 2101 val = 0; 2102 while (i < inArr.length) { 2103 val = (val << 8) + (inArr[i++]&0xff); 2104 } 2105 2106 int append = 4 - n; 2107 while (append-- > 0) { 2108 val = val << 8; 2109 } 2110 byte []c = new byte[5]; 2111 rem = val; 2112 c[0] = (byte)(rem / p4 + pling); rem = rem % p4; 2113 c[1] = (byte)(rem / p3 + pling); rem = rem % p3; 2114 c[2] = (byte)(rem / p2 + pling); rem = rem % p2; 2115 c[3] = (byte)(rem / p1 + pling); rem = rem % p1; 2116 c[4] = (byte)(rem + pling); 2117 2118 for (int b = 0; b < n+1 ; b++) { 2119 outArr[olen++] = c[b]; 2120 } 2121 } 2122 2123 // write EOD marker. 2124 outArr[olen++]='~'; outArr[olen++]='>'; 2125 2126 /* The original intention was to insert a newline after every 78 bytes. 2127 * This was mainly intended for legibility but I decided against this 2128 * partially because of the (small) amount of extra space, and 2129 * partially because for line breaks either would have to hardwire 2130 * ascii 10 (newline) or calculate space in bytes to allocate for 2131 * the platform's newline byte sequence. Also need to be careful 2132 * about where its inserted: 2133 * Ascii 85 decoder ignores white space except for one special case: 2134 * you must ensure you do not split the EOD marker across lines. 2135 */ 2136 byte[] retArr = new byte[olen]; 2137 System.arraycopy(outArr, 0, retArr, 0, olen); 2138 return retArr; 2139 2140 } 2141 2142 /** 2143 * PluginPrinter generates EPSF wrapped with a header and trailer 2144 * comment. This conforms to the new requirements of Mozilla 1.7 2145 * and FireFox 1.5 and later. Earlier versions of these browsers 2146 * did not support plugin printing in the general sense (not just Java). 2147 * A notable limitation of these browsers is that they handle plugins 2148 * which would span page boundaries by scaling plugin content to fit on a 2149 * single page. This means white space is left at the bottom of the 2150 * previous page and its impossible to print these cases as they appear on 2151 * the web page. This is contrast to how the same browsers behave on 2152 * Windows where it renders as on-screen. 2153 * Cases where the content fits on a single page do work fine, and they 2154 * are the majority of cases. 2155 * The scaling that the browser specifies to make the plugin content fit 2156 * when it is larger than a single page can hold is non-uniform. It 2157 * scales the axis in which the content is too large just enough to 2158 * ensure it fits. For content which is extremely long this could lead 2159 * to noticeable distortion. However that is probably rare enough that 2160 * its not worth compensating for that here, but we can revisit that if 2161 * needed, and compensate by making the scale for the other axis the 2162 * same. 2163 */ 2164 public static class PluginPrinter implements Printable { 2165 2166 private EPSPrinter epsPrinter; 2167 private Component applet; 2168 private PrintStream stream; 2169 private String epsTitle; 2170 private int bx, by, bw, bh; 2171 private int width, height; 2172 2173 /** 2174 * This is called from the Java Plug-in to print an Applet's 2175 * contents as EPS to a postscript stream provided by the browser. 2176 * @param applet the applet component to print. 2177 * @param stream the print stream provided by the plug-in 2178 * @param x the x location of the applet panel in the browser window 2179 * @param y the y location of the applet panel in the browser window 2180 * @param w the width of the applet panel in the browser window 2181 * @param h the width of the applet panel in the browser window 2182 */ 2183 @SuppressWarnings("deprecation") PluginPrinter(Component applet, PrintStream stream, int x, int y, int w, int h)2184 public PluginPrinter(Component applet, 2185 PrintStream stream, 2186 int x, int y, int w, int h) { 2187 2188 this.applet = applet; 2189 this.epsTitle = "Java Plugin Applet"; 2190 this.stream = stream; 2191 bx = x; 2192 by = y; 2193 bw = w; 2194 bh = h; 2195 width = applet.size().width; 2196 height = applet.size().height; 2197 epsPrinter = new EPSPrinter(this, epsTitle, stream, 2198 0, 0, width, height); 2199 } 2200 printPluginPSHeader()2201 public void printPluginPSHeader() { 2202 stream.println("%%BeginDocument: JavaPluginApplet"); 2203 } 2204 printPluginApplet()2205 public void printPluginApplet() { 2206 try { 2207 epsPrinter.print(); 2208 } catch (PrinterException e) { 2209 } 2210 } 2211 printPluginPSTrailer()2212 public void printPluginPSTrailer() { 2213 stream.println("%%EndDocument: JavaPluginApplet"); 2214 stream.flush(); 2215 } 2216 printAll()2217 public void printAll() { 2218 printPluginPSHeader(); 2219 printPluginApplet(); 2220 printPluginPSTrailer(); 2221 } 2222 print(Graphics g, PageFormat pf, int pgIndex)2223 public int print(Graphics g, PageFormat pf, int pgIndex) { 2224 if (pgIndex > 0) { 2225 return Printable.NO_SUCH_PAGE; 2226 } else { 2227 // "aware" client code can detect that its been passed a 2228 // PrinterGraphics and could theoretically print 2229 // differently. I think this is more likely useful than 2230 // a problem. 2231 applet.printAll(g); 2232 return Printable.PAGE_EXISTS; 2233 } 2234 } 2235 2236 } 2237 2238 /* 2239 * This class can take an application-client supplied printable object 2240 * and send the result to a stream. 2241 * The application does not need to send any postscript to this stream 2242 * unless it needs to specify a translation etc. 2243 * It assumes that its importing application obeys all the conventions 2244 * for importation of EPS. See Appendix H - Encapsulated Postscript File 2245 * Format - of the Adobe Postscript Language Reference Manual, 2nd edition. 2246 * This class could be used as the basis for exposing the ability to 2247 * generate EPSF from 2D graphics as a StreamPrintService. 2248 * In that case a MediaPrintableArea attribute could be used to 2249 * communicate the bounding box. 2250 */ 2251 public static class EPSPrinter implements Pageable { 2252 2253 private PageFormat pf; 2254 private PSPrinterJob job; 2255 private int llx, lly, urx, ury; 2256 private Printable printable; 2257 private PrintStream stream; 2258 private String epsTitle; 2259 EPSPrinter(Printable printable, String title, PrintStream stream, int x, int y, int wid, int hgt)2260 public EPSPrinter(Printable printable, String title, 2261 PrintStream stream, 2262 int x, int y, int wid, int hgt) { 2263 2264 this.printable = printable; 2265 this.epsTitle = title; 2266 this.stream = stream; 2267 llx = x; 2268 lly = y; 2269 urx = llx+wid; 2270 ury = lly+hgt; 2271 // construct a PageFormat with zero margins representing the 2272 // exact bounds of the applet. ie construct a theoretical 2273 // paper which happens to exactly match applet panel size. 2274 Paper p = new Paper(); 2275 p.setSize((double)wid, (double)hgt); 2276 p.setImageableArea(0.0,0.0, (double)wid, (double)hgt); 2277 pf = new PageFormat(); 2278 pf.setPaper(p); 2279 } 2280 print()2281 public void print() throws PrinterException { 2282 stream.println("%!PS-Adobe-3.0 EPSF-3.0"); 2283 stream.println("%%BoundingBox: " + 2284 llx + " " + lly + " " + urx + " " + ury); 2285 stream.println("%%Title: " + epsTitle); 2286 stream.println("%%Creator: Java Printing"); 2287 stream.println("%%CreationDate: " + new java.util.Date()); 2288 stream.println("%%EndComments"); 2289 stream.println("/pluginSave save def"); 2290 stream.println("mark"); // for restoring stack state on return 2291 2292 job = new PSPrinterJob(); 2293 job.epsPrinter = this; // modifies the behaviour of PSPrinterJob 2294 job.mPSStream = stream; 2295 job.mDestType = RasterPrinterJob.STREAM; // prevents closure 2296 2297 job.startDoc(); 2298 try { 2299 job.printPage(this, 0); 2300 } catch (Throwable t) { 2301 if (t instanceof PrinterException) { 2302 throw (PrinterException)t; 2303 } else { 2304 throw new PrinterException(t.toString()); 2305 } 2306 } finally { 2307 stream.println("cleartomark"); // restore stack state 2308 stream.println("pluginSave restore"); 2309 job.endDoc(); 2310 } 2311 stream.flush(); 2312 } 2313 getNumberOfPages()2314 public int getNumberOfPages() { 2315 return 1; 2316 } 2317 getPageFormat(int pgIndex)2318 public PageFormat getPageFormat(int pgIndex) { 2319 if (pgIndex > 0) { 2320 throw new IndexOutOfBoundsException("pgIndex"); 2321 } else { 2322 return pf; 2323 } 2324 } 2325 getPrintable(int pgIndex)2326 public Printable getPrintable(int pgIndex) { 2327 if (pgIndex > 0) { 2328 throw new IndexOutOfBoundsException("pgIndex"); 2329 } else { 2330 return printable; 2331 } 2332 } 2333 2334 } 2335 } 2336