I've been using a monospaced font to render a table onto a graphics object, and to align the columns of text I'd been padding the text using space characters.
I've been asked to change the font to a proportional font, and no surprise, but the columns don't line up anymore.
If given a string such as
"Bill-of-Material Edits\r\n------------------------------\r\n200 510024 Door 24\" x 58\"\r\n 3 530058 Panel 58\" x 58\"\r\n";
how do I ensure that the 3 lines up properly under the one's place on the 200 value from the line above, and subsequently have the 510024 sit directly above the 530058?
Here's the code I use to draw the string:
var fnt = new Font(FontFamily.GenericSansSerif, 10, FontStyle.Regular, GraphicsUnit.Point);
StringFormat strFormat = new StringFormat(StringFormat.GenericTypographic);
strFormat.Alignment = StringAlignment.Near;
string text = "Bill-of-Material Edits\r\n------------------------------\r\n200 510024 Door 24\" x 58\"\r\n 3 530058 Panel 58\" x 58\"\r\n";
g.DrawString(text, fnt, Brushes.Black, new RectangleF(10f, 10f, 38.1062851f, 12.9231777f), strFormat);
I tried replacing the spaces with other characters such as unicode control characters (0x0080) to no avail. I've also tried using string.Format() with formatters like {0,10} which didn't help either.
What do I do to get my columns to line up?
You can't make it work like this. Draw each individual string in its column, pass the Rectangle of the column. How wide you make each column is up to you.
Related
I need to draw a string in a panel perfectly centered, horizontally and vertically.
Centering horizontally is not a big deal using the MeasureString function.
Bug, the MeasureString function returns a height that takes care of every possible char (like P and p), but not the real height of the actual string.
Here is the sample code I'm using :
using (Graphics gr = Graphics.FromImage(mBackImage))
{
gr.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
gr.FillRectangle(new SolidBrush(BackColor), new Rectangle(0, 0, this.Width, this.Height));
var f = new Font(Font.FontFamily, this.ClientSize.Height, FontStyle.Bold, GraphicsUnit.Pixel);
var sz = gr.MeasureString(Label, f);
var pt = new PointF((this.ClientSize.Width - sz.Width) / 2, (this.ClientSize.Height - sz.Height) / 2);
gr.DrawString(Label, f, new SolidBrush(ForeColor), pt);
}
My target is to draw numbers, and the result is that numbers are drawn a little bit too high.
Even if I change the text from "10" to "P", to "p", and I see that the text stays aligned on a "base line".
I really would like to center the text, depending on the real footprint.
How can I achieve this?
Note: this question is absolutely not duplicate of "Center text for receipt printing", because I'm talking about vertical centering, not horizontal centering.
MeasureString or MeasureCharacterRanges are returning sizes taller than the real printed chars, causing vertical alignment issues.
Bitmap bmpChar = new Bitmap(16,16);
FontFamily fontFamily = new FontFamily("Arial");
Font font = new Font(
fontFamily,
16,
FontStyle.Regular,
GraphicsUnit.Pixel);
Graphics g = Graphics.FromImage(testBmp);
g.DrawString("test", font, Brushes.Red, 0, 0);
Upper code prints two characters in 16x16 area(it has "te" of "test" while I expected only a "t"). Whats could be a platform-independent(32-bit, 64bit, NT, XP, 7, 10) way to have constant width characters for all letters and numbers when drawing them as strings onto a bitmap, in winforms?
I would use a monospaced font if it's possible. Otherwise you'd just be either stretching the letters out after rasterization or having to calculate the amount of spacing to put in between each letter which would be a lot more complicated.
Okay, so I have been working on something for a little while and I have gotten to the point where I am planning the Text rendering part.
I can already draw strings of text in two ways; DrawString and TextRenderer.DrawText. I prefer DrawText since measuring text is more accurate when using TextRenderer.Measure text.
I have a class:
public class Character
{
public string character {get; set; }
public Font font {get; set; }
public Point position {get; set; }
}
And a list of all characters pressed:
public List<Character> chars = new List<Character>();
Now my problem is that I need to be able to set a different font and color and boldness or italicization to any given selected characters or words at runtime. So I can't just draw a whole string because then there'd be no way for me to set individual font settings for each character the user has selected to change.
So I need to be able to store different font style info for each character and then add them all to a list so I can kinda go through each one and draw each one as it should be drawn (I. E. each char having its own style etc).
This solution works fine for me. And since I've not been able to find any info about this anywhere for months, I'm totally stuck.
My main problem is that because I am drawing char by char, I have no idea how far apart each character should be from the previously drawn character (kerning).
For input (text box) controls, how can we custom draw text and allow the user to make a part of a word blue, and the other half of the word a different size and color and style, for example, while still adhering to proper kerning settings?
How do we know where to draw each character?
People have said just keep restarting the whole string at once. But that doesn't solve my initial problem. I need to be able to draw each char one by one so I can save font info about it.
Kerning and Character Spacing are different and if you want to have complete control over what your code prints you may need to implement both.
Let's look at an example output first :
Image one shows direct output with an extra character spacing of 1 pixel, no kerning.
Image two has some kerning applied, but only for three kerning pairs.
I have tried to make things clearer by also drawing the result of the characterwise text measurements. Also there is a tiled 1 pixel raster as the panel BackgroundImage. (To see it better you may want to download the png files!)
private void panel2_Paint(object sender, PaintEventArgs e)
{
string fullText = "Text;1/2' LTA";
StringFormat strgfmt = StringFormat.GenericTypographic;
Font font = new Font("Times", 60f, FontStyle.Regular);
float x = 0f;
using (SolidBrush brush = new SolidBrush(Color.FromArgb(127, 0, 127, 127)))
{
for (int i = 0; i < fullText.Length; i++)
{
string text = fullText.Substring(i, 1);
SizeF sf = e.Graphics.MeasureString(text, font, 9999, strgfmt );
e.Graphics.FillRectangle(brush, new RectangleF(new PointF(x, 0f), sf));
e.Graphics.DrawString(text, font, Brushes.Black, x, 0, strgfmt );
x += sf.Width + 1; // character spacing = +1
//if (i < fullText.Length - 1) doKerning(fullText.Substring(i, 2), ref x);
}
}
}
void doKerning(string c12, ref float x)
{
if (smallKerningTable.ContainsKey(c12)) x -= smallKerningTable[c12];
}
Dictionary<string, float> smallKerningTable = new Dictionary<string, float>();
void initKerningTable()
{
smallKerningTable.Add("Te", 7f);
smallKerningTable.Add("LT", 8f);
smallKerningTable.Add("TA", 11f);
//..
}
This is how the background is created:
public Form1()
{
InitializeComponent();
Bitmap bmpCheck2 = new Bitmap(2, 2);
bmpCheck2.SetPixel(0, 0, Color.FromArgb(127, 127, 127, 0));
panel2.BackgroundImage = bmpCheck2;
panel2.BackgroundImageLayout = ImageLayout.Tile;
//..
}
If you want to use kerning you will need to build a much longer kerning table.
In real life typographers and font designers do that manually, looking hard at the glyphs, tweaking the kerning until it looks real good.
That is rather expensive and still doesn't cover font mixes.
So you may want to either
not use kerning after all. Make sure to use the StringFormat.GenericTypographic option both for measuring and for drawing the strings!
create a small kerning table for some of the especially problematic characters, like 'L', 'T', 'W', "V' and 'A'..
write code to create a full kerning table for all pairs you need or..
for all pairs
To write code to create a kerning table you would:
Create a Bitmap for each charcter
Iterate over all pairs and
move the second bitmap to the left until some non-transparent/ black pixels collide.
the moving should not got further than, say, half of the width, otherwise the distance should be reset to 0, because some character pairs will not collide at all and should not have any kerning, e.g.: '^_' or '.-'
If you want to mix fonts and /or FontStyles the key to the kerning table would have to be expanded to include some ID of the two respective fonts&styles the characters have..
How can get the LineHeight of a Font given the FontSize? It seems that it is different depending on the font and not necessarily connected to the FontSize. I am using BlockLineHeight for the LineStackingStrategy.
Clarification. I understand there are methods of determining the total line height. In this case, I'm looking for the height from the baseline to the top of the font (so minus the tails of the p's etc.)
In the case of the picture above. I want the ascent.
FontFamily fontFamily = new FontFamily("Arial");
Font font = new Font(fontFamily, 16, FontStyle.Regular, GraphicsUnit.Pixel);
ascent = fontFamily.GetCellAscent(FontStyle.Regular);
ascentPixel = font.Size * ascent / fontFamily.GetEmHeight(FontStyle.Regular);
from:
http://msdn.microsoft.com/en-us/library/xwf9s90b.aspx
If you are using a Graphics object to draw on and have reference to, then you can do this.
Font myFont = new Font("Verdana", 15);
SizeF fontSize = e.Graphics.MeasureString("my text", myFont);
This will then tell you the height and width of the string. You can use this for a singluar line to test the line height.
Or by this answer here: How to calculate font height in WPF?
You can easily calculate the line height using some simple calculations.
I'm working on a project that has me approximating text rendered as an image and a DHTML editor for the text. The images are rendered using the .NET 4 DrawingContext object's DrawText method.
The DrawText method will take text along with font information as well as dimensions and calculate the wrapping necessary to get the text to fit as much as possible, placing an ellipsis at the end if the text is too long. So, if I have the following code to draw text in a Rectangle it will abbrevaiate it:
string longText = #"A choice of five engines, although the 2-liter turbo diesel, supposedly good for 48 m.p.g. highway, is not coming to America, at least for now. A 300-horsepower supercharged gasoline engine will likely be the first offered in the United States. All models will use start-stop technology, and fuel consumption will decrease by an average of 19 percent across the A6 lineup. A 245-horsepower A6 hybrid was also unveiled, but no decision has yet been made as to its North America sales prospects. Figure later in 2012, if sufficient demand is detected.";
var drawing = new DrawingGroup();
using (var context = drawing.Open())
{
var text = new FormattedText(longText,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Calibri"),
30,
Brushes.Green);
text.MaxTextHeight = myRect.Height;
text.MaxTextWidth = myRect.Width;
context.DrawText(text, new Point(0, 0));
}
var db = new DrawingBrush(drawing);
db.Stretch = Stretch.None;
myRect.Fill = db;
Is there a way to calculate how the text will be wrapped? In this example, the outputted text is wrapped at "2-liter" and "48 m.p.g" etc as seen in the image below:
You can use the Graphics.MeasureString(String, Font, Int32) function. You pass it the string, font, and maximum width. It returns a SizeF with the rectangle it would form. You can use this to get the overall height, and thus the number of lines:
Graphics g = ...;
Font f = new Font("Calibri", 30.0);
SizeF sz = g.MeasureString(longText, f, myRect.Width);
float height = sz.Height;
int lines = (int)Math.round(height / f.Height); // overall height divided by the line height = number of lines
There are many ways to get a Graphics object, and any will do since you are only using it to measure and not to draw (you may have to correct its DpiX, DpiY, and PageUnit fields since those effect measurements.
Ways to get a Graphics object:
Graphics g = e.Graphics; // in OnPaint, with PaintEventArgs e
Graphics g = x.CreateGrahics(); // where x is any Form or Control
Graphics g = Graphics.CreateFrom(img); // where img is an Image.
Not sure if you still need a solution or if this particular solution is appropriate for your application, but if you insert the below snippet just after your using block it will show you the text in each line (and therefore where the text was broken for wrapping).
I arrived at this solution using the very ghetto/guerrilla approach of just browsing properties while debugging, looking for the wrapped text segments - I found 'em and they were in accessible properties...so there you go. There very well may be a more proper/direct way.
// Object heirarchy:
// DrawingGroup (whole thing)
// - DrawingGroup (lines)
// - GlyphRunDrawing.GlyphRun.Characters (parts of lines)
// Note, if text is clipped, the ellipsis will be placed in its own
// separate "line" below. Give it a try and you'll see what I mean.
List<DrawingGroup> lines = drawing.Children.OfType<DrawingGroup>().ToList();
foreach (DrawingGroup line in lines)
{
List<char> lineparts = line.Children
.OfType<GlyphRunDrawing>()
.SelectMany(grd => grd.GlyphRun.Characters)
.ToList();
string lineText = new string(lineparts.ToArray());
Debug.WriteLine(lineText);
}
Btw, Hi David. :-)