65.9K
CodeProject 正在变化。 阅读更多。
Home

CAP_FontInstaller - 应用程序特定字体的基本(卸)安装程序类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.62/5 (18投票s)

2005 年 10 月 31 日

4分钟阅读

viewsIcon

67765

downloadIcon

910

一个简单的包装器,用于从编译的资源动态安装/卸载特定于应用程序的字体。

AppFontInstaller screenshot

引言

我们开发的应用程序偶尔需要用户系统中可能存在或不存在的字体。一种常见的解决方案是将字体文件与应用程序设置的其他部分捆绑在一起,并随其他所有内容一起安装。MSI、NSIS 或 Inno 等软件包可以轻松实现这一点。缺点是您的字体将永久地在整个系统范围内可见,例如,用户可以随时删除/卸载您的应用程序所依赖的字体。为防止这种情况发生,您可以实现代码,在每次运行时检查字体状态,并在需要时自行终止、警告用户或即时安装字体。

当然,如果您是即时安装字体,您可以更进一步,将字体的寿命限制在应用程序执行期间。这种方法还有一个额外的优点,可以一定程度上保护您的字体的“隐私”,因为它只会在您的应用程序运行时可见。本文介绍了一个简单的类,它封装了这种功能,即允许应用程序仅用几行代码即可从编译的资源动态安装/卸载字体。

使用代码

代码的直接用法如下:

  1. AP_FontInstaller.hAP_FontInstaller.cpp 包含到您的项目中。
  2. 将相关的字体文件作为资源添加到项目中。
  3. CAP_FontInstaller 类的头文件插入到主对话框的头文件中。
    #include "AP_FontInstaller.h"
  4. 创建一个 CAP_FontInstaller 类的对象。
    CAP_FontInstaller m_capFontInstaller;
  5. OnInitDialog() 中,将字体资源添加到安装程序并安装字体。
    m_capFontInstaller
        .AddFont( _T("Camelot MF"), IDR_FONT_CAMELOT, _T("FONTS") )
        .AddFont( _T("Cigno MF"), IDR_FONT_CIGNO, _T("FONTS") );
    
    m_capFontInstaller.InstallAllFonts();
  6. 务必在应用程序终止前卸载字体,例如,在 OnClose() 方法中。
    m_capFontInstaller.UninstallAllFonts();

该类 CAP_FontInstaller 只会安装和卸载系统中尚不存在的字体。因此,如果您忘记卸载字体,下次运行应用程序时,它会将这些字体标记为已存在,您将无法卸载它们。当然,您也可以修改代码的行为以适应其他目的,这可能不是问题。

关注点

CAP_FontInstaller 类代码的实现方式相当简单:它维护一个可安装和卸载的字体列表。需要遵守的规则是,如果这些字体中的任何一个已存在于系统中,它们将不会被更改。

因此,首要任务是找出哪些字体已安装。这通过调用 API EnumFontFamiliesEx 来完成,该 API 又依赖于回调函数的实现。代码如下:

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
}

回调函数 _EnumFontFamExProc 由系统调用,每次处理一种字体。请注意,字体名称可能(而且通常不是)与字体文件名相同。如有疑问,请使用字体查看器。

如前所述,安装程序维护一个要安装/卸载的字体列表。此列表是结构体数组,其声明如下:

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

为传递给公共方法 AddFont 的每种字体填充一个结构体并将其添加到数组中。

安装涉及从应用程序编译的资源中提取字体,并在适当的系统文件夹(不同操作系统版本不同)中创建字体文件。代码如下:

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

正如您所见,提取资源和写入字体文件非常简单。如果成功,安装字体将涉及调用 API AddFontResource

卸载更简单:首先调用 API RemoveFontResource,然后删除字体文件本身。

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

请记住,如果字体文件被系统锁定,_unlink 调用将失败。

注释

Windows 在处理字体时特别挑剔。因此,我提醒您,代码按“原样”提供,不附带任何明示或暗示的保证。请自行承担风险。

演示项目使用的字体(Camelot MF 和 Cigno MF)是 Rick W. Mueller 的财产。

演示应用程序/项目不是非常复杂。如果任何一个字体已安装在您的系统中,演示将无法安装/卸载字体,因此将无法正常工作。

历史

  • 2005 年 10 月 31 日:初始发布。
  • 2006 年 4 月 24 日:修复了一个小错误(感谢 Hugh S. Myers)。

致谢

一如既往,我感谢使 CodeProject 成为现实的人们,以及所有继续自由分享他们知识的人们。谢谢大家。

© . All rights reserved.