/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*
 *
 * 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.FontWrite 4.2 1998/02/13 17:23:01 stoklund Stable $";

/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*
 * $Revision: 4.2 $	$Date: 1998/02/13 17:23:01 $
 *
 * Creating and writing RISC OS font files.
 * Outlines and IntMetrics. Always writes newest version.
 * Originally part of Fontfile.c 1.4
 *
 * $Log: TFTLib.c.FontWrite $
 * Revision 4.2  1998/02/13 17:23:01  stoklund
 * (Fontfile_FreeCharacter, Fontfile_FreeScaffold): Moved to FontCommon.c
 * (Intmetrics_Create, setmapsize, Intmetrics_SetSize): Redundant assertions removed.
 * (<many>): Heap checking removed
 * (writecharacter): outlines list is now int rather than short
 *
 * Revision 4.1  1998/01/29 23:27:23  stoklund
 * OSLib call in X form.
 *
 * Revision 3.3  1997/11/02 23:17:11  stoklund
 * Errors made GNU compliant
 *
 * Revision 3.2  1997/11/01 12:45:51  stoklund
 * Error library and heap checking.
 * The table entry in Fontfile has been replaced by bmpsize struct.
 *
 * Revision 3.1  1997/10/13 17:55:03  stoklund
 * New const char * prototypes.
 *
 * Revision 2.1  1997/07/04 20:44:28  stoklund
 * Writes error message when files don't open.
 * Copyright fix, Log cleanup
 *
 * Revision 1.5  1997/04/13 15:55:34  stoklund
 * Compaction of outlines.
 *
 * Revision 1.4  1997/04/08 22:18:56  stoklund
 * Lots of memory debugging added.
 * Bug with Intmetrics::misc initialisation fixed.
 * Kern array can now be NULL.
 * Intmetrics_Compact: yoffset and kern arrays are removed when empty.
 * Fontfile_Create allocates one chunk from start.
 * 	and ns bug fixed (must be 1=tablesize).
 *
 * Revision 1.3  1997/04/05 17:36:36  stoklund
 * Box16_Union added.
 *
 * Revision 1.2  1997/04/04 23:38:24  stoklund
 * Memory leaks removed.
 * map array corrected.
 * bbox writing is now correct.
 *
 * Revision 1.1  1997/04/01 20:57:35  stoklund
 * Initial revision
 *
 *- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/

/* {{{ ---	Include files		--- */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "os:font.h"
#include "os:osfile.h"
#include "os:osfscontro.h"

#include "Debug.h"
#include "Error.h"
#include "Fontfile.h"

/* }}} ---	Include files		--- */
/* {{{ ---	Box16 bounding box	--- */
/* Expand box to contain point (x,y) */
void
Box16_UnionPoint(Box16 * a, int x, int y)
{
  if (x < a->x0)
    a->x0 = x;
  if (y < a->y0)
    a->y0 = y;
  if (x > a->x1)
    a->x1 = x;
  if (y > a->y1)
    a->y1 = y;
}
/* Expand box a to contain box b */
void
Box16_Union(Box16 * a, Box16 * b)
{
  if (b->x0 < a->x0)
    a->x0 = b->x0;
  if (b->y0 < a->y0)
    a->y0 = b->y0;
  if (b->x1 > a->x1)
    a->x1 = b->x1;
  if (b->y1 > a->y1)
    a->y1 = b->y1;
}
/* Expand box a to contain box b, offset (x,y) */
void
Box16_UnionOffset(Box16 * a, Box16 * b, int x, int y)
{
  if (b->x0 + x < a->x0)
    a->x0 = b->x0 + x;
  if (b->y0 + y < a->y0)
    a->y0 = b->y0 + y;
  if (b->x1 + x > a->x1)
    a->x1 = b->x1 + x;
  if (b->y1 + y > a->y1)
    a->y1 = b->y1 + y;
}
/* }}} ---	Box16 bounding box	--- */
/* {{{ ---	Misc functions		--- */
/* Tell FontManager to recache this file */
static void
recachefontfile(const char *filename)
{
  char            buf[256];
  int             remain;

  if (!xosfscontrol_canonicalise_path(filename,
                                      buf, 0, 0, 256, &remain)) {
    xfont_un_cache_file(buf, 0);
    xfont_un_cache_file(buf, 1);
  }
}
/* }}} ---	Misc functions		--- */
/* {{{ ---	Intmetrics files	--- */

