Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

CAP_FontInstaller - A basic (un)installer class for application specific fonts

4.62/5 (18 votes)
24 Apr 20064 min read 2   864  
A simple wrapper to dynamically install/uninstall application specific fonts from compiled resources.

AppFontInstaller screenshot

Introduction

Every once in a while, the applications we develop require fonts that may or may not be in the user's system. A common solution is to bundle the font files with the rest of the application setup and to install them with everything else. Packages like MSI, NSIS, or Inno make this easy enough. A drawback is that your fonts become permanently visible system-wide and, for example, the user could at any point remove/uninstall the fonts your application relies on. To prevent this scenario, you could implement code that checks the fonts' status every time your application is run and, if need be, proceeds to self-terminate, to warn the user, or to install the fonts on the fly.

Of course, if you are installing fonts on the fly, you could take it a step further and limit the lifespan of the font to the application's execution. The approach would have the additional advantage of protecting somewhat the "privacy" of your font as it will only be visible while your application is up. This article presents a simple class that encapsulates such a functionality, namely, allowing an application to dynamically install/uninstall fonts from compiled resources, with just a few lines of code.

Using the code

A straightforward use of the code would follow these steps:

  1. Including the AP_FontInstaller.h and AP_FontInstaller.cpp in your project.
  2. Adding the relevant font files as resources to the project.
  3. Inserting the CAP_FontInstaller class header into the header of the main dialog:
    #include "AP_FontInstaller.h"
  4. Creating an object of the class CAP_FontInstaller:
    CAP_FontInstaller m_capFontInstaller;
  5. In the OnInitDialog(), adding the font resources to the installer and installing the fonts:
    m_capFontInstaller
        .AddFont( _T("Camelot MF"), IDR_FONT_CAMELOT, _T("FONTS") )
        .AddFont( _T("Cigno MF"), IDR_FONT_CIGNO, _T("FONTS") );
    
    m_capFontInstaller.InstallAllFonts();
  6. Making sure to uninstall the fonts before the application terminates, for example, in the OnClose() method:
    m_capFontInstaller.UninstallAllFonts();

Only fonts that are not already present in the system will be installed and uninstalled by the class CAP_FontInstaller. Therefore, if you forget to uninstall your fonts, the next time your application runs, it will flag these fonts as already present and you will not be able to uninstall them. Of course, you can also modify the behavior of the code to suit other purposes, and this may not be an issue.

Points of Interest

The code for the class CAP_FontInstaller is meant to implement a rather uncomplicated formula, that is, to keep a list of fonts that it can install and uninstall. The etiquette to respect is that if any of these fonts is already present in the system, they will not be meddled with.

Thus, the first order of business is to find out which fonts are already installed. This is accomplished via a call to the API EnumFontFamiliesEx which, in turn, relies on the implementation of a callback function. The code looks like this:

BOOL CAP_FontInstaller::IsFontAlreadyInSystem( const CString& csFontName )
{
    HDC          hDC = GetDC( NULL );
    LOGFONT      lf  = { 0, 0, 0, 0, 0, 0, 0, 0, 
                         ANSI_CHARSET, 0, 0, 0, 0, NULL };
    FONT_DETAILS fdFont;

    fdFont.m_csFontName = csFontName;
    fdFont.m_bInstalled = FALSE;

    EnumFontFamiliesEx( hDC, &lf, (FONTENUMPROC)_EnumFontFamExProc, 
                                  (LPARAM)(LPVOID)&fdFont, 0 );

    return fdFont.m_bInstalled;
}

int CALLBACK CAP_FontInstaller::_EnumFontFamExProc( ENUMLOGFONTEX * lpelfe,
                                                    NEWTEXTMETRICEX * /*lpntme*/,
                                                    int /*nFontType*/,
                                                    LPARAM lParam )
{
    PFONT_DETAILS pfd = (PFONT_DETAILS)lParam;

    if( pfd->m_csFontName == lpelfe->elfLogFont.lfFaceName )
    {
        pfd->m_bInstalled = TRUE;

        return FALSE;   // Finished
    }
    else
        return TRUE;    // Continue
}

