//===============================================================
// vfont.cxx - The font class - Windows
//
// Copyright (C) 1995,1996,1997,1998  Bruce E. Wampler
//
// This file is part of the V C++ GUI Framework, and is covered
// under the terms of the GNU Library General Public License,
// Version 2. This library has NO WARRANTY. See the source file
// vapp.cxx for more complete information about license terms.
//===============================================================

#include <v/vos2.h>		// for OS/2 stuff
#include <v/vapp.h>
#include <v/vfont.h>
#include <math.h>

//=======================>>> vFont::vFont <<<=============================
  vFont::vFont(vFontID fam, int size, vFontID sty, vFontID wt, int und, int ang)
  {
    SysDebug(Constructor,"vFont::vFont() constructor\n")

    _family = fam;
    _pointSize = size;
    _style = sty;
    _weight = wt;
    _underlined = und;
    _angle = ang;
    _isFromFntDlg = 0;
    memset(&_fat, 0, sizeof(FATTRS));             // clear structure
    _fat.usRecordLength = sizeof(FATTRS);
    memset(&_status, 0, sizeof(_status));   // clear structure

  }

//=====================>>> vFont::vFont <<<===========================
  vFont::vFont(const vFont& ft)
  {
    // Copy constructor - needs to delete object if already
    // created, and copy the values as needed

    _family = ft._family;
    _pointSize = ft._pointSize;
    _style = ft._style;
    _weight = ft._weight;
    _underlined = ft._underlined;
    _angle = ft._angle;
    _isFromFntDlg = ft._isFromFntDlg;
    // copy over the selected file attribute and status structures
    memcpy(&_fat, &ft._fat, sizeof(FATTRS));
    memcpy(&_status, &ft._status, sizeof(_status));
  }

//=====================>>> vFont::= <<<===========================
  vFont& vFont::operator =(const vFont& ft)
  {
    if (this == &ft)     // assigning to self
    {
      return *this;
    }
    // just like the copy constructor
    _family = ft._family;
    _pointSize = ft._pointSize;
    _style = ft._style;
    _weight = ft._weight;
    _underlined = ft._underlined;
    _angle = ft._angle;
    _isFromFntDlg = ft._isFromFntDlg;

    // copy over the selected file attribute structure
    memcpy(&_fat, &ft._fat, sizeof(FATTRS));
    memcpy(&_status, &ft._status, sizeof(_status));

    return *this;
  }

//=====================>>> vFont::SetFontValues <<<===========================
  void vFont::SetFontValues(vFontID fam, int size, vFontID sty, vFontID wt,
    int und)
  {
    // delete the old font from all assigned dc's
    for (int i=0; i<MAXPS; i++)
    {
      if (_status[i].dc != 0)
      {
	GpiSetCharSet(_status[i].dc, LCID_DEFAULT);     // restore default font
	GpiDeleteSetId(_status[i].dc, _status[i].fid);  // destroy logical font
      }
    }
    _family = fam;
    _pointSize = size;
    _style = sty;
    _weight = wt;
    _underlined = und;
    _isFromFntDlg = 0;
    memset(&_fat, 0, sizeof(FATTRS));             // clear structure
    _fat.usRecordLength = sizeof(FATTRS);
    memset(&_status, 0, sizeof(_status));         // clear structure
  }


