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

/*
 *   This program converts TTF files to TeX TFM files, and optionally
 *   to TeX VPL files that retain all kerning and ligature information.
 *   Both files make the characters not normally encoded by TeX available
 *   by character codes greater than 0x7F.
 */

/*
 *   Adapted from afm2tfm by F. Loyer <loyer@ensta.fr>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>         /* for size_t */
#include <string.h>

#include "ttf2tfm.h"
#include "newobj.h"
#include "ttfenc.h"
#include "ligkern.h"
#include "texenc.h"
#include "ttfaux.h"
#include "tfmaux.h"
#include "vplaux.h"
#include "errormsg.h"
#include "filesrch.h"
#include "parse.h"
#include "subfont.h"


char ident[] = "ttf2tfm version 1.0";
char progname[] = "ttf2tfm";            /* for error/warning messages */

/* command line options */

static char    makevpl;                 /* can be 1 or 2 */
static Boolean pedantic;
static Boolean quiet;
static Boolean forceoctal;



/*
 *   Re-encode the TTF font.
 */

static void
handlereencoding(Font *fnt)
{
  int i;
  ttfinfo *ti;
  char *p;


  if (fnt->inencname)
  {
    fnt->inencoding = readencoding(fnt->inencname, fnt, True);

    /* reset all pointers in the mapping table */
    for (i = 0; i <= 0xFF; i++)
      if (NULL != (ti = fnt->inencptrs[i]))
      {
        ti->incode = -1;
        fnt->inencptrs[i] = NULL;
      }

    /*
     *   Reencode TTF <--> raw TeX.  Only these code points will be used
     *   for the output encoding.
     */

    for (i = 0; i <= 0xFF; i++)
    {
      p = fnt->inencoding->vec[i];
      if (p && *p)
        if ((ti = findadobe(p, fnt->charlist)))
        {
          if (ti->incode >= 0)
          {
            warning("Character `%s' encoded twice in input encoding\n"
                    "         (positions %x and %x; the latter is ignored).",
                    p, ti->incode, i);
            fnt->inencoding->vec[i] = ".notdef";
            continue;
          }
          if (ti->charcode >= 0)
          {
            ti->incode = i;
            fnt->inencptrs[i] = ti;
          }
        }
        else
          warning("Cannot find character `%s'\n"
                  "         specified in input encoding.", p);
    }
    fnt->codingscheme = fnt->inencoding->name;
  }

  if (!quiet)
  {
    if (fnt->inencname)
      printf("\nUsing %s as input encoding.\n", fnt->inencname);
    else
    {
      printf(
        "\nUsing the first 256 glyphs in the following input encoding:\n\n");
      for (i = 0; i <= 0xFF; i++)
      {
        if ((ti = fnt->inencptrs[i]))
          printf("  0x%02x    %s\n", i, ti->adobename);
      }
      printf("\n");
    }
  }

  if (fnt->outencname)
    fnt->outencoding = readencoding(fnt->outencname, fnt, False);
  else
    fnt->outencoding = readencoding(NULL, fnt, False);
}


static void
assignchars(Font *fnt)
{
  register char **p;
  register int i, j;
  register ttfinfo *ti;
  int nextfree = 0x80;


  /*
   *   First, we assign all those that match perfectly.
   */

  for (i = 0, p = fnt->outencoding->vec; i <= 0xFF; i++, p++)
    if ((ti = findmappedadobe(*p, fnt->inencptrs)))
    {
      if (ti->outcode >= 0)
        fnt->nextout[i] = ti->outcode;       /* linked list */
      ti->outcode = i;
      fnt->outencptrs[i] = ti;
    }
    else if (strcmp(*p, ".notdef") != 0)
      warning("Cannot map character `%s'\n"
              "         specified in output encoding.", *p);

  if (pedantic)
    goto end;

  /*
   *   Next, we assign all the others, retaining the TTF code positions,
   *   possibly multiplying assigned characters, unless the output encoding
   *   was precisely specified.
   */

  for (i = 0; i <= 0xFF; i++)
    if ((ti = fnt->inencptrs[i]) &&
         ti->charcode >= 0 && ti->charcode <= 0xFF &&
         ti->outcode < 0 && fnt->outencptrs[ti->charcode] == NULL)
    {
      ti->outcode = ti->charcode;
      fnt->outencptrs[ti->charcode] = ti;
    }

  /*
   *   Finally, we map all remaining characters into free locations beginning
   *   with 0x80.
   */

  for (i = 0; i <= 0xFF; i++)
    if ((ti = fnt->inencptrs[i]) && ti->outcode < 0)
    {
      while (fnt->outencptrs[nextfree])
      {
        nextfree = (nextfree + 1) & 0xFF;
        if (nextfree == 0x80)
          goto finishup;        /* all slots full */
      }
      ti->outcode = nextfree;
      fnt->outencptrs[nextfree] = ti;
    }

finishup:

  /*
   *   Now, if any of the characters are encoded multiple times, we want
   *   ti->outcode to be the first one assigned, since that is most likely
   *   to be the most important one.  So we reverse the above lists.
   */

  for (i = 0; i <= 0xFF; i++)
    if ((ti = fnt->inencptrs[i]) && ti->outcode >= 0)
    {
      j = -1;
      while (fnt->nextout[ti->outcode] >= 0)
      {
        i = fnt->nextout[ti->outcode];
        fnt->nextout[ti->outcode] = j;
        j = ti->outcode;
        ti->outcode = i;
      }
      fnt->nextout[ti->outcode] = j;
    }

end:

  if (!quiet)
  {
    printf("\nUsing the following output encoding:\n\n");
    for (i = 0; i <= 0xFF; i++)
    {
      if ((ti = fnt->outencptrs[i]))
        printf("  0x%02x    %s\n", i, ti->adobename);
    }
    printf("\n");
  }
}


