/*                                                                         */
/*             OS/2 Font Driver using the FreeType library                 */
/*                                                                         */
/*       Copyright (C) 1997, 1998 Michal Necasek <mike@mendelu.cz>         */
/*       Copyright (C) 1997, 1998 David Turner <turner@email.ENST.Fr>      */
/*       Copyright (C) 1997, 1998 International Business Machines          */
/*                                                                         */
/*       Version: 0.9.36 (Beta)                                            */
/*                                                                         */
/* This source is to be compiled with IBM VisualAge C++ 3.0 or alternately */
/* Watcom C/C++ 10.0 or higher (Wactom doesn't quite work yet).            */
/* Other compilers may actually work too but don't forget this is NOT a    */
/* normal DLL but rather a subsystem DLL. That means it shouldn't use      */
/* the usual C library as it has to run without runtime environment.       */
/* VisualAge provides a special subsystem version of the run-time library. */
/* All this is of course very compiler-dependent. See makefiles for        */
/* discussion of switches used.                                            */
/*                                                                         */
/*  Implemantation Notes:                                                  */
/* Note #1: Some local variables are declared static and you might wonder  */
/*   why. The reason is to conserve stack space. It might not be really    */
/*   necessary, but as they say, it's better to be safe than sorry.        */
/*   Even so, stack usage is roughly 100-200 bytes, perhaps more.          */
/*   I really hope I won't have to perform any stack-switching magic :)    */
/*                                                                         */
/* Note #2: As a consequence of this being a subsystem librarary, I had to */
/*   slightly modify the FreeType source, namely ttmemory.c and ttfile.c.  */
/*   FreeType/2 now allocates several chunks of memory and uses them as a  */
/*   heap. Note that memory allocation should use TTAlloc(), possibly      */
/*   directly SSAllocMem(). malloc() is unusable here and it doesn't work  */
/*   at all (runtime library isn't even initialized). See ttmemory.c for   */
/*   more info.                                                            */
/*    In ttfile.c I had to change all fopen(), fseek()... calls            */
/*   to OS/2 API calls (DosOpen, DosSetFilePtr...) because without proper  */
/*   runtime environment a subsystem DLL cannot use std C library calls.   */
/*                                                                         */
/* Note #3: On exit of each function reading from font file the API        */
/*   TT_Flush_Stream() must be called. This is because file handles opened */
/*   by this DLL actually belong to the calling process. As a consequence  */
/*    a) it's easy to run out of file handles, which results in really     */
/*       very nasty behavior and/or crashes. This could be solved by       */
/*       increased file handles limit, but cannot because                  */
/*    b) it is impossible to close files open by another process and       */
/*       therefore the fonts cannot be properly uninstalled (you can't     */
/*       delete them while the're open by other process)                   */
/*   The only solution I found is very simple - just close the file before */
/*   exiting a DLL function. This ensures files are not left open across   */
/*   processes and other problems.                                         */
/*                                                                         */
/* Note #4: The whole business with linked lists is aimed at lowering      */
/*   memory consumption drastically. If you install 50 TT fonts, OS/2      */
/*   opens all of them at startup. Even if you never use them, they take   */
/*   up at least over 1 Meg memory. With certain fonts the consumption can */
/*   easily go over several MB. We limit such waste of memory by only      */
/*   actually keeping open several typefaces most recently used. Their     */
/*   number can be set via entry in OS2.INI.                               */
/*                                                                         */
/* For Intelligent Font Interface (IFI) specification please see IFI32.TXT */

#ifndef  __IBMC__
   #ifndef __WATCOMC__
      #error "This source requires IBM VisualAge C++ or Watcom C/C++"
   #endif
#endif

/* Defining the following uses UCONV.DLL instead of the built-in  */
/* translation tables. I have yet to determine what OS/2 versions */
/* and FP levels support this. Current code should work on any    */
/* Warp 4                                                         */
/* Note: this should be defined before FTIFI.H is #included       */
/* #define USE_UCONV */

#define INCL_WINSHELLDATA
#define INCL_DOSMISC
#define INCL_DOSNLS
#define INCL_DOSPROCESS
#define INCL_GRE_DCS
#define INCL_GRE_DEVSUPPORT
#define INCL_DDIMISC
#define INCL_IFI
#include <os2.h>
#include <pmddi.h>          /* SSAllocmem(), SSFreemem() and more */

#ifdef USE_UCONV       /* uconv.h isn't always available */
#include <uconv.h>
#endif  /* USE_UCONV */

#include <string.h>
#include <stdlib.h>         /* min and max macros */

#define  _syscall  _System  /* the IFI headers don't compile without it */

#include "32pmifi.h"        /* IFI header             */
#include "freetype.h"       /* FreeType header        */
#include "ftxkern.h"        /* kerning extension      */
#include "ftxwidth.h"       /* glyph width extension  */
#include "ftifi.h"          /* xlate table            */


/* For the sake of Netscape's text rendering bugs ! */
#define NETSCAPE_FIX


/* (indirectly) exported functions */
LONG _System ConvertFontFile(PSZ pszSrc, PSZ pszDestDir, PSZ pszNewName);
HFF  _System LoadFontFile(PSZ pszFileName);
LONG _System UnloadFontFile(HFF hff);
LONG _System QueryFaces(HFF hff, PIFIMETRICS pifiMetrics, ULONG cMetricLen,
                        ULONG cFountCount, ULONG cStart);
HFC  _System OpenFontContext(HFF hff, ULONG ulFont);
LONG _System SetFontContext(HFC hfc, PCONTEXTINFO pci);
LONG _System CloseFontContext(HFC hfc);
LONG _System QueryFaceAttr(HFC hfc, ULONG iQuery, PBYTE pBuffer,
                           ULONG cb, PGLYPH pagi, GLYPH giStart);
LONG _System QueryCharAttr(HFC hfc, PCHARATTR pCharAttr,
                           PBITMAPMETRICS pbmm);
LONG _System QueryFullFaces(HFF hff, PVOID pBuff, PULONG buflen,
                            PULONG cFontCount, ULONG cStart);

FDDISPATCH fdisp = {        /* Font driver dispatch table */
   LoadFontFile,
   QueryFaces,
   UnloadFontFile,
   OpenFontContext,
   SetFontContext,
   CloseFontContext,
   QueryFaceAttr,
   QueryCharAttr,
   NULL,  /* this one is never used, only the spec fails to mention it */
   ConvertFontFile,
   QueryFullFaces
};



/****************************************************************************/
/* the single exported entry point; this way is faster than exporting every */
/* single function                                                          */
/*                                                                          */
#pragma export (fdhdr, "FONT_DRIVER_DISPATCH_TABLE", 1)
FDHEADER   fdhdr =
{  /* Font driver Header */
   sizeof(FDHEADER),
   "OS/2 FONT DRIVER",                  /* do not change */
   "TrueType (Using FreeType Engine)",  /* desc. up to 40 chars */
   IFI_VERSION20,                       /* version */
   0,                                   /* reserved */
   &fdisp
};


/****************************************************************************/
/* some debug macros and functions. the debug version logs system requests  */
/* to the file C:\FTIFI.LOG                                                 */
/*                                                                          */
#ifdef DEBUG
  HFILE LogHandle = NULLHANDLE;
  ULONG Written   = 0;
  char  log[2048] = "";
  char  buf[2048] = "";


char*  itoa10( int i, char* buffer ) {
    char*  ptr  = buffer;
    char*  rptr = buffer;
    char   digit;

    if (i == 0) {
      buffer[0] = '0';
      buffer[1] =  0;
      return buffer;
    }

    if (i < 0) {
      *ptr = '-';
       ptr++; rptr++;
       i   = -i;
    }

    while (i != 0) {
      *ptr = (char) (i % 10 + '0');
       ptr++;
       i  /= 10;
    }

    *ptr = 0;  ptr--;

    while (ptr > rptr) {
      digit = *ptr;
      *ptr  = *rptr;
      *rptr = digit;
       ptr--;
       rptr++;
    }

    return buffer;
}

  #define  COPY(s)     strcpy(log, s)
  #define  CAT(s)      strcat(log, s)
  #define  CATI(v)     strcat(log, itoa10( (int)v, buf ))
  #define  WRITE       DosWrite(LogHandle, log, strlen(log), &Written)

  #define  ERET1(label) { COPY("Error at ");  \
                          CATI(__LINE__);    \
                          CAT("\r\n");       \
                          WRITE;             \
                          goto label;        \
                       }

  #define  ERRRET(e)   { COPY("Error at ");  \
                          CATI(__LINE__);    \
                          CAT("\r\n");       \
                          WRITE;             \
                          return(e);         \
                       }


#else

  #define  COPY(s)
  #define  CAT(s)
  #define  CATI(v)
  #define  WRITE

  #define  ERET1(label)  goto label;

  #define  ERRRET(e)  return(e);

#endif /* DEBUG */


/****************************************************************************/
/*                                                                          */
/* 'engine' :                                                               */
/*                                                                          */
/*  The FreeType engine instance. Although this is a DLL, it isn't          */
/*  supposed to be shared by apps, as it is only called by the OS/2 GRE.    */
/*  This means that there is no need to bother with reentrancy/thread       */
/*  safety, which aren't supported by FreeType 1.0 anyway.                  */
/*                                                                          */
TT_Engine engine;

/****************************************************************************/
/*                                                                          */
/* TList and TListElement :                                                 */
/*                                                                          */
/*  simple structures used to implement a doubly linked list. Lists are     */
/*  used to implement the HFF object lists, as well as the font size and    */
/*  outline caches.                                                         */
/*                                                                          */

typedef struct _TListElement  TListElement, *PListElement;

struct _TListElement
{
  PListElement  next;    /* next element in list - NULL if tail     */
  PListElement  prev;    /* previous element in list - NULL if head */
  long          key;     /* value used for searches                 */
  void*         data;    /* pointer to the listed/cached object     */
};

typedef struct _TList  TList, *PList;
struct _TList
{
  PListElement  head;    /* first element in list - NULL if empty */
  PListElement  tail;    /* last element in list - NULL if empty  */
  int           count;   /* number of elements in list            */
};

static  PListElement  free_elements = 0;

/****************************************************************************/
/*                                                                          */
/* TGlyph_Image :                                                           */
/*                                                                          */
/*  structure used to store a glyph's attributes, i.e. outlines and metrics */
/*  Note that we do not cache bitmaps ourselves for the moment.             */
/*                                                                          */
typedef struct _TGlyph_Image  TGlyph_Image,  *PGlyph_Image;

struct _TGlyph_Image
{
  PListElement      element;  /* list element for this glyph image */
  TT_Glyph_Metrics  metrics;
  TT_Outline        outline;
};

/****************************************************************************/
/*                                                                          */
/* TFontFace :                                                              */
/*                                                                          */
/* a structure related to an open font face handle. All TFontFaces are      */
/* kept in a simple linked list. There can be several faces in one font     */
/* but this isn't currently supported (TrueType collections).               */
/*                                                                          */

typedef struct _TFontFace  TFontFace, *PFontFace;

struct _TFontFace
{
   PListElement  element;         /* list element for this font face        */
   HFF           hff;             /* HFF handle used from outside           */
   CHAR          filename[260];   /* font file name                         */
   TT_Face       face;            /* handle to FreeType face object.        */
   TT_Glyph      glyph;           /* handle to FreeType glyph container     */
   TT_CharMap    charMap;         /* handle to FreeType unicode map         */
   TT_Kerning    directory;       /* kerning directory                      */
   USHORT        *widths;         /* glyph width cache for large fonts      */
   LONG          ref_count;       /* number of times this font file is open */
   LONG          em_size;         /* points per em square                   */
   ULONG         flags;           /* various flags (such as FF_FLAG_FIXED)  */
   TList         sizes;           /* list of live child font sizes          */
   LONG          charMode;        /* character translation mode :           */
};                                /*   0 = Unicode                          */
                                  /*   1 = Symbol (no translation)          */
                                  /*   2 = Unicode w/o translation          */


