// XFont.cpp  Version 1.1
//
// Author:       Philip Patrick (GetFontProperties)
//
// Version 1.0   - Initial release of GetFontProperties()
//
// Modified by:  Hans Dietrich
//               hdietrich2@hotmail.com
//
// Version 1.1:  - Removed MFC dependency from GetFontProperties()
//               - Converted CFile file I/O to memory mapped file
//               - Added Unicode support
//               - Combined with my GetFontFile() routine
//
///////////////////////////////////////////////////////////////////////////////

#include "XTrace.h"
#include "XFont.h"

#include <crtdbg.h>
#include <shlobj.h>

///////////////////////////////////////////////////////////////////////////////
//
// If you do not want TRACE output you can
// uncomment the following lines:
//
#undef  TRACE
#if _MSC_VER > 1300
  #define TRACE __noop
#else
  #define TRACE ((void)0)
#endif
//
///////////////////////////////////////////////////////////////////////////////

#pragma warning(disable : 4127)		// conditional expression is constant

///////////////////////////////////////////////////////////////////////////////
// private routines
static LONG GetNextNameValue(HKEY key, LPCTSTR subkey, LPTSTR szName, LPTSTR szData);

///////////////////////////////////////////////////////////////////////////////
// defines used by GetWinVer()
#define WUNKNOWNSTR	_T("unknown Windows version")

#define W2KSTR		 _T("Windows 2000")
#define WXPSTR		 _T("Windows XP")
#define W2003STR	 _T("Windows Server 2003")
#define WVISTASTR	 _T("Windows Vista")
#define W2008STR	 _T("Windows Server 2008")
#define W2008R2STR _T("Windows Server 2008 R2")
#define W7STR	     _T("Windows 7")
#define W2012STR	 _T("Windows Server 2012")
#define W8STR	     _T("Windows 8")

#define WUNKNOWN	0

#define W9XFIRST	1
#define W95			  1
#define W95SP1		2
#define W95OSR2		3
#define W98			  4
#define W98SP1		5
#define W98SE		  6
#define WME			  7
#define W9XLAST	 99

#define WNTFIRST	101
#define WNT351		101
#define WNT4		  102
#define W2K			  103
#define WXP			  104
#define W2003		  105
#define WVISTA    106
#define W2008		  107
#define W2008R2	  108
#define W7	   	  109
#define W2012		  110
#define W8	   	  111
#define WNTLAST		199

#define WCEFIRST	201
#define WCE			  201
#define WCELAST		299


///////////////////////////////////////////////////////////////////////////////
//
// structs used by GetFontProperties()
//
typedef struct _tagFONT_PROPERTIES_ANSI
{
	char csName[1024];
	char csCopyright[1024];
	char csTrademark[1024];
	char csFamily[1024];
} FONT_PROPERTIES_ANSI;

typedef struct _tagTT_OFFSET_TABLE
{
	USHORT	uMajorVersion;
	USHORT	uMinorVersion;
	USHORT	uNumOfTables;
	USHORT	uSearchRange;
	USHORT	uEntrySelector;
	USHORT	uRangeShift;
} TT_OFFSET_TABLE;

typedef struct _tagTT_TABLE_DIRECTORY
{
	char	szTag[4];			//table name
	ULONG	uCheckSum;			//Check sum
	ULONG	uOffset;			//Offset from beginning of file
	ULONG	uLength;			//length of the table in bytes
} TT_TABLE_DIRECTORY;

typedef struct _tagTT_NAME_TABLE_HEADER
{
	USHORT	uFSelector;			//format selector. Always 0
	USHORT	uNRCount;			//Name Records count
	USHORT	uStorageOffset;		//Offset for strings storage, from start of the table
} TT_NAME_TABLE_HEADER;

typedef struct _tagTT_NAME_RECORD
{
	USHORT	uPlatformID;
	USHORT	uEncodingID;
	USHORT	uLanguageID;
	USHORT	uNameID;
	USHORT	uStringLength;
	USHORT	uStringOffset;	//from start of storage area
} TT_NAME_RECORD;

