如何在不安裝字型的情況下使用字型

2020-08-13 16:07:30
如何在不首先在使用者系統上安裝字型的情況下使用它

目錄

 

介紹

很多時候,由於內部圖形設計人員會選擇字型,因此需要在應用程式中使用特定字型。爲了使應用程式使用字型,需要使用安裝程式來安裝字型。使用者計算機上的字型過多可能會大大降低系統速度。

實際上,您無需安裝字型就可以擺脫困境:作爲程式設計師,GDI和GDI +分別爲您提供了兩種新增字型的方式,供應用程式使用而無需安裝字型。我將在本文中向您展示!

GDI的AddFontResourceEx

首先讓我談談GDI的兩個嚮應用程式新增字型的功能。然後,我將討論GDI +自身的功能。您可以使用AddFontResourceEx新增物理字型檔案供應用程式使用。

int AddFontResourceEx(
  LPCTSTR lpszFilename, 	// font file name
  DWORD fl,             	// font characteristics
  PVOID pdv             	// reserved
);

 

這是一個使用方法的例子AddFontResourceEx

CString szFontFile = "D:\\SkiCargo.ttf";

int nResults = AddFontResourceEx(
    m_szFontFile, 		// font file name
    FR_PRIVATE,    	// font characteristics
    NULL);

 

要使用新增的字型,只需在CreateFontCreateFontIndirect函數中指定其名稱即可,就像其他已安裝的字型一樣。要知道字型的名稱,只需在Windows資源管理器中右鍵單擊TTF擴充套件名檔案,然後選擇「開啓」即可看到其實際名稱。或者,您可以使用我編寫的TTFTTC類來了解字型名稱。

注意:本文中的字型檔名稱(「 SkiCargo.ttf 」)實際上是其字型名稱「 SkiCargo」;通常不是這種情況!爲了安全起見,請使用Windows資源管理器右鍵單擊方法或剛纔提到的TTFand TTC類來查詢名稱!

CClientDC dc(this);

dc.SetBkMode(TRANSPARENT);

LOGFONT lf;
memset(&lf, 0, sizeof(lf));
lf.lfHeight = -MulDiv(24, pDC->GetDeviceCaps(LOGPIXELSY), 72);
lf.lfWeight = FW_NORMAL;
lf.lfOutPrecision = OUT_TT_ONLY_PRECIS;
wcscpy_s(lf.lfFaceName, L"SkiCargo");

// create and select it
CFont newFont;
if (!newFont.CreateFontIndirect(&lf))
    return;
CFont* pOldFont = dc.SelectObject(&newFont);

// use a path to record how the text was drawn
wchar_t buf[] = _T("The quick brown fox jumps over the lazy dog!");
dc.TextOut( 10, 10, buf, wcslen(buf));

// Put back the old font
dc.SelectObject(pOldFont);

 

您必須記得RemoveFontResourceEx在應用程式退出之前先打電話。您應該注意,這些參數必須與您輸入的參數相同AddFontResourceEx

BOOL RemoveFontResourceEx(
  LPCTSTR lpFileName,  	// name of font file
  DWORD fl,            	// font characteristics
  PVOID pdv            	// Reserved.
);

CString szFontFile = "D:\\SkiCargo.ttf";

BOOL b = RemoveFontResourceEx(
    m_szFontFile, 		// name of font file
    FR_PRIVATE,   		// font characteristics
    NULL         		// Reserved.
    );

 

GDI的AddFontMemResourceEx

如果我們的字型位於資源DLL,cabinet檔案或檔案壓縮檔案中,則可以將其提取到記憶體中,然後用於AddFontMemResourceEx從記憶體中讀取它。

HANDLE AddFontMemResourceEx(
  PVOID pbFont,       	// font resource
  DWORD cbFont,       	// number of bytes in font resource 
  PVOID pdv,          	// Reserved. Must be 0.
  DWORD *pcFonts      	// number of fonts installed
);

 

這是一個如何AddFontMemResourceEx在資源中嵌入的字型檔案上使用範例。注意:要瞭解如何將字型檔案新增到資源中,可以在本文後面參考本

HINSTANCE hResInstance = AfxGetResourceHandle( );

HRSRC res = FindResource(hResInstance,
    MAKEINTRESOURCE(IDR_MYFONT),L"BINARY");
if (res) 
{
    HGLOBAL mem = LoadResource(hResInstance, res);
    void *data = LockResource(mem);
    size_t len = SizeofResource(hResInstance, res);

    DWORD nFonts;
    m_fonthandle = AddFontMemResourceEx(
        data,       	// font resource
        len,       	// number of bytes in font resource 
        NULL,          	// Reserved. Must be 0.
        &nFonts      	// number of fonts installed
        );

    if(m_fonthandle==0)
    {
        MessageBox(L"Font add fails", L"Error");
    }
}

 

