/*
* Descent 3 
* Copyright (C) 2024 Parallax Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.

--- HISTORICAL COMMENTS FOLLOW ---

 * $Logfile: /DescentIII/Main/grtext/grfont.cpp $
 * $Revision: 25 $
 * $Date: 11/16/99 4:56p $
 * $Author: Samir $
 *
 *
 *
 * $Log: /DescentIII/Main/grtext/grfont.cpp $
 *
 * 25    11/16/99 4:56p Samir
 * upped max texture count per font.
 *
 * 24    11/16/99 3:18p Samir
 * added new data to font file and kept compatibility with D3 fonts:
 * tracking value.
 *
 * 23    8/10/99 5:11p Jeff
 * close open files
 *
 * 22    6/08/99 5:45p Samir
 * correctly take care of values about 128 when character passed to
 * grfont_GetCharWidth
 *
 * 21    4/24/99 8:41p Samir
 * fixed clipped text scaling probs.
 *
 * 20    4/17/99 6:16p Samir
 * added kerning and 4444 alphaed font support.
 *
 * 19    4/14/99 1:15a Jeff
 * fixed case mismatched #includes
 *
 * 18    4/01/99 5:23p Samir
 * Added function to get character info.
 *
 * 17    3/02/99 6:26p Samir
 * added font template width and height functions.
 *
 * 16    2/21/99 6:39p Samir
 * added function to get ascii value of font character.
 *
 * 15    10/16/98 1:54p Kevin
 * Changes for Demo Beta 4
 *
 * 14    5/22/98 12:44p Samir
 * fixed grayscale conversion bug.
 *
 * 13    5/15/98 5:36p Samir
 * reset references count of font when freeing.
 *
 * 12    5/08/98 5:23p Samir
 * added font brightness and grayscale caps.
 *
 * 11    5/01/98 3:09p Samir
 * properly translate monochrome fonts to 555 output.
 *
 * 10    4/27/98 3:46p Samir
 * scaling fonts.
 *
 * 9     4/24/98 8:00a Samir
 * hopefully fixed font pixel format translation to 555 problem.
 *
 * 8     4/23/98 6:38p Jason
 * made bitmaps use 1555 format
 *
 * 7     4/15/98 12:10p Samir
 * commented out unnecessary mprints.
 *
 * 6     3/31/98 3:49p Jason
 * added memory lib
 *
 * 5     1/12/98 7:03p Samir
 * Centering works fully.
 *
 * 4     1/12/98 5:24p Samir
 * Fixed font reading and created font spew test function.
 *
 * 3     1/02/98 12:53p Samir
 * Convert lowercase to uppercase if there are no lowercase chars in the
 * font.
 *
 * 2     12/29/97 5:48p Samir
 * Fixed problem with non clipping text rendering (a hack.)
 *
 * 1     12/29/97 3:24p Samir
 * Initial revision
 *
 * $NoKeywords: $
 */

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>

#include "bitmap.h"
#include "cfile.h"
#include "ddio.h"
#include "grtextlib.h"
#include "mem.h"
#include "pserror.h"
#include "renderer.h"

const int MAX_FONTS = 16, MAX_FONT_BITMAPS = 32;

#define FT_UPPERCASE 128

#define GRFONT_SURFACE_WIDTH 128
#define GRFONT_SURFACE_HEIGHT 128

#define BITS_TO_BYTES(_c) (((_c) + 7) >> 3)
#define BITS_TO_SHORTS(_c) (((_c) + 15) >> 4)

//////////////////////////////////////////////////////////////////////////////
//	Variables

struct tFontInfo {
  char filename[32];          // filename of font
  int references;             // number of references of that font
  int bmps[MAX_FONT_BITMAPS]; // font bitmap handles
  uint8_t *ch_u, *ch_v, *ch_w, *ch_h;
  int *ch_bmp;
  float *ch_uf, *ch_vf, *ch_wf, *ch_hf;
  tFontFileInfo font;
};

//	Font information stored here.
static tFontInfo Fonts[MAX_FONTS];
static bool Font_init = false;

#define GRFONT_SURFACE_WIDTH 128
#define GRFONT_SURFACE_HEIGHT 128

//	----------------------------------------------------------------------------
//	Macros for file io.
//	----------------------------------------------------------------------------

typedef CFILE *FONTFILE;

static inline int READ_FONT_INT(FONTFILE ffile);
static inline int16_t READ_FONT_SHORT(FONTFILE ffile);
static inline uint8_t READ_FONT_BYTE(FONTFILE ffile);
static inline int READ_FONT_DATA(FONTFILE ffile, void *buf, int size, int nelem);
static inline FONTFILE OPEN_FONT(char *filename);
static inline void CLOSE_FONT(FONTFILE ffile);

inline int READ_FONT_INT(FONTFILE ffile) { return cf_ReadInt(ffile); }

inline int16_t READ_FONT_SHORT(FONTFILE ffile) { return cf_ReadShort(ffile); }

inline uint8_t READ_FONT_BYTE(FONTFILE ffile) { return (uint8_t)cf_ReadByte(ffile); }

inline int READ_FONT_DATA(FONTFILE ffile, void *buf, int size, int nelem) {
  int i;

  i = cf_ReadBytes((uint8_t *)buf, size * nelem, ffile);

  ASSERT(i == (size * nelem));

  return i;
}

