001/* Graphics class supporting EPS export from plots. 002 003 Copyright (c) 1998-2014 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 */ 027package ptolemy.plot; 028 029import java.awt.Color; 030import java.awt.Font; 031import java.awt.FontMetrics; 032import java.awt.Graphics; 033import java.awt.Image; 034import java.awt.Point; 035import java.awt.Rectangle; 036import java.awt.Shape; 037import java.awt.Toolkit; 038import java.awt.datatransfer.Clipboard; 039import java.awt.datatransfer.StringSelection; 040import java.awt.image.ImageObserver; 041import java.io.BufferedOutputStream; 042import java.io.OutputStream; 043import java.io.PrintWriter; 044import java.util.Hashtable; 045 046import ptolemy.util.StringUtilities; 047 048/////////////////////////////////////////////////////////////////// 049//// EPSGraphics 050 051/** 052 Graphics class supporting EPS export from plots. 053 If this is used from within an applet, then the output goes to the standard 054 output. Unfortunately, with standard browsers, this is not useful. 055 With MS Internet Explorer, standard output is not available. 056 With Netscape Navigator, standard output is available in the Java console, 057 but is limited to fewer lines than what is usually generated. 058 Thus, we recommend using this within Sun's appletviewer, and redirecting 059 its standard output to a file. 060 061 @author Edward A. Lee 062 @version $Id$ 063 @since Ptolemy II 0.2 064 @Pt.ProposedRating Yellow (cxh) 065 @Pt.AcceptedRating Yellow (cxh) 066 */ 067public class EPSGraphics extends Graphics { 068 /** Constructor for a graphics object that writes encapsulated 069 * PostScript to the specified output stream. If the out argument is 070 * null, then it writes to standard output (it would write it to 071 * the clipboard, but as of this writing, writing to the clipboard 072 * does not work in Java). 073 * @param out The stream to write to, or null to write to standard out. 074 * @param width The width of the plot graphic, in units of 1/72 inch. 075 * @param height The height of the plot graphic, in units of 1/72 inch. 076 */ 077 public EPSGraphics(OutputStream out, int width, int height) { 078 _width = width; 079 _height = height; 080 _out = out; 081 _buffer.append("%!PS-Adobe-3.0 EPSF-3.0\n"); 082 _buffer.append("%%Creator: UC Berkeley Plot Package\n"); 083 _buffer.append("%%BoundingBox: 50 50 " + (50 + width) + " " 084 + (50 + height) + "\n"); 085 _buffer.append("%%Pages: 1\n"); 086 _buffer.append("%%Page: 1 1\n"); 087 _buffer.append("%%LanguageLevel: 2\n"); 088 } 089 090 /////////////////////////////////////////////////////////////////// 091 //// public methods //// 092 @Override 093 public void clearRect(int x, int y, int width, int height) { 094 } 095 096 @Override 097 public void clipRect(int x, int y, int width, int height) { 098 } 099 100 @Override 101 public void copyArea(int x, int y, int width, int height, int dx, int dy) { 102 } 103 104 @Override 105 public Graphics create() { 106 return new EPSGraphics(_out, _width, _height); 107 } 108 109 @Override 110 public void dispose() { 111 } 112 113 @Override 114 public void drawArc(int x, int y, int width, int height, int startAngle, 115 int arcAngle) { 116 } 117 118 @Override 119 public boolean drawImage(Image img, int x, int y, ImageObserver observer) { 120 return true; 121 } 122 123 @Override 124 public boolean drawImage(Image img, int x, int y, int width, int height, 125 ImageObserver observer) { 126 return true; 127 } 128 129 @Override 130 public boolean drawImage(Image img, int x, int y, Color bgcolor, 131 ImageObserver observer) { 132 return true; 133 } 134 135 @Override 136 public boolean drawImage(Image img, int x, int y, int width, int height, 137 Color bgcolor, ImageObserver observer) { 138 return true; 139 } 140 141 @Override 142 public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, 143 int sx1, int sy1, int sx2, int sy2, ImageObserver observer) { 144 return true; 145 } 146 147 @Override 148 public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, 149 int sx1, int sy1, int sx2, int sy2, Color bgcolor, 150 ImageObserver observer) { 151 return true; 152 } 153 154 /** Draw a line, using the current color, between the points (x1, y1) 155 * and (x2, y2) in this graphics context's coordinate system. 156 * @param x1 the x coordinate of the first point. 157 * @param y1 the y coordinate of the first point. 158 * @param x2 the x coordinate of the second point. 159 * @param y2 the y coordinate of the second point. 160 */ 161 @Override 162 public void drawLine(int x1, int y1, int x2, int y2) { 163 Point start = _convert(x1, y1); 164 Point end = _convert(x2, y2); 165 _buffer.append("newpath " + start.x + " " + start.y + " moveto\n"); 166 _buffer.append("" + end.x + " " + end.y + " lineto\n"); 167 _buffer.append("stroke\n"); 168 } 169 170 @Override 171 public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { 172 } 173 174 /** Draw a closed polygon defined by arrays of x and y coordinates. 175 * Each pair of (x, y) coordinates defines a vertex. The third argument 176 * gives the number of vertices. If the arrays are not long enough to 177 * define this many vertices, or if the third argument is less than three, 178 * then nothing is drawn. 179 * @param xPoints An array of x coordinates. 180 * @param yPoints An array of y coordinates. 181 * @param nPoints The total number of vertices. 182 */ 183 @Override 184 public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { 185 if (!_polygon(xPoints, yPoints, nPoints)) { 186 return; 187 } else { 188 _buffer.append("closepath stroke\n"); 189 } 190 } 191 192 /** Draw an oval bounded by the specified rectangle with the current color. 193 * @param x The x coordinate of the upper left corner 194 * @param y The y coordinate of the upper left corner 195 * @param width The width of the oval to be filled. 196 * @param height The height of the oval to be filled. 197 */ 198 199 // FIXME: Currently, this ignores the fourth argument and draws a circle 200 // with diameter given by the third argument. 201 @Override 202 public void drawOval(int x, int y, int width, int height) { 203 int radius = width / 2; 204 Point center = _convert(x + radius, y + radius); 205 _buffer.append("newpath " + center.x + " " + center.y + " " + radius 206 + " 0 360 arc closepath stroke\n"); 207 } 208 209 @Override 210 public void drawRect(int x, int y, int width, int height) { 211 Point start = _convert(x, y); 212 _buffer.append("newpath " + start.x + " " + start.y + " moveto\n"); 213 _buffer.append("0 " + -height + " rlineto\n"); 214 _buffer.append("" + width + " 0 rlineto\n"); 215 _buffer.append("0 " + height + " rlineto\n"); 216 _buffer.append("" + -width + " 0 rlineto\n"); 217 _buffer.append("closepath stroke\n"); 218 } 219 220 @Override 221 public void drawRoundRect(int x, int y, int width, int height, int arcWidth, 222 int arcHeight) { 223 } 224 225 @Override 226 public void drawString(java.text.AttributedCharacterIterator iterator, 227 int x, int y) { 228 // FIXME: This method is present in the graphics class in JDK1.2, 229 // but not in JDK1.1. 230 throw new RuntimeException( 231 "Sorry, drawString(java.text.AttributedCharacterIterator, " 232 + "int , int) is not implemented in EPSGraphics"); 233 } 234 235 /** Draw a string. "(" is converted to "\(" and 236 * ")" is converted to "\) so as to avoid EPS Syntax errors. 237 * @param str The string to draw. 238 * @param x The x location of the string. 239 * @param y The y location of the string. 240 */ 241 @Override 242 public void drawString(String str, int x, int y) { 243 Point start = _convert(x, y); 244 _buffer.append("" + start.x + " " + start.y + " moveto\n"); 245 246 if (str.indexOf("(") > -1 && str.indexOf("\\(") == -1) { 247 str = StringUtilities.substitute(str, "(", "\\("); 248 } 249 250 if (str.indexOf(")") > -1 && str.indexOf("\\)") == -1) { 251 str = StringUtilities.substitute(str, ")", "\\)"); 252 } 253 254 _buffer.append("(" + str + ") show\n"); 255 } 256 257 @Override 258 public void fillArc(int x, int y, int width, int height, int startAngle, 259 int arcAngle) { 260 } 261 262 /** Draw a filled polygon defined by arrays of x and y coordinates. 263 * Each pair of (x, y) coordinates defines a vertex. The third argument 264 * gives the number of vertices. If the arrays are not long enough to 265 * define this many vertices, or if the third argument is less than three, 266 * then nothing is drawn. 267 * @param xPoints An array of x coordinates. 268 * @param yPoints An array of y coordinates. 269 * @param nPoints The total number of vertices. 270 */ 271 @Override 272 public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { 273 if (!_polygon(xPoints, yPoints, nPoints)) { 274 return; 275 } else { 276 _buffer.append("closepath fill\n"); 277 } 278 } 279 280 /** Fill an oval bounded by the specified rectangle with the current color. 281 * @param x The x coordinate of the upper left corner 282 * @param y The y coordinate of the upper left corner 283 * @param width The width of the oval to be filled. 284 * @param height The height of the oval to be filled. 285 */ 286 287 // FIXME: Currently, this ignores the fourth argument and draws a circle 288 // with diameter given by the third argument. 289 @Override 290 public void fillOval(int x, int y, int width, int height) { 291 int radius = width / 2; 292 Point center = _convert(x + radius, y + radius); 293 _buffer.append("newpath " + center.x + " " + center.y + " " + radius 294 + " 0 360 arc closepath fill\n"); 295 } 296 297 /** Fill the specified rectangle and draw a thin outline around it. 298 * The left and right edges of the rectangle are at x and x + width - 1. 299 * The top and bottom edges are at y and y + height - 1. 300 * The resulting rectangle covers an area width pixels wide by 301 * height pixels tall. The rectangle is filled using the 302 * brightness of the current color to set the level of gray. 303 * @param x The x coordinate of the top left corner. 304 * @param y The y coordinate of the top left corner. 305 * @param width The width of the rectangle. 306 * @param height The height of the rectangle. 307 */ 308 @Override 309 public void fillRect(int x, int y, int width, int height) { 310 Point start = _convert(x, y); 311 _fillPattern(); 312 _buffer.append("newpath " + start.x + " " + start.y + " moveto\n"); 313 _buffer.append("0 " + -height + " rlineto\n"); 314 _buffer.append("" + width + " 0 rlineto\n"); 315 _buffer.append("0 " + height + " rlineto\n"); 316 _buffer.append("" + -width + " 0 rlineto\n"); 317 _buffer.append("closepath gsave fill grestore\n"); 318 _buffer.append("0.5 setlinewidth 0 setgray [] 0 setdash stroke\n"); 319 320 // reset the gray scale to black 321 _buffer.append("1 setlinewidth\n"); 322 } 323 324 @Override 325 public void fillRoundRect(int x, int y, int width, int height, int arcWidth, 326 int arcHeight) { 327 } 328 329 @Override 330 public Shape getClip() { 331 return null; 332 } 333 334 @Override 335 public Rectangle getClipBounds() { 336 return null; 337 } 338 339 /** Get the current color. 340 * @return The current color. 341 * @see #setColor(Color) 342 */ 343 @Override 344 public Color getColor() { 345 return _currentColor; 346 } 347 348 @Override 349 public Font getFont() { 350 return _currentFont; 351 } 352 353 @Override 354 public FontMetrics getFontMetrics(Font f) { 355 return null; 356 } 357 358 @Override 359 public void setFont(Font font) { 360 if (font == null) { 361 return; 362 } 363 364 int size = font.getSize(); 365 boolean bold = font.isBold(); 366 367 if (bold) { 368 _buffer.append("/Helvetica-Bold findfont\n"); 369 } else { 370 _buffer.append("/Helvetica findfont\n"); 371 } 372 373 _buffer.append("" + size + " scalefont setfont\n"); 374 _currentFont = font; 375 } 376 377 @Override 378 public void setClip(Shape clip) { 379 } 380 381 @Override 382 public void setClip(int x, int y, int width, int height) { 383 } 384 385 /** Set the current color. Since we are generating gray scale 386 * postscript, set a line style. Set the gray level to zero (black). 387 * @param c The desired current color. 388 * @see #getColor() 389 */ 390 @Override 391 public void setColor(Color c) { 392 if (c == Color.black) { 393 _buffer.append("0 setgray\n"); 394 _buffer.append("[] 0 setdash\n"); 395 _buffer.append("1 setlinewidth\n"); 396 } else if (c == Color.white) { 397 _buffer.append("1 setgray\n"); 398 _buffer.append("[] 0 setdash\n"); 399 _buffer.append("1 setlinewidth\n"); 400 } else if (c == Color.lightGray) { 401 _buffer.append("0.9 setgray\n"); 402 _buffer.append("[] 0 setdash\n"); 403 _buffer.append("0.5 setlinewidth\n"); 404 } else { 405 if (_linepattern.containsKey(c)) { 406 _buffer.append((String) _linepattern.get(c) + " 0 setdash\n"); 407 _buffer.append("1 setlinewidth\n"); 408 } else { 409 _buffer.append("0 setgray\n"); 410 411 // construct a new line pattern. 412 if (_patternIndex >= _patterns.length) { 413 _patternIndex = 0; 414 } 415 416 _buffer.append(_patterns[_patternIndex] + " 0 setdash\n"); 417 _buffer.append("1 setlinewidth\n"); 418 _linepattern.put(c, _patterns[_patternIndex]); 419 _patternIndex++; 420 } 421 } 422 423 _currentColor = c; 424 } 425 426 @Override 427 public void setPaintMode() { 428 } 429 430 @Override 431 public void setXORMode(Color c1) { 432 } 433 434 /** Issue the PostScript showpage command, then write and flush the output. 435 * If the output argument of the constructor was null, then write 436 * to the clipboard. 437 */ 438 public void showpage() { 439 _buffer.append("showpage\n"); 440 441 if (_out != null) { 442 PrintWriter output = new PrintWriter( 443 new BufferedOutputStream(_out)); 444 445 output.println(_buffer.toString()); 446 output.flush(); 447 } else { 448 // Write to clipboard instead 449 // NOTE: This doesn't work at least with jdk 1.3beta 450 if (_clipboard == null) { 451 _clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 452 } 453 454 StringSelection sel = new StringSelection(_buffer.toString()); 455 _clipboard.setContents(sel, sel); 456 } 457 } 458 459 @Override 460 public void translate(int x, int y) { 461 } 462 463 /////////////////////////////////////////////////////////////////// 464 //// private methods //// 465 // Convert the screen coordinate system to that of postscript. 466 private Point _convert(int x, int y) { 467 return new Point(x + 50, _height + 50 - y); 468 } 469 470 // Draw a closed polygon defined by arrays of x and y coordinates. 471 // Return false if arguments are misformed. 472 private boolean _polygon(int[] xPoints, int[] yPoints, int nPoints) { 473 if (nPoints < 3 || xPoints.length < nPoints 474 || yPoints.length < nPoints) { 475 return false; 476 } 477 478 Point start = _convert(xPoints[0], yPoints[0]); 479 _buffer.append("newpath " + start.x + " " + start.y + " moveto\n"); 480 481 for (int i = 1; i < nPoints; i++) { 482 Point vertex = _convert(xPoints[i], yPoints[i]); 483 _buffer.append("" + vertex.x + " " + vertex.y + " lineto\n"); 484 } 485 486 return true; 487 } 488 489 // Issue a command to set the fill pattern. 490 // Currently, this is a gray level that is a function of the color. 491 private void _fillPattern() { 492 // FIXME: We probably want a fill pattern rather than 493 // just a gray scale. 494 int red = _currentColor.getRed(); 495 int green = _currentColor.getGreen(); 496 int blue = _currentColor.getBlue(); 497 498 // Scaling constants so that fully saturated R, G, or B appear 499 // different. 500 double bluescale = 0.6; // darkest 501 double redscale = 0.8; 502 double greenscale = 1.0; // lightest 503 double fullscale = Math.sqrt(255.0 * 255.0 * (bluescale * bluescale 504 + redscale * redscale + greenscale * greenscale)); 505 double graylevel = Math.sqrt(red * red * redscale * redscale 506 + blue * blue * bluescale * bluescale 507 + green * green * greenscale * greenscale) / fullscale; 508 _buffer.append("" + graylevel + " setgray\n"); 509 510 // NOTE -- for debugging, output color spec in comments 511 _buffer.append("%---- rgb: " + red + " " + green + " " + blue + "\n"); 512 } 513 514 /////////////////////////////////////////////////////////////////// 515 //// private variables //// 516 private Color _currentColor = Color.black; 517 518 private Font _currentFont; 519 520 private int _width; 521 522 private int _height; 523 524 private Hashtable _linepattern = new Hashtable(); 525 526 private OutputStream _out; 527 528 private StringBuffer _buffer = new StringBuffer(); 529 530 private Clipboard _clipboard; 531 532 // Default line patterns. 533 // FIXME: Need at least 11 of these. 534 static private String[] _patterns = { "[]", "[1 1]", "[4 4]", "[4 4 1 4]", 535 "[2 2]", "[4 2 1 2 1 2]", "[5 3 2 3]", "[3 3]", "[4 2 1 2 2 2]", 536 "[1 2 5 2 1 2 1 2]", "[4 1 2 1]", }; 537 538 private int _patternIndex = 0; 539}