Groups | Blog | Home
all groups > dotnet drawing api > december 2005 >

dotnet drawing api : DrawString and Font Widths


Shawn B.
12/4/2005 11:16:57 PM
Greetings,

I am trying to draw text, character by character, using GDI+. When I use
Graphics.MeasureString() and add the result to my X coordinate and draw the
character, I get a completely different spacing between characters than when
I use Graphics.DrawString().

My specific question is: what am I doing wrong?

First, create a Form called "CanvasForm" and then paste the following code
into it:


private void ConsoleForm_Load(object sender, EventArgs e)
{
Bitmap image = new Bitmap(pictureBox1.Width, pictureBox1.Height);
Graphics canvas = Graphics.FromImage(image);

float top = 0;
float left = 0;
float width = 0;
float height = 0;

string text = "Hello, World!";

Font font = new Font("Lucida Console", 9.0f);
SizeF size = canvas.MeasureString("H", font);

height = size.Height;
width = size.Width;

SolidBrush brush = new SolidBrush(Color.LightGray);
PointF point = new PointF();

for (int index = 0; index < text.Length; index++)
{
if (index > 0)
{
point.X += width;
}

canvas.DrawString(
text.Substring(index, 1),
font,
brush,
point
);

}

point.X += (width * 5);

canvas.DrawString(
"Hello, World!",
font,
brush,
point
);

pictureBox1.Image = image;
}

Shawn B.
12/4/2005 11:24:09 PM
I finally get the correct result by making the following changes, does
anyone know why I must multiply the width from Graphics.MeasureString() by
0.65 in order to make it corret? Is there a metric I should know about
somewhere to obtain this value, because it seems to be different from each
font size I choose. I need predictability.

Thanks,
Shawn


private void ConsoleForm_Load(object sender, EventArgs e)
{
Bitmap image = new Bitmap(pictureBox1.Width, pictureBox1.Height);
Graphics canvas = Graphics.FromImage(image);

float top = 0;
float left = 0;
float width = 0;
float height = 0;

string text = "Hello, World! I am magnificantly wonderful";

Font font = new Font("Lucida Console", 9.0f);
SizeF size = canvas.MeasureString("H", font);

height = size.Height;
width = size.Width * 0.65f;

SolidBrush brush = new SolidBrush(Color.LightGray);
PointF point = new PointF();

for (int index = 0; index < text.Length; index++)
{
if (index > 0)
{
point.X += width;
}

canvas.DrawString(
text.Substring(index, 1),
font,
brush,
point
);

}

point.X = 0;
point.Y = 15;

canvas.DrawString(
text,
font,
brush,
point
);

pictureBox1.Image = image;
}

Shawn B.
12/5/2005 9:12:01 AM
[quoted text, click to view]

<snip>

[quoted text, click to view]

Yes, I can understand the confusion. I thought about that. Didn't make a
difference. In this case, it was always be a fixed-width font such as
Terminal, Courier, etc. It'll never be a variable width font such as Ariel.
Thus, the width of each character is exactly the same. Because I know this
in advance, I thought it to something of an optimization to measure the
string only once.

The exact string I want to draw could, indeed, be only a character, and not
a series of characters, thus, I choose to render each character at a time
(for better or worse), since each individual character will also have its
own attributes, such as color and background color and other things (whether
it is underlined (but not bold)).

I did try to look at MeasureCharacterRanges but couldn't figure out how to
extract the information I wanted. I'll try again when it's not 2am.


Thanks,
Shawn

bhavin
12/5/2005 10:28:56 AM
Shawn,
I see two problems in your approach.

1.
You are measuring the character "H" and using the same measure through
out. The width of each character is going to be different, and i think
you would need to measure the exact string that you wish to draw, to get
things right.

2.
MeasureString is anyway an approximation. It is probably adding some
width to each "string" you measure, which in your case is a character..
and when you couple it all together, the problem just gets compounded.

I am not sure of your final goal, but it would be better if you try to
measure the exact string that you want to draw.

Also try looking at the Graphics.MeasureCharacterRanges method. It is
supposed to give more accurate results, and might be more suited for you
if you really need to be working on a character by character basis.



[quoted text, click to view]
Shawn B.
12/5/2005 1:10:19 PM
[quoted text, click to view]