#define SWAPWORD(x)		MAKEWORD(HIBYTE(x), LOBYTE(x))
#define SWAPLONG(x)		MAKELONG(SWAPWORD(HIWORD(x)), SWAPWORD(LOWORD(x)))


///////////////////////////////////////////////////////////////////////////////
//
// GetFontFile()
//
// Purpose:     Find the name of font file from the font name
//
// Parameters:  lpszFontName     - name of font
//              lpszDisplayName  - pointer to buffer where font display name
//                                 will be copied
//              nDisplayNameSize - size of display name buffer in TCHARs
//              lpszFontFile     - pointer to buffer where font file name
//                                 will be copied
//              nFontFileSize    - size of font file buffer in TCHARs
//
// Returns:     BOOL - TRUE = success
//
// Notes:       This is *not* a foolproof method for finding the name of a
//              font file. If a font has been installed in a normal manner,
//              and if it is in the Windows "Font" directory, then this method
//              will probably work. It will probably work for most screen
//              fonts and TrueType fonts. However, this method might not work
//              for fonts that are created or installed dynamically, or that
//              are specific to a particular device, or that are not installed
//              into the font directory.
//
BOOL GetFontFile(LPCTSTR lpszFontName,
				 LPTSTR lpszDisplayName,
				 int nDisplayNameSize,
				 LPTSTR lpszFontFile,
				 int nFontFileSize)
{
	_ASSERTE(lpszFontName && lpszFontName[0] != 0);
	if (!lpszFontName || lpszFontName[0] == 0)
		return FALSE;

	_ASSERTE(lpszDisplayName);
	if (!lpszDisplayName)
		return FALSE;

	_ASSERTE(lpszFontFile);
	if (!lpszFontFile)
		return FALSE;

	lpszDisplayName[0] = _T('\0');
	lpszFontFile[0] = _T('\0');

	TCHAR szName[2 * MAX_PATH];
	TCHAR szData[2 * MAX_PATH];

	int nVersion;
	TCHAR szVersion[100];
	GetWinVer(szVersion, sizeof(szVersion)/sizeof(TCHAR)-1, &nVersion);

	LPCTSTR szFontPath = NULL;
	if ((nVersion >= WNTFIRST) && (nVersion <= WNTLAST))
		szFontPath = _T("Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts");
	else
		szFontPath = _T("Software\\Microsoft\\Windows\\CurrentVersion\\Fonts");

	BOOL bResult = FALSE;

  while (GetNextNameValue(HKEY_LOCAL_MACHINE, szFontPath, szName, szData) == ERROR_SUCCESS)
	{
		if (_tcsnicmp(lpszFontName, szName, _tcslen(lpszFontName)) == 0)
		{
			TRACE(_T("found font\n"));
			_tcsncpy(lpszDisplayName, szName, nDisplayNameSize-1);
			_tcsncpy(lpszFontFile, szData, nFontFileSize-1);
			bResult = TRUE;
			break;
		}
		szFontPath = _T("");	// this will get next value, same key
	}

	GetNextNameValue(HKEY_LOCAL_MACHINE, NULL, NULL, NULL);	// close the registry key

	return bResult;
}

bool GetFontsFolder(LPTSTR lpszFontPath, int nFontPathSize)
{
  _ASSERTE(nFontPathSize >= _MAX_PATH);
  *lpszFontPath = '\0';
  SHGetFolderPath(NULL, CSIDL_FONTS, NULL, 0, lpszFontPath);
  return *lpszFontPath != '\0';
}