inline FONTFILE OPEN_FONT(const char *filename) {
  FONTFILE fp;
  int file_id;

  fp = (FONTFILE)cfopen(filename, "rb");
  if (!fp)
    return nullptr;

  file_id = READ_FONT_INT(fp);
  if (file_id != 0xfeedbaba)
    return (FONTFILE)(-1);

  return fp;
}

inline void CLOSE_FONT(FONTFILE ffile) { cfclose(ffile); }

typedef FILE *FONTFILE2;

static inline int WRITE_FONT_INT(FONTFILE2 ffile, int i);
static inline int WRITE_FONT_SHORT(FONTFILE2 ffile, int16_t s);
static inline int WRITE_FONT_BYTE(FONTFILE2 ffile, uint8_t c);
static inline int WRITE_FONT_DATA(FONTFILE2 ffile, void *buf, int size, int nelem);
static inline FONTFILE2 OPEN_FONT2(char *filename);
static inline void CLOSE_FONT2(FONTFILE2 ffile);

inline int WRITE_FONT_INT(FONTFILE2 ffile, int i) { return fwrite(&i, sizeof(i), 1, (FILE *)ffile); }

inline int WRITE_FONT_SHORT(FONTFILE2 ffile, int16_t s) { return fwrite(&s, sizeof(s), 1, (FILE *)ffile); }

inline int WRITE_FONT_BYTE(FONTFILE2 ffile, uint8_t c) { return fwrite(&c, sizeof(c), 1, (FILE *)ffile); }

inline int WRITE_FONT_DATA(FONTFILE2 ffile, void *buf, int size, int nelem) {
  int i;
  i = (int)fwrite(buf, size, nelem, (FILE *)ffile);

  ASSERT(i == nelem);

  return i;
}

inline FONTFILE2 OPEN_FONT2(const char *filename) {
  FONTFILE2 fp;

  fp = (FONTFILE2)fopen(filename, "wb");
  if (!fp)
    return nullptr;

  return fp;
}

inline void CLOSE_FONT2(FONTFILE2 ffile) { fclose((FILE *)ffile); }

//////////////////////////////////////////////////////////////////////////////
//	Functions

static void grfont_Close();
static void grfont_TranslateToBitmaps(int handle);
static void grfont_XlateMonoChar(int bmp_handle, int x, int y, int index, tFontFileInfo *ft, int width);
static void grfont_XlateColorChar(int bmp_handle, int x, int y, int index, tFontFileInfo *ft, int width);
static void grfont_XlateColorGrayChar(int bmp_handle, int x, int y, int index, tFontFileInfo *ft, int width);
static void grfont_ClearBitmap(int bmp_handle);

//	clears out font buffer.
void grfont_Reset() {
  int i;

  if (Font_init) {
    //	free all allocated fonts.
    grfont_Close();
  } else {
    atexit(grfont_Close);
  }

  for (i = 0; i < MAX_FONTS; i++)
    Fonts[i].references = 0;

  Font_init = true;
}

void grfont_Close() {
  int i;

  ASSERT(Font_init);

  for (i = 0; i < MAX_FONTS; i++)
    if (Fonts[i].references)
      grfont_Free(i);

  Font_init = false;
}

