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