/* Allocate blank tables with room for n chars and flags */
void
Intmetrics_Create(Intmetrics * im, int n, Intmetrics_Flags flags)
{
  im->n = n;
  im->version = 2;		/* we always write version 2 files */
  if (n > 255) {		/* we can't have a map in this case */
    im->m = n;
    im->map = NULL;
    flags |= imf_mapsize;
  } else {
    /* We allocate an initial map of 128 chars. It will auto-extend */
    im->m = 128;
    im->map = Error_Memory(calloc(128, sizeof(im->map[0])),
                           "making IntMetrics");
  }

  im->flags = flags;

  if (!(flags & imf_nobbox)) {
    im->bbox = Error_Memory(calloc(n, sizeof(im->bbox[0])),
                            "making IntMetrics");
  } else
    im->bbox = NULL;

  if (!(flags & imf_noxoffset)) {
    im->xoffset = Error_Memory(calloc(n, sizeof(im->xoffset[0])),
                               "making IntMetrics");
  } else
    im->xoffset = NULL;

  if (!(flags & imf_noyoffset)) {
    im->yoffset = Error_Memory(calloc(n, sizeof(im->yoffset[0])),
                               "making IntMetrics");
  } else
    im->yoffset = NULL;

  if (flags & imf_moredata) {
    im->misc = Error_Memory(calloc(1, sizeof(im->misc[0])),
                            "making IntMetrics");
    /* NOTE kern[0] is never used */
    im->kern = Error_Memory(calloc(im->m, sizeof(im->kern[0])),
                            "making IntMetrics");
  } else {
    im->misc = NULL;
    im->kern = NULL;
  }
}

/* Set a new mapsize, new entries cleared */
/* DO NOT use to remove a map. (creating is OK) */
static void
setmapsize(Intmetrics * im, int newsize)
{
  int             n, oldsize;

  if (im->map) {		/* we already have a map */
    oldsize = im->m;
    im->map = Error_Memory(realloc(im->map, newsize), "making IntMetrics");
    if (newsize > oldsize)
      memset(im->map + oldsize, 0, newsize - oldsize);
  } else {
    /* There was no map, so char codes 0-255 were used as index */
    im->map = Error_Memory(calloc(newsize, 1), "making IntMetrics");
    assert(im->map != NULL);
    for (n = 0; n < 255 && n < newsize; n++)
      im->map[n] = n;
  }
  /* alter kern table size */
  if (im->kern) {
    if (newsize > im->m) {
      im->kern = Error_Memory(realloc(im->kern,
                                      newsize * sizeof(im->kern[0])),
                              "making IntMetrics");
      assert(im->kern != NULL);
      memset(im->kern + im->m, 0, (newsize - im->m) * sizeof(im->kern[0]));
    } else
      for (n = newsize; n < im->m; n++)
        free(im->kern[n]);
  }
  im->m = newsize;
}

/* Copy slot b into slot a */
static void
copyslot(Intmetrics * im, int a, int b)
{
  if (im->bbox)
    im->bbox[a] = im->bbox[b];
  if (im->xoffset)
    im->xoffset[a] = im->xoffset[b];
  if (im->yoffset)
    im->yoffset[a] = im->yoffset[b];
}

/*
 * Force char code cc to be mapped to index idx or vacant slot if
 * idx<0.
 */
/* Returns the index allocated for cc, negative if error */
int
Intmetrics_MapChar(Intmetrics * im, int cc, int idx)
{
  /* Do we have a map? */
  if (im->map) {
    /* find free slot if requested */
    if (idx < 0) {
      int             tbl[256];

      /* initialize table */
      for (idx = 0; idx < 256; idx++)
        tbl[idx] = -1;
      /* make reverse lookup (slot -> char code) */
      for (idx = 0; idx < im->m; idx++)
        tbl[im->map[idx]] = idx;
      /* find first vacant slot!=0 (slot 0 means undef) */
      for (idx = 1; idx < 256; idx++)
        if (tbl[idx] == -1)
          break;
    }
    if (idx >= im->n)
      return -1;		/* no more slots available */
    /* we only have a map if im->n <= 256 */
    /* enlarge map if necesary */
    if (cc >= im->m)
      setmapsize(im, (cc + 32) & ~31);

    im->map[cc] = idx;
    return idx;
  } else {			/* no map */
    if (cc >= im->n)
      return -1;		/* no room for this char */
    /* Copy entries from idx if in range */
    if (idx >= 0 && idx < im->n)
      copyslot(im, cc, idx);
    return cc;
  }
}

/*
 * Change the number of available slots. If shortened, slots will be
 * lost
 */
/* Returns old size */
int
Intmetrics_SetSize(Intmetrics * im, int size)
{
  int             oldsize = im->n;

  if (im->bbox) {
    im->bbox = Error_Memory(realloc(im->bbox, size * sizeof(*im->bbox)),
                            "making IntMetrics");
    if (size > oldsize)
      memset(im->bbox + oldsize, 0, (size - oldsize) * sizeof(*im->bbox));
  }
  if (im->xoffset) {
    im->xoffset = Error_Memory(realloc(im->xoffset,
                                       size * sizeof(*im->xoffset)),
                               "making IntMetrics");
    if (size > oldsize)
      memset(im->xoffset + oldsize, 0,
             (size - oldsize) * sizeof(*im->xoffset));
  }
  if (im->yoffset) {
    im->yoffset = Error_Memory(realloc(im->yoffset,
                                       size * sizeof(*im->yoffset)),
                               "making IntMetrics");
    if (size > oldsize)
      memset(im->yoffset + oldsize, 0,
             (size - oldsize) * sizeof(*im->yoffset));
  }
  im->n = size;
  return oldsize;
}