//	returns a handle to a loaded font.
int grfont_Load(const char *fname) {
  FONTFILE ff;
  tFontFileInfo fnt;
  char fontname[32];
  int num_char, i, handle = -1;

  ASSERT(Font_init);

  //	check if font is in list.  if so, just return the handle.

  //	find free slot in font list.
  for (i = 0; i < MAX_FONTS; i++) {
    if (Fonts[i].references == 0)
      break;
  }
  ASSERT(i < MAX_FONTS);

  handle = i;

  //	open file.
  ff = OPEN_FONT(fname);
  if (!ff) {
    return false;
  } else if (ff == (FONTFILE)0xffffffff) {
    mprintf(0, "Illegal font file: %s.\n", fname);
    return false;
  }

  //	read header information
  fnt.width = READ_FONT_SHORT(ff);
  fnt.height = READ_FONT_SHORT(ff);
  fnt.flags = READ_FONT_SHORT(ff);
  fnt.baseline = READ_FONT_SHORT(ff);
  fnt.min_ascii = READ_FONT_BYTE(ff);
  fnt.max_ascii = READ_FONT_BYTE(ff);
  READ_FONT_DATA(ff, fontname, 32, 1);

  // read new info
  if (fnt.flags & FT_FFI2) {
    fnt.ffi2.tracking = READ_FONT_SHORT(ff);
    READ_FONT_DATA(ff, fnt.ffi2.reserved, sizeof(fnt.ffi2.reserved), 1);
  } else {
    fnt.ffi2.tracking = 0;
  }

  fnt.brightness = ((fnt.baseline >> 8) / 10.0f);
/*
  mprintf(0, "%s font.\n", fname);
  mprintf(0, "  <ht %d>::<min %d>::<max %d>::<base %d>", fnt.height, fnt.min_ascii, fnt.max_ascii, fnt.baseline);
*/
  num_char = fnt.max_ascii - fnt.min_ascii + 1;

  if (fnt.max_ascii < 'a') {
    fnt.flags |= FT_UPPERCASE;
  }

  //	Read in all widths
  if (fnt.flags & FT_PROPORTIONAL) {
    fnt.char_widths = (uint8_t *)mem_malloc(sizeof(uint8_t) * num_char);
    for (i = 0; i < num_char; i++)
      fnt.char_widths[i] = (uint8_t)READ_FONT_SHORT(ff);
  } else {
    fnt.char_widths = nullptr;
  }

  //	Read in kerning data
  if (fnt.flags & FT_KERNED) {
    int n_pairs = (int)READ_FONT_SHORT(ff);
    fnt.kern_data = (uint8_t *)mem_malloc(sizeof(uint8_t) * 3 * (n_pairs + 1));
    for (i = 0; i < n_pairs; i++) {
      fnt.kern_data[i * 3] = READ_FONT_BYTE(ff);
      fnt.kern_data[i * 3 + 1] = READ_FONT_BYTE(ff);
      fnt.kern_data[i * 3 + 2] = READ_FONT_BYTE(ff);
    }
    fnt.kern_data[i * 3] = 255;
    fnt.kern_data[i * 3 + 1] = 255;
    fnt.kern_data[i * 3 + 2] = 0;
  } else {
    fnt.kern_data = nullptr;
  }

  //	Read in pixel data.
  //	for color fonts, read in byte count and then the data,
  //		generate character data pointer table
  //	for mono fonts, read in byte count, then the data, convert to bits and store
  //		generate character data pointer table
  int bytesize = READ_FONT_INT(ff);

  fnt.raw_data = (uint8_t *)mem_malloc(bytesize);
  fnt.char_data = (uint8_t **)mem_malloc(num_char * sizeof(uint8_t *));

  READ_FONT_DATA(ff, fnt.raw_data, bytesize, 1);

  if (fnt.flags & FT_COLOR) {
    int off = 0;
    //		mprintf(0, "::color");
    for (i = 0; i < num_char; i++) {
      fnt.char_data[i] = fnt.raw_data + off;
      if (fnt.flags & FT_PROPORTIONAL)
        off += (fnt.char_widths[i] * fnt.height * BITS_TO_BYTES(BPP_16));
      else
        off += (fnt.width * fnt.height * BITS_TO_BYTES(BPP_16));
    }
  } else { // Monochrome
    uint8_t *ptr = fnt.raw_data;
    //		mprintf(0, "::mono");
    for (i = 0; i < num_char; i++) {
      fnt.char_data[i] = ptr;
      if (fnt.flags & FT_PROPORTIONAL)
        ptr += BITS_TO_BYTES(fnt.char_widths[i]) * fnt.height;
      else
        ptr += BITS_TO_BYTES(fnt.width) * fnt.height;
    }
  }

  //	Then read in
  CLOSE_FONT(ff);

  //	mprintf(0, "\n");

  strcpy(Fonts[handle].filename, fname);
  Fonts[handle].references = 1;
  Fonts[handle].font = fnt;

  //	draw font to bitmaps, load into surfaces too.
  grfont_TranslateToBitmaps(handle);

  return handle;
}

//	frees a loaded font
void grfont_Free(int handle) {
  tFontInfo *ft;
  int i;

  ASSERT(Font_init);
  ASSERT(handle > -1 && handle < MAX_FONTS);

  //	delete font surface info.
  ft = &Fonts[handle];
  if (ft->ch_bmp)
    delete[] ft->ch_bmp;
  if (ft->ch_wf)
    delete[] ft->ch_wf;
  if (ft->ch_hf)
    delete[] ft->ch_hf;
  if (ft->ch_uf)
    delete[] ft->ch_uf;
  if (ft->ch_vf)
    delete[] ft->ch_vf;
  if (ft->ch_w)
    delete[] ft->ch_w;
  if (ft->ch_h)
    delete[] ft->ch_h;
  if (ft->ch_u)
    delete[] ft->ch_u;
  if (ft->ch_v)
    delete[] ft->ch_v;

  for (i = 0; i < MAX_FONT_BITMAPS; i++) {
    if (ft->bmps[i] != -1)
      bm_FreeBitmap(ft->bmps[i]);
  }

  //	delete font file info.
  if ((ft->font.flags & FT_KERNED) && ft->font.kern_data) {
    mem_free(ft->font.kern_data);
  }

  if (ft->font.flags & FT_PROPORTIONAL) {
    mem_free(ft->font.char_widths);
  }

  ft->references = 0;

  mem_free(ft->font.raw_data);
  mem_free(ft->font.char_data);
}

