001/*
002// $Id: Chart.java 3 2009-05-11 08:11:57Z jhyde $
003// Clapham generates railroad diagrams to represent computer language grammars.
004// Copyright (C) 2008-2009 Julian Hyde
005// Copyright (c) 2005 Stefan Schoergenhumer, Markus Dopler
006//
007// This program is free software; you can redistribute it and/or modify it
008// under the terms of the GNU General Public License as published by the Free
009// Software Foundation; either version 2 of the License, or (at your option)
010// any later version approved by The Eigenbase Project.
011//
012// This program is distributed in the hope that it will be useful,
013// but WITHOUT ANY WARRANTY; without even the implied warranty of
014// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
015// GNU General Public License for more details.
016//
017// You should have received a copy of the GNU General Public License
018// along with this program; if not, write to the Free Software
019// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
020*/
021package net.hydromatic.clapham.graph;
022
023import org.apache.batik.svggen.SVGGraphics2D;
024
025import java.awt.*;
026import java.awt.font.FontRenderContext;
027import java.awt.font.GlyphVector;
028import java.awt.geom.Point2D;
029
030/**
031 * TODO:
032*
033* @author jhyde
034* @version $Id: Chart.java 3 2009-05-11 08:11:57Z jhyde $
035* @since Aug 26, 2008
036*/
037public class Chart {
038    // Constants
039
040    public static final Color ITER_COLOR = Color.PINK;
041    public static final Color EPS_COLOR = Color.DARK_GRAY;  // was DarkKhaki
042    public static final Color OPT_COLOR = Color.DARK_GRAY;  // was DarkKhaki
043    public static final Color RERUN_COLOR = Color.GREEN;
044    public static final Color RERUN1_COLOR = Color.magenta; // was Fuschia
045    public static final Color N_NT_COLOR = Color.CYAN; // was PaleGreen
046
047    private final Font titleFont =
048        Font.decode("Serif").deriveFont(0, 14f);
049    private final Color titleColor = Color.BLACK;
050
051    // Default Settings
052
053    /** show the rectangles around the components */
054    private static final int   defaultComponentArcSize  = 16;
055
056    public final boolean showBorders = false;
057    private static final int   defaultComponentGapWidth         = 32;
058    private static final int   defaultComponentGapHeight        = 10;
059    private static final Font  defaultCharFont =
060        Font.decode("Serif").deriveFont(Font.PLAIN, 12f);
061
062    private static final int   defaultArrowSize                 = 3;
063    public static final BasicStroke STROKE1 = new BasicStroke(1f);
064    private static final Stroke defaultLineStroke = STROKE1;
065    private static final Color defaultLineColor           = Color.BLACK;
066    private static final int   defaultSymbolGapHeight   = 4;
067    private static final Color defaultCharColor                 = Color.BLACK;
068
069    // Initialize variables with default settings
070
071    // size of the arcs
072    public int   componentArcSize       = defaultComponentArcSize;
073
074    // gap between subcomponent size and actual size
075    public int   componentGapWidth      = defaultComponentGapWidth;
076
077    // gap between subcomponent size and actual size
078    public int   componentGapHeight     = defaultComponentGapHeight;
079
080    // font of the t and nt symbols
081    public Font  charFont                       = defaultCharFont;
082
083    // size of the arrows
084    public int   arrowSize                      = defaultArrowSize;
085
086    // thickness of the line
087    public Stroke lineStroke        = defaultLineStroke;
088
089    /** color of the line */
090    public Color lineColor          = Color.BLACK;
091
092    /** fontColor of the T and NT symbols */
093    public Color charColor                      = defaultCharColor;
094
095    /** gap between the line of the symbol and the font */
096    public int symbolGapHeight = defaultSymbolGapHeight;
097
098    public final int symbolGapWidth = 2;
099
100    /** the total size of the current Rule */
101    private Size        symbolSize                              = new Size(1,1);
102
103    private float xMin = Integer.MAX_VALUE;
104    private float yMin = Integer.MAX_VALUE;
105    private float xMax = Integer.MIN_VALUE;
106    private float yMax = Integer.MIN_VALUE;
107
108    /** needed to make the gap between the symbol and and the font possible */
109    public int   getFontHeight() {
110        return (int) (charFont.getSize2D() + symbolGapHeight);
111    }
112
113    /** where the drawing starts (X) */
114    private final int           beginningYCoordinate    = 40;
115
116    /** where the drawing starts (Y) */
117    public final int            beginningXCoordinate    = 50;
118
119    /** the graphics object from the EBNFForm on which the drawing takes place */
120    private final Grammar grammar;
121
122    final Graphics2D g;
123
124    public Chart(Grammar grammar, SVGGraphics2D graphics) {
125        this.grammar = grammar;
126        this.g = graphics;
127    }
128
129    public Dimension getDimension() {
130        assert xMin >= 0;
131        assert yMin >= 0;
132        return new Dimension((int) xMax + 10, (int) yMax + 10);
133    }
134
135    public int getStringWidth(Font font, String text) {
136        g.setFont(font);
137        final FontRenderContext context = g.getFontRenderContext();
138        final GlyphVector glyphVector =
139            this.charFont.layoutGlyphVector(
140                context, text.toCharArray(), 0, text.length(), 0);
141        double width = glyphVector.getVisualBounds().getWidth();
142        //System.out.println("width of " + text + " is " + width);
143        width *= 1.35;
144        return (int) width;
145    }
146
147    public void drawString(
148        String text, Font font, Color color, float x, float y)
149    {
150        g.setFont(font);
151        g.setColor(color);
152        g.drawString(text, x, y);
153    }
154
155    public void setCharFont(Font value) {
156        charFont = value;
157    }
158
159    public Font getCharFont() {
160        return charFont;
161    }
162
163    public void setCharColor(Color value) {
164        this.charColor = value;
165    }
166
167    public Color getCharColor() {
168        return charColor;
169    }
170
171    public void setArrowSize(int value) {
172        this.arrowSize = value;
173    }
174
175    public int getArrowSize() {
176        return arrowSize;
177    }
178
179    public void setSymbolGapHeight(int value) {
180        this.symbolGapHeight = value;
181    }
182
183    public int getSymbolGapHeight() {
184        return symbolGapHeight;
185    }
186
187    void setComponentGapHeight(int value) {
188        componentGapHeight = value;
189        final int fontHeight = getFontHeight();
190        if (componentGapHeight / 2 + fontHeight / 2
191            < Chart.defaultComponentArcSize) {
192            componentArcSize = (componentGapHeight + fontHeight) / 2;
193        } else {
194            componentArcSize = Chart.defaultComponentArcSize;
195        }
196        if (componentArcSize % 2 != 0) {
197            componentArcSize -= 1;
198        }
199    }
200
201    public int getComponentGapHeight() {
202        return componentGapHeight;
203    }
204
205    public void setComponentGapWidth(int value) {
206        componentGapWidth = value;
207    }
208
209    public int getComponentGapWidth() {
210        return componentGapWidth;
211    }
212
213    public Size getSymbolSize() {
214        return symbolSize;
215    }
216
217    public void restoreDefaultSettings() {
218        componentArcSize = defaultComponentArcSize;
219        componentGapWidth = defaultComponentGapWidth;
220        setComponentGapHeight(Chart.defaultComponentGapHeight);
221        charFont = defaultCharFont;
222        arrowSize = Chart.defaultArrowSize;
223        lineStroke = defaultLineStroke;
224        lineColor = defaultLineColor;
225        symbolGapHeight = defaultSymbolGapHeight;
226        charColor = defaultCharColor;
227    }
228
229    public void drawComponent(Symbol s) {
230        if (s == null) {
231            return;
232        }
233        symbolSize = new Size(
234            s.graph.graphSize.getWidth()
235                + beginningXCoordinate
236                + componentGapWidth * 2,
237            s.graph.graphSize.getHeight()
238                + beginningYCoordinate
239                + componentGapHeight * 2
240                + 5);
241//                      EbnfForm.Drawarea=new Bitmap(Node.getSymbolSize().getWidth(),Node.getSymbolSize().getHeight(),  System.Drawing.Imaging.PixelFormat.Format24bppRgb);
242
243        //decide either draw on visualized bitmap or record a metafile
244        g.setColor(Color.WHITE);
245        g.fillRect(
246            0,
247            0,
248            (int) symbolSize.getWidth(),
249            (int) symbolSize.getHeight());
250        g.setColor(Color.BLACK);
251        drawString(
252            s.name,
253            titleFont,
254            titleColor,
255            beginningXCoordinate - 20,
256            beginningYCoordinate - 30);
257        //g.DrawRectangle(new Pen(Color.Orange,2),p.X,p.Y+30,s.graph.graphSize.getWidth(),s.graph.graphSize.getHeight());
258        g.setStroke(lineStroke);
259        g.setColor(lineColor);
260        g.drawLine(
261            beginningXCoordinate
262                - componentGapWidth / 4
263                - componentArcSize / 2,
264            (int) s.graph.l.posLine.y,
265            beginningXCoordinate,
266            (int) s.graph.l.posLine.y);
267        Point2D.Float p =
268            new Point2D.Float(
269                beginningXCoordinate,
270                beginningYCoordinate - 30);
271        s.graph.l.drawComponents(this, p, s.graph.graphSize);
272//        final SizeMapper sizeMapper = new SizeMapper();
273//        s.graph.l.accept(sizeMapper);
274//        s.graph.r.accept(sizeMapper);
275        final Dimension dimension = getDimension();
276        ((SVGGraphics2D) g).setSVGCanvasSize(dimension);
277    }
278
279    public void calcDrawing() {
280        for (Symbol s : grammar.nonterminals) {
281            s.graph.graphSize = s.graph.l.calcSize(this);
282            s.graph.l.setWrapSize(this);
283            s.graph.l.calcPos(this, beginningYCoordinate);
284            if (Grammar.TRACE) {
285                System.out.println("\n\n" + s.graph.graphSize.toString());
286            }
287        }
288        if (Grammar.TRACE) {
289            grammar.printNodes(System.out);
290        }
291    }
292
293    // draws arrows for different directions
294    void drawArrow(
295        float x1,
296        float y1,
297        float x2,
298        float y2,
299        Grammar.Direction direction)
300    {
301        drawArrow(
302            (int) x1, (int) y1, (int) x2, (int) y2, direction);
303    }
304
305    private void drawArrow(
306        int x1,
307        int y1,
308        int x2,
309        int y2,
310        Grammar.Direction direction)
311    {
312        expandBounds(x1, y1);
313        expandBounds(x2, y2);
314        g.setColor(lineColor);
315        g.setStroke(lineStroke);
316        g.drawLine(x1, y1, x2, y2);
317        switch (direction) {
318        case RIGHT:
319            g.fillPolygon(
320                new int[] {x2, x2 - arrowSize * 2, x2 - arrowSize * 2},
321                new int[] {y2, y2 - arrowSize, y2 + arrowSize},
322                3);
323            break;
324        case UP:
325            g.fillPolygon(
326                new int[] {x2, x2 - arrowSize, x2 + arrowSize},
327                new int[] {y2, y2 + arrowSize * 2, y2 + arrowSize * 2},
328                3);
329            break;
330        case LEFT:
331            g.fillPolygon(
332                new int[] {x2, x2 + arrowSize * 2, x2 + arrowSize * 2},
333                new int[] {y2, y2 + arrowSize, y2 - arrowSize},
334                3);
335            break;
336        case DOWN:
337            g.fillPolygon(
338                new int[] {x2, x2 - arrowSize, x2 + arrowSize},
339                new int[] {y2, y2 - arrowSize * 2, y2 - arrowSize * 2},
340                3);
341            break;
342        }
343        }
344
345    private void expandBounds(float x, float y) {
346        if (x < xMin) {
347            xMin = x;
348        }
349        if (y < yMin) {
350            yMin = y;
351        }
352        if (x > xMax) {
353            xMax = x;
354        }
355        if (y > yMax) {
356            yMax = y;
357        }
358    }
359
360    void drawArc(
361        Stroke stroke,
362        Color color,
363        float x,
364        float y,
365        float width,
366        float height,
367        float startAngleF,
368        float arcAngle)
369    {
370        expandBounds(x - width, y - height);
371        expandBounds(x + width, y - height);
372        expandBounds(x - width, y + height);
373        expandBounds(x + width, y + height);
374        int startAngle = (int) startAngleF;
375        g.setStroke(stroke);
376        g.setColor(color);
377        g.drawArc(
378            (int) x,
379            (int) y,
380            (int) width,
381            (int) height,
382            startAngle == 180 ? 90
383                : startAngle == 90 ? 180
384                    : startAngle == 270 ? 0
385                        : startAngle == 0 ? 270
386                            : startAngle,
387            (int) arcAngle);
388    }
389
390    void drawArcCorner(
391        float x,
392        float y,
393        float arcSize,
394        float startAngle)
395    {
396        drawArc(
397            lineStroke,
398            lineColor,
399            x,
400            y,
401            arcSize,
402            arcSize,
403            startAngle,
404            90);
405    }
406
407    void drawArcCorner(
408        float x,
409        float y,
410        float startAngle)
411    {
412        drawArc(
413            lineStroke,
414            lineColor,
415            x,
416            y,
417            componentArcSize,
418            componentArcSize,
419            startAngle,
420            90);
421    }
422
423    void drawLine(
424        float x, float y, float x1, float y1)
425    {
426        expandBounds(x, y);
427        expandBounds(x1, y1);
428        g.setColor(lineColor);
429        g.setStroke(lineStroke);
430        g.drawLine((int) x, (int) y, (int) x1, (int) y1);
431    }
432
433    void drawRectangle(
434        Color color,
435        Stroke stroke,
436        float x,
437        float y,
438        float width,
439        float height)
440    {
441        expandBounds(x, y);
442        expandBounds(x + width, y + height);
443        g.setColor(color);
444        g.setStroke(stroke);
445        g.drawRect((int) x, (int) y, (int) width, (int) height);
446    }
447
448    interface NodeVisitor {
449        void visit(Node node);
450    }
451
452    static class SizeMapper implements NodeVisitor {
453        private int x1 = Integer.MAX_VALUE;
454        private int y1 = Integer.MAX_VALUE;
455        private int x2 = Integer.MIN_VALUE;
456        private int y2 = Integer.MIN_VALUE;
457
458        public void visit(Node node) {
459            foo(node.posBegin);
460            foo(node.posEnd);
461            foo(node.posLine);
462            node.visitChildren(this);
463        }
464
465        private void foo(Point2D.Float pos) {
466            x1 = Math.min(x1, (int) pos.x);
467            y1 = Math.min(y1, (int) pos.y);
468            x2 = Math.max(x2, (int) pos.x);
469            y2 = Math.max(y2, (int) pos.y);
470        }
471
472        Dimension getDimension() {
473            assert x1 >= 0;
474            assert y1 >= 0;
475            return new Dimension(x2, y2);
476        }
477    }
478}
479
480// End Chart.java