// -*- C++ -*-
/* $Id: pixelfont.cc 1.1 1998/04/23 18:00:54 atterer Exp $
  __   _
  |_) /|  Copyright Richard Atterer
  | \/|  <atterer@informatik.tu-muenchen.de>
   ` 
  Create a 4bpp font from a sprite file
  uses OSLib

  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 2 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, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

const char* rcsid =
"$Id: pixelfont.cc 1.1 1998/04/23 18:00:54 atterer Exp $";

#include <string.h>
#include <stdio.h>
#include <math.h>
// OSLib typedef's "bool", which causes a compiler warning
#include <osfile.h>
#include <osfscontro.h>
#include <font.h>
#include <limits.h>
extern "C" {
  #include "Fontfile.h"
  #include "Error.h"
}
#include "Sprite.h"
#include "SpritePixel.h"

#ifdef DEBUGGING
  #define DEBUG(cmd) { cmd ; }
  #define NODEBUG(cmd) { }
#else
  #define DEBUG(cmd) { }
  #define NODEBUG(cmd) { cmd ; }
#endif

typedef unsigned char  byte;
typedef unsigned int   word;
typedef unsigned short hword;

static int fontnamelen;
static int filenamelen = 0;
static int capheight, ascender, ex, descender; // in pixels
static int baseline; // y position of baseline in pixels
static const int maxchars = 256 - 33;
static unsigned char ascii = 32; // current code while scanning sprite file
static Intmetrics im;
static Fontfile ff;
static int fontx0 = INT_MAX;
static int fontx1 = INT_MIN;
static int fonty0 = INT_MAX;
static int fonty1 = INT_MIN;
static os_error* err;

// command line switches
static char* spritefile;
static char* fontname; // max. length 40
static char filename[256] = "";
static int xfontsize = 12 * 16;
static int yfontsize = 12 * 16;
static int xfontdpi = 90;
static int yfontdpi = 90;
static int underlinepos = -102;
static int underlinethick = 30;
static int monospaced;
static double gamma = 1.0; // gamma correction
static char coltrans[16];
static const int buflen = 40 + 256 + 256 + 4 * 12 + 5 * 7;
static char buf[buflen];
static const char keywords[] =
    "/a,/a,/a,,size/k/e,height/k/e,xdpi/k/e,ydpi/k/e,ulinepos/k/e,"
    "ulinethick=ul/k/e,monospaced/k/e,gamma/k";
static const char syntax[] =
    "Syntax: *Pixelfont <spritefile> <fontname> [<destination dir>] "
    "[-size <points*16>] [-height <points*16>] [-xdpi <dpi>] [-ydpi <dpi>] "
    "[-ulinepos <1/256th em>] [-ulinethick <1/256th em>] "
    "[-monospaced <pixel width>] [-gamma <fpvalue>]";

enum { none = -1, black = 0, white = 15 };
//______________________________________________________________________

// ugly, but best way to make OSLib & SharedCLib co-exist peacefully here
extern "C" void _kernel_raise_error (os_error*);

inline void error(void) {
  // needs c:o.stubs to generate external error
  if (err) _kernel_raise_error(err);
  return;
}
//______________________________________________________________________

/* Scan one column of the sprite from bottom to top. Give error if lowest two
   pixels not white or whole column not white-black-white.
   pix must be at y coo. 0 */
int scancolumn(const SpritePixel& pixel, int& height) {
  SpritePixel scan = pixel;
  if (scan.col() != white || scan.up() != white) {
    fprintf(stderr, "Column %d: Lowest 2 pixels must be white.\n",
        scan.x());
    exit(1);
  }
  int c;
  while ((c = scan.up()) == white) ; // search for 1st non-white pixel
  if (c != black) {
    fprintf(stderr, "Column %d: Must only contain black or white pixels.\n",
        scan.x());
    exit(1);
  }
  int baseline = scan.y();
  while ((c = scan.up()) == black) ; // search for 1st non-black pixel
  switch (c) {
    case none:
      height = scan.height() - baseline; // black pixels til top of sprite
      return baseline;
    case white:
      height = scan.y() - baseline;
      while ((c = scan.up()) == white) ; // search for 1st non-white pixel
      if (c == none) {
        return baseline;
      } // else error
    default:
      fprintf(stderr, "Column %d: Must only contain black or white "
          "pixels.\n", scan.x());
      exit(1);
  }
}
//________________________________________

