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:
- Including the AP_FontInstaller.h and AP_FontInstaller.cpp in your project.
- Adding the relevant font files as resources to the project.
- Inserting the
CAP_FontInstaller
class header into the header of the main dialog:
#include "AP_FontInstaller.h"
- Creating an object of the class
CAP_FontInstaller
:
CAP_FontInstaller m_capFontInstaller;
- 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();
- 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 * ,
int ,
LPARAM lParam )
{
PFONT_DETAILS pfd = (PFONT_DETAILS)lParam;
if( pfd->m_csFontName == lpelfe->elfLogFont.lfFaceName )
{
pfd->m_bInstalled = TRUE;
return FALSE;
}
else
return TRUE;
}
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.