Skip to content
polymeris edited this page Jul 4, 2013 · 4 revisions

The CurvedText class offers the ability to text arranged in a circular arc. The interface is similar to SFML's native Text class, but the constructor takes two additional parameters:

  • radius: The radius of the circle this text is arranged upon
  • spacing: Extra character spacing, in pixels

You can also set the text to be "center justified" (put the origin at the middle of the arc), by specifying CurvedText.Centered = true.

How it works

Roughly, the same way as sf::Text. In the UpdateGeometry method, a VertexArray of Quads is created, to be textured by the Draw method. The main difference is the X coordinate of the vertices is rotated around a point a distance Radius below the baseline of the text. This is done incrementally, saving the Transform for each previous character.

            float angle = 2f * (float)Math.Atan((x - previousX) / 2f / Radius)
                * 180f / (float)Math.PI;
            angleCovered += angle;

            transform.Rotate(angle, x0 - g.Bounds.Width / 2f, Radius / 2f);

            Vector2f topLeft     = new Vector2f(x0, y0);
            Vector2f bottomLeft  = new Vector2f(x0, y1);
            Vector2f topRight    = new Vector2f(x1, y0);
            Vector2f bottomRight = new Vector2f(x1, y1);

            topLeft     = transform.TransformPoint(topLeft);
            topRight    = transform.TransformPoint(topRight);
            bottomLeft  = transform.TransformPoint(bottomLeft);
            bottomRight = transform.TransformPoint(bottomRight);

###Known bugs & limitations

  • Centered does not work as expected for multi-line text.
  • Just like SFML.Graphics.Text changing the displayed string or geometry of the Text is an expensive operation.

Full code below. Please let me know through the forums if you find any bugs or improve the class.

/// Copyright 2013, Camilo Polymeris. License: ZLIB/PNG (Same as SFML)
public class CurvedText : Transformable, Drawable
{
    public CurvedText(
        string str,
        float radius,
        float spacing,
        Font font,
        uint charSize) : this(str, radius, font, charSize)
    {
        Spacing = spacing;
    }

    public CurvedText(
        string str,
        float radius,
        Font font = null,
        uint charSize = 30)
    {
        vertices = new VertexArray(PrimitiveType.Quads);
        DisplayedString = str;
        CharacterSize = charSize;
        Radius = radius;
        Spacing = 0;
        Color = Color.Black;
        Font = font;
    }

    public void Draw(RenderTarget target, RenderStates states)
    {
        if (Font == null)
            return;
        states.Transform *= Transform;
        if (Centered)
            states.Transform *= centerTransform;
        states.Texture = Font.GetTexture(CharacterSize);
        target.Draw(vertices, states);
    }

    protected void UpdateGeometry()
    {
        if (font == null)
            return;
        vertices.Clear();
        float hspace = Font.GetGlyph(' ', CharacterSize, true).Advance;
        float vspace = Font.GetLineSpacing(CharacterSize);
        float previousX = 0;
        float x = 0;
        float y = CharacterSize;
        char previousChar = '\\0';
        float angleCovered = 0;

        Transform transform = Transform.Identity;

        foreach (char c in DisplayedString)
        {
            x += Font.GetKerning(previousChar, c, CharacterSize) + Spacing;
            previousChar = c;

            switch (c)
            {
                case ' ':
                    x += hspace;
                    continue;
                case '\t':
                    x += 4 * hspace;
                    continue;
                case '\n':
                    y += vspace;
                    previousX = 0;
                    angleCovered = 0;
                    transform = Transform.Identity;
                    x = 0;
                    continue;
                case '\v':
                    y += vspace * 4;
                    continue;
            }

            Glyph g = Font.GetGlyph(c, CharacterSize, false);
            float x0 = x  + g.Bounds.Left;
            float y0 = y  + g.Bounds.Top;
            float x1 = x0 + g.Bounds.Width;
            float y1 = y0 + g.Bounds.Height;
            float u0 =      g.TextureRect.Left;
            float v0 =      g.TextureRect.Top;
            float u1 = u0 + g.TextureRect.Width;
            float v1 = v0 + g.TextureRect.Height;

            float angle = 2f * (float)Math.Atan((x - previousX) / 2f / Radius)
                * 180f / (float)Math.PI;
            angleCovered += angle;

            transform.Rotate(angle, x0 - g.Bounds.Width / 2f, Radius / 2f);

            Vector2f topLeft     = new Vector2f(x0, y0);
            Vector2f bottomLeft  = new Vector2f(x0, y1);
            Vector2f topRight    = new Vector2f(x1, y0);
            Vector2f bottomRight = new Vector2f(x1, y1);

            topLeft     = transform.TransformPoint(topLeft);
            topRight    = transform.TransformPoint(topRight);
            bottomLeft  = transform.TransformPoint(bottomLeft);
            bottomRight = transform.TransformPoint(bottomRight);

            vertices.Append(new Vertex(topLeft,     Color, new Vector2f(u0, v0)));
            vertices.Append(new Vertex(topRight,    Color, new Vector2f(u1, v0)));
            vertices.Append(new Vertex(bottomRight, Color, new Vector2f(u1, v1)));
            vertices.Append(new Vertex(bottomLeft,  Color, new Vector2f(u0, v1)));

            previousX = x;
            x+= g.Advance;
        }

        centerTransform = Transform.Identity;
        centerTransform.Rotate(-angleCovered / 2f, 0, Radius);
        centerTransform.Translate(0, -Radius / 2f);
        Color = color;
    }

    public float Radius
    {
        get { return radius; }
        set
        {
            radius = value;
            UpdateGeometry();
        }
    }

    public string DisplayedString
    {
        get { return displayedString; }
        set
        {
            displayedString = value;
            UpdateGeometry();
        }
    }

    public Font Font
    {
        get { return font; }
        set
        {
            font = value;
            UpdateGeometry();
        }
    }

    public uint CharacterSize
    {
        get { return characterSize; }
        set
        {
            characterSize = value;
            UpdateGeometry();
        }
    }

    public Color Color
    {
        get { return color; }
        set
        {
            color = value;
            for (uint i = 0; i < vertices.VertexCount; i++)
            {
                Vertex v = vertices[i];
                v.Color = color;
                vertices[i] = v;
            }
        }
    }

    public float Spacing
    {
        get { return spacing; }
        set
        {
            spacing = value;
            UpdateGeometry();
        }
    }

    public bool Centered
    {
        get { return centered; }
        set
        {
            centered = value;
        }
    }

    VertexArray vertices;
    float radius;
    string displayedString;
    Font font;
    uint characterSize;
    Color color;
    float spacing;
    bool centered;
    Transform centerTransform;
}
Clone this wiki locally