/* Ensure that pix (pix->y() == 0) is on a blank column. Skip the 1 or more
   blank columns, then set bx0/bx1 to the coordinates indicated by the lowest
   pixel line, x0/x1 to the ones by the line above the lowest. Move pix
   right to the next blank column in the process.
   Return false if right border of sprite reached */
bool nextchardef(SpritePixel& pixel,int& bx0, int& bx1, int& wx0, int& wx1) {
  int c;
  SpritePixel scan;
  pixel.left();
  // skip white columns
  while ((c = pixel.right()) == white) {
    scan = pixel;
    while ((c = scan.up()) == white) ;
    if (c != none) {
      fprintf(stderr, "Character %c: Column %d must be completely "
          "white, as the lowest pixel is white.\n", ascii, scan.x());
      exit(1);
    }
  }
  if (c == none) {
    DEBUG(printf("end: deleting %X\n", int(&scan)))
    return false; // right border of sprite reached
  }
  // find width of black pixels at bottom, set bx0, later bx1
  bx0 = pixel.x();
  while ((c = pixel.right()) == black) ;
  /* If c == none, leave pix outside sprite. Causes the sprite end to be
     recognized next time nextchardef() is called, because pixel.left();
     pixel.right() above fails. */
  bx1 = pixel.x();
  // scan line above bottom one, set x0, x1
  wx0 = bx0; wx1 = bx0;
  scan.xy(bx0 - 1, 1);
  while (scan.right() == white && scan.x() < bx1) ;
  if (scan.x() < bx1) {
    wx0 = scan.x();
    while ((c = scan.right()) == black && scan.x() < bx1) ;
    wx1 = scan.x();
    if (scan.x() < bx1) {
      // check that piece of line above bottom one is white-black-white
      while (scan.right() == white && scan.x() < bx1) ;
      if (scan.x() < bx1)
        wx1 = wx0; // special case; width zero
    }
  }
  return true;
}
//________________________________________

// shave white lines off the edges of the bitmap by altering coordinates
void shrinkbitmap(const Sprite& spr, int& x0, int& x1, int& y0, int& y1) {
  int width = x1 - x0;
  int height = y1 - y0;
  int c = none; // initialise to keep the compiler happy
  SpritePixel scan(spr);
  // empty rows at bottom?
  do {
    scan.xy(x0 - 1, y0);
    for (int i = width; i && (c = scan.right()) == white; i--) ;
    if (c == white) {
      height--;
      y0++;
    }
  } while (height && c == white);
  // if character blank, return now
  if (height == 0) {
    x1 = x0;
    return;
  }

  // empty rows at top?
  do {
    scan.xy(x0 - 1, y1 - 1);
    for (int i = width; i && (c = scan.right()) == white; i--) ;
    if (c == white) {
      height--;
      y1--;
    }
  } while (c == white);
  // empty columns at left side?
  do {
    scan.xy(x0, y0 - 1);
    for (int i = height; i && (c = scan.up()) == white; i--) ;
    if (c == white) x0++;
  } while (c == white);
  // empty columns at right side?
  do {
    scan.xy(x1 - 1, y0 - 1);
    for (int i = height; i && (c = scan.up()) == white; i--) ;
    if (c == white) x1--;
  } while (c == white);
  return;
}
//________________________________________

