Get a font filename based on the font handle (HFONT)

c++, qt, windows

Solution

The Windows API Font and Text Functions doesn't contain a function that returns the filename of a font. So a more creative solution has to be worked out.

The solution is to use the `GetFontData` function, which will give us the exact copy of the original font file. The only thing that's left is comparing this data with the contents of all installed/known fonts.

Lookup table

We will first create a lookup table (`FontList`) of all installed/known fonts:

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

The `FingerPrint` is a random part read from the font file in order to distinguish between fonts of the same filesize. You could also use a hash (f.e. SHA1) of the complete file to establish this.

Adding fonts

Method for adding a single font to this list is pretty straightforward:

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

A Qt way to add all Windows fonts to the lookup table goes like this:

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());

File enumeration can also be done using `FindFirstFile`/`FindNextFile` Windows API functions, but would be less readable for the purpose of this answer.

GetFontData helper

Then we create a wrapper function for the `GetFontData` function that creates a DC, selects the font by the `HFONT` handle and returns the fonts data:

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

Font filename lookup

Now we're all set for looking up the exact filename of a font by only knowing the `HFONT` handle:

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

Problem

I came across a situation where we needed to know the filename of a font currently in use by a `QFont`. Knowing that a `QFont` can give us the font family and Windows `HFONT` handle. The font family is not enough, because manipulating styles like `Bold` or `Italic` can result in Windows selecting a different font file. (f.e. arial.ttf, arialbd.ttf, arialbi.ttf, ariali.ttf). This code sample should give us `<path>\arial.ttf`: ``` QFont font("Arial", 12); FindFontFileName(font.handle()); ``` while this code sample should give us `<path>\arialbi.ttf` ``` QFont font("Arial", 12); font.setStyle(QFont::StyleItalic); font.setWeight(QFont::Bold); FindFontFileName(font.handle()); ```

Original source

Related problems