/* Flag : The font face has a fixed pitch width */
#define FF_FLAG_FIXED_WIDTH      1

/* Flag : The font face has a live FreeType face object */
#define FF_FLAG_LIVE_FACE        2

/* Flag : The font face has already been used */
#define FF_FLAG_PREVIOUSLY_USED  4

/* Flag : The font face has a context open - DON'T CLOSE IT! */
#define FF_FLAG_CONTEXT_OPEN     8

/* Flag : This is a face including DBCS characters; this also means  */
/*       the font driver presents to the system a second, vertically */
/*       rendered, version of this typeface with name prepended by   */
/*       an '@' (used in horizontal-only word processors)            */
#define FF_FLAG_DBCS_FACE 16

/* Note, we'll only keep the first max_open_faces faces with opened */
/* FreeType objects/instances..                                     */
int  max_open_faces = 10;

/* the list of live faces */
static TList  liveFaces  = { NULL, NULL, 0 };

/* the list of sleeping faces */
static TList  idleFaces = { NULL, NULL, 0 };

/****************************************************************************/
/*                                                                          */
/* TFontSize :                                                              */
/*                                                                          */
/*  a structure related to a opened font context (a.k.a. instance or        */
/*  transform/pointsize).                                                   */
/*                                                                          */

typedef struct _TFontSize  TFontSize, *PFontSize;

struct _TFontSize
{
   PListElement  element;       /* List element for this font size          */
   HFC           hfc;           /* HFC handle used from outside             */
   TT_Instance   instance;      /* handle to FreeType instance              */
   BOOL          transformed;   /* TRUE = rotation/shearing used (rare)     */
   BOOL          vertical;      /* TRUE = vertically rendered DBCS face     */
   TT_Matrix     matrix;        /* transformation matrix                    */
   PFontFace     face;          /* HFF this context belongs to              */
   TList         outlines;      /* outlines cache list                      */
};

#define MAX_CACHED_SIZES  5

/****************************************************************************/
/* array of font context handles. Note that there isn't more than one font  */
/* context open at any time anyway, but we want to be safe..                */
/*                                                                          */
#define MAX_CONTEXTS  5

static TFontSize  contexts[MAX_CONTEXTS]; /* this is rather too much */


/****************************************************************************/
/* a few globals used for NLS                                               */
/*                                                                          */
/* Note: most of the internationalization (I18N) code was kindly provided   */
/*  by Ken Borgendale and Marc L Cohen from IBM (big thanks!). I also       */
/*  received help from Tetsuro Nishimura from IBM Japan.                    */
/*  I was also unable to test the I18N code on actual Japanese, Chinese...  */
/*  etc. systems. But it might work.                                        */
/*                                                                          */
typedef struct _UDCRANGE {
   ULONG    udcStart;
   ULONG    udcEnd;
} UDCRANGE, *PUDCRANGE;

typedef struct  _UDCINFO {
   ULONG    ulCount;
   ULONG    udcFirst;
   ULONG    udcLast;
   UDCRANGE *UdcRange;
} UDCINFO, *PUDCINFO;

PUDCINFO pUdcInfo = (PUDCINFO) - 1;                  /* UDC info structure  */
CHAR    szNlsFmProfile[] = " :\\OS2\\SYSDATA\\PMNLSFM.INI";
CHAR    szUdcApp[] = "UserDefinedArea";

static ULONG  ScriptTag = -1;
static ULONG  LangSysTag = -1;
static ULONG  iLangId = LANGID_ENGLISH_AMERICAN; /* language ID             */
static ULONG  uLastGlyph = 255;                  /* last glyph for language */
static PSZ    pGlyphlistName = "SYMBOL";         /* PM383, PMJPN, PMKOR.... */
static BOOL   isGBK = TRUE;                      /* only used for China     */
static ULONG  ulCp[2] = {1};                     /* codepages used          */

/* rather simple-minded test to decide if given glyph index is a 'halfchar',*/
/* i.e. Latin character in a DBCS font which is _not_ to be rotated         */
#define is_HALFCHAR(_x)  ((_x) < 0x0400)

/****************************************************************************/
/*                                                                          */
/* LookUpName :                                                             */
/*                                                                          */
/* this function tries to find M$ English name for a face                   */
/* length is limited to FACESIZE (defined by OS/2); returns NULL if         */
/* unsuccessful. warning: the string gets overwritten on the next           */
/* invocation                                                               */
/*                                                                          */
/* TODO: needs enhancing for I18N support                                   */
static  char*  LookupName(TT_Face face,  int index );


/****************************************************************************/
/*                                                                          */
/* GetCharMap :                                                             */
/*                                                                          */
/* get suitable charmap from font                                           */
/*                                                                          */
static  ULONG GetCharmap(TT_Face face);


/****************************************************************************/
/*                                                                          */
/* GetOutlineLen :                                                          */
/*                                                                          */
/* get # of bytes needed for glyph outline                                  */
/*                                                                          */
static  int GetOutlineLen(TT_Outline *ol);


/****************************************************************************/
/*                                                                          */
/* GetOutline :                                                             */
/*                                                                          */
/* get glyph outline in PM format                                           */
/*                                                                          */
static  int GetOutline(TT_Outline *ol, PBYTE pb);



/****************************************************************************/
/*                                                                          */
/* TT_Alloc & TT_Free :                                                     */
/*                                                                          */
/* The following two functions are declared here because including          */
/* the entire ttmemory.h creates more problems than it solves               */
/*                                                                          */
TT_Error  TT_Alloc( long  Size, void**  P );
TT_Error  TT_Free( void**  P );
TT_Error  TTMemory_Init(void);

static  TT_Error  error;

#define  ALLOC( p, size )  TT_Alloc( (size), (void**)&(p) )
#define  FREE( p )         TT_Free( (void**)&(p) )

/****************************************************************************/
/*                                                                          */
/* New_Element :                                                            */
/*                                                                          */
/*   return a fresh list element. Either new or recycled.                   */
/*   returns NULL if out of memory.                                         */
/*                                                                          */
static  PListElement   New_Element( void )
{
  PListElement e = free_elements;

  if (e)
    free_elements = e->next;
  else
  {
    if ( ALLOC( e, sizeof(TListElement) ) )
      return NULL;
  }
  e->next = e->prev = e->data = NULL;
  e->key  = 0;

  return e;
}

/****************************************************************************/
/*                                                                          */
/*  Done_Element :                                                          */
/*                                                                          */
/*    recycles an old list element                                          */
/*                                                                          */
static  void  Done_Element( PListElement  element )
{
  element->next = free_elements;
  free_elements = element;
}

/****************************************************************************/
/*                                                                          */
/*  List_Insert :                                                           */
/*                                                                          */
/*    inserts a new object at the head of a given list                      */
/*    returns 0 in case of success, -1 otherwise.                           */
/*                                                                          */
static  int  List_Insert( PList  list,  PListElement  element )
{
  if (!list || !element)
    return -1;

  element->next = list->head;

  if (list->head)
    list->head->prev = element;

  element->prev = NULL;
  list->head    = element;

  if (!list->tail)
    list->tail = element;

  list->count++;
  return 0;
}

/****************************************************************************/
/*                                                                          */
/*  List_Remove :                                                           */
/*                                                                          */
/*    removes an element from its list. Returns 0 in case of success,       */
/*    -1 otherwise. WARNING : this function doesn't check that the          */
/*    element is part of the list.                                          */
/*                                                                          */
static  int  List_Remove( PList  list,  PListElement  element )
{
  if (!element)
    return -1;

  if (element->prev)
    element->prev->next = element->next;
  else
    list->head = element->next;

  if (element->next)
    element->next->prev = element->prev;
  else
    list->tail = element->prev;

  element->next = element->prev = NULL;
  list->count --;
  return 0;
}

/****************************************************************************/
/*                                                                          */
/*  List_Find :                                                             */
/*                                                                          */
/*    Look for a given object with a specified key. Returns NULL if the     */
/*    list is empty, or the object wasn't found.                            */
/*                                                                          */
static  PListElement  List_Find( PList  list, long  key )
{
  static PListElement  cur;

  for ( cur=list->head; cur; cur = cur->next )
    if ( cur->key == key )
      return cur;

  /* not found */
  return NULL;
}

/****************************************************************************/
/*                                                                          */
/* Sleep_FontFace :                                                         */
/*                                                                          */
/*   closes a font face's FreeType objects to leave room in memory.         */
/*                                                                          */
static  int  Sleep_FontFace( PFontFace  cur_face )
{
  if (!(cur_face->flags & FF_FLAG_LIVE_FACE))
    ERRRET(-1);  /* already sleeping */

  /* is this face in use?  */
  if (cur_face->flags & FF_FLAG_CONTEXT_OPEN) {
     /* move face to top of the list */
     if (List_Remove( &liveFaces, cur_face->element ))
        ERRRET(-1);
     if (List_Insert( &liveFaces, cur_face->element ))
        ERRRET(-1);

     cur_face = (PFontFace)(liveFaces.tail->data);
  }

  /* remove the face from the live list */
  if (List_Remove( &liveFaces, cur_face->element ))
     ERRRET(-1);

  /* add it to the sleep list */
  if (List_Insert( &idleFaces, cur_face->element ))
    ERRRET(-1);

  /* deactivate its objects - we ignore errors there */
  TT_Done_Glyph( cur_face->glyph );
  TT_Close_Face( cur_face->face  );
  cur_face->flags  &= ~FF_FLAG_LIVE_FACE;

  return 0;
}

/****************************************************************************/
/*                                                                          */
/* Wake_FontFace :                                                          */
/*                                                                          */
/*   awakes a font face, and reloads important data from disk.              */
/*                                                                          */
static  int  Wake_FontFace( PFontFace  cur_face )
{
  static TT_Face              face;
  static TT_Glyph             glyph;
  static TT_CharMap           cmap;
  static TT_Face_Properties   props;
  ULONG                       encoding;

  if (cur_face->flags & FF_FLAG_LIVE_FACE)
    ERRRET(-1);  /* already awoken !! */

  /* OK, try to activate the FreeType objects */
  error = TT_Open_Face( engine, cur_face->filename, &face);
  if (error)
  {
     COPY( "Error while opening " ); CAT( cur_face->filename );
     CAT( ", error code = " ); CATI( error ); CAT( "\r\n" ); WRITE;
     return -1; /* error, can't open file                 */
                /* XXX : should set error condition here! */
  }

  /* Create a glyph container for it */
  error = TT_New_Glyph( face, &glyph );
  if (error)
  {
     COPY( "Error while creating container for " ); CAT( cur_face->filename );
     CAT( ", error code = " ); CATI( error ); CAT( "\r\n" ); WRITE;
     goto Fail_Face;
  }

  /*  now get Unicode/Apple Roman charmap for this font */
  encoding = GetCharmap(face);
  error = TT_Get_CharMap(face, encoding & 0xFFFF, &cmap);
  if (error)
  {
     COPY( "Error: No char map in " ); CAT(  cur_face->filename );
     CAT(  "\r\n" ); WRITE;
     goto Fail_Glyph;
  }

  /* all right, now remove the face from the sleep list */
  if (List_Remove( &idleFaces, cur_face->element ))
    ERET1( Fail_Glyph );

  /* add it to the live list */
  if (List_Insert( &liveFaces, cur_face->element ))
    ERET1( Fail_Glyph );

  cur_face->face       = face;
  cur_face->glyph      = glyph;
  cur_face->charMap    = cmap;
  cur_face->flags     |= FF_FLAG_LIVE_FACE;

  /* the following code needs to be executed only once! */
  if (!(cur_face->flags & FF_FLAG_PREVIOUSLY_USED)) {
     cur_face->charMode = encoding >> 16;  /* 0 - Unicode; 1 - Symbol */
     cur_face->flags  |= FF_FLAG_PREVIOUSLY_USED;
     /* if a font contains over 1024 glyphs, assume it's a DBCS font - */
     /* VERY probable                                                  */
     error = TT_Get_Face_Properties(cur_face->face, &props);
     if (error)
        ERET1(Fail_Face);

     if (props.num_Glyphs > 1024)
        cur_face->flags |= FF_FLAG_DBCS_FACE;

     cur_face->widths = NULL;
  }

  /* load kerning directory, if any */
  error = TT_Get_Kerning_Directory(face, &(cur_face->directory));
  if (error)
     cur_face->directory.nTables = 0; /* indicates no kerning in font */

  TT_Flush_Face(face);     /* this is important !                      */
  return 0;    /* everything is in order, return 0 == success */

Fail_Glyph:
  /* This line isn't really necessary, because the glyph container */
  /* would be destroyed by the following TT_Close_Face anyway. We  */
  /* however use it for the sake of orthodoxy                      */
  TT_Done_Glyph( glyph );

Fail_Face:
  TT_Close_Face(face);

  /* note that in case of error (e.g. out of memory), the face stays */
  /* on the sleeping list                                            */
  return -1;
}

