/* makepangoaliases
 * makepangoaliases.c:
 *
 * Copyright (C) 2005 Kazuki IWAMOTO
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
 
#include <stdio.h>
#include <tchar.h>
#include <windows.h>


#define NAME 0x656d616e
#define APPLE_UNICODE_PLATFORM_ID 0
#define MACINTOSH_PLATFORM_ID 1
#define ISO_PLATFORM_ID 2
#define MICROSOFT_PLATFORM_ID 3
#define SYMBOL_ENCODING_ID 0
#define UNICODE_ENCODING_ID 1
#define UCS4_ENCODING_ID 10


#include <pshpack1.h>
typedef struct _NAMEHEADER
{
  WORD wFormatSelector;
  WORD wNumRecords;
  WORD wStringStorageOffset;
} NAMEHEADER, *LPNAMEHEADER, *PNAMEHEADER;
typedef struct _NAMERECORD
{
  WORD wPlatformID;
  WORD wEncodingID;
  WORD wLanguageID;
  WORD wNameID;
  WORD wStringLength;
  WORD wStringOffset;
} NAMERECORD, *LPNAMERECORD, *PNAMERECORD;
#include <poppack.h>
typedef struct _FONTLIST
{
  int nPriority;
  BOOL fFixed;
  BYTE bFamily;
  DWORD dwCharSet;
  LPSTR lpszName;
  struct _FONTLIST *lpflPrev, *lpflNext;
} FONTLIST, *LPFONTLIST, *PFONTLIST;
typedef struct _FONTINFO
{
  int nCharSet;
  BYTE bCharSet[32];
  DWORD dwCharSet, dwCurrent;
  HDC hDC;
  LPFONTLIST lpflEnumed;
  LPSTR lpszSystem[6];
} FONTINFO, *LPFONTINFO, *PFONTINFO;


/******************************************************************************
*                                                                             *
******************************************************************************/
BOOL
GetNameHeader (HDC          hDC,
               LPNAMEHEADER lpnhData)
{
  if (GetFontData (hDC, NAME, 0, lpnhData, sizeof (NAMEHEADER))
                                                        != sizeof (NAMEHEADER))
    return FALSE;
  lpnhData->wFormatSelector
                        = MAKEWORD (HIBYTE (lpnhData->wFormatSelector),
                                    LOBYTE (lpnhData->wFormatSelector));
  lpnhData->wNumRecords = MAKEWORD (HIBYTE (lpnhData->wNumRecords),
                                    LOBYTE (lpnhData->wNumRecords));
  lpnhData->wStringStorageOffset
                        = MAKEWORD (HIBYTE (lpnhData->wStringStorageOffset),
                                    LOBYTE (lpnhData->wStringStorageOffset));
  return TRUE;
}


BOOL
GetNameRecord (HDC          hDC,
               int          i,
               LPNAMERECORD lpnrData)
{
  if (GetFontData (hDC, NAME, 6 + i * sizeof (NAMERECORD),
                        lpnrData, sizeof (NAMERECORD)) != sizeof (NAMERECORD))
    return FALSE;
  lpnrData->wPlatformID =   MAKEWORD (HIBYTE (lpnrData->wPlatformID),
                                      LOBYTE (lpnrData->wPlatformID));
  lpnrData->wEncodingID =   MAKEWORD (HIBYTE (lpnrData->wEncodingID),
                                      LOBYTE (lpnrData->wEncodingID));
  lpnrData->wLanguageID =   MAKEWORD (HIBYTE (lpnrData->wLanguageID),
                                      LOBYTE (lpnrData->wLanguageID));
  lpnrData->wNameID =       MAKEWORD (HIBYTE (lpnrData->wNameID),
                                      LOBYTE (lpnrData->wNameID));
  lpnrData->wStringLength = MAKEWORD (HIBYTE (lpnrData->wStringLength),
                                      LOBYTE (lpnrData->wStringLength));
  lpnrData->wStringOffset = MAKEWORD (HIBYTE (lpnrData->wStringOffset),
                                      LOBYTE (lpnrData->wStringOffset));
  return TRUE;
}