/* find a slot equal to slot with a lower (or equal) index. */
static int
equalslot(Intmetrics * im, int slot)
{
  int             n;

  for (n = 0; n < slot; n++) {
    /* 1) compare xoffset */
    if (!im->xoffset || im->xoffset[n] == im->xoffset[slot]) {
      /* 2) compare yoffset */
      if (!im->yoffset || im->yoffset[n] == im->yoffset[slot]) {
        /* 3) compare bbox */
        if (!im->bbox ||
            !memcmp(im->bbox + n, im->bbox + slot, sizeof(Box16)))
          break;
      }
    }
  }
  /* We stopped because n==slot or the slots are equal */
  return n;
}

/*
 * Compact the file as much as possible by joining equal slots. A map
 * is created if necessary, all indexes are invalidated. Returns new
 * number of slots.
 */
int
Intmetrics_Compact(Intmetrics * im)
{
  int             np, op, *newpos;

  /* Remove redundant offsets */
  if (im->yoffset) {
    for (np = 0; np < im->n; np++)
      if (im->yoffset[np] != 0)
        break;
    if (np == im->n) {
      free(im->yoffset);
      im->yoffset = NULL;
    }
  }
  /* Remove missing kerning */
  if (im->kern) {
    int             c;
    int             flag = 0;
    for (np = 0; np < im->m; np++)
      if (im->kern[np] != NULL) {
        flag |= 1;		/* bit0 = kern data found */
        /* Now check for 16-bit chars */
        for (c = 0; im->kern[np][c].rightchar; c++)
          if (im->kern[np][c].rightchar > 255) {
            flag |= 2;		/* bit2 = 16-bit chars found */
            break;		/* Look no further */
          }
      }
    if (flag == 0) {		/* No kern data at all */
      free(im->kern);
      im->kern = NULL;
    } else {			/* There is kern data, set imf_kern16 */
      if (flag & 2)
        im->flags |= imf_kern16;/* we have 16-bit */
      else
        im->flags &= ~imf_kern16;	/* only 8-bit kern chars */
    }
  }
  /* table to hold map permutation */
  newpos = Error_Memory(malloc(im->n * sizeof(int)),
                        "compacting IntMetrics");
  assert(newpos != NULL);
  /* compare slots one at at time */
  for (np = op = 0; op < im->n && np < 256; op++) {
    int             eq = equalslot(im, op);	/* previous duplicate */
    if (eq < op)
      newpos[op] = newpos[eq];	/* yes, so give same pos */
    else
      newpos[op] = np++;	/* new position of slot op */
  }
  /* There can only be a map if n<=256 */
  if (op < im->n) {		/* premature stop due to large np */
    free(newpos);
    return im->n;
  }
  /* create a map if there is none */
  if (!im->map) {
    setmapsize(im, im->n);
    /* Generate map from newpos array */
    for (op = 0; op < im->m; op++)
      im->map[op] = newpos[op];
  } else {
    /* remap, using newpos array */
    for (op = 0; op < im->m; op++) {
      im->map[op] = newpos[im->map[op]];
    }
  }

  /* remove redundant slots */
  for (op = 0; op < im->n; op++) {
    if (newpos[op] != op)
      copyslot(im, newpos[op], op);
  }
  /* new size */
  if (np != im->n)
    Intmetrics_SetSize(im, np);

  free(newpos);
  return np;
}

/* Calculate the bounding box of the font from the bbox table */
/* If there is no bbox table, bbox is untouched and */
/* 0 is returned, otherwise 1 */
int
Intmetrics_GetBBox(Intmetrics * im, Box16 * bbox)
{
  int             n;

  if (!im->bbox)
    return 0;			/* Nothing to calculate */
  *bbox = im->bbox[0];		/* Starting point */
  for (n = 1; n < im->n; n++)
    Box16_Union(bbox, im->bbox + n);
  return 1;
}

