/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*
 *
 * This file is part of the TeX Font Tools package.
 *
 * Copyright  1997, 98 Jakob Stoklund Olesen.
 *
 * 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.
 *
 *- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/

static char     rcsid[] =
"$Id: TFTLib.c.FontRead 4.2 1998/02/15 20:00:38 stoklund Stable $";

/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*
 * $Revision: 4.2 $	$Date: 1998/02/15 20:00:38 $
 *
 * Reading of RISC OS font files.
 * Outlines and IntMetrics. Always writes newest version.
 * Originally part of Fontfile.c 1.4
 *
 * $Log: TFTLib.c.FontRead $
 * Revision 4.2  1998/02/15 20:00:38  stoklund
 * Copyright
 *
 * Revision 4.1  1998/02/13 17:24:25  stoklund
 * (<many>): Heap Checking removed
 * (readcoords): coordinates treated as ints
 *
 * Revision 3.3  1997/11/02 23:18:13  stoklund
 * Errors made GNU compliant
 *
 * Revision 3.2  1997/11/01 12:47:25  stoklund
 * Error reporting and heap checking.
 * Fontfile_GetCharacter moved to FontCommon.c
 *
 * Revision 3.1  1997/10/13 17:56:13  stoklund
 * New const char * prototypes for read functions.
 *
 * Revision 2.2  1997/08/19 22:55:48  stoklund
 * Spelling mistake in error message!!
 *
 * Revision 2.1  1997/07/04 20:45:32  stoklund
 * usage of fgetc changes to getc for speed.
 * Prints error message when files won't open.
 * Copyright fix, Log cleanup
 *
 * Revision 1.7  1997/07/02 21:01:52  stoklund
 * Checks for illegal data in chunk index.
 *
 * Revision 1.5  1997/04/13 15:48:48  stoklund
 * Fontfile BBox read in accordance with Fontfile_Read.
 *
 * Revision 1.4  1997/04/08 22:19:28  stoklund
 * Better versionchecking on outline files.
 * Some memory holes removed / memory debugging increased.
 *
 * Revision 1.3  1997/04/05 17:37:29  stoklund
 * Confusion about misc struct resolved.
 * Intmetrics_Compact compaction routine was completely wrong.
 *
 * Revision 1.2  1997/04/04 23:41:54  stoklund
 * Confusion about the interpretation of map[] entries resolved.
 * Memory leaks [hopefully] removed.
 *
 * Revision 1.1  1997/04/01 20:59:02  stoklund
 * Initial revision
 *
 *- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/

#include "Debug.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "Error.h"
#include "Fontfile.h"


/*
 * Read IntMetrics file, and allocate necessary memory. Returns 0 on
 * failure
 */
