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