/* Copyright 2022-2026 Kurt Nienhaus
 *
 * This file is part of VgaGames.
 * VgaGames 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 2 of the License, or
 * (at your option) any later version.
 * VgaGames 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 VgaGames.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include "font.h"

void init_font(void);
void dest_font(void);

struct VG_Font * vg4_font_loaddefault(void);
void vg4_font_gap_remove(const char *, int, int, VG_BOOL, int *, int);

static void font_path(const char *);
static struct VG_Font * font_load(const char *);
static void font_destroy(struct VG_Font *);
static void * font_destroy_doit(struct VG_Font *);
static void font_destroyall(void);
static const char * font_getname(const struct VG_Font *);
static void font_setdefault(const char *);
static void font_getdefault(char *, size_t);
static void font_maxsize(const struct VG_Font *, int *, int *);
static void font_gaps(const struct VG_Font *, int *, int *);
static int font_getchars(const struct VG_Font *, struct VG_Fontchar **);
static void font_dump(FILE *);

static int cmp_chars(const void *, const void *);


/* set functions */
void
init_font(void)
{
  *vg4data.lists.font.userdir = '\0';
  *vg4data.lists.font.dflfont = '\0';
  vg4data.lists.font.hash = vg4_hash_create_nolist();
  vg4->font->path = font_path;
  vg4->font->load = font_load;
  vg4->font->destroy = font_destroy;
  vg4->font->destroyall = font_destroyall;
  vg4->font->getname = font_getname;
  vg4->font->setdefault = font_setdefault;
  vg4->font->getdefault = font_getdefault;
  vg4->font->maxsize = font_maxsize;
  vg4->font->gaps = font_gaps;
  vg4->font->getchars = font_getchars;
  vg4->font->dump = font_dump;
  init_font_text();
} /* Ende init_font */

void
dest_font(void)
{
  vg4->font->destroyall();
  *vg4data.lists.font.userdir = '\0';
  *vg4data.lists.font.dflfont = '\0';
  vg4->hash->destroy(vg4data.lists.font.hash);
  vg4data.lists.font.hash = NULL;
} /* Ende dest_font */


/* vg4_font_loaddefault: return default font for window-size */
struct VG_Font *
vg4_font_loaddefault(void)
{
  struct VG_Font *fntp = NULL;

  if (*vg4data.lists.font.dflfont != '\0') {
    fntp = vg4->font->load(vg4data.lists.font.dflfont);
    if (fntp != NULL) { return fntp; }
  }

  if (vg4data.lists.window.wsize == VG_WINDOW_SIZE_LOW) {
    fntp = vg4->font->load("sys:low");
  } else if (vg4data.lists.window.wsize == VG_WINDOW_SIZE_HIGH) {
    fntp = vg4->font->load("sys:high");
  } else {
    fntp = vg4->font->load("sys:high");
  }

  return fntp;
} /* Ende vg4_font_loaddefault */


