0

I am using this code to get an embedded font:

/// <summary>
    /// Returns an Embedded Font
    /// </summary>
    /// <param name="ImagePath">String begins with namespace e.g MyProgram.Image.png</param>
    /// <returns></returns>
    public static Font GetEmbeddedFont(string FontPath, float Size)
    {
        Font _font = null;
        Thread getFontThread = new Thread(() => GetFont(FontPath, Size, out _font));
        getFontThread.Start();
        getFontThread.Join();
        return _font;            
    }
    #region GetFont
    private static void GetFont(string FontPath, float Size, out Font FontOut)
    {
        Font fnt = null;
        Assembly asm = Assembly.GetExecutingAssembly();
        Stream resStream = asm.GetManifestResourceStream(FontPath);
        if (null != resStream)
        {
            // 
            // GDI+ wants a pointer to memory, GDI wants the memory.
            // We will make them both happy.
            //
            // First read the font into a buffer
            byte[] rgbyt = new Byte[resStream.Length];
            resStream.Read(rgbyt, 0, rgbyt.Length);
            resStream.Close();
            // Then do the unmanaged font (Windows 2000 and later)
            // The reason this works is that GDI+ will create a font object for
            // controls like the RichTextBox and this call will make sure that GDI
            // recognizes the font name, later.
            uint cFonts;
            AddFontMemResourceEx(rgbyt, rgbyt.Length, IntPtr.Zero, out cFonts);
            // Now do the managed font
            IntPtr pbyt = Marshal.AllocCoTaskMem(rgbyt.Length);
            if (null != pbyt)
            {
                Marshal.Copy(rgbyt, 0, pbyt, rgbyt.Length);
                m_pfc = new PrivateFontCollection();
                m_pfc.AddMemoryFont(pbyt, rgbyt.Length);
                Marshal.FreeCoTaskMem(pbyt);
            }
        }

        if (m_pfc.Families.Length > 0)
        {
            // Handy how one of the Font constructors takes a
            // FontFamily object, huh? :-)
            fnt = new Font(m_pfc.Families[0], Size);
        }
        m_pfc.Dispose();    
        FontOut = fnt;            
    }

I use a Thread to attempt to wait for it to finish, because the error seems to occur, if this method is called multiple times within a short space of time.

How can I stop this error occuring, I think it has something to do with the method being called within quick succession of each other.

I get the exception here:

_font = value;
            using (Graphics g = _parent.CreateGraphics())
            {
                SizeF soize = g.MeasureString(_text, _font);
                _size = new Size((int)soize.Width, (int)soize.Height);
                _width = _size.Width;
                _height = _size.Height;
            }

On the line g.MeasureString(_text, _font);

However I know that the error is in the GetEmbeddedFont Method, as it only throws an error if the font is set using the GetEmbeddedFont method.

It will work fine once, but if it is used a second time to shortly after the first, it will throw the error.

And if I debug the code, _font returns this:

{Name = '((System.Drawing.Font)(_font)).fontFamily.Name' threw an exception of type 'System.ArgumentException' Size=15.0}
ρσݥzση
  • 217
  • 3
  • 13
  • Why are you doing this on a separate thread? You immediately call `Join()` after starting the thread, which causes the calling thread to block until it finishes anyway. Seems completely pointless to me. – Ed S. Jun 23 '12 at 22:27
  • So it waits until it is done, it was an attempt to stop it being called in quick succession but it did not work. I'm just showing what I have already tried. – ρσݥzση Jun 23 '12 at 22:46
  • What line is giving you an exception? – Will Jun 24 '12 at 00:23
  • There's actually a larger issue at play here - even after removing the Marshal.FreeCoTaskMem() call, when other things are running, eventually the memory that holds the fonts are overwritten. In my case I'm loading five embedded fonts and adding them to a Dictionary<>. With small data sets, this is never a problem. With large data sets, my Dictionary<> starts having issues with the fonts that it contained. – Jesse Williams Aug 11 '15 at 18:36

1 Answers1

5

There's a documentation problem with AddMemoryFont(), it doesn't specify how long the pointer needs to remain valid. I've always chosen the conservative route and made sure to not release the memory until after the program stops using the private font. That has worked well, no access violations.

I thus strongly recommend you remove the Marshal.FreeCoTaskMem() call. Either entirely, Windows automatically cleans up when the program terminates, or move it to, say, the FormClosed event. The same goes for your m_pfc.Dispose() call. Don't release resources until you're done with them.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • So If I remove those two dispose calls, the error will stop throwing? – ρσݥzση Jun 24 '12 at 22:27
  • Thank you, that did actually fix half of it. I have just now realised that the error was being thrown for two reasons. Firstly, the error that you have just fixed, secondly the font needed to be reloaded in some cases because the font would have been loaded, then the same method loads a new font, and for some reason corrupts the first, so if I needed to change text I have to reload the font first. – ρσݥzση Jun 25 '12 at 07:58