Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

windows

qt

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());
like image 929
huysentruitw Avatar asked May 27 '13 09:05

huysentruitw


1 Answers

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();
}
like image 100
huysentruitw Avatar answered Oct 20 '22 00:10

huysentruitw