WinForms: Measure Text With No Padding - c#

In a WinForms app, I am trying to measure the size of some text I want to draw with no padding. Here's the closest I've gotten...
protected override void OnPaint(PaintEventArgs e) {
DrawIt(e.Graphics);
}
private void DrawIt(Graphics graphics) {
var text = "123";
var font = new Font("Arial", 32);
var proposedSize = new Size(int.MaxValue, int.MaxValue);
var measuredSize = TextRenderer.MeasureText(graphics, text, font, proposedSize, TextFormatFlags.NoPadding);
var rect = new Rectangle(100, 100, measuredSize.Width, measuredSize.Height);
graphics.DrawRectangle(Pens.Blue, rect);
TextRenderer.DrawText(graphics, text, font, rect, Color.Black, TextFormatFlags.NoPadding);
}
... but as you can see from the results ...
... there is still a considerable amount of padding, particularly on the top and bottom. Is there any way to measure the actual bounds of the drawn characters (with something really awful like printing to an image and then looking for painted pixels)?
Thanks in advance.

(I've marked this answer as "the" answer just so people know it was answered, but #TaW actually provided the solution -- see his link above.)
#TaW - That was the trick. I'm still struggling to get the text to go where I want it to, but I'm over the hump. Here's the code I ended out with...
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
DrawIt(e.Graphics);
}
private void DrawIt(Graphics graphics) {
var text = "123";
var font = new Font("Arial", 40);
// Build a path containing the text in the desired font, and get its bounds.
GraphicsPath path = new GraphicsPath();
path.AddString(text, font.FontFamily, (int)font.Style, font.SizeInPoints, new Point(0, 0), StringFormat.GenericDefault);
var bounds = path.GetBounds();
// Move it where I want it.
var xlate = new Matrix();
xlate.Translate(100, 100);
path.Transform(xlate);
// Draw the path (and a bounding rectangle).
graphics.DrawPath(Pens.Black, path);
bounds = path.GetBounds();
graphics.DrawRectangle(Pens.Blue, bounds.Left, bounds.Top, bounds.Width, bounds.Height);
}
... and here is the result (notice the nice, tight bounding box) ...

Have you tried
Graphics.MeasureString("myString", myFont, int.MaxValue, StringFormat.GenericTypographic)

Related

Calculate x offset on FormattedText instance with TextAlignment.Center

I've a class which inherits from Shape and also needs to precisely draw some multiline text within the OnRender(DrawingContect drawingContext) method.
I can fill a rectangle which exactly fills the rectangular size of the text:
And the relevant simplified code snippet:
protected override void OnRender(DrawingContext drawingContext)
{
...
var formattedText = new FormattedText(
Text,
CultureInfo.CurrentCulture,
CultureInfo.CurrentCulture.TextInfo.IsRightToLeft
? FlowDirection.RightToLeft
: FlowDirection.LeftToRight,
TypeFace,
FontSize,
TextBrush
);
formattedText.TextAlignment = TextAlignment.Left;
formattedText.Trimming = TextTrimming.CharacterEllipsis;
formattedText.SetFontWeight(FontWeight);
formattedText.MaxTextWidth = Width;
formattedText.MaxTextHeight = Height;
...
DrawShape(
drawingContext,
new List<Point>
{
new Point(0, 0),
new Point(formattedText.Width, 0),
new Point(formattedText.Width, formattedText.Height),
new Point(0, formattedText.Height)
},
brush,
pen
);
drawingContext.DrawText(formattedText, new Point(0, 0));
...
}
void DrawShape(DrawingContext dc, List<Point> points, Brush fill, Pen pen)
{
var streamGeometry = new StreamGeometry();
using (var ctx = streamGeometry.Open())
{
ctx.BeginFigure(points[0], true, true);
foreach (var point in points)
{
ctx.LineTo(point, true, true);
}
}
streamGeometry.Freeze();
dc.DrawGeometry(fill, pen, streamGeometry);
}
My problem is when I try to use the same code above but with TextAlignment.Center I'm unable to correctly position that rectangle behind the text:
How can I get the x offset to correctly draw that rectangle?
This isn't what I'm trying to achieve, but is a simplified example which highlights the issue.
There are two properties, OverhandLeading and OverhandTrailing which provide this information:
I overlooked these originally as they weren't giving me the expected behaviour, but turns out I had some incorrect logic to calculate my rotation centre point on my real shape.
So, for the example above, the points would be:
new Point(formattedText.OverhangLeading, 0),
new Point(formattedText.Width - formattedText.OverhangTrailing, 0),
new Point(formattedText.Width - formattedText.OverhangTrailing, formattedText.Height),
new Point(formattedText.OverhangLeading, formattedText.Height)

