I need to print numbers where some of the digits in the middle are emphasized by increasing the font size and weight. In the example below, 456
is emphasized.
The font and the two sizes used are user-configurable.
The current code does this using three calls to Graphics.DrawString(...)
.
The problem I am having is that with most fonts, I am seeing off-by-1pixel problems (relative to the gray line, the 456
is sitting an extra pixel higher that the other digits):
I've attached some debugging dump (of the Bob Powell formula) for various fonts at the bottom of my post. The other techniques yielded similar results.
In order to print text on a common baseline, one needs to calculate the baseline offset for a particular Font
. I've tried using three techniques:
First, MSDN's code: http://msdn.microsoft.com/en-us/library/xwf9s90b(v=vs.80).aspx
ascent = fontFamily.GetCellAscent(FontStyle.Regular);
ascentPixel = font.Size * ascent / fontFamily.GetEmHeight(FontStyle.Regular)
Second, code from: Using GDI+, what's the easiest approach to align text (drawn in several different fonts) along a common baseline?
Finally, code from Bob Powell's post: Formatting text on a common baseline.
Here's my draw method:
private void DrawOnBaseline(Graphics g, string text, FontWithBaseline fwb, Brush brush, float x, float y) {
g.DrawString(text, fwb.Font, brush, x, y - fwb.Baseline, StringFormat.GenericTypographic);
}
Where FontWithBaseline
simply associates a Font with its respective baseline calculation:
public class FontWithBaseline {
private Font m_font;
private float m_baseline;
public FontWithBaseline(Font font) {
m_font = font;
m_baseline = CalculateBaseline(font);
}
public Font Font { get { return m_font; } }
public float Baseline { get { return m_baseline; } }
private static float CalculateBaseline(Font font) {
... // I've tried the three formulae here.
}
}
I have not experimented with Graphics.TestRenderingHint
yet. Is that the magic sauce?
What am I missing? Is there an alternative API I can use, where I call the call to draw supplies the baseline's Y coordinate?
Update 1
I interpolated my code with @LarsTech. He was doing one subtly different; he was adding a 0.5f
. However, even this variant does not fix the issue. Here's the code:
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
TryLarsTechnique(e);
}
private void TryLarsTechnique(PaintEventArgs e) {
base.OnPaint(e);
Graphics g = e.Graphics;
GraphicsContainer c = g.BeginContainer();
g.Clear(Color.White);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TextRenderingHint = TextRenderingHint.AntiAlias;
Font small = new Font("Arial", 13, FontStyle.Regular, GraphicsUnit.Pixel);
Font large = new Font("Arial", 17, FontStyle.Bold, GraphicsUnit.Pixel);
int x = 100;
int y = 100;
x += DrawLars(g, "12.3", small, x, y);
x += DrawLars(g, "456", large, x, y);
x += DrawLars(g, "8", small, x, y);
g.EndContainer(c);
}
// returns width of text
private int DrawLars(Graphics g, string text, Font font, int x, int y) {
float offset = font.SizeInPoints /
font.FontFamily.GetEmHeight(font.Style) *
font.FontFamily.GetCellAscent(font.Style);
float pixels = g.DpiY / 72f * offset;
int numTop = y - (int)(pixels + 0.5f);
TextRenderer.DrawText(g, text, font, new Point(x, numTop), Color.Black, Color.Empty, TextFormatFlags.NoPadding);
return TextRenderer.MeasureText(g, text, font, Size.Empty, TextFormatFlags.NoPadding).Width;
}
I am wondering whether specifying font size using GraphicsUnit.Pixel
is the culprit. Perhaps there is a way to find on the preferred sizes for any particular font?
Update 2
I've tried to specify font sizes in Points instead of Pixels, and this doesn't fully solve the problem, either. Note that using just whole point sizes is not an option in my case. To see if this is possible, I tried this on Windows Wordpad. Sizes Using 96 dpi (and 72 points per inch by definition), 17px, 13px translate to 12.75 and 9.75. Here's the output compared:
Notice how the smaller fonts are the same height at a pixel level. So Wordpad somehow manages to get this correct without rounding the font sizes to convenient values.
You haven't shown enough code to reproduce the problem, so here is a working example using that Bob Powell example you gave.
Demonstration code only:
private void panel1_Paint(object sender, PaintEventArgs e) {
e.Graphics.Clear(Color.White);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
string[] numbers = new string[] { "1", "2", ".", "3", "4", "5", "6", "8" };
int x = 10;
int y = 30;
foreach (string num in numbers) {
Font testFont;
if (num == "4" || num == "5" || num == "6")
testFont = new Font("Arial", 16, FontStyle.Bold);
else
testFont = new Font("Arial", 11, FontStyle.Regular);
float offset = testFont.SizeInPoints /
testFont.FontFamily.GetEmHeight(testFont.Style) *
testFont.FontFamily.GetCellAscent(testFont.Style);
float pixels = e.Graphics.DpiY / 72f * offset;
int numTop = y - (int)(pixels + 0.5f);
TextRenderer.DrawText(e.Graphics, num, testFont, new Point(x, numTop),
Color.Black, Color.Empty, TextFormatFlags.NoPadding);
x += TextRenderer.MeasureText(e.Graphics, num, testFont,
Size.Empty, TextFormatFlags.NoPadding).Width;
}
e.Graphics.DrawLine(Pens.Red, new Point(5, y + 1), new Point(x + 5, y + 1));
}
This produces:
Maybe the problem is that you are specifying your font size in pixels
while your offset is calculated in points
and converted back in pixels
. This might introduce all sorts of imprecisions.
Try specifying the font sizes in points
and see if it works.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With