We have looked at fonts in a previous C# tutorial, in Auto Ellipsingin C#. But whereas in
that tutorial a string gets cut off if there is not enough room, in this
tutorial we are going to take a look at how to scale the font such that
the text will fit in whatever you need it in. As a side note, look for a
revisit on ellipsing in C# at some point in the future - as one of our
readers sent us some interesting info about the performance
characteristics of the API calls we used in that tutorial.
The code we will show here today can probably be used with any drawing
API with minimal tweaks, because, surprisingly, we don't need to do
anything crazy. Essentially, we are going to build a function today that
takes in a string and a
Size
, and returns a font size that will make
that string take up that amount of space. Below, you can see some screen
shots of the example app at different sizes:
So how do we do this? Well, here is the method signature:
public static Font AppropriateFont(Graphics g, float minFontSize,
float maxFontSize, Size layoutSize, string s, Font f, out SizeF extent)
Some of these arguments should be pretty self explanatory. For example,
minFontSize
and maxFontSize
are the minimum and maximum font sizes,
respectively, that the function will be allowed to return. The string
s
is the string that we will be fitting, and the layoutSize
is the
size that the function will fit the string to. The graphics object, g
,
is needed because one of the methods we use in the function is attached
to the graphics object. The font f
that is passed in is needed so we
know what font family and style the string will be drawn with (the size
set in this font object will actually be completely ignored). Finally,
the out param extent
is the final size the string ends up being using
the font returned by the function.
Onto the body of the method:
public static Font AppropriateFont(Graphics g, float minFontSize,
float maxFontSize, Size layoutSize, string s, Font f, out SizeF extent)
{
if (maxFontSize == minFontSize)
f = new Font(f.FontFamily, minFontSize, f.Style);
extent = g.MeasureString(s, f);
if (maxFontSize <= minFontSize)
return f;
float hRatio = layoutSize.Height / extent.Height;
float wRatio = layoutSize.Width / extent.Width;
float ratio = (hRatio < wRatio) ? hRatio : wRatio;
float newSize = f.Size * ratio;
if (newSize < minFontSize)
newSize = minFontSize;
else if (newSize > maxFontSize)
newSize = maxFontSize;
f = new Font(f.FontFamily, newSize, f.Style);
extent = g.MeasureString(s, f);
return f;
}
Not much code at all, really. First, we check to see if the min and max
font size are equal, and if so, we create a new font with that size
(because we will return it without doing any work in a moment). Then we
measure the string with the current font (because out params always need
to be set before a function can return). Next, if the max font size is
less than or equal to the min font size, we return the current font.
This means that if you passed in a bad min and max, you are going to get
back the font you passed in, and if you passed in min and max as the
same number, you will get a font back out whose size is that number.
After those checks, we get to the real work. In order to do the
calculation in a single pass, we calculate a ratio of the available size
over the size of the string. A ratio above 1 means there is space left
and we can make the font bigger, and a ratio below 1 means that the font
is too big for the space and it need to be made smaller. And guess what?
All we need to do is multiply the current font size by that ratio, and
we get the correct font size for the space. Cool, eh?
Of course, that font could be above max or below the minimum value
provided, so we throw in some checks to constrain the value. Finally, we
create the new font, measure the string with that font (so the extent
out param has the right value), and return the new font. And that is all
there is to it!
Of course, it wouldn't be very nice of me if I didn't give you the code
showing how to use this function. So here is the code from the silly
example app:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace FontScaling
{
public partial class Form1 : Form
{
string _text = "Resize Me!!";
public Form1()
{
InitializeComponent();
this.SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void OnPaint(PaintEventArgs e)
{
using (Font f = new Font("Tahoma", 15))
{
SizeF size;
using (Font f2 = AppropriateFont(e.Graphics, 5, 50,
ClientRectangle.Size, _text, f, out size))
{
PointF p = new PointF(
(ClientRectangle.Width - size.Width) / 2,
(ClientRectangle.Height - size.Height) / 2);
e.Graphics.DrawString(_text, f2, Brushes.Black, p);
}
}
base.OnPaint(e);
}
public static Font AppropriateFont(Graphics g, float minFontSize,
float maxFontSize, Size layoutSize, string s, Font f, out SizeF extent)
{
if (maxFontSize == minFontSize)
f = new Font(f.FontFamily, minFontSize, f.Style);
extent = g.MeasureString(s, f);
if (maxFontSize <= minFontSize)
return f;
float hRatio = layoutSize.Height / extent.Height;
float wRatio = layoutSize.Width / extent.Width;
float ratio = (hRatio < wRatio) ? hRatio : wRatio;
float newSize = f.Size * ratio;
if (newSize < minFontSize)
newSize = minFontSize;
else if (newSize > maxFontSize)
newSize = maxFontSize;
f = new Font(f.FontFamily, newSize, f.Style);
extent = g.MeasureString(s, f);
return f;
}
}
}
Pretty simple - I find the appropriate font for the string in the
OnPaint
method, using the current size of the form as the area for the
string. I get back the font and draw the string with it, using the
size
out param (and the size of the form) to center the string on the
form.
One neat little tidbit to note here is the
ControlStyle
I set in the
form constructor. By setting the ResizeRedraw
style to true, the form
will automatically repaint every time it is resized.
And there you go - quick font scaling in C#!
Comments
Post a Comment