unsigned* makerawbpp(const Sprite& spr, int& datasize,
    int x0, int x1, int y0, int y1) {
  int width = x1 - x0;
  if (width == 0) {
    datasize = 0;
    return 0;
  }
  unsigned* data = new unsigned[(width * (y1 - y0) + 7) / 8];
  unsigned* dataptr = data;
  unsigned x = 0;
  int shift = 0;

  SpritePixel rowstart(spr, x0 - 1, y0);
  SpritePixel scan;
  for (int j = y1 - y0; j; j--) {
    scan = rowstart;
    for (int i = width; i; i--) {
      x |= coltrans[scan.right()] << shift;
      shift += 4;
      if (shift == 32) { *dataptr++ = x; shift = 0; x = 0; }
    }
    rowstart.up();
  }
  if (shift) *dataptr = x;
  datasize = (dataptr - data) * 4 + shift / 4;
  return data;
}
//________________________________________

inline char* arg_string(char*& bufptr) {
  bufptr +=4;
  return *static_cast<char**>(bufptr);
}

int arg_evalint(char*& bufptr) {
  bufptr += 4;
  char* ptr = *static_cast<char**>(bufptr);
  if (ptr)
    return int(ptr[1]) |
           int(ptr[2]) << 8 |
           int(ptr[3]) << 16 |
           int(ptr[4]) << 24;
  else
    return INT_MIN;
}

/*inline bool arg_switch(char*& bufptr) {
  bufptr += 4;
  if (*static_cast<int*>(bufptr)) return true; else return false;
}*/
//________________________________________