// loads a font template
bool grfont_LoadTemplate(const char *fname, tFontTemplate *ft) {
  FONTFILE ff;
  char fontname[32];
  int16_t ft_width, ft_height, ft_flags, ft_minasc, ft_maxasc, num_char, i;
  tFontFileInfo2 ffi2;

  //	open file.
  ff = OPEN_FONT(fname);
  if (!ff) {
    Error("Unable to open font %s.\n", fname);
  } else if (ff == (FONTFILE)0xffffffff) {
    Error("Illegal font file: %s.\n", fname);
  }

  //	read header information
  ft_width = READ_FONT_SHORT(ff);
  ft_height = READ_FONT_SHORT(ff);
  ft_flags = READ_FONT_SHORT(ff);
  READ_FONT_SHORT(ff); // skip baseline value (HACKED FOR BRIGHTNESS)
  ft_minasc = READ_FONT_BYTE(ff);
  ft_maxasc = READ_FONT_BYTE(ff);
  READ_FONT_DATA(ff, fontname, 32, 1); // read namae

  // read ffi2 font info
  if (ft_flags & FT_FFI2) {
    ffi2.tracking = READ_FONT_SHORT(ff);
    READ_FONT_DATA(ff, &ffi2.reserved, sizeof(ffi2.reserved), 1);
  }

  num_char = ft_maxasc - ft_minasc + 1;

  //	Read in all widths
  if (ft_flags & FT_PROPORTIONAL) {
    ft->ch_widths = (uint8_t *)mem_malloc(num_char);
    for (i = 0; i < num_char; i++)
      ft->ch_widths[i] = (uint8_t)READ_FONT_SHORT(ff);
  } else {
    ft->ch_widths = nullptr;
  }

  if (ft_flags & FT_KERNED) {
    int n_pairs = (int)READ_FONT_SHORT(ff);
    ft->kern_data = (uint8_t *)mem_malloc(sizeof(uint8_t) * 3 * (n_pairs + 1));
    for (i = 0; i < n_pairs; i++) {
      ft->kern_data[i * 3] = READ_FONT_BYTE(ff);
      ft->kern_data[i * 3 + 1] = READ_FONT_BYTE(ff);
      ft->kern_data[i * 3 + 2] = READ_FONT_BYTE(ff);
    }
    ft->kern_data[i * 3] = 255;
    ft->kern_data[i * 3 + 1] = 255;
    ft->kern_data[i * 3 + 2] = 0;
  } else {
    ft->kern_data = NULL;
  }

  ft->ch_height = (uint8_t)ft_height;
  ft->ch_maxwidth = (uint8_t)ft_width;
  ft->max_ascii = ft_maxasc;
  ft->min_ascii = ft_minasc;
  ft->proportional = (ft_flags & FT_PROPORTIONAL) ? true : false;
  ft->uppercase = (ft_maxasc < 'a');
  ft->monochromatic = (ft_flags & FT_COLOR) ? false : true;
  ft->newstyle = (ft_flags & FT_FMT4444) ? true : false;
  ft->ffi2 = (ft_flags & FT_FFI2) ? true : false;

  ft->ch_tracking = (int8_t)((ft_flags & FT_FFI2) ? ffi2.tracking : 0);

  CLOSE_FONT(ff);

  return true;
}

// frees a font template
void grfont_FreeTemplate(tFontTemplate *ft) {
  if (ft->kern_data) {
    mem_free(ft->kern_data);
    ft->kern_data = nullptr;
  }
  if (ft->ch_widths) {
    mem_free(ft->ch_widths);
    ft->ch_widths = nullptr;
  }
}