//=====================>>> vFont::SetWinFontValues <<<========================
// This is the entry point from the font dialog box and is the typical
// way an app will set the desired font
void vFont::SetWinFontValues(VCONST FONTDLG& fdlg)
{
  // delete the old font from all assigned dc's
  for (int i=0; i<MAXPS; i++)
  {
    if (_status[i].dc != 0)
    {
      GpiSetCharSet(_status[i].dc, LCID_DEFAULT);     // restore default font
      GpiDeleteSetId(_status[i].dc, _status[i].fid);  // destroy logical font
    }
  }
  memset(&_status, 0, sizeof(_status));           // clear structure

  _isFromFntDlg = 1;		// font selection is from dialog box

  // copy over the selected file attribute structure
  memcpy(&_fat, &fdlg.fAttrs, sizeof(FATTRS));
/*
  _fat.usRecordLength = sizeof(FATTRS);
  _fat.fsSelection = fdlg.fAttrs.fsSelection;
  _fat.lMatch = fdlg.fAttrs.lMatch;
  strcpy(_fat.szFacename, fdlg.fAttrs.szFacename);
  _fat.idRegistry = fdlg.fAttrs.idRegistry;
  _fat.usCodePage = fdlg.fAttrs.usCodePage;
  _fat.lMaxBaselineExt = fdlg.fAttrs.lMaxBaselineExt;
  _fat.lAveCharWidth = fdlg.fAttrs.lAveCharWidth;
  _fat.fsType = fdlg.fAttrs.fsType;
  _fat.fsFontUse = fdlg.fAttrs.fsFontUse;
*/

  // now fill in the V font variables
  _pointSize = FIXEDINT(fdlg.fxPointSize);

  if (_fat.fsSelection & FATTR_SEL_UNDERSCORE)
    _underlined = 1;
  else
    _underlined = 0;

  // fsSelection will indicate Bold and Italic only for synthesized
  // (ie. bitmap) fonts and will not be set for regular Adobe outline fonts
  // For outline fonts must use FONTMETRICS to determine these properties
  // in vfont::loadfont
  if (_fat.fsFontUse & FATTR_FONTUSE_OUTLINE)  // its an outline font
  {
    if (fdlg.usWeight >= FWEIGHT_BOLD)
      _weight = vfBold;
    else
      _weight = vfNormal;

    if (_fat.fsSelection & FATTR_SEL_ITALIC)
      _style = vfItalic;
    else
      _style = vfNormal;
  }
  else  // an image font
  {
    if (_fat.fsSelection & FATTR_SEL_ITALIC)
      _style = vfItalic;
    else
      _style = vfNormal;

    if (_fat.fsSelection & FATTR_SEL_BOLD)
      _weight = vfBold;
    else
      _weight = vfNormal;
  }

  // Now, determine if it is a V family
  // Serif fonts
  if (strcmp(_fat.szFacename, "Times New Roman") == 0 ||
      strcmp(_fat.szFacename, "Tms Rmn") == 0)
  {
    _family = vfSerif;
    _weight = vfNormal;
    _style = vfNormal;
  }
  else if (strcmp(_fat.szFacename, "Times New Roman Italic") == 0 ||
	   strcmp(_fat.szFacename, "Tms Rmn Italic") == 0)
  {
    _family = vfSerif;
    _weight = vfNormal;
    _style = vfItalic;
  }
  else if (strcmp(_fat.szFacename, "Times New Roman Bold") == 0 ||
	   strcmp(_fat.szFacename, "Tms Rmn Bold") == 0)
  {
    _family = vfSerif;
    _weight = vfBold;
    _style = vfNormal;
  }
  else if (strcmp(_fat.szFacename, "Times New Roman Bold Italic") == 0 ||
	   strcmp(_fat.szFacename, "Tms Rmn Bold Italic") == 0)
  {
    _family = vfSerif;
    _weight = vfBold;
    _style = vfItalic;
  }
  //  Sans Serif fonts
  else if (strcmp(_fat.szFacename, "Helvetica") == 0 ||
	   strcmp(_fat.szFacename, "Helv") == 0)
  {
    _family = vfSansSerif;
    _weight = vfNormal;
    _style = vfNormal;
  }
  else if (strcmp(_fat.szFacename, "Helvetica Bold") == 0 ||
	   strcmp(_fat.szFacename, "Helv Bold") == 0)
  {
    _family = vfSansSerif;
    _weight = vfBold;
    _style = vfNormal;
  }
  else if (strcmp(_fat.szFacename, "Helvetica Italic") == 0 ||
	   strcmp(_fat.szFacename, "Helv Italic") == 0)
  {
    _family = vfSansSerif;
    _weight = vfNormal;
    _style = vfItalic;
  }
  else if (strcmp(_fat.szFacename, "Helvetica Bold Italic") == 0 ||
	   strcmp(_fat.szFacename, "Helv Bold Italic") == 0)
  {
    _family = vfSansSerif;
    _weight = vfBold;
    _style = vfItalic;
  }
  //  fixed pitch fonts
  else if (strcmp(_fat.szFacename, "Courier") == 0 )
  {
    _family = vfFixed;
    _weight = vfNormal;
    _style = vfNormal;
  }
  else if (strcmp(_fat.szFacename, "Courier Bold") == 0 )
  {
    _family = vfFixed;
    _weight = vfBold;
    _style = vfNormal;
  }
  else if (strcmp(_fat.szFacename, "Courier Italic") == 0 )
  {
    _family = vfFixed;
    _weight = vfNormal;
    _style = vfItalic;
  }
  else if (strcmp(_fat.szFacename, "Courier Bold Italic") == 0 )
  {
    _family = vfFixed;
    _weight = vfBold;
    _style = vfItalic;
  }
  // decorative font
  else if (strcmp(_fat.szFacename, "Symbol Set") == 0 )
  {
    _family = vfDecorative;
    _weight = vfNormal;
    _style = vfNormal;
  }
  // default fixed pitch font
  else if (strcmp(_fat.szFacename, "System Monospaced") == 0 )
  {
    _family = vfDefaultFixed;
  }
  // default proportional pitch font
  else if (strcmp(_fat.szFacename, "System Proportional") == 0 )
  {
    _family = vfDefaultVariable;
  }
  // otherwise, it must be an 'other' font
  else
  {
    _family = vfOtherFont;  // a Native OS/2 font
  }

  // Finally, check things that might make it NOT a V font.
  if (_fat.fsSelection & FATTR_SEL_STRIKEOUT ||
      _fat.fsSelection & FATTR_SEL_OUTLINE)
  {
    _family = vfOtherFont;  // a Native OS/2 font
  }
}