/* Write the Intmetrics to file. */
/* Compaction should be done manually if necessary */
/* returns 0 on failure */
int
Intmetrics_Write(Intmetrics * im, const char *filename)
{
  FILE           *file;
  int             n;

  Error_Filename(filename);

  /* Set the flags according to the truth */
  n = 0;
  if (!im->bbox)
    n |= imf_nobbox;
  if (!im->xoffset)
    n |= imf_noxoffset;
  if (!im->yoffset)
    n |= imf_noyoffset;
  if (im->misc || im->kern)
    n |= imf_moredata;
  if (im->m != 256)
    n |= imf_mapsize;
  /* We don't check if kern chars are 16-bit */
  im->flags = (im->flags & imf_kern16) | n;

  file = fopen(filename, "wb");
  if (!file) {
    Error_Error("cannot write to file");
    return 0;
  }
  /* Font name should be '\r' padded */
  for (n = 0; n < 40; n++)
    if (im->name[n] < 32)
      break;
  memset(im->name + n, '\r', 40 - n);
  fwrite(im->name, 40, 1, file);
  /* 2 x 16 (?) */
  n = 16;
  fwrite(&n, 4, 1, file);
  fwrite(&n, 4, 1, file);
  fputc(im->n & 0xff, file);	/* nlo */
  fputc(2, file);		/* version is always 2 */
  fputc(im->flags, file);	/* flags */
  fputc(im->n >> 8, file);	/* nhi */
  /* do we have a mapsize? */
  if (im->flags & imf_mapsize) {
    if (im->map)
      fwrite(&im->m, 2, 1, file);
    else {
      fputc(0, file);
      fputc(0, file);
    }
  }
  /* write map if any */
  if (im->map)
    fwrite(im->map, 1, im->m, file);
  /* write bboxes if any */
  if (!(im->flags & imf_nobbox)) {
    int             i;
    for (i = 0; i < im->n; i++)
      fwrite(&im->bbox[i].x0, 2, 1, file);
    for (i = 0; i < im->n; i++)
      fwrite(&im->bbox[i].y0, 2, 1, file);
    for (i = 0; i < im->n; i++)
      fwrite(&im->bbox[i].x1, 2, 1, file);
    for (i = 0; i < im->n; i++)
      fwrite(&im->bbox[i].y1, 2, 1, file);
  }
  /* write character widths */
  if (!(im->flags & imf_noxoffset))
    fwrite(im->xoffset, 2, im->n, file);
  if (!(im->flags & imf_noyoffset))
    fwrite(im->yoffset, 2, im->n, file);
  /* misc data */
  if (im->flags & imf_moredata) {
    long            tablepos;
    unsigned short  offset[4];

    tablepos = ftell(file);
    fwrite(offset, 2, 4, file);	/* write bogus table */
    /* Misc data area */
    offset[0] = (unsigned short) (ftell(file) - tablepos);
    if (im->misc)
      fwrite(im->misc, 2, 14, file);

    /* Kern data */
    offset[1] = (unsigned short) (ftell(file) - tablepos);
    if (im->kern) {
      for (n = 0; n < im->m; n++)
        if (im->kern[n]) {
          int             m;
          /* left hand char */
          fputc(n & 0xff, file);
          if (im->flags & imf_kern16)
            fputc(n >> 8, file);
          /* right hand pairs */
          for (m = 0; im->kern[n][m].rightchar != 0; m++) {
            /* right hand char */
            fputc(im->kern[n][m].rightchar & 0xff, file);
            if (im->flags & imf_kern16)
              fputc(im->kern[n][m].rightchar >> 8, file);
            /* x-kern */
            if (!(im->flags & imf_noxoffset))
              fwrite(&im->kern[n][m].xkern, 2, 1, file);
            /* y-kern */
            if (!(im->flags & imf_noyoffset))
              fwrite(&im->kern[n][m].ykern, 2, 1, file);
          }
          /* Terminate this left-hand char */
          fputc(0, file);
          if (im->flags & imf_kern16)
            fputc(0, file);
        }
      /* Terminate kern list */
      fputc(0, file);
      if (im->flags & imf_kern16)
        fputc(0, file);
    }
    /* Reserved areas #1 and #2 are always null */
    offset[2] = offset[3] = (unsigned short) (ftell(file) - tablepos);
    /* Now return and write offset table */
    fseek(file, tablepos, SEEK_SET);
    fwrite(offset, 2, 4, file);
  }
  fclose(file);
  xosfile_set_type(filename, 0xff6 /* osfile_TYPE_FONT */ );
  recachefontfile(filename);
  return 1;
}

/* }}} */
/* {{{ ---	Fontfiles		--- */
/* Create a blank Fontfile */
void
Fontfile_Create(Fontfile * ff, int bpp)
{
  ff->bpp = bpp;
  ff->version = 8;		/* we always write version 8 */
  ff->designsize = 0;		/* To be filled in by the user */
  ff->flags = fff_chunkflag;	/* Because version=8, subpix not
                                 * supported */
  ff->nchunks = 1;		/* Will auto-expand in SetCharacter */
  ff->chunk = Error_Memory(calloc(1, sizeof(*ff->chunk)),
                           "making fontfile");
  ff->ns = 1;			/* Will auto-expand in SetScaffold */
  ff->scaffold = Error_Memory(calloc(1, sizeof(*ff->scaffold)),
                              "making fontfile");
  assert(ff->scaffold != NULL);
  ff->scaffoldflags = 0;	/* sensible defaults */
  ff->bbox.x0 = ff->bbox.y0 = 0;
  ff->bbox.x1 = ff->bbox.y1 = 0;
}