int
Intmetrics_Read(Intmetrics * im, const char *file)
{
  FILE           *f;
  int             tmp[2];

  Error_Filename(file);
  f = fopen(file, "r");
  if (!f)
    return 0;

  fread(im->name, 40, 1, f);	/* 40 bytes of \r padded fontname */
  fread(tmp, 2, 4, f);		/* 2 x word = 16 (?) */
  im->n = getc(f);		/* nlo */
  im->version = getc(f);	/* version [must be 0 or 2] */
  im->flags = getc(f);
  im->n += getc(f) << 8;	/* nhi */

  /* Check consistency */
  if (feof(f) || im->version > 2 || tmp[0] != 16 || tmp[1] != 16) {
    fclose(f);
    Error_Error("not an intmetrics file");
    return 0;
  }
  /* Mapsize follows? */
  if (im->flags & imf_mapsize) {
    im->m = getc(f);
    im->m += getc(f) << 8;
  } else
    im->m = 256;

  /* read map */
  if (im->m > 0) {
    im->map = Error_Memory(malloc(im->m), "reading IntMetrics map");
    fread(im->map, im->m, 1, f);
  } else {
    im->map = NULL;		/* no map */
    im->m = im->n;		/* direct mapping */
  }

  /* read bbox data */
  if (!(im->flags & imf_nobbox)) {
    int             i;
    im->bbox = Error_Memory(malloc(8 * im->n), "reading IntMetrics bbox");
    for (i = 0; i < im->n; i++)
      fread(&im->bbox[i].x0, 1, 2, f);
    for (i = 0; i < im->n; i++)
      fread(&im->bbox[i].y0, 1, 2, f);
    for (i = 0; i < im->n; i++)
      fread(&im->bbox[i].x1, 1, 2, f);
    for (i = 0; i < im->n; i++)
      fread(&im->bbox[i].y1, 1, 2, f);
  } else
    im->bbox = NULL;

  /* read xoffset data */
  if (!(im->flags & imf_noxoffset)) {
    im->xoffset = Error_Memory(malloc(2 * im->n),
                               "reading IntMetrics xoffset");
    fread(im->xoffset, im->n, 2, f);
  } else
    im->xoffset = NULL;

  /* read yoffset data */
  if (!(im->flags & imf_noyoffset)) {
    im->yoffset = Error_Memory(malloc(2 * im->n),
                               "reading IntMetrics yoffset");
    fread(im->yoffset, im->n, 2, f);
  } else
    im->yoffset = NULL;


  /* read extra data */
  if (im->flags & imf_moredata) {
    unsigned short  off[4];
    long            pos;

    pos = ftell(f);
    fread(off, 4, 2, f);	/* offsets from pos to misc, kern,
                                 * reserved 1/2 */

    /* read misc area if present */
    if (off[1] > off[0]) {
      fseek(f, pos + off[0], SEEK_SET);
      im->misc = Error_Memory(calloc(1, sizeof(*im->misc)),
                              "reading IntMetrics misc block");
      fread(im->misc, 2, 14, f);
    } else
      im->misc = NULL;

    /* read kern data if present */
    if (off[2] > off[1]) {
      int             lhc, ks;
      char           *data, *p;

      /* we need to convert char codes etc. to uniform format */
      data = Error_Memory(malloc(off[2] - off[1]), "reading kern data");
      fseek(f, pos + off[1], SEEK_SET);
      fread(data, off[2] - off[1], 1, f);
      /* calc size of each kern pair in file */
      ks = (im->flags & imf_kern16) ? 2 : 1;
      ks += 2 * !(im->flags & imf_noxoffset);	/* +2 for xoffset */
      ks += 2 * !(im->flags & imf_noyoffset);	/* +2 for yoffset */

      /* kern array is indexed by char code */
      im->kern = Error_Memory(calloc(im->m, sizeof(*im->kern)),
                              "reading kern data");

      /* read the kern pairs. */
      p = data;
      while (p - data < off[2] - off[1]) {
        int             n;
        /* get left hand code */
        lhc = *p++;
        if (im->flags & imf_kern16)
          lhc += *p++ << 8;

        if (lhc == 0)
          break;		/* end of kern data */
        if (lhc >= im->m) {
          Error_Warning("inconsistent kern data");
          break;
        }
        /* now scan for number of kern pairs for this char */
        if (im->flags & imf_kern16)
          for (n = 0; p[ks * n] | p[ks * n + 1]; n++);	/* stop when rhc=0 */
        else
          for (n = 0; p[ks * n]; n++);	/* stop when rhc=0 */
        /* allocate kern array for lhc */
        im->kern[lhc] = Error_Memory(calloc(n + 1, sizeof(Intmetrics_Kern)),
                                     "reading kern data");
        for (n = 0;; n++) {
          im->kern[lhc][n].rightchar = *p++;
          if (im->flags & imf_kern16)
            im->kern[lhc][n].rightchar += *p++ << 8;

          if (!im->kern[lhc][n].rightchar)
            break;		/* end for this letter */

          if (!(im->flags & imf_noxoffset)) {
            im->kern[lhc][n].xkern = *p + (*(p + 1) << 8);
            p += 2;
          }
          if (!(im->flags & imf_noyoffset)) {
            im->kern[lhc][n].ykern = *p + (*(p + 1) << 8);
            p += 2;
          }
        }
      }
      free(data);
    } else
      im->kern = NULL;
  } else {
    im->misc = NULL;
    im->kern = NULL;
  }
  fclose(f);
  return 1;
}

/* Get the index in the table for char code cc. */
/* Index is 0..m-1 (negative if no char) */
/* The index can be used in the tables bbox, xoffset, yoffset */
int
Intmetrics_GetIndex(Intmetrics * im, int cc)
{
  /* Do we have a map? If so, use it, otherwise use cc directly */
  if (im->map)
    return cc < im->m ? im->map[cc] : -1;
  else
    return cc < im->n ? cc : -1;
}