I read in the .NET 2.0 documentation that, for accurate font metrics, use
GDI. GDI+, it profusely cautions, is not as accurate as GDI+. What is the
purpose of GDI+ if it is so crippled (many other examples) compared to GDI,
further more, it doesn't appear to be regularily enhanced and less capable
(can't use bitmap fonts, for example).

In any case, this isn't academic, in the general sense. I'm creating a
console replacement that has more features than the standard console, such
as a text-mode GUI (ala TurboVision), more than 16 colors (So far I have a
color pallette of 256 colors (customizable), 16 being reserved to match the
same 16 of the system console, transparency (alpha-blending), bitmaps, and
so on. One might actually argue with me whether its even necessary. I'll
agree, its certainly not modern, but I have a fascination with console
applications and I'm wanting to experiment on a smaller scale some concepts
that are otherwise hidden from me by using someone elses (win32) API.
Besides, the drawing techniques are very similar in nature to a music
editing package I'm working on, and I'm experimenting with different
techniques.

In any case, I'm in the process of creating some massive network and
communications libraries and protocols, and I'm thinking about using the
console as the interface, and would rather it not be command-based, but
would like to experiment with making text-mode GUI's for this. I've already
created a console class that uses the system console API's to do the same,
except it is limited in colors to the standard 16 that the console API
supports, without transparancies, and no support for bitmaps and other
feature I'm putting into it that the console API doesn't directly support,
nor can I find an easy way to implement it. sush as drag-and-drop (with
itself and with windows) and so on. It is indeed an interesting excercise.
Also, I'm creating a tablature editing package for guitarists and was
thinking about creating some of my internal tools using the text-mode GUI
(others will probly never see the utilities). Just another way for me to
excercise my nuerons, is all.

I'm in the process of creating a TrueType font for the DOSAPP.FON fonts used
by the system console, since I don't like using "Lucida Console" very much
and I'm having a heck of a time locating one or a tool to convert them, all
the tools I found simply don't do good enough job, a joke at best.

I am creating the means to pipe in and out of the console (using named and
anonymous pipes) so it'll be pretty close to the original.

One of the major things, is that each character position on the screen needs
to be treated like a character position, not a string, to make this thing
work right. The mouse, as with the System Console, is accurate only to the
character, although, I must provide a way to make it accurate to the pixel
as well, but for text purposes, I must be able to predict the characture in
the grid.


Thanks,
Shawn

http://www.zenofdotnet.com

bhavin
12/5/2005 2:00:23 PM
I forgot to write it when i replying to your post, but i did think about
the fixed-width font, and so you are ok with measuring the width just
once i guess.

Here is an implementation of MeasureCharacterRanges that i use.
Feel free to modify it to suit yourself.
--------------------------------------------------------------------------------------
private static float MeasureStringLength(string text, Font font,Graphics g)
{
RectangleF rf = new RectangleF(
new PointF(0,0),g.MeasureString(text, font));
StringFormat sformat = new StringFormat();
sformat.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
CharacterRange[] cr = new CharacterRange[1];
cr[0] = new CharacterRange(0,text.Length);
sformat.SetMeasurableCharacterRanges(cr);
Region[] regions = new Region[1];
regions = g.MeasureCharacterRanges(text, font, rf, sformat);
rf = regions[0].GetBounds(g);
float stringLength = rf.Width;
return stringLength ;
}
--------------------------------------------------------------------------------------

Just like you i also was looking for precise string length (as precise
as possible anyway) and for now the result is acceptable to me.
It still does have its approximation problems, but it is atleast
supposed to be more accurate than MeasureString. Hopefully you will be
able to work something out using trial and error and some relatively
consistent adjustment factors.

Measuring string length, is considered to be a tricky one, and atleast
when i last researched it, could not get to a 100% accurate mechanism
used by anyone.

I'd be very interested in the final implementation you come up with,
even if it is for academic reasons :)

HTH
-bhavin


[quoted text, click to view]
Shawn B.
1/27/2006 10:25:36 AM
Although this is a dead issue, I found an interesting KB article (while
looking for something else) today that has good information.

http://support.microsoft.com/default.aspx?scid=kb;en-us;307208


Thanks,
Shawn



[quoted text, click to view]

AddThis Social Bookmark Button