fonts that aren't installed on system

Mar 28, 2013 at 10:24 PM
Hi there,

I've integrated HtmlRenderer into my program and it is a huge improvement over the RichTextBox. Scrolling is smoother and flicker free, and it supports transaprency AND font smooting at the same time, which is a huge bonus. I was pleased to find that HtmlPanel supports Transparency as well, not just the HtmlLabel.

However, one thing I haven't been able to figure out a solution to is how to use fonts that aren't installed on the system, but are embedded in my application. I deploy TTF's as an embedded resource within the exe, and create Font objects at runtime out of them which can then be assigned to Windows Forms controls. This way, less common fonts such as Moderne Fraktur and Apple Garamond can be successfully used within the application, without ever having been installed to the system. Yes, it does work. ;-)

The problem is it seems that HtmlRenderer will only pick these fonts up by default if they are installed in the system (not a suprise naturally). I'd rather not have my application install them into the system if it is not necessary. Normally I would have an installer put these font files into the system, but this application IS an installer/launcher/patcher, and will not have it's own installation package. It's essentially a bootstrapping download agent that doubles as the patcher and launcher for a MMO game. You download and run this small program, log in, and then it deploys the rest of the game over the Internet. So far it doesn't touch anything outside of the game deployment dir or user AppData, and I'd like to keep it that way.

Is there any straight-forward way to "feed" the fonts I've loaded into memory into the HtmlRenderer so that when font-family styles are evaluated, it will find and use them?

Thanks,
Ryan
Mar 30, 2013 at 3:14 AM
OK so I looked into this some more and have a solution to propose.

The key is in CssUtils.GetCachedFont(), which is the only place in the code where Font's are created.

This is the relevant code:
        if (font == null)
        {
            font = new Font(family, size, style);
            _fontsCache[family][size][style] = font;
        }
That particular Font() constructor which takes the family name will only look into the INSTALLED fonts on the system. If the family name is not found, MS Sans Serif is substituted. That's straight from MS Docs. The problem is the application may have fonts loaded into memory via PrivateFontCollection, and they may not be installed in the system. My application does this, and there are many reasons why you would want to do this. If you Google "C# embed font" you will find tons of info on it.

Alternatively if you use the Font() constructor which takes the FontFamily instance, then the font does not have to be installed on the system, and for example can be loaded into a PrivateFontCollection() instance instead. The other way to look at this is if the FontFamily is already in memory, the the Font() constructor has no need to go look at the system fonts, it is already in memory.

So what I suggest is we do the following:
  • expose a new library-level public singleton PrivateFonts for setting an optional global PrivateFontCollection instance via property of the same name.
  • modify GetCachedFont() so that when it needs to create an uncached font, it first checks the singleton to see if the requested font family has already been loaded into memory. If it is, then the Font(FontFamily, string, string) ctor is used to directly create the font from the in-memory font family. If the FontFamily is not found, then the other constructor is used to load the font from the system installed fonts.
As you can see, a nice side-effect of this solution is it avoids the good'ol "Windows Font Fight" between applications. One major shortfall of Windows is it has ONE place to store ALL fonts shared among ALL applications. Two applications that installed different versions of the same font will fight, and the last in wins, and the first one ends up using the wrong font. With this solution, the application would always embed the specific fonts it uses as Embedded Resources, load them into it's own PrivateFontCollection, and it can then set this reference into a library singleton for HtmlRenderer. The application would then be guaranteed to always get the correct fonts, regardless of what fonts are or are not installed on the system.

So this solution would add one new source file for the singleton, and minor modification to CssUtils.GetCachedFont().

What do you think? I can't think of any reason why this wouldn't work.

Later,
Ryan
Mar 30, 2013 at 6:13 PM
OK so I made the changes. They work great. I ended up using a factory pattern as it was more flexible, and allowed me to only have to change two lines of existing code:

In CssUtils.GetCachedFont() change the creation line to:
                font = CssFontFactory.Instance.Create(family, size, style);
                // font = new Font(family, size, style); <-- this is the old line