Winforms Transformation

The current application am developing has lot of drawings. The Origin of the drawing start from Left,Bottom instead of Top,Left. Drawings works perfectly except "DrawingString".
Graphics g;
g.TranslateTransform(0, Height);
g.ScaleTransform(1, -1);
//All drawings
g.DrawString("1", new Font("Segoei UI", 9), Brushes.Green, new Point(x, y));
The result I get is upside down Text [![enter image description here][1]][1]
I wanted to draw only the text normal and rest of the drawings should always start from the bottom left?
EDIT
private void panel1_Paint(object sender,PaintEventArgs e)
{
var g = e.Graphics;
var height = panel1.Height;
g.TranslateTransform(0,height);
g.ScaleTransform(1,-1);
g.DrawRectangle(new Pen(Brushes.Black,1),new Rectangle(10,10,100,100));
g.DrawString("Test",new Font("Segoei UI",9),Brushes.Green,new Point(10,110));
}
RESULT
I want only the text to be flipped. Keeping the drawing as it is
Flip it over again.
See the example below. The text Sunday appears normally. Then we transpose the graphics object's matrix and write Monday, so Monday appears laterally inverted on the Y-axis, and finally we flip over the Y-axis yet again so it restores itself to its original state before writing Tuesday, which appears normally.
private void Form1_Paint(object sender, PaintEventArgs e)
{
var graphics = e.Graphics; // this.CreateGraphics();
var font = new Font("Georgia", 12.0F);
var brush = new SolidBrush(Color.Black);
var pointF = new PointF(20F, 20F);
graphics.DrawString("Sunday", font, brush, pointF);
graphics.ScaleTransform(1F, -1F);
pointF = new PointF(10F, -210F);
graphics.DrawString("Monday", font, brush, pointF);
graphics.ScaleTransform(1, -1);
pointF = new PointF(200F, 200F);
graphics.DrawString("Tuesday", font, brush, pointF);
brush.Dispose();
font.Dispose();
}
Do that in your code, making sure to calculate the value of the Y-axis where you want your text to appear.
g.ScaleTransform(1,-1);
g.DrawRectangle(new Pen(Brushes.Black,1),new Rectangle(10,10,100,100));
g.ScaleTransform(1,-1);
g.DrawString("Test",new Font("Segoei UI",9),Brushes.Green,new Point(10, -110));

Position Of String Using StringFormat

Is there a way to get the position of a drawn string in a control? I'm writing a control that acts as a GroupBox and I when I draw the string that acts as the title, the border line strikes through it.
In order to fix this problem, I was just going to fill and rectangle where the title is with a back color so the title doesn't appear to be struck through. My problem is that the position of the title is dictated by the StringFormat I pass through the Graphics.DrawString() method. I don't have to explicitly declare the location of the string, but I do have to explicitly declare the location of the rectangle and I don't know where the location of the string is.
How should I go about this?
I believe you can use MeasureCharacterRanges to accurately measure string position
here is a little sample:
The method for doing measurement:
public static RectangleF MeasureStringBounds(Graphics graphics, string text, Font font,RectangleF bounding, StringFormat format)
{
var ranges =new[] {new CharacterRange(0, text.Length)};
format.SetMeasurableCharacterRanges(ranges);
var regions = graphics.MeasureCharacterRanges(text, font, bounding, format);
var accurateBoundings = regions[0].GetBounds(graphics);
return accurateBoundings;
}
Usage:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var str = "hello";
var format = new StringFormat {Alignment = StringAlignment.Center};
e.Graphics.DrawString(str, Font, new SolidBrush(Color.Black), new Rectangle(0, 0, Width, Height), format);
//measuring part
var region = MeasureStringBounds(e.Graphics, str, Font, new RectangleF(0, 0, Width, Height), format);
//Draw measured region
e.Graphics.DrawRectangle(new Pen(Color.Red), region.X, region.Y, region.Width, region.Height);
}