/**** Fontfile, outlines and bitmap files ****/

static Fontfile_Character *readcharacter(FILE * file, int len);
static void
readchunk(Fontfile * ff, struct fontfile_chunk * ch, FILE * file,
          long pos, int size);
static void     readscaffolds(Fontfile * ff, FILE * file);

/*
 * Initialise and read given fontfile from filename. Return 0 on
 * failure
 */
int
Fontfile_Read(Fontfile * ff, const char *filename)
{
  FILE           *file;
  int             tmp;
  long            chunktable;
  int            *offset;
  long            tablestart;
  short           tablesize;

  Error_Filename(filename);
  file = fopen(filename, "r");
  if (!file)
    return 0;

  fread(&tmp, 4, 1, file);	/* ID word 'FONT' */
  ff->bpp = getc(file);
  ff->version = getc(file);

  /* Test integrity */
  if (0 != memcmp(&tmp, "FONT", 4) ||
      (ff->bpp != 0 && ff->bpp != 1 && ff->bpp != 4) ||
      ff->version < 4 || ff->version > 8) {
    Error_Error("not a font file");
    fclose(file);
    return 0;
  }
  if (ff->bpp == 0) {
    /* Outlines, so designsize follows */
    ff->designsize = getc(file);
    ff->designsize += getc(file) << 8;
    ff->flags = ff->version >= 7 ? 1 << 6 : 0;
  } else {
    /* Bitmaps have flags */
    ff->flags = getc(file);
    ff->flags += getc(file) << 8;
  }
  fread(&ff->bbox, 1, 8, file);	/* maximum bbox */
  /* Convert bbox to absolutes */
  ff->bbox.x1 += ff->bbox.x0;
  ff->bbox.y1 += ff->bbox.y0;

  if (ff->version < 8) {
    /* old formats have 8 chunks, with offsets here */
    chunktable = ftell(file);
    ff->nchunks = 8;
    ff->ns = 256;
    ff->scaffoldflags = 0;
    fseek(file, 9 * 4, SEEK_CUR);	/* skip chunktable */
  } else {
    /* new formats have chunk table elsewhere */
    fread(&chunktable, 1, 4, file);	/* offset of chunktable */
    fread(&ff->nchunks, 1, 4, file);
    fread(&ff->ns, 1, 4, file);	/* # scaffold entries+1 */
    fread(&ff->scaffoldflags, 1, 4, file);
    fseek(file, 5 * 4, SEEK_CUR);	/* skip reserved */
  }

  /* the table follows */
  tablestart = ftell(file);
  fread(&tablesize, 1, 2, file);
  if (ff->bpp > 0) {
    /* for bitmaps, table gives resolution and size */
    ff->scaffold = NULL;
    fread(&ff->bmpsize, 8, 1, file);
  } else {			/* bpp=0 => outlines */
    ff->scaffold = Error_Memory(calloc(ff->ns, sizeof(*ff->scaffold)),
                                "reading font file");
    readscaffolds(ff, file);
  }
  /* Table postamble holds the fontname */
  fseek(file, tablestart + tablesize, SEEK_SET);
  fgets(ff->name, 40, file);

  /* now read the offsets to the chunks */
  offset = Error_Memory(malloc((ff->nchunks + 1) * 4), "reading font file");
  fseek(file, chunktable, SEEK_SET);
  fread(offset, ff->nchunks + 1, 4, file);

  /* read in each chunk */
  ff->chunk = Error_Memory(calloc(ff->nchunks, sizeof(*ff->chunk)),
                           "reading font file");
  assert(ff->chunk != NULL);
  for (tmp = 0; tmp < ff->nchunks; tmp++) {
    /* chunk is null if size is zero */
    if (offset[tmp] < offset[tmp + 1])
      readchunk(ff, ff->chunk + tmp, file,
                offset[tmp], offset[tmp + 1] - offset[tmp]);
  }
  fclose(file);
  free(offset);
  return 1;
}

/* count the number of set bits in n */
static int
countbits(unsigned n)
{
  int             bits;
  for (bits = 0; n; n >>= 1)
    bits += n & 1;
  return bits;
}