LPSTR
GetFamilyName (HDC hDC)
{
  LPSTR lpszName = NULL;
  NAMEHEADER nhData;

  if (GetNameHeader (hDC, &nhData))
    {
      int i, unicode_ix = -1, mac_ix = -1, microsoft_ix = -1, name_ix = -1;
      NAMERECORD nrData;

      for (i = 0; i < nhData.wNumRecords; i++)
        if (GetNameRecord (hDC, i, &nrData)
                            && (nrData.wNameID == 1 || nrData.wNameID == 16)
                            && nrData.wStringLength > 0)
          {
            if (nrData.wPlatformID == APPLE_UNICODE_PLATFORM_ID ||
                nrData.wPlatformID == ISO_PLATFORM_ID)
              unicode_ix = i;
            else if (nrData.wPlatformID == MACINTOSH_PLATFORM_ID &&
                     nrData.wEncodingID == 0 && /* Roman */
                     nrData.wLanguageID == 0) /* English */
              mac_ix = i;
            else if (nrData.wPlatformID == MICROSOFT_PLATFORM_ID)
              if ((microsoft_ix == -1 ||
                   PRIMARYLANGID (nrData.wLanguageID) == LANG_ENGLISH) &&
                  (nrData.wEncodingID == SYMBOL_ENCODING_ID ||
                   nrData.wEncodingID == UNICODE_ENCODING_ID ||
                   nrData.wEncodingID == UCS4_ENCODING_ID))
                microsoft_ix = i;
          }
      if (microsoft_ix >= 0)
        name_ix = microsoft_ix;
      else if (mac_ix >= 0)
        name_ix = mac_ix;
      else if (unicode_ix >= 0)
        name_ix = unicode_ix;
      if (name_ix >= 0 && GetNameRecord (hDC, name_ix, &nrData))
        {
          LPVOID lpName;

          lpName = HeapAlloc (GetProcessHeap (), 0, nrData.wStringLength);
          if (GetFontData (hDC, NAME,
                        nhData.wStringStorageOffset + nrData.wStringOffset,
                        lpName, nrData.wStringLength) == nrData.wStringLength)
            {
              if (name_ix == microsoft_ix)
                if (nrData.wEncodingID == SYMBOL_ENCODING_ID ||
                    nrData.wEncodingID == UNICODE_ENCODING_ID)
                  {
                    lpszName = HeapAlloc (GetProcessHeap (),
                            HEAP_ZERO_MEMORY, nrData.wStringLength / 2 + 1);
                    for (i = 0; i < nrData.wStringLength / 2; i++)
                      lpszName[i] = ((LPBYTE)lpName)[i * 2 + 1];
                  }
                else
                  {
                    lpszName = HeapAlloc (GetProcessHeap (),
                            HEAP_ZERO_MEMORY, nrData.wStringLength / 4 + 1);
                    for (i = 0; i < nrData.wStringLength / 4; i++)
                      lpszName[i] = ((LPBYTE)lpName)[i * 4 + 3];
                  }
              else if (name_ix == mac_ix)
                {
                  lpszName = HeapAlloc (GetProcessHeap (),
                                HEAP_ZERO_MEMORY, nrData.wStringLength + 1);
                  CopyMemory (lpszName, lpName, nrData.wStringLength);
                }
              else /* name_ix == unicode_ix */
                {
                  lpszName = HeapAlloc (GetProcessHeap (),
                            HEAP_ZERO_MEMORY, nrData.wStringLength / 4 + 1);
                  for (i = 0; i < nrData.wStringLength / 4; i++)
                    lpszName[i] = ((LPBYTE)lpName)[i * 4 + 3];
                }
              for (i = 0; lpszName[i] != '\0' ; i++)
                if (lpszName[i] < ' ' || '~' < lpszName[i]
                                                        || lpszName[i] == ',')
                  {
                    HeapFree (GetProcessHeap (), 0, lpszName);
                    lpszName = NULL;
                    break;
                  }
              CharLowerA (lpszName);
            }
          HeapFree (GetProcessHeap (), 0, lpName);
        }
    }
  return lpszName;
}