/* vg4_font_gap_remove: remove empty boundarys and set distances */
void
vg4_font_gap_remove(const char *fontdir, int dist_w, int dist_h, VG_BOOL fullwidth, int *sepc, int sepi)
{
  const char *fontname;
  struct VG_Font *fntp;
  char userdir[sizeof(vg4data.lists.font.userdir)];

  if (fontdir == NULL) { pwarn("no font-directory passed\n"); return; }

  if (dist_w < 0) { dist_w = 0; }
  if (dist_h < 0) { dist_h = 0; }

  memmove(userdir, vg4data.lists.font.userdir, sizeof(userdir));
  vg4data.lists.font.userdir[0] = '.';
  vg4data.lists.font.userdir[1] = '\0';

  if ((fontname = strrchr(fontdir, '/')) == NULL) { fontname = fontdir; } else { fontname++; }
  fntp = (struct VG_Font *)vg4->hash->get(vg4data.lists.font.hash, fontname, NULL);
  if (fntp == NULL) {
    fontname = NULL;
    fntp = vg4->font->load(fontdir);
    if (fntp == NULL) {
      memmove(vg4data.lists.font.userdir, userdir, sizeof(userdir));
      return;
    }
  }

  { void *vpos;
    const char *key;
    struct vgi_font *vgft;
    int w1, h1;
    unsigned char *dptr, *dnptr, *ndata;
    int gap_top, gap_bottom, gap_left, gap_right, gap1, gap2;
    int nwidth, nheight, maxwidth, maxheight, plusw, plusm;
    int dist_wd, dist_wm;
    char fontfile[VGI_PATHSIZE], sepspace[8];

    /* calculate empty border lines of all characters and the max.width */

    gap_top = gap_bottom = -1;
    maxwidth = maxheight = 0;

    dist_wd = dist_w / 2;
    dist_wm = dist_w % 2;

    vpos = NULL;
    for (key = vg4->hash->list(fntp->hfont, &vpos);
         vpos != NULL;
         key = vg4->hash->list(fntp->hfont, &vpos)) {
      vgft = (struct vgi_font *)vg4->hash->get(fntp->hfont, key, NULL);
      if (vgft == NULL || vgft->data == NULL) { continue; }

      /* top empty lines */
      gap1 = 0;
      for (h1 = 0; h1 < vgft->h; h1++) {
        dptr = &vgft->data[h1 * vgft->w];
        for (w1 = 0; w1 < vgft->w; w1++) {
          if (*dptr++) { break; }
        }
        if (w1 < vgft->w) { break; }
        gap1++;
      }
      if (gap1 == vgft->h) {  /* remove empty character */
        snprintf(fontfile, sizeof(fontfile), "%s/%s.pbm", fontdir, key);
        unlink(fontfile);
        vpos = vg4->hash->remove(fntp->hfont, key);
        continue;
      }
      if (gap_top < 0 || gap1 < gap_top) { gap_top = gap1; }

      /* bottom empty lines */
      gap2 = 0;
      for (h1 = vgft->h - 1; h1 >= 0; h1--) {
        dptr = &vgft->data[h1 * vgft->w];
        for (w1 = 0; w1 < vgft->w; w1++) {
          if (*dptr++) { break; }
        }
        if (w1 < vgft->w) { break; }
        gap2++;
      }
      if (gap_bottom < 0 || gap2 < gap_bottom) { gap_bottom = gap2; }

      /* left empty columns */
      gap1 = 0;
      for (w1 = 0; w1 < vgft->w; w1++) {
        dptr = &vgft->data[w1];
        for (h1 = 0; h1 < vgft->h; h1++) {
          if (*dptr) { break; }
          dptr += vgft->w;
        }
        if (h1 < vgft->h) { break; }
        gap1++;
      }

      /* right empty columns */
      gap2 = 0;
      for (w1 = vgft->w - 1; w1 >= 0; w1--) {
        dptr = &vgft->data[w1];
        for (h1 = 0; h1 < vgft->h; h1++) {
          if (*dptr) { break; }
          dptr += vgft->w;
        }
        if (h1 < vgft->h) { break; }
        gap2++;
      }

      /* max. width */
      nwidth = vgft->w - gap1 - gap2;
      if (maxwidth < nwidth) { maxwidth = nwidth; }
    }
    if (gap_top < 0 || gap_bottom < 0) { goto grem_end; }

    /* truncate to new font-data */

    vpos = NULL;
    for (key = vg4->hash->list(fntp->hfont, &vpos);
         vpos != NULL;
         key = vg4->hash->list(fntp->hfont, &vpos)) {
      vgft = (struct vgi_font *)vg4->hash->get(fntp->hfont, key, NULL);
      if (vgft == NULL || vgft->data == NULL) { continue; }

      /* calculate left and right empty columns of this character */

      gap_left = gap_right = 0;

      for (w1 = 0; w1 < vgft->w; w1++) {
        dptr = &vgft->data[w1];
        for (h1 = 0; h1 < vgft->h; h1++) {
          if (*dptr) { break; }
          dptr += vgft->w;
        }
        if (h1 < vgft->h) { break; }
        gap_left++;
      }
      if (gap_left < vgft->w) {
        for (w1 = vgft->w - 1; w1 >= 0; w1--) {
          dptr = &vgft->data[w1];
          for (h1 = 0; h1 < vgft->h; h1++) {
            if (*dptr) { break; }
            dptr += vgft->w;
          }
          if (h1 < vgft->h) { break; }
          gap_right++;
        }
      } else {
        gap_left = gap_right = 0;
      }

      /* create new font-data */

      nwidth = vgft->w - gap_left - gap_right;
      if (fullwidth) {
        plusw = (maxwidth - nwidth) / 2;
        plusm = (maxwidth - nwidth) % 2;
      } else {
        plusw = plusm = 0;
      }
      nwidth += (plusw + plusw + plusm + dist_w);
      nheight = vgft->h - gap_top - gap_bottom + dist_h;
      ndata = SML3_calloc(nwidth * nheight, sizeof(*ndata));

      dnptr = ndata + (dist_h / 2 * nwidth);
      for (h1 = gap_top; h1 < vgft->h - gap_bottom; h1++) {
        dptr = &vgft->data[h1 * vgft->w];
        dnptr += plusw + dist_wd;
        for (w1 = gap_left; w1 < vgft->w - gap_right; w1++) {
          *dnptr++ = dptr[w1];
        }
        dnptr += (plusw + plusm + dist_wd + dist_wm);
      }

      free(vgft->data);
      vgft->data = ndata;
      vgft->w = nwidth;
      vgft->h = nheight;

      if (maxheight < vgft->h) { maxheight = vgft->h; }

      /* overwrite font-character */
      snprintf(fontfile, sizeof(fontfile), "%s/%s.pbm", fontdir, key);
      if (!vg4_font_savefile(vgft, fontfile)) {
        pwarn("Error saving font-character %s\n", fontfile);
      }
    }

    /* create and write out space-character with maxwidth * maxheight pixels */
    vgft = SML3_calloc(1, sizeof(*vgft));
    vgft->w = maxwidth;
    vgft->h = maxheight;
    vgft->data = SML3_calloc(vgft->w * vgft->h, sizeof(*vgft->data));
    snprintf(fontfile, sizeof(fontfile), "%s/%d.pbm", fontdir, VGI_FONT_SEPSPACE);
    if (!vg4_font_savefile(vgft, fontfile)) {
      pwarn("Error saving font-character %s\n", fontfile);
    }
    snprintf(sepspace, sizeof(sepspace), "%d", VGI_FONT_SEPSPACE);
    vg4->hash->set(fntp->hfont, sepspace, vgft, sizeof(*vgft));
    free(vgft);

    /* write out additional separation characters */
    memset(fntp->sepc, 0, sizeof(fntp->sepc));
    if (sepc != NULL && sepi > 0) {
      FILE *ffp;
      char cbuf[8];
      snprintf(fontfile, sizeof(fontfile), "%s/separators", fontdir);
      if ((ffp = fopen(fontfile, "w")) != NULL) {
        for (w1 = 0; w1 < sepi; w1++) {
          h1 = vg4->misc->utf8_from_codepoint(sepc[w1], cbuf);
          if (h1 > 0) {
            fwrite(cbuf, 1, h1, ffp);
            if (w1 < VGI_FONT_SEPC_MAX) { fntp->sepc[w1] = sepc[w1]; }
          }
        }
        fclose(ffp);
      } else {
        pwarn("Error saving separators file: %s\n", fontfile);
      }
    }
  }

grem_end:
  if (fontname != NULL) { vg4->font->destroy(fntp); }

  memmove(vg4data.lists.font.userdir, userdir, sizeof(userdir));
} /* Ende vg4_font_gap_remove */


