Click here to Skip to main content
Click here to Skip to main content

Text on Path with VB.NET

, 2 May 2006
Rate this:
Please Sign up or sign in to vote.
A VB.NET class for drawing text on a path.

Sample Image - Text_on_Path_with_VBNET.jpg

Sample Image - Text_on_Path_with_VBNET.jpg

Introduction

I searched the internet for a long time for some algorithm to display a "Text on a Path", but I only fond the Batik SVG Toolkit. It's a Java project for displaying SVG. SVG has the ability to display "Text on a Path", but I only needed the Text on Path functionality.

So, I started recollecting my old math education, and coded the first lines to calculate the angle of defined position on a path.

Do you remember a²+b²=c²? That is the base to solve this problem. You need two points on a straight line.

a²+b²=c²

Private Function GetAngle(ByVal _point1 As PointF, _
                 ByVal _point2 As PointF) As Decimal
    Dim c As Decimal
    c = Math.Sqrt((_point2.X - _point1.X) ^ 2 + _
                  (_point2.Y - _point1.Y) ^ 2)
    'Oh yeah good old math a²+b²=c²

    If c = 0 Then
        Return 0
    End If 'We must change the side where the triangle is
    If _point1.X > _point2.X Then 
        Return Math.Asin((_point1.Y - _point2.Y) / c) * _
                                 180 / Math.PI - 180
    Else
        Return Math.Asin((_point2.Y - _point1.Y) / c) * _
                                       180 / Math.PI
    End If

End Function

Using the code

You can use the TextOnPath class to get a bitmap with the text on the given path.

The path must be set as PathData. You can also the the TextPathPosition (Under, Center, Above) and the align the text (Left, Center, Right).

Dim _points(5) As PointF
Dim _top As TextOnPath.TextOnPath = New TextOnPath.TextOnPath
Dim _gp As GraphicsPath = New GraphicsPath

_points(0) = New PointF(81, 183)
_points(1) = New PointF(321, 305)
_points(2) = New PointF(333, 622)
_points(3) = New PointF(854, 632)
_points(4) = New PointF(864, 153)
_points(5) = New PointF(562, 224)
_gp.AddCurve(_points)

_top.PathDataTOP = _gp.PathData
_top.FontTOP = New Font("Microsoft Sans Serif", _
                        36, FontStyle.Regular)
_top.FillColorTOP = Color.Black
_top.ColorTOP = Color.White
_top.TextTOP = "Text on path goes around a path"
_top.ShowPath = True
_top.PathAlignTOP = TextOnPath.PathAlign.Center
_top.LetterSpacePercentage = 100
_top.TextPathPosition = TextOnPath.TextPosition.CenterPath

Dim oBitmap as Bitmap 
oBitmap = _top.TextOnPathBitmap

You can also get the new path data as an SVG string, so you can use it in HTML with Adobe SVG Viewer, or in Corel Draw or Adobe Illustrator.

Dim _svg As String
_svg = _top.GetSVG(1024, 768)

Points of interest

If someone has ideas to optimize this code, please contact me, and sorry for the bad English and the short description. This is my first article, and I'm still learning.

History

  • Version 1.0 released.
  • Version 1.01 released (PathPoint calculation optimized).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Alexander Seel
Software Developer
Germany Germany
No Biography provided

Comments and Discussions

 
QuestionQuick look does not reveal how to modify 'starting postion' of first char.... Pinmemberboblogan3-Aug-12 3:33 
Questiondraw text within any path? PinmemberASPUIPAB29-Oct-11 11:39 
GeneralC# version and with optimization. Pinmemberguidebee9-Jul-09 17:15 
It provide as Graphics extension methods.
with similar syntax as normal MeasureString and DrawString.
 
public static RectangleF[] MeasureString(this Graphics graphics, string s, Font font, Brush brush, TextPathAlign textPathAlign, TextPathPosition textPathPosition, int letterSpace,float rotateDegree, GraphicsPath graphicsPath);
 
public static void DrawString(this Graphics graphics, string s, Font font, Brush brush, TextPathAlign textPathAlign, TextPathPosition textPathPosition, int letterSpace,float rotateDegree, GraphicsPath graphicsPath)
 
sample code
// Adds two Bezier curves.
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
Point[] myArray =
{
new Point(20, 100),
new Point(40, 75),
new Point(60, 125),
new Point(80, 100),
new Point(100, 50),
new Point(120, 150),
new Point(400, 400)
};
 