C#: Using drawstring to annotate images works but not able to get text in bottom left corner, only bottom right

I'm making a batch watermarking tool for myself and some others at work and using the following code allows me to annotate text on the bottom right of the image but I'm not able to make it annotate on the bottom left without manually adjusting the coordinates which differs for any given image. Also changing StringAlignment.Far to StringAlignment.Near etc doesn't do anything but possibly annotate the text outside the image somewhere that doesn't show up.
MSDN has some explanation but it is not helping me. Any help would be great I've been fighting this for some time now.
private void button1_Click(object sender, EventArgs e)
{
foreach (string images in Directory.GetFiles(textBox1.Text))
{
System.Drawing.Image img = System.Drawing.Image.FromFile(images);
Graphics gr = Graphics.FromImage(img);
Font font = new Font("Times New Roman", (float)25,
System.Drawing.FontStyle.Regular);
System.Drawing.Color color = System.Drawing.Color.Red;
StringFormat stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Far;
stringFormat.LineAlignment = StringAlignment.Far;
gr.SmoothingMode = SmoothingMode.AntiAlias;
gr.DrawString("WATERMARK GOES HERE"+ images, font,
new System.Drawing.SolidBrush(color),
new System.Drawing.Point(img.Width - 0, img.Height - 0),
stringFormat);
MemoryStream outputStream = new MemoryStream();
img.Save(images+"Stamped.jpg");
}
MessageBox.Show("done");
}
Name your controls. Don't use "button1", "textbox1", etc.
Use the "using" statement. Writing "System.Drawing.Point" and other fully qualified names just increases the size of your code and makes it harder to read.
You are creating a new instance of a SolidBrush class for each image you are watermarking. You should create the brush before the loop and just use it in the loop, then dispose of it afterwards.
Your declaration of a MemoryStream does nothing and is used nowhere.
As for the watermarking itself, you should decide if you want it to scale with image size, or to be a consistent size. Or you can have it have a maximum/minimum size. That's your preference.
private void watermark_btn_Click(object sender, EventArgs e)
{
string watermarkText = "ShowThisWatermark";
using (Font font = new Font("Times New Roman", (float)25, FontStyle.Regular))
using (SolidBrush brush = new SolidBrush(Color.Red))
foreach (string file in Directory.GetFiles(directory_txt.Text))
{
try
{
Bitmap b = new Bitmap(file);
using (Graphics g = Graphics.FromImage(b))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
SizeF measuredSize = g.MeasureString(watermarkText, font);
// Use this to watermark the bottom-left corner
g.DrawString(watermarkText, font, brush, 0, b.Height - measuredSize.Height);
// Use this to watermark the bottom-right corner
g.DrawString(watermarkText, font, brush, b.Width - measuredSize.Width, b.Height - measuredSize.Height);
}
b.Save(Path.GetFileNameWithoutExtension(file) + "_stamped" + Path.GetExtension(file));
}
catch
{
continue;
}
}
}
The try/catch is a lazy way of skipping files which aren't images. Since Directory.GetFiles returns all files in the directory, a non-image file would cause an exception. This could be done in a much neater fashion, but since that was not the nature of your question I kept it simple.

Graphics DrawString to Exactly Place Text on a System.Label