/* compare function for qsort() */
static int
cmp_chars(const void *v1, const void *v2)
{   
  struct VG_Fontchar *c1 = (struct VG_Fontchar *)v1;
  struct VG_Fontchar *c2 = (struct VG_Fontchar *)v2;

  if (c1->index < c2->index) { return -1; }
  if (c1->index > c2->index) { return 1; }
  return 0;
} /* Ende cmp_chars */


/* font_path:
 * set path to user-created fonts
 * @param fontpath  path to user-created fonts, or NULL = clear it
 */
static void
font_path(const char *fontpath)
{
  if (fontpath == NULL || *fontpath == '\0') {
    *vg4data.lists.font.userdir = '\0';
  } else {
    snprintf(vg4data.lists.font.userdir, sizeof(vg4data.lists.font.userdir), "%s", fontpath);
  }
} /* Ende font_path */


/* font_load:
 * load font from font-directory
 * @param fontname  font-name, or NULL = default font
 * @return  font or NULL = error
 */
static struct VG_Font *
font_load(const char *fontname)
{
  struct VG_Font *fntp;

  if (fontname == NULL || *fontname == '\0') { return vg4_font_loaddefault(); }

  if (strcspn(fontname, "/.") != strlen(fontname)) {
    outerr("load font: invalid fontname: \"%s\"", fontname);
    return NULL;
  }

  /* load font (once) */
  fntp = (struct VG_Font *)vg4->hash->get(vg4data.lists.font.hash, fontname, NULL);
  if (fntp == NULL) {
    DIR *dirp;
    struct dirent *direntp;
    char *pext, fontdir[VGI_PATHSIZE], fontfile[VGI_PATHSIZE], key[16];
    struct vgi_font *vgft;

    *fontdir = '\0';
    if (*vg4data.lists.font.userdir != '\0') {
      vg4->misc->strccat(fontdir, sizeof(fontdir), vg4data.lists.font.userdir, "/", fontname, NULL);
      if ((dirp = opendir(fontdir)) != NULL) { closedir(dirp); } else { *fontdir = '\0'; }
    }
    if (*fontdir == '\0') {
      const char *prefix = vg4_get_prefix(NULL);
      snprintf(fontdir, sizeof(fontdir), "%s/share/vgagames4/fonts/%s", prefix, fontname);
    }

    if ((dirp = opendir(fontdir)) == NULL) {
      outerr("load font: cannot open \"%s\": %s", fontdir, strerror(errno));
      return NULL;
    }

    fntp = SML3_calloc(1, sizeof(*fntp));
    snprintf(fntp->name, sizeof(fntp->name), "%s", fontname);
    fntp->hfont = vg4_hash_create_nolist();

    { FILE *ffp;
      int sepi = 0;
      snprintf(fontfile, sizeof(fontfile), "%s/separators", fontdir);
      if ((ffp = fopen(fontfile, "r")) != NULL) {
        char sepbuf[64], *sepptr;
        size_t sepanz;
        int no_bytes, cindex;
        if ((sepanz = fread(sepbuf, 1, sizeof(sepbuf), ffp)) > 0) {
          sepptr = sepbuf;
          for (; sepi < VGI_FONT_SEPC_MAX; sepi++) {
            no_bytes = vg4->misc->utf8_next(sepptr, sepanz, &cindex);
            if (no_bytes <= 0) { break; }
            fntp->sepc[sepi] = cindex;
            sepptr += no_bytes;
            sepanz -= no_bytes;
          }
        }
        fclose(ffp);
      }
      if (sepi < VGI_FONT_SEPC_MAX) { fntp->sepc[sepi] = 0; }
    }

    fntp->maxw = fntp->maxh = 0;
    fntp->gaptop = fntp->gapbottom = -1;

    while ((direntp = readdir(dirp)) != NULL) {
      if (*direntp->d_name == '.') { continue; }
      if (strcmp(direntp->d_name, "separators") == 0) { continue; }
      if ((pext = strrchr(direntp->d_name, '.')) == NULL) { continue; }
      if (strcmp(pext, ".pbm") != 0) { continue; }

      if (strspn(direntp->d_name, "0123456789") == (size_t)(pext - direntp->d_name)) {
        int utf8index = (int)SML3_antoi(direntp->d_name, (size_t)(pext - direntp->d_name));
        if (utf8index < VGI_FONT_SEPSPACE) { pwarn("load font \"%s\": skipping %s\n", fontname, direntp->d_name); continue; }
        snprintf(key, sizeof(key), "%d", utf8index);
      } else if (strncmp(direntp->d_name, "unknown", (size_t)(pext - direntp->d_name)) == 0) {
        snprintf(key, sizeof(key), "unknown");
      }

      snprintf(fontfile, sizeof(fontfile), "%s/%s%s", fontdir, key, pext);
      vgft = vg4_font_loadfile(fontfile);
      if (vgft == NULL) { closedir(dirp); vg4->hash->destroy(fntp->hfont); free(fntp); return NULL; }

      if (fntp->maxw < vgft->w) { fntp->maxw = vgft->w; }
      if (fntp->maxh < vgft->h) { fntp->maxh = vgft->h; }

      /* top and bottom empty lines */
      { int w1, h1, gaplines;
        unsigned char *dptr;

        /* top empty lines */
        gaplines = 0;
        for (h1 = 0; h1 < vgft->h; h1++) {
          dptr = &vgft->data[h1 * vgft->w];
          for (w1 = 0; w1 < vgft->w; w1++) {
            if (*dptr++) { break; }
          }
          if (w1 < vgft->w) { break; }
          gaplines++;
        }
        if (fntp->gaptop < 0 || fntp->gaptop > gaplines) { fntp->gaptop = gaplines; }

        /* bottom empty lines */
        gaplines = 0;
        for (h1 = vgft->h - 1; h1 >= 0; h1--) {
          dptr = &vgft->data[h1 * vgft->w];
          for (w1 = 0; w1 < vgft->w; w1++) {
            if (*dptr++) { break; }
          }
          if (w1 < vgft->w) { break; }
          gaplines++;
        }
        if (fntp->gapbottom < 0 || fntp->gapbottom > gaplines) { fntp->gapbottom = gaplines; }
      }

      vg4->hash->set(fntp->hfont, key, vgft, sizeof(*vgft));
      free(vgft);
    }

    closedir(dirp);
    vg4->hash->set(vg4data.lists.font.hash, fontname, fntp, sizeof(*fntp));
    free(fntp);
    fntp = (struct VG_Font *)vg4->hash->get(vg4data.lists.font.hash, fontname, NULL);
  }

  return fntp;
} /* Ende font_load */