In CssUtils IsFontExists() change the return line to:
            return CssFontFactory.Instance.Contains(fontFamily) || _existingFonts.ContainsKey(fontFamily);
            // return _existingFonts.ContainsKey(fontFamily); <-- this is the old line
And then simply add the new class CssFontFactory which is as follows:
// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they bagin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
// 
// - Sun Tsu,
// "The Art of War"

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Text;
using System.Runtime.InteropServices;


namespace HtmlRenderer
{
    /// <summary>
    /// Global public singleton for creating fonts from in-memory FontFamiliy instances or from installed system fonts.
    /// FontFamily instances may be added through one of several Add overloads.
    /// The Font instance is created from the Create method which will use the FontFamily if found, or the system fonts if not.
    /// </summary>
    public class CssFontFactory
    {
        // static members
        private static volatile CssFontFactory _instance = null;
        private static object _syncRoot = new Object();

        // instance members
        private Dictionary<string, FontFamily> _fontFamilies;


        /// <summary>
        /// Instace method for retrieving the singleton font factory.
        /// </summary>
        public static CssFontFactory Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_syncRoot)
                    {
                        if (_instance == null)
                        {
                            _instance = new CssFontFactory();
                        }
                    }
                }
                return _instance;
            }
        }

        /// <summary>
        /// Constructor for the font factory. This is private and only created from the Instance property get.
        /// </summary>
        private CssFontFactory()
        {
            _fontFamilies = new Dictionary<string, FontFamily>();
        }

        /// <summary>
        /// Attempts to create and returns the Font with the specified family, size, and style.
        /// If a matching FontFamily has previously been added via AddFontFamily, then it will be used for creating the Font.
        /// Othewise the font will be created directly from the installed system fonts if the Font is installed
        /// If the font is not found in that case, .Net will return MS Sans Serif as the Font.
        /// </summary>
        /// <param name="family">The font family name of the font to create.</param>
        /// <param name="size">The point size of the font to create.</param>
        /// <param name="style">The style of the font to create.</param>
        /// <returns></returns>
        public Font Create(string family, float size, FontStyle style)
        {
            Debug.WriteLine("[CssFontFactory] Looking up font family: " + family);

            family = family.ToLower();

            FontFamily foundFamily;
            _fontFamilies.TryGetValue(family, out foundFamily);

            if (foundFamily != null)
            {
                Debug.WriteLine("[CssFontFactory] Family found: " + family);

                return new Font(foundFamily, size, style);
            }
            else
            {
                Debug.WriteLine("[CssFontFactory] Family NOT found: " + family);

                return new Font(family, size, style);
            }
        }

        /// <summary>
        /// Used to determine if a particular font family has been added to the factory.
        /// </summary>
        /// <param name="family">The font family name to search for.</param>
        /// <returns> Returns true if the font family has been added, false otherwise.</returns>
        public bool Contains(string family)
        {
            family = family.ToLower();
            return _fontFamilies.ContainsKey(family);
        }

        /// <summary>
        /// Adds a single font family to the factory.
        /// </summary>
        /// <param name="fontFamily">The font family to add.</param>
        public void Add(FontFamily fontFamily)
        {
            if (fontFamily == null) throw new ArgumentNullException("fontFamily may not be null");

            _fontFamilies[fontFamily.Name.ToLower()] = fontFamily; // overwrite if already in the dictionary

            Debug.WriteLine("[CssFontFactory] Added font family: " + fontFamily.Name);
        }

        /// <summary>
        /// Adds an array of font families to the factory.
        /// </summary>
        /// <param name="fontFamilies"></param>
        private void Add(FontFamily[] fontFamilies)
        {
            if (fontFamilies == null) throw new ArgumentNullException("fontFamilies may not be null");

            for (int i = 0; i < fontFamilies.Length; i++)
            {
                Add(fontFamilies[i]);
            }
        }

        /// <summary>
        /// Adds all font families contained in the PrivateFontCollection to the factory.
        /// </summary>
        /// <param name="collection"></param>
        public void Add(PrivateFontCollection collection)
        {
            if (collection == null) throw new ArgumentNullException("collection may not be null");

            Add(collection.Families);
        }
    }
}
That's it! Now any class can simply pass their own PrivateFontCollecion into CssFontFactory.Instance.Add() and from that point forward, all private fonts used by the application will now be used by HtmlRenderer as well.

