Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Embedded resource font in C# does not work correctly

I embedded a .ttf font file ("Amatic Bold", specifically) in my resources and I'm using this code below to get the Font. I tried the code fom this post: How do I Embed a font with my C# application? (using Visual Studio 2005)

This is my implementation:

    static public Font GetCustomFont (byte[] fontData, float size, FontStyle style)
    {
        if (_fontCollection == null) _fontCollection = new PrivateFontCollection();
        IntPtr fontPtr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(fontData.Length);
        System.Runtime.InteropServices.Marshal.Copy(fontData, 0, fontPtr, fontData.Length);
        _fontCollection.AddMemoryFont(fontPtr, fontData.Length);
        System.Runtime.InteropServices.Marshal.FreeCoTaskMem(fontPtr);
        return new Font(_fontCollection.Families[0], size, style);
    }

Im using it like that:

Font font = GetCustomFont(Properties.MainResources.Amatic_Bold, 25, System.Drawing.FontStyle.Bold);     

The font should look like: enter image description here

The problem is the font is loading but not correctly showing when used; it looks like an "Arial" or other standard font instead of what it should be. If I install the font in Windows, it works (I suppose is obvious...)

I searched for an existing answer but could'nt find my exact problem...

Any help will be appreciated. Thanks in advance.

like image 683
Adam Calvet Bohl Avatar asked Apr 07 '16 19:04

Adam Calvet Bohl


1 Answers

Well, then... I think I got it!

I'll explain what I've "discovered" (whether it can be obvious or not):

  • First: Application.SetCompatibleTextRenderingDefault must be set to true for Memory fonts to be rendered in the controls. (Also Control.UseCompatibleTextRendering can be used) It's perfectly specified in Microsoft documentation but I've missed that :-(

  • Second: PrivateFontCollection.Families return an array of added fonts, but.. Surprise! It's alphabetically ordered! No matter what's the order you add the fonts or the method you use (AddMemoryFont/AddFontFile), you'll get it alphabetically ordered! So if you're adding more than one font and then trying to get the last font you've added, you'll probably getting the wrong one.

  • Third: I've also tried doing FreeCoTaskMem() after adding the font in the collection or doing it on form closing. Both were working for me! I don't know the exact implications of this...

This is my final code:

    //This list is used to properly dispose PrivateFontCollection after usage
    static private List<PrivateFontCollection> _fontCollections;

    [STAThread]
    private static void Main(string[] args)
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(true);    //Mandatory in order to have Memory fonts rendered in the controls.

        //Dispose all used PrivateFontCollections when exiting
        Application.ApplicationExit += delegate {
            if (_fontCollections != null) {
                foreach (var fc in _fontCollections) if (fc != null) fc.Dispose();
                _fontCollections = null;
            }
        };

        Application.Run(new frmMain());
    }

    void frmMain_Load(object sender, EventArgs e)
    {
        Font font1 = GetCustomFont(Properties.Resources.Amatic_Bold, 25, FontStyle.Bold);
        //or...
        Font font1 = GetCustomFont("Amatic-Bold.ttf", 25, FontStyle.Bold);

        labelTestFont1.Font = font1;

        Font font2 = GetCustomFont(Properties.Resources.<font_resource>, 25, FontStyle.Bold);
        //or...
        Font font2 = GetCustomFont("<font_filename>", 25, FontStyle.Bold);

        labelTestFont2.Font = font2;

        //...

    }
    static public Font GetCustomFont (byte[] fontData, float size, FontStyle style)
    {
        if (_fontCollections == null) _fontCollections = new List<PrivateFontCollection>();
        PrivateFontCollection fontCol = new PrivateFontCollection();
        IntPtr fontPtr = Marshal.AllocCoTaskMem(fontData.Length);
        Marshal.Copy(fontData, 0, fontPtr, fontData.Length);
        fontCol.AddMemoryFont(fontPtr, fontData.Length);
        Marshal.FreeCoTaskMem(fontPtr);     //<-- It works!
        _fontCollections.Add (fontCol);
        return new Font(fontCol.Families[0], size, style);
    }


    static public Font GetCustomFont (string fontFile, float size, FontStyle style)
    {
        if (_fontCollections == null) _fontCollections = new List<PrivateFontCollection>();
        PrivateFontCollection fontCol = new PrivateFontCollection();
        fontCol.AddFontFile (fontFile);
        _fontCollections.Add (fontCol);
        return new Font(fontCol.Families[0], size, style);
    }

As you can see, I've decided to create an exclusive PrivateFontCollection for each font, then store it to a List for a final disposal on application end.

This was tested in 3 different PC's (both with Windows 7, 32 and 64 bits) and 3 different .ttf fonts.

An example of the result:

enter image description here

I don't know if my approach is good enough, but I expect it could be useful for others!

One more detail: Unlike what I expected, AddMemoryFont is slower then AddFontFile (21ms vs. 15 ms)

Again, thanks to all comments!

like image 183
Adam Calvet Bohl Avatar answered Nov 11 '22 01:11

Adam Calvet Bohl