The callback function, _EnumFontFamExProc, is called by the system, for one font at a time. Note that the face-name need not be (and often times, isn't) the same as the font file name. Use a font viewer if in doubt.

As mentioned, the installer keeps a list of the fonts to install/uninstall. This list is an array of structures whose declaration looks as follows:

typedef struct
{
    CString m_csFontName;
    CString m_csInstalledFontFullPath;
    UINT    m_uResID;
    CString m_csResType;
    BOOL    m_bInstalled;
    BOOL    m_bAlreadyInTheSystem;
} FONT_DETAILS, *PFONT_DETAILS;

A structure is populated and added to the array for each font passed to the public method AddFont.

Installation involves the extraction of the font from the application's compiled resources and the creation of the font file in the appropriate system folder (which varies among OS versions). The code looks as follows:

BOOL CAP_FontInstaller::WriteFontFile( const CString& csInstalledFontFullPath, 
                                        UINT uResID, const CString& csResType )
{
    BOOL      bSuccess  = FALSE;
    HINSTANCE hInst     = AfxGetResourceHandle();
    HRSRC     hResource = FindResource( hInst, 
                          MAKEINTRESOURCE( uResID ), csResType );

    if( hResource )
    {
        HGLOBAL hGlobal = LoadResource( hInst, hResource );

        if( hGlobal )
        {
            TCHAR* szTemp = (TCHAR*)LockResource( hGlobal );
            UINT   uSize  = (UINT)SizeofResource( hInst, hResource );

            DeleteObject( (HGDIOBJ)hGlobal );

            CFile cf;

            if( cf.Open( csInstalledFontFullPath, 
                         CFile::modeWrite | CFile::modeCreate ) )
            {
                cf.Write( szTemp, uSize );
                cf.Close();

                bSuccess = TRUE;
            }
        }
    }

    return bSuccess;
}

BOOL CAP_FontInstaller::InstallFont( const CString& csFontName )
{
    BOOL          bSuccess = FALSE;
    PFONT_DETAILS pfd      = NULL;

    if( FindFontDescription( csFontName, pfd ) && 
        pfd->m_bInstalled == FALSE && 
        pfd->m_bAlreadyInTheSystem == FALSE &&
        WriteFontFile( pfd->m_csInstalledFontFullPath, 
                       pfd->m_uResID, pfd->m_csResType ) )
    {
        bSuccess          = 
          ( AddFontResource( pfd->m_csInstalledFontFullPath ) != 0 );
        pfd->m_bInstalled = bSuccess;
    }

    return bSuccess;
}

As you can see, extracting the resource and writing the font file is trivial. If successful, installing the font involves a call to the API AddFontResource.

Un-installation is even simpler: first a call to the API RemoveFontResource, and then removing the font file itself.

BOOL CAP_FontInstaller::UninstallFont( const CString& csFontName )
{
    BOOL          bSuccess = FALSE;
    PFONT_DETAILS pfd      = NULL;

    if( FindFontDescription( csFontName, pfd ) &&
        pfd->m_bInstalled == TRUE &&
        pfd->m_bAlreadyInTheSystem == FALSE &&
        RemoveFontResource( pfd->m_csInstalledFontFullPath ) )
    {
        _unlink( pfd->m_csInstalledFontFullPath );

        pfd->m_bInstalled = FALSE;
        bSuccess          = TRUE;
    }

    return bSuccess;
}

Keep in mind that if the font file is locked by the system, the call to _unlink will fail.

Notes

Windows is particularly fastidious when it comes to tinkering with fonts. So let me remind you that the code is provided 'AS IS' with no expressed or implied warranty. Proceed at your own risk.

The fonts used for the demo project (Camelot MF and Cigno MF) are the property of Rick W. Mueller.

The demo application/project is not very sophisticated. If either font is already installed in your system, the demo will not be able to install/uninstall the font(s) and, thus, will not work properly.

History

  • 31 Oct 2005: Initial release.
  • 24 Apr 2006: Fixed a small bug (thanks to Hugh S. Myers).

Acknowledgments

As always, my appreciation goes to the folks that make the CodeProject happen, and to all those guys that continue to freely share what they know. Thank you all.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here