/****************************************************************************/
/*                                                                          */
/* Done_FontFace :                                                          */
/*                                                                          */
/*   destroys a given font face object. This will also destroy all of its   */
/*   live child font sizes (which in turn will destroy the glyph caches).   */
/*                                                                          */
/* WARNING : The font face must be removed from its list by the caller      */
/*           before this function is called.                                */
/*                                                                          */
static  void  Done_FontFace( PFontFace  *face )
{
  static PListElement  element;
  static PListElement  next;

  /* destroy its font sizes */
  element = (*face)->sizes.head;
  while (element)
  {
    next = element->next;
    /* XXX : right now, we simply free the font size object, */
    /*       because the instance is destroyed automatically */
    /*       by FreeType.                                    */

    FREE( element->data );
    /* Done_FontSize( (PFontSize)element->data ); - later */

    Done_Element( element );
    element = next;
  }

  /* now discard the font face itself */
  if ((*face)->flags & FF_FLAG_LIVE_FACE)
  {
    TT_Done_Glyph((*face)->glyph);
    TT_Close_Face((*face)->face);
    if ((*face)->widths)
       FREE((*face)->widths);
  }

  FREE( *face );
}


/****************************************************************************/
/*                                                                          */
/* New_FontFace :                                                           */
/*                                                                          */
/*   return the address of the TFontFace corresponding to a given           */
/*   HFF. Note that in our implementation, we could simply to a             */
/*   typecast like '(PFontFace)hff'. However, for safety reasons, we        */
/*   look-up the handle in the list.                                        */
/*                                                                          */
static  PFontFace  New_FontFace( char*  file_name )
{
   static PListElement  element;
   static PFontFace     cur_face;
   static TT_CharMap    cmap;

   /* first, check if it's already open - in the live list */
   for ( element = liveFaces.head; element; element = element->next )
   {
     cur_face = (PFontFace)element->data;
     if (strcmp( cur_face->filename, file_name ) == 0)
       goto Exit_Same;
   }

   /* check in the idle list */
   for ( element = idleFaces.head; element; element = element->next )
   {
     cur_face = (PFontFace)element->data;
     if (strcmp( cur_face->filename, file_name ) == 0)
       goto Exit_Same;
   }

   /* OK, this file isn't opened yet. Create a new font face object   */
   /* then try to awake it. This will fail if the file can't be found */
   /* or if we lack memory..                                          */

   element = New_Element();
   if (!element)
     ERRRET(NULL);

   if ( ALLOC( cur_face, sizeof(TFontFace) ) )
     ERET1( Fail_Element );

   element->data        = cur_face;
   element->key         = (long)cur_face;         /* use the HFF as cur key */

   cur_face->element    = element;
   cur_face->ref_count  = 1;
   cur_face->hff        = (HFF)cur_face;
   strcpy( cur_face->filename, file_name);
   cur_face->flags      = 0;
   cur_face->sizes.head = NULL;
   cur_face->sizes.tail = NULL;
   cur_face->sizes.count= 0;
   cur_face->charMode   = 0;

   /* add new font face to sleep list */
   if (List_Insert( &idleFaces, element ))
     ERET1( Fail_Face );

   /* Make enough room in the live list */
   if ( liveFaces.count >= max_open_faces)
   {
     COPY( "rolling...\n" ); WRITE;
     if (Sleep_FontFace( (PFontFace)(liveFaces.tail->data) ))
       ERET1( Fail_Face );
   }

   /* wake new font face */
   if ( Wake_FontFace( cur_face ) )
   {
     COPY( "could not open/wake " ); CAT( file_name ); CAT( "\r\n" ); WRITE;
     if (List_Remove( &idleFaces, element ))
       ERET1( Fail_Face );

     ERET1( Fail_Face );
   }

   return cur_face;      /* everything is in order */

Fail_Face:
   FREE( cur_face );

Fail_Element:
   Done_Element( element );
   return  NULL;

Exit_Same:
   cur_face->ref_count++;  /* increment reference count */

   COPY( " -> (duplicate) hff = " ); CATI( cur_face->hff );
   CAT( "\r\n" ); WRITE;

   return cur_face;        /* no sense going on */
}

/****************************************************************************/
/*                                                                          */
/* getFontFace :                                                            */
/*                                                                          */
/*   return the address of the TFontFace corresponding to a given           */
/*   HFF. If asleep, the face object is awoken.                             */
/*                                                                          */
PFontFace  getFontFace( HFF  hff )
{
  static PListElement  element;

  /* look in the live list first */
  element = List_Find( &liveFaces, (long)hff );
  if (element)
  {
    /* move it to the front of the live list - if it isn't already */
    if ( liveFaces.head != element )
    {
      if ( List_Remove( &liveFaces, element ) )
        ERRRET( NULL );

      if ( List_Insert( &liveFaces, element ) )
        ERRRET( NULL );
    }
    return (PFontFace)(element->data);
  }

  /* the face may be asleep, look in the second list */
  element = List_Find( &idleFaces, (long)hff );
  if (element)
  {
    /* we need to awake the font, but before that, we must be sure */
    /* that there is enough room in the live list                  */
    if ( liveFaces.count >= max_open_faces )
      if (Sleep_FontFace( (PFontFace)(liveFaces.tail->data) ))
        ERRRET( NULL );

    if ( Wake_FontFace( (PFontFace)(element->data) ) )
      ERRRET( NULL );

    COPY ( "hff " ); CATI( hff ); CAT( " awoken\n" ); WRITE;
    return (PFontFace)(element->data);
  }

  COPY( "Could not find hff " ); CATI( hff ); CAT( " in lists\n" ); WRITE;

#ifdef DEBUG

  /* dump faces lists */
  COPY( "Live faces : " ); CATI( liveFaces.count ); CAT( "\r\n" ); WRITE;

  for (element = liveFaces.head; element; element = element->next)
  {
    COPY( ((PFontFace)(element->data))->filename ); CAT("\r\n");WRITE;
  }

  COPY( "Idle faces : " ); CATI( idleFaces.count ); CAT( "\r\n" ); WRITE;
  for (element = idleFaces.head; element; element = element->next)
  {
    COPY( ((PFontFace)(element->data))->filename ); CAT("\r\n");WRITE;
  }
#endif

  /* could not find the hff in the list */
  return NULL;
}


/****************************************************************************/
/*                                                                          */
/* getFontSize :                                                            */
/*                                                                          */
/*   return pointer to a TFontSize given a HFC handle, NULL if error        */
/*                                                                          */
static  PFontSize  getFontSize( HFC  hfc )
{
   int i;
   for ( i = 0; i < MAX_CONTEXTS; i++ )
      if ( contexts[i].hfc == hfc ) {
         return &contexts[i];
      }

   return NULL;
}

#ifdef USE_UCONV

/* maximum number of cached UCONV objects */
#define MAX_UCONV_CACHE   10

/* UCONV objects cache entry */
typedef struct  _UCACHEENTRY {
   UconvObject   object;      /* actual UCONV object */
   PID           pid;         /* process ID the object is valid for */
} UCACHEENTRY, *PUCACHEENTRY;

/****************************************************************************/
/*                                                                          */
/* getUconvObject :                                                         */
/*                                                                          */
/*   a function to cache UCONV objects based on current process. The only   */
/*  problem is that FT/2 currently doesn't keep track of processes and      */
/*  consequently the objects aren't freed when a process ends. But UCONV    */
/*  frees the objects itself anyway.                                        */
int getUconvObject(UniChar *name, UconvObject *ConvObj) {
   static UCACHEENTRY    UconvCache[MAX_UCONV_CACHE];  /* 10 should do it */
   static int            slotsUsed = 0;     /* number of cache slots used */

   PPIB                  ppib;   /* process/thread info blocks */
   PTIB                  ptib;
   PID                   curPid; /* current process ID */
   int                   i;

   /* query current process ID */
   if (DosGetInfoBlocks(&ptib, &ppib))
      return -1;

   curPid = ppib->pib_ulpid;

   if (slotsUsed == 0) {     /* initialize cache */
      if (UniCreateUconvObject(name, ConvObj) != ULS_SUCCESS)
         return -1;
      UconvCache[0].object = *ConvObj;
      UconvCache[0].pid    = curPid;

      for (i = 1; i < MAX_UCONV_CACHE; i++) {
         UconvCache[i].object = NULL;
         UconvCache[i].pid    = 0;
      }
      slotsUsed = 1;
      return 0;
   }

   /* search cache for current PID */
   i = 0;
   while (UconvCache[i].pid != curPid && i < slotsUsed)
      i++;

   if (i < slotsUsed) {  /* entry found in cache */
      *ConvObj = UconvCache[i].object;
      return 0;
   }

   /* create new entry */
   if (slotsUsed == MAX_UCONV_CACHE)  /* cache full, remove first entry */
      for (i = 1; i < MAX_UCONV_CACHE; i++) {
         UconvCache[i - 1].object = UconvCache[i].object;
         UconvCache[i - 1].pid    = UconvCache[i].pid;
      }

   if (UniCreateUconvObject(name, ConvObj) != ULS_SUCCESS)
      return -1;

   if (slotsUsed < MAX_UCONV_CACHE)
      slotsUsed++;

   UconvCache[slotsUsed - 1].object = *ConvObj;
   UconvCache[slotsUsed - 1].pid    = curPid;

   return 0;
}
#endif  /* USE_UCONV */

/****************************************************************************/
/*                                                                          */
/* PM2TT :                                                                  */
/*                                                                          */
/*   a function to convert PM codepoint to TT glyph index                   */
/*    mode = TRANSLATE_UGL - translate UGL to Unicode                       */
/*    mode = TRANSLATE_SYMBOL - no translation - symbol font                */
/*    mode = TRANSLATE_UNICODE- no translation - Unicode                    */
static  int PM2TT( TT_CharMap  charMap,
                   ULONG       mode,
                   int         index)
{
#ifdef USE_UCONV
   /* Brand new version that uses UCONV.DLL. This should make FreeType/2 */
   /* smaller and at the same time more flexible as it now should use    */
   /* the Unicode translation tables supplied with OS/2                  */
   /* Unfortunately there's a complication (again) since UCONV objects   */
   /* created in one process can't be used in another. Therefore we      */
   /* keep a small cache of recently used UCONV objects.                 */
   static UconvObject   UGLObj = NULL; /* UGL->Unicode conversion object */
   static BOOL          UconvSet = FALSE;
          char          char_data[2], *pin_char_str;
          size_t        in_bytes_left, uni_chars_left, num_subs;
          UniChar       *pout_uni_str, uni_buffer[2];
          int           rc;
          UniChar       uglName[10] = L"OS2UGL";

   switch (mode) {
      case TRANSLATE_UGL:
         if (UconvSet == FALSE) {
            switch (iLangId) {   /* select proper conversion table */
               case LANGID_GREEK_GREECE:
                  strncpy((char*)uglName, (char*)L"OS2UGLG", 16);
                  break;
               case LANGID_HEBREW_ISRAEL:
                  strncpy((char*)uglName, (char*)L"OS2UGLH", 16);
                  break;
               case LANGID_ARABIC_ARABIC:
                  strncpy((char*)uglName, (char*)L"OS2UGLA", 16);
                  break;
            }
            UconvSet = TRUE;
         }

         /* get Uconv object - either new or cached */
         if (getUconvObject(uglName, &UGLObj) != 0)
            return 0;

         if (index > MAX_GLYPH)
            return 0;

         char_data[0] = index;
         char_data[1] = index >> 8;

         pout_uni_str = uni_buffer;
         pin_char_str = char_data;
         in_bytes_left = 2;
         uni_chars_left = 1;

         rc = UniUconvToUcs(UGLObj, (void**)&pin_char_str, &in_bytes_left,
                           &pout_uni_str, &uni_chars_left,
                           &num_subs);
         if (rc != ULS_SUCCESS)
            return 0;
         else
            return TT_Char_Index(charMap, ((unsigned short*)uni_buffer)[0]);

      case TRANSLATE_SYMBOL:
      case TRANSLATE_UNICODE:
      case TRANSLATE_PRC:
         return TT_Char_Index(charMap, index);

      default:
         return 0;
   }
#else
   switch (mode)
   {
      /* convert from PM383 to Unicode */
      case TRANSLATE_UGL:
         /* TODO: Hebrew and Arabic UGL */
         if (iLangId == LANGID_GREEK_GREECE)  /* use Greek UGL */
            if ((index >= GREEK_START) && (index < GREEK_START + GREEK_GLYPHS))
               return TT_Char_Index(charMap, SubUGLGreek[index - GREEK_START]);

         if (index <= MAX_GLYPH)
            return TT_Char_Index(charMap, UGL2Uni[index]);
         else
            ERRRET(0);

      case TRANSLATE_SYMBOL :
      case TRANSLATE_UNICODE:
      case TRANSLATE_PRC:
         return TT_Char_Index(charMap, index);

      default:
         return 0;
   }
#endif
}