I have overridden the OnPaint method of my Label control in VS2008:
void Label_OnPaint(object sender, PaintEventArgs e) {
base.OnPaint(e);
dim lbl = sender as Label;
if (lbl != null) {
string Text = lbl.Text;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
if (myShowShadow) { // draw the shadow first!
e.Graphics.DrawString(Text, lbl.Font, new SolidBrush(myShadowColor), myShadowOffset, StringFormat.GenericDefault);
}
e.Graphics.DrawString(Text, lbl.Font, new SolidBrush(lbl.ForeColor), 0, 0, StringFormat.GenericDefault);
}
}
This works, but I really want to find out how to center the text both vertically and horizontally. I've heard of the MeasureString() method, but my "Text" complicates matters because it could include page breaks.
Could someone guide me with how to do this?
Alternatively you can create your own StringFormat object and pass it in using an overload of DrawString that supports a RectangleF:
StringFormat formatter = new StringFormat();
formatter.LineAlignment = StringAlignment.Center;
formatter.Alignment = StringAlignment.Center;
RectangleF rectangle = new RectangleF(0, 0, lbl.Width, lbl.Height);
e.Graphics.DrawString(Text, lbl.Font, new SolidBrush(lbl.ForeColor), rectangle, formatter);
You can call TextRenderer.DrawText with the HorizontalCenter and VerticalCenter flags.
Here is the code i'm using at the moment,
SizeF size;
string text = "Text goes here";
size = e.Graphics.MeasureString(text, font);
x = (lineWidth / 2) - (size.Width / 2);
y = top;
e.Graphics.DrawString(text, font, Brushes.Black, x, y);
I just wanted to add (a year later) a tool I created because StringAlignment turned out to be not very dependable. It turns out to be very similar to Neo's version.
The code below does an excellent job of centering the text both vertically and horizontally. Also, I wrote it with various overloads so that different options could be supplied to make this control behave exactly like I want.
Here are my overloads:
private static void DrawCenter(Label label, Graphics graphics) {
DrawCenter(label.Text, label, label.Location, label.ForeColor, graphics);
}
private void DrawCenter(string text, Label label, Graphics graphics) {
DrawCenter(text, label, label.Location, label.ForeColor, graphics);
}
private static void DrawCenter(string text, Label label, Point location, Graphics graphics) {
DrawCenter(text, label, location, label.ForeColor, graphics);
}
private static void DrawCenter(string text, Label label, Point location, Color fontColor, Graphics graphics) {
Rectangle rect = new Rectangle(location, label.Size);
SizeF lSize = graphics.MeasureString(text, label.Font, rect.Width);
PointF lPoint = new PointF(rect.X + (rect.Width - lSize.Width) / 2, rect.Y + (rect.Height - lSize.Height) / 2);
graphics.DrawString(text, label.Font, new SolidBrush(fontColor), lPoint);
}
To use these for the Label's OnPaint event, simply modify my original code in the question to following:
private void Label_OnPaint(object sender, PaintEventArgs e) {
base.OnPaint(e);
Label lbl = sender as Label;
if (lbl != null) {
string txt = lbl.Text;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
if (myShowShadow) { // draw the shadow first!
Point offset = new Point(lbl.Location.X - 1, lbl.Location.Y - 1)
DrawCenter(txt, lbl, offset, myShadowColor, e.Graphics);
}
DrawCenter(lbl, e.Graphics);
}
}
For a Print_Document event, I have a version that will also print a box around the label if there is already a box around it in the designer:
private static void DrawCenter(string text, Label label, Point location, Color fontColor, Graphics graphics) {
Rectangle rect = new Rectangle(location, label.Size);
SizeF lSize = graphics.MeasureString(text, label.Font, rect.Width);
PointF lPoint = new PointF((rect.Width - lSize.Width) / 2, (rect.Height - lSize.Height) / 2);
graphics.DrawString(text, label.Font, new SolidBrush(fontColor), lPoint);
if (label.BorderStyle != BorderStyle.None) {
using (Pen p = new Pen(Color.Black)) {
graphics.DrawRectangle(p, rect);
}
}
}
If you find this at all useful, give me a +1.
~Joe

Categories

Resources