// Create the path and add the curves.
GraphicsPath myPath = new GraphicsPath();
myPath.AddBeziers(myArray);
 
// Draw the path to the screen.
Pen myPen = new Pen(Color.Black, 5);
e.Graphics.DrawPath(myPen, myPath);
 
System.Drawing.RectangleF[] regions = e.Graphics.MeasureString("Text on Path", new Font(FontFamily.GenericSerif, 24), new SolidBrush(Color.Red), TextPathAlign.Center, TextPathPosition.CenterPath, 100, 0, myPath);
foreach (var region in regions)
{
e.Graphics.FillRectangle(new SolidBrush(Color.GreenYellow), region);
}
e.Graphics.DrawString("Text on Path", new Font(FontFamily.GenericSerif, 24), new SolidBrush(Color.Red), TextPathAlign.Center, TextPathPosition.CenterPath, 100, 0, myPath);


 
GraphicsExtenstion class
 

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
 
namespace WindowsFormsApplication6
{
 
public enum TextPathAlign
{
Left = 0,
Center = 1,
Right = 2
}
 
public enum TextPathPosition
{
OverPath = 0,
CenterPath = 1,
UnderPath = 2
}
 
public static class GraphicsExtension
{
private static readonly TextOnPath TEXT_ON_PATH = new TextOnPath();
 
public static RectangleF[] MeasureString(this Graphics graphics, string s, Font font, Brush brush, GraphicsPath graphicsPath)
{
return MeasureString(graphics, s, font, brush, TextPathAlign.Left, TextPathPosition.CenterPath, 100, graphicsPath);
}
 
public static RectangleF[] MeasureString(this Graphics graphics, string s, Font font, Brush brush, TextPathAlign textPathAlign, TextPathPosition textPathPosition, GraphicsPath graphicsPath)
{
return MeasureString(graphics, s, font, brush, textPathAlign, textPathPosition, 100, graphicsPath);
}
 
public static void DrawString(this Graphics graphics, string s, Font font, Brush brush, GraphicsPath graphicsPath)
{
DrawString(graphics, s, font, brush, TextPathAlign.Left, TextPathPosition.CenterPath, 100, graphicsPath);
}
 
public static void DrawString(this Graphics graphics, string s, Font font, Brush brush, TextPathAlign textPathAlign, TextPathPosition textPathPosition, GraphicsPath graphicsPath)
{
DrawString(graphics, s, font, brush, textPathAlign, textPathPosition, 100, graphicsPath);
}
 
public static RectangleF[] MeasureString(this Graphics graphics, string s, Font font, Brush brush, TextPathAlign textPathAlign, TextPathPosition textPathPosition, int letterSpace, GraphicsPath graphicsPath)
{
return MeasureString(graphics, s, font, brush, textPathAlign, textPathPosition, 100, 0,graphicsPath);
}
 
public static void DrawString(this Graphics graphics, string s, Font font, Brush brush, TextPathAlign textPathAlign, TextPathPosition textPathPosition, int letterSpace, GraphicsPath graphicsPath)
{
DrawString(graphics, s, font, brush, textPathAlign, textPathPosition, 100,0, graphicsPath);
}
 
public static RectangleF[] MeasureString(this Graphics graphics, string s, Font font, Brush brush, TextPathAlign textPathAlign, TextPathPosition textPathPosition, int letterSpace,float rotateDegree, GraphicsPath graphicsPath)
{
TEXT_ON_PATH.Text = s;
TEXT_ON_PATH.Font = font;
TEXT_ON_PATH.FillColorTop = brush;
TEXT_ON_PATH.TextPathPathPosition = textPathPosition;
TEXT_ON_PATH.TextPathAlignTop = textPathAlign;
TEXT_ON_PATH.PathDataTop = graphicsPath.PathData;
TEXT_ON_PATH.LetterSpacePercentage = letterSpace;
TEXT_ON_PATH._graphics = graphics;
TEXT_ON_PATH._graphicsPath = graphicsPath;
TEXT_ON_PATH._measureString = true;
TEXT_ON_PATH._rotateDegree = rotateDegree;
TEXT_ON_PATH.DrawTextOnPath();
return TEXT_ON_PATH._regionList.ToArray();
}
 
public static void DrawString(this Graphics graphics, string s, Font font, Brush brush, TextPathAlign textPathAlign, TextPathPosition textPathPosition, int letterSpace,float rotateDegree, GraphicsPath graphicsPath)
{
TEXT_ON_PATH.Text = s;
TEXT_ON_PATH.Font = font;
TEXT_ON_PATH.FillColorTop = brush;
TEXT_ON_PATH.TextPathPathPosition = textPathPosition;
TEXT_ON_PATH.TextPathAlignTop = textPathAlign;
TEXT_ON_PATH.PathDataTop = graphicsPath.PathData;
TEXT_ON_PATH.LetterSpacePercentage = letterSpace;
TEXT_ON_PATH._graphics = graphics;
TEXT_ON_PATH._graphicsPath = graphicsPath;
TEXT_ON_PATH._measureString = false;
TEXT_ON_PATH._rotateDegree = rotateDegree;
TEXT_ON_PATH.DrawTextOnPath();

}
 
}
 

internal class TextOnPath
{
private PathData _pathdata;
private string _text;
private Font _font;
private Color _color = Color.Black;
private Brush _fillBrush = new SolidBrush(Color.Black);
private TextPathAlign _pathalign = TextPathAlign.Center;
private int _letterspacepercentage = 100;
private TextPathPosition _textPathPathPosition = TextPathPosition.CenterPath;
public Exception LastError;
internal Graphics _graphics;
internal GraphicsPath _graphicsPath;
internal bool _measureString;
internal List<RectangleF> _regionList = new List<RectangleF>();
internal float _rotateDegree;
public TextPathPosition TextPathPathPosition
{
get { return _textPathPathPosition; }
set { _textPathPathPosition = value; }
}
public PathData PathDataTop
{
get { return _pathdata; }
set { _pathdata = value; }
}
 
public string Text
{
get { return _text; }
set { _text = value; }
}
 
public Font Font
{
get { return _font; }
set { _font = value; }
}
 
public Color Color
{
get { return _color; }
set { _color = value; }
}
 
public Brush FillColorTop
{
get { return _fillBrush; }
set { _fillBrush = value; }
}
 
public TextPathAlign TextPathAlignTop
{
get { return _pathalign; }
set { _pathalign = value; }
}
 
public int LetterSpacePercentage
{
get { return _letterspacepercentage; }
set { _letterspacepercentage = value; }
}
public void DrawTextOnPath(PathData pathdata, string text, Font font, Color color, Brush fillcolor, int letterspacepercentage)
{
 
_pathdata = pathdata;
_text = text;
_font = font;
_color = color;
_fillBrush = fillcolor;
_letterspacepercentage = letterspacepercentage;
 
DrawTextOnPath();
}
 

public void DrawTextOnPath()
{
PointF[] tmpPoints;
PointF[] points = new PointF[25001];
int count = 0;
GraphicsPath gp = new GraphicsPath(_pathdata.Points, _pathdata.Types) { FillMode = FillMode.Winding };
_regionList.Clear();
gp.Flatten(null, 1);
try
{
PointF tmpPoint = gp.PathPoints[0];
int i;
for (i = 0; i <= gp.PathPoints.Length - 2; i++)
{
if (gp.PathTypes[i + 1] == (byte)PathPointType.Start | (gp.PathTypes[i] & (byte)PathPointType.CloseSubpath) == (byte)PathPointType.CloseSubpath)
{
tmpPoints = GetLinePoints(gp.PathPoints[i], tmpPoint, 1);
Array.ConstrainedCopy(tmpPoints, 0, points, count, tmpPoints.Length);
count += 1;
tmpPoint = gp.PathPoints[i + 1];
}
else
{
tmpPoints = GetLinePoints(gp.PathPoints[i], gp.PathPoints[i + 1], 1);
Array.ConstrainedCopy(tmpPoints, 0, points, count, tmpPoints.Length);
count += tmpPoints.Length - 1;
 
}
}
tmpPoints = new PointF[count];
Array.Copy(points, tmpPoints, count);
points = CleanPoints(tmpPoints);
 
count = points.Length - 1;
DrawText(points, count);
}
catch (Exception ex)
{
LastError = ex;
 

}
}
private static PointF[] CleanPoints(PointF[] points)
{
 
int i;
PointF[] tmppoints = new PointF[points.Length + 1];
PointF lastpoint = default(PointF);
int count = 0;
 
for (i = 0; i <= points.Length - 1; i++)
{
if (i == 0 | points[i].X != lastpoint.X | points[i].Y != lastpoint.Y)
{
tmppoints[count] = points[i];
count += 1;
}
lastpoint = points[i];
}
 

points = new PointF[count];
Array.Copy(tmppoints, points, count);
 
return points;
}
 
private void DrawText(PointF[] points, int maxPoints)
{
 
GraphicsPath gp = new GraphicsPath(_pathdata.Points, _pathdata.Types) { FillMode = FillMode.Winding };
gp.Flatten();
gp.Dispose();
Graphics g = _graphics;
//GraphicsContainer graphicsContainer= g.BeginContainer();
//g.TranslateTransform(_graphicsPath.GetBounds().X, _graphicsPath.GetBounds().Y);
int count = 0;
PointF point1 = default(PointF);
int charStep = 0;
double maxWidthText = default(double);
int i;
 
for (i = 0; i <= _text.Length - 1; i++)
{
maxWidthText += StringRegion(g, i);
}
 
switch (_pathalign)
{
case TextPathAlign.Left:
point1 = points[0];
count = 0;
break;
case TextPathAlign.Center:
count = (int)((maxPoints - maxWidthText) / 2);
if (count > 0)
{
point1 = points[count];
}
else
{
point1 = points[0];
}
 
break;
case TextPathAlign.Right:
count = (int)(maxPoints - maxWidthText - (double)StringRegion(g, _text.Length - 1) * LetterSpacePercentage / 100);
if (count > 0)
{
point1 = points[count];
}
else
{
point1 = points[0];
}
 
break;
}
 
while (!(charStep > _text.Length - 1))
{
int lStrWidth = (int)(StringRegion(g, charStep) * LetterSpacePercentage / 100);
if ((count + lStrWidth / 2) >= 0 & (count + lStrWidth) < maxPoints)
{
count += lStrWidth;
PointF point2 = points[count];
PointF point = points[count - lStrWidth / 2];
double angle = GetAngle(point1, point2);
DrawRotatedText(g, _text[charStep].ToString(), (float)angle, point);
point1 = points[count];
}
else
{
count += lStrWidth;
}
charStep += 1;
}
 
}
 
private RectangleF StringRegionValue(Graphics g, int textpos)
{
 
string measureString = _text.Substring(textpos, 1);
int numChars = measureString.Length;
CharacterRange[] characterRanges = new CharacterRange[numChars + 1];
StringFormat stringFormat = new StringFormat
{
Trimming = StringTrimming.None,
FormatFlags =
StringFormatFlags.NoClip | StringFormatFlags.NoWrap |
StringFormatFlags.LineLimit
};
SizeF size = g.MeasureString(_text, _font, 100);
RectangleF layoutRect = new RectangleF(0f, 0f, size.Width, size.Height);
characterRanges[0] = new CharacterRange(0, 1);
stringFormat.FormatFlags = StringFormatFlags.NoClip;
stringFormat.SetMeasurableCharacterRanges(characterRanges);
stringFormat.Alignment = StringAlignment.Near;
Region[] stringRegions = g.MeasureCharacterRanges(_text.Substring(textpos), _font, layoutRect, stringFormat);
return stringRegions[0].GetBounds(g);
}
 
private float StringRegion(Graphics g, int textpos)
{
return StringRegionValue(g,textpos).Width;
}
 
private static double GetAngle(PointF point1, PointF point2)
{
double c = Math.Sqrt(Math.Pow((point2.X - point1.X), 2) + Math.Pow((point2.Y - point1.Y), 2));
if (c == 0)
{
return 0;
}
if (point1.X > point2.X)
{
//We must change the side where the triangle is
return Math.Asin((point1.Y - point2.Y) / c) * 180 / Math.PI - 180;
}
return Math.Asin((point2.Y - point1.Y) / c) * 180 / Math.PI;
 

}
private void DrawRotatedText(Graphics gr, string text, float angle, PointF pointCenter)
{
angle -= _rotateDegree;
StringFormat stringFormat = new StringFormat { Alignment = StringAlignment.Center };
 
gr.SmoothingMode = SmoothingMode.HighQuality;
gr.CompositingQuality = CompositingQuality.HighQuality;
gr.TextRenderingHint = TextRenderingHint.AntiAlias;
GraphicsPath graphicsPath = new GraphicsPath(FillMode.Winding);
int x = (int)pointCenter.X;
int y = (int)pointCenter.Y;
 
switch (TextPathPathPosition)
{
case TextPathPosition.OverPath:
graphicsPath.AddString(text, _font.FontFamily, (int)_font.Style, _font.Size, new Point(x, (int)(y - _font.Size)), stringFormat);
break;
case TextPathPosition.CenterPath:
graphicsPath.AddString(text, _font.FontFamily, (int)_font.Style, _font.Size, new Point(x, (int)(y - _font.Size / 2)), stringFormat);
break;
case TextPathPosition.UnderPath:
graphicsPath.AddString(text, _font.FontFamily, (int)_font.Style, _font.Size, new Point(x, y), stringFormat);
break;
}
 

Matrix rotationMatrix = new Matrix();
rotationMatrix.RotateAt(angle, new PointF(x, y));
graphicsPath.Transform(rotationMatrix);
if (!_measureString)
{
gr.DrawPath(new Pen(_color), graphicsPath);
gr.FillPath(_fillBrush, graphicsPath);
}else
{
_regionList.Add(graphicsPath.GetBounds());
}
 
graphicsPath.Dispose();
}
 

 
public PointF[] GetLinePoints(PointF p1, PointF p2, int stepWitdth)
{
 
int lCount = 0;
PointF[] tmpPoints = new PointF[10001];
long ix;
long iy;
int dd;
int id;
int lStep = stepWitdth;
 
p1.X = (int)p1.X;
p1.Y = (int)p1.Y;
p2.X = (int)p2.X;
p2.Y = (int)p2.Y;
long width = (long)(p2.X - p1.X);
long height = (long)(p2.Y - p1.Y);
long d = 0;
 
if (width < 0)
{
width = -width;
ix = -1;
}
else
{
ix = 1;
}
 
if (height < 0)
{
height = -height;
iy = -1;
}
else
{
iy = 1;
}
 
if (width > height)
{
dd = (int)(width + width);
id = (int)(height + height);
 
do
{
if (lStep == stepWitdth)
{
tmpPoints[lCount].X = p1.X;
tmpPoints[lCount].Y = p1.Y;
lCount += 1;
}
else
{
lStep = lStep + stepWitdth;
}
if ((int)p1.X == (int)p2.X) break;
 
p1.X = p1.X + ix;
d = d + id;
 
if (d > width)
{
p1.Y = p1.Y + iy;
d = d - dd;
}
}
 
while (true);
}
else
{
dd = (int)(height + height);
id = (int)(width + width);
 
do
{
if (lStep == stepWitdth)
{
tmpPoints[lCount].X = p1.X;
tmpPoints[lCount].Y = p1.Y;
lCount += 1;
}
else
{
lStep = lStep + stepWitdth;
}
if ((int)p1.Y == (int)p2.Y) break;
 
p1.Y = p1.Y + iy;
d = d + id;
 
if (d > height)
{
p1.X = p1.X + ix;
d = d - dd;
}
}
while (true);
}
 
PointF[] tmpPoints2 = new PointF[lCount];
 
Array.Copy(tmpPoints, tmpPoints2, lCount);
 
return tmpPoints2;
}
 
}
}

GeneralGreat job Pinmembersf4all15-Jul-09 4:45 
GeneralRe: C# version and with optimization. PinmemberMember 740216621-Apr-11 5:21 
GeneralRe: C# version and with optimization. PinprotectorMarc Clifton14-Sep-14 5:25 
GeneralInterested in getting 1 developer license PinmemberLarryKudrow17-Mar-08 5:49 
GeneralProblem with Running Project Pinmemberbnjneer5-Jan-08 14:30 
GeneralProblems Pinmembermysticwars13-Apr-07 9:36 
GeneralSqrt and Decimal PinmemberWesner Moise2-May-06 19:11 
GeneralRe: Sqrt and Decimal Pinmemberjarvisa29-May-06 22:55 
GeneralVGdotnet PinmemberPSwartzell124-Apr-06 5:51 
GeneralExcellent Pinmemberchimeric6923-Apr-06 16:53 
GeneralInteresting graphical effect... PinmemberVlad Stanciu21-Apr-06 8:32 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.141022.2 | Last Updated 2 May 2006
Article Copyright 2006 by Alexander Seel
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid