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