Click here to Skip to main content
15,895,746 members
Articles / Programming Languages / C#

Visual AST for ANTLR Generated Parser Output

Rate me:
Please Sign up or sign in to vote.
4.92/5 (14 votes)
17 May 2012CPOL13 min read 90.8K   2.2K   41  
Build a visual AST from ANTLR parser
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace EntMapping.Utility
{
    public class VisualAST
    {
        private static Font NodeTextFont = new Font("Verdana", 8f);
        private static SizeF MinimumNodeSize = new SizeF(32, 28);
        private static Size NodeGapping = new Size(4, 32);
        private static Dictionary<string, Pen> Pens = new Dictionary<string, Pen>();

        public IASTTreeNode ASTTreeNode { get; private set; }

        public VisualAST(IASTTreeNode astTreeNode)
        {
            ASTTreeNode = astTreeNode;
        }

        private static Bitmap CreateNodeImage(Size size, string text, Font font)
        {
            Bitmap img = new Bitmap(size.Width, size.Height);
            using (var g = Graphics.FromImage(img))
            {
                g.SmoothingMode = SmoothingMode.HighQuality;
                var rcl = new Rectangle(1, 1, img.Width - 2, img.Height - 2);
                g.FillRectangle(Brushes.White, rcl);

                LinearGradientBrush linearBrush = new LinearGradientBrush(rcl, Color.LightBlue, Color.White, LinearGradientMode.ForwardDiagonal);
                g.DrawEllipse(NodeBorderPen, rcl);
                g.FillEllipse(linearBrush, rcl);
                linearBrush.Dispose();

                var sizeText = g.MeasureString(text, font);
                g.DrawString(text, font, Brushes.Black, Math.Max(0, (size.Width - sizeText.Width) / 2), Math.Max(0, (size.Height - sizeText.Height) / 2));
            }
            return img;
        }

        private static Pen ConnectionPen
        {
            get
            {
                string penName = "ConnectionPen";
                if (!Pens.ContainsKey(penName))
                {
                    Pens.Add(penName, new Pen(Brushes.Black, 1) { EndCap = LineCap.ArrowAnchor, StartCap = LineCap.Round });
                }
                return Pens[penName];
            }
        }

        private static Pen NodeBorderPen
        {
            get
            {
                string penName = "NodeBorderPen";
                if (!Pens.ContainsKey(penName))
                {
                    Pens.Add(penName, new Pen(Color.Silver, 1));
                }
                return Pens[penName];
            }
        }

        public Image Draw()
        {
            int center;
            Image image = Draw(this.ASTTreeNode, out center);
            return image;
        }

        private Image Draw(IASTTreeNode astTreeNode, out int center)
        {
            var nodeText = astTreeNode.Text;
            var nodeSize = TextMeasurer.MeasureString("*" + nodeText + "*", NodeTextFont);
            nodeSize.Width = Math.Max(MinimumNodeSize.Width, nodeSize.Width);
            nodeSize.Height = Math.Max(MinimumNodeSize.Height, nodeSize.Height);

            var childCentres = new int[astTreeNode.Count];
            var childImages = new Image[astTreeNode.Count];
            var childSizes = new Size[astTreeNode.Count];

            var enumerator = astTreeNode.Children.GetEnumerator(); ;
            int i = 0;
            while (enumerator.MoveNext())
            {
                var currentNode = enumerator.Current;
                var lCenter = 0;
                childImages[i] = Draw(currentNode, out lCenter);
                childCentres[i] = lCenter;
                if (childImages[i] != null)
                {
                    childSizes[i] = childImages[i] != null ? childImages[i].Size : new Size();
                }
                ++i;
            }

            // draw current node and it's children
            var under = childImages.Any(nodeImg => nodeImg != null);// if true the current node has childs
            var maxHeight = astTreeNode.Count > 0 ? childSizes.Max(c => c.Height) : 0;
            var totalFreeWidth = astTreeNode.Count > 0 ? NodeGapping.Width * (astTreeNode.Count - 1) : NodeGapping.Width;
            var totalChildWidth = childSizes.Sum(s => s.Width);

            var nodeImage = CreateNodeImage(nodeSize.ToSize(), nodeText, NodeTextFont);

            var totalSize = new Size
            {
                Width = Math.Max(nodeImage.Size.Width, totalChildWidth) + totalFreeWidth,
                Height = nodeImage.Size.Height + (under ? maxHeight + NodeGapping.Height : 0)
            };

            var result = new Bitmap(totalSize.Width, totalSize.Height);
            var g = Graphics.FromImage(result);
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.FillRectangle(Brushes.White, new Rectangle(new Point(0, 0), totalSize));

            var left = (totalSize.Width - nodeImage.Width) / 2;

            g.DrawImage(nodeImage, left, 0);

            center = Math.Max(totalSize.Width / 2, (nodeImage.Width + NodeGapping.Width) / 2);

            var fromLeft = 0;

            for (int j = 0; j < astTreeNode.Count; ++j)
            {
                float x1 = center;
                float y1 = nodeImage.Height;
                float y2 = nodeImage.Height + NodeGapping.Height;
                float x2 = fromLeft + childCentres[j];
                var h = y2 - y1;
                var w = x1 - x2;
                var childImg = childImages[j];
                if (childImg != null)
                {
                    g.DrawImage(childImg, fromLeft, nodeImage.Size.Height + NodeGapping.Height);
                    fromLeft += childImg.Width + NodeGapping.Width; // Prepare next child left starting point 
                    var points1 = new List<PointF>
                                  {
                                      new PointF(x1, y1),
                                      new PointF(x1 - w/6, y1 + h/3.5f),
                                      new PointF(x2 + w/6, y2 - h/3.5f),
                                      new PointF(x2, y2),
                                  };
                    g.DrawCurve(ConnectionPen, points1.ToArray(), 0.5f);
                }

                childImages[j].Dispose(); // Release child image as it aleady drawn on parent node's surface 
            }

            g.Dispose();

            return result;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder Software Force
Hong Kong Hong Kong
I am always interested in finding innovative ways for building better applications and founded a technology company since 2003. Welcome to exchange any idea with you and if I am not too busy before deadline of projects, I will reply your emails. Also, if you willing to pay for consulting works and customized software development, you can leave me message.

Comments and Discussions