Click here to Skip to main content
15,896,606 members
Articles / Desktop Programming / Windows Forms

GMarkupLabel - A C# Windows Forms control to display XML-formatted text

Rate me:
Please Sign up or sign in to vote.
4.95/5 (77 votes)
25 Nov 2008CPOL9 min read 132.5K   1.1K   166  
A framework, and a WinForms control which enables .NET 2.0 users to visualize XML-formatted rich text.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;

namespace GFramework.View.Text
{
    /// <summary>
    /// A line of text which children are GChar instances.
    /// </summary>
    internal class GTextLine
    {
        #region Constructor

        public GTextLine()
        {
            m_Words = new LinkedList<GWord>();
            m_Decorations = new List<GFontDecorationInfo>();
        }

        #endregion

        #region Implementation

        internal void Paint(GTextViewPaintContext context)
        {
            LinkedListNode<GWord> currNode = m_Words.First;
            GWord currWord;
            RectangleF clip = context.m_DeviceContext.ClipBounds;
            m_Decorations.Clear();

            while (currNode != null)
            {
                currWord = currNode.Value;

                RectangleF bounds = new RectangleF(currWord.m_Location, currWord.m_Metric.Size);
                if (clip.IntersectsWith(bounds) && (currWord.IsWhitespace == false))
                {
                    context.m_DeviceContext.PaintWord(currWord);
                }

                currNode = currNode.Next;

                ProcessDecoration(currWord, currNode == null);
            }

            //paint all decorations
            int count = m_Decorations.Count;
            GPen pen = new GPen();

            for (int i = 0; i < count; i++)
            {
                GFontDecorationInfo info = m_Decorations[i];
                pen.Width = (int)(info.Thickness + .5F);

                foreach (GLine line in info.Lines)
                {
                    pen.Color = line.Color;
                    PointF start = new PointF(line.StartX, line.StartY + info.Offset);
                    PointF end = new PointF(line.EndX, line.EndY + info.Offset);
                    context.m_DeviceContext.DrawLine(pen, start, end);
                }
            }
        }

        internal void Layout(GTextViewLayoutContext context)
        {
            //remove any whitespaces on left and right
            Trim();

            //no need to perform operations of word count is zero
            int count = m_Words.Count;
            if (count == 0)
            {
                return;
            }

            //calculate baseline position and line height
            CalculateMetrics(context);
            //remember the Y coordinate of the line
            m_Top = context.Y;

            //determine the X-coordinate of the firts word
            float x = GetLineStart(context);
            float maxRight = context.AvailableSize.Width - x - context.Right;
            float y;

            LinkedListNode<GWord> firstNode = m_Words.First;
            LinkedListNode<GWord> node = firstNode;
            GWord currWord = node.Value;

            while (node != null)
            {
                currWord = node.Value;
                node = node.Next;

                //subtract the render padding on the left
                x -= currWord.m_Metric.Padding.Left;
                //the y coordinate of the word is baseline minus word's EmHeight
                y = context.Y + (m_Baseline - currWord.m_FontMetric.TextMetric.tmAscent);

                //remember the calculated location
                currWord.m_Location = new PointF(x, y);

                //advance the x value
                x += currWord.m_Metric.Size.Width - currWord.m_Metric.Padding.Right;
                x += m_SpaceToDistribute;
            }

            //advance the Y value of the context with the height of the line
            context.Y += m_WordsHeight;
        }
        internal void CalculateMetrics(GTextViewLayoutContext context)
        {
            LinkedListNode<GWord> currNode = m_Words.First;
            GWord currWord;

            while (currNode != null)
            {
                currWord = currNode.Value;
                currNode = currNode.Next;

                m_WordsWidth += currWord.m_Metric.BlackBox.Width;
                m_WordsHeight = Math.Max(m_WordsHeight, currWord.m_Metric.Size.Height);

                m_Baseline = Math.Max(m_Baseline, currWord.m_FontMetric.TextMetric.tmAscent);
            }
        }
        internal float GetLineStart(GTextViewLayoutContext context)
        {
            m_SpaceToDistribute = 0F;
            float x = context.X;
            float lineWidth;

            switch (context.Align)
            {
                case ParagraphAlign.Right:
                    float right = context.AvailableSize.Width - context.Right;
                    x = Math.Max(x, right - m_WordsWidth);
                    break;
                case ParagraphAlign.Center:
                    lineWidth = context.AvailableSize.Width - context.X - context.Right;
                    x += (lineWidth - m_WordsWidth) / 2F;
                    break;
                case ParagraphAlign.Justify:
                    //calculate the offset to apply to each space to accomodate the Justify setting
                    if (m_IsLastLine)
                    {
                        break;
                    }
                    lineWidth = context.AvailableSize.Width - context.X - context.Right;
                    m_SpaceToDistribute = (lineWidth - m_WordsWidth) / (m_Words.Count - 1);
                    m_SpaceToDistribute = Math.Max(0, m_SpaceToDistribute);
                    break;
            }

            return x;
        }
        internal void AddWord(GWord word)
        {
            m_Words.AddLast(word);
            word.m_Owner = this;
        }
        internal void Trim()
        {
            LinkedListNode<GWord> node = m_Words.First;
            GWord word;

            //trim left
            while (node != null)
            {
                word = node.Value;
                node = node.Next;

                if (word.IsWhitespace)
                {
                    m_Words.RemoveFirst();
                }
                else
                {
                    break;
                }
            }

            node = m_Words.Last;
            //trim right
            while (node != null)
            {
                word = node.Value;
                node = node.Previous;

                if (word.IsWhitespace)
                {
                    m_Words.RemoveLast();
                }
                else
                {
                    break;
                }
            }
        }