要使用新增的字型,請參考前面的AddFontResourceEx範例。他們是一樣的。就像其他已安裝的字型一樣使用它。您應RemoveFontMemResourceEx在應用程式退出之前致電。處理結束後,即使您不致電,系統也會解除安裝字型RemoveFontMemResourceEx注意:參數必須與您輸入的參數相同AddFontResourceEx

BOOL RemoveFontMemResourceEx(
  HANDLE fh   // handle to the font resource
);

if(m_fonthandle)
{
    BOOL b = RemoveFontMemResourceEx(m_fonthandle);
    if(b==0)
    {
        MessageBox(L"Font remove fails", L"Error");
    }
}

 

GDI +的PrivateFontCollection的AddFontFile

對於GDI +,您可以使用其PrivateFontCollection類成員AddFontFile新增物理字型檔案。

Status AddFontFile(const WCHAR* filename);

 

這是AddFontFile新增字型檔案的方法:

Gdiplus::PrivateFontCollection m_fontcollection;
//...
CString szFontFile = szExePath + L"SkiCargo.ttf";

Gdiplus::Status nResults = m_fontcollection.AddFontFile(szFontFile);

 

這是使用剛剛新增到PrivateFontCollection物件中的字型的方法m_fontcollection

// When painting the text
FontFamily fontFamily;
int nNumFound=0;
m_fontcollection.GetFamilies(1,&fontFamily,&nNumFound);

if(nNumFound>0)
{
    Font font(&fontFamily,28,FontStyleRegular,UnitPixel);

    StringFormat strformat;
    wchar_t buf[] = L"The quick brown fox jumps over the lazy dog!";
    graphics.DrawString(buf,wcslen(buf),&font, 
             PointF(10.0f,10.0f),&strformat,&brush);
}

 

注意:與GDI的AddFontResourceEx和不同AddFontMemResourceEx,沒有RemoveFontFilefor AddFontFile。所有新增的字型都會被PrivateFontCollection的解構函式刪除。

GDI +的PrivateFontCollection的AddMemoryFont

對於GDI +,您可以使用其PrivateFontCollection類成員AddMemoryFont在記憶體中新增字型。

Status AddMemoryFont(const VOID *memory, INT length);

 

這是AddMemoryFont在資源中嵌入的字型檔案上使用方法。與相似AddFontFile,沒有RemoveMemoryFont呼叫。PrivateFontCollection的解構函式將處理所有事情。注意:要瞭解如何將字型檔案新增到資源中,可以在本文後面參考本

HINSTANCE hResInstance = AfxGetResourceHandle( );

HRSRC res = FindResource(hResInstance,
    MAKEINTRESOURCE(IDR_MYFONT),L"BINARY");
if (res) 
{
    HGLOBAL mem = LoadResource(hResInstance, res);
    void *data = LockResource(mem);
    size_t len = SizeofResource(hResInstance, res);

    Gdiplus::Status nResults = m_fontcollection.AddMemoryFont(data,len);

    if(nResults!=Gdiplus::Ok)
    {
        MessageBox(L"Font add fails", L"Error");
    }
}

 

至於如何使用剛剛新增到PrivateFontCollection物件中的字型m_fontcollection,請參考前面的AddFontFile範例,它們是相同的。

獲取TTF和TTC字型名稱

我編寫了兩個類,分別是TTF和,分別TTC從TTF / OTF和TTC字型檔案中讀取字型名稱。爲了支援Matroska(mkv)檔案字型讀取或嵌入式字型資源讀取,my TTFTTCclass支援解析記憶體中的字型檔案。僅供參考,這些Matroska檔案通常包含視訊通道,多種語言的音訊通道,字幕以及視訊中字幕的字型。我的課程非常易於使用。下面 下麪是一個以物理方式或在記憶體中讀取TTF檔案並顯示其資訊的範例:

void TestReadTtfFromFile(const std::wstring& szFile)
{
    TTF ttf;
    ttf.Parse(szFile);
    Display(ttf);
}

void TestReadTtfFromMemory(const std::wstring& szFile)
{
    struct _stat bufferStat;
    int nRet = _wstat(szFile.c_str(), &bufferStat);
    FILE* pFile = _wfopen(szFile.c_str(), L"rb");
    if(pFile == NULL)
    {
        std::wcout<<L"Failed to create file"<<std::endl;
        return;
    }
    BYTE* buf = new BYTE[bufferStat.st_size];
    fread(buf,bufferStat.st_size,1,pFile);
    fclose(pFile);
    TTF ttf;
    ttf.Parse(buf, bufferStat.st_size);

    delete [] buf;

    Display(ttf);
}