/* Read in the scaffold lines from file at current position */
static void
readscaffolds(Fontfile * ff, FILE * file)
{
  unsigned short *offset;
  long            pos = ftell(file) - 2;	/* Position of table
                                                 * start */
  int             n, m, i, mask;
  int             base2;

  offset = Error_Memory(malloc(ff->ns * sizeof(unsigned short)),
                        "reading scaffold data");
  offset[0] = 0;		/* char 0 is never defined, and not
                                 * in index */
  fread(offset + 1, 2, ff->ns - 1, file);
  if (ff->version >= 5)
    ff->threshold = getc(file);
  else
    ff->threshold = 0;

  /* read in each char */
  for (n = 0; n < ff->ns; n++)
    if (offset[n]) {
      ff->scaffold[n] = Error_Memory(calloc(1, sizeof(*ff->scaffold[n])),
                                     "reading scaffold data");
      if (ff->scaffoldflags & 1) {	/* always 16-bit base chars */
        base2 = 1;
        fseek(file, pos + offset[n], SEEK_SET);
      } else {
        base2 = offset[n] >> 15;/* bit-15 says if basechar is 16-bit */
        fseek(file, pos + (offset[n] & 0x8fffl), SEEK_SET);
      }
      /* read basechar (8 or 16 bits) */
      ff->scaffold[n]->basechar = getc(file);
      if (base2)
        ff->scaffold[n]->basechar += getc(file) << 8;
      /* read all line masks */
      ff->scaffold[n]->basex = getc(file);
      ff->scaffold[n]->basey = getc(file);
      ff->scaffold[n]->localx = getc(file);
      ff->scaffold[n]->localy = getc(file);
      /* now get the lines that follow */
      mask = ff->scaffold[n]->localx + (ff->scaffold[n]->localy << 8);
      ff->scaffold[n]->lines = countbits(mask);	/* # local lines */
      if (ff->scaffold[n]->lines) {
        ff->scaffold[n]->line = Error_Memory(calloc(ff->scaffold[n]->lines,
          sizeof(*ff->scaffold[n]->line)), "reading scaffold data");
        i = 0;
        for (m = 0; m < 16; m++)
          if (mask & (1 << m)) {
            int             tmp;
            tmp = getc(file);
            tmp += getc(file) << 8;
            /* clear and sign-extend bits 0-11 as coordinate */
            ff->scaffold[n]->line[i].coord = (tmp << 20) >> 20;
            ff->scaffold[n]->line[i].link = tmp >> 12;	/* incl linear */
            ff->scaffold[n]->line[i].index = m;
            ff->scaffold[n]->line[i].width = getc(file);
            i++;
          }
      }
    }
  free(offset);
}

/* Read in a chunk from file at position pos */
static void
readchunk(Fontfile * ff, struct fontfile_chunk * ch,
          FILE * file, long pos, int len)
{
  int             n, m;
  int             (*offset)[32];

  fseek(file, pos, SEEK_SET);

  if (ff->flags & (1 << 6)) {
    fread(&ch->flags, 1, 4, file);
    pos += 4;
    len -= 4;			/* offsets are from the index */
  } else
    ch->flags = 0x80000000;	/* default flags */

  /* set multiplicity depending on subpixel flags */
  /* x4 if vertical, x4 if horizontal */
  ch->mult = ch->flags & 1 ? 4 : 1;
  if (ch->flags & 2)
    ch->mult *= 4;
  /* offsets to each character follows */
  offset = Error_Memory(malloc((32 * ch->mult + 1) * sizeof(int)), "reading chunk");
  fread(offset, 32 * ch->mult, 4, file);
  offset[ch->mult][0] = len;	/* offset to end of last char */

  /* dependency bytes follows. We read four each time */
  if (ff->bpp == 0 && ff->version >= 6)
    fread(&ch->dependency, 1, 4, file);
  else
    ch->dependency = 0;

  /* now allocate chars array */
  ch->character =
    Error_Memory(
                 calloc(ch->mult, 32 * sizeof(Fontfile_Character *)),
                 "reading chunk");
  for (m = 0; m < ch->mult; m++) {	/* each multiplicity */
    for (n = 0; n < 32; n++)
      if (offset[m][n]) {	/* each char */
        int            *next = &offset[m][n] + 1;
        while (*next == 0)
          next++;		/* find offset of next char */
        if (offset[m][n] >= len) {
          Error_Warning("illegal character offset %8x in chunk",
                        offset[m][n]);
          continue;
        }
        fseek(file, pos + offset[m][n], SEEK_SET);
        ch->character[m][n] = readcharacter(file, *next - offset[m][n]);
      }
  }
  free(offset);
}