// MTS: unused?
// sets a template to a font, be careful.
bool grfont_SetTemplate(const char *pathname, const tFontTemplate *ft) {
  // okay, load the font manually, set the template members, then save it out.
  tFontFileInfo fnt;
  char tempstr[32];
  FONTFILE ffin;
  FONTFILE2 ffout;
  int num_char, i;

  tFontFileInfo2 ffi2;

  ffin = OPEN_FONT(pathname);
  if (!ffin) {
    return false;
  } else if (ffin == (FONTFILE)0xffffffff) {
    mprintf(0, "Illegal font file %s\n", pathname);
    return false;
  }

  //	read header information
  fnt.width = READ_FONT_SHORT(ffin);
  fnt.height = READ_FONT_SHORT(ffin);
  fnt.flags = READ_FONT_SHORT(ffin);
  fnt.baseline = READ_FONT_SHORT(ffin);
  fnt.min_ascii = READ_FONT_BYTE(ffin);
  fnt.max_ascii = READ_FONT_BYTE(ffin);
  READ_FONT_DATA(ffin, tempstr, 32, 1);

  // read ffi2 font info
  if (fnt.flags & FT_FFI2) {
    ffi2.tracking = READ_FONT_SHORT(ffin);
    READ_FONT_DATA(ffin, &ffi2.reserved, sizeof(ffi2.reserved), 1);
  }

  fnt.brightness = ((fnt.baseline >> 8) / 10.0f);

  num_char = fnt.max_ascii - fnt.min_ascii + 1;

  //	Read in all widths
  if (fnt.flags & FT_PROPORTIONAL) {
    fnt.char_widths = (uint8_t *)mem_malloc(sizeof(uint8_t) * num_char);
    for (i = 0; i < num_char; i++)
      fnt.char_widths[i] = (uint8_t)READ_FONT_SHORT(ffin);
  } else {
    fnt.char_widths = nullptr;
  }

  //	Read in kerning data
  if (fnt.flags & FT_KERNED) {
    int n_pairs = (int)READ_FONT_SHORT(ffin);
    fnt.kern_data = (uint8_t *)mem_malloc(sizeof(uint8_t) * 3 * (n_pairs + 1));
    for (i = 0; i < n_pairs; i++) {
      fnt.kern_data[i * 3] = READ_FONT_BYTE(ffin);
      fnt.kern_data[i * 3 + 1] = READ_FONT_BYTE(ffin);
      fnt.kern_data[i * 3 + 2] = READ_FONT_BYTE(ffin);
    }
    fnt.kern_data[i * 3] = 255;
    fnt.kern_data[i * 3 + 1] = 255;
    fnt.kern_data[i * 3 + 2] = 0;
  } else {
    fnt.kern_data = nullptr;
  }

  //	Read in pixel data.
  int bytesize = READ_FONT_INT(ffin);

  fnt.raw_data = (uint8_t *)mem_malloc(bytesize);

  READ_FONT_DATA(ffin, fnt.raw_data, bytesize, 1);

  CLOSE_FONT(ffin);

  // set template values
  fnt.width = ft->ch_maxwidth;
  fnt.height = ft->ch_height;
  fnt.flags = (ft->proportional ? FT_PROPORTIONAL : 0) + ((!ft->monochromatic) ? FT_COLOR : 0) +
              (ft->kern_data ? FT_KERNED : 0) + (ft->newstyle ? FT_FMT4444 : 0);
  fnt.min_ascii = (uint8_t)ft->min_ascii;
  fnt.max_ascii = (uint8_t)ft->max_ascii;

  if (ft->ffi2)
    fnt.flags |= FT_FFI2;

  if (fnt.kern_data) {
    mem_free(fnt.kern_data);
  }
  if (fnt.char_widths) {
    mem_free(fnt.char_widths);
  }
  fnt.kern_data = ft->kern_data;
  fnt.char_widths = ft->ch_widths;

  // write out font.
  ffout = OPEN_FONT2(pathname);
  if (!ffout) {
    return false;
  }

  //	Write file id.
  WRITE_FONT_INT(ffout, 0xfeedbaba);
  WRITE_FONT_SHORT(ffout, fnt.width);
  WRITE_FONT_SHORT(ffout, fnt.height);
  WRITE_FONT_SHORT(ffout, fnt.flags);
  WRITE_FONT_SHORT(ffout, fnt.baseline);
  WRITE_FONT_BYTE(ffout, fnt.min_ascii);
  WRITE_FONT_BYTE(ffout, fnt.max_ascii);
  WRITE_FONT_DATA(ffout, tempstr, 32, 1);

  if (fnt.flags & FT_FFI2) {
    WRITE_FONT_SHORT(ffout, (int16_t)ft->ch_tracking);
    WRITE_FONT_DATA(ffout, ffi2.reserved, sizeof(ffi2.reserved), 1);
  }

  num_char = ft->max_ascii - ft->min_ascii + 1;

  //	Write widths now if necessary.(FT_PROPORTIONAL)
  if (fnt.flags & FT_PROPORTIONAL) {
    for (i = 0; i < num_char; i++)
      WRITE_FONT_SHORT(ffout, (int16_t)fnt.char_widths[i]);
  }

  if (fnt.flags & FT_KERNED) {
    // iterate new kerning info bytes
    uint8_t *ch = fnt.kern_data;
    int n_bytes = 0, n_pairs;
    while (ch[n_bytes] != 255) {
      n_bytes += 3;
    }

    ASSERT((n_bytes % 3) == 0);
    n_pairs = n_bytes / 3;
    WRITE_FONT_SHORT(ffout, (int16_t)n_pairs);

    for (i = 0; i < n_pairs; i++) {
      WRITE_FONT_BYTE(ffout, ch[i * 3]);
      WRITE_FONT_BYTE(ffout, ch[i * 3 + 1]);
      WRITE_FONT_BYTE(ffout, ch[i * 3 + 2]);
    }
  }

  WRITE_FONT_INT(ffout, bytesize);
  WRITE_FONT_DATA(ffout, fnt.raw_data, bytesize, 1);

  CLOSE_FONT2(ffout);

  // free raw data loaded, don't free kern and widths, already freed, template versions remain
  mem_free(fnt.raw_data);

  return true;
}

// MTS: Unused?
// sets a font's template without saving...
bool grfont_SetKerning(int font, uint8_t *kern_data) {
  tFontInfo *oldft = &Fonts[font];
  int n_pairs = 0;

  // reset kerning...
  if (oldft->font.kern_data) {
    mem_free(oldft->font.kern_data);
    oldft->font.kern_data = nullptr;
  }

  if (kern_data) {
    while (kern_data[n_pairs * 3] != 255)
      n_pairs++;

    oldft->font.kern_data = (uint8_t *)mem_malloc((n_pairs + 1) * 3 * sizeof(uint8_t));
    n_pairs = 0;
    while (kern_data[n_pairs * 3] != 255) {
      oldft->font.kern_data[n_pairs * 3] = kern_data[n_pairs * 3];
      oldft->font.kern_data[n_pairs * 3 + 1] = kern_data[n_pairs * 3 + 1];
      oldft->font.kern_data[n_pairs * 3 + 2] = kern_data[n_pairs * 3 + 2];
      n_pairs++;
    }
    oldft->font.kern_data[n_pairs * 3] = 255;
    oldft->font.kern_data[n_pairs * 3 + 1] = 255;
    oldft->font.kern_data[n_pairs * 3 + 2] = 0;
  }

  // we're not going to reset other stuff for now... just kerning!
  return true;
}

// MTS: Unused?
// sets a font's tracking
bool grfont_SetTracking(int font, int tracking) {
  ASSERT(font >= 0 && font < MAX_FONTS);

  tFontInfo *oldft = &Fonts[font];

  oldft->font.ffi2.tracking = (int16_t)tracking;

  return false;
}

int grfont_GetTracking(int font) {
  ASSERT(font >= 0 && font < MAX_FONTS);
  //	if (Fonts[font].font.flags & FT_FFI2) {
  return (int)Fonts[font].font.ffi2.tracking;
  //	}

  //	return 0;
}