/* font_destroy:
 * destroy font
 * @param fntp  font
 */
static void
font_destroy(struct VG_Font *fntp)
{
  font_destroy_doit(fntp);
} /* Ende font_destroy */


/* destroy font */
static void *
font_destroy_doit(struct VG_Font *fntp)
{
  void *vpos;
  const char *key;
  struct vgi_font *vgft;

  if (fntp == NULL) { return NULL; }

  vpos = NULL;
  for (key = vg4->hash->list(fntp->hfont, &vpos);
       vpos != NULL;
       key = vg4->hash->list(fntp->hfont, &vpos)) {
    vgft = (struct vgi_font *)vg4->hash->get(fntp->hfont, key, NULL);
    if (vgft != NULL && vgft->data != NULL) { free(vgft->data); }
  }
  vg4->hash->destroy(fntp->hfont);

  return vg4->hash->remove(vg4data.lists.font.hash, fntp->name);
} /* Ende font_destroy_doit */


/* font_destroyall:
 * destroy all fonts
 */
static void
font_destroyall(void)
{
  struct VG_Font *fntp;
  void *vpos;
  const char *key;

  vpos = NULL;
  for (key = vg4->hash->list(vg4data.lists.font.hash, &vpos);
       vpos != NULL;
       key = vg4->hash->list(vg4data.lists.font.hash, &vpos)) {
    fntp = (struct VG_Font *)vg4->hash->get(vg4data.lists.font.hash, key, NULL);
    vpos = font_destroy_doit(fntp);
  }
} /* Ende font_destroyall */