/* Read coords x,y from file in signed 8 or 12-bit */
static void
readcoords(FILE * file, int *x, int *y, int coord12)
{
  if (coord12) {
    struct {
      int             x:12;
      int             y:12;
    }               cs;
    fread(&cs, 1, 3, file);
    *x = (cs.x << 20) >> 20;
    *y = (cs.y << 20) >> 20;
  } else {
    *x = (getc(file) << 24) >> 24;
    *y = (getc(file) << 24) >> 24;
  }
}

/* read character definition of total length len from file */
static Fontfile_Character *
readcharacter(FILE * file, int len)
{
  Fontfile_Character *c = Error_Memory(calloc(1, sizeof(*c)), "reading font char");
  long            pos = ftell(file);

  c->flags = getc(file);
  /* if outline char, composite chars may follow */
  c->base = c->accent = 0;
  if (c->flags & fff_outline) {
    if (c->flags & fff_composite) {
      c->base = getc(file);
      /* NOTE: 16-bit chars not supported */
    }
    if (c->flags & fff_accent) {
      c->accent = getc(file);
      /* NOTE: 16-bit chars not supported */
      readcoords(file, &c->accx, &c->accy, c->flags & fff_coord12);
    }
  }
  /* more data follows if there was no composite chars */
  if (c->base == 0 && c->accent == 0) {
    int             x, y;
    readcoords(file, &x, &y, c->flags & fff_coord12);
    c->bbox.x0 = x;
    c->bbox.y0 = y;
    /* NOTE: the following are offsets */
    readcoords(file, &x, &y, c->flags & fff_coord12);
    /* Convert to absolute */
    c->bbox.x1 = x + c->bbox.x0;
    c->bbox.y1 = y + c->bbox.y0;
    /* If we have outlines, read them in */
    if (c->flags & fff_outline) {
      int             buf[1024];
      int             n = 0;
      char            x;

      do {
        x = getc(file);
        switch (x & 3) {
        case ol_terminate:	/* no following data */
          buf[n++] = x & ol_stroke;
          break;
        case ol_move:
        case ol_line:		/* x,y follows */
          buf[n++] = x;
          readcoords(file, buf + n, buf + n + 1, c->flags & fff_coord12);
          n += 2;
          break;
        case ol_curve:		/* 3 coord pairs follow */
          buf[n++] = x;
          readcoords(file, buf + n, buf + n + 1, c->flags & fff_coord12);
          n += 2;
          readcoords(file, buf + n, buf + n + 1, c->flags & fff_coord12);
          n += 2;
          readcoords(file, buf + n, buf + n + 1, c->flags & fff_coord12);
          n += 2;
          break;
        }
      } while (x & (3 | ol_stroke));	/* terminator and no stroke
                                         * paths=>stop */
      /* copy buffer to character data */
      c->outline = Error_Memory(malloc(n * sizeof(int)), "reading font char");
      memcpy(c->outline, buf, n * sizeof(int));

      /* read composite character inclusions if any */
      if (x & ol_composite) {
        struct fontfile_composite_str buf[25];

        n = 0;
        do {
          buf[n].code = getc(file);
          /* NOTE: 16-bit chars not supported */
          readcoords(file, &buf[n].x, &buf[n].y, c->flags & fff_coord12);
        } while (buf[n++].code);/* char=0 stop */
        c->composite = Error_Memory(malloc(n * sizeof(*c->composite)),
                                    "reading font char");
        memcpy(c->composite, buf, n * sizeof(*c->composite));
      }
    } else {			/* not outlines, so just read raw
                                 * data */
      len = len - (int) (ftell(file) - pos);	/* length of remaining
                                                 * data */
      c->data = Error_Memory(malloc(len), "reading font char");
      c->datasize = len;
      fread(c->data, len, 1, file);
    }
  }
  return c;
}

/* Get the scaffold lines for cc, NULL if none */
Fontfile_Scaffold *
Fontfile_GetScaffold(Fontfile * ff, int cc)
{
  if (!ff->scaffold || cc >= ff->ns)
    return NULL;
  else
    return ff->scaffold[cc];
}