//	render a character
int grfont_BltChar(int font, tCharBlt *cbi) {
  ASSERT(font > -1 && font < MAX_FONTS);

  tFontInfo *ft = &Fonts[font];

  //	save current x and get this font
  if (cbi->ch > ft->font.max_ascii && (ft->font.flags & FT_UPPERCASE)) {
    cbi->ch = toupper(cbi->ch);
  }

  if ((cbi->ch < ft->font.min_ascii) || (cbi->ch > ft->font.max_ascii))
    return (cbi->x + 1);

  cbi->ch = cbi->ch - ft->font.min_ascii;

  if (!cbi->clipped) {

    if (ft->font.flags & FT_PROPORTIONAL)
      cbi->sw = (int)(ft->font.char_widths[cbi->ch]);
    else
      cbi->sw = (int)(ft->font.width);
    cbi->sh = (int)(ft->font.height);
    cbi->sx = 0;
    cbi->sy = 0;

    rend_DrawFontCharacter(ft->bmps[ft->ch_bmp[cbi->ch]], cbi->x, cbi->y, (int)(cbi->x + cbi->sw * cbi->dsw),
                           (int)(cbi->y + cbi->sh * cbi->dsh), ft->ch_uf[cbi->ch], ft->ch_vf[cbi->ch],
                           ft->ch_wf[cbi->ch], ft->ch_hf[cbi->ch]);
    return (cbi->x + (int)(cbi->sw * cbi->dsw));
    //@@		rend_DrawFontCharacter (ft->bmps[ft->ch_bmp[cbi->ch]], cbi->x,cbi->y,
    //@@ cbi->x+ft->ch_w[cbi->ch], cbi->y+ft->ch_h[cbi->ch],
    //@@											ft->ch_uf[cbi->ch],
    // ft->ch_vf[cbi->ch],
    //@@											ft->ch_wf[cbi->ch],
    // ft->ch_hf[cbi->ch]);
    //@@		return (cbi->x + ft->ch_w[cbi->ch] - 1);
  } else {
    rend_DrawFontCharacter(
        ft->bmps[ft->ch_bmp[cbi->ch]], cbi->x, cbi->y,
        (int)(cbi->x + cbi->sw), // don't scale since these values are already scaled
        (int)(cbi->y + cbi->sh), ft->ch_uf[cbi->ch] + (((float)cbi->sx) / ((float)GRFONT_SURFACE_WIDTH)),
        ft->ch_vf[cbi->ch] + (((float)cbi->sy) / ((float)GRFONT_SURFACE_HEIGHT)),
        ((float)cbi->sw) / ((float)GRFONT_SURFACE_WIDTH), ((float)cbi->sh) / ((float)GRFONT_SURFACE_HEIGHT));
    return (cbi->x + (int)(cbi->sw)); // scaled value already
  }
}

//	returns a character's width
int grfont_GetCharWidth(int font, int ch) {
  tFontFileInfo *ft;

  ASSERT(font > -1 && font < MAX_FONTS);

  ft = &Fonts[font].font;
  ch = (int)((uint8_t)ch);

  if (ch > ft->max_ascii && (ft->flags & FT_UPPERCASE)) {
    ch = toupper(ch);
  }

  if (ch < ft->min_ascii || ch > ft->max_ascii)
    return 0;
  else if (ft->flags & FT_PROPORTIONAL)
    return ft->char_widths[ch - ft->min_ascii];
  else
    return ft->width;
}

//	returns a font's height
int grfont_GetHeight(int font) {
  ASSERT(font > -1 && font < MAX_FONTS);
  return Fonts[font].font.height;
}