#define VERSION "\
Copyright (C) 1997-1998 Frederic Loyer and Werner Lemberg.\n\
There is NO warranty.  You may redistribute this software\n\
under the terms of the GNU General Public License\n\
and the Dvips copyright.\n\
\n\
For more information about these matters, see the files\n\
named COPYING and ttf2tfm.c.\n\
\n\
Primary authors of ttf2tfm: F. Loyer and W. Lemberg.\n\
\n\
ttf2tfm is based on afm2tfm from T. Rokicki\n\
and the FreeType project from\n\
David Turner, Robert Wilhelm, and Werner Lemberg.\n\
"

static void
version(void)
{
  fputs(ident, stdout);
  fprintf(stdout, " (%s)\n", TeX_search_version());
  fputs(VERSION, stdout);
  exit(0);
}


#define USAGE "\
  Convert a TrueType font table to TeX's font metric format.\n\
\n\
-c REAL             use REAL for height of small caps made with -V [0.8]\n\
-e REAL             widen (extend) characters by a factor of REAL [1.0]\n\
-E INT              select INT as the TTF encoding ID [1]\n\
-f INT              select INT as the font index in a TTC [0]\n\
-O                  use octal for all character codes in the vpl file\n\
-p ENCFILE[.enc]    read ENCFILE for the TTF->raw TeX mapping\n\
-P INT              select INT as the TTF platform ID [3]\n\
-q                  suppress informational output\n\
-s REAL             oblique (slant) characters by REAL, usually <<1 [0.0]\n\
-t ENCFILE[.enc]    read ENCFILE for the encoding of the vpl file\n\
-T ENCFILE[.enc]    equivalent to -p ENCFILE -t ENCFILE\n\
-u                  output only characters from encodings, nothing extra\n\
-v FILE[.vpl]       make a VPL file for conversion to VF\n\
-V SCFILE[.vpl]     like -v, but synthesize smallcaps as lowercase\n\
--help              print this message and exit\n\
--version           print version number and exit\n\
"

static void
usage(void)
{
  fputs("Usage: ttf2tfm FILE[.ttf] [OPTION]... [FILE[.tfm]]\n", stdout);
  fputs(USAGE, stdout);
  exit(0);
}