/******************************************************************************
*                                                                             *
******************************************************************************/
VOID
AppendFontList (LPSTR      lpszName,
                BOOL       fFixed,
                BYTE       bFamily,
                DWORD      dwCharSet,
                LPFONTINFO lpfiData)
{
  int i;
  LPFONTLIST p = NULL;

  lpfiData->dwCharSet |= dwCharSet;
  if (lpfiData->lpflEnumed)
    {
      for (p = lpfiData->lpflEnumed; p->lpflPrev; p = p->lpflPrev);
      while (p)
        {
          if (lstrcmpA (lpszName, p->lpszName) == 0)
            {
              if ((p->dwCharSet & dwCharSet) == 0)
                {
                  p->dwCharSet |= dwCharSet;
                  p->nPriority++;
                  if (p == lpfiData->lpflEnumed)
                    lpfiData->lpflEnumed = lpfiData->lpflEnumed->lpflPrev
                                            ? lpfiData->lpflEnumed->lpflPrev
                                            : lpfiData->lpflEnumed->lpflNext;
                  if (p->lpflPrev)
                    p->lpflPrev->lpflNext = p->lpflNext;
                  if (p->lpflNext)
                    p->lpflNext->lpflPrev = p->lpflPrev;
                  break;
                }
              else
                {
                  return;
                }
            }
          p = p->lpflNext;
        }
    }
  if (!p)
    {
      p = HeapAlloc (GetProcessHeap (), 0, sizeof (FONTLIST));
      p->lpszName = HeapAlloc (GetProcessHeap (), 0, lstrlenA (lpszName) + 1);
      p->nPriority = 0;
      p->fFixed = fFixed;
      p->bFamily = bFamily;
      p->dwCharSet = dwCharSet;
      lstrcpyA (p->lpszName, lpszName);
    }
  for (i = 0; i < 6; i++)
    if (lpfiData->lpszSystem[i]
                    && lstrcmpA (lpfiData->lpszSystem[i], p->lpszName) == 0)
      {
        p->nPriority = 40 - i;
        break;
      }
  if (lpfiData->lpflEnumed)
    {
      LPFONTLIST q;

      for (q = lpfiData->lpflEnumed; q->lpflPrev; q = q->lpflPrev);
      while (q)
        {
          if (p->nPriority > 32 && p->nPriority > q->nPriority
              || (p->dwCharSet & lpfiData->dwCurrent)
                                        > (q->dwCharSet & lpfiData->dwCurrent)
              || (p->dwCharSet & lpfiData->dwCurrent)
                                        == (q->dwCharSet & lpfiData->dwCurrent)
                && p->nPriority > q->nPriority
              || (p->dwCharSet & lpfiData->dwCurrent)
                                        == (q->dwCharSet & lpfiData->dwCurrent)
                && p->nPriority == q->nPriority
                && lstrcmpA (p->lpszName, q->lpszName) < 0)
            {
              p->lpflPrev = q->lpflPrev;
              p->lpflNext = q;
              if (q->lpflPrev)
                q->lpflPrev->lpflNext = p;
              q->lpflPrev = p;
              return;
            }
          q = q->lpflNext;
        }
      for (q = lpfiData->lpflEnumed; q->lpflNext; q = q->lpflNext);
      p->lpflPrev = q;
      p->lpflNext = NULL;
      q->lpflNext = p;
    }
  else
    {
      p->lpflPrev = p->lpflNext = NULL;
      lpfiData->lpflEnumed = p;
    }
}


VOID
FreeFontList (LPFONTLIST lpflSource)
{
  if (lpflSource)
    {
      LPFONTLIST p;

      for (p = lpflSource; p->lpflPrev; p = p->lpflPrev);
      while (p)
        {
          LPFONTLIST q;

          q = p->lpflNext;
          HeapFree (GetProcessHeap (), 0, p->lpszName);
          HeapFree (GetProcessHeap (), 0, p);
          p = q;
        }
    }
}


/******************************************************************************
*                                                                             *
******************************************************************************/
int CALLBACK
EnumFontFamExFunc (ENUMLOGFONTEX   *lpelfe,
                   NEWTEXTMETRICEX *lpntme,
                   DWORD            FontType,
                   LPARAM           lParam)
{
  LPFONTINFO lpfiData;

  lpfiData = (LPFONTINFO)lParam;
  if (FontType == TRUETYPE_FONTTYPE
                            && lpelfe->elfLogFont.lfCharSet != SYMBOL_CHARSET
                            && lpelfe->elfLogFont.lfFaceName[0] != '@')
    {
      int i;

      for (i = 0; i < lpfiData->nCharSet; i++)
        if (lpfiData->bCharSet[i] == lpelfe->elfLogFont.lfCharSet)
          break;
      if (i == lpfiData->nCharSet)
        {
          lpfiData->bCharSet[i] = lpelfe->elfLogFont.lfCharSet;
          lpfiData->dwCharSet = lpfiData->dwCharSet << 1 | 1;
          lpfiData->nCharSet++;
        }
    }
  return lpfiData->nCharSet < 32 ? 1 : 0;
}


