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}