C# Tutorial - Font Scaling

Skill

C# Tutorial - Font Scaling

Posted in:

We have looked at fonts in a previous C# tutorial, in Auto Ellipsing in 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:

Font Scaling Screenshot

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#! I even packaged my example project up for you to play with - you can download it here. As always, leave any questions or comments below.

Alpha
11/16/2007 - 08:55

Hello there... it's been some time since I've been struggling with text resizing in C#, and your article certainly pointed me to a good direction.

But I'm still having trouble with one approach.

If I reduce the display string just to one letter (let's suppose: "A") and adjust the maximum font size to any high value (such as 1500), the text measurements will be getting lots of spaces.

I haven't found any way in which I can overcome those spaces. What if I want to scale letters to the pixel? Do you know how to do this?

Thanks in advance.

reply

Gee
01/02/2008 - 16:12

Alpha, the spaces are the result of what is called kerning - I am not sure how much fidelity there is in the newer .net frameworks to adjust this - hopefully a nudge in the right direction?

reply

Jeff
04/07/2008 - 23:42

Great function!

Just what I needed. However, I think you could make it just a tad simpler still without the 'extent' out parameter. You can also drop the p PointF in the client code used for positioning.

With the StringFormat object, you don't need to have the size returned to the client as .Net can do the vertical and horizontal text alignment for you! You just need your function to return the 'AppropriateFont'!

using (StringFormat s)
{
  //...
  s.Alignment = StringAlignment.Center
  s.LineAlignment = StringAlignment.Center

  using (Font f2 = AppropriateFont(e.Graphics, 5, 50,
      ClientRectangle.Size, _text, f))
    e.Graphics.DrawString(_text, f2, Brushes.Black,s)
}

Jeff

reply

Hatem
05/06/2008 - 07:42

Thanks, very useful!

reply

Dbooksta
05/28/2008 - 16:08

Thank you!

FYI, here is a simplified version I'm using:

public static float BestFontSize(Graphics g, Size z,
    Font f, string s)
{
    SizeF p = g.MeasureString(s, f);
    float hRatio = z.Height / p.Height;
    float wRatio = z.Width / p.Width;
    float ratio = Math.Min(hRatio, wRatio);
    return f.Size * ratio;
}

reply

Rakesh
06/17/2008 - 02:52

i want to do similar thing in wpf window.
It's different that normal windows form.

Is there any solution for it

reply

ClockEndGooner
11/01/2009 - 21:55

Hi, Rakesh:

Did you ever find an equivilent function for scaling the font size for a WPF based Content Control class you would be able to share?

Thank you in advance for your time and help...

reply

Shabana
04/16/2009 - 04:16

hi.....!
It was really nice to be here As it gives great platform of idea sharing & problem solving facility...Thnx...!!

My problem is that i want to develop web application which does the Custom t-shirt designing & in that i want the functionality of choosing the ClipArts,ColorPalletes,TextSize,Rotation,Zoom in-out
All this functionality should be done on the t-shirt itself...So,Can Anyone help me in finding out the source code for the same...Most important is that i am using Visual Studio 2005(.net) & C# as language....Thanks for reading me.....!!

reply

Ninos
08/13/2009 - 03:05

Hi ....
This is an excellent app.

But instead of on the form how do I apply to a label.

What I have is an application where you can type in text which will be displayed on a label control. We allready know the font, size, type they are using. We also know the maximum height and length of the label control. What I need to do is reduce the font size if the typed font does not fit onto that label. Which this small code would do it, but how do i apply it for a label if you could please help I would appriciate it very much.

Thank you

reply

SmithaRao
09/11/2009 - 01:38

Hi,

We have an application where we want to chnage the font size as in Office Excel 2007 SLider bar at the right bottom corner. This is not exactly chnaging the font, instead it is resizing the entire image of the window.
Any idea on how to implemet in c#

reply

ClockEndGooner
11/02/2009 - 00:15

Greetings:

After a bit of reseach, I came across a following posting at http://www.dotnetmonster.com/Uwe/Forum.aspx/winform/21292/wpf-font-scaling that describes how to get automatic font scaling within WPF.

In short, to obtain the same effect in WPF, just embed a inside a . When the is resized inside the WPF Window, the contents of the will automatically be resized, provided that the Content Control doesn't have a MaxWidth nor MaxHeight constraining element. As a result, the completed XAML Code, with no custom C# code does the same as the WinForms example presented in this tutorial.

Application.xaml:

MainWindow.xaml:

Resize Me!!

Again, there is no change required to either the Application nor MainWindow C# code.

I hope this was of help....

reply

ClockEndGooner
11/02/2009 - 00:21

My apologies for the oversight on my part. The XAML is as follows:

Application.xaml

<Application x:Class="WpfApplication1.App"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   StartupUri="MainWindow.xaml">
    <Application.Resources>
         
    </Application.Resources>
</Application>

<Window x:Class="WpfApplication1.MainWindow"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Title="MainWindow" Height="300" Width="300">
    <Grid>
        <Viewbox>
            <TextBlock >Resize Me!!</TextBlock>
        </Viewbox>
    </Grid>
</Window>

reply

ClockEndGooner
11/02/2009 - 00:50

OK, in my haste to post the WPF solution, I think I made my previous two postings less than readable. Perhaps the third time will be the charm. {:-)

The original posting for resizing text within a WPF window can be found at http://www.dotnetmonster.com/Uwe/Forum.aspx/winform/21292/wpf-font-scaling

In short, to obtain the same effect in WPF, just embed a TextBlock inside a Viewbox. When the TextBlock is resized inside the WPF Window, the contents of the TextBlock will automatically resize the Text property, provided that the TextBlock Content Control doesn't have a MaxWidth nor MaxHeight constraining element to a fixed minimum or maximum size. As a result, the following XAML code does the same as the WinForms example presented in this tutorial:

Application.xaml:

<Application x:Class="WpfApplication1.App"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   StartupUri="MainWindow.xaml">
    <Application.Resources>
         
    </Application.Resources>
</Application>

MainWindow.xaml:

<Window x:Class="WpfApplication1.MainWindow"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Title="MainWindow" Height="300" Width="300" WindowStartupLocation="CenterScreen">
    <Grid>
        <Viewbox>
            <TextBlock >Resize Me!!!</TextBlock>
        </Viewbox>
    </Grid>
</Window>

Again, there is no change required to either the Application nor MainWindow C# code.

I hope this was of help...

reply

Anonymous
03/14/2010 - 09:54

tyrd

reply

Add Comment

Put code snippets inside language tags:
[language] [/language]

Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]

See here for supported languages.

Javascript must be enabled to submit anonymous comments - or you can login.

Sponsors