int main(void) {
  Error_Initialise("Pixelfont");
  char* cli;

  xos_get_env(&cli, SKIP, SKIP);
  err = xos_read_args(keywords, cli, buf, buflen, SKIP);
  if (err) {
    fprintf(stderr, "%s\n%s\n", err->errmess, syntax);
    exit(1);
  }
  // analyse command options
  char* bufptr = buf;
  spritefile = arg_string(bufptr);
  fontname = arg_string(bufptr);
  if ((fontnamelen = strlen(fontname)) > 39) {
    fprintf(stderr, "Font name too long by %d characters\n",
        fontnamelen - 39);
    exit(1);
  }
  char* strarg = arg_string(bufptr); // font name
  if (strarg != 0) { // destination directory
    strcpy(filename, strarg);
    filenamelen = strlen(filename);
    if (filenamelen > 0 && filename[filenamelen - 1] != ':'
        && filename[filenamelen - 1] != '.') {
      filename[filenamelen++] = '.';
      DEBUG(filename[filenamelen] = 0)
    }
  }
  int intarg;
  if ((intarg = arg_evalint(bufptr)) != INT_MIN) {
    xfontsize = intarg; // -size
    yfontsize = intarg;
  }
  if ((intarg = arg_evalint(bufptr)) != INT_MIN)
    yfontsize = intarg; // -height
  if ((intarg = arg_evalint(bufptr)) != INT_MIN)
    xfontdpi = intarg; // -xdpi
  if ((intarg = arg_evalint(bufptr)) != INT_MIN)
    yfontdpi = intarg; // -ydpi
  if ((intarg = arg_evalint(bufptr)) != INT_MIN)
    underlinepos = intarg; // -ulinepos
  if ((intarg = arg_evalint(bufptr)) != INT_MIN)
    underlinethick = intarg; // -ulinethick
  monospaced = arg_evalint(bufptr); // is INT_MIN if not present

  strarg = arg_string(bufptr); // gamma value
  if (strarg != 0)
    sscanf(strarg, "%lf", &gamma);
  char* colentry = coltrans; // build colour translation table
  for (int i = 15; i >= 0; i--)
    *(colentry++) = int(pow(i / 15.0, gamma) * 15.0 + 0.5);
  DEBUG(
    for (int i = 0; i < 16; i++) {
      printf("%02d: ", i);
      for (int j = coltrans[i]; j; j--) printf("#");
      printf("\n");
    }
  )

  DEBUG(
    printf("Pixelfont %s %s %s -size %d -height %d -xdpi %d -ydpi %d "
        "-ulinepos %d -ulinethick %d -monospaced &%X -gamma %f\n",
        spritefile, fontname, filename, xfontsize, yfontsize, xfontdpi,
        yfontdpi, underlinepos, underlinethick, monospaced, gamma);
  )
  Error_Filename(spritefile);
  //____________________

  // init IntMetrics
  Intmetrics_Create(&im, maxchars + 1,
      Intmetrics_Flags (imf_noyoffset | imf_moredata | imf_mapsize));
  strcpy(im.name, fontname);
  for (int i = fontnamelen; i < 40; i++)
    im.name[i] = '\r'; // pad with CRs

  // init Fontfile
  Fontfile_Create(&ff, 4); // 4bpp
  strcpy(ff.name, fontname);
  ff.bmpsize.xsize = xfontsize;
  ff.bmpsize.ysize = yfontsize;
  ff.bmpsize.xdpi = xfontdpi;
  ff.bmpsize.ydpi = yfontdpi;
  int pix2em = xfontdpi * xfontsize;

  // init sprite area
  Spritearea sa(err, spritefile);
  error();
  Sprite& spr = sa.first();
  if (spr.height() < 3 || spr.width() < 5) {
    fprintf(stderr, "Sprite too small!\n");
    exit(1);
  }
  int sprbpp = spr.bpp();
  if (sprbpp != 2) {
    fprintf(stderr, "First sprite in %s must be 4bpp, but is %dbpp.\n",
        spritefile, 1 << sprbpp);
    exit(1);
  }
  // init sprite pixel object
  SpritePixel pixel(spr);
  DEBUG(printf("size: %d x %d\n", pixel.width(), pixel.height()))
  //____________________

  // check that lowest two lines only contain black or white pixels
  SpritePixel bwcheck(spr, 3, 0);
  int c;
  while ((c = bwcheck.right()) == black || c == white) ;
  if (c == none) {
    bwcheck.xy(3, 1);
    while ((c = bwcheck.right()) == black || c == white) ;
  }
  if (c != none) {
    fprintf(stderr, "Column %d: Lowest 2 pixels must be either black or "
        "white.\n", bwcheck.x());
    exit(1);
  }

  // get values for capheight, ascender, ex, descender
  baseline = scancolumn(pixel, capheight);
  pixel.right();
  int bl = scancolumn(pixel, ascender);
  if (bl != baseline) {
    fprintf(stderr, "Column %d: Baseline differs from previous columns.\n",
        pixel.x());
    exit(1);
  }
  pixel.right();
  bl = scancolumn(pixel, ex);
  if (bl != baseline) {
    fprintf(stderr, "Column %d: Baseline differs from previous columns.\n",
        pixel.x());
    exit(1);
  }
  pixel.right();
  bl = scancolumn(pixel, descender);
  bl += descender;
  if (bl != baseline) {
    fprintf(stderr, "Column %d: Baseline differs from previous columns.\n",
        pixel.x());
    exit(1);
  }
  pixel.right();

  DEBUG(printf("Height in pixels: X=%d, l=%d, ex=%d. p extends below "
        "baseline by %d.\n", capheight, ascender, ex, descender))
  descender = -descender;
  // Write info to IntMetrics.
  im.misc->xoffset = 0; // is overridden by table
  im.misc->yoffset = 0;
  im.misc->hoffset = 0;
  im.misc->ulpos = underlinepos;
  im.misc->ulthick = underlinethick;
  im.misc->capheight = (capheight * 72000 * 16 + pix2em / 2) / pix2em;
  im.misc->xheight = (ex * 72000 * 16 + pix2em / 2) / pix2em;
  // NB negative:
  im.misc->descender = (descender * 72000 * 16 + pix2em / 2) / pix2em;
  im.misc->ascender = // usually positive
      ((ascender - capheight) * 72000 * 16 + pix2em / 2) / pix2em;
  //____________________

  // read in the character data and set up font data structures
  int monospacewidth = (monospaced * 72000 * 16) / pix2em + 1;
  int bx0, bx1, wx0, wx1;
  int x0, y0, x1, y1; // sprite coordinates of bbox
  Fontfile_Character* ffchar;
  while (ascii != '\0' && nextchardef(pixel, bx0, bx1, wx0, wx1)) {
    DEBUG(printf("Character %c: Bitmap is %d, character is %d pixels "
        "wide.\n", ascii, bx1 - bx0, wx1 - wx0))
    x0 = bx0; x1 = bx1;
    y0 = 2; y1 = spr.height();
    DEBUG(printf("Character %c: lower left (%d, %d), upper right "
        "(%d, %d).\n", ascii, x0, y0, x1, y1))
    shrinkbitmap(spr, x0, x1, y0, y1);
    if (x0 != x1 || wx0 != wx1) { // don't define if blank and width = 0
      int imindex = Intmetrics_MapChar(&im, ascii, -1);
      if (monospaced != INT_MIN) {
        wx0 -= (monospaced - wx1 + wx0) / 2;
        im.xoffset[imindex] = monospacewidth;
      } else {
        int ffwidth = ((wx1 - wx0) * 72000 * 16) / pix2em;
        if (ffwidth) ffwidth++;
        im.xoffset[imindex] = ffwidth;
      }
      // create a new Fontfile_Character
      ffchar = new Fontfile_Character;
      int(ffchar->flags) = 0;
      // copy raw data away from sprite
      ffchar->data = makerawbpp(spr, ffchar->datasize, x0, x1, y0, y1);
      // finally, hand over to Fontfile
      Fontfile_SetCharacter(&ff, ascii, ffchar);

      x0 -= wx0; x1 -= wx0;
      y0 -= baseline; y1 -= baseline;
      ffchar->bbox.x0 = x0;
      ffchar->bbox.x1 = x1;
      ffchar->bbox.y0 = y0;
      ffchar->bbox.y1 = y1;
      if (fontx0 > x0) fontx0 = x0;
      if (fontx1 < x1) fontx1 = x1;
      if (fonty0 > y0) fonty0 = y0;
      if (fonty1 < y1) fonty1 = y1;
      DEBUG(printf("Character %c: lower left (%d, %d), upper right "
        "(%d, %d), %d bytes.\n", ascii, x0, y0, x1, y1, ffchar->datasize))
      im.bbox[imindex].x0 = (x0 * 72000 * 16) / pix2em;
      im.bbox[imindex].x1 = (x1 * 72000 * 16) / pix2em + 1;
      im.bbox[imindex].y0 = (y0 * 72000 * 16) / pix2em;
      im.bbox[imindex].y1 = (y1 * 72000 * 16) / pix2em + 1;
    }
    ascii++;
    if (ascii == 127) ascii = 128; // skip delete character
  }
  //____________________

  Intmetrics_GetBBox(&im, &im.misc->maxbbox); // calculate font bbox
  Intmetrics_Compact(&im);
  ff.bbox.x0 = static_cast<short>(fontx0);
  ff.bbox.x1 = static_cast<short>(fontx1);
  ff.bbox.y0 = static_cast<short>(fonty0);
  ff.bbox.y1 = static_cast<short>(fonty1);
  Fontfile_Compact(&ff);
  //____________________

  // create directories
  char* append = filename + filenamelen;
  char* restname = fontname;
  while (*restname) {
    do *(append++) = *(restname++);
      while (*restname && *restname != '.');
    *append = 0;
    err = xosfile_create_dir(filename, 0);
    error();
  }
  *(append++) = '.';

  // write IntMetrics
  strcpy(append, "IntMetrics");
  int written = Intmetrics_Write(&im, filename);
  if (written == 0) {
    fprintf(stderr, "Couldn't write file %s.\n", filename);
    exit(1);
  }

  // write "f...x..." file with 4bpp data
  sprintf(append, "f%dx%d",
      xfontsize * xfontdpi / 72,
      yfontsize * yfontdpi / 72);
  written = Fontfile_Write(&ff, filename);
  if (written == 0) {
    fprintf(stderr, "Couldn't write file %s.\n", filename);
    exit(1);
  }
  return 0;
}
