Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / WPF
Article

Text On A Path in WPF

Rate me:
Please Sign up or sign in to vote.
4.81/5 (30 votes)
30 Oct 2008CPOL2 min read 123.3K   4.2K   78   29
A control that will display a text string on a specified geometry

Introduction

Currently, with Window Presentation Foundation (WPF), it is not easy to display a text string that will follow the path of any given geometry. WPF’s TextEffects provides transformation on individual characters, but trying to use this capability to get the text to follow an arbitrary geometry requires some additional work. Also, TextEffects is not available in Silverlight. With the control provided here, a given text string can be made to display on any 2D WPF geometry. The control can, for example, display text on an EllipseGeometry:

ellipse.PNG

A sample application and source code has been provided.

Background

To display text on a path, the fundamental problem that must be solved is to figure out how to translate and rotate each character of the string to fit the specified geometry. A big help in solving the problem is the GetFlattenedPathGeometry() method provided by the Geometry base class. This method returns a PathGeometry that approximates the geometry by a series of lines (PolyLineSegment and LineSegment). For example, the red lines below depict the flattened path of the path geometry (black):

flattenedpath.PNG

For a good overview of the WPF geometry classes, see MSDN.

The next step in the problem is to take the flattened path geometry and figure out how to transform the characters of the string to follow the flattened path. The figure below depicts the angle that must be calculated so the letter “A” will follow the flattened path. In TextOnAPath, the method ‘GetIntersectionPoints’ in the GeometryHelper class calculates the points along the flattened path where the characters intersect with the path. Knowing the intersection points, the angle (and translation) values can then be calculated.

flattenedpathWithText.PNG

Using the Code

The TextOnAPath control is used just like any normal UIElement in WPF. The XAML below will display the text string along the curved path, centered in a grid:

XML
<Window x:Class="TextOnAPathTestApp.Window1"
    xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
    xmlns:TextOnAPath="clr-namespace:TextOnAPath;assembly=TextOnAPath"
    Title="Test App" Height="500" Width="500">
    <Grid>
    <TextOnAPath:TextOnAPath FontSize="30" DrawPath="True" 
        Text="The quick brown fox jumped over the lazy hen.">
            <TextOnAPath:TextOnAPath.TextPath>
                <PathGeometry Figures="M0,0 C120,361 230.5,276.5 230.5,276.5 
                      L308.5,237.50001 C308.5,237.50001 419.5,179.5002 367.5,265.49993 
                      315.5,351.49966 238.50028,399.49924 238.50028,399.49924 L61.500017,
                      420.49911"/>
            </TextOnAPath:TextOnAPath.TextPath>
        </TextOnAPath:TextOnAPath>
    </Grid>
</Window>

ExampleWindow.PNG

Parameters for the TextOnAPath control:

  • Text (string): The string to be displayed. If the text string is longer than the geometry path, then text will be truncated.
  • TextPath (Geometry): The geometry for the text to follow.
  • DrawPath (Boolean): If true, draws the geometry below the text string.
  • DrawLinePath (Boolean): If true, draws the flattened path geometry below the text string.
  • ScaleTextPath (Boolean): If true, the geometry will be scaled to fit the size of the control.

Points of Interest

History

  • 13th October, 2008
    • Initial post
  • 29th October, 2008
    • Update to fix code that draws geometry
    • Provided link to Silverlight version of code

License

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


Written By
Software Developer (Senior)
United States United States
I work in the Bay Area primarily developing software on the Windows platform using C++, .NET/C#, WPF, and Silverlight.

Comments and Discussions

 
BugNot working in data templates Pin
xkip2-Mar-17 11:21
xkip2-Mar-17 11:21 
QuestionAdding to Canvas Pin
Hanvyj28-Jan-16 6:23
Hanvyj28-Jan-16 6:23 
AnswerRe: Adding to Canvas Pin
Hanvyj28-Jan-16 6:31
Hanvyj28-Jan-16 6:31 
QuestionHow to make this work from code? Pin
Kyudos9-Jan-14 12:13
Kyudos9-Jan-14 12:13 
AnswerRe: How to make this work from code? Pin
Kyudos9-Jan-14 12:30
Kyudos9-Jan-14 12:30 
AnswerRe: How to make this work from code? Pin
Member 111331657-Jun-18 22:03
Member 111331657-Jun-18 22:03 
GeneralRe: How to make this work from code? Pin
Kyudos27-Jun-18 14:52
Kyudos27-Jun-18 14:52 
I added an Alignment property:

C#
public TextAlignment Alignment
{
    get { return (TextAlignment)GetValue(AlignProperty); }
    set { SetValue(AlignProperty, value); }
}

public static readonly DependencyProperty AlignProperty = DependencyProperty.Register("Alignment", typeof(TextAlignment), typeof(TextOnAPath),
        new PropertyMetadata(TextAlignment.Left, new PropertyChangedCallback(OnAlignmentPropertyChanged), new CoerceValueCallback(CoerceAlignmentValue)));

static void OnAlignmentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    TextOnAPath textOnAPath = d as TextOnAPath;

    if (textOnAPath == null)
    {
        return;
    }

    if (e.NewValue == e.OldValue || e.NewValue == null)
    {
        if (textOnAPath._layoutPanel != null)
        {
            textOnAPath._layoutPanel.Children.Clear();
        }
        return;
    }

    textOnAPath.UpdateText();
    textOnAPath.Update();
}

static object CoerceAlignmentValue(DependencyObject d, object baseValue)
{
    return baseValue;
}


And then modified UpdateText():

