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