void Display(TTF& ttf)
{
    std::wcout<<L"FontName : "<<ttf.GetFontName()<<std::endl;
    std::wcout<<L"Copyright : "<<ttf.GetCopyright()<<std::endl;
    std::wcout<<L"FontFamilyName : "<<ttf.GetFontFamilyName()<<std::endl;
    std::wcout<<L"FontSubFamilyName : "<<ttf.GetFontSubFamilyName()<<std::endl;
    std::wcout<<L"FontID : "<<ttf.GetFontID()<<std::endl;
    std::wcout<<L"Version : "<<ttf.GetVersion()<<std::endl;
    std::wcout<<L"PostScriptName : "<<ttf.GetPostScriptName()<<std::endl;
    std::wcout<<L"Trademark : "<<ttf.GetTrademark()<<std::endl;

    std::wstring szBold = ttf.IsBold() ? L"true" : L"false"; 
    std::wstring szItalic = ttf.IsItalic() ? L"true" : L"false"; 
    std::wstring szRegular = ttf.IsRegular() ? L"true" : L"false"; 

    std::wcout<<L"Bold : "<<szBold<<std::endl;
    std::wcout<<L"Italic : "<<szItalic<<std::endl;
    std::wcout<<L"Regular : "<<szRegular<<std::endl;

    std::wcout<<std::endl;
}

 

TTC是包含TTF字型集合的字型檔案。下面 下麪是一個以物理方式或在記憶體中讀取TTC檔案並顯示其資訊的範例。

void TestReadTtcFromFile(const std::wstring& szFile)
{
    TTC ttc;
    ttc.Parse(szFile);
    Display(ttc);
}

void TestReadTtcFromMemory(const std::wstring& szFile)
{
    struct _stat bufferStat;
    int nRet = _wstat(szFile.c_str(), &bufferStat);
    FILE* pFile = _wfopen(szFile.c_str(), L"rb");
    if(pFile == NULL)
    {
        std::wcout<<L"Failed to create file"<<std::endl;
        return;
    }
    BYTE* buf = new BYTE[bufferStat.st_size];
    fread(buf,bufferStat.st_size,1,pFile);
    fclose(pFile);
    TTC ttc;
    ttc.Parse(buf, bufferStat.st_size);

    delete [] buf;

    Display(ttc);
}

void Display(TTC& ttc)
{
    for(size_t i=0; i<ttc.Size(); ++i )
    {
        std::wcout<<L"FontName : "<<ttc.GetFontName(i)<<std::endl;
        std::wcout<<L"Copyright : "<<ttc.GetCopyright(i)<<std::endl;
        std::wcout<<L"FontFamilyName : "<<ttc.GetFontFamilyName(i)<<std::endl;
        std::wcout<<L"FontSubFamilyName : "<<ttc.GetFontSubFamilyName(i)<<std::endl;
        std::wcout<<L"FontID : "<<ttc.GetFontID(i)<<std::endl;
        std::wcout<<L"Version : "<<ttc.GetVersion(i)<<std::endl;
        std::wcout<<L"PostScriptName : "<<ttc.GetPostScriptName(i)<<std::endl;
        std::wcout<<L"Trademark : "<<ttc.GetTrademark(i)<<std::endl;

        std::wstring szBold = ttc.IsBold(i) ? L"true" : L"false"; 
        std::wstring szItalic = ttc.IsItalic(i) ? L"true" : L"false"; 
        std::wstring szRegular = ttc.IsRegular(i) ? L"true" : L"false"; 

        std::wcout<<L"Bold : "<<szBold<<std::endl;
        std::wcout<<L"Italic : "<<szItalic<<std::endl;
        std::wcout<<L"Regular : "<<szRegular<<std::endl;

        std::wcout<<std::endl;
    }
}

 

注意:您應該始終呼叫GetFontFamilyName方法來獲取字型名稱,而不是GetFontName方法。大多數位體屬於字型家族。例如,在Arial字型家族下,有幾種Arial字型,其字型名稱爲「 Arial Bold」,「 Arial Bold Italic」,等等。以下是有關如何將TTFGetFontFamilyName方法與AddFontResourceEx函數一起使用的範例:

TTF m_Ttf;

//... During Initialization
CString szFontFile = "D:\\SkiCargo.ttf";

int nResults = AddFontResourceEx(
    m_szFontFile, 		// font file name
    FR_PRIVATE,           	// font characteristics
    NULL);

m_Ttf.Parse((LPCWSTR)(m_szFontFile));    
    
//... In the OnPaint method    
    
CClientDC dc(this);

dc.SetBkMode(TRANSPARENT);

