Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get the exact text margins used by TextRenderer

Tags:

c#

winforms

gdi

System.Windows.Forms.TextRenderer.DrawText method renders formatted text with or without left and right padding depending on the value of the flags parameter:

  • TextFormatFlags.NoPadding - fits the text tightly into the bounding box,
  • TextFormatFlags.GlyphOverhangPadding - adds some left and right margins,
  • TextFormatFlags.LeftAndRightPadding - adds even bigger margins.

Now, my question is how can I get the exact amount of padding (left and right) added by DrawText to the text for a given device context, string, font etc?

I've dug into .NET 4 with .NET Reflector and found that TextRenderer calculates "overhang padding" which is 1/6 of the font's height and then multiplies this value to calculate left and right margins using these coefficients:

  • left 1.0, right 1.5 for TextFormatFlags.GlyphOverhangPadding,
  • left 2.0, right 2.5 for TextFormatFlags.LeftAndRightPadding.

The resulting values are rounded up and passed to the DrawTextExA or DrawTextExW native API functions. It's difficult to recreate this process because font's height is taken not from System.Drawing.Font but from System.Windows.Forms.Internal.WindowsFont and these classes return different values for the same font. And a lot of other internal BCL classes from the System.Windows.Forms.Internal namespace are involved. Decompiling all of them and reusing their code in my app is not an option, because that would be a serious .NET implementation dependency. That's why I need to know if there is some public API in WinForms or at least which Windows functions I can use to get the values of left and right margins.


Note: I've tried to TextRenderer.MeasureText with and without padding and compare the results but that gave me only the sum of left and right margins and I need them separately.


Note 2: In case you wonder why I need this: I want to draw one string with multiple fonts/colors. That involves calling DrawText once for every uniformly formatted substring with NoPadding option (so that the text doesn't spread) but I also want to add manually normal GlyphOverhangPadding at the very beginning and very end of the whole multi-format text.

like image 881
Jacek Mokrzycki Avatar asked Dec 13 '10 11:12

Jacek Mokrzycki


2 Answers

The value you need for computing left and right margins is TEXTMETRIC.tmHeight, which is possible to obtain using Win32 API.

However, I found that tmHeight is just a line height of a font in pixels, so these three approaches will give you the same value (you can use whichever you like in your code):

int apiHeight = GetTextMetrics(graphics, font).tmHeight; int gdiHeight = TextRenderer.MeasureString(...).Height; int gdipHeight = (int)Math.Ceiling(font.GetHeight(graphics)); 

To obtain left and right margins, we use the same code as TextRenderer does under the hood:

private const float ItalicPaddingFactor = 0.5f;  ...  float overhangPadding = (gdiHeight / 6.0f);  //NOTE: proper margins for TextFormatFlags.LeftAndRightPadding flag //int leftMargin = (int)Math.Ceiling(overhangPadding); //int rightMargin = (int)Math.Ceiling(overhangPadding * (2 + ItalicPaddingFactor));  //NOTE: proper margins for TextFormatFlags.GlyphOverhangPadding flag int leftMargin = (int)Math.Ceiling(overhangPadding); int rightMargin = (int)Math.Ceiling(overhangPadding * (1 + ItalicPaddingFactor));  Size sizeOverhangPadding = TextRenderer.MeasureText(e.Graphics, "ABC", font, Size.Empty, TextFormatFlags.GlyphOverhangPadding); Size sizeNoPadding = TextRenderer.MeasureText(e.Graphics, "ABC", font, Size.Empty, TextFormatFlags.NoPadding);  int overallPadding = (sizeOverhangPadding.Width - sizeNoPadding.Width); 

Now you can easily check that

(leftMargin + rightMargin) == overallPadding 

Just to note:

I needed to solve this problem in order to implement "Search Highlight" feature in a ListView-based control that uses GDI text rendering:

enter image description here

Works like a charm :)

like image 150
Libor Avatar answered Sep 21 '22 07:09

Libor


This answer is an excerpt from here - http://www.techyv.com/questions/how-get-exact-text-margins-used-textrenderer#comment-35164

If you have ever wanted a Label or TextBox in Windows Forms that performs a little more like on the web, then you've probably figured out that there's no intuitive way to make a Label or TextBox automatically adjust its height to fit the text it contains. While it may not be intuitive, it's definitely not impossible.

In this example, I'll use a TextBox (you could just as easily use a Label) that is docked to the top of a form.To use this, add aTextBox called MyTextBox to the form, and set Dock to DockStyle.Top. Wire up the Resize event of the TextBox to this event handler.

private void MyTextBox_Resize( object sender, EventArgs e ) {     // Grab a reference to the TextBox     TextBox tb = sender as TextBox;       // Figure out how much space is used for borders, etc.      int chromeHeight = tb.Height - tb.ClientSize.Height;      // Create a proposed size that is very tall, but exact in width.      Size proposedSize = new Size( tb.ClientSize.Width, int.MaxValue );      // Measure the text using the TextRenderer     Size textSize = TextRenderer.MeasureText( tb.Text, tb.Font,         proposedSize, TextFormatFlags.TextBoxControl          | TextFormatFlags.WordBreak );      // Adjust the height to include the text height, chrome height,     // and vertical margin     tb.Height = chromeHeight + textSize.Height          + tb.Margin.Vertical;  } 

If you want to resize the a Label or TextBox that is not docked (for example, one that is in a FlowLayoutPanel or other Panel, or just placed on the form), then you can handle the Form's Resize even instead, and just modify the Control's properties directly.

like image 26
jhjuisaha Avatar answered Sep 22 '22 07:09

jhjuisaha