获取基于字体句柄的字体文件名(HFONT)

huy*_*itw 7 c++ windows qt

我遇到了一种情况,我们需要知道当前正在使用的字体的文件名QFont.知道一个QFont可以给我们字体系列和Windows HFONT句柄.

字体系列是不够的,因为操纵类似BoldItalic可能导致Windows选择不同的字体文件.(fe arial.ttf,arialbd.ttf,arialbi.ttf,ariali.ttf).

这段代码示例应该给我们<path>\arial.ttf:

QFont font("Arial", 12);
FindFontFileName(font.handle());
Run Code Online (Sandbox Code Playgroud)

而这段代码样本应该给我们 <path>\arialbi.ttf

QFont font("Arial", 12);
font.setStyle(QFont::StyleItalic);
font.setWeight(QFont::Bold);
FindFontFileName(font.handle());
Run Code Online (Sandbox Code Playgroud)

huy*_*itw 10

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;
Run Code Online (Sandbox Code Playgroud)

FingerPrint是从字体文件中读取的随机部分,以便区分相同文件大小的字体.您还可以使用完整文件的哈希(fe MD5)来建立它.

添加字体

将单个字体添加到此列表的方法非常简单:

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));
}
Run Code Online (Sandbox Code Playgroud)

将所有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());
Run Code Online (Sandbox Code Playgroud)

文件枚举也可以使用FindFirstFile/ FindNextFileWindows 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;
}
Run Code Online (Sandbox Code Playgroud)

字体文件名查找

现在我们都设置为只知道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();
}
Run Code Online (Sandbox Code Playgroud)