/*
 *   tfmaux.c
 *
 *   This file is part of the ttf2pk package.
 *
 *   Copyright 1997-1998 by
 *     Frederic Loyer <loyer@ensta.fr>
 *     Werner Lemberg <wl@gnu.org>
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "ttf2tfm.h"
#include "newobj.h"
#include "tfmaux.h"
#include "errormsg.h"


static long nextd;   /* smallest value that will give a different mincover */


static int lf, lh, nw, nh, nd, ni, nl, nk, ne, np;
static int bc, ec;

static long *header, *charinfo,
            *width, *height, *depth,
            *ligkern, *kerns, *tparam,
            *italic;


static int source[257];     /* utility variables for sorting tfm arrays */
static int unsort[257];



/*
 *   The next routine simply scales something.
 *   Input is in TFM units per em.  Output is in FIXFACTORths of units
 *   per em.  We use 1 em = 1000 TFM units.
 */

static long
scale(long what)
{
  return ((what / 1000) * FIXFACTOR) +
         (((what % 1000) * FIXFACTOR) + 500) / 1000;
}


/*
 *   Next we need a routine to reduce the number of distinct dimensions
 *   in a TFM file.  Given an array what[0]..what[oldn-1], we want to
 *   group its elements into newn clusters, in such a way that the maximum
 *   difference between elements of a cluster is as small as possible.
 *   Furthermore, what[0]=0, and this value must remain in a cluster by
 *   itself.  Data such as `0 4 6 7 9' with newn=3 shows that an iterative
 *   scheme in which 6 is first clustered with 7 will not work.  So we
 *   borrow a neat algorithm from METAFONT to find the true optimum.
 *   Memory location what[oldn] is set to 0x7FFFFFFFL for convenience.
 */


/*
 *   Tells how many clusters result, given max difference d.
 */

static int
mincover(long *what,
         register long d)
{
  register int m;
  register long l;
  register long *p;


  nextd = 0x7FFFFFFFL;
  p = what+1;
  m = 1;

  while (*p < 0x7FFFFFFFL)
  {
    m++;
    l = *p;
    while (*++p <= l + d)
      ;
    if (*p - l < nextd)
      nextd = *p - l;
  }
  return m;
}


static void
remap(long *what,
      int oldn,
      int newn,
      int *source,
      int *unsort)
{
  register int i, j;
  register long d, l;

  what[oldn] = 0x7FFFFFFFL;
  for (i = oldn-1; i > 0; i--)
  {
    d = what[i];
    for (j = i; what[j+1] < d; j++)
    {
      what[j] = what[j+1];
      source[j] = source[j+1];
    }
    what[j] = d;
    source[j] = i;
  }

  i = mincover(what, 0L);
  d = nextd;
  while (mincover(what, d + d) > newn)
    d += d;
  while (mincover(what, d) > newn)
    d = nextd;

  i = 1;
  j = 0;
  while (i < oldn)
  {
    j++;
    l = what[i];
    unsort[source[i]] = j;
    while (what[++i] <= l + d)
    {
      unsort[source[i]] = j;
      if (i - j == oldn - newn)
        d = 0;
    }
    what[j] = (l + what[i-1])/2;
  }
}


void
write16(register short what,
        register FILE *out)
{
  (void)fputc(what >> 8, out);
  (void)fputc(what & 0xFF, out);
}


void
writearr(register long *p,
         register int n,
         register FILE *out)
{
  while (n)
  {
    write16((short)(*p >> 16), out);
    write16((short)(*p & 65535), out);
    p++;
    n--;
  }
}


void
writesarr(long *what,
          int len,
          FILE *out)
{
  register long *p;
  int i;


  p = what;
  i = len;
  while (i)
  {
    *p = scale(*p);
    (void)scale(*p);    /* need this kludge for some compilers */
    p++;
    i--;
  }
  writearr(what, len, out);
}


static void
makebcpl(register long *p,
         register char *s,
         register int n)
{
  register long t;
  register long sc;


  if (strlen(s) < n)
    n = strlen(s);
  t = ((long)n) << 24;
  sc = 16;

  while (n > 0)
  {
    t |= ((long)(*(unsigned char *)s++)) << sc;
    sc -= 8;
    if (sc < 0)
    {
      *p++ = t;
      t = 0;
      sc = 24;
    }
    n--;
  }
  *p++ = t;
}


static long
checksum(ttfinfo **array)
{
  int i;
  unsigned long s1 = 0, s2 = 0;
  char *p;
  ttfinfo *ti;


  for (i = 0; i < 256; i++)
    if (NULL != (ti = array[i]))
    {
      s1 = ((s1 << 1) ^ (s1 >> 31)) ^ ti->width; /* cyclic left shift */
      s1 &= 0xFFFFFFFF;         /* in case we're on a 64-bit machine */

      for (p = ti->adobename; *p; p++)
        s2 = (s2 * 3) + *p;
    }

  s1 = (s1 << 1) ^ s2;
  return s1;
}


int
transform(register int x, register int y,
          float ef, float sl)
{
  register double acc;


  acc = ef * x + sl * y;
  return (int)(acc >= 0 ? floor(acc + 0.5) : ceil(acc - 0.5));
}