/* Set character data for cc, return old data or NULL */
/* The old data should be freed by user */
Fontfile_Character *
Fontfile_SetCharacter(Fontfile * ff, int cc,
                      Fontfile_Character * ch)
{
  int             chunk;
  Fontfile_Character *old;

  assert(cc >= 0);
  chunk = cc >> 5;
  /* increase nchunks if necessary */
  if (chunk >= ff->nchunks) {
    /* allocate chunk array */
    ff->chunk = Error_Memory(realloc(ff->chunk,
                                  (chunk + 1) * sizeof(*ff->chunk)),
                             "making fontfile");
    assert(ff->chunk != NULL);
    /* new chunks are null */
    memset(ff->chunk + ff->nchunks, 0,
           (chunk + 1 - ff->nchunks) * sizeof(*ff->chunk));
    ff->nchunks = chunk + 1;
  }
  /* null chunks are created */
  if (!ff->chunk[chunk].character) {
    ff->chunk[chunk].flags =
      (ff->flags & 3) | 0x80000000;	/* default flags, no
                                         * dependency */
    ff->chunk[chunk].dependency = 1 << chunk;	/* we depend on us */
    ff->chunk[chunk].mult = ff->flags & 1 ? 4 : 1;
    if (ff->flags & 2)
      ff->chunk[chunk].mult *= 4;
    ff->chunk[chunk].character
      = Error_Memory(calloc(ff->chunk[chunk].mult,
                            32 * sizeof(Fontfile_Character *)),
                     "making fontfile");
    assert(ff->chunk[chunk].character != NULL);
  }
  /* NOTE no support for subpixeling */
  old = ff->chunk[chunk].character[0][cc & 0x1f];
  ff->chunk[chunk].character[0][cc & 0x1f] = ch;
  return old;
}

/* Set the scaffold lines for cc, NULL for none */
/* The old data should be freed by user */
Fontfile_Scaffold *
Fontfile_SetScaffold(Fontfile * ff, int cc,
                     Fontfile_Scaffold * sc)
{
  Fontfile_Scaffold *old;

  /* Extend scaffold index if necessary */
  if (cc >= ff->ns) {
    int             nns = (cc + 32) & ~31;

    ff->scaffold = Error_Memory(realloc(ff->scaffold,
                                        nns * sizeof(*ff->scaffold)),
                                "making fontfile");
    assert(ff->scaffold != NULL);
    memset(ff->scaffold + ff->ns, 0,
           (nns - ff->ns) * sizeof(*ff->scaffold));
    ff->ns = nns;
  }
  old = ff->scaffold[cc];
  ff->scaffold[cc] = sc;
  return old;
}

/*
 * Check if the given signed integer will fit in 8 bits, return 1 if
 * it won't
 */
static int
not8bit(int n)
{
  return n < -128 || n > 127;
}

/*
 * Calculate the bounding box of the character and set the ff_coord12
 * flag. Returns 0 if there was no character definition at all
 */
static int
character_bbox(Fontfile * ff,
               Fontfile_Character * ch, unsigned *dep)
{
  int             n, m;
  Fontfile_Character *ch2;

  ch->flags &= ~fff_coord12;	/* assume 8bit coords */

  /* The following only applies to outlines */
  if (ch->flags & fff_outline) {
    /* Calc from composite bases */
    if ((ch->flags & fff_composite) &&
        NULL != (ch2 = Fontfile_GetCharacter(ff, ch->base))) {
      ch->bbox = ch2->bbox;
      *dep |= 1 << (ch->base >> 5);
    } else {
      /* Initialise bbox */
      ch->bbox.x0 = ch->bbox.y0 = 0x7fff;	/* Maximum short */
      ch->bbox.x1 = ch->bbox.y1 = -0x8000;	/* Minimum short */
    }

    /* Do we have an accent? */
    if (ch->flags & fff_accent &&
        NULL != (ch2 = Fontfile_GetCharacter(ff, ch->accent))) {
      if (not8bit(ch->accx) || not8bit(ch->accy))
        ch->flags |= fff_coord12;
      Box16_UnionOffset(&ch->bbox, &ch2->bbox, ch->accx, ch->accy);
      *dep |= 1 << (ch->accent >> 5);
    }
    /*
     * Get the outlines bbox. Note that the bbox of Bezier curves is
     * not calculated exactly, but we use the fact that a Bezier
     * curve is contained in the convex hull of the control points.
     */
    if (ch->outline) {
      n = 0;
      while ((ch->outline[n] & (3 | ol_stroke)) != 0) {
        /* Find the number of coordinates */
        switch (ch->outline[n] & 3) {
        case ol_move:
        case ol_line:
          m = 1;
          break;
        case ol_curve:
          m = 3;
          break;
        default:
          m = 0;
        }
        n++;			/* Point at coordinates */
        for (; m > 0; n += 2, m--)
          Box16_UnionPoint(&ch->bbox, ch->outline[n], ch->outline[n + 1]);
      }
    }
    /* Composite character inclusions */
    if (ch->composite) {
      for (n = 0; ch->composite[n].code; n++) {
        if (not8bit(ch->composite[n].x) || not8bit(ch->composite[n].y))
          ch->flags |= fff_coord12;
        *dep |= 1 << (ch->composite[n].code >> 5);
        ch2 = Fontfile_GetCharacter(ff, ch->composite[n].code);
        if (ch2)
          Box16_UnionOffset(&ch->bbox, &ch2->bbox,
                            ch->composite[n].x, ch->composite[n].y);
      }
    }
  }
  /* Set coord12 if there was large coordinates */
  if (not8bit(ch->bbox.x0) || not8bit(ch->bbox.x1) ||
      not8bit(ch->bbox.y0) || not8bit(ch->bbox.y1))
    ch->flags |= fff_coord12;

  /* if now x0>x1 then there was no points at all thus no chardef */
  return ch->bbox.x0 <= ch->bbox.x1;
}