int CALLBACK
EnumFontFamExProc (ENUMLOGFONTEX   *lpelfe,
                   NEWTEXTMETRICEX *lpntme,
                   DWORD            FontType,
                   LPARAM           lParam)
{
  if (FontType == TRUETYPE_FONTTYPE
                            && lpelfe->elfLogFont.lfCharSet != SYMBOL_CHARSET
                            && lpelfe->elfLogFont.lfFaceName[0] != '@')
    {
      HFONT hFont;
      LPSTR lpszName = NULL;
      LPFONTINFO lpfiData;

      lpfiData = (LPFONTINFO)lParam;
      hFont = CreateFontIndirect (&lpelfe->elfLogFont);
      if (hFont)
        {
          hFont = SelectObject (lpfiData->hDC, hFont);
          lpszName = GetFamilyName (lpfiData->hDC);
          hFont = SelectObject (lpfiData->hDC, hFont);
          DeleteObject (hFont);
        }
      if (lpszName)
        {
          int i;
          DWORD dwCharSet = 1;

          for (i = 0; i < lpfiData->nCharSet; i++)
            if (lpfiData->bCharSet[i] == lpelfe->elfLogFont.lfCharSet)
              break;
            else
              dwCharSet <<= 1;
          AppendFontList (
                lpszName,
                (lpelfe->elfLogFont.lfPitchAndFamily & 0x0f) == FIXED_PITCH,
                lpelfe->elfLogFont.lfPitchAndFamily & 0xf0,
                dwCharSet,
                lpfiData);
          HeapFree (GetProcessHeap (), 0, lpszName);
        }
    }
  return 1;
}