int
buildtfm(Font *fnt, int range_only)
{
  register int i, j;
  register ttfinfo *ti;
  long cksum;
  double Slant;


  for (i = 0; i <= 0xFF && fnt->inencptrs[i] == NULL; i++)
    ;
  bc = i;
  for (i = 0xFF; i >= 0 && fnt->inencptrs[i] == NULL; i--)
    ;
  ec = i;

  if (ec < bc)
  {
    if (range_only)
      return 0;
    else
      oops("No TTF characters.");
  }

  header = (long *)mymalloc(40000L);
  cksum = checksum(fnt->inencptrs);
  header[0] = cksum;
  header[1] = 0xA00000;                     /* 10pt design size */
  makebcpl(header + 2, fnt->codingscheme, 39);
  makebcpl(header + 12, fnt->fullname, 19);
  lh = 17;
  charinfo = header + lh;

  width = charinfo + (ec - bc + 1);
  width[0] = 0;
  nw = 1;

  for (i = bc; i <= ec; i++)
    if (NULL != (ti = fnt->inencptrs[i]))
    {
      width[nw] = ti->width;
      for (j = 1; width[j] != ti->width; j++)
        ;
      ti->wptr = j;
      if (j == nw)
        nw++;
    }
  if (nw > 256)
    oops("256 chars with different widths.");

  depth = width + nw;
  depth[0] = 0;
  nd = 1;

  for (i = bc; i <= ec; i++)
    if (NULL != (ti = fnt->inencptrs[i]))
    {
      depth[nd] = -ti->lly;
      for (j = 0; depth[j] != -ti->lly; j++)
        ;
      ti->dptr = j;
      if (j == nd)
        nd++;
    }

  if (nd > 16)
  {
    remap(depth, nd, 16, source, unsort);
    nd = 16;
    for (i = bc; i <= ec; i++)
      if (NULL != (ti = fnt->inencptrs[i]))
        ti->dptr = unsort[ti->dptr];
  }

  height = depth + nd;
  height[0] = 0;
  nh = 1;

  for (i = bc; i <= ec; i++)
    if (NULL != (ti = fnt->inencptrs[i]))
    {
      height[nh] = ti->ury;
      for (j = 0; height[j] != ti->ury; j++)
        ;
      ti->hptr = j;
      if (j == nh)
        nh++;
    }

  if (nh > 16)
  {
    remap(height, nh, 16, source, unsort);
    nh = 16;
    for (i = bc; i <= ec; i++)
      if (NULL != (ti = fnt->inencptrs[i]))
        ti->hptr = unsort[ti->hptr];
  }

  italic = height + nh;
  italic[0] = 0;
  ni = 1;

  for (i = bc; i <= ec; i++)
    if (NULL != (ti = fnt->inencptrs[i]))
    {
      italic[ni] = ti->urx - ti->width;
      if (italic[ni] < 0)
        italic[ni] = 0;
      for (j = 0; italic[j] != italic[ni]; j++)
        ;
      ti->iptr = j;
      if (j == ni)
        ni++;
    }

  if (ni > 64)
  {
    remap(italic, ni, 64, source, unsort);
    ni = 64;
    for (i = bc; i <= ec; i++)
      if (NULL != (ti = fnt->inencptrs[i]))
        ti->iptr = unsort[ti->iptr];
  }

  for (i = bc; i <= ec; i++)
    if (NULL != (ti = fnt->inencptrs[i]))
      charinfo[i - bc] = ((long)(ti->wptr)<<24) +
                         ((long)(ti->hptr)<<20) +
                         ((long)(ti->dptr)<<16) +
                         ((long)(ti->iptr)<<10);
    else
      charinfo[i - bc] = 0;

  ligkern = italic + ni;
  nl = 0;               /* ligatures and kerns omitted from raw TeX font */
  kerns = ligkern + nl;
  nk = 0;

  Slant = fnt->slant - fnt->efactor * tan(fnt->italicangle * (M_PI / 180.0));

  tparam = kerns + nk;
  tparam[0] = (long)(FIXFACTOR * Slant + 0.5);
  tparam[1] = scale((long)fnt->fontspace);
  tparam[2] = (fnt->fixedpitch ? 0 : scale((long)(300 * fnt->efactor + 0.5)));
  tparam[3] = (fnt->fixedpitch ? 0 : scale((long)(100 * fnt->efactor + 0.5)));
  tparam[4] = scale((long)fnt->xheight);
  tparam[5] = scale((long)(1000 * fnt->efactor + 0.5));
  np = 6;

  return 1;
}


void
writetfm(Font *fnt)
{
  FILE *out;
  char *tfm_name;
  int len = 0;


  if (fnt->tfm_path)
    len += strlen(fnt->tfm_path);
  len += strlen(fnt->fullname);
  len += strlen(fnt->tfm_ext);
  len++;

  tfm_name = (char *)mymalloc(len);
  tfm_name[0] = '\0';
  if (fnt->tfm_path)
    strcat(tfm_name, fnt->tfm_path);
  strcat(tfm_name, fnt->fullname);
  strcat(tfm_name, fnt->tfm_ext);

  if ((out = fopen(tfm_name, "wb")) == NULL)
    oops("Cannot open tfm file `%s'.", tfm_name);

  free(tfm_name);


  lf = 6 + lh + (ec - bc + 1) + nw + nh + nd + ni + nl + nk + ne + np;

  write16(lf, out);
  write16(lh, out);
  write16(bc, out);
  write16(ec, out);
  write16(nw, out);
  write16(nh, out);
  write16(nd, out);
  write16(ni, out);
  write16(nl, out);
  write16(nk, out);
  write16(ne, out);
  write16(np, out);
  writearr(header, lh, out);
  writearr(charinfo, ec - bc + 1, out);
  writesarr(width, nw, out);
  writesarr(height, nh, out);
  writesarr(depth, nd, out);
  writesarr(italic, ni, out);
  writearr(ligkern, nl, out);
  writesarr(kerns, nk, out);
  writearr(tparam, np, out);

  free(header);
  fclose(out);
}


/* end */
