Embedded resource font in C# does not work correct

2019-04-09 14:51发布

问题:

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:

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.

回答1:

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:

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!



回答2:

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);