static void
handle_options(int argc, char *argv[], Font *fnt)
{
  register int lastext;
  register int i;
  int arginc;
  char *temp;
  char c;
  char *vpl_name = NULL;
  int have_capheight = 0;
  int have_sfd = 0;
  int sfd_begin, postfix_begin;
  int base_name;


#if defined(MSDOS) || defined(OS2) || defined(ATARIST)
  /* Make VPL file identical to that created under Unix */
  fnt->titlebuf = (char *)mymalloc(strlen(argv[1] + 8 + 1);
  sprintf(fnt->titlebuf, "ttf2tfm %s", argv[1]);
#else
  fnt->titlebuf = (char *)mymalloc(strlen(argv[0]) + strlen(argv[1]) + 1 + 1);
  sprintf(fnt->titlebuf, "%s %s", argv[0], argv[1]);
#endif


  /*
   *   TrueType font name.
   */

  fnt->ttfname = newstring(argv[1]);
  handle_extension(&(fnt->ttfname), ".ttf");

  /*
   *   The arguments.  We delay the final processing of some switches until
   *   the tfm font name has been scanned -- if it contains two `@'s, many
   *   switches are ignored.
   */

  while (argc > 2 && *argv[2] == '-')
  {
    arginc = 2;
    i = argv[2][1];
    if (i == '/')
      i = argv[2][2] - 32; /* a ==> A for VMS */

    switch (i)
    {
    case 'V':
      makevpl++;
    case 'v':
      makevpl++;
      vpl_name = newstring(argv[3]);
      handle_extension(&vpl_name, ".vpl");
      break;

    case 'f':
      if (sscanf(argv[3], "%lu", &(fnt->fontindex)) == 0)
        oops("Invalid font index.");
      fnt->fontindexparam = argv[3];
      break;

    case 'E':
      if (sscanf(argv[3], "%hu", &(fnt->eid)) == 0)
        oops("Invalid encoding ID.");
      fnt->eidparam = argv[3];
      break;

    case 'P':
      if (sscanf(argv[3], "%hu", &(fnt->pid)) == 0)
        oops("Invalid platform ID.");
      fnt->pidparam = argv[3];
      break;

    case 'e':
      if (sscanf(argv[3], "%f", &(fnt->efactor)) == 0 || fnt->efactor < 0.01)
        oops("Bad extension factor.");
      fnt->efactorparam = argv[3];
      break;

    case 'c':
      have_capheight = 1;
      if (sscanf(argv[3], "%f", &(fnt->capheight)) == 0)
        fnt->capheight = 0;
      break;

    case 's':
      if (sscanf(argv[3], "%f", &(fnt->slant)) == 0)
        oops("Bad slant parameter.");
      fnt->slantparam = argv[3];
      break;

    case 'p':
      fnt->inencname = newstring(argv[3]);
      handle_extension(&(fnt->inencname), ".enc");
      break;

    case 'T':
      fnt->inencname = fnt->outencname = newstring(argv[3]);
      handle_extension(&(fnt->inencname), ".enc");
      break;

    case 't':
      fnt->outencname = newstring(argv[3]);
      handle_extension(&(fnt->outencname), ".enc");
      break;

    case 'O':
      forceoctal = True;
      arginc = 1;
      break;

    case 'u':
      pedantic = True;
      arginc = 1;
      break;

    case 'q':
      quiet = True;
      arginc = 1;
      break;

    default:
      (void)fprintf(stderr, "Unknown option %s %s will be ignored.\n",
                    argv[2], argv[3]);
    }

    for (i = 0; i < arginc; i++)
    {
      size_t l;


      l = strlen(fnt->titlebuf);
      fnt->titlebuf = (char *)myrealloc((void *)fnt->titlebuf,
                                        l + strlen(argv[2]) + 1 + 1);
      sprintf(fnt->titlebuf + strlen(fnt->titlebuf), " %s", argv[2]);
      argv++;
      argc--;
    }
  }

  if (argc > 3 || (argc == 3 && *argv[2] == '-'))
    oops("Need at most two non-option arguments.");


  /*
   *   The tfm file name.
   */

  if (argc == 2)
    temp = newstring(fnt->ttfname);
  else
    temp = newstring(argv[2]);

  handle_sfd(temp, &sfd_begin, &postfix_begin);

  if (sfd_begin > -1)
  {
    have_sfd = True;
    i = sfd_begin - 2;
  }
  else
    i = strlen(temp) - 1;

  /*
   *   Now we search the beginning of the name without directory.
   */

  for (; i >= 0; i--)
    if (temp[i] == '/' || temp[i] == ':' || temp[i] == '\\')
      break;

  base_name = i + 1;

  /*
   *   We store the path (with the final directory separator).
   */

  if (base_name > 0)
  {
    c = temp[base_name];
    temp[base_name] = '\0';
    fnt->tfm_path = newstring(temp);
    temp[base_name] = c;
  }

  if (have_sfd)
  {
    /* the prefix and the sfd file name */

    if (temp[base_name])
      fnt->outname = newstring(temp + base_name);

    fnt->sfdname = newstring(temp + sfd_begin);
    handle_extension(&(fnt->sfdname), ".sfd");
  }
  else
    postfix_begin = base_name;

  /*
   *   Get the extension.
   */

  lastext = -1;
  for (i = postfix_begin; temp[i]; i++)
    if (temp[i] == '.')
      lastext = i;

  if (argc == 2)
  {
    temp[lastext] = '\0';       /* remove TTF file extension */
    lastext = -1;
  }

  if (lastext == -1)
    fnt->tfm_ext = newstring(".tfm");
  else
  {
    fnt->tfm_ext = newstring(temp + lastext);
    temp[lastext] = '\0';
  }

  if (have_sfd)
  {
    if (temp[postfix_begin])
      fnt->outname_postfix = newstring(temp + postfix_begin);
  }
  else
  {
    if (temp[base_name])
      fnt->outname = newstring(temp + base_name);
    else
      oops("Invalid tfm file name.");
  }


  /*
   *   Now we can process the remaining parameters.
   */

  if (have_sfd)
  {
    if (makevpl)
    {
      warning("Ignoring `-v' and `-V' switches for subfonts.");
      makevpl = 0;
    }
    if (have_capheight)
      warning("Ignoring `-c' switch for subfonts.");
    if (fnt->inencname || fnt->outencname)
    {
      warning("Ignoring `-p', `-t', and `-T' switches for subfonts.");
      fnt->inencname = NULL;
      fnt->outencname = NULL;
    }

    init_sfd(fnt, True);
  }
  else
  {
    if (have_capheight && fnt->capheight < 0.01)
      oops("Bad small caps height.");

    if (vpl_name)
      if ((fnt->vplout = fopen(vpl_name, "wt")) == NULL)
        oops("Cannot open vpl output file.");
  }

  if (vpl_name)
    free(vpl_name);
  free(temp);
}


/*
 *   This routine prints out the line that needs to be added to ttfonts.map.
 */

static void
consttfonts(Font *fnt)
{
  printf("\n");
  printf("%s", fnt->outname);
  if (fnt->sfdname)
    printf("@%s@", fnt->sfdname);
  if (fnt->outname_postfix)
    printf("%s", fnt->outname_postfix);

  printf("   %s", fnt->ttfname);

  if (fnt->slantparam || fnt->efactorparam ||
      fnt->inencname ||
      fnt->pidparam || fnt->eidparam ||
      fnt->fontindexparam)
  {
    if (fnt->slantparam)
      printf(" Slant=%s", fnt->slantparam);
    if (fnt->efactorparam)
      printf(" Extend=%s", fnt->efactorparam);
    if (fnt->inencname)
      printf(" Encoding=%s", fnt->inencname);
    if (fnt->pidparam)
      printf(" Pid=%s", fnt->pidparam);
    if (fnt->eidparam)
      printf(" Eid=%s", fnt->eidparam);
    if (fnt->fontindexparam)
      printf(" Fontindex=%s", fnt->fontindexparam);
  }
  printf("\n");
}


int
main(int argc, char *argv[])
{
  Font font;
  ttfinfo *ti;


  init_font_structure(&font);

  TeX_search_init(argv[0], "ttf2tfm", "TTF2TFM");

  if (argc == 1)
  {
    fputs("ttf2tfm: Need at least one file argument.\n", stderr);
    fputs("Try `ttf2tfm --help' for more information.\n", stderr);
    exit(1);
  }
  if (argc == 2)
  {
    if (strcmp(argv[1], "--help") == 0)
      usage();
    else if (strcmp(argv[1], "--version") == 0)
      version();
  }

  if (!quiet)
    printf("This is %s\n", ident);

  handle_options(argc, argv, &font);

  if (font.sfdname)
  {
    while (get_sfd(&font))
    {
      char *temp;
      int i, start, end, len;


      get_tfm_fullname(&font);

      /*
       *   Extract base name of sfd file.
       */

      temp = newstring(font.sfdname);
      len = strlen(temp);

      start = 0;
      for (i = len - 1; i >= 0; i--)
        if (temp[i] == '/' || temp[i] == ':' || temp[i] == '\\')
        {
          start = i + 1;
          break;
        }

      end = len;
      for (i = len - 1; i >= 0; i--)
        if (temp[i] == '.')
        {
          end = i;
          break;
        }
      temp[end] = '\0';

      font.codingscheme = (char *)mymalloc(strlen(temp + start) + 4 + 1);
      sprintf(font.codingscheme, "CJK-%s", temp + start);
      free(temp);

      readttf(&font, quiet, 1);

      if (NULL != (ti = findadobe("space", font.charlist)))
        font.fontspace = ti->width;
      else if (NULL != (ti = findadobe(".c0x20", font.charlist)))
        font.fontspace = ti->width;
      else
        font.fontspace = transform(500, 0, font.efactor, font.slant);

      if (buildtfm(&font, 1))
        writetfm(&font);
    }

    close_sfd();
  }
  else
  {
    get_tfm_fullname(&font);

    readttf(&font, quiet, 0);

    if (NULL != (ti = findadobe("space", font.charlist)))
      font.fontspace = ti->width;
    else if (NULL != (ti = findadobe(".c0x20", font.charlist)))
      font.fontspace = ti->width;
    else
      font.fontspace = transform(500, 0, font.efactor, font.slant);

    handlereencoding(&font);

    buildtfm(&font, 0);
    writetfm(&font);
  }

  if (makevpl)
  {
    assignchars(&font);
    if (makevpl > 1)
      upmap(&font);
    writevpl(&font, makevpl, forceoctal);
    fclose(font.vplout);
  }

  consttfonts(&font);

  exit(0);      /* for safety reasons */
  return 0;     /* never reached */
}


/* end */
