Calling TextRenderer.MeasureText as follows:
TextRenderer.MeasureText(myControl.Text, myControl.Font);
and comparing the result to the size of the control to check if text fits. The results are sometimes incorrect. Have observed the following two issues:
Often when a Label is set to AutoSize, TextRenderer will report a width that is 1 pixel wider than the auto-sized width of the Control.
False negative where TextRenderer reports a width smaller than the control's but the text is still cut off. This occurred with "Estación de trabajo" -- not sure if the accent could somehow affect the width calculation?
Is there any way to improve the accuracy of the MeasureText method? Should I be calling one of the overrides that accepts a device context and/or format flags?
I know it's probably no actual anymore. Yet for future readers here is a simple yet accurate method of measuring text in a control:
Graphics g=Graphics.FromHwnd(YOUR CONTROL HERE.Handle);
SizeF s=g.MeasureString("YOUR STRING HERE", Font, NULL, NULL, STRING LENGTH HERE, 1)
Is there any way to improve the accuracy of the MeasureText method? Should I be calling one of the overrides that accepts a device context and/or format flags?
You have answered your question by yourself. Actually MeasureText based on Win32 DrawTextEx, and this function cannot work without valid device context. So when you call MeasureText override without hdc, it internally create desktop compatible hdc to do measurement.
Of course measurement depends on additional TextFormatFlags. Also keep in mind that Label painting (and measurement) depends on UseCompatibleTextRendering.
So general conclusion you should use MeasureText for your own code, for example when you then call DrawText with exactly same parameters, in all other cases size returned by MeasureText cannot be treated as precise.
If you need to get expected Label size, you should use GetPreferredSize method.
Check out the TextFormatFlags parameter to this function:
TextRenderer::MeasureText(String, Font, Size, TextFormatFlags)
http://msdn.microsoft.com/en-us/library/8wafk2kt.aspx
"The Size, in pixels, of text drawn on a single line with the specified font. You can manipulate how the text is drawn by using one of the DrawText overloads that takes a TextFormatFlags parameter. For example, the default behavior of the TextRenderer is to add padding to the bounding rectangle of the drawn text to accommodate overhanging glyphs. If you need to draw a line of text without these extra spaces you should use the versions of DrawText and MeasureText that take a Size and TextFormatFlags parameter. For an example, see MeasureText(IDeviceContext, String, Font, Size, TextFormatFlags)."
hth
I don't know if I have a perfect solution but I ran into this when I was doing WinForms a few years back. The way I ended up compensating was by adjusting the returned measurement by a percentage. I cannot recall what I used (maybe 5% or 105?), but I do recall that I ended up using a constant percentage across the app and always rounded up.
I haven't got enough points to comment yet, so I've had to put this as an answer:
Perhaps ClearType affects measurement accuracy, because although a character has a known width calculated from its glyph, its rendering and position are adjusted to place axial lines on whole pixels.
Just a thought.
Related
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.
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 need to measure or somehow determine the maximum height for a given font.
I also need to determine where the baseline of the font is.
The Graphics.MeasureString function seems to only determine the bounding box of a particular string (which is useful, but not what I need right now).
Specifically, I'm looking for ONLY the vertical size of the font in pixels. However, it needs to vertically accommodate all possible strings.
Is this something that is possible to programatically determine?
You are looking for the Height property of your font, which is "the line spacing, in pixels, of this font". The position of the baseline (above the top of the next line of text) is given by the GetCellDescent method of the font family, but it is in "design units", which has to be converted to pixels (here is how). There is an interesting article on MSDN about obtaining font metrics.
I have an interesting problem. I'm almost there but am curious how others would tackle it. I want to display some multi-line text in a predefined area. I don't know what the text will be or how big the area will be so the function would have to be written generically. You can assume a standard font is always used but the point size is what must change.
Assume you have a function that will draw text that is passed to it in a string parameter. The function has a form object to draw in, and is also passed a rectangle object defining the bounding area of the text on the form. The function needs to display the text on the form in the given rectangle in as large a font as will fit. The challenge for me was is in calculating the size of the font to use to have the text fit as best it can, in the rectangle with minimal white space.
These 2 equations might be useful:
float pixels = (points *dpi)/72f;
float points = (pixels*72f)/dpi);
Also:
float dpi = CreateGraphics().DpiY;
Well, it is tricky. Calculating a point size directly isn't going to work, the width of the text is dependent on the font metrics. Binary search is an obvious strategy but it cannot work in practice. True-type hinting and word wrapping conspire to destabilize it.
I'd recommend you start with binary search, setting hi and lo to reasonable defaults like 72 and 6. Then when the range narrows down to, say, 5 points, start testing each individual point size until you find the largest one that fits. When you write the algorithm, do make sure you count on a size N that fits but a size N-1 that doesn't fit.
There is a significant problem with any solution, which is you need to determine this based on the width as well, which is completely dependent upon the font. This means you need to calculate the width of each word independently based on a predefined point size font. As you change the point size, it is not guaranteed to be consistent.
The solution won't be fast if you want it to be accurate.
I would suggest selecting two point sizes (say 6 and 18) that represent the smallest and mid- to high-point and compute the pixel width of each word in each point size. You could then compute the area of both sizes of text.
You could then extrapolate the area of the rectangle you find appropriate and determine the target size (width and height) using an arbitrary width/height ratio based on the length of text - there is an optimum readable width, for instance.
You will then need to iteratively attempt to word-wrap inside the rectangle and work backwards in point size until the text fits within the rectangle.
binary search over point sizes:
Start with the biggest available point size. If it doesn't fit, try half of that, ...
I arrive to output a string on multiple lines inside a retangle but haven't find a way to reduce or enlarge the line spacing.
How to do that?
This MSDN should help you. Line spacing is a result of the Font you are using. You may need to break your DrawString commands up into multiple calls if you need custom line spacing.
This Microsoft forum posting may be helpful:
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1507414&SiteID=1
This shows how MeasureString can be used to determine how much of your text will fit on each line, then using this to progressively render the entire rectangle's contents line by line. Unfortunately I don't think there's a built-in line spacing property, so you'll have to go for the manual approach. The post's author uses the font's Height * 1.5.
It's also worth researching StringFormatFlags - you'll need to make sure both your DrawString and MeasureString calls use the same StringFormat so the rendering and measurement are consistent:
http://msdn.microsoft.com/en-us/library/system.drawing.stringformatflags.aspx