/****************************************************************************/
/*                                                                          */
/* mystricmp :                                                              */
/*                                                                          */
/* A simple function for comparing strings without case sensitivity. Just   */
/* returns zero if strings match, one otherwise. I wrote this because       */
/* stricmp is not available in the subsystem run-time library (probably     */
/* because it uses locales). toupper() is unfortunately unavailable too.    */
/*                                                                          */

#define toupper( c ) ( ((c) >= 'a') && ((c) <= 'z') ? (c) - 'a' + 'A' : (c) )

static
int mystricmp(const char *s1, const char *s2) {
   int i = 0;
   int match = 0;

   if (strlen(s1) != strlen(s2))
      return 1;   /* no match */

   while (i < strlen(s1)) {
      if (toupper(s1[i]) != toupper(s2[i])) {
         match = 1;
         break;
      }
      i++;
   }
   return match;
}


/* -------------------------------------------------------------------------*/
/* here begin the exported functions                                        */
/* -------------------------------------------------------------------------*/

/****************************************************************************/
/*                                                                          */
/* ConvertFontFile :                                                        */
/*                                                                          */
/*  Install/delete font file                                                */
/*                                                                          */
LONG _System ConvertFontFile( PSZ  source,
                              PSZ  dest_dir,
                              PSZ  new_name )
{
   PSZ  source_name;

   COPY("ConvertFontFile: Src = "); CAT(source);
   if (dest_dir) {
      CAT(", DestDir = "); CAT(dest_dir);
   }
   CAT("\r\n"); WRITE;

   if (dest_dir && new_name)
   {
     /* check if file is to be copied onto itself */
     if (strncmp(source, dest_dir, strlen(dest_dir)) == 0)
        return OK;  /* do nothing */

     /* install the font file */
     source_name = strrchr( source, '\\' );  /* find the last backslash */
     if (!source_name)
       ERRRET(-1);

     source_name++;
     strcpy( new_name, source_name );
     if ( DosCopy( source, dest_dir, DCPY_EXISTING) )  /* overwrite file */
       ERRRET(-1);  /* XXX : we should probably set the error condition */

      COPY(" -> Name: "); CAT(new_name); CAT("\r\n"); WRITE;
   }
   else
   {
      COPY("Delete file "); CAT(source); CAT("\r\n"); WRITE;
      DosDelete(source);  /* fail quietly */
   }

   return OK;
}

/****************************************************************************/
/*                                                                          */
/* LoadFontFile :                                                           */
/*                                                                          */
/*  open a font file and return a handle for it                             */
/*                                                                          */
HFF _System LoadFontFile( PSZ file_name )
{
   PSZ           extension;
   static PFontFace     cur_face;
   static PListElement  element;

   COPY( "LoadFontFile " ); CAT( file_name ); CAT( "\r\n" ); WRITE;

   /* first check if the file extension is supported */
   extension = strrchr( file_name, '.' );  /* find the last dot */
   if ( !extension ||
        mystricmp( extension, ".TTF" ) )
     return ((HFF)-1);                    /* Check that this is a TTF file */

   cur_face = New_FontFace( file_name );
   if (cur_face)
     return  cur_face->hff;
   else
     return (HFF)-1;
}

/****************************************************************************/
/*                                                                          */
/* UnloadFontFile :                                                         */
/*                                                                          */
/*  destroy resources associated with a given HFF                           */
/*                                                                          */
LONG _System UnloadFontFile( HFF hff )
{
   PListElement  element;

   COPY("UnloadFontFile: hff = "); CATI((int) hff); CAT("\r\n"); WRITE;

   /* look in the live list first */
   for (element = liveFaces.head; element; element = element->next)
   {
     if (element->key == (long)hff)
     {
       PFontFace  face = (PFontFace)element->data;

       if (--face->ref_count > 0)  /* don't really close, return OK */
         return 0;

       List_Remove( &liveFaces, element );
       Done_Element( element );
       Done_FontFace( &face );
       return 0;
     }
   }

   /* now look in sleep list */
   for (element = idleFaces.head; element; element = element->next)
   {
     if (element->key == (long)hff)
     {
       PFontFace  face = (PFontFace)element->data;

       if (--face->ref_count > 0)  /* don't really close, return OK */
         return 0;

       List_Remove( &idleFaces, element );
       Done_Element( element );
       Done_FontFace( &face );
       return 0;
     }
   }

   /* didn't find the file */
   return -1;
}

/****************************************************************************/
/*                                                                          */
/* QueryFaces :                                                             */
/*                                                                          */
/*   Return font metrics. This routine has to do a lot of not very          */
/*   hard work.                                                             */
/*                                                                          */
LONG _System QueryFaces( HFF          hff,
                         PIFIMETRICS  pifiMetrics,
                         ULONG        cMetricLen,
                         ULONG        cFontCount,
                         ULONG        cStart)
{
   static TT_Face_Properties   properties;
   TT_Header            *phead;
   TT_Horizontal_Header *phhea;
   TT_OS2               *pOS2;
   TT_Postscript        *ppost;
   PIFIMETRICS          xpim;   /* temporary structure */
   PIFIMETRICS          pifi2;
   PFontFace            face;
   LONG                 index, ifiCount = 0;
   char                 *name;

   COPY( "QueryFaces: hff = " ); CATI( hff );
   CAT(  ", cFontCount = " );    CATI( cFontCount );
   CAT(  ", cStart = " );        CATI( cStart );
   CAT(  ", cMetricLen = " );    CATI( cMetricLen );
   CAT( "\r\n");
   WRITE;

   face = getFontFace(hff);
   if (!face)
      ERRRET(-1) /* error, invalid handle */

   if (cMetricLen == 0) {   /* only number of faces is requested */
      if (face->flags & FF_FLAG_DBCS_FACE)
         return 2;
      else
         return 1;
   }
   error = TT_Get_Face_Properties( face->face, &properties );
   if (error)
     ERET1( Fail );

   pOS2  = properties.os2;
   phead = properties.header;
   phhea = properties.horizontal;
   ppost = properties.postscript;

   /* ??? maybe we should check for NULL strings here */
/*   if (cMetricLen == 64)
   {
      strncpy(pifiMetrics->szFamilyname,
              LookupName( face->face, 1), FACESIZE );
      pifiMetrics->szFamilyname[FACESIZE - 1] = '\0';
      strncpy(pifiMetrics->szFacename,
              LookupName( face->face, 4), FACESIZE );
      pifiMetrics->szFacename[FACESIZE - 1] = '\0';
      goto Exit;
   } */

   /* get font name and check it's found */
   name = LookupName(face->face, 1);
   if (name == NULL)
      ERET1(Fail);

   ALLOC( xpim, sizeof(IFIMETRICS)); /* allocate temp structure */
   if (xpim == NULL)
     ERET1( Fail );

   strncpy(xpim->szFamilyname, name, FACESIZE);    /* family name */
   xpim->szFamilyname[FACESIZE - 1] = '\0';

   name = LookupName(face->face, 4);               /* face name */
   if (name == NULL) {
      FREE(xpim);
      ERET1(Fail);
   }
   strncpy(xpim->szFacename, name, FACESIZE);
   xpim->szFacename[FACESIZE - 1] = '\0';

 /* here comes a hack: if Unicode encoding exists in font and it contains  */
 /* more than, say, 1024 glyphs, then do not translate from UGL to Unicode */
 /* and use straight Unicode                                               */

   if ((face->charMode == TRANSLATE_UGL) && (properties.num_Glyphs > 1024))
   {
      strcpy( xpim->szGlyphlistName, "UNICODE" );
      face->charMode = TRANSLATE_UNICODE; /* straight Unicode */
   }
   else
      if (face->charMode == TRANSLATE_SYMBOL)  /* symbol encoding */
         strcpy(xpim->szGlyphlistName, "SYMBOL");
      else
         if (face->charMode == TRANSLATE_PRC) /* PRC encoding */
            strcpy(xpim->szGlyphlistName, "PMCHT");
         else
            strcpy(xpim->szGlyphlistName, "PM383");

   xpim->idRegistry         = 0;
   xpim->lCapEmHeight       = phead->Units_Per_EM; /* ??? probably correct  */
   xpim->lXHeight           = phead->yMax /2;      /* IBM TRUETYPE.DLL does */
   xpim->lMaxAscender       = pOS2->usWinAscent;

   if ((LONG)pOS2->usWinDescent >= 0)
      xpim->lMaxDescender   = pOS2->usWinDescent;
   else
      xpim->lMaxDescender   = -pOS2->usWinDescent;

   xpim->lLowerCaseAscent   = phhea->Ascender;
   xpim->lLowerCaseDescent  = -phhea->Descender;

   xpim->lInternalLeading   = xpim->lMaxAscender + xpim->lMaxDescender
                              - xpim->lCapEmHeight;

   xpim->lExternalLeading   = 0;
   xpim->lAveCharWidth      = pOS2->xAvgCharWidth;
   xpim->lMaxCharInc        = phhea->advance_Width_Max;
   xpim->lEmInc             = phead->Units_Per_EM;
   xpim->lMaxBaselineExt    = xpim->lMaxAscender + xpim->lMaxDescender;
   xpim->fxCharSlope        = -ppost->italicAngle;    /* is this correct ?  */
   xpim->fxInlineDir        = 0;
   xpim->fxCharRot          = 0;
   xpim->usWeightClass      = pOS2->usWeightClass;    /* hopefully OK       */
   xpim->usWidthClass       = pOS2->usWidthClass;
   xpim->lEmSquareSizeX     = phead->Units_Per_EM;
   xpim->lEmSquareSizeY     = phead->Units_Per_EM;    /* probably correct   */
   xpim->giFirstChar        = 0;            /* following values should work */
   xpim->giLastChar         = 503;          /* either 383 or 503            */
   xpim->giDefaultChar      = 0;
   xpim->giBreakChar        = 32;
   xpim->usNominalPointSize = 120;   /*    these are simply constants       */
   xpim->usMinimumPointSize = 10;
   xpim->usMaximumPointSize = 10000; /* limit to 1000 pt (like the ATM fonts) */
   xpim->fsType             = pOS2->fsType & IFIMETRICS_LICENSED; /* ???    */
   xpim->fsDefn             = IFIMETRICS_OUTLINE;  /* always with TrueType  */
   xpim->fsSelection        = 0;
   xpim->fsCapabilities     = 0; /* must be zero according to the IFI spec */
   xpim->lSubscriptXSize    = pOS2->ySubscriptXSize;
   xpim->lSubscriptYSize    = pOS2->ySubscriptYSize;
   xpim->lSubscriptXOffset  = pOS2->ySubscriptXOffset;
   xpim->lSubscriptYOffset  = pOS2->ySubscriptYOffset;
   xpim->lSuperscriptXSize  = pOS2->ySuperscriptXSize;
   xpim->lSuperscriptYSize  = pOS2->ySuperscriptYSize;
   xpim->lSuperscriptXOffset= pOS2->ySuperscriptXOffset;
   xpim->lSuperscriptYOffset= pOS2->ySuperscriptYOffset;
   xpim->lUnderscoreSize    = ppost->underlineThickness;/* IBM's TT does it */
   xpim->lUnderscorePosition= -ppost->underlinePosition;
   xpim->lStrikeoutSize     = pOS2->yStrikeoutSize;
   xpim->lStrikeoutPosition = pOS2->yStrikeoutPosition;

#if 0
   if (face->directory.nTables != 0) {
      xpim->cKerningPairs   = (face->directory.tables[0].length - 8) / 6;
      xpim->fsType |= IFIMETRICS_KERNING; /* !!! for testing only! */
   }
   else
#endif
      xpim->cKerningPairs   = 0;   /* !!!! not supported yet! */

   /* Note that the following field seems to be the only reliable method of */
   /* recognizing a TT font from an app!  Not that it should be done.       */
   xpim->ulFontClass        = 0x10D; /* just like TRUETYPE.DLL */

   /* the following adjustment are needed because the TT spec defines */
   /* usWeightClass and fsType differently                            */
   if (xpim->usWeightClass >= 100)
      xpim->usWeightClass /= 100;
   if (xpim->usWeightClass == 4)
      xpim->usWeightClass = 5;    /* does this help? */
   if (ppost->isFixedPitch) {
      xpim->fsType |= IFIMETRICS_FIXED;
      face->flags |= FF_FLAG_FIXED_WIDTH; /* we'll need this later */
   }

/*   xpim->fsType |= IFIMETRICS_KERNING;*/ /* !!! for testing only! */

   switch (face->charMode) {      /* adjustments for var. encodings */
      case TRANSLATE_UNICODE:
         xpim->giLastChar = pOS2->usLastCharIndex;
         xpim->fsType |= IFIMETRICS_MBCS | IFIMETRICS_DBCS;
         break;

      case TRANSLATE_PRC:
         xpim->giLastChar = 383;
         xpim->fsType |= IFIMETRICS_MBCS | IFIMETRICS_DBCS;
         break;

      case TRANSLATE_SYMBOL:
         xpim->giLastChar = 255;
         break;

   }

   /* adjust fsSelection (TT defines this differently) */
   if (pOS2->fsSelection & 0x01) {
       xpim->fsSelection |= IFIMETRICS_ITALIC;
   }
   if (pOS2->fsSelection & 0x02) {
       xpim->fsSelection |= IFIMETRICS_UNDERSCORE;
   }
   if (pOS2->fsSelection & 0x04) {
       xpim->fsSelection |= IFIMETRICS_OVERSTRUCK;
   }

   face->em_size = phead->Units_Per_EM; /* we'll need this later */

   /* copy to output buffer then deallocate temporary storage */
   /* also handle the 'fake' vertically rendered DBCS fonts   */
   index = 0 /*loop*/ * ((face->flags & FF_FLAG_DBCS_FACE) ? 2 : 1);
   if ((index >= cStart) && (index < (cStart + cFontCount))) {
      memcpy((((PBYTE) pifiMetrics) + ifiCount), xpim,
             sizeof(IFIMETRICS) > cMetricLen ? cMetricLen : sizeof(IFIMETRICS));
      ifiCount += (sizeof(IFIMETRICS) > cMetricLen ? cMetricLen : sizeof(IFIMETRICS));
   }
   if ((face->flags & FF_FLAG_DBCS_FACE) && (index + 1 >= cStart) &&
       (index + 1 < (cStart + cFontCount))) {

      pifi2 = (PIFIMETRICS) (((PBYTE) pifiMetrics) + ifiCount);
      memcpy(pifi2, xpim,
         sizeof(IFIMETRICS) > cMetricLen ? cMetricLen : sizeof(IFIMETRICS));
      strcpy(pifi2->szFamilyname + 1, xpim->szFamilyname);
      pifi2->szFamilyname[0] = '@';
      strcpy(pifi2->szFacename + 1, xpim->szFacename);
      pifi2->szFacename[0] = '@';
      ifiCount += (sizeof(IFIMETRICS) > cMetricLen ? cMetricLen : sizeof(IFIMETRICS));
   }
   FREE(xpim);

Exit:
   TT_Flush_Face(face->face);
   return cFontCount;

Fail:
   TT_Flush_Face( face->face );
   return -1;
}