///////////////////////////////////////////////////////////////////////////////
//
// GetFontProperties()
//
// Purpose:     Get font name from font file
//
// Parameters:  lpszFilePath - file path of font file
//              lpFontPropsX - pointer to font properties struct
//
// Returns:     BOOL - TRUE = success
//
BOOL GetFontProperties(LPCTSTR lpszFilePath, FONT_PROPERTIES * lpFontPropsX)
{
	FONT_PROPERTIES_ANSI fp;
	FONT_PROPERTIES_ANSI * lpFontProps = &fp;

	memset(lpFontProps, 0, sizeof(FONT_PROPERTIES_ANSI));

	HANDLE hFile = INVALID_HANDLE_VALUE;
	hFile = ::CreateFile(lpszFilePath,
						 GENERIC_READ,// | GENERIC_WRITE,
						 0,
						 NULL,
						 OPEN_ALWAYS,
						 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
						 NULL);

	if (hFile == INVALID_HANDLE_VALUE)
	{
		TRACE(_T("ERROR:  failed to open '%s'\n"), lpszFilePath);
		TRACE(_T("ERROR: %s failed\n"), _T("CreateFile"));
		return FALSE;
	}

	// get the file size
	DWORD dwFileSize = ::GetFileSize(hFile, NULL);

	if (dwFileSize == INVALID_FILE_SIZE)
	{
		TRACE(_T("ERROR: %s failed\n"), _T("GetFileSize"));
		::CloseHandle(hFile);
		return FALSE;
	}

	TRACE(_T("dwFileSize = %d\n"), dwFileSize);

	// Create a file mapping object that is the current size of the file
	HANDLE hMappedFile = NULL;
	hMappedFile = ::CreateFileMapping(hFile,
									  NULL,
									  PAGE_READONLY, //PAGE_READWRITE,
									  0,
									  dwFileSize,
									  NULL);

	if (hMappedFile == NULL)
	{
		TRACE(_T("ERROR: %s failed\n"), _T("CreateFileMapping"));
		::CloseHandle(hFile);
		return FALSE;
	}

	LPBYTE lpMapAddress = (LPBYTE) ::MapViewOfFile(hMappedFile,		// handle to file-mapping object
											FILE_MAP_READ,//FILE_MAP_WRITE,			// access mode
											0,						// high-order DWORD of offset
											0,						// low-order DWORD of offset
											0);						// number of bytes to map

	if (lpMapAddress == NULL)
	{
		TRACE(_T("ERROR: %s failed\n"), _T("MapViewOfFile"));
		::CloseHandle(hMappedFile);
		::CloseHandle(hFile);
		return FALSE;
	}

	BOOL bRetVal = FALSE;
	int index = 0;

	TT_OFFSET_TABLE ttOffsetTable;
	memcpy(&ttOffsetTable, &lpMapAddress[index], sizeof(TT_OFFSET_TABLE));
	index += sizeof(TT_OFFSET_TABLE);

	ttOffsetTable.uNumOfTables = SWAPWORD(ttOffsetTable.uNumOfTables);
	ttOffsetTable.uMajorVersion = SWAPWORD(ttOffsetTable.uMajorVersion);
	ttOffsetTable.uMinorVersion = SWAPWORD(ttOffsetTable.uMinorVersion);

	//check is this is a true type font and the version is 1.0
	if (ttOffsetTable.uMajorVersion != 1 || ttOffsetTable.uMinorVersion != 0)
		return bRetVal;

	TT_TABLE_DIRECTORY tblDir;
	memset(&tblDir, 0, sizeof(TT_TABLE_DIRECTORY));
	BOOL bFound = FALSE;
	char szTemp[4096];
	memset(szTemp, 0, sizeof(szTemp));

	for (int i = 0; i< ttOffsetTable.uNumOfTables; i++)
	{
		//f.Read(&tblDir, sizeof(TT_TABLE_DIRECTORY));
		memcpy(&tblDir, &lpMapAddress[index], sizeof(TT_TABLE_DIRECTORY));
		index += sizeof(TT_TABLE_DIRECTORY);

		strncpy(szTemp, tblDir.szTag, 4);
		if (_stricmp(szTemp, "name") == 0)
		{
			bFound = TRUE;
			tblDir.uLength = SWAPLONG(tblDir.uLength);
			tblDir.uOffset = SWAPLONG(tblDir.uOffset);
			break;
		}
		else if (szTemp[0] == 0)
		{
			break;
		}
	}

	if (bFound)
	{
		index = tblDir.uOffset;

		TT_NAME_TABLE_HEADER ttNTHeader;
		memcpy(&ttNTHeader, &lpMapAddress[index], sizeof(TT_NAME_TABLE_HEADER));
		index += sizeof(TT_NAME_TABLE_HEADER);

		ttNTHeader.uNRCount = SWAPWORD(ttNTHeader.uNRCount);
		ttNTHeader.uStorageOffset = SWAPWORD(ttNTHeader.uStorageOffset);
		TT_NAME_RECORD ttRecord;
		bFound = FALSE;

		for (int i = 0;
			 i < ttNTHeader.uNRCount &&
			 (lpFontProps->csCopyright[0] == 0 ||
			  lpFontProps->csName[0] == 0      ||
			  lpFontProps->csTrademark[0] == 0 ||
			  lpFontProps->csFamily[0] == 0);
			 i++)
		{
			memcpy(&ttRecord, &lpMapAddress[index], sizeof(TT_NAME_RECORD));
			index += sizeof(TT_NAME_RECORD);

			ttRecord.uNameID = SWAPWORD(ttRecord.uNameID);
			ttRecord.uStringLength = SWAPWORD(ttRecord.uStringLength);
			ttRecord.uStringOffset = SWAPWORD(ttRecord.uStringOffset);

			if (ttRecord.uNameID == 1 || ttRecord.uNameID == 0 || ttRecord.uNameID == 7)
			{
				int nPos = index; //f.GetPosition();

				index = tblDir.uOffset + ttRecord.uStringOffset + ttNTHeader.uStorageOffset;

				memset(szTemp, 0, sizeof(szTemp));

				memcpy(szTemp, &lpMapAddress[index], ttRecord.uStringLength);
				index += ttRecord.uStringLength;

				if (szTemp[0] != 0)
				{
					_ASSERTE(strlen(szTemp) < sizeof(lpFontProps->csName));

					switch (ttRecord.uNameID)
					{
						case 0:
							if (lpFontProps->csCopyright[0] == 0)
								strncpy(lpFontProps->csCopyright, szTemp,
									sizeof(lpFontProps->csCopyright)-1);
							break;

						case 1:
							if (lpFontProps->csFamily[0] == 0)
								strncpy(lpFontProps->csFamily, szTemp,
									sizeof(lpFontProps->csFamily)-1);
							bRetVal = TRUE;
							break;

						case 4:
							if (lpFontProps->csName[0] == 0)
								strncpy(lpFontProps->csName, szTemp,
									sizeof(lpFontProps->csName)-1);
							break;

						case 7:
							if (lpFontProps->csTrademark[0] == 0)
								strncpy(lpFontProps->csTrademark, szTemp,
									sizeof(lpFontProps->csTrademark)-1);
							break;

						default:
							break;
					}
				}
				index = nPos;
			}
		}
	}

	::UnmapViewOfFile(lpMapAddress);
	::CloseHandle(hMappedFile);
	::CloseHandle(hFile);

	if (lpFontProps->csName[0] == 0)
		strcpy(lpFontProps->csName, lpFontProps->csFamily);

	memset(lpFontPropsX, 0, sizeof(FONT_PROPERTIES));

#ifdef _UNICODE
	::MultiByteToWideChar(CP_ACP, 0, lpFontProps->csName, -1, lpFontPropsX->csName,
		sizeof(lpFontPropsX->csName)/sizeof(TCHAR)-1);
	::MultiByteToWideChar(CP_ACP, 0, lpFontProps->csCopyright, -1, lpFontPropsX->csCopyright,
		sizeof(lpFontPropsX->csCopyright)/sizeof(TCHAR)-1);
	::MultiByteToWideChar(CP_ACP, 0, lpFontProps->csTrademark, -1, lpFontPropsX->csTrademark,
		sizeof(lpFontPropsX->csTrademark)/sizeof(TCHAR)-1);
	::MultiByteToWideChar(CP_ACP, 0, lpFontProps->csFamily, -1, lpFontPropsX->csFamily,
		sizeof(lpFontPropsX->csFamily)/sizeof(TCHAR)-1);
#else
	strcpy(lpFontPropsX->csName, lpFontProps->csName);
	strcpy(lpFontPropsX->csCopyright, lpFontProps->csCopyright);
	strcpy(lpFontPropsX->csTrademark, lpFontProps->csTrademark);
	strcpy(lpFontPropsX->csFamily, lpFontProps->csFamily);
#endif

	return bRetVal;
}


