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