/****************************************************************************/
/*                                                                          */
/* OpenFontContext :                                                        */
/*                                                                          */
/*  open new font context                                                   */
/*                                                                          */
HFC _System OpenFontContext( HFF    hff,
                             ULONG  ulFont)
{
   int          i = 0;
   static TT_Instance  instance;
   static PFontFace    face;

   COPY("OpenFontContext: hff = "); CATI((int) hff); CAT("\r\n");
   COPY("              ulFont = "); CATI((int) ulFont); CAT("\r\n");
   WRITE;

   face = getFontFace(hff);
   if (!face)
      ERRRET((HFC)-1) /* error, invalid font handle */

   /* TODO: TTC support! */
   if (face->flags & FF_FLAG_DBCS_FACE) {
      if (ulFont > 1)
         ERRRET((HFC)-1)
   }
   else
      if (ulFont > 0)
         ERRRET((HFC)-1)

   /* OK, create new instance with defaults */
   error = TT_New_Instance( face->face, &instance);
   if (error)
     ERET1( Fail );

   /* Instance resolution is set to 72 dpi and is never changed     */
   error = TT_Set_Instance_Resolutions(instance, 72, 72);
   if (error)
      ERRRET((HFC)-1)

   /* find first unused index */
   i = 0;
   while ((contexts[i].hfc != 0) && (i < MAX_CONTEXTS))
      i++;

   if (i == MAX_CONTEXTS)
     ERET1( Fail );  /* no free slot in table */

   contexts[i].hfc          = (HFC)(i + 0x100); /* initialize table entries */
   contexts[i].instance     = instance;
   contexts[i].transformed  = FALSE;            /* no scaling/rotation assumed */
   contexts[i].face         = face;
   if (ulFont == 1)  /* TODO: TTC support */
      contexts[i].vertical  = TRUE;
   else
      contexts[i].vertical  = FALSE;

   face->flags |= FF_FLAG_CONTEXT_OPEN;         /* flag as in-use */

   COPY("-> hfc "); CATI((int) contexts[i].hfc); CAT("\r\n"); WRITE;

   TT_Flush_Face(face->face);
   return contexts[i].hfc; /* everything OK */

Fail:
   TT_Flush_Face( face->face );
   return (HFC)-1;
}

/****************************************************************************/
/*                                                                          */
/* SetFontContext :                                                         */
/*                                                                          */
/*  set font context parameters                                             */
/*                                                                          */
LONG _System SetFontContext( HFC           hfc,
                             PCONTEXTINFO  pci )
{
   LONG       ptsize, temp;
   PFontSize  size;

   COPY("SetFontContext: hfc = ");           CATI((int) hfc);
   CAT(", sizlPPM.cx = ");                   CATI((int) pci->sizlPPM.cx);
   CAT(", sizlPPM.cy = ");                   CATI((int) pci->sizlPPM.cy);
   CAT("\r\n                pfxSpot.x = ");  CATI((int) pci->pfxSpot.x);
   CAT(", pfxSpot.y = ");                    CATI((int) pci->pfxSpot.y);
   CAT("\r\n                eM11 = ");       CATI((int) pci->matXform.eM11);
   CAT(", eM12 = ");                         CATI((int) pci->matXform.eM12);
   CAT(", eM21 = ");                         CATI((int) pci->matXform.eM21);
   CAT(", eM22 = ");                         CATI((int) pci->matXform.eM22);
   CAT("\r\n");
   WRITE;

   size = getFontSize(hfc);
   if (!size)
      ERRRET(-1) /* error, invalid context handle */

   /* Look at matrix and see if a transform is asked for */
   /* Actually when rotating by 90 degrees hinting could be used */

   size->transformed =
     ( pci->matXform.eM11 != pci->matXform.eM22      ||
       (pci->matXform.eM12|pci->matXform.eM21) != 0  ||
       pci->matXform.eM11 <= 0 );

   if ( size->transformed )
   {
      /* note that eM21 and eM12 are swapped; I have no idea why, but */
      /* it seems to be correct */
      size->matrix.xx = pci->matXform.eM11 * 64;
      size->matrix.xy = pci->matXform.eM21 * 64;
      size->matrix.yx = pci->matXform.eM12 * 64;
      size->matrix.yy = pci->matXform.eM22 * 64;
      return 0;
   }

   /* calculate & set  point size  */
   ptsize = (size->face->em_size * (pci->matXform.eM11 + pci->matXform.eM21)) >> 10;

   if (ptsize <= 0)                /* must not allow zero point size ! */
      ptsize = 1;                  /* !!!  could be handled better     */

   error = TT_Set_Instance_CharSize(size->instance, ptsize);
   if (error)
      ERRRET(-1)  /* engine problem */

   return 0;      /* pretend everything is OK */
}

/****************************************************************************/
/*                                                                          */
/* CloseFontContext :                                                       */
/*                                                                          */
/*  destroy a font context                                                  */
/*                                                                          */
LONG _System CloseFontContext( HFC hfc)
{
   PFontSize  size;

   COPY("CloseFontContext: hfc = "); CATI((int)hfc); CAT("\r\n"); WRITE;

   size = getFontSize(hfc);
   if (!size)
      ERRRET(-1) /* error, invalid context handle */

   /* mark table entry as free */
   size->hfc = 0;

   size->face->flags &= ~FF_FLAG_CONTEXT_OPEN;   /* reset the in-use flag */

   if (size->face->flags & FF_FLAG_LIVE_FACE) {
      COPY("Closing instance: "); CATI((int)(size->instance.z)); CAT("\r\n"); WRITE;
      error = TT_Done_Instance(size->instance);
      if (error)
         ERRRET(-1)  /* engine error */
   }

   COPY("CloseFontContext successful\r\n"); WRITE;

   return 0; /* success */
}