LOGFONT lf;
memset(&lf, 0, sizeof(lf));
lf.lfHeight = -MulDiv(24, pDC->GetDeviceCaps(LOGPIXELSY), 72);
lf.lfWeight = FW_NORMAL;
lf.lfOutPrecision = OUT_TT_ONLY_PRECIS;
//wcscpy_s(lf.lfFaceName, L"SkiCargo");
wcscpy_s(lf.lfFaceName, m_Ttf.GetFontFamilyName().c_str());

// create and select it
CFont newFont;
if (!newFont.CreateFontIndirect(&lf))
    return;
CFont* pOldFont = dc.SelectObject(&newFont);

// use a path to record how the text was drawn
wchar_t buf[] = _T("The quick brown fox jumps over the lazy dog!");
dc.TextOut( 10, 10, buf, wcslen(buf));

// Put back the old font
dc.SelectObject(pOldFont);

 

注意:我在網路上找不到足夠的資訊來解析fon檔案,該檔案是帶有「 fon」擴充套件名的字型檔案。我嘗試了反向工程來獲取檔名,但是失敗了。但是,我會繼續嘗試。

將字型檔案新增到資源

要將字型檔案新增到資源部分,請遵循我的演練範例。請注意,我的方法是直接編輯資原始檔,而不是通過IDE的資源編輯器新增它,因爲根據我的經驗,資源編輯器傾向於弄亂資源的rc檔案,從而導致所見即所得對話方塊編輯器不可用。注意:現在,最新的資源編輯器可能更加強大和穩定。要新增字型檔案,我們必須分配一個資源ID來參照該字型。爲此,請關閉您關注的解決方案或專案(如果已開啓)。要分配資源ID,請開啓Resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by TestGDI_AddFontMem.RC
//
#define IDR_MAINFRAME			128
#define IDD_TESTGDI_ADDFONTMEM_DIALOG	102

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS

#define _APS_NEXT_RESOURCE_VALUE		129
#define _APS_NEXT_CONTROL_VALUE		1000
#define _APS_NEXT_SYMED_VALUE		101
#define _APS_NEXT_COMMAND_VALUE		32771
#endif
#endif

 

我相信您比我的這個簡單專案擁有更多的資源ID。讓我們將定義的ID命名爲「 IDR_MYFONT」。當然,您可以使用您認爲合適的任何方式來命名。我們分配IDR_MYFONT,目前的價值_APS_NEXT_RESOURCE_VALUE 129。然後我們將增加_APS_NEXT_RESOURCE_VALUE; 這很重要,我們必須這樣做,否則下一個資源將與您的字型共用相同的數位ID。下面 下麪是手動編輯的Resource.h的外觀:

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by TestGDI_AddFontMem.RC
//
#define IDR_MAINFRAME			128
#define IDD_TESTGDI_ADDFONTMEM_DIALOG	102
#define IDR_MYFONT				129

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS

#define _APS_NEXT_RESOURCE_VALUE		130
#define _APS_NEXT_CONTROL_VALUE		1000
#define _APS_NEXT_SYMED_VALUE		101
#define _APS_NEXT_COMMAND_VALUE		32771
#endif
#endif

 

接下來,我們將編輯rc擴充套件檔案。在您喜歡的文字編輯器中開啓檔案。注意:Visual Studio不應開啓此rc檔案所在的專案。搜尋下面 下麪列出的部分:

/////////////////////////////////////////////////////////////////////////////
//
// BINARY
//

 

如果找不到此部分,則可以自己新增。接下來新增字型ID及其字型檔案。您的二進制部分可能看起來像這樣。

/////////////////////////////////////////////////////////////////////////////
//
// BINARY
//
IDR_MYFONT              BINARY                  "res\\SkiCargo.ttf"

 

如RC程式碼所示,IDR_MYFONT 是一個二進制資源,它參照專案資料夾的「 res 」子資料夾中的SkiCargo.ttf檔案。

如果發現在資源中新增字型很麻煩,則可以重新命名字型檔名及其擴充套件名,這樣沒人會知道該檔案是一種字型並弄亂了它。您甚至可以加密或壓縮它。在讀取記憶體中的檔案之前,只需對其解密或將其解壓縮即可。

結論

您已經從GDI和GDI +中看到了兩種方法,它們可以物理載入字型檔案或從記憶體載入字型檔案並使用它們。我希望這可以消除程式設計師在使用者計算機上安裝字型以使用它們的需要。我介紹了兩個類來讀取TTF和TTC字型檔案的字型名稱。您喜歡或不喜歡這篇文章的任何地方,請告訴我,以便我可以對本文進行改進。我希望您喜歡閱讀我的文章!