/*
 * Compact the fontfile structure, by removing undefined characters,
 * chunks and scaffold entries. Also calculate char bboxes and font
 * bbox. Also set ff_coord12 flag and scaffoldflags:0
 */
void
Fontfile_Compact(Fontfile * ff)
{
  int             chunk, m, n, n8;
  int             maxchar = 0;	/* Last non-empty character met */

  /* Initialise bbox */
  ff->bbox.x0 = ff->bbox.y0 = 0x7fff;	/* Maximum short */
  ff->bbox.x1 = ff->bbox.y1 = -0x8000;	/* Minimum short */

  /* Go through all characters */
  for (chunk = 0; chunk < ff->nchunks; chunk++) {
    ff->chunk[chunk].dependency = 0;
    if (ff->chunk[chunk].character) {
      for (m = 0; m < ff->chunk[chunk].mult; m++) {
        for (n = 0; n < 32; n++) {
          Fontfile_Character *ch = ff->chunk[chunk].character[m][n];
          if (ch) {
            /* Calculate BBox and dependencies */
            if (!character_bbox(ff, ch, &ff->chunk[chunk].dependency)) {
              /* No char def, so delete */
              Fontfile_FreeCharacter(ch);
              ff->chunk[chunk].character[m][n] = NULL;
            } else {
              /* We have a char, so set maxchar and font bbox */
              maxchar = (chunk << 5) + n;
              Box16_Union(&ff->bbox, &ch->bbox);
            }
          }
        }
      }
    }
    /* Don't write dependency bytes if zero */
    if (ff->chunk[chunk].dependency)
      ff->chunk[chunk].flags |= 1 << 7;
    else
      ff->chunk[chunk].flags &= ~(1 << 7);
  }
  /* Calc # chunks used, don't reallocate */
  ff->nchunks = 1 + (maxchar >> 5);

  /*
   * Now we compact the scaffold indices. We need to find the highest
   * used scaffold. We also calculate the size of the total table to
   * be able to clear scaffold flags bit 0 if <32k
   */
  maxchar = 0;			/* last used scaffold entry */
  m = 0;			/* Bytes used when all base chars are
                                 * 16-bit */
  n8 = 0;			/* #entries with 8-bit base char */
  for (n = 1; n < ff->ns; n++) {
    /* Only defined scaffolds */
    if (ff->scaffold[n]) {
      if (ff->scaffold[n]->basechar == 0 && ff->scaffold[n]->lines == 0) {
        /* void scaffold definition */
        Fontfile_FreeScaffold(ff->scaffold[n]);
        ff->scaffold[n] = NULL;
      } else {
        /*
         * OK scaffold, 2 bytes basechar, 4 bytes flags 3 bytes per
         * line
         */
        maxchar = n;
        m += 6 + 3 * ff->scaffold[n]->lines;
        if (ff->scaffold[n]->basechar < 256)
          n8++;			/* count 8-bit bases */
      }
    }
  }
  ff->ns = maxchar + 1;		/* We don't reallocate for a few
                                 * bytes */
  /* Calc total table size, if it fits in 15 bits... */
  if (2 * ff->ns + m - n8 <= 0x7fff)
    ff->scaffoldflags &= ~fff_scafbase16;
  else
    ff->scaffoldflags |= fff_scafbase16;

}

static long     writescaffolds(Fontfile * ff, FILE * file);
static void     writechunk(struct fontfile_chunk * ch, FILE * file);
static void     writecharacter(Fontfile_Character * c, FILE * file);

