Embedding Fonts in your .Net Application

by Brian Brewder September 17, 2011 18:40

WARNING: This article contains code that is a bit naïve. My new article shows a better way of dealing with fonts: Embedding Fonts in your .Net Application Part 2

At my job, I’m working on converting our Classic ASP application to .Net. One of my goals for the project is to make sure it can be XCopy deployed. This makes it much easier to setup development, test, staging, and production environments. Recently I found myself in a situation where we needed to use a font that wasn’t installed on the server by default (we create some graphics with text in them). Instead of installing the fonts on the server, I decided to learn how to embed the font as a resource in an assembly. Here’s what I came up with.

If you want to follow along, here’s how to go about setting this up:

  1. Create a console application in Visual Studio (this technique works in any type of project, but the console app is used for demo purposes).
  2. Create a folder called Fonts.
  3. Add a ttf file to the folder. You can find ttf files in your fonts directory (C:\Windows\Fonts) or you can download one from the Internet. You will probably want to rename the file to better reflect the name of the font.
  4. Create a resource file in the Fonts directory. For the demo, I called mine MyFonts.
  5. Add the font to the resource file. You can do this by opening the resource file in Visual Studio and dragging the font you added in step 3 into it.
  6. Create a static class called ResFonts in the Fonts directory.
  7. Create an enumeration called ResFontFamily and add an entry for each font you want to expose. Make sure the numbering for the fonts start at 0. The enum value is used as the index into the fonts collection.
  8. Create a private static field of type PrivateFontCollection. This class is in the System.Drawing.Text namespace. You will probably need to reference System.Drawing.dll to access it.
  9. Initialize the field in a static constructor (don’t worry, the code is below).
  10. Add a static method called Create that takes the enumeration value and a couple other values used for initializing the font and returns the Font. You can use this font just like you use fonts created in the more traditional way (don’t forget to dispose of it).

 

Now for the code…

using System.Drawing;
using System.Drawing.Text;
using System.Runtime.InteropServices;
namespace EmbeddedFonts.Fonts
{
    public class ResFonts
    {
        private static PrivateFontCollection sFonts;
        static ResFonts()
        {
            sFonts = new PrivateFontCollection();
            // The order the fonts are added to the collection 
            // should be the same as the order they are added
            // to the ResFontFamily enum.
            AddFont(MyFonts.Consolas);
        }
        private static void AddFont(byte[] font)
        {
            var buffer = Marshal.AllocCoTaskMem(font.Length);
            Marshal.Copy(font, 0, buffer, font.Length);
            sFonts.AddMemoryFont(buffer, font.Length);
        }
        public static Font Create(
            ResFontFamily family, 
            float emSize, 
            FontStyle style = FontStyle.Regular, 
            GraphicsUnit unit = GraphicsUnit.Pixel)
        {
            var fam = sFonts.Families[(int)family];
            return new Font(fam, emSize, style, unit);
        }
    }
    public enum ResFontFamily
    {
        /// <summary>Consolas</summary>
        Consolas = 0
    }
}

And here’s an example of how you can use the fonts (the highlighted line is the call to our embedded fonts class)…

using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using EmbeddedFonts.Fonts;

namespace EmbeddedFonts
{
    class Program
    {
        static void Main(string[] args)
        {
            var str = args.Length > 0 ? args[0] : "Hello World!";
            using (var f = ResFonts.Create(ResFontFamily.Consolas, 26))
            {
                SizeF sz;
                using (var szBmp = new Bitmap(1, 1))
                using (var g = Graphics.FromImage(szBmp))
                    sz = g.MeasureString(str, f);

                var bmp = new Bitmap((int)sz.Width + 4, (int)sz.Height + 4);
                using (var g = Graphics.FromImage(bmp))
                {
                    g.SmoothingMode = SmoothingMode.AntiAlias;
                    g.DrawString(str, f, Brushes.Black, 3, 3);
                    g.DrawString(str, f, Brushes.White, 1, 1);
                }

                var path = Path.Combine(Path.GetTempPath(), "test.png");
                bmp.Save(path, ImageFormat.Png);
                Process.Start(path);
            }

        }
    }
}

I hope this code proves useful to you. If you see any way to improve it, please let me know.

Tags:

Powered by BlogEngine.NET 1.6.0.0