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    */
021    package net.hydromatic.clapham.graph;
022    
023    import org.apache.batik.svggen.SVGGraphics2D;
024    
025    import java.awt.*;
026    import java.awt.font.FontRenderContext;
027    import java.awt.font.GlyphVector;
028    import 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    */
037    public 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