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