/* Write Fontfile to disc */
/* Returns 0 on failure */
int
Fontfile_Write(Fontfile * ff, const char *filename)
{
  FILE           *file;
  long            chunktableoffsetpos;	/* file pos of offset to
                                         * chunk table */
  long            chunktablepos;/* file pos of chunk table */
  long            tablestart;	/* pos of table start */
  long            tablesize;	/* total size of table data (incl
                                 * size) */
  long            chunkpos[33];	/* pos of each chunk in file */

  int             zeros[] = {0, 0, 0, 0, 0};
  int             n;

  Error_Filename(filename);
  file = fopen(filename, "wb");
  if (!file) {
    Error_Error("cannot write to file");
    return 0;
  }
  fprintf(file, "FONT");	/* ID word */
  fputc(ff->bpp, file);
  fputc(8, file);		/* Version always 8 */
  /* if bpp=0 then designsize, else flags */
  /* Don't we love little-endian machines :-) */
  if (ff->bpp > 0)
    fwrite(&ff->flags, 2, 1, file);
  else
    fwrite(&ff->designsize, 2, 1, file);

  /* the bbox should have relative second coordniates. */
  ff->bbox.x1 -= ff->bbox.x0;
  ff->bbox.y1 -= ff->bbox.y0;
  fwrite(&ff->bbox, 2, 4, file);/* bbox is 4x short */
  ff->bbox.x1 += ff->bbox.x0;
  ff->bbox.y1 += ff->bbox.y0;
  /* The next word shall be written later, so remember pos */
  chunktableoffsetpos = ftell(file);
  fwrite(zeros, 4, 1, file);	/* Dummy fill */
  fwrite(&ff->nchunks, 4, 1, file);
  fwrite(&ff->ns, 4, 1, file);
  fwrite(&ff->scaffoldflags, 4, 1, file);
  fwrite(zeros, 4, 5, file);	/* 5 reserved words */

  /* End of Header, table follows */
  tablestart = ftell(file);
  if (ff->bpp > 0) {
    /* Bitmaps are simple, always 10 bytes of table */
    tablesize = 10;
    fwrite(&tablesize, 2, 1, file);	/* table size incl this short */
    fwrite(&ff->bmpsize, 2, 4, file);
  } else {
    /* For outlines we have scaffold data in the table */
    tablesize = writescaffolds(ff, file);
  }
  /* End of table, postamble follows */
  fseek(file, tablestart + tablesize, SEEK_SET);
  fprintf(file, "%s%c", ff->name, 0);	/* 0-terminated fontname */
  if (ff->bpp > 0) {
    /* Bitmaps say 999x999 point at 999 dpi */
    fprintf(file, "%dx%d point at %dx%d dpi%c",
            ff->bmpsize.xsize / 16, ff->bmpsize.ysize / 16,	/* point size */
            ff->bmpsize.xdpi, ff->bmpsize.ydpi, 0);
  } else {
    /* outlines just say 'Outlines' */
    fprintf(file, "Outlines%c", 0);
  }
  /* End of table postamble */
  while (ftell(file) & 3)
    fputc(0, file);		/* Word align */
  /* Here goes the chunktable */
  chunktablepos = ftell(file);
  fwrite(chunkpos, 4, ff->nchunks + 1, file);	/* Write dummy
                                                 * chunktable */

  /* Now write each chunk */
  for (n = 0; n < ff->nchunks; n++) {
    chunkpos[n] = ftell(file);	/* remember for chunktable */
    writechunk(ff->chunk + n, file);
    while (ftell(file) & 3)
      fputc(0, file);		/* Word align */
  }
  chunkpos[n] = ftell(file);	/* filesize */
  /* We are almost finished, but we have some pending offsets */
  /* 1) the pointer to the chunktable */
  fseek(file, chunktableoffsetpos, SEEK_SET);
  fwrite(&chunktablepos, 4, 1, file);
  /* 2) the chunktable itself. The last entry is the filesize */
  fseek(file, chunktablepos, SEEK_SET);
  fwrite(chunkpos, 4, ff->nchunks + 1, file);
  /* phew */
  fclose(file);
  xosfile_set_type(filename, 0xff6 /* osfile_TYPE_FONT */ );
  /* Recache with font manager */
  recachefontfile(filename);

  return 1;
}

/* Write scaffold lines to file incl first two bytes, return size */
static long
writescaffolds(Fontfile * ff, FILE * file)
{
  long            start = ftell(file);
  long            end;
  unsigned short *offset;
  int             n, m, i, mask;

  offset = Error_Memory(calloc(ff->ns, 2),	/* offset of each entry */
                        "writing fontfile");
  assert(offset != NULL);
  fwrite(offset, 2, ff->ns, file);	/* dummy fill */
  fputc(ff->threshold, file);
  for (n = 1; n < ff->ns; n++)
    if (ff->scaffold[n]) {
      offset[n] = (unsigned short) (ftell(file) - start);
      if (ff->scaffoldflags & fff_scafbase16) {
        /* base chars are always 16-bit */
        fwrite(&ff->scaffold[n]->basechar, 2, 1, file);
      } else {
        /* bit-15 of offset marks 16-bit */
        if (ff->scaffold[n]->basechar & 0xff00) {
          offset[n] |= 0x8000;	/* mark 16-bit char */
          fwrite(&ff->scaffold[n]->basechar, 2, 1, file);
        } else
          fputc(ff->scaffold[n]->basechar, file);	/* simple 8-bit */
      }
      /* It is crucial that localx/y match the given number of lines */
      mask = 0;
      for (i = 0; i < ff->scaffold[n]->lines; i++)
        mask |= 1 << ff->scaffold[n]->line[i].index;
      /* clear the lines that we don't have */
      ff->scaffold[n]->localx &= mask & 0x00ff;
      ff->scaffold[n]->localy &= (mask & 0xff00) >> 8;

      fputc(ff->scaffold[n]->basex, file);
      fputc(ff->scaffold[n]->basey, file);
      fputc(ff->scaffold[n]->localx, file);
      fputc(ff->scaffold[n]->localy, file);
      /* local scaffold lines follow */
      mask = ff->scaffold[n]->localx | (ff->scaffold[n]->localy << 8);
      /* we will not assume that the lines are sorted properly */
      for (m = 0; m < 16; m++)
        if (mask & (1 << m)) {
          unsigned        tmp;
          /*
           * find line of index m (it must be there, or localx/y is
           * wrong
           */
          for (i = 0; i < ff->scaffold[n]->lines; i++)
            if (ff->scaffold[n]->line[i].index == m)
              break;
          /* i gives the proper line */
          tmp = ff->scaffold[n]->line[i].coord & 0x0fff;	/* 0-11=coord */
          tmp |= ff->scaffold[n]->line[i].link << 12;	/* 12-15=link */
          fputc(tmp & 0xff, file);
          fputc((tmp >> 8) & 0xff, file);
          fputc(ff->scaffold[n]->line[i].width, file);
        }
    }
  /* OK, all written */
  end = ftell(file);
  offset[0] = (unsigned short) (end - start);	/* table size */
  /* Now return and write the offset array */
  fseek(file, start, SEEK_SET);
  fwrite(offset, 2, ff->ns, file);

  free(offset);
  return end - start;		/* return tablesize */
}