///////////////////////////////////////////////////////////////////////////////
//
// GetNextNameValue()
//
// Purpose:     Get first/next name/value pair from registry
//
// Parameters:  key       - handle to open key, or predefined key
//              pszSubkey - subkey name
//              pszName   - pointer to buffer that receives the value string
//              pszData   - pointer to buffer that receives the data string
//
// Returns:     LONG - return code from registry function; ERROR_SUCCESS = success
//
// Notes:       If pszSubkey, pszName, and pszData are all NULL, then the open
//              handle will be closed.
//
//              The first time GetNextNameValue is called, pszSubkey should be
//              specified.  On subsequent calls, pszSubkey should be NULL or
//              an empty string.
//
static LONG GetNextNameValue(HKEY key, LPCTSTR pszSubkey, LPTSTR pszName, LPTSTR pszData)
{
	static HKEY hkey = NULL;	// registry handle, kept open between calls
	static DWORD dwIndex = 0;	// count of values returned
	LONG retval = ERROR_SUCCESS;

	// if all parameters are NULL then close key
	if (pszSubkey == NULL && pszName == NULL && pszData == NULL)
	{
		TRACE(_T("closing key\n"));
		if (hkey)
      ::RegCloseKey(hkey);
		hkey = NULL;
		return ERROR_SUCCESS;
	}

	// if subkey is specified then open key (first time)
	if (pszSubkey && *pszSubkey)
	{
    // retval = ::RegOpenKeyEx(key, pszSubkey, 0, KEY_ALL_ACCESS, &hkey); // Incapace...
    retval = ::RegOpenKeyEx(key, pszSubkey, 0, KEY_READ, &hkey);          // ... devi solo leggere!
		if (retval != ERROR_SUCCESS)
		{
			TRACE(_T("ERROR: RegOpenKeyEx failed\n"));
			return retval;
		}
		else
		{
			TRACE(_T("RegOpenKeyEx ok\n"));
		}
		dwIndex = 0;
	}
	else
	{
		dwIndex++;
	}

	_ASSERTE(pszName != NULL && pszData != NULL);

	*pszName = 0;
	*pszData = 0;

	TCHAR szValueName[MAX_PATH];
	DWORD dwValueNameSize = sizeof(szValueName)-1;
	BYTE szValueData[MAX_PATH];
	DWORD dwValueDataSize = sizeof(szValueData)-1;
	DWORD dwType = 0;

  retval = ::RegEnumValue(hkey, dwIndex, szValueName, &dwValueNameSize, NULL,
		&dwType, szValueData, &dwValueDataSize);
	if (retval == ERROR_SUCCESS)
	{
		TRACE(_T("szValueName=<%s>  szValueData=<%s>\n"), szValueName, szValueData);
		lstrcpy(pszName, (LPTSTR)szValueName);
		lstrcpy(pszData, (LPTSTR)szValueData);
	}
	else
	{
		TRACE(_T("RegEnumKey failed\n"));
	}

	return retval;
}


