DrawString ignores tabs when using overload with StringFormat - c#

I have a form which reads the text from a textbox and then draws it with the DrawString method from System.Drawing.Graphics.
Depending on which overload I use, the result is different. The overload that expects a additional StringFormat seems to ignore tabs. The overload without StringFormat works just fine.
The result looks like this:
This is the code:
public Form1()
{
InitializeComponent();
textBox1.AcceptsTab = true;
textBox1.Multiline = true;
}
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
Graphics g = this.CreateGraphics();
Font font = new Font("Arial", 20);
StringFormat format = StringFormat.GenericDefault;
format.Trimming = StringTrimming.None;
g.DrawLine(new Pen(Color.Black), new Point(10, 50), new Point(10, 140));
g.DrawString(textBox1.Text, font, Brushes.Black, new Point(10,60));
g.DrawString(textBox1.Text, font, Brushes.Black, new Point(10,100), format);
}
}
It seems that I could set the TabStops manually by calling format.SetTabStops(), but thats not a preferred solution.
I tried to set format.Trimming = StringTrimming.None but that doesn`t work.
Is there a possibility to print the tabs from the text while using the StringFormat overload?

Yeah, it looks like design fallacy of Graphics.DrawString — with having less-detailed overload and more-detailed overload, e.g.:
DrawString(String, Font, Brush, PointF)
DrawString(String, Font, Brush, PointF, StringFormat)
— it doesn't allow the more-detailed overload to be called in a way that fully resembles the behavior of the less-detailed overload.
For example:
calling graphics.DrawString(…, new StringFormat()) will make all tabs zero-width;
calling var stringFormat = new StringFormat(); stringFormat.SetTabStops(0, new float[] { 8 }); graphics.DrawString(…, stringFormat) will make all tabs to have width of 8 pixels, not 8 spaces;
of course, you can mangle something like var stringFormat = new StringFormat(); stringFormat.SetTabStops(0, new float[] { Font.SizeInPoints / 2 * e.Graphics.DpiX / 72 * 8 }); graphics.DrawString(…, stringFormat) — de facto it works (the specified tab-width coincides with the tab-width of StringFormat-less DrawString-call; last item in the float[] array is applied to all further tabs) — but no parts of documentation guarantee that it won't change in future.
Of course, technically calling a StringFormat-less DrawString overload is equivalent to passing null into StringFormat parameter — but that doesn't help in practice (e.g. if you want to specify some of StringFormat properties, but keep default tab-behavior).

Related

Some Unicode characters are not drawn using DrawString method

This is one of the strange behavior of csharp code under my testing.
I use DrawString method to draw some unicode character under Times New Roman Font. However, I can draw some characters and but some characters are just drawn as blank square (i.e. unsupported character.
To elaborate more, I can draw 42 (*) and 43 (+) without any problem using DrawString method.
But I can not draw 9978 and 9900. Both 9978 and 9900 are the supported characters by Times New Roman. Check the font map here.
https://www.martinstoeckli.ch/fontmap/fontmap.html
For your information, when I try to print the same characters in the text box window control, everything works fine. Textbox show all the supported characters including 9978 and 9900.
However, just this DrawString method is inconsistent under my testing. Why I can not draw these supported characters under the font using DrawString method ?
int unicodeIndex = 10728;
this._provider = new CultureInfo("en-us");
string unicodeString = ((char)unicodeIndex).ToString(_provider);
this.Font = new Font("Times New Roman", 10);
int x = 200;
int y = 200;
SizeF sizef = g.MeasureString(unicodeString+"W", this.Font, new PointF(x, y), _sformat);
int w = Convert.ToInt32(sizef.Width) + 2;
int h = Convert.ToInt32(sizef.Height) + 2;
RectangleF rectf = new RectangleF(x, y-h/2, w, h);
g.DrawString(unicodeString, this.Font, myBrush, rectf, myFormat);
Some more clue here as this seems tough problem.
I am currently using East Asian Language pack installed on my Window.
Is there any possibility that East Asian Language pack installed on my window can cause this problem ? For example, in localized window with Chinese and Japanese language, "Times New Roman" font might be not called correctly ? Just guess though.
I changed your code a bit and It's working well now:
private void Form1_Paint(object sender, PaintEventArgs g)
{
CultureInfo _provider = new CultureInfo("en-us");
String drawString = ((char)9900).ToString(_provider);
Font drawFont = new Font("Times New Roman", 16);
SolidBrush drawBrush = new SolidBrush(Color.Black);
float x = 150.0F;
float y = 50.0F;
StringFormat drawFormat = new StringFormat();
g.Graphics.DrawString(drawString, drawFont, drawBrush, x, y, drawFormat);
}
Times New Roman (any version, I'm quite sure) actually does not have the characters in question.
The second sentence on https://www.martinstoeckli.ch/fontmap/fontmap.html says:
What you see on this page is all declared as font Arial, though the operating system and the browser can substitute missing characters with characters from other fonts.
What you're seeing on that webpage is font substitution.

How can I set font height in system.drawings.font?

I'm using C# to write a text in a certain format. My problem is that when I edit font size both width and height are changing while I just want to change the font height.
My code:
using (Graphics graphics = Graphics.FromImage(bitmap))
{
using (System.Drawing.Font romanfont = new System.Drawing.Font("Times New Roman",11, FontStyle.Bold))
//using (System.Drawing.Font romanfont = new System.Drawing.Font("Times New Roman", 11, FontStyle.Bold))
{
SolidBrush transBrush = new SolidBrush(Color.FromArgb(65, 79, 79));
StringFormat format = new StringFormat(StringFormatFlags.DirectionRightToLeft);
graphics.DrawString(firstname, romanfont, transBrush, firstnameLocation, format);
graphics.DrawString(secondname, romanfont, transBrush, secondnameLocation, format);
graphics.DrawString(finalfirstadd, romanfont, transBrush, firstaddresslocation, format);
graphics.DrawString(finalsecondadd, romanfont, transBrush, secondaddresslocation, format);
}
}
You can achieve this effect by setting a transform on the Graphics object.
For example, if you want to make the text twice as tall but still the same width, you can do this:
graphics.scaleTransform(1, 2);
You would put this anywhere above the place where you draw your strings. Note that this change will make everything twice as tall, so you may need to adjust your positions and sizes of your rectangles (such as firstnameLocation; in this case you'd probably want to divide the top and height of the rectangle by 2.)

Bold Font is Rendered Wrong

When I try to render a Chinese string like 试标记好不好 Graphics.DrawString draws it
even if I change the Font Linking to SimSun. On the other hand TextRenderer works but it fails to render readable strings when bold fonts are used. It seems there is no correct way to render bold strings.
The issue is described in greater detail here. Am I doing something wrong or does Windows not support professional looking localizable applications with some bold strings in the UI?
The code to repro the issue is for a Windows Forms application with two PictureBoxes:
const string combined = "测试标记好不好This is a aber long";
private void cFontSize_ValueChanged(object sender, EventArgs e)
{
Bitmap bmp = new Bitmap(750, 140);
Font f = new Font("Arial", (float)cFontSize.Value, IsBold ? FontStyle.Bold : FontStyle.Regular);
using (var g = Graphics.FromImage(bmp))
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
// Rendering with a Background Color solves the issue but
// this would produce boxes of white or black color in the displayed image which looks even worse
TextRenderer.DrawText(g, combined, f, new Point(0, 0), FontColor);
}
cPictureBox.Image = bmp;
Bitmap bmp2 = new Bitmap(750, 140);
using (var g = Graphics.FromImage(bmp2))
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
g.DrawString(combined, f, FontBrush, 0, 0);
}
cPicture2.Image = bmp2;
}
Update 1:
When I add as Font Link Setting to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink
Arial Bold
SIMSUNB.TTC,SimSun Bold
MSGOTHIC.TTC,MS UI Gothic
then Graphics.DrawString looks ok now althogh TextRender now gets problems. After restarting the application now both outputs look font wise ok although TextRenderer still has the problem that bold fonts become unreadable due to anti aliasing with black. I will restart the machine to check out any caching effects.

Why TextRenderer adds margins while measuring text?

Because of lack of Graphics object in certain places in my application, I decided to use TextRenderer class. What is quite surprising though is that it adds a lot of margins to measured text. For example:
private void button1_Click(object sender, EventArgs e) {
using (var g = this.CreateGraphics()) {
Font font = new Font("Calibri", 20.0f, GraphicsUnit.Pixel);
Size size = TextRenderer.MeasureText("Ala ma kota", font);
g.DrawRectangle(Pens.Red, new Rectangle(new Point(10, 10), size));
TextRenderer.DrawText(g, "Ala ma kota", font, new Point(10, 10), Color.Black);
}
}
Gives the following result:
Why does it do so? Is there a way to force it to get the real text size? (and of course draw it in the same rectangle it returns)
From MSDN:
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, use the versions of DrawText and MeasureText that take a Size and TextFormatFlags parameter, as shown in the example.
You must also pass the Graphics object for correct results, because:
This overload of MeasureText(String, Font, Size, TextFormatFlags) will ignore a TextFormatFlags value of NoPadding or LeftAndRightPadding. If you are specifying a padding value other than the default, you should use the overload of MeasureText(IDeviceContext, String, Font, Size, TextFormatFlags) that takes a IDeviceContext object.
Size size = TextRenderer.MeasureText(g,
"Ala ma kota",
font,
new Size(int.MaxValue, int.MaxValue),
TextFormatFlags.NoPadding);
TextRenderer.DrawText(g, "Ala ma kota", font,
new Point(10, 10),
Color.Black,
TextFormatFlags.NoPadding);
g.DrawRectangle(Pens.Red, new Rectangle(new Point(10, 10), size));
Also have a look at using the Graphics methods directly: GDI+ MeasureString() is incorrectly trimming text

How can I underline some part of a multi-line text with GDI?

I am using Graphics.DrawString to draw my usercontrol's text like this:
protected override void OnPaint(PaintEventArgs e)
{
RectangleF bounds = DisplayRectangle;
bounds.Inflate(-4, -4); // Padding
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Near;
format.LineAlignment = StringAlignment.Near;
format.Trimming = StringTrimming.None;
using (Brush bFore = new SolidBrush(ForeColor))
{
g.DrawString(Text, Font, bFore, bounds, format);
}
}
If control's Text is wider than the DisplayRectangle, DrawString nicely breaks the Text into multiple lines at word boundaries.
Now I want to underline some words from Text, but I couldn't work it out. I tried splitting the Text, then MeasureString the string just before an underlined part starts, DrawString the normal part, then DrawString the underlined part. But this works only if Text is single-line.
I am sure using a child LinkLabel or RichTextBox to render my control's text will solve this, but I don't like the idea of using a child control just to underline a few words. Is there another way?
This is a crude example that will work using the string split into parts and two different font styles, rather than drawing the underline separately ( though that would work too ). In actual practice, I would recommend splitting the text by word, not by phrase, and dealing with each word individually, in a loop. Otherwise, like in this example, the line-breaking doesn't work quite right.
Dim fntNormal As New Font(myFontFamily, myFontSize, FontStyle.Regular, GraphicsUnit.Pixel)
Dim fntUnderline As New Font(myFontFamily, myFontSize, FontStyle.Underline, GraphicsUnit.Pixel)
g.DrawString("This is ", fntNormal, Brushes.Black, rctTextArea)
w1 = g.MeasureString("This is ", fntNormal).Width
w2 = g.MeasureString("underlined", fntUnderline).Width
If w1 + w2 > rctTextArea.Width Then
yPos = rctTextArea.Y + g.MeasureString("This is ", fntNormal).Height + 5
xPos = rctTextArea.X
Else
yPos = rctTextArea.Y
xPos = 0
End If
g.DrawString("underlined", fntUnderline, Brushes.Black, xPos, yPos)
w1 = g.MeasureString("underlined", fntUnderline).Width
w2 = g.MeasureString(", and this is not.", fntNormal).Width
If w1 + w2 > rctTextArea.Width Then
yPos += g.MeasureString("underlined", fntUnderline).Height + 5
xPos = rctTextArea.X
Else
xPos = 0
End If
g.DrawString(", and this is not.", fntNormal, Brushes.Black, xPos, yPos)
This code can really be cleaned up and made more efficient to let you loop through each word in your text string.
This example also does not include any code to check if you have exceeded the vertical limit of your bounds rectangle.
Sorry for the VB code, I just noticed your question was in C#.

Categories