/* font_getname:
 * get name of font
 * @param fntp  font, or NULL = default font
 * @return  filename
 */
static const char *
font_getname(const struct VG_Font *fntp)
{
  if (fntp == NULL) { fntp = vg4_font_loaddefault(); }
  return fntp->name;
} /* Ende font_getname */


/* font_setdefault:
 * set default fontname
 * @param fontname  default fontname, or empty/NULL = system default
 */
static void
font_setdefault(const char *fontname)
{
  if (fontname == NULL) { *vg4data.lists.font.dflfont = '\0'; return; }

  if (strcspn(fontname, "/.") != strlen(fontname)) {
    outerr("set default font: invalid fontname: \"%s\"", fontname);
    return;
  }

  vg4->misc->strcpy(vg4data.lists.font.dflfont, sizeof(vg4data.lists.font.dflfont), fontname);
} /* Ende font_setdefault */


/* font_getdefault:
 * get default fontname
 * @param fontname  for returning fontname
 * @param size      sizeof(fontname)
 */
static void
font_getdefault(char *fontname, size_t size)
{
  if (fontname == NULL || size == 0) { return; }
  vg4->misc->strcpy(fontname, size, vg4data.lists.font.dflfont);
} /* Ende font_getdefault */


/* font_maxsize:
 * get greatest width and height of font
 * @param fntp    font, or NULL = default font
 * @param width   for returning width
 * @param height  for returning height
 */
static void
font_maxsize(const struct VG_Font *fntp, int *width, int *height)
{
  if (fntp == NULL) { fntp = vg4_font_loaddefault(); }
  if (width != NULL) { *width = 0; }
  if (height != NULL) { *height = 0; }
  if (width != NULL) { *width = fntp->maxw; }
  if (height != NULL) { *height = fntp->maxh; }
} /* Ende font_maxsize */


/* font_gaps:
 * get number of empty pixels-lines at the top and the bottom of font
 * @param fntp    font, or NULL = default font
 * @param top     for returning number of empty pixel-lines at the top
 * @param bottom  for returning number of empty pixel-lines at the bottom
 */
static void
font_gaps(const struct VG_Font *fntp, int *top, int *bottom)
{
  if (fntp == NULL) { fntp = vg4_font_loaddefault(); }
  if (top != NULL) { *top = 0; }
  if (bottom != NULL) { *bottom = 0; }
  if (top != NULL) { *top = fntp->gaptop; }
  if (bottom != NULL) { *bottom = fntp->gapbottom; }
} /* Ende font_gaps */