Let me know if any question.

Thanks,
Ryan
Apr 2, 2013 at 7:14 PM
How do you deal with the lack of certain fonts (Helvetica is an easy one) that aren't installed, but are very common in HTML / CSS code? Are you just going to bundle up a set of Browser fonts? Perhaps it's not a problem, but I swear I had some CSS that called out for Helvetica exclusively, and was rendered horribly with HTMLRenderer.

In that case, IE / Chrome / FireFox would likely have an internal mapping -> Arial, no? Do we need to add this functionality?

I found this list here http://www.ampsoft.net/webdesign-l/WindowsMacFonts.html of common fonts.
Apr 2, 2013 at 7:59 PM
Edited Apr 2, 2013 at 8:01 PM
Great point. No you are right that is a problem, and yes the solution I built could solve it as is, but perhaps it is not the most elegant solution for those general cases. Every application wo uld have to add the fonts they think they will need. In my app's case this isn't a problem, I know exactly what fonts I will ever run into. But in other apps they may be pointing to content that has not yet been created, and it may reference a font that simply isn't on the system. The way HtmlRenderer does this today is it delegates to the parent through inheritance, and eventually to MS sans serif.

I do think technically what I proposed is the right solution, but perhaps for very common fonts that are often missing from HtmlRenderer could actually embed them within the assembly itself. During FontFactory constructor it would add these embedded fonts to the FontFactory. Philosophically it is no different than the fact that it HtmlRenderer comes with it's own default base CSS. You don't have to start from scratch with your CSS. You could say the same for the fonts, for common web fonts, they would already be included. The downside is this would slightly bloat the HtmlRenderer assembly. At a minimum, all of the fonts referenced by the default CSS should come from HtmlRenderer. That way it always has coverage for itself now matter what the end user has done to the local system.

By having these be loaded during FontFactory constructor, since it is a singleton, you could also then provide a FontFactory.Instance.Clear() method so that application that want to use THEIR OWN Helvetica for example can simply call Clear() before adding in their own FontFamily instances. That way some users get the benefit of automatic coverage for all common fonts, but other users who wish to ensure that THEIR version of those fonts are used for THEIR application and re-bundled them, and add them after calling Clear().

Best of both worlds.

Thoughts?

Later,
Ryan
Developer
Apr 7, 2013 at 1:01 PM
In next version I will add support for adding custom font, similar to what you did with CssFontFactory but no exactly for performance reasons.
also I will add unavailable font mapping option, so the user can define if 'Helvetica' doesn't exists it will use 'Arial'.
Font are very expensive, I don't want to bloat the dll for everyone.
Apr 7, 2013 at 6:12 PM
Gotcha, as long as it’s still easy to use so that somebody who has custom loaded their font families can register them with the library. Keep in mind these could be either file or memory fonts, right now the control works with either, which is great and would be nice to keep it that way.

Are you planning on keeping the custom font submission global, or per control? Global met my needs and was simple, but there could in theory be somebody who wants to use one set of fonts for one control, and a different set for different control…but I didn’t need that as I only have one instance of HtmlPanel.

Also, as mentioned in the other thread I have selection text fore and back color working, Right now they are both simply constants that you can hardcode in CssUtils (selection backcolor was already there, but I added forecolor). As part of adding this I made the selection be whole-word just like in Outlook / Word / IE. Anyway, it would be fantastic if the ::selection style was honored. I didn’t bite that off because hardcoding it into the code of my custom build was acceptable for now. But it would be great if ::selection style was honored.

Later,
Ryan
Mar 3, 2015 at 2:15 PM
Hi TheRhinoDude,

Would this solution to add custom fonts work to render as a PDF?

Thanks