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