/****************************************************************************/
/*                                                                          */
/* QueryFaceAttr                                                            */
/*                                                                          */
/*  Return various info about font face                                     */
/*                                                                          */
LONG _System QueryFaceAttr( HFC     hfc,
                            ULONG   iQuery,
                            PBYTE   pBuffer,
                            ULONG   cb,
                            PGLYPH  pagi,
                            GLYPH   giStart )
{
   int                        count, i = 0;
   static TT_Glyph_Metrics    metrics;
   PFontSize                  size;
   PFontFace                  face;
   static TT_Face_Properties  properties;
   TT_OS2                     *pOS2;
   ABC_TRIPLETS*              pt;

   COPY("QueryFaceAttr: hfc = "); CATI((int) hfc); CAT("\r\n"); WRITE;

   size = getFontSize(hfc);
   if (!size)
      ERRRET(-1) /* error, invalid context handle */

   face = size->face;

   if (iQuery == FD_QUERY_KERNINGPAIRS)
   {
      return 0;  /* no support yet */

      /* NOTE: Don't link ftxkern.c until we really support kerning - David */
#if 0
      TT_Kern_0        kerntab;   /* actual kerning table */
      ULONG            used = 0;  /* # bytes used in output buffer */
      FD_KERNINGPAIRS  kpair;

      /* !!!! needs better error checking */
      if (face->directory.tables[0].format != 0) /* need only format 0 */
         ERRRET(-1);

      error = TT_Load_Kerning_Table(face->face, 0);
      if (error)
        ERET1( Fail );

      kerntab = face->directory.tables[0].t.kern0;
      while ((i < kerntab.nPairs) && (used <= cb))
      {
         /* !!! glyph indices need to be translated ! */
         kpair.giFirst        = kerntab.pairs[i].left;
         kpair.giSecond       = kerntab.pairs[i].right;
         kpair.eKerningAmount = kerntab.pairs[i].value;
         memcpy(pBuffer + i * sizeof(kpair), &kpair, sizeof(kpair));
         i++;
      }
      return i - 1; /* # items filled */
#endif
   }

   if (iQuery == FD_QUERY_ABC_WIDTHS)
   {
      count = cb / sizeof(ABC_TRIPLETS);

      COPY("QUERY_ABC_WIDTHS, "); CATI((int) count);
      CAT(" items, giStart = ");  CATI((int) giStart);
      if (pBuffer == NULL)
         CAT(" NULL buffer");
      CAT("\r\n"); WRITE;

      /* This call never fails - no error check needed */
      TT_Get_Face_Properties( face->face, &properties );

      pt = (ABC_TRIPLETS*)pBuffer;
      for (i = giStart; i < giStart + count; i++, pt++)
      {
         int                    index;
         unsigned short         wid;
         static unsigned short  adv_widths [2];
         static unsigned short  adv_heights[2];

         static unsigned short  widths[2], heights[2];
         static short           lefts [2], tops   [2];

         index = PM2TT( size->face->charMap,
                        size->face->charMode,
                        i );

         /* get advances and bearings */
         if (size->vertical && properties.vertical)
           error = TT_Get_Face_Metrics( face->face, index, index,
                                        lefts, adv_widths, tops, adv_heights );
         else
           error = TT_Get_Face_Metrics( face->face, index, index,
                                        lefts, adv_widths, NULL, NULL );

         if (error)
           goto Broken_Glyph;

         /* skip complicated calculations for fixed fonts */
         if (size->face->flags & FF_FLAG_FIXED_WIDTH) {
            wid = adv_widths[0] - lefts[0];
         }
         else {   /* proportianal font, it gets trickier */
            /* store glyph widths for DBCS fonts
               - needed for reasonable performance */
            if (size->face->flags & FF_FLAG_DBCS_FACE) {
               if (size->face->widths == NULL) {
                  error = ALLOC(size->face->widths,
                                properties.num_Glyphs * sizeof (USHORT));
                  if (error)
                     goto Broken_Glyph;   /* this error really shouldn't happen */

                  /* tag all entries as unused */
                  memset(size->face->widths, 0xFF,
                         properties.num_Glyphs * sizeof (USHORT));
               }
               if (size->face->widths[index] == 0xFFFF) { /* get from file if needed */
                  error = TT_Get_Face_Widths( face->face, index, index,
                                              widths, heights );
                  if (error)
                     goto Broken_Glyph;

                  /* save for later */
                  wid = size->face->widths[index] = widths[0];
               }
               else
                  wid = size->face->widths[index];
            }
            /* 'small' font, no need to remember widths */
            else {
               /* get width or height - use ftxwidth.c */
               error = TT_Get_Face_Widths( face->face, index, index,
                                           widths, heights );
               if (error)
                 goto Broken_Glyph;

               wid = widths[0];
            }
         }

         if (size->vertical && !is_HALFCHAR(i))
         {
            if (properties.vertical)
            {
              pt->lA = tops[0];
              pt->ulB = heights[0];
              pt->lC = adv_heights[0] - pt->lA - pt->ulB;
            }
            else
            {
              pt->lA  = pt->lC = 0;
              pt->ulB = properties.os2->usWinAscent +
                        properties.os2->usWinDescent;
            }

         }
         else
         {
           pt->lA = lefts[0];
           pt->ulB = wid;
           pt->lC = adv_widths[0] - pt->lA - pt->ulB;
         }

         continue;

     Broken_Glyph:  /* handle broken glyphs gracefully */
           pt->lA = pt->lC = 0;

           if (size->vertical && !is_HALFCHAR(i))
             pt->ulB = properties.os2->usWinAscent +
                      properties.os2->usWinDescent;
           else
             pt->ulB = properties.horizontal->xMax_Extent;

#ifdef NETSCAPE_FIX
         if (size->face->charMode != TRANSLATE_SYMBOL) {
            if (size->face->flags & FF_FLAG_FIXED_WIDTH) {
               pt->ulB = metrics.advance - metrics.bearingX;
               pt->lC = 0;
            } else if (i == 32) {
               /* return nonzero B width for 'space' */
               pt->ulB = metrics.advance - 2 * metrics.bearingX;
               pt->lC  = metrics.bearingX;
            }
         }
#endif
      }
   }

   TT_Flush_Face(face->face);
   return count; /* number of entries filled in */

Fail:
   TT_Flush_Face(face->face);
   return -1;
}

/****************************************************************************/
/*                                                                          */
/* QueryCharAttr :                                                          */
/*                                                                          */
/*  Return glyph attributes, basically glyph's bit-map or outline           */
/*  some variables are declared static to conserve stack space.             */
/*                                                                          */
LONG _System QueryCharAttr( HFC             hfc,
                            PCHARATTR       pCharAttr,
                            PBITMAPMETRICS  pbmm )
{
   static   TT_Raster_Map  bitmap;
   static   TT_Outline     outline;
   static   TT_BBox        bbox;

   PFontSize  size;
   PFontFace  face;
   LONG       temp;
   PBYTE      pb;
   int        i, j;
   ULONG      cb;

   size = getFontSize(hfc);
   if (!size)
      ERRRET(-1) /* error, invalid context handle */

   face = size->face;

   error = TT_Load_Glyph( size->instance,
                          face->glyph,
                          PM2TT( face->charMap,
                                 face->charMode,
                                 pCharAttr->gi),
                          size->transformed ? 0 : TTLOAD_DEFAULT);
      /* do scaling and hinting only if no rotation/scaling requested */

   if (error)
   {
      if (i == 0)
         ERET1( Fail )  /* this font's no good, return error */
      else
      { /* try to recover quietly */
         error = TT_Load_Glyph( size->instance,
                                face->glyph,
                                0,
                                size->transformed ? 0 : TTLOAD_DEFAULT);
         if (error) {
            COPY("Error code is "); CATI(error); CAT("\r\n"); WRITE;
            ERET1( Fail );
         }
      }
   }

   TT_Flush_Face( face->face );

   error = TT_Get_Glyph_Outline( face->glyph, &outline );
   if (error)
      ERRRET(-1);

   /* --- Vertical fonts handling----------------------------------- */
   if (size->vertical && !is_HALFCHAR(pCharAttr->gi)) {
      TT_Matrix     vertMatrix;

      vertMatrix.xx =  0x00000;
      vertMatrix.xy = -0x10000;
      vertMatrix.yx =  0x10000;
      vertMatrix.yy =  0x00000;
      TT_Get_Outline_BBox( &outline, &bbox );
      /* rotate outline 90 degrees counterclockwise (?) */
      TT_Transform_Outline(&outline, &vertMatrix);
      /* move outline to the right to adjust for rotation */
      TT_Translate_Outline(&outline, bbox.yMax, 0);
      /* move outline down a bit */
      TT_Translate_Outline(&outline, 0, -bbox.xMin);
   }
   /* --- Outline processing --------------------------------------- */

   if ( pCharAttr->iQuery & FD_QUERY_OUTLINE )
   {
     if (pCharAttr->cbLen == 0)      /* send required outline size in bytes */
       return GetOutlineLen( &outline );

     if (size->transformed)
       TT_Transform_Outline( &outline, &size->matrix );

     return GetOutline( &outline, pCharAttr->pBuffer );
   }

   /* --- Bitmap processing ---------------------------------------- */

   if (size->transformed)
     TT_Transform_Outline( &outline, &size->matrix );

   TT_Get_Outline_BBox( &outline, &bbox );

   /* the following seems to be necessary for rotated glyphs */
   if (size->transformed) {
      bbox.xMax = bbox.xMin = 0;
      for (i = 0; i < outline.n_points; i++) {
         if (bbox.xMin  > outline.points[i].x)
            bbox.xMin = outline.points[i].x;
         if (bbox.xMax  < outline.points[i].x)
            bbox.xMax = outline.points[i].x;
      }
   }
   /* grid-fit the bbox */
   bbox.xMin &= -64;
   bbox.yMin &= -64;

   bbox.xMax = (bbox.xMax+63) & -64;
   bbox.yMax = (bbox.yMax+63) & -64;

   if (pCharAttr->iQuery & FD_QUERY_BITMAPMETRICS)
   {
      /* fill in bitmap metrics */
      /* metrics values are in 26.6 format ! */
      pbmm->sizlExtent.cx = (bbox.xMax - bbox.xMin) >> 6;
      pbmm->sizlExtent.cy = (bbox.yMax - bbox.yMin) >> 6;
      pbmm->cyAscent      = 0;
      pbmm->pfxOrigin.x   = bbox.xMin << 10;
      pbmm->pfxOrigin.y   = bbox.yMax << 10;

      if (!(pCharAttr->iQuery & FD_QUERY_CHARIMAGE))
         return sizeof(*pbmm);
   }

   /* --- actual bitmap processing --- */
   if (pCharAttr->iQuery & FD_QUERY_CHARIMAGE)
   {
      /* values in 26.6 format ?!? */
      bitmap.width  = (bbox.xMax - bbox.xMin) >> 6;
      bitmap.rows   = (bbox.yMax - bbox.yMin) >> 6;
      /* width rounded up to nearest multiple of 4 */
      bitmap.cols   = ((bitmap.width + 31) / 8) & -4;
      bitmap.flow   = TT_Flow_Down;
      bitmap.bitmap = pCharAttr->pBuffer;
      bitmap.size   = bitmap.rows * bitmap.cols;

      if (pCharAttr->cbLen == 0)
         return bitmap.size;

      if (bitmap.size > pCharAttr->cbLen)
         ERRRET(-1)     /* otherwise we might overwrite something */

      /* clean provided buffer (unfortunately necessary) */
      memset(bitmap.bitmap, 0, pCharAttr->cbLen);

      error = TT_Get_Glyph_Bitmap( face->glyph,
                                   &bitmap,
                                   -bbox.xMin,
                                   -bbox.yMin );
      if (error)
         ERRRET(-1); /* engine error */

      return bitmap.size; /* return # of bytes */
   }
   ERRRET(-1) /* error */

Fail:
   TT_Flush_Face( face->face );
   return -1;
}

/****************************************************************************/
/*                                                                          */
/* QueryFullFaces :                                                         */
/*                                                                          */
/*  Query names of all faces in this file                                   */
/*                                                                          */
LONG _System QueryFullFaces( HFF     hff,
                             PVOID   pBuff,
                             PULONG  buflen,
                             PULONG  cFontCount,
                             ULONG   cStart )
{
   COPY("!QueryFullFaces: hff = "); CATI((int) hff); CAT("\r\n"); WRITE;
   ERRRET(-1) /* error ? */
}

/*---------------------------------------------------------------------------*/
/* end of exported functions */
/*---------------------------------------------------------------------------*/


/****************************************************************************/
/* LimitsInit reads OS2.INI and sets up max_open_faces limit, possibly      */
/* other variables as well.                                                 */
/*                                                                          */
static  void LimitsInit(void) {
   char  cBuffer[25];  /* ought to be enough */

   if (PrfQueryProfileString(HINI_USERPROFILE, "FreeType/2", "OPENFACES",
                             NULL, cBuffer, sizeof(cBuffer)) > 0) {
      max_open_faces = atoi(cBuffer);

      if (max_open_faces < 8)  /* ensure limit isn't too low */
         max_open_faces = 8;
   }
   else
      max_open_faces = 12;   /* reasonable default */
}