//=======================>>> vFont::~vFont <<<=============================
  vFont::~vFont()
  {
    SysDebug(Destructor,"vFont::~vFont() destructor\n")

    // delete the font from all assigned dc's
    for (int i=0; i<MAXPS; i++)
    {
      if (_status[i].dc != 0)
      {
	GpiSetCharSet(_status[i].dc, LCID_DEFAULT);     // restore default font
	GpiDeleteSetId(_status[i].dc, _status[i].fid);  // destroy logical font
      }
    }
  }

//=======================>>> vFont::LoadFont <<<=============================
  void vFont::LoadFont(HPS dc)
  {
    // if font already created, then just set to PS, otherwise need to create the
    // logical font first
    int i;
    for (i=0; i< MAXPS; i++)
    {
      if (dc == _status[i].dc)
      {
	GpiSetCharSet(dc, _status[i].fid);
	// get info on current font and load into font metrics
	GpiQueryFontMetrics(dc, sizeof(_fm), &_fm);

	// set the char box size if its an Adobe (outline) font
	if (_fm.fsDefn & FM_DEFN_OUTLINE)
	{
	  GpiSetCharBox(dc, &_status[i].CharBoxSize);
	}
	return;  // font loaded, we are done
      }
    }
    // else font not created yet

    // fill in the _fat structure and create the logical font
    ULONG chrSet = theApp->AppCP();
    switch (_family)
    {
      // make monospaced system default as per windows code
      case vfDefaultSystem:
      case vfDefaultFixed:
        _fat.fsFontUse = 0;
        _angle = 0;
        strncpy(_fat.szFacename, "System Monospaced", FACESIZE);
        break;

      case vfSerif:	// Serif font
        _fat.fsFontUse = FATTR_FONTUSE_OUTLINE;
        if (_weight == vfNormal && _style == vfNormal)
          strncpy(_fat.szFacename, "Times New Roman", FACESIZE);
        else if (_weight == vfBold && _style == vfNormal)
          strncpy(_fat.szFacename, "Times New Roman Bold", FACESIZE);
        else if (_weight == vfNormal && _style == vfItalic)
          strncpy(_fat.szFacename, "Times New Roman Italic", FACESIZE);
        else
          strncpy(_fat.szFacename, "Times New Roman Bold Italic", FACESIZE);
        break;

      case vfSansSerif:	// SansSerif Font
        _fat.fsFontUse = FATTR_FONTUSE_OUTLINE;
        if (_weight == vfNormal && _style == vfNormal)
          strncpy(_fat.szFacename, "Helvetica", FACESIZE);
        else if (_weight == vfBold && _style == vfNormal)
          strncpy(_fat.szFacename, "Helvetica Bold", FACESIZE);
        else if (_weight == vfNormal && _style == vfItalic)
          strncpy(_fat.szFacename, "Helvetica Italic", FACESIZE);
        else
          strncpy(_fat.szFacename, "Helvetica Bold Italic", FACESIZE);
        break;

      case vfFixed:		// Courier font

        _fat.fsFontUse = 0;
        _angle = 0;
        strncpy(_fat.szFacename, "System Monospaced", FACESIZE);
/*
        // Although Courier is a fixed space font, OS/2 may or may not
        // work that way.  If you choose Courier at a font size of 10 or 12
        // you get the desired monospace font (a bitmap font).
        // For other point sizes, you get an "outline" Courier font that is proportional.
        // Just another saga is the disgusting mess that OS/2 calls a font system.  Imagine
        // having both outline and bitmap fonts with the same name.... uuughhh!
        // Hence, we will use system monospace instead to avoid a debacle!
        _fat.fsFontUse = FATTR_FONTUSE_OUTLINE;
        if (_weight == vfNormal && _style == vfNormal)
          strncpy(_fat.szFacename, "Courier", FACESIZE);
        else if (_weight == vfBold && _style == vfNormal)
          strncpy(_fat.szFacename, "Courier Bold", FACESIZE);
        else if (_weight == vfNormal && _style == vfItalic)
          strncpy(_fat.szFacename, "Courier Italic", FACESIZE);
        else
          strncpy(_fat.szFacename, "Courier Bold Italic", FACESIZE);
*/
        break;

      case vfDecorative:	// Decorative
        _fat.fsFontUse = FATTR_FONTUSE_OUTLINE;
        strncpy(_fat.szFacename, "Symbol Set", FACESIZE);
        chrSet = 65400;
        break;

      case vfOtherFont:	// set by font picker dialog
        if (_isFromFntDlg == 1 )
        {
          // all okay
          break;
        }
        // otherwise fall through to default variable font

      case vfDefaultVariable:	// default font
      default:
        _fat.fsFontUse = 0;
        _angle = 0;
        strncpy(_fat.szFacename, "System Proportional",FACESIZE);
        break;
    }
    // Now, set the rest of the font attributes
    _fat.usCodePage = chrSet;

    // synthesize attributes if bitmap font
    if (!(_fat.fsFontUse & FATTR_FONTUSE_OUTLINE))
    {
      if (_weight == vfNormal && _style == vfItalic)
	_fat.fsSelection = FATTR_SEL_ITALIC;
      else if (_weight == vfBold && _style == vfNormal)
	_fat.fsSelection = FATTR_SEL_BOLD;
      else if (_weight == vfBold && _style == vfItalic)
	_fat.fsSelection = FATTR_SEL_BOLD | FATTR_SEL_ITALIC;

      // compute required pointsize if not from font dialog box
      if (_isFromFntDlg == 0 )
      {
	// For bitmap (image) fonts we match the requested font point size
        // to the os/2 font with the closest font size for the system dpi!
        // But there's more to this nightmare.  We must query the bitmap font
        // FONTMETRICS to get the exact value for MaxBaseLineExt and AveCharWidth.
        // in order to select the font, otherwise the selection fails
        // and we get the default font which is System Proportional!!!!!
	// They must have been on heavy drugs when they came
	// with this bizarre font API
        LoadBitmapFont(dc);
      }
    }
    // set underlining for all fonts
    if (_underlined )
      _fat.fsSelection |= FATTR_SEL_UNDERSCORE;

    // now create the logical font
    // First, find an unused index in the _status array and save dc
    for (i=0; i< MAXPS; i++)
    {
      if (_status[i].dc == 0)
	break;
    }
    _status[i].dc = dc;
    // Next, allocate an available LCID.  This routine will find the
    // lowest numbered id not currently being used
    LONG Count = GpiQueryNumberSetIds(dc);   // query the number of set ids
    if (Count==0)
      _status[i].fid = 1;            // none allocated yet, so choice is easy
    else
    {
      // we need space for 3 arrays (LONG[Count], LONG[Count], STR8[Count])
      PLONG pLcids = (PLONG) malloc((ULONG)((2 * sizeof(LONG) + sizeof(STR8)) * Count ));
      PLONG pTypes = (PLONG)(pLcids + Count);  // start of 2nd array
      PSTR8 pNames = (PSTR8)(pTypes + Count);  // start of 3rd array

      GpiQuerySetIds(dc, Count, pTypes, pNames, pLcids); // get all allocated ids

      // search over the entire set of ids
      for (_status[i].fid=1; _status[i].fid<=LCID_MAX; _status[i].fid++)
      {
	// we check if any of the allocated ids are set to _status[i].fid
        int j;
	for (j=0; (j<Count) && (pLcids[j]!=_status[i].fid); j++);
	// if j==Count then none of the allocated ids matched _status[i].fid,
	// and thus _status[i].fid is available for assignment, otherwise keep looking
	if (j==Count)
	  break;
      }
      free(pLcids);
    }

    // now create the logical font
    int rc = GpiCreateLogFont(dc, (PSTR8)NULL, _status[i].fid, &_fat);
    SysDebug3(Text,"vFont::LoadFont %s size=%u (GpiCreateLogFont=%u)\n",
      _fat.szFacename, _pointSize, rc);

    GpiSetCharSet(dc, _status[i].fid);

    // we set the character angle here
    // note that bitmap fonts do not work very well with angles
    // other than 0 degrees.
    LONG dx, dy;
    dx = (LONG)( (2^16) * cos(_angle*PI/180.) );
    dy = (LONG)( (2^16) * sin(_angle*PI/180.) );
    GRADIENTL grAngle={dx,dy};
    GpiSetCharAngle(dc, &grAngle);

    // get info on current font and load into font metrics
    GpiQueryFontMetrics(dc, sizeof(_fm), &_fm);

    // now set the character box (pointsize) if its an outline font
    if (_fm.fsDefn & FM_DEFN_OUTLINE)
    {
      // get the device resolution in pels/meter
      POINTL points[2];
      SIZEF CharBoxSize;

      // compute the point size in pels
      // get dc resolution pels/meter
      HDC hDevCxt = GpiQueryDevice(dc);
      DevQueryCaps(hDevCxt, CAPS_HORIZONTAL_RESOLUTION, 1L, &_XDeviceRes);
      DevQueryCaps(hDevCxt, CAPS_VERTICAL_RESOLUTION, 1L, &_YDeviceRes);
      // compute equivalent pointsize in pels
      // we add half DPM to get correct round-off behaviour
      points[0].x = 0;
      points[0].y = 0;
      points[1].x = (_pointSize * 10 * _XDeviceRes + DPM/2)/DPM;
      points[1].y = (_pointSize * 10 * _YDeviceRes + DPM/2)/DPM;
      // this is probably a unity transform but play it safe...
      GpiConvert(dc, CVTC_DEVICE, CVTC_WORLD, 2L, points);
      // transform to type fixed
      CharBoxSize.cx = MAKEFIXED((points[1].x - points[0].x), 0);
      CharBoxSize.cy = MAKEFIXED((points[1].y - points[0].y), 0);
      GpiSetCharBox(dc, &CharBoxSize);
      // save for future loads
      _status[i].CharBoxSize.cx = CharBoxSize.cx;
      _status[i].CharBoxSize.cy = CharBoxSize.cy;

    }
  }


