根据字体句柄(HFONT)获取字体文件名

9

我遇到了这样一种情况,我们需要知道当前被 QFont 使用的字体文件名。了解到一个 QFont 可以给我们提供字体系列和 Windows HFONT 句柄。

仅知道字体系列是不够的,因为像 BoldItalic 这样的样式调整会导致 Windows 选择不同的字体文件(例如:arial.ttf、arialbd.ttf、arialbi.ttf、ariali.ttf 等)。

以下代码示例应该能够给我们返回 <path>\arial.ttf

QFont font("Arial", 12);
FindFontFileName(font.handle());

尽管这段代码示例应该给我们<path>\arialbi.ttf

QFont font("Arial", 12);
font.setStyle(QFont::StyleItalic);
font.setWeight(QFont::Bold);
FindFontFileName(font.handle());

有许多人想做这件事情:根据字体名称和样式(粗体/斜体)获取字体文件名在C++ / Windows中从名称和样式获取字体文件路径。很奇怪,因为我想不出需要这样做的情况。对我来说似乎是糟糕的设计。 - Cody Gray
@CodeGray:顺便说一下,这不是重复的,因为这更像是从HFONT句柄转换到文件名,而不是从字体族转换到文件名。 - huysentruitw
1
不确定这是否意味着它不是重复的。只需调用 GetObject 检索 LOGFONT 即可在给定 HFONT 时检索字体系列,这很简单。但请不要将此解释为您的建议无用或不受欢迎——我也对问题和答案进行了投票支持。 - Cody Gray
1
这也是我想做的事情。我的原因是为了解决我们字体密集型软件应用产品中客户的字体安装问题。虽然字体通常驻留在官方 Windows 字体文件夹中,但它们不必如此。字体管理产品可以将它们移动到其他位置。而且,某些安装字体的产品并不完全正确。最后,人们会更改他们的注册表。顺便提一下,Mac OSX 有一个相当的 API。 - Paul Topping
1
@CodeGray 用于在 PDF 文件中嵌入字体,例如,您需要知道字体文件的名称。 - Pierre
显示剩余2条评论
1个回答

11
The Windows API 字体和文本函数 中没有包含返回字体文件名的函数。因此,需要想出更有创意的解决方案。
解决方案是使用 GetFontData 函数,它将给我们原始字体文件的精确副本。唯一剩下的就是将这些数据与所有已安装/已知字体的内容进行比较。 查找表 我们首先将创建一个查找表(FontList),其中包含所有已安装/已知字体的信息:
#define FONT_FINGERPRINT_SIZE    256
struct FontListItem
{
    std::string FileName;
    int FingerPrintOffset;
    char FingerPrint[FONT_FINGERPRINT_SIZE];
};

std::multimap< size_t, std::shared_ptr<FontListItem> > FontList;

"FingerPrint" 是从字体文件中读取的随机部分,用于区分大小相同的字体。您也可以使用文件的完整哈希值(例如 SHA1)来建立这个区分。 添加字体 将单个字体添加到列表中的方法非常简单:
void AddFontToList(const std::string& fontFileName)
{
    std::ifstream file(fontFileName, std::ios::binary | std::ios::ate);
    if (!file.is_open())
        return;

    size_t fileSize = file.tellg();
    if (fileSize < FONT_FINGERPRINT_SIZE)
        return;

    std::shared_ptr<FontListItem> fontListItem(new FontListItem());
    fontListItem->FileName = fontFileName;
    fontListItem->FingerPrintOffset = rand() % (fileSize - FONT_FINGERPRINT_SIZE);
    file.seekg(fontListItem->FingerPrintOffset);
    file.read(fontListItem->FingerPrint, FONT_FINGERPRINT_SIZE);
    FontList.insert(std::pair<size_t, std::shared_ptr<FontListItem> >(fileSize, fontListItem));
}

添加所有Windows字体到查找表的Qt方法如下:

const QDir dir(QString(getenv("WINDIR")) + "\\fonts");
dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks);
foreach (const QFileInfo fileInfo, dir.entryInfoList())
    AddFontToList(fileInfo.absoluteFilePath().toUtf8().constData());

文件枚举也可以使用FindFirstFile/FindNextFile Windows API函数进行,但对于本回答的目的来说会更加难以阅读。 GetFontData助手 然后我们创建一个包装函数用于GetFontData函数,它会创建一个DC,通过HFONT句柄选择字体并返回字体数据:
bool GetFontData(const HFONT fontHandle, std::vector<char>& data)
{
    bool result = false;
    HDC hdc = ::CreateCompatibleDC(NULL);
    if (hdc != NULL)
    {
        ::SelectObject(hdc, fontHandle);
        const size_t size = ::GetFontData(hdc, 0, 0, NULL, 0);
        if (size > 0)
        {
            char* buffer = new char[size];
            if (::GetFontData(hdc, 0, 0, buffer, size) == size)
            {
                data.resize(size);
                memcpy(&data[0], buffer, size);
                result = true;
            }
            delete[] buffer;
        }
        ::DeleteDC(hdc);
    }
    return result;
}

字体文件名查找

现在我们已经准备好通过仅知道 HFONT 句柄来查找字体的确切文件名:

std::string FindFontFileName(const HFONT fontHandle)
{
    std::vector<char> data;
    if (GetFontData(fontHandle, data))
    {
        for (auto i = FontList.lower_bound(data.size()); i != FontList.upper_bound(data.size()); ++i)
        {
            if (memcmp(&data[i->second->FingerPrintOffset], i->second->FingerPrint, FONT_FINGERPRINT_SIZE) == 0)
                return i->second->FileName;
        }
    }
    return std::string();
}

请注意,并非所有字体都存储在 %WINDIR%\Fonts 中。字体路径存储在注册表设置中,即 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Fonts -- 有关更多详细信息,请参见 https://dev59.com/DG455IYBdhLWcg3wCPjh#39093093,包括为什么存储在该注册表键中的字体名称不可靠。 - Marc Durdin

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