/* font_getchars:
 * get all characters of a font
 * @param fntp    font, or NULL = default font
 * @param carray  for returning characters, must be freed with free() if not NULL
 * @return  number of characters
 */
static int
font_getchars(const struct VG_Font *fntp, struct VG_Fontchar **carray)
{
  void *vpos;
  const char *key;
  int anzchars, findex, fclen;
  char fcharacter[8];
  struct vgi_font *vgft;

  if (fntp == NULL) { fntp = vg4_font_loaddefault(); }
  if (carray != NULL) { *carray = NULL; }

  /* get number of characters */
  anzchars = 0;
  vpos = NULL;
  for (key = vg4->hash->list(fntp->hfont, &vpos); vpos != NULL; key = vg4->hash->list(fntp->hfont, &vpos)) { anzchars++; }
  if (anzchars == 0) { return 0; }
  if (carray != NULL) { *carray = SML3_calloc(anzchars, sizeof(**carray)); }

  /* set characters into carray */
  anzchars = 0;
  vpos = NULL;
  for (key = vg4->hash->list(fntp->hfont, &vpos); vpos != NULL; key = vg4->hash->list(fntp->hfont, &vpos)) {
    vgft = (struct vgi_font *)vg4->hash->get(fntp->hfont, key, NULL);
    if ((findex = atoi(key)) == 0) { continue; }
    fclen = vg4->misc->utf8_from_codepoint(findex, fcharacter);
    if (fclen == 0) { continue; }
    if (carray != NULL) {
      (*carray)[anzchars].index = findex;
      memcpy((*carray)[anzchars].character, fcharacter, fclen);
      (*carray)[anzchars].width = vgft->w;
      (*carray)[anzchars].height = vgft->h;
    }
    anzchars++;
  }

  if (anzchars > 0) {
    qsort(*carray, anzchars, sizeof(**carray), cmp_chars);
  } else {
    free(*carray);
    *carray = NULL;
  }

  return anzchars;
} /* Ende font_getchars */


/* font_dump:
 * dump font entries
 * @param outfp  filepointer to dump to, or NULL = stdout
 */
static void
font_dump(FILE *outfp)
{
  struct VG_Font *fntp;
  void *vpos1, *vpos2;
  const char *key1, *key2;
  int i1;
  char cbuf[8];
  struct vgi_font *vgft;

  if (outfp == NULL) { outfp = stdout; }

  fprintf(outfp, "\nDump of font entries\n");
  fprintf(outfp, "====================\n\n");

  vpos1 = NULL;
  for (key1 = vg4->hash->list(vg4data.lists.font.hash, &vpos1);
       vpos1 != NULL;
       key1 = vg4->hash->list(vg4data.lists.font.hash, &vpos1)) {
    fntp = (struct VG_Font *)vg4->hash->get(vg4data.lists.font.hash, key1, NULL);

    fprintf(outfp, "- [%s]\n", fntp->name);

    fprintf(outfp, "  Additional word-separation-UTF8-chars: ");
    for (i1 = 0; i1 < VGI_FONT_SEPC_MAX; i1++) {
      if (fntp->sepc[i1] == 0) { break; }
      fprintf(outfp, "%d ", fntp->sepc[i1]);
    }
    fprintf(outfp, "\n");

    fprintf(outfp, "  Greatest-width+height=%d+%d, empty-lines-at-top=%d, empty-lines-at-bottom=%d\n",
            fntp->maxw, fntp->maxh, fntp->gaptop, fntp->gapbottom);

    fprintf(outfp, "  Font-characters:\n");
    vpos2 = NULL;
    for (key2 = vg4->hash->list(fntp->hfont, &vpos2); vpos2 != NULL; key2 = vg4->hash->list(fntp->hfont, &vpos2)) {
      vgft = (struct vgi_font *)vg4->hash->get(fntp->hfont, key2, NULL);
      if ((i1 = atoi(key2)) == 0) { continue; }
      i1 = vg4->misc->utf8_from_codepoint(i1, cbuf);
      if (i1 > 0) {
        fprintf(outfp, "  - %s (", key2);
        fwrite(cbuf, 1, i1, outfp);
        fprintf(outfp, "): width=%d, height=%d\n", vgft->w, vgft->h);
      } else {
        fprintf(outfp, "  - %s: width=%d, height=%d\n", key2, vgft->w, vgft->h);
      }
    }

    fprintf(outfp, "\n");
  }
} /* Ende font_dump */
