Measure text ranges for TextRenderer - c#

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?

Related

C# draw a string pixel perfect

I'm trying to draw a string using either textrenderer.drawtext, graphics.drawstring or graphicspath.addstring - the main purpose is to extract all fonts to bitmaps to edit them and use them as bitmaps with shaders in a game.
With textrenderer.drawtext and graphics.drawstring, I get a padding on top of varying degrees - so I try graphicspath.addstring. I extract the font family's ascent height and descent height, but they are wildly unusable with emheight. (using ascent and descent with emheight is how microsoft suggest you do what I am trying to do - via http://msdn.microsoft.com/en-us/library/xwf9s90b%28v=vs.110%29.aspx. Has anyone successfully ever draw pixel perfect fonts using C#? Every time I ever try or look it up, textrenderer and graphics always' padding always screwed up drawing and this new graphicspath method seems to have an issue with using a specific scale.
The usual methods using TextRenderer or MeasureString will give you a SizeF, containing the bounds of the string you measure. Most formats include a little slack so you can compose text by adding strings together.
The aim of theses methods is to help create blocks of text by letting you measure when a line will be full or how many pixels to advance for the next line.
They are not really meant for maesuring single characters.
For this there is a special stringformat GenericTypographic as described here which leaves out the white space.
To get an even more precise measurement one can use GraphicsPath.AddString and then GetBounds, maybe after switching antialias off..
Now, if you wanted to draw a single character precisely, say centered on a Button this would do the job.
But you know all that and your aim is different - if I understand you correctl,y you want to create Bitmaps from each character in order to later join them to form text. This means you need them to line up correctly vertically, ie sit on the same baseline.
The sizes of the characters don't help you here; now, normally you'd need the baseline of each charcater, which you don't get, at least not for anything descending like 'f' or even just ',' etc..
But it wouldn't help you either because in GDI you don't print/draw to the baseline anyway..
What you should do, imo is either draw one long string with all characters, so that they're all lined up right and then cut out the characters one by one. Or you could draw each character on its own, but suffix all or some characters you know to have ascenders and descenders and then only pick the first columns from the result.
So the only way I figured out how to do this is is to first draw the string to a graphicpath, then measure all the empty spots in the graphic path, and get it's height only after I've measure every spot, then redraw the string (I have an attempt counter to limit attempts but increase em to pixel accuracy) taking the old size and new size into account by a modifier and then extract the final size and store it.
Only I got to get around the BS of every font having a weird top padding that isn't associated with it's ascent and internal overflow (ex: Ñ), as well as descent, in refrence to a 0,0 point, this way.

Draw a lot of single characters to a WinForms Control

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.

How to draw a string without padding

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.

Drawing text centered within a rectangle

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.

How to create a font with precise character height in Windows Forms?

In my program I need to generate a bitmap with all digit characters (0..9) laid from left to right, plus a few other characters.
The user will select from the UI:
the desired font, and
the desired character height in pixels.
So I want to create a Bitmap, then a Graphics from this bitmap, then draw the digits one by one to this bitmap, and then save it to disk.
What I couldn't figure out in hours:
How do I create a font with the correct size so that the digit '0' has the height given by the user?
I played with all the parameters in the Font constructor, with properties of StringFormat, with MeasureString/MeasureText/MeasureCharacterRanges, I tried creating the font with the native CreateFont() via P/Invoke (with positive/negative nHeight). All parameters called "size" or "height" seem to indicate the size of some hypothetical character that's much larger than '0'.
I did read the theory with line height, em height, ascent, descent etc. There seem to be no notion for the real height of a character, without padding and spacing and so on
Again my question: Given the font name and the desired pixel height for the '0' glyph, how can I create a font which draws that glyph with the required height?
I would appreciate any guidance.
LATER EDIT
Some more details about my project: It's an embedded device with a big display, and I need to provide an easy way for the designers to to generate and try out bitmap fonts of their liking. A Windows tool that generates such bitmap fonts seemed like a good solution to me.
While I wasn't far off the mark with what I posted here earlier, it didn't actually work. In banging my head against this I found it interesting to note that graphics.MeasureString("M", ...) reports a height far larger than graphics.MeasureString("MM", ...). In calculating the font sizes by hand I've found the first(which correlates with GetEMSize's response) is actually the full line size. In the end I came to realize the actual character sizes of the digits aren't required to correlate to any of the metrics, whether real or .net.
I believe you could use this Font Constructor to specify the GraphicsUnit to be pixels. Then it should create the font with the appropriate size.
Adding test code - edit accordingly for your case and don't judge for style, I just wanted something I could paste in LINQPad and would produce an image.
using (var font = new Font("Arial",10,FontStyle.Regular, GraphicsUnit.Pixel))
using (var image = new Bitmap(30, 15))
using (var graphics = Graphics.FromImage(image))
{
graphics.FillRectangle(Brushes.White, new Rectangle(0, 0, 30, 15));
graphics.DrawString("Ay", font, Brushes.Black, 0, 0);
image.Save(#"E:\test.bmp", ImageFormat.Bmp);
}
Remember when setting size by Pixels that all characters in the Font need to fit in that range, meaning letters with a descender and letters with an ascender.

Categories

Resources