8

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.

Community
  • 1
  • 1
Adam Calvet Bohl
  • 1,009
  • 14
  • 29
  • 1
    That horribly buggy code is like a virus, it is extraordinarily hard to get rid of. You are being saved by the font, it is an OpenType font. Only usable in a WPF or Direct2D app. The font mapper finds a substitute that can work in a Winforms app. – Hans Passant Apr 08 '16 at 06:26
  • Thanks, @HansPassant. Is there any good alternative to use a font without having to install it? – Adam Calvet Bohl Apr 08 '16 at 09:07
  • 1
    Does it actually work when you install the font? Then fix the bugs in this code. You cannot call Marshal.FreeCoTaskMem() until the font can no longer be used and the ._fontCollection.Dispose() method is called And _fontCollection.Families[0] always returns the first font, not the last loaded font. – Hans Passant Apr 08 '16 at 09:23
  • Ok, I'll try. I'm aware of the _fontCollection.Families[0] issue, it's the behavior i initially planned, although I'm going to modify it. Thanks again. – Adam Calvet Bohl Apr 08 '16 at 10:37
  • Adam, you need to reorganize your memory management so you create the font collection, `AllocCoTaskMem` and `AddMemoryFont` once only, then Dispose the font collection and `FreeCoTaskMem` once only when the form/control disposes. If you are trying to use the font on controls there are some requirements about compatible text rendering [here](https://msdn.microsoft.com/en-us/library/system.drawing.text.privatefontcollection.addmemoryfont%28v=vs.100%29.aspx) - if this is wrong the font will be ignored. – Stuart Whitehouse Apr 08 '16 at 11:04

2 Answers2

11

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!

Adam Calvet Bohl
  • 1,009
  • 14
  • 29
  • 1
    Thanks a lot, Control.UseCompatibleTextRendering is what solved the problem for me using AddMemoryFont, after so many hours !! – Youssef Jul 21 '18 at 02:25
0

The problem might be since fontfamily exact font needs to be specified while using a fontfile, compiler switches to default font. You can get the basic idea from the following two methods on what you are missing.

var fontFile = new FontFamily("pack://application:,,,/Resources/#YourFont");
var typeface = new Typeface(new FontFamily(new Uri("pack://application:,,,/"), "/Resources/#YourFont"), FontStyles.Normal, FontWeights.Regular, FontStretches.Normal);
var cultureinfo = new CultureInfo("en-us");
var ft = new FormattedText("YourText", cultureinfo, FlowDirection.LeftToRight,
    typeface, 28, Brushes.White)
dc.DrawText(ft, new Point(0,0));

Install font on client system by using resource path.

PrivateFontCollection yourfont = new PrivateFontCollection();
yourfont.AddFontFile("Your font Path");
label1.Font = new Font(yourfont.Families[0], 16, FontStyle.Regular);
Jerin
  • 3,657
  • 3
  • 20
  • 45