//	translates raw font data to bitmaps.
void grfont_TranslateToBitmaps(int handle) {
  tFontFileInfo *fntfile;
  tFontInfo *fnt;
  int i;

  fnt = &Fonts[handle];
  fntfile = &Fonts[handle].font;

  //	start creating font surfaces, map these surfaces onto bitmaps created via bitmap library
  //	this is needed for the renderer library.
  //	create a 128x128 bitmap.
  //	draw each character into bitmap until we need to create another
  //	surface.
  uint8_t u = 0, v = 0, w;
  int ch, num_ch;
  uint8_t surf_index = 0;

  num_ch = fntfile->max_ascii - fntfile->min_ascii + 1;

  //	initialize memory
  fnt->ch_w = new uint8_t[num_ch];
  fnt->ch_h = new uint8_t[num_ch];
  fnt->ch_u = new uint8_t[num_ch];
  fnt->ch_v = new uint8_t[num_ch];
  fnt->ch_bmp = new int[num_ch];
  fnt->ch_uf = new float[num_ch];
  fnt->ch_vf = new float[num_ch];
  fnt->ch_wf = new float[num_ch];
  fnt->ch_hf = new float[num_ch];

  for (i = 0; i < MAX_FONT_BITMAPS; i++)
    fnt->bmps[i] = -1;

  fnt->bmps[surf_index] = bm_AllocBitmap(GRFONT_SURFACE_WIDTH, GRFONT_SURFACE_HEIGHT, 0);
  if (fnt->bmps[surf_index] == -1 || fnt->bmps[surf_index] == BAD_BITMAP_HANDLE)
    Error("TranslateToBitmaps <Bitmap allocation error>");
  if (fntfile->flags & FT_FMT4444) {
    GameBitmaps[fnt->bmps[surf_index]].format = BITMAP_FORMAT_4444;
  }

  grfont_ClearBitmap(fnt->bmps[surf_index]);
  surf_index++;

  for (ch = 0; ch < num_ch; ch++) {
    if (fntfile->flags & FT_PROPORTIONAL)
      w = (int)fntfile->char_widths[ch];
    else
      w = (int)fntfile->width;

    if ((u + w) > GRFONT_SURFACE_WIDTH) {
      u = 0;
      v += fntfile->height;
      if ((v + fntfile->height) > GRFONT_SURFACE_HEIGHT) {
        if (surf_index == MAX_FONT_BITMAPS)
          Int3();
        fnt->bmps[surf_index] = bm_AllocBitmap(GRFONT_SURFACE_WIDTH, GRFONT_SURFACE_HEIGHT, 0);
        if (fnt->bmps[surf_index] == -1 || fnt->bmps[surf_index] == BAD_BITMAP_HANDLE)
          Error("TranslateToBitmaps <Bitmap allocation error>");
        if (fntfile->flags & FT_FMT4444) {
          GameBitmaps[fnt->bmps[surf_index]].format = BITMAP_FORMAT_4444;
        }
        grfont_ClearBitmap(fnt->bmps[surf_index]);
        surf_index++;
        v = 0;
      }
    }

    // blt each character
    if (fntfile->flags & FT_COLOR) {
      if (fntfile->flags & FT_GRADIENT)
        grfont_XlateColorGrayChar(fnt->bmps[surf_index - 1], u, v, ch, fntfile, w);
      //			else
      grfont_XlateColorChar(fnt->bmps[surf_index - 1], u, v, ch, fntfile, w);
    } else { //	font monochrome, convert bits to pixels
      grfont_XlateMonoChar(fnt->bmps[surf_index - 1], u, v, ch, fntfile, w);
    }

    fnt->ch_h[ch] = (uint8_t)fntfile->height;
    fnt->ch_w[ch] = w;
    fnt->ch_u[ch] = u;
    fnt->ch_v[ch] = v;
    fnt->ch_bmp[ch] = surf_index - 1;
    fnt->ch_hf[ch] = ((float)fntfile->height) / ((float)GRFONT_SURFACE_HEIGHT);
    fnt->ch_wf[ch] = ((float)w) / ((float)GRFONT_SURFACE_WIDTH);
    fnt->ch_uf[ch] = ((float)u) / ((float)GRFONT_SURFACE_WIDTH);
    fnt->ch_vf[ch] = ((float)v) / ((float)GRFONT_SURFACE_HEIGHT);

    //	check to adjust uv's if we are outside surface.
    u += w;
  }
}

//	Font translation routines
void grfont_XlateMonoChar(int bmp_handle, int x, int y, int index, tFontFileInfo *ft, int width) {
  int row, col; // byte width of char
  uint8_t bit_mask = 0, byte = 0;
  uint8_t *fp;

  fp = ft->char_data[index];

  /*	draw one-bit one color. */
  uint16_t *dest_ptr;
  uint16_t col_w = GR_COLOR_TO_16(GR_RGB(255, 255, 255));
  int rowsize_w;

  dest_ptr = bm_data(bmp_handle, 0);
  rowsize_w = bm_rowsize(bmp_handle, 0) >> 1;
  dest_ptr += (y * rowsize_w) + x;

  for (row = 0; row < ft->height; row++) {
    bit_mask = 0;
    for (col = 0; col < width; col++) {
      if (bit_mask == 0) {
        byte = *fp++;
        bit_mask = 0x80;
      }

      if (byte & bit_mask)
        dest_ptr[col] = (col_w | OPAQUE_FLAG);

      bit_mask >>= 1;
    }
    dest_ptr += rowsize_w;
  }
}

void grfont_XlateColorChar(int bmp_handle, int x, int y, int index, tFontFileInfo *ft, int width) {
  /*	16-bit copy from source bitmap to destination surface just created and
          locked
          This function performs scaling if the source width and height don't match
          that of the destinations - JL
  */
  uint16_t *dptr, *sptr;
  int row, col;
  int rowsize_w;

  dptr = bm_data(bmp_handle, 0);
  sptr = (uint16_t *)ft->char_data[index];
  rowsize_w = bm_rowsize(bmp_handle, 0) / 2;

  // height = std::min(h, ddsfObj.h);
  // width = std::min(w, ddsfObj.w);
  dptr = dptr + (y * rowsize_w);

  if (ft->flags & FT_FMT4444) {
    for (row = 0; row < ft->height; row++) {
      for (col = 0; col < width; col++) {
        dptr[x + col] = *(sptr++);
      }
      dptr += rowsize_w;
    }
  } else {
    // old style
    for (row = 0; row < ft->height; row++) {
      for (col = 0; col < width; col++) {
        uint16_t col565 = *(sptr++);
        if (col565 == 0x07e0)
          dptr[x + col] = NEW_TRANSPARENT_COLOR;
        else
          dptr[x + col] = (((col565 & 0xf800) >> 1) | ((col565 & 0x07c0) >> 1) | (col565 & 0x001f)) | OPAQUE_FLAG;
      }
      dptr += rowsize_w;
    }
  }
}