// from winbase.h
#ifndef VER_PLATFORM_WIN32s
#define VER_PLATFORM_WIN32s             0
#endif
#ifndef VER_PLATFORM_WIN32_WINDOWS
#define VER_PLATFORM_WIN32_WINDOWS      1
#endif
#ifndef VER_PLATFORM_WIN32_NT
#define VER_PLATFORM_WIN32_NT           2
#endif
#ifndef VER_PLATFORM_WIN32_CE
#define VER_PLATFORM_WIN32_CE           3
#endif


/*
    This table has been assembled from Usenet postings, personal
    observations, and reading other people's code.  Please feel
    free to add to it or correct it.


         dwPlatFormID  dwMajorVersion  dwMinorVersion  dwBuildNumber
95             1              4               0             950
95 SP1         1              4               0        >950 && <=1080
95 OSR2        1              4             <10           >1080
98             1              4              10            1998
98 SP1         1              4              10       >1998 && <2183
98 SE          1              4              10          >=2183
ME             1              4              90            3000

NT 3.51        2              3              51
NT 4           2              4               0            1381
2000           2              5               0            2195
XP             2              5               1            2600
2003           2              5               2 
Vista          2              6               0
2008           2              6               0
2008 R2        2              6               1
Win7           2              6               1            3600

CE             3

*/