/****************************************************************************/
/* my_itoa is used only in the following function GetUdcInfo.               */
/* Works pretty much like expected.                                         */
/*                                                                          */
void    my_itoa(int num, char *cp) {
    char    temp[10];
    int     i = 0;

    do {
        temp[i++] = (num % 10) + '0';
        num /= 10;
    } while (num);                  /* enddo */

    while (i--) {
        *cp++ = temp[i];
    }                               /* endwhile */
    *cp = '\0';
}

/****************************************************************************/
/* GetUdcInfo determines the UDC ranges used                                */
/*                                                                          */
VOID    GetUdcInfo(VOID) {
    ULONG   ulUdc, ulUdcInfo, i;
    PVOID   gPtr;
    HINI    hini;
    CHAR    szCpStr[10] = "CP";
    typedef struct _USUDCRANGE {
        USHORT  usStart;
        USHORT  usEnd;
    }       USUDCRANGE;
    USUDCRANGE *pUdcTemp;

    DosQueryCp(sizeof(ulCp), (ULONG*)&ulCp, &i);  /* find out default codepage */
    my_itoa((INT) ulCp, szCpStr + 2);     /* convert to ASCII          */

    /* determine boot drive */
    DosQuerySysInfo(QSV_BOOT_DRIVE, QSV_BOOT_DRIVE, &i, sizeof(ULONG));
    szNlsFmProfile[0] = 'A' + i - 1;

    /* open and query PMNLSFM.INI */
    if ((hini = PrfOpenProfile((HAB) 0L, szNlsFmProfile)) != NULLHANDLE) {
        if (PrfQueryProfileSize(hini, szUdcApp, szCpStr, &ulUdc) == TRUE) {
            ulUdcInfo = sizeof(UDCINFO) + sizeof(UDCRANGE) * (ulUdc / 4);
            ALLOC(pUdcInfo, ulUdcInfo);
            ALLOC(pUdcTemp, ulUdc);

            if (PrfQueryProfileData(hini, szUdcApp, szCpStr,
                                    pUdcTemp, &ulUdc) == TRUE) {
                /* Initialize UDC info */
                pUdcInfo->ulCount = ulUdc >> 2;
                pUdcInfo->udcFirst = 0xffffffff;
                pUdcInfo->udcLast = 0;

                /* Loop for all UDC ranges */
                for (i = 0; i < pUdcInfo->ulCount; i++) {
                    /* Copy this UDC range */
                    pUdcInfo->UdcRange[i].udcStart = pUdcTemp[i].usStart;
                    pUdcInfo->UdcRange[i].udcEnd = pUdcTemp[i].usEnd;

                    /* Update the first or last UDC code point */
                    if (pUdcInfo->udcFirst > pUdcInfo->UdcRange[i].udcStart)
                        pUdcInfo->udcFirst = pUdcInfo->UdcRange[i].udcStart;

                    if (pUdcInfo->udcLast < pUdcInfo->UdcRange[i].udcEnd)
                        pUdcInfo->udcLast = pUdcInfo->UdcRange[i].udcEnd;
                }
            }
            FREE(pUdcTemp);
        }
        PrfCloseProfile(hini);
    }
}