void grfont_XlateColorGrayChar(int bmp_handle, int x, int y, int index, tFontFileInfo *ft, int width) {
  /*	16-bit copy from source bitmap to destination surface just created and
          locked
          This function performs scaling if the source width and height don't match
          that of the destinations - JL
  */
  uint16_t *dptr, *sptr;
  int row, col;
  int rowsize_w;

  dptr = bm_data(bmp_handle, 0);
  sptr = (uint16_t *)ft->char_data[index];
  rowsize_w = bm_rowsize(bmp_handle, 0) / 2;

  // height = std::min(h, ddsfObj.h);
  // width = std::min(w, ddsfObj.w);
  dptr = dptr + (y * rowsize_w);

  float recip32 = 1.0f / 32.0f;

  for (row = 0; row < ft->height; row++) {
    for (col = 0; col < width; col++) {
      uint16_t col565 = *(sptr++);
      if (col565 == 0x07e0)
        dptr[x + col] = NEW_TRANSPARENT_COLOR;
      else {
        uint8_t r = (uint8_t)((col565 & 0xf800) >> 11);
        uint8_t g = (uint8_t)((col565 & 0x07c0) >> 6);
        uint8_t b = (uint8_t)(col565 & 0x001f);
        float brightness = ((r * 0.30f) + (g * 0.59f) + (b * 0.11f)) * recip32;
        uint8_t elem = (uint8_t)(255 * brightness * ft->brightness);
        if ((brightness * ft->brightness) > 1.0f)
          elem = 255;
        dptr[x + col] = GR_RGB16(elem, elem, elem) | OPAQUE_FLAG;
      }
    }
    dptr += rowsize_w;
  }
}

void grfont_ClearBitmap(int bmp_handle) {
  int dx, dy;
  int rowsize_w = bm_rowsize(bmp_handle, 0) / 2;
  uint16_t *bmpdata = bm_data(bmp_handle, 0);

  for (dy = 0; dy < bm_h(bmp_handle, 0); dy++) {
    for (dx = 0; dx < rowsize_w; dx++)
      bmpdata[dx] = NEW_TRANSPARENT_COLOR;

    bmpdata += rowsize_w;
  }
}

#ifndef RELEASE
void grfont_Spew(int font, int x, int y) {
  int i;
  tFontInfo *fnt;

  fnt = &Fonts[font];

  for (i = 0; i < MAX_FONT_BITMAPS; i++)
    if (fnt->bmps[i] > -1)
      rend_DrawScaledBitmap(i * GRFONT_SURFACE_WIDTH, 0, (i + 1) * GRFONT_SURFACE_WIDTH, GRFONT_SURFACE_HEIGHT,
                            fnt->bmps[i], 0, 0, 1.0, 1.0);
}
#endif

// returns character equivalent in font (converts lowercase to uppercase if no lowercase version avail)
// similar to ddio_KeyToAscii
int grfont_KeyToAscii(int font, int key) {
  key = ddio_KeyToAscii(key);

  if (font >= 0 && font < MAX_FONTS) {
    tFontInfo *ft = &Fonts[font];

    //	save current x and get this font
    if (key > ft->font.max_ascii && (ft->font.flags & FT_UPPERCASE)) {
      return toupper(key);
    }
  }

  return key;
}

// MTS: Unused?
// returns the raw bitmap data for a character in a font, its width and height
// returned data should be in 565 hicolor format if (*mono) is false.  if (*mono) is true,
//	then a bitmask will be returned, and you should treat a bit as a pixel.
uint16_t *grfont_GetRawCharacterData(int font, int ch, int *w, int *h, bool *mono) {
  tFontFileInfo *fntfile;
  tFontInfo *fnt;

  ASSERT(font > -1 && font < MAX_FONTS);

  fnt = &Fonts[font];
  fntfile = &fnt->font;

  if (ch > fnt->font.max_ascii && (fntfile->flags & FT_UPPERCASE)) {
    ch = toupper(ch);
  }

  if ((ch < fnt->font.min_ascii) || (ch > fnt->font.max_ascii))
    return nullptr;

  ch = ch - fnt->font.min_ascii;
  *mono = !(fnt->font.flags & FT_COLOR);

  if (fnt->font.flags & FT_PROPORTIONAL) {
    *w = (int)(fnt->font.char_widths[ch]);
  } else {
    *w = (int)(fnt->font.width);
  }

  *h = fnt->font.height;

  return (uint16_t *)fnt->font.char_data[ch];
}

//	returns a character's width
int grfont_GetKernedSpacing(int font, int ch1, int ch2) {
  tFontFileInfo *ft;

  ASSERT(font > -1 && font < MAX_FONTS);

  ft = &Fonts[font].font;

  if (ft->kern_data) {
    uint8_t *kern = ft->kern_data;
    while (kern[0] != 255) {
      if (ch1 == kern[0] && ch2 == kern[1]) {
        return (int)((int8_t)kern[2]);
      }
      kern += 3;
    }
  }
  return 0;
}

//	returns a character's width
int grfont_GetKernedSpacingTemp(const tFontTemplate *ft, int ch1, int ch2) {
  if (ft->kern_data) {
    uint8_t *kern = ft->kern_data;
    while (kern[0] != 255) {
      if (ch1 == kern[0] && ch2 == kern[1]) {
        return (int)((int8_t)kern[2]);
      }
      kern += 3;
    }
  }
  return 0;
}