///////////////////////////////////////////////////////////////////////////////
//
// GetWinVer()
//
// Purpose:     Get Windows version info
//
// Parameters:  lpszVersion  - pointer to buffer that receives the version
//                             string
//              nVersionSize - size of the version buffer in TCHARs
//              pnVersion    - pointer to int that receives the version code
//
// Returns:     BOOL - TRUE = success
//
///////////////////////////////////////////////////////////////////////////////
// GetWinVer
bool GetWinVer(LPTSTR lpszVersion, int nVersionSize, int *pnVersion)
{
	int nVersion = WUNKNOWN;
  LPCTSTR cp = WUNKNOWNSTR;

	OSVERSIONINFOEX osinfo; memset(&osinfo, 0, sizeof(osinfo));
	osinfo.dwOSVersionInfoSize = sizeof(osinfo);
  if (::GetVersionEx((OSVERSIONINFO*)&osinfo))
  {
	  DWORD dwPlatformId   = osinfo.dwPlatformId;
	  DWORD dwMinorVersion = osinfo.dwMinorVersion;
	  DWORD dwMajorVersion = osinfo.dwMajorVersion;
	  DWORD dwBuildNumber  = osinfo.dwBuildNumber & 0xFFFF;	// Win 95 needs this
	  TRACE(_T("%d:  %d.%d.%d\n"), dwPlatformId, dwMajorVersion, dwMinorVersion, dwBuildNumber);

	  if (dwPlatformId == VER_PLATFORM_WIN32_NT)
	  {
      if (dwMajorVersion <= 5)
		  {
        switch (dwMinorVersion)
        {
        case  0: cp = W2KSTR; nVersion = W2K; break;
        case  1: cp = WXPSTR; nVersion = WXP; break;
        case  2:
        default: cp = W2003STR;	nVersion = W2003; break;
        }
		  }
		  else if (dwMajorVersion == 6)
		  {
        const bool bServer = osinfo.wProductType != VER_NT_WORKSTATION;
        switch (dwMinorVersion)
        {
        case  0: 
          if (bServer)
          { cp = W2008STR; nVersion = W2008; }
          else
          { cp = WVISTASTR; nVersion = WVISTA; }
          break;
        case 1:
          if (bServer)
          { cp = W2008R2STR; nVersion = W2008R2; }
          else
          { cp = W7STR; nVersion = W7; }       
          break;
        default: 
          if (bServer)
          { cp = W2012STR; nVersion = W2012; }
          else
          { cp = W8STR; nVersion = W8; }       
          break;
        }
		  }
	  }
  }

  if (lpszVersion != NULL && nVersionSize > 0)
    _tcsncpy(lpszVersion, cp, nVersionSize-1);
  if (pnVersion != NULL)
    *pnVersion = nVersion;

  return nVersion == WUNKNOWN;
}