I have code, similar to the following:
string myText = "This is a test";
Font myFont = new Font("Arial", 10);
Rectangle rect = new Rectangle(10,10,100,100);
Graphics g = e.Graphics;
g.DrawString(myText, myFont, rect.X, rect.Y);
Although this works, what I would like to do is have the text vertically and horizontally centered within the dimensions of the rectangle. In addition, if its possible, I'd like to wrap the text if its too big to fit on one line. How do I do this in C#/GDI+?
I would use one of the DrawString Overloads that takes a StringFormat You would have to check the Length of your Text using MeasureString to make sure it would fit and wrap it yourself. In looking further at the StringFormatFlags it has a NoWrap Flag implying that the default is to wrap:
from last link:
Text wrapping between lines when formatting within a rectangle is disabled. This flag is implied when a point is passed instead of a rectangle, or when the specified rectangle has a zero line length.
the simple, un-researched way to do it:
to word-wrap break your strings up into smaller sub-strings. I.e.
"this is"
"a test"
you can use the String.Split() method to split your strings up into words, and then append the words together until your string has reached a certain threshold. and then make a new string.
As for centering, you can pad your individual strings with spaces. for vertical centering, you can add some newlines to the beginning of your strings.
I'll leave the arithmetic involved to you. it shouldn't be that hard.
Related
I have a custom control which renders text, including word wrap, using System.Drawing.Graphics.DrawString(). Part of the rendering includes drawing highlight, such as a background color, or an underline, or other decoration on specified ranges of the text.
void HighlightRegion(Graphics g, string text, int startPos, int length, RectangleF region, Font font, StringFormat sf, Color c)
{
var range = new CharacterRange[1];
range[0].First = startPos;
range[0].Length = length;
sf.SetMeasureableCharacterRanges(range);
var ranges = g.MeasureCharacterRanges(text, font, region, sf);
using (ranges[0])
using (var brush = new SolidBrush(c))
{
g.FillRegion(brush, ranges[0]);
}
}
Unfortunately, I discovered that Graphics.DrawString doesn't handle font fallbacks properly. I was making sure it could handle Unicode, and testing with emojis showed that they render as little boxes. I found, however, that System.Windows.Forms.TextRenderer.DrawText() handles font fallback correctly. So I've been experimenting with changing the text rendering to use TextRenderer.DrawText(). But I can't figure out the range highlighting portion.
TextRenderer.MeasureText works fine for calculating the entire text size, but there doesn't seem to be any way to measure just a portion of it. A range of characters could start in the middle of a line, wrap around to the next line, and continue on. Trying to predict where those characters would fall would mean exactly reproducing the word wrap logic that TextRenderer uses internally.
I've looked into P/Invoke of GDI GetCharacterPlacementW (no word wrap), I've looked into Uniscribe (no built-in font fallback), I've considered measuring character by character (messy, and I'm likely to get edge cases wrong, and I have to somehow match the built in word wrap), but they all have issues. Is there a correct way to measure a TextRenderer.DrawText range? Or should I just take over the rendering process entirely and lay out the text character by character?
I have a custom WinForms control which could be described as some kind of terminal control (like a control containing Putty). According to this, the control should display a lot of characters, each with a different color and background color (worst case).
Currently I'm using the (kind of obsolete) Graphics.MeasureString method to determine the size of a single character in my fixed-size font so I can calculate the position of a character at a specific row and column. Then I use Graphics.DrawString to draw the characters. To optimize the performance I create a BufferedGraphics and group characters by their properties to draw consecutive characters with the same color with just one DrawString call. (Because one DrawString call per character is really slow.)
Now the problem is that DrawString apparently calculates the width of a character slightly different from MeasureString. When I draw a complete line at once, the resulting text width is different from what I calculated using the width of a single character multiplied by the character count of the line. It's just one or two pixels (or maybe less), but you can see it clearly - especially because I'm using anti-alias so you can even see a difference of just half a pixel.
The following sample code draws a long string a on the form's graphics, followed by character 'B'. Then it draws just a 'B' on the position calculated by measuring a.
var f = new Form {
Width = 1200,
Height = 500,
Font = new Font("Consolas", 11, FontStyle.Regular)
};
f.Paint += delegate(object sender, PaintEventArgs e) {
var a = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
var size = e.Graphics.MeasureString(a, f.Font, new PointF(0, 0), StringFormat.GenericTypographic);
using (var black = new SolidBrush(Color.Black))
{
e.Graphics.DrawString(a + "B", f.Font, black, 0, 0, StringFormat.GenericTypographic);
e.Graphics.DrawString("B", f.Font, black, size.Width, 20, StringFormat.GenericTypographic);
}
};
f.Show();
And if you look closely, you will see that the second B is about one pixel more right - at least with my display settings (100% dpi scale, ClearType enabled). Although one pixel is not much, when you draw lines using unicode characters U+2500 through U+257F it looks pretty ugly if the characters aren't perfectly aligned.
Also I can't use the TextRenderer class because its MeasureString method returns integer values, but DrawString of course does not draw each character on a full pixel position (what would be required to align it with a position calculated using row/column and the measured integer character size).
So my question is: Is there any (efficient) method to draw strings which are perfectly aligned to their corresponding position calculated using the character size?
P.S.: MeasureString with a 200-character-string returns exactly 200 times the return value of MeasureString with a single-character-string.
I have had some similar issues with measuring the strings in Graphics. I have used SizeInPoints, a property from the class Font and multiplied it for the number of characters I have in the string... I dont't know if it helps.
If not it can be a "rounding" problem with the pixels... then I would try to scale up the font size (maybe 10 times), measure it and then divide it by 10 again when using the size to color the background.
I hope it helps.
Good luck! Regards!
I'm having same issue.
It seems that the behavior depends on the font size.
After changing font with following code, this issue didn't occur.
Font = new Font("Consolas", 20.0F, FontStyle.Regular, GraphicsUnit.Pixel)
However, with another font size as below this issue still occurs.
Font = new Font("Consolas", 20.3F, FontStyle.Regular, GraphicsUnit.Pixel)
My guess on background of this issue: MeasureString and DrawString uses different layout routine and they have different characteristic in rounding error of float number. Only with 'simple' font size they gives same results.
Also, this issue didn't occur with bitmap fonts.
I find the way the DrawString function cuts off entire characters or words to be counterintuitive. Showing parts of the text clearly conveys that something is missing.
Here are some examples:
StringTrimming.None:
StringTrimming.Character:
What I want:
(GIMP mockup)
Is StringTrimming.None the same as StringTrimming.Character? They seem to behave exactly alike in my case. Is there something I could have overlooked or is this a known "feature"?
According to the docs StringTrimming.None "Specifies no trimming."
This site with examples created with Java even show "None" to trim more characters than "Character".
Are there other tricks to get this effect?
Note: I do not want to display "…" or similar. I find this to be a waste of space but that is probably a discussion for UX.
It's possible that the text appears to be trimmed because it's actually wrapping invisibly onto the next line. In the call to Graphics.DrawString, disable wrapping by passing a StringFormat object whose FormatFlags property is set to NoWrap:
StringFormat format =
new StringFormat
{
FormatFlags = StringFormatFlags.NoWrap,
Trimming = StringTrimming.None
};
g.DrawString(s, font, brush, rect, format);
As a workaround, you can give the DrawString method a bigger RectangleF than you really want, and set the clipping region of your Graphics object to the actual RectangleF you want to draw the string in.
RectangleF rect = new RectangleF(10, 10, 30, 15);
e.Graphics.DrawRectangle(Pens.Red, Rectangle.Truncate(rect));
RectangleF fakeRect = new RectangleF(rect.Location, new SizeF(short.MaxValue, short.MaxValue));
Region r = e.Graphics.Clip;
e.Graphics.SetClip(rect);
e.Graphics.DrawString("1 Default", this.Font, SystemBrushes.ControlText, fakeRect);
e.Graphics.Clip = r;
Note that this code assumes you wish to anchor your text to the top left corner of the rectangle. Otherwise you should generate the fake rectangle in a method that maintains the same anchor point as your intended layout rectangle.
I am using the GraphicsPath.AddString() function, but it draws the text with a little space around the text. Any idea how to draw the string without that padding, only the paths of the text?
My code is like this:
GraphicsPath gp = new GraphicsPath();
gp.AddString(text, font.FontFamily, (int)font.Style, font.Size,
boundsRectangle, format);
g.DrawPath(pen, gp);
What is happening is the under the hood it is probably using Graphics.MeasureString(), from the documentation :
GDI+ adds a small amount (1/6 em) to each end of every string displayed. This 1/6 em allows >for glyphs with overhanging ends (such as italic 'f'), and also gives GDI+ a small amount >of leeway to help with grid fitting expansion.
The default action of DrawString will work against you in displaying adjacent runs:
Firstly the default StringFormat adds an extra 1/6 em at each end of each output;
Secondly, when grid fitted widths are less than designed, the string is allowed to contract
by up to an em.
To avoid these problems:
Always pass MeasureString and DrawString a StringFormat based on the typographic >StringFormat (GenericTypographic).
Set the Graphics TextRenderingHint to TextRenderingHintAntiAlias. This rendering method >uses anti-aliasing and sub-pixel glyph positioning to avoid the need for grid-fitting, and >is thus inherently resolution independent.
So it looks like you should be able to fix this using the correct StringFormat.
I have the following code to create a Label on a PictureBox:
Label l = new Label();
l.Text = _name;
l.Size = CreateGraphics().MeasureString(_name, l.Font).ToSize();
l.BackColor = Color.White;
but the label is always dropping the last character. If I add a character to the call:
l.Size = CreateGraphics().MeasureString(_name+".", l.Font).ToSize();
it works fine, but that doesn't feel right.
There seems to be some white space just before the text in the label, but Padding is set to 0. How can I fix this the correct way?
Can't you use the AutoSize property?
MeasureString is notoriously inaccurate, though normally it returns a size bigger than you'd expect:
The MeasureString method is designed for use with individual strings and includes a small amount of extra space before and after the string to allow for overhanging glyphs. Also, the DrawString method adjusts glyph points to optimize display quality and might display a string narrower than reported by MeasureString. To obtain metrics suitable for adjacent strings in layout (for example, when implementing formatted text), use the MeasureCharacterRanges method or one of the MeasureString methods that takes a StringFormat, and pass GenericTypographic. Also, ensure the TextRenderingHint for the Graphics is AntiAlias.
http://msdn.microsoft.com/en-us/library/6xe5hazb.aspx
ToSize() truncates values of the SizeF to the next lower integer values.
So, to avoid losses you can do something like that:
l.Size = (CreateGraphics().MeasureString(_name, l.Font) + new SizeF(1, 0)).ToSize();
Is it something as simple as getting the size wrong when you declare your font compared to the UI font size?