//====================>>> vFont::loadBitmapFont <<<==========================
  // finds the nearest bitmap font to the requested pointsize and
  // facename.  This is for internal use only. Minimal error
  // checking is done.
  void vFont::LoadBitmapFont( HPS dc )
  {
    PFONTMETRICS pFM, pCurFM;
    LONG Count = 0L;
    int  i;

    // Do any fonts with the requested facename exist
    Count = GpiQueryFonts (dc, QF_PUBLIC | QF_PRIVATE, _fat.szFacename,
      &Count, (LONG)sizeof(FONTMETRICS), 0);

    if (Count)
    {
      // If so allocate memory and retrieve their metrics
      DosAllocMem ((PPVOID)&pFM, sizeof(FONTMETRICS) * (USHORT)Count,
        OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT);

      GpiQueryFonts (dc,QF_PUBLIC | QF_PRIVATE, _fat.szFacename,
        &Count, (LONG)sizeof(FONTMETRICS), pFM);

      // get the font resolution (dpi) of the display
      HDC hDevCxt = GpiQueryDevice(dc);
      LONG   cxFontRes, cyFontRes;
      DevQueryCaps(hDevCxt, CAPS_VERTICAL_FONT_RES, 1L, &cyFontRes);
      DevQueryCaps(hDevCxt, CAPS_HORIZONTAL_FONT_RES, 1L, &cxFontRes);

      // Look through the list trying to find a match
      pCurFM = pFM;
      int BestDelta = 9999999;   // set to something huge

      while (Count)
      {
        if (pCurFM->fsDefn & FM_DEFN_OUTLINE)
        {
          // this should not happen, since this is intended to be used
          // only for finding bitmap fonts
          Count--;
          continue;
        }
        // check if resolution is what we are looking for +/- some slop
        else if ( (pCurFM->sYDeviceRes == cyFontRes) &&
          (pCurFM->sXDeviceRes == cxFontRes) )
        {
          int Delta = abs((pCurFM->sNominalPointSize / 10) - _pointSize);
          if (Delta <= BestDelta)
          {
            BestDelta = Delta;
            pFM = pCurFM;
          }
        }
        Count--;
        if (Count)
          pCurFM++;
      // now set the font attributes to those of the best match
      _fat.lAveCharWidth = pFM->lAveCharWidth;
      _fat.lMaxBaselineExt = pFM->lMaxBaselineExt;

      SysDebug3(Text,"vFont::LoadBitmapFont %s CharWidth=%u (Delta=%u)\n",
        _fat.szFacename, pFM->lAveCharWidth, BestDelta);
      }
      DosFreeMem(pFM);
    }
  }