        internal void ProcessDecoration(GWord word, bool isLast)
        {
            if (word.m_Style.m_Font.Underline)
            {
                Underline(word);
                if (isLast)
                {
                    EndUnderline();
                }
            }
            else
            {
                EndUnderline();
            }
        }
        internal void Underline(GWord word)
        {
            if (string.IsNullOrEmpty(word.m_Text))
            {
                return;
            }

            if (m_CurrDecoration != null)
            {
                m_CurrDecoration.Thickness = Math.Max(word.m_FontMetric.DecorationThickness, m_CurrDecoration.Thickness);
                m_CurrDecoration.Offset = Math.Max(word.m_FontMetric.UnderlinePosition, m_CurrDecoration.Offset);

                //update lines
                GLine currLine = m_CurrDecoration.Lines.Last.Value;
                //add new line with new color
                if (currLine.Color != word.m_Style.m_Brush.Color)
                {
                    GLine newLine = new GLine(currLine);
                    newLine.StartX = word.m_Location.X + word.m_Metric.Padding.Left;
                    newLine.EndX = newLine.StartX + word.m_Metric.BlackBox.Width;
                    newLine.Color = word.m_Style.m_Brush.Color;

                    m_CurrDecoration.Lines.AddLast(newLine);
                }
                else
                {
                    currLine.EndX = word.m_Location.X + word.m_Metric.Size.Width - word.m_Metric.Padding.Right;
                }
            }
            else
            {
                m_CurrDecoration = new GFontDecorationInfo();
                m_CurrDecoration.Thickness = word.m_FontMetric.DecorationThickness;
                m_CurrDecoration.Offset = word.m_FontMetric.UnderlinePosition;

                GLine newLine = new GLine();
                newLine.StartX = word.m_Location.X + word.m_Metric.Padding.Left;
                newLine.EndX = newLine.StartX + word.m_Metric.BlackBox.Width;
                newLine.StartY = (int)(m_Top + m_Baseline);
                newLine.EndY = newLine.StartY;
                newLine.Color = word.m_Style.m_Brush.Color;

                m_CurrDecoration.Lines.AddLast(newLine);
            }
        }
        internal void EndUnderline()
        {
            if (m_CurrDecoration == null)
            {
                return;
            }

            m_Decorations.Add(m_CurrDecoration);
            m_CurrDecoration = null;
        }

        #endregion

        #region Fields

        //using linked list for dynamic sequential storage
        internal LinkedList<GWord> m_Words;
        internal List<GFontDecorationInfo> m_Decorations;
        internal GFontDecorationInfo m_CurrDecoration;
        internal float m_WordsWidth;
        internal float m_WordsHeight;
        internal float m_SpaceToDistribute;
        internal float m_Top;
        internal float m_Baseline;
        //determines whether the line is the last one in the text block
        internal bool m_IsLastLine;

        #endregion
    }
}

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
Team Leader Telerik Corp.
Bulgaria Bulgaria
.NET & C# addicted. Win8 & WinRT enthusiast and researcher @Telerik.

Comments and Discussions