GraphicsPath text rendering issue (empty space between) - how to rotate around a point?

Jan Seris 21 Reputation points
2022-07-24T22:26:31.447+00:00

Hi, there is a bug or a misunderstood feature with GraphicsPath that if AddString is used, the path renders in incorrect location (can be seen by showing the bounding rectangle using GraphicsPath.GetBounds()).
The space between the requested topleft point and actual topleft point can be calculated using GraphicsPath.GetBounds().Location.

However I cannot find any way how to normalize this when rotating around a point. I tried a lot of calculations and none worked.
I need to rotate around the point closely, without any empty space inbetween.

Note: I need to use GraphicsPath and not Graphics to be able to calculate mouse click on the GraphicsPath etc.

224153-winforms-graphicspath-question.png

Minimalistic source code of a working solution with rotating GraphicsPath with text on mouse position:

using System.Diagnostics;  
using System.Drawing;  
using System.Drawing.Drawing2D;  
using System.Windows.Forms;  
  
namespace WindowsFormsApp4  
{  
    public partial class Form1 : Form  
    {  
        Timer Timer = new Timer()  
        {  
            Interval = 20  
        };  
  
        int Rotation = 0;  
        PointF Position = PointF.Empty;  
  
        public Form1()  
        {  
            InitializeComponent();  
            Timer.Tick += (s, e) => { Rotation++; Rotation %= 360; Redraw(); };  
            Timer.Start();  
            this.Paint += Form1_Paint;  
            this.MouseMove += (s, e) => { Position = e.Location; Redraw(); };  
            this.DoubleBuffered = true; //prevent flickering  
        }  
  
        private void Form1_Paint(object sender, PaintEventArgs e)  
        {  
            using (var path = new GraphicsPath())  
            {  
                path.AddString("sample text", new FontFamily("Arial"), (int)FontStyle.Regular, 48, Position, StringFormat.GenericDefault);  
  
                //rotate  
                Matrix rotationMatrix = new Matrix();  
                rotationMatrix.RotateAt(Rotation, Position);  
                path.Transform(rotationMatrix);  
  
                var boundingRectangle = path.GetBounds();  
                //offset is present for an unknown reason in GraphicsPath containing text  
                var boundingRectanglePosition = boundingRectangle.Location;  
                if(Rotation == 0)  
                {  
                    var offset = new PointF(boundingRectanglePosition.X - Position.X, boundingRectanglePosition.Y - Position.Y);  
                    Trace.WriteLine($"Offset against mouse position: {offset}");  
                }  
  
                e.Graphics.DrawPath(new Pen(Brushes.Black), path);  
                e.Graphics.DrawRectangles(new Pen(Brushes.Aqua), new RectangleF[] { boundingRectangle });  
            }  
  
            e.Graphics.DrawEllipse(new Pen(Color.Black, width: 5), new RectangleF(Position, new Size(1, 1)));  
  
        }  
  
        private void Redraw()  
        {  
            this.Invalidate(); //request paint event  
        }  
    }  
}  
  

Thank you for help

Windows Forms
Windows Forms
A set of .NET Framework managed libraries for developing graphical user interfaces.
1,897 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,995 questions
0 comments No comments
{count} vote

3 answers

Sort by: Most helpful
  1. Reza Aghaei 4,951 Reputation points MVP
    2022-07-24T23:19:36.277+00:00

    Set the LineAlignment of the text to Center, so it will be vertically aligned center. Then when you rotate it, it always rotates around the center:

    new StringFormat(StringFormat.GenericDefault) { LineAlignment = StringAlignment.Center }  
    

    The other desired fix (as per the comments) is fixing the horizontal offset. To do so, you can calculate the offsetX, then apply a Translate to the matrix. Here is the code:

    private void Form1_Paint(object sender, PaintEventArgs e)  
    {  
        using (var path = new GraphicsPath())  
        {  
            path.AddString("sample text", new FontFamily("Arial"), (int)FontStyle.Regular, 48, Position,  
                new StringFormat(StringFormat.GenericDefault) { LineAlignment = StringAlignment.Center});  
            var offsetx = path.GetBounds().Left - Position.X;  
            //transform  
            Matrix rotationMatrix = new Matrix();  
            rotationMatrix.RotateAt(Rotation, Position);  
            rotationMatrix.Translate(-offsetx, 0);  
            path.Transform(rotationMatrix);  
            var boundingRectangle = path.GetBounds();  
            e.Graphics.DrawPath(new Pen(Brushes.Black), path);  
            e.Graphics.DrawRectangles(new Pen(Brushes.Aqua), new RectangleF[] { boundingRectangle });  
        }  
        e.Graphics.DrawEllipse(new Pen(Color.Black, width: 5), new RectangleF(Position, new Size(1, 1)));  
    }  
    

    And you can see the result, here:

    224344-text-2.gif

    1 person found this answer helpful.

  2. Viorel 117.9K Reputation points
    2022-07-25T10:19:40.463+00:00

    If you do not want to apply transformations on e.Graphics, then check another example:

    private void Form1_Paint( object sender, PaintEventArgs e )  
    {  
        using( var path = new GraphicsPath( ) )  
        {  
            path.AddString( "sample text", new FontFamily( "Arial" ), (int)FontStyle.Regular, 48, new PointF( 0, 0 ), StringFormat.GenericDefault );  
      
            var br = path.GetBounds( );  
            var bp = br.Location;  
      
            var m1 = new Matrix( );  
            m1.Translate( Position.X - bp.X, Position.Y - bp.Y );  
            path.Transform( m1 );  
      
            // To rotate arround the center of the path:  
            //PointF o = new PointF( br.Width / 2, br.Height / 2 );  
            //var m2 = new Matrix( );  
            //m2.Translate( -o.X, -o.Y );  
            //path.Transform( m2 );  
      
            var m = new Matrix( );  
            m.RotateAt( Rotation, Position );  
            path.Transform( m );  
      
            e.Graphics.DrawPath( new Pen( Brushes.Black ), path );  
        }  
      
        e.Graphics.DrawEllipse( new Pen( Color.Black, width: 5 ), new RectangleF( Position, new Size( 1, 1 ) ) );  
    }  
    
    1 person found this answer helpful.

  3. Viorel 117.9K Reputation points
    2022-07-25T10:04:48.41+00:00

    Check this investigation too:

    private void Form1_Paint( object sender, PaintEventArgs e )  
    {  
        using( var path = new GraphicsPath( ) )  
        {  
            path.AddString( "sample text", new FontFamily( "Arial" ), (int)FontStyle.Regular, 48, new PointF( 0, 0 ), StringFormat.GenericDefault );  
      
            var br = path.GetBounds( );  
            var bp = br.Location;  
      
            e.Graphics.TranslateTransform( Position.X, Position.Y );  
            e.Graphics.RotateTransform( Rotation );  
            e.Graphics.TranslateTransform( -bp.X, -bp.Y );  
      
            // To rotate arround the center of the path:  
            //PointF o = new PointF( br.Width / 2, br.Height / 2 );  
            //e.Graphics.TranslateTransform( -o.X, -o.Y );  
      
            e.Graphics.DrawPath( new Pen( Brushes.Black ), path );  
            e.Graphics.DrawRectangles( new Pen( Brushes.Magenta ), new RectangleF[] { br } );  
      
            e.Graphics.ResetTransform( );  
        }  
      
        e.Graphics.DrawEllipse( new Pen( Color.Black, width: 5 ), new RectangleF( Position, new Size( 1, 1 ) ) );  
    }  
    
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.