int
main (int   argc,
      char *argv[])
{
  FONTINFO fiData;

  memset (&fiData, 0, sizeof (FONTINFO));
  fiData.hDC = CreateDC (_T("DISPLAY"), NULL, NULL, NULL);
  if (fiData.hDC)
    {
      int i;
      HFONT hFont;
      LOGFONT lf;

      memset (&lf, 0, sizeof (LOGFONT));
      lf.lfCharSet = DEFAULT_CHARSET;
      EnumFontFamiliesEx (fiData.hDC, &lf, (FONTENUMPROC)EnumFontFamExFunc,
                                                        (LPARAM)&fiData, 0);
      for (i = 0; i < 6; i++)
        {
          int nObject[6] = {ANSI_FIXED_FONT, ANSI_VAR_FONT, DEFAULT_GUI_FONT,
                            SYSTEM_FONT, OEM_FIXED_FONT, SYSTEM_FIXED_FONT};

          hFont = GetStockObject (nObject[i]);
          if (hFont)
            {
              int j;
              TEXTMETRIC tm;

              hFont = SelectObject (fiData.hDC, hFont);
              fiData.lpszSystem[i] = GetFamilyName (fiData.hDC);
              GetTextMetrics (fiData.hDC, &tm);
              SelectObject (fiData.hDC, hFont);
              if (fiData.lpszSystem[i])
                {
                  DWORD dwCharSet = 1;

                  dwCharSet = 1;
                  for (j = 0; j < fiData.nCharSet; j++)
                    if (fiData.bCharSet[j] == tm.tmCharSet)
                      break;
                    else
                      dwCharSet <<= 1;
                  fiData.dwCurrent |= dwCharSet;
                }
            }
        }
      memset (&lf, 0, sizeof (LOGFONT));
      lf.lfCharSet = DEFAULT_CHARSET;
      EnumFontFamiliesEx (fiData.hDC, &lf, (FONTENUMPROC)EnumFontFamExProc,
                                                        (LPARAM)&fiData, 0);
      if (fiData.lpflEnumed)
        {
          BOOL fComma;
          DWORD dwCharSet;
          LPFONTLIST p;

          /* courier */
          for (p = fiData.lpflEnumed; p->lpflPrev; p = p->lpflPrev);
          while (p)
            {
              if (lstrcmpA (p->lpszName, "courier new") == 0)
                {
                  printf ("courier = \"courier new\"\n");
                  break;
                }
              p = p->lpflNext;
            }

          /* monospace */
          fComma = FALSE;
          dwCharSet = 0;
          printf ("monospace = \"");
          for (p = fiData.lpflEnumed; p->lpflPrev; p = p->lpflPrev);
          while (p)
            {
              if (p->fFixed)
                {
                  if (fComma)
                    printf (",");
                  printf ("%s", p->lpszName);
                  fComma = TRUE;
                  dwCharSet |= p->dwCharSet;
                }
              p = p->lpflNext;
            }
          for (p = fiData.lpflEnumed; p->lpflPrev; p = p->lpflPrev);
          while (p)
            {
              if ((fiData.dwCurrent & dwCharSet) == fiData.dwCurrent
                    && (!p->lpflPrev || p->lpflPrev->nPriority > p->nPriority))
                break;
              if (!p->fFixed && p->bFamily == FF_MODERN)
                {
                  if (fComma)
                    printf (",");
                  printf ("%s", p->lpszName);
                  fComma = TRUE;
                  dwCharSet |= p->dwCharSet;
                }
              p = p->lpflNext;
            }
          for (p = fiData.lpflEnumed; p->lpflPrev; p = p->lpflPrev);
          while (p)
            {
              if ((fiData.dwCurrent & dwCharSet) == fiData.dwCurrent
                    && (!p->lpflPrev || p->lpflPrev->nPriority > p->nPriority))
                break;
              if (!p->fFixed && p->bFamily != FF_MODERN)
                {
                  if (fComma)
                    printf (",");
                  printf ("%s", p->lpszName);
                  fComma = TRUE;
                  dwCharSet |= p->dwCharSet;
                }
              p = p->lpflNext;
            }
          printf ("\"\n");

          /* serif */
          fComma = FALSE;
          dwCharSet = 0;
          printf ("serif = \"");
          for (p = fiData.lpflEnumed; p->lpflPrev; p = p->lpflPrev);
          while (p)
            {
              if (!p->fFixed && p->bFamily == FF_ROMAN)
                {
                  if (fComma)
                    printf (",");
                  printf ("%s", p->lpszName);
                  fComma = TRUE;
                  dwCharSet |= p->dwCharSet;
                }
              p = p->lpflNext;
            }
          for (p = fiData.lpflEnumed; p->lpflPrev; p = p->lpflPrev);
          while (p)
            {
              if ((fiData.dwCurrent & dwCharSet) == fiData.dwCurrent
                    && (!p->lpflPrev || p->lpflPrev->nPriority > p->nPriority))
                break;
              if (!p->fFixed && p->bFamily != FF_ROMAN)
                {
                  if (fComma)
                    printf (",");
                  printf ("%s", p->lpszName);
                  fComma = TRUE;
                  dwCharSet |= p->dwCharSet;
                }
              p = p->lpflNext;
            }
          for (p = fiData.lpflEnumed; p->lpflPrev; p = p->lpflPrev);
          while (p)
            {
              if ((fiData.dwCurrent & dwCharSet) == fiData.dwCurrent
                    && (!p->lpflPrev || p->lpflPrev->nPriority > p->nPriority))
                break;
              if (p->fFixed)
                {
                  if (fComma)
                    printf (",");
                  printf ("%s", p->lpszName);
                  fComma = TRUE;
                  dwCharSet |= p->dwCharSet;
                }
              p = p->lpflNext;
            }
          printf ("\"\n");

          /* sans */
          fComma = FALSE;
          dwCharSet = 0;
          printf ("sans = \"");
          for (p = fiData.lpflEnumed; p->lpflPrev; p = p->lpflPrev);
          while (p)
            {
              if (!p->fFixed && p->bFamily == FF_SWISS)
                {
                  if (fComma)
                    printf (",");
                  printf ("%s", p->lpszName);
                  fComma = TRUE;
                  dwCharSet |= p->dwCharSet;
                }
              p = p->lpflNext;
            }
          for (p = fiData.lpflEnumed; p->lpflPrev; p = p->lpflPrev);
          while (p)
            {
              if ((fiData.dwCurrent & dwCharSet) == fiData.dwCurrent
                    && (!p->lpflPrev || p->lpflPrev->nPriority > p->nPriority))
                break;
              if (!p->fFixed && p->bFamily != FF_SWISS)
                {
                  if (fComma)
                    printf (",");
                  printf ("%s", p->lpszName);
                  fComma = TRUE;
                  dwCharSet |= p->dwCharSet;
                }
              p = p->lpflNext;
            }
          for (p = fiData.lpflEnumed; p->lpflPrev; p = p->lpflPrev);
          while (p)
            {
              if ((fiData.dwCurrent & dwCharSet) == fiData.dwCurrent
                    && (!p->lpflPrev || p->lpflPrev->nPriority > p->nPriority))
                break;
              if (p->fFixed)
                {
                  if (fComma)
                    printf (",");
                  printf ("%s", p->lpszName);
                  fComma = TRUE;
                  dwCharSet |= p->dwCharSet;
                }
              p = p->lpflNext;
            }
          printf ("\"\n");
        }
      FreeFontList (fiData.lpflEnumed);
      for (i = 0; i < 6; i++)
        if (fiData.lpszSystem[i])
          HeapFree (GetProcessHeap (), 0, fiData.lpszSystem[i]);
      DeleteDC (fiData.hDC);
    }

  return 0;
}