C#
void UpdateText()
       {
           if (Text == null || FontFamily == null || FontWeight == null || FontStyle == null)
           {
               return;
           }

           // For reasons I haven't time to delve into, the first letter of the string gets inverted
           // when using centre or right alignment. As an ugly shortcut, adding a space to the start
           // and end keeps the alignment correct without the flipped letter
           int offset = 0;
           _textLength = 0.0;

           switch (Alignment)
           {
               case TextAlignment.Left:
                   offset = 0;
                   _textBlocks = new TextBlock[Text.Length];
                   _segmentLengths = new double[Text.Length];
                   break;

               case TextAlignment.Center:
                   offset = 1;
                   _textBlocks = new TextBlock[Text.Length + 2];
                   _segmentLengths = new double[Text.Length + 2];

                   TextBlock tc = new TextBlock();
                   tc.FontSize = this.FontSize;
                   tc.FontFamily = this.FontFamily;
                   tc.FontStretch = this.FontStretch;
                   tc.FontWeight = this.FontWeight;
                   tc.FontStyle = this.FontStyle;
                   tc.Text = new String(' ', 1);
                   tc.RenderTransformOrigin = new Point(0.0, 1.0);
                   tc.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));

                   _textBlocks[0] = tc;
                   _segmentLengths[0] = tc.DesiredSize.Width;
                   _textLength += _segmentLengths[0];

                   _textBlocks[Text.Length + 1] = tc;
                   _segmentLengths[Text.Length + 1] = tc.DesiredSize.Width;
                   _textLength += _segmentLengths[Text.Length + 1];
                   break;

               case TextAlignment.Right:
                   offset = 1;
                   _textBlocks = new TextBlock[Text.Length + 1];
                   _segmentLengths = new double[Text.Length + 1];
                   TextBlock tr = new TextBlock();
                   tr.FontSize = this.FontSize;
                   tr.FontFamily = this.FontFamily;
                   tr.FontStretch = this.FontStretch;
                   tr.FontWeight = this.FontWeight;
                   tr.FontStyle = this.FontStyle;
                   tr.Text = new String(' ', 1);
                   tr.RenderTransformOrigin = new Point(0.0, 1.0);
                   tr.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));

                   _textBlocks[0] = tr;
                   _segmentLengths[0] = tr.DesiredSize.Width;
                   _textLength += _segmentLengths[0];

                   break;
           }

           for (int i = 0; i < Text.Length; i++)
           {
               TextBlock t = new TextBlock();
               t.FontSize = this.FontSize;
               t.FontFamily = this.FontFamily;
               t.FontStretch = this.FontStretch;
               t.FontWeight = this.FontWeight;
               t.FontStyle = this.FontStyle;
               t.Text = new String(Text[i], 1);
               t.RenderTransformOrigin = new Point(0.0, 1.0);

               t.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));

               _textBlocks[i + offset] = t;
               _segmentLengths[i + offset] = t.DesiredSize.Width;
               _textLength += _segmentLengths[i];
           }
       }

QuestionAlignment of the text Pin
Neelendu Kumar24-Sep-13 3:29
Neelendu Kumar24-Sep-13 3:29 
AnswerRe: Alignment of the text Pin
Kyudos19-Dec-13 15:54
Kyudos19-Dec-13 15:54 
GeneralRe: Alignment of the text Pin
Kyudos22-Dec-13 12:59
Kyudos22-Dec-13 12:59 
AnswerRe: Alignment of the text Pin
Kyudos23-Dec-13 9:46
Kyudos23-Dec-13 9:46 
GeneralRe: Alignment of the text Pin
Neelendu Kumar24-Dec-13 1:18
Neelendu Kumar24-Dec-13 1:18 
QuestionAlternative for RenderTransformOrigin ...??? Pin
patelmayur15-Jun-12 3:55
patelmayur15-Jun-12 3:55 
AnswerRe: Alternative for RenderTransformOrigin ...??? Pin
lneir19-Jun-12 19:08
lneir19-Jun-12 19:08 
Questiondraw text within any path? Pin
ASPUIPAB29-Oct-11 11:37
ASPUIPAB29-Oct-11 11:37 
AnswerRe: draw text within any path? Pin
lneir30-Oct-11 7:00
lneir30-Oct-11 7:00 
GeneralRe: draw text within any path? Pin
Member 83727423-Nov-11 7:10
Member 83727423-Nov-11 7:10 
GeneralRe: draw text within any path? Pin
lneir3-Nov-11 7:26
lneir3-Nov-11 7:26 
AnswerText on EllipseGeometry Pin
ANTONELLO7221-May-09 0:57
ANTONELLO7221-May-09 0:57 
General'hen' -&gt; 'dog' Pin
NigelHome20-Oct-08 21:26
NigelHome20-Oct-08 21:26 
GeneralRe: 'hen' -&gt; 'dog' Pin
lneir21-Oct-08 5:26
lneir21-Oct-08 5:26 
GeneralRe: 'hen' -&gt; 'dog' Pin
Richard Deeming22-Oct-08 4:06
mveRichard Deeming22-Oct-08 4:06 
GeneralRe: 'hen' -&gt; 'dog' Pin
User 12301622-Oct-08 8:16
User 12301622-Oct-08 8:16 
GeneralRe: 'hen' -&gt; 'dog' Pin
Member 46471624-Nov-08 23:35
Member 46471624-Nov-08 23:35 
GeneralPath Flattening Pin
KevinAG20-Oct-08 14:42
KevinAG20-Oct-08 14:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.