使用Windows API加载TTF字体

3
使用 CreateFont 函数可以指定字体名称和其他属性。但是,如果我有一个 font.ttf 文件,并且希望 Windows 加载该特定字体,我该如何指定使用该文件?

那么CreateFontIndirectEx不是一个选项吗?在那里你可以挑选。 - 0xC0000022L
@Claudiu:所以你知道文件名,但不知道字体名称?或者说你必须预期字体名称和属性不足以区分你(已安装的)字体和其他字体?几年前我曾经寻找过加载字体的解决方案(除了PE资源中的旧样式字体),但没有找到任何方法可以在不安装的情况下加载字体。当时我花费了相当多的时间。 - 0xC0000022L
@STATUS_ACCESS_DENIED:更像是我有一个用户提供的字体文件,我想使用它,但它不一定被安装。我的程序可以安装它-没问题。但是我怎么得到那个特定已安装的字体?文件名可以是 foo.ttf - 它并没有说明它的逻辑名称。 - Claudiu
@Claudiu:我明白了。嗯,这个有点棘手。+1 - 0xC0000022L
@Alex K.:你能再深入一点吗? - Claudiu
显示剩余2条评论
4个回答

3

诚然,这种方法相对来说比较间接,但在运行于Windows 7+的环境下,你可以利用GDI与DWrite进行交互。

#include <Windows.h>
#include <WindowsX.h>
#include <DWrite.h>

...

// Make the font file visible to GDI.
AddFontResourceEx(fontFileName, FR_PRIVATE, 0);
if (SUCCEEDED(GetLogFontFromFileName(fontFileName, &logFont)))
{
    logFont.lfHeight = -long(desiredPpem);
    HFONT hf = CreateFontIndirect(&logFont);
    HFONT oldFont = SelectFont(hdc, hf);
    ...
    // Do stuff...
    ...
    SelectFont(hdc, oldFont);
}
RemoveFontResource(fontFileName);

....

HRESULT GetLogFontFromFileName(_In_z_ wchar const* fontFileName, _Out_ LOGFONT* logFont)
{
    // DWrite objects
    ComPtr<IDWriteFactory> dwriteFactory;
    ComPtr<IDWriteFontFace> fontFace;
    ComPtr<IDWriteFontFile> fontFile;
    ComPtr<IDWriteGdiInterop> gdiInterop;

    // Set up our DWrite factory and interop interface.
    IFR(DWriteCreateFactory(
        DWRITE_FACTORY_TYPE_SHARED,
        __uuidof(IDWriteFactory),
        reinterpret_cast<IUnknown**>(&dwriteFactory)
        );
    IFR(g_dwriteFactory->GetGdiInterop(&gdiInterop));

    // Open the file and determine the font type.
    IFR(g_dwriteFactory->CreateFontFileReference(fontFileName, nullptr, &fontFile));
    BOOL isSupportedFontType = false;
    DWRITE_FONT_FILE_TYPE fontFileType;
    DWRITE_FONT_FACE_TYPE fontFaceType;
    UINT32 numberOfFaces = 0;
    IFR(fontFile->Analyze(&isSupportedFontType, &fontFileType, &fontFaceType, &numberOfFaces));

    if (!isSupportedFontType)
        return DWRITE_E_FILEFORMAT;

    // Set up a font face from the array of font files (just one)
    ComPtr<IDWriteFontFile> fontFileArray[] = {fontFile};
    IFR(g_dwriteFactory->CreateFontFace(
        fontFaceType,
        ARRAYSIZE(fontFileArray), // file count
        &fontFileArray[0], // or GetAddressOf if WRL ComPtr
        0, // faceIndex
        DWRITE_FONT_SIMULATIONS_NONE,
        &fontFace
        );

    // Get the necessary logical font information.
    IFR(gdiInterop->ConvertFontFaceToLOGFONT(fontFace, OUT logFont));

    return S_OK;
}

其中IFR只是一个失败宏,当HRESULT失败时返回,并且ComPtr是一个辅助智能指针类(可用自己的替代,或者ATL CComPtr、WinRT ComPtr、VS2013 _com_ptr_t等)。


3

我非常确定你是做不到的。所有字体请求都通过字体映射器进行,并且它会选择最接近满足所给定规格的字体文件。虽然我不确定它是否在现实中这样做,但至少从理论上讲,它可以使用来自两个完全不同的字体文件的数据来创建一个逻辑字体。


有没有一种方法可以解析 ttf 文件以获取其所有属性,以便我可以安装它,然后将这些属性传递给 Windows,以获取特定的字体? - Claudiu
@Claudiu:是的,但自己做这件事绝对不容易。我可能会使用类似Freetype(实际上是Freetype 2)的东西来访问字体文件中的数据。 - Jerry Coffin
是的,最终我只需枚举现有的Windows字体来解决我的问题。 - Claudiu
使用AddFontResourceEx("data/fonts/quantico.ttf", FR_PRIVATE)似乎可以使嵌入式Webkit识别font-family:Quantico,即使该字体在我的游戏引擎中实际上并不可用。 - Петър Петров

2
一种可能的方法是使用EnumFonts()来保存结果。然后使用AddFontResourceEx()添加您自己的私有字体,并再次使用EnumFonts(),区别在于您添加的内容。请注意,TTF和位图字体枚举方式不同,但对于此测试,这并不重要。
如果您正在使用位图字体,则可以轻松解析(.FNT和.FON)。对于TTF,您可能需要构建(或借用,如另一位评论者建议的FreeType)一个解析器,以从TTF文件中提取“名称”表。
对于您控制或提供应用程序的字体来说,这似乎是很多工作。
我们使用AddFontResourceEx()添加私有字体,但由于我们控制要添加的字体,因此我们只需将传递给CreateFontIndirect()的字体名称硬编码为匹配即可。

问题在于,如果您添加的字体已经安装,则差异将为空。 - Tony Edgecombe
如果您将字体标记为“FR_PRIVATE”,然后使用其众所周知的名称进行“CreateFontIndirect”操作,您的应用程序将使用它,覆盖具有相同名称的系统字体。 - Петър Петров

1

如果您不关心安装字体,可以使用AddFontResource进行安装,然后您可以通过查看HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts中的映射来获取物理.TTF和逻辑/系列名称之间的关系。

我在评论中提到了PrivateFontCollection,因为我认为您想暂时这样做;您可以使用PrivateFontCollection::AddFontFile将TTF加载到PFC中,从集合中获取新的FontFamily对象并检查GetFamilyName。(我已经使用过此项的.net实现,但没有使用原始API)


AddFontResource 实际上并没有将添加的字体信息持久化到注册表中以便于重启后使用,只是暂时存在于 GDI 的系统表中。当您通过资源管理器安装字体文件(右键单击字体文件),它会调用 AddFontResource 函数,并更新相应的注册表键值,并发送 WM_FONTCHANGE 消息。详情请参考 http://msdn.microsoft.com/en-us/library/windows/desktop/dd144833(v=vs.85).aspx。 - Dwayne Robinson
这就是为什么未标记为FR_PRIVATE的ttf文件会被PID 4使用的原因 :P - Петър Петров

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接