/****************************************************************************/
/* LangInit determines language used at DLL startup, non-zero return value  */
/* means error.                                                             */
/* This code is crucial, because it determines behaviour of the font driver */
/* with regard to language encodings it will use.                           */
static  ULONG LangInit(void) {
   COUNTRYCODE cc = {0, 0};
   COUNTRYINFO ci;
   ULONG       cilen;

   isGBK = FALSE;

   GetUdcInfo();           /* get User Defined Character info */
   /* get country info; ci.country then contains country code */
   if (DosQueryCtryInfo(sizeof(ci), &cc, &ci, &cilen))
      return -1;
   else {
      uLastGlyph = 383;
      switch (ci.country) {
         case 81:            /* Japan */
            iLangId = LANGID_JAPANESE_JAPAN;
            ScriptTag = *(ULONG *) "kana";
            LangSysTag = *(ULONG *) "JAN ";
            pGlyphlistName = "PMJPN";
            uLastGlyph = 890;
            break;

         case 88:            /* Taiwan */
            iLangId = LANGID_CHINESE_TAIWAN;
            ScriptTag = *(ULONG *) "kana";
            LangSysTag = *(ULONG *) "CHT ";
            pGlyphlistName = "PMCHT";
            break;

         case 86:            /* People's Republic of China */
            if (ci.codepage == 1386 || ulCp[0] == 1386 || ulCp[1] == 1386) {
               isGBK = TRUE;
            }               /* endif */
            iLangId = LANGID_CHINESE_CHINA;
            ScriptTag = *(ULONG *) "kana";
            LangSysTag = *(ULONG *) "CHS ";
            pGlyphlistName = "PMPRC";
            break;

         case 82:            /* Korea */
            iLangId = LANGID_WANSUNG_KOREA;
            ScriptTag = *(ULONG *) "hang";
            LangSysTag = *(ULONG *) "KOR ";
            pGlyphlistName = "PMKOR";
            uLastGlyph = 949;
            break;

         case 30:            /* Greece  - for Alex! */
            iLangId = LANGID_GREEK_GREECE;

         default:            /* none of the above countries */
            ScriptTag = *(ULONG *) "";
            LangSysTag = *(ULONG *) "";
            break;
      }                      /* endswitch */
   }                         /* endif */

   return 0;
}
/****************************************************************************/
/*                                                                          */
/* _DLL_InitTerm :                                                          */
/*                                                                          */
/*  This is the DLL Initialization/termination function. It initializes     */
/*  the FreeType engine and some interl structures at startup. It seems     */
/*  that the termination isn't actually called at all.                      */
/*                                                                          */
/*  Slightly modified for use with Watcom C/C++                             */
/*  Termination function is here although it never gets called anyway       */
/*                                                                          */
#ifdef __IBMC__
ULONG _DLL_InitTerm(ULONG hModule, ULONG ulFlag) {
   #ifdef DEBUG
    ULONG Action;
   #endif /* DEBUG */
   switch (ulFlag) {
      case 0:             /* initializing */
#endif
#ifdef __WATCOMC__
unsigned __dll_initialize(void) {
   #ifdef DEBUG
    ULONG Action;
   #endif /* DEBUG */
#endif
         #ifdef DEBUG
            DosOpen("C:\\FTIFI.LOG", &LogHandle, &Action, 0, FILE_NORMAL,
                    OPEN_ACTION_CREATE_IF_NEW | OPEN_ACTION_REPLACE_IF_EXISTS,
                    OPEN_FLAGS_NO_CACHE | OPEN_FLAGS_WRITE_THROUGH |
                    OPEN_FLAGS_SEQUENTIAL | OPEN_SHARE_DENYWRITE | OPEN_ACCESS_WRITEONLY,
                    NULL);

            COPY("FreeType/2 loaded.\r\n");
            WRITE;
         #endif /* DEBUG */
         /* TTMemory_Init(); */
         error = TT_Init_FreeType(&engine);   /* turn on the FT engine */
         if (error)
            return 0;     /* exit immediately */
         error = TT_Init_Kerning_Extension(engine); /* load kerning support */
         COPY("FreeType Init called\r\n");
         WRITE;

         if (LangInit())      /* initialize NLS */
            return 0;         /* exit on error  */
         COPY("NLS initialized.\r\n");
         WRITE;

         LimitsInit();  /* initialize max_open_faces */
         COPY("Open faces limit set to "); CATI(max_open_faces); CAT("\r\n");
         WRITE;

         if (error)
            return 0;     /* exit immediately */
         COPY("Initialization successful.\r\n");
         WRITE;
         return 1;

#ifdef __IBMC__

      case 1:            /* terminating */
#else /* Watcom */
}
unsigned __dll_terminate(void) {
#endif
         TT_Done_FreeType(engine);
         COPY("FreeType/2 terminated.\r\n");
         WRITE;
         #ifdef DEBUG
            DosClose(LogHandle);
         #endif
         return 1;
#ifdef __IBMC__
         break;

      default:
         return 0;
   }

   return 1;
#endif
}

/****************************************************************************/
/*                                                                          */
/* interfaceSEId (Interface-specific Encoding Id) determines what encoding  */
/* the font driver should use if a font supports the Unicode encoding.      */
/*                                                                          */
/* Note: I don't much like the way this code works but I want FreeType/2    */
/*    to behave as much like IBM's TRUETYPE.DLL as possible.                */
/*                                                                          */
LONG    interfaceSEId(TT_Face face, int UDCflag) {
    LONG    encoding;
    ULONG   range1 = 0;
    ULONG   bits, mask;
    TT_OS2  *pOS2;
    static  TT_Face_Properties  props;

    TT_Get_Face_Properties(face, &props);
    if (error) {
       ERRRET(-1);
    }
    encoding = 0; /* TODO!! ttf->PlatformSEId;*/

    if (encoding == PSEID_UNICODE) {

       /* if font is 'small', use PM383; this is done because of Far East
          systems  */
       if (!UDCflag && props.num_Glyphs < 1024) {
          encoding = PSEID_PM383;
       } else if (pOS2->version >= 1) {
          /*
           * *  OS/2 table version 1 and later contains codepage *
           * bitfield to support multiple codepages.
           */
          range1 = pOS2->ulCodePageRange1;

          /* mask other bits off */
          range1 &= (OS2_CP1_ANSI_OEM_JAPANESE_JIS |
                     OS2_CP1_ANSI_OEM_CHINESE_SIMPLIFIED |
                     OS2_CP1_ANSI_OEM_CHINESE_TRADITIONAL |
                     OS2_CP1_ANSI_OEM_KOREAN_WANSUNG |
                     OS2_CP1_ANSI_OEM_KOREAN_JOHAB);

          bits = 0;
/*          for (mask = 0x00100000, bits = 0; mask > 0; mask >>=1)
             if (mask & range1)
                bits++; */
          if (range1 & OS2_CP1_ANSI_OEM_JAPANESE_JIS)
             bits++;
          if (range1 & OS2_CP1_ANSI_OEM_CHINESE_SIMPLIFIED)
             bits++;
          if (range1 & OS2_CP1_ANSI_OEM_CHINESE_TRADITIONAL)
             bits++;
          if (range1 & OS2_CP1_ANSI_OEM_KOREAN_WANSUNG)
             bits++;
          if (range1 & OS2_CP1_ANSI_OEM_KOREAN_JOHAB)
             bits++;

          /* Note: if font supports more than one of the following codepages,
           * encoding is left at PSEID_UNICODE!
           */
          if (bits == 1) {
             switch (range1) {
                case OS2_CP1_ANSI_OEM_JAPANESE_JIS:
                   encoding = PSEID_SHIFTJIS;
                   break;
                case OS2_CP1_ANSI_OEM_CHINESE_SIMPLIFIED:
                   encoding = PSEID_PRC;
                   break;
                case OS2_CP1_ANSI_OEM_CHINESE_TRADITIONAL:
                   encoding = PSEID_BIG5;
                   break;
                case OS2_CP1_ANSI_OEM_KOREAN_WANSUNG:
                   encoding = PSEID_WANSUNG;
                   break;
                case OS2_CP1_ANSI_OEM_KOREAN_JOHAB:
                   encoding = PSEID_JOHAB;
                   break;
                default:
                   break;
             }                   /* endswitch */
          }                      /* endif */
       } else {
          /*
           * The codepage range bitfield is not available.
           * Codepage must be assumed from the COUNTRY setting.
           */

          switch (iLangId) {
             case LANGID_JAPANESE_JAPAN:
                encoding = PSEID_SHIFTJIS;
                break;
             case LANGID_CHINESE_CHINA:
             case LANGID_CHINESE_SINGAPORE:
                encoding = PSEID_PRC;
                break;
             case LANGID_CHINESE_TAIWAN:
             case LANGID_CHINESE_HONGKONG:
                encoding = PSEID_BIG5;
                break;
             case LANGID_WANSUNG_KOREA:
                encoding = PSEID_WANSUNG;
                break;
             case LANGID_JOHAB_KOREA:
                encoding = PSEID_JOHAB;
                break;
          }

       }                           /* endif */
    }                              /* endif */
    return encoding;
}

/****************************************************************************/
/*                                                                          */
/* LookUpName :                                                             */
/*                                                                          */
/*   Look up for a TrueType name by index                                   */
/*                                                                          */
static  char*  LookupName(TT_Face face,  int index )
{
   static char name_buffer[FACESIZE + 2];
   int    name_len = 0;
   int i, j, n;

   USHORT platform, encoding, language, id;
   char*  string;
   USHORT string_len;

   int    found;

   n = TT_Get_Name_Count( face );
   if ( n < 0 )
      return NULL;

   for ( i = 0; i < n; i++ )
   {
      TT_Get_Name_ID( face, i, &platform, &encoding, &language, &id );
      TT_Get_Name_String( face, i, &string, &string_len );

      if ( id == index )
      {
        found = 0;

        /* Try to find an appropriate name */
        if ( platform == 3 )
          for ( j = 5; j >= 0; j-- )
            if ( encoding == j )  /* Microsoft ? */
              switch (language)
              {
                case LANGID_CHINESE_TAIWAN:
                   if (encoding == PSEID_PRC)
                      found = 1;
                   break;

                case 0x409:
                case 0x809:
                case 0xc09:
                case 0x1009:
                case 0x1409:
                case 0x1809: found = 1;
                             break;
              }

        if ( !found && platform == 0 && language == 0 )
          found = 1;

        /*
         * In all cases, the name is in Unicode
         */
        if (found)
        {
          if (language == 0x404) {
             if (string_len > FACESIZE - 1)
                string_len = FACESIZE - 1;

             strncpy(name_buffer, string, string_len);
             name_buffer[string_len] = '\0';

             return name_buffer;
          }
          else {
             if ( string_len > FACESIZE * 2)
               string_len = FACESIZE * 2;

             name_len = 0;

             for ( i = 1; i < string_len; i += 2 )
               name_buffer[name_len++] = string[i];

             name_buffer[name_len] = '\0';

             return name_buffer;
           }
        }
      }
   }

   /* Not found */
   return NULL;
}

/****************************************************************************/
/*                                                                          */
/* GetCharMap :                                                             */
/*                                                                          */
/*  A function to find a suitable charmap, searching in the following       */
/*  order of importance :                                                   */
/*                                                                          */
/*   1) Windows Unicode                                                     */
/*   2) Apple Unicode                                                       */
/*   3) PRC (China)                                                         */
/*   4) Apple Roman                                                         */
/*   5) Windows Symbol - not really supported                               */
/*                                                                          */
/* High word of returned ULONG contains type of encoding                    */
/*                                                                          */
/* David: Complete rewrite to make it simpler and faster.                   */
/*                                                                          */
static  ULONG GetCharmap(TT_Face face)
{
   int    n;      /* # of encodings (charmaps) available */
   USHORT platform, encoding;
   int    i, best, bestVal, val;

   n = TT_Get_CharMap_Count(face);

   if (n < 0)      /* no encodings at all; don't yet know what the best course of action would be */
      ERRRET(-1)   /* such font should probably be rejected */

   bestVal = 16;
   best    = -1;

   for (i = 0; i < n; i++)
   {
     TT_Get_CharMap_ID( face, i, &platform, &encoding );

     /* Windows Unicode is the highest encoding, return immediately */
     /* if we find it..                                             */
     if ( platform == 3 && encoding == 1)
       return i;

     /* otherwise, compare it to the best encoding found */
     val = -1;
     if (platform == 0)                        /* Unicode */
        val = 2;
     else if (platform == 3 && encoding == 4)  /* PRC */
        val = 3;
     else if (platform == 1 && encoding == 0)  /* Apple Roman */
        val = 4;
     else if (platform == 3 && encoding == 0)  /* Windows Symbol (?) */
        val = 5;

     if (val > 0 && val <= bestVal)
     {
       bestVal = val;
       best    = i;
     }
   }

   if (i < 0)
     return 0;           /* we didn't find any suitable encoding !! */

   if (bestVal == 4)        /* for Apple Roman encoding only, this      */
     best |= ( 1 << 16 );   /* means no translation should be performed */

   if (bestVal == 3)        /* Chinese font */
     best |= ( TRANSLATE_PRC << 16 );

   return best;
}

/****************************************************************************/
/*                                                                          */
/* GetOutlineLen :                                                          */
/*                                                                          */
/*   Used to compute the size of an outline once it is converted to         */
/*   OS/2's specific format. The translation is performed by the later      */
/*   function called simply "GetOultine".                                   */
/*                                                                          */
static  int GetOutlineLen(TT_Outline *ol)
{
   int    index;     /* current point's index */
   BOOL   on_curve;  /* current point's state */
   int    i, start = 0;
   int    first, last;
   ULONG  cb = 0;

   /* loop thru all contours in a glyph */
   for ( i = 0; i < ol->n_contours; i++ ) {

      cb += sizeof(POLYGONHEADER);

      first = start;
      last  = ol->contours[i];

      on_curve = (ol->flags[first] & 1);
      index    = first;

      /* process each contour point individually */
      while ( index < last ) {
         index++;

         if ( on_curve ) {
            /* the previous point was on the curve */
            on_curve = ( ol->flags[index] & 1 );
            if ( on_curve ) {
               /* two successive on points => emit segment */
               cb += sizeof(PRIMLINE);
            }
         }
         else {
            /* the previous point was off the curve */
            on_curve = ( ol->flags[index] & 1 );
            if ( on_curve ) {
               /* reaching an `on' point */
               cb += sizeof(PRIMSPLINE);
            }
            else {
               /* two successive `off' points => create middle point */
               cb += sizeof(PRIMSPLINE);
            }
         }
      }

      /* end of contour, close curve cleanly */
      if ( ol->flags[first] & 1 )
      {
        if ( on_curve )
           cb += sizeof(PRIMLINE);
        else
           cb += sizeof(PRIMSPLINE);
      }
      else
        if (!on_curve)
           cb += sizeof(PRIMSPLINE);

      start = ol->contours[i] + 1;

   }
   return cb; /* return # bytes used */
}

/****************************************************************************/
/*                                                                          */
/*  a few global variables used in the following functions                  */
/*                                                                          */
static ULONG          cb = 0, polycb;
static LONG           lastX, lastY;
static PBYTE          pb;
static POINTFX        Q, R;
static POLYGONHEADER  hdr = {0, FD_POLYGON_TYPE};
static PRIMLINE       line = {FD_PRIM_LINE};
static PRIMSPLINE     spline = {FD_PRIM_SPLINE};

/****************************************************************************/
/*                                                                          */
/* LineFrom :                                                               */
/*                                                                          */
/*   add a line segment to the PM outline that GetOultine is currently      */
/*   building.                                                              */
/*                                                                          */
static  void Line_From(LONG x, LONG y) {
   line.pte.x = x << 10;
   line.pte.y = y << 10;
   /* store to output buffer */
   memcpy(&(pb[cb]), &line, sizeof(line));
   cb     += sizeof(PRIMLINE);
   polycb += sizeof(PRIMLINE);
}


/****************************************************************************/
/*                                                                          */
/* BezierFrom :                                                             */
/*                                                                          */
/*   add a bezier arc to the PM outline that GetOutline is currently        */
/*   buidling. The second-order Bezier is trivially converted to its        */
/*   equivalent third-order form.                                           */
/*                                                                          */
static  void Bezier_From( LONG x0, LONG y0, LONG x2, LONG y2, LONG x1, LONG y1 ) {
   spline.pte[0].x = x0 << 10;
   spline.pte[0].y = y0 << 10;
   /* convert from second-order to cubic Bezier spline */
   Q.x = (x0 + 2 * x1) / 3;
   Q.y = (y0 + 2 * y1) / 3;
   R.x = (x2 + 2 * x1) / 3;
   R.y = (y2 + 2 * y1) / 3;
   spline.pte[1].x = Q.x << 10;
   spline.pte[1].y = Q.y << 10;
   spline.pte[2].x = R.x << 10;
   spline.pte[2].y = R.y << 10;
   /* store to output buffer */
   memcpy(&(pb[cb]), &spline, sizeof(spline));
   cb     += sizeof(PRIMSPLINE);
   polycb += sizeof(PRIMSPLINE);
}


/****************************************************************************/
/*                                                                          */
/* GetOutline :                                                             */
/*                                                                          */
/*   Translate a FreeType glyph outline into PM format. The buffer is       */
/*   expected to be of the size returned by a previous call to the          */
/*   function GetOutlineLen().                                              */
/*                                                                          */
/*   This code is taken right from the FreeType ttraster.c source, and      */
/*   subsequently modified to emit PM segments and arcs.                    */
/*                                                                          */
static  int GetOutline(TT_Outline *ol, PBYTE pbuf) {
   LONG   x,  y;   /* current point                */
   LONG   cx, cy;  /* current Bezier control point */
   LONG   mx, my;  /* current middle point         */
   LONG   x_first, y_first;  /* first point's coordinates */
   LONG   x_last,  y_last;   /* last point's coordinates  */

   int    index;     /* current point's index */
   BOOL   on_curve;  /* current point's state */
   int    i, start = 0;
   int    first, last;
   ULONG  polystart;

   pb = pbuf;
   cb = 0;

   /* loop thru all contours in a glyph */
   for ( i = 0; i < ol->n_contours; i++ ) {

      polystart = cb;  /* save this polygon's start offset */
      polycb = sizeof(POLYGONHEADER); /* size of this polygon */
      cb += sizeof(POLYGONHEADER);

      first = start;
      last = ol->contours[i];

      x_first = ol->points[first].x;
      y_first = ol->points[first].y;

      x_last  = ol->points[last].x;
      y_last  = ol->points[last].y;

      lastX = cx = x_first;
      lastY = cy = y_first;

      on_curve = (ol->flags[first] & 1);
      index    = first;

      /* check first point to determine origin */
      if ( !on_curve ) {
         /* first point is off the curve.  Yes, this happens... */
         if ( ol->flags[last] & 1 ) {
            lastX = x_last;  /* start at last point if it */
            lastY = y_last;  /* is on the curve           */
         }
         else {
            /* if both first and last points are off the curve, */
            /* start at their middle and record its position    */
            /* for closure                                      */
            lastX = (lastX + x_last)/2;
            lastY = (lastY + y_last)/2;

            x_last = lastX;
            y_last = lastY;
         }
      }

      /* now process each contour point individually */
      while ( index < last ) {
         index++;
         x = ( ol->points[index].x );
         y = ( ol->points[index].y );

         if ( on_curve ) {
            /* the previous point was on the curve */
            on_curve = ( ol->flags[index] & 1 );
            if ( on_curve ) {
               /* two successive on points => emit segment */
               Line_From( lastX, lastY ); /*x, y*/
               lastX = x;
               lastY = y;
            }
            else {
               /* else, keep current control point for next bezier */
               cx = x;
               cy = y;
            }
         }
         else {
            /* the previous point was off the curve */
            on_curve = ( ol->flags[index] & 1 );
            if ( on_curve ) {
               /* reaching an `on' point */
               Bezier_From(lastX, lastY, x, y, cx, cy );
               lastX = x;
               lastY = y;
            }
            else {
               /* two successive `off' points => create middle point */
               mx = (cx + x) / 2;
               my = (cy + y)/2;

               Bezier_From( lastX, lastY, mx, my, cx, cy );
               lastX = mx;
               lastY = my;

               cx = x;
               cy = y;
            }
         }
      }

      /* end of contour, close curve cleanly */
      if ( ol->flags[first] & 1 ) {
         if ( on_curve )
            Line_From( lastX, lastY); /* x_first, y_first );*/
         else
            Bezier_From( lastX, lastY, x_first, y_first, cx, cy );
      }
      else
        if (!on_curve)
          Bezier_From( lastX, lastY, x_last, y_last, cx, cy );

      start = ol->contours[i] + 1;

      hdr.cb = polycb;
      memcpy(&(pb[polystart]), &hdr, sizeof(hdr));

   }
   return cb; /* return # bytes used */
}