/* Write the given chunk to file. File points to end at return */
static void
writechunk(struct fontfile_chunk * ch, FILE * file)
{
  long            start;	/* Start of chunk, excluding flag
                                 * word */
  long            end;
  long            (*offset)[32];
  int             n, m;

  if (!ch->character)
    return;			/* NULL chunk is empty */
  fwrite(&ch->flags, 4, 1, file);	/* flag word */

  offset = Error_Memory(calloc(ch->mult, sizeof(*offset)),
                        "writing fontfile");
  assert(offset != NULL);
  start = ftell(file);		/* start of index */
  fwrite(offset, 32 * 4, ch->mult, file);	/* dummy fill */
  if (ch->flags & (1 << 7))	/* depend. bytes present */
    fwrite(&ch->dependency, 4, 1, file);

  /* chars follow in order */
  for (m = 0; m < ch->mult; m++)
    for (n = 0; n < 32; n++)
      if (ch->character[m][n]) {
        offset[m][n] = ftell(file) - start;
        writecharacter(ch->character[m][n], file);
      }
  /* all chars written, so wordalign end of chunk */
  while ((end = ftell(file)) & 3)
    fputc(0, file);

  /* return and write the index */
  fseek(file, start, SEEK_SET);
  fwrite(offset, 32 * 4, ch->mult, file);
  free(offset);
  /* point at end as promised */
  fseek(file, end, SEEK_SET);
}

/* write coordinate pair as 8- or 12-bit */
static void
writecoords(FILE * file, int x, int y, int bit12)
{
  if (bit12) {
    unsigned        tmp = (x & 0xfff) | ((y << 12) & 0xfff000);
    fputc(tmp & 0xff, file);
    fputc((tmp >> 8) & 0xff, file);
    fputc((tmp >> 16) & 0xff, file);
  } else {
    fputc(x & 0xff, file);
    fputc(y & 0xff, file);
  }
}

/* write character data to file, leaving file pointing at the end */
static void
writecharacter(Fontfile_Character * c, FILE * file)
{
  int             n;

  fputc(c->flags, file);

  if (c->flags & fff_outline) {	/* composite bases only for outlines */
    if (c->flags & fff_composite) {
      fputc(c->base, file);
      /* NOTE: 16-bit chars not supported */
    }
    if (c->flags & fff_accent) {
      fputc(c->accent, file);
      writecoords(file, c->accx, c->accy, c->flags & fff_coord12);
    }
  }
  /* More data follows if bitmap or outline and no base inclusions */
  if (!(c->flags & fff_outline) ||
      (c->flags & (fff_outline | fff_composite | fff_accent))
      == fff_outline) {
    /* bounding box */
    writecoords(file, c->bbox.x0, c->bbox.y0, c->flags & fff_coord12);
    /* The second pair is relative width and height */
    writecoords(file, c->bbox.x1 - c->bbox.x0,
                c->bbox.y1 - c->bbox.y0, c->flags & fff_coord12);

    if (c->flags & fff_outline) {
      int             typ, *s = c->outline;
      /* 1) list of move/curves */
      do {
        typ = *s++;
        /*
         * make sure that composite flag is included in last
         * terminator
         */
        if ((typ & (ol_stroke | 3)) == ol_terminate && c->composite)
          typ |= ol_composite;
        fputc(typ, file);
        switch (typ & 3) {
        case ol_curve:
          writecoords(file, s[0], s[1], c->flags & fff_coord12);
          writecoords(file, s[2], s[3], c->flags & fff_coord12);
          s += 4;
          /* fall through */
        case ol_move:
        case ol_line:
          writecoords(file, s[0], s[1], c->flags & fff_coord12);
          s += 2;
        }
      } while (typ & (ol_stroke | 3));	/* stop when stroke=0 and
                                         * terminator */

      /* Now the composite inclusions if any */
      if (c->composite) {
        for (n = 0; c->composite[n].code; n++) {
          fputc(c->composite[n].code, file);
          /* NOTE 16-bit chars not supported */
          writecoords(file, c->composite[n].x, c->composite[n].y,
                      c->flags & fff_coord12);
        }
        fputc(0, file);		/* terminate composite inclusions */
      }
    } else {			/* Not outlines i.e. bitmaps */
      /* just write raw data */
      fwrite(c->data, 1, c->datasize, file);
    }
  }
}

/* }}} */
