ttf2tfm.c   [plain text]


/*
 *   ttf2tfm.c
 *
 *   This file is part of the ttf2pk package.
 *
 *   Copyright 1997-1999 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.3";
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, k;
  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)
    {
      k = -1;
      while (fnt->nextout[ti->outcode] >= 0)
      {
        j = fnt->nextout[ti->outcode];
        fnt->nextout[ti->outcode] = k;
        k = ti->outcode;
        ti->outcode = j;
      }
      fnt->nextout[ti->outcode] = k;
    }

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-1999 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\
-l                  create 1st/2nd byte ligatures in subfonts\n\
-n                  use PS names of TrueType font\n\
-N                  use only PS names and no cmap\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\
-r OLDNAME NEWNAME  replace glyph name OLDNAME with NEWNAME\n\
-R RPLFILE[.rpl]    read RPLFILE containing glyph replacement names\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\
-x                  rotate subfont glyphs by 90 degrees\n\
-y REAL             move rotated glyphs down by a factor of REAL [0.25]\n\
--help              print this message and exit\n\
--version           print version number and exit\n\
"

static void
usage(void)
{
  fputs("Usage: ttf2tfm FILE[.ttf|.ttc] [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;
  size_t l;
  int arginc;
  char *temp;
  char c;
  char *vpl_name = NULL;
  Boolean have_capheight = 0;
  Boolean have_sfd = 0;
  int sfd_begin, postfix_begin;
  int base_name;
  stringlist* sl;


  /* scan first whether the -q switch is set */
  for (i = 1; i < argc; i++)
    if (argv[i][0] == '-' && argv[i][1] == 'q')
      quiet = True;

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

#if defined(MSDOS) || defined(OS2) || defined(ATARIST)
  /* Make VPL file identical to that created under Unix */
  fnt->titlebuf = (char *)mymalloc(strlen(progname) + strlen(argv[1]) +
                                   1 + 1);
  sprintf(fnt->titlebuf, "%s %s", progname, 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]);

  /*
   *   The other 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];

    switch (i)
    {
    case 'v':
      makevpl = 1;
      if (argc <= 3)
        oops("Missing parameter for -v option.");
      if (vpl_name)
        free(vpl_name);
      vpl_name = newstring(argv[3]);
      handle_extension(&vpl_name, ".vpl");
      break;

    case 'V':
      makevpl = 2;
      if (argc <= 3)
        oops("Missing parameter for -V option.");
      if (vpl_name)
        free(vpl_name);
      vpl_name = newstring(argv[3]);
      handle_extension(&vpl_name, ".vpl");
      break;

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

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

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

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

    case 'c':
      if (argc <= 3)
        oops("Missing parameter for -c option.");
      have_capheight = True;
      if (sscanf(argv[3], "%f", &(fnt->capheight)) == 0)
        fnt->capheight = 0;
      break;

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

    case 'p':
      if (argc <= 3)
        oops("Missing parameter for -p option.");
      if (fnt->inencname)
        free(fnt->inencname);
      fnt->inencname = newstring(argv[3]);
      break;

    case 'T':
      if (argc <= 3)
        oops("Missing parameter for -T option.");
      if (fnt->inencname)
        free(fnt->inencname);
      if (fnt->outencname)
        free(fnt->outencname);
      fnt->inencname = newstring(argv[3]);
      fnt->outencname = newstring(argv[3]);
      break;

    case 't':
      if (argc <= 3)
        oops("Missing parameter for -T option.");
      if (fnt->outencname)
        free(fnt->outencname);
      fnt->outencname = newstring(argv[3]);
      break;

    case 'r':
      if (argc <= 4)
        oops("Not enough parameters for -r option.");
      sl = newstringlist();
      sl->old_name = newstring(argv[3]);
      sl->new_name = newstring(argv[4]);
      sl->single_replacement = True;
      sl->next = fnt->replacements;
      fnt->replacements = sl;
      arginc = 3;
      break;

    case 'R':
      if (argc <= 3)
        oops("Missing parameter for -R option.");
      if (fnt->replacementname)
        free(fnt->replacementname);
      fnt->replacementname = newstring(argv[3]);
      break;

    case 'y':
      if (argc <= 3)
        oops("Missing parameter for -y option.");
      if (sscanf(argv[3], "%f", &(fnt->y_offset)) == 0)
        oops("Invalid y-offset.");
      fnt->y_offsetparam = argv[3];
      break;

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

    case 'n':
      fnt->PSnames = Yes;
      arginc = 1;
      break;

    case 'N':
      fnt->PSnames = Only;
      arginc = 1;
      break;

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

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

    case 'l':
      fnt->subfont_ligs = True;
      arginc = 1;
      break;

    case 'x':
      fnt->rotate = True;
      arginc = 1;
      break;

    default:
      if (argc <= 3 || argv[3][0] == '-')
      {
        warning("Unknown option `%s' will be ignored.\n", argv[2]);
        arginc = 1;
      }
      else
        warning("Unknown option `%s %s' will be ignored.\n",
                argv[2], argv[3]);
    }

    for (i = 0; i < arginc; i++)
    {
      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--;
    }
  }

  /* Read replacement glyph name file */

  get_replacements(fnt);

  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]);
    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]);
  }

  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);
  }
  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 && lastext >= 0)
  {
    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;
    }
    if (fnt->y_offsetparam && !fnt->rotate)
      warning("Ignoring `-y' switch for non-rotated subfonts.");
    if (fnt->PSnames)
    {
      warning("Ignoring `-n' or '-N' switch for subfonts.");
      fnt->PSnames = No;
    }

    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 (fnt->subfont_ligs)
    {
      warning("Ignoring `-l' switch for non-subfont.");
      fnt->subfont_ligs = False;
    }

    if (fnt->rotate)
    {
      warning("Ignoring `-x' switch for non-subfont.");
      fnt->rotate = False;
    }

    if (fnt->y_offsetparam)
      warning("Ignoring `-y' switch for non-subfont.");
  }

  if (fnt->PSnames == Only)
  {
    if (fnt->pidparam || fnt->eidparam)
    {
      warning("Ignoring `-P' and `-E' options if `-N' switch is selected.");
      fnt->pidparam = NULL;
      fnt->eidparam = NULL;
    }
  }

  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)
{
  if (!quiet)
    printf("\n");
  if (fnt->outname)
    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 ||
      fnt->replacements ||
      fnt->replacementname ||
      fnt->PSnames ||
      fnt->rotate || fnt->y_offsetparam)
  {
    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);
    if (fnt->PSnames)
      printf(" PS=%s", fnt->PSnames == Yes ? "Yes" : "Only");
    if (fnt->rotate)
      printf(" Rotate=Yes");
    if (fnt->y_offsetparam)
      printf(" Y-Offset=%s", fnt->y_offsetparam);
    if (fnt->replacementname && fnt->inencoding)
      printf(" Replacement=%s", fnt->replacementname);
    if (fnt->replacements && fnt->inencoding)
    {
      stringlist *sl;


      for (sl = fnt->replacements; sl; sl = sl->next)
        if (sl->single_replacement)
          printf(" %s=%s", sl->old_name, sl->new_name);
    }
  }
  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();
  }

  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, True);
      if (font.replacements)
        warning("Replacement glyphs will be ignored.");

      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))
        writetfm(&font);
    }

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

    readttf(&font, quiet, False);
    replace_glyphs(&font);

    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);
    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 */