Centering an individual character with DrawString
Asked Answered
C

3

6

I have tried all of the suggested ways to center text, but I can't seem to get the results I want while centering an individual character.

I have a rectangle. In that rectangle I'm drawing a circle with DrawEllipse. Now I want to draw a single character inside the circle using the same rectangle and DrawString, to have it perfectly centered.

Here is my basic code:

StringFormat stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Center;
stringFormat.LineAlignment = StringAlignment.Center;

using (Graphics g = Graphics.FromImage(xImage))
{
    g.SmoothingMode = SmoothingMode.AntiAlias;
    g.TextRenderingHint = TextRenderingHint.AntiAlias;
    g.CompositingQuality = CompositingQuality.HighQuality;
    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
    g.PixelOffsetMode = PixelOffsetMode.HighQuality;

    g.FillEllipse(fillBrush, imageRect.X, imageRect.Y, imageRect.Width - 1, imageRect.Height - 1);

    g.DrawString(Text, font, Brushes.White, imageRect, stringFormat);
}

The text is centered horizontally... but it's not properly centered veritcally. Using a symmetrical character like an uppercase "I", I find that the top of the character is always much nearer to the edge of the rectangle than the bottom of the character. The distance is probably at least a 50% increase.

I assume that it is measuring enough space for characters like a lowercase "j" which hangs lower. However, since I am trying to create a graphical icon with a single letter, I want more precise centering.

Caravan answered 8/12, 2011 at 17:39 Comment(5)
Have you compares the results of the DrawString method to those of the TextRenderer.DrawText method with TextFormatFlags of HoriztonalCenter and VerticalCenter?Lunchroom
No repro. Beware that an uppercase I doesn't have a descender like the character g.Bename
I tried TextRenderer.DrawText and got similar results.Caravan
Hans: My point is that when I draw characters which don't have descenders, they are being vertically centered in a way that provides blank space for the nonexistant descenders. Thus, the character is not really centered.Caravan
I am assuming the GDI has no way to do this, so what I am going to do is measure the text, create a Bitmap the size of the measure, draw the text on the bitmap, lock the bits, and get the bounding area of the sub-Rectangle which actually contains data (non-transparent pixels). Then bit blit that rectangle in a centered position over the first Rectangle. Should work I think, but the performance will be awful.Caravan
B
11

Use GraphicsPath to accomplish the size calculation.

public static void DrawCenteredText(Graphics canvas, Font font, float size, Rectangle bounds, string text)
{
    var path = new GraphicsPath();
    path.AddString(text, font.FontFamily, (int)font.Style, size, new Point(0, 0), StringFormat.GenericTypographic);

    // Determine physical size of the character when rendered
    var area = Rectangle.Round(path.GetBounds());

    // Slide it to be centered in the specified bounds
    var offset = new Point(bounds.Left + (bounds.Width / 2 - area.Width / 2) - area.Left, bounds.Top + (bounds.Height / 2 - area.Height / 2) - area.Top);
    var translate = new Matrix();
    translate.Translate(offset.X, offset.Y);
    path.Transform(translate);

    // Now render it however desired
    canvas.SmoothingMode = SmoothingMode.AntiAlias;
    canvas.FillPath(SystemBrushes.ControlText, path);
}
Brutalize answered 8/12, 2011 at 19:26 Comment(5)
That's pretty genius. I'll try it out.Caravan
@ccnyou is right, this is perfect! I tried with MeasureString but without any luck. Today I found this answer. Question is old, but problem was actual. This fixed it immediately!Cepeda
Very cool! +1 - GraphicsPath is so underrated (together with Label..) ;-)Nerissanerita
This should have more attention, I seached hours for this :)Morsel
I lost too much sleep trying to use "MeasureString" on Wingdings to create a sort of char-map. The behavior was odd and my math wasnt working. This worked flawlessly!Rhubarb
A
1

if you use

StringFormat stringFormat = new StringFormat(StringFormat.GenericTypographic);

you got

enter image description here

instead

enter image description here

hope this helps

Aubree answered 8/12, 2011 at 17:59 Comment(3)
Notice in your images that the "g" is not centered. I'm not looking for typographical centering, I'm not drawing words where the characters need to be aligned. I'm drawing a single character and I want it to be in the exact center on the circle regardless of which character it is. Think of how a graphic designer would make an icon in Photoshop by centering it. I need to do that programmatically.Caravan
Ok, I've lost, John Arlen has the perfect answer!Aubree
It should be StringFormat stringFormat = new StringFormat(StringFormat.GenericTypographic());Setter
P
1

Though John Arlen's answer is perfect, I'd like to post my answer:

    private void Form1_Paint(object sender, PaintEventArgs e)
    {
         // Set up string.
        string measureString = "HelloWorld";
        Font stringFont = new Font("Arial", 100, FontStyle.Regular, GraphicsUnit.Pixel);

        // Measure string.
        SizeF stringSize = new SizeF();
        stringSize = e.Graphics.MeasureString(measureString, stringFont);

        // Draw rectangle representing size of string.
        e.Graphics.DrawRectangle(new Pen(Color.Red, 1), 10.0F, 10.0F, stringSize.Width, stringSize.Height);

        // Draw string to screen.
        e.Graphics.DrawString(measureString, stringFont, Brushes.Black, new PointF(10, 10f + stringSize.Height / 12.0f));
    }

code result like that: result in my computer

"HelloWorld" is centered vertically in the red box.

For the height of descender line is approximately equal 1/6 of stringSize.Height calculated by MeasureString


| top padding 1/6

| word body 3/6

| word descender 1/6

| bottom padding 1/6

Perren answered 6/3, 2014 at 12:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.