@ @c
/* mapfile.c

  Copyright 1996-2006 Han The Thanh <thanh@@pdftex.org>
  Copyright 2006-2010 Taco Hoekwater <taco@@luatex.org>

  This file is part of LuaTeX.

  LuaTeX 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.

  LuaTeX 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 Lesser General Public
  License for more details.

  You should have received a copy of the GNU General Public License along
  with LuaTeX; if not, see <http://www.gnu.org/licenses/>. */

static const char _svn_version[] =
   "$Id: mapfile.w 3786 2010-08-02 15:25:12Z taco $ "
   "$URL: http://foundry.supelec.fr/svn/luatex/tags/beta-0.70.1/source/texk/web2c/luatexdir/font/mapfile.w $";

#include <math.h>
#include "ptexlib.h"
#include <kpathsea/c-auto.h>
#include <kpathsea/c-memstr.h>
#include <string.h>

#define FM_BUF_SIZE     1024

static FILE *fm_file;

static unsigned char *fm_buffer = NULL;
static int fm_size = 0;
static int fm_curbyte = 0;

#define fm_open(a)      (fm_file = fopen((char *)(a), FOPEN_RBIN_MODE))
#define fm_read_file()  readbinfile(fm_file,&fm_buffer,&fm_size)
#define fm_close()      xfclose(fm_file, cur_file_name)
#define fm_getchar()    fm_buffer[fm_curbyte++]
#define fm_eof()        (fm_curbyte>fm_size)
#define is_cfg_comment(c) \
   (c == 10 || c == '*' || c == '#' || c == ';' || c == '%')

typedef enum { FM_DUPIGNORE, FM_REPLACE, FM_DELETE } updatemode;

typedef struct mitem {
   updatemode mode;            /* FM_DUPIGNORE or FM_REPLACE or FM_DELETE */
   maptype type;               /* map file or map line */
   char *line;                 /* pointer to map file name or map line */
   int lineno;                 /* line number in map file */
} mapitem;
mapitem *mitem = NULL;

#define read_field(r, q, buf) do {  \
   q = buf;                        \
   while (*r != ' ' && *r != '<' && *r != '"' && *r != '\0') \
       *q++ = *r++;                \
   *q = '\0';                      \
   skip (r, ' ');                  \
} while (0)

#define set_field(F) do {           \
   if (q > buf)                    \
       fm->F = xstrdup(buf);       \
   if (*r == '\0')                 \
       goto done;                  \
} while (0)

fm_entry *new_fm_entry(void)
{
   fm_entry *fm;
   fm = xtalloc(1, fm_entry);
   fm->tfm_name = NULL;
   fm->sfd_name = NULL;
   fm->ps_name = NULL;
   fm->fd_flags = FD_FLAGS_NOT_SET_IN_MAPLINE;
   fm->ff_name = NULL;
   fm->encname = NULL;
   fm->type = 0;
   fm->slant = 0;
   fm->extend = 1000;
   fm->pid = -1;
   fm->eid = -1;
   fm->subfont = NULL;
   unset_slantset(fm);
   unset_extendset(fm);
   unset_inuse(fm);
   return fm;
}

void delete_fm_entry(fm_entry * fm)
{
   xfree(fm->tfm_name);
   xfree(fm->sfd_name);
   xfree(fm->ps_name);
   xfree(fm->ff_name);
   xfree(fm);
}

static ff_entry *new_ff_entry(void)
{
   ff_entry *ff;
   ff = xtalloc(1, ff_entry);
   ff->ff_name = NULL;
   ff->ff_path = NULL;
   return ff;
}

static void delete_ff_entry(ff_entry * ff)
{
   xfree(ff->ff_name);
   xfree(ff->ff_path);
   xfree(ff);
}

/**********************************************************************/

static struct avl_table *tfm_tree = NULL;
static struct avl_table *ff_tree = NULL;
static struct avl_table *encname_tree = NULL;

/* AVL sort fm_entry into tfm_tree by tfm_name */

static int comp_fm_entry_tfm(const void *pa, const void *pb, void *p)
{
   (void) p;
   return strcmp(((const fm_entry *) pa)->tfm_name,
                 ((const fm_entry *) pb)->tfm_name);
}

/* AVL sort ff_entry into ff_tree by ff_name */

static int comp_ff_entry(const void *pa, const void *pb, void *p)
{
   (void) p;
   return strcmp(((const ff_entry *) pa)->ff_name,
                 ((const ff_entry *) pb)->ff_name);
}

static void create_avl_trees(void)
{
   assert(tfm_tree == NULL);
   tfm_tree = avl_create(comp_fm_entry_tfm, NULL, &avl_xallocator);
   assert(tfm_tree != NULL);
   assert(ff_tree == NULL);
   ff_tree = avl_create(comp_ff_entry, NULL, &avl_xallocator);
   assert(ff_tree != NULL);
   assert(encname_tree == NULL);
   encname_tree = avl_create(comp_string_entry, NULL, &avl_xallocator);
   assert(encname_tree != NULL);
}

int avl_do_entry(fm_entry * fm, int mode)
{
   fm_entry *p;
   void *a;
   void **aa;
   int delete_new = 0;
   if (tfm_tree == NULL)
       create_avl_trees();
   p = (fm_entry *) avl_find(tfm_tree, fm);
   if (p != NULL) {
       switch (mode) {
       case FM_DUPIGNORE:
           pdftex_warn
               ("fontmap entry for `%s' already exists, duplicates ignored",
                fm->tfm_name);
           delete_new = 1;
           break;
       case FM_REPLACE:
       case FM_DELETE:
           if (is_inuse(p)) {
               pdftex_warn
                   ("fontmap entry for `%s' has been used, replace/delete not allowed",
                    fm->tfm_name);
               delete_new = 1;
           } else {
               a = avl_delete(tfm_tree, p);
               assert(a != NULL);
               delete_fm_entry(p);
           }
           break;
       default:
           assert(0);
       }
   }
   if ((mode == FM_DUPIGNORE || mode == FM_REPLACE) && delete_new == 0) {
       aa = avl_probe(tfm_tree, fm);
       assert(aa != NULL);
   } else
       delete_new = 1;
   return delete_new;
}

/* add the encoding name to an AVL tree. this has nothing to do with writeenc.c */

static char *add_encname(char *s)
{
   char *p;
   void **aa;
   assert(s != NULL);
   assert(encname_tree != NULL);
   if ((p = (char *) avl_find(encname_tree, s)) == NULL) {     /* encoding name not yet registered */
       p = xstrdup(s);
       aa = avl_probe(encname_tree, p);
       assert(aa != NULL);
   }
   return p;
}

/**********************************************************************/
/* consistency check for map entry, with warn flag */

static int check_fm_entry(fm_entry * fm, boolean warn)
{
   int a = 0;
   assert(fm != NULL);

   if (is_fontfile(fm) && !is_included(fm)) {
       if (warn)
           pdftex_warn
               ("ambiguous entry for `%s': font file present but not included, "
                "will be treated as font file not present", fm->tfm_name);
       xfree(fm->ff_name);
       /* do not set variable |a| as this entry will be still accepted */
   }

   /* if both ps_name and font file are missing, drop this entry */
   if (fm->ps_name == NULL && !is_fontfile(fm)) {
       if (warn)
           pdftex_warn
               ("invalid entry for `%s': both ps_name and font file missing",
                fm->tfm_name);
       a += 1;
   }

   /* TrueType fonts cannot be reencoded without subsetting */
   if (is_truetype(fm) && is_reencoded(fm) && !is_subsetted(fm)) {
       if (warn)
           pdftex_warn
               ("invalid entry for `%s': only subsetted TrueType font can be reencoded",
                fm->tfm_name);
       a += 2;
   }

   /* the value of SlantFont and ExtendFont must be reasonable */
   if (fm->slant < FONT_SLANT_MIN || fm->slant > FONT_SLANT_MAX) {
       if (warn)
           pdftex_warn
               ("invalid entry for `%s': too big value of SlantFont (%g)",
                fm->tfm_name, fm->slant / 1000.0);
       a += 8;
   }
   if (fm->extend < FONT_EXTEND_MIN || fm->extend > FONT_EXTEND_MAX) {
       if (warn)
           pdftex_warn
               ("invalid entry for `%s': too big value of ExtendFont (%g)",
                fm->tfm_name, fm->extend / 1000.0);
       a += 16;
   }

   /* subfonts must be used with subsetted non-reencoded TrueType fonts */
   if (fm->pid != -1 &&
       !(is_truetype(fm) && is_subsetted(fm) && !is_reencoded(fm))) {
       if (warn)
           pdftex_warn
               ("invalid entry for `%s': PidEid can be used only with subsetted non-reencoded TrueType fonts",
                fm->tfm_name);
       a += 32;
   }

   return a;
}

/**********************************************************************/
/* returns the font number if s is one of the 14 std. font names, -1 otherwise; speed-trimmed. */

int check_std_t1font(char *s)
{
   static const char *std_t1font_names[] = {
       "Courier",              /* 0:7 */
       "Courier-Bold",         /* 1:12 */
       "Courier-Oblique",      /* 2:15 */
       "Courier-BoldOblique",  /* 3:19 */
       "Helvetica",            /* 4:9 */
       "Helvetica-Bold",       /* 5:14 */
       "Helvetica-Oblique",    /* 6:17 */
       "Helvetica-BoldOblique",        /* 7:21 */
       "Symbol",               /* 8:6 */
       "Times-Roman",          /* 9:11 */
       "Times-Bold",           /* 10:10 */
       "Times-Italic",         /* 11:12 */
       "Times-BoldItalic",     /* 12:16 */
       "ZapfDingbats"          /* 13:12 */
   };
   static const int index[] =
       { -1, -1, -1, -1, -1, -1, 8, 0, -1, 4, 10, 9, -1, -1, 5, 2, 12, 6, -1,
       3, -1, 7
   };
   size_t n;
   int k = -1;
   assert(s != NULL);
   n = strlen(s);
   if (n > 21)
       return -1;
   if (n == 12) {              /* three names have length 12 */
       switch (*s) {
       case 'C':
           k = 1;              /* Courier-Bold */
           break;
       case 'T':
           k = 11;             /* Times-Italic */
           break;
       case 'Z':
           k = 13;             /* ZapfDingbats */
           break;
       default:
           return -1;
       }
   } else
       k = index[n];
   if (k > -1 && !strcmp(std_t1font_names[k], s))
       return k;
   return -1;
}

/**********************************************************************/

static void fm_scan_line(void)
{
   int a, b, c, j, u = 0, v = 0;
   char cc;
   float d;
   fm_entry *fm;
   char fm_line[FM_BUF_SIZE], buf[FM_BUF_SIZE];
   char *p, *q, *r, *s;
   switch (mitem->type) {
   case MAPFILE:
       p = fm_line;
       while (!fm_eof()) {
           if (fm_curbyte == fm_size) {
               fm_curbyte++;
               cc = 10;
           } else {
               cc = (char) fm_getchar();
           }
           append_char_to_buf(cc, p, fm_line, FM_BUF_SIZE);
           if (cc == 10)
               break;
       }
       *(--p) = '\0';
       r = fm_line;
       break;
   case MAPLINE:
       r = mitem->line;        /* work on string from makecstring() */
       break;
   default:
       assert(0);
   }
   if (*r == '\0' || is_cfg_comment(*r))
       return;
   fm = new_fm_entry();
   read_field(r, q, buf);
   set_field(tfm_name);
   if (!isdigit(*r)) {         /* 2nd field ps_name may not start with a digit */
       read_field(r, q, buf);
       set_field(ps_name);
   }
   if (isdigit(*r)) {          /* font descriptor /Flags given? */
       for (s = r; isdigit(*s); s++);
       if (*s == ' ' || *s == '"' || *s == '<' || *s == '\0') {        /* not e. g. 8r.enc */
           fm->fd_flags = atoi(r);
           while (isdigit(*r))
               r++;
       }
   }
   while (1) {                 /* loop through "specials", encoding, font file */
       skip(r, ' ');
       switch (*r) {
       case '\0':
           goto done;
       case '"':              /* opening quote */
           r++;
           u = v = 0;
           do {
               skip(r, ' ');
               if (sscanf(r, "%f %n", &d, &j) > 0) {
                   s = r + j;  /* jump behind number, eat also blanks, if any */
                   if (*(s - 1) == 'E' || *(s - 1) == 'e')
                       s--;    /* e. g. 0.5ExtendFont: %f = 0.5E */
                   if (str_prefix(s, "SlantFont")) {
                       d *= (float) 1000.0;    /* correct rounding also for neg. numbers */
                       fm->slant = (int) (d > 0 ? d + 0.5 : d - 0.5);
                       set_slantset(fm);
                       r = s + strlen("SlantFont");
                   } else if (str_prefix(s, "ExtendFont")) {
                       d *= (float) 1000.0;
                       fm->extend = (int) (d > 0 ? d + 0.5 : d - 0.5);
                       set_extendset(fm);
                       r = s + strlen("ExtendFont");
                   } else {    /* unknown name */
                       for (r = s; *r != ' ' && *r != '"' && *r != '\0'; r++); /* jump over name */
                       c = *r; /* remember char for temporary end of string */
                       *r = '\0';
                       pdftex_warn
                           ("invalid entry for `%s': unknown name `%s' ignored",
                            fm->tfm_name, s);
                       *r = (char) c;
                   }
               } else
                   for (; *r != ' ' && *r != '"' && *r != '\0'; r++);
           }
           while (*r == ' ');
           if (*r == '"')      /* closing quote */
               r++;
           else {
               pdftex_warn
                   ("invalid entry for `%s': closing quote missing",
                    fm->tfm_name);
               goto bad_line;
           }
           break;
       case 'P':              /* handle cases for subfonts like 'PidEid=3,1' */
           if (sscanf(r, "PidEid=%i, %i %n", &a, &b, &c) >= 2) {
               fm->pid = (short) a;
               fm->eid = (short) b;
               r += c;
               break;
           }
       default:               /* encoding or font file specification */
           a = b = 0;
           if (*r == '<') {
               a = *r++;
               if (*r == '<' || *r == '[')
                   b = *r++;
           }
           read_field(r, q, buf);
           /* encoding, formats: '8r.enc' or '<8r.enc' or '<[8r.enc' */
           if (strlen(buf) > 4 && strcasecmp(strend(buf) - 4, ".enc") == 0) {
               fm->encname = add_encname(buf);
               u = v = 0;      /* u, v used if intervening blank: "<< foo" */
           } else if (strlen(buf) > 0) {       /* file name given */
               /* font file, formats:
                * subsetting:    '<cmr10.pfa'
                * no subsetting: '<<cmr10.pfa'
                * no embedding:  'cmr10.pfa'
                */
               if (a == '<' || u == '<') {
                   set_included(fm);
                   if ((a == '<' && b == 0) || (a == 0 && v == 0))
                       set_subsetted(fm);
                   /* otherwise b == '<' (or '[') => no subsetting */
               }
               set_field(ff_name);
               u = v = 0;
           } else {
               u = a;
               v = b;
           }
       }
   }
 done:
   if (fm->ps_name != NULL && (check_std_t1font(fm->ps_name) >= 0))
       set_std_t1font(fm);
   if (is_fontfile(fm) && strlen(fm_fontfile(fm)) > 3) {
       if (strcasecmp(strend(fm_fontfile(fm)) - 4, ".ttf") == 0)
           set_truetype(fm);
       else if (strcasecmp(strend(fm_fontfile(fm)) - 4, ".ttc") == 0)
           set_truetype(fm);
       else if (strcasecmp(strend(fm_fontfile(fm)) - 4, ".otf") == 0)
           set_opentype(fm);
       else
           set_type1(fm);
   } else
       set_type1(fm);          /* assume a builtin font is Type1 */
   if (check_fm_entry(fm, true) != 0)
       goto bad_line;
   /*
      Until here the map line has been completely scanned without errors;
      fm points to a valid, freshly filled-out fm_entry structure.
      Now follows the actual work of registering/deleting.
    */
   if (handle_subfont_fm(fm, mitem->mode))     /* is this a subfont? */
       return;
   if (avl_do_entry(fm, mitem->mode) == 0)
       return;
 bad_line:
   delete_fm_entry(fm);
}

/**********************************************************************/

static void fm_read_info(void)
{
   int callback_id;
   int file_opened = 0;

   if (tfm_tree == NULL)
       create_avl_trees();
   if (mitem->line == NULL)    /* nothing to do */
       return;
   mitem->lineno = 1;
   switch (mitem->type) {
   case MAPFILE:
       xfree(fm_buffer);
       fm_curbyte = 0;
       fm_size = 0;
       cur_file_name = luatex_find_file(mitem->line, find_map_file_callback);
       if (cur_file_name) {
           callback_id = callback_defined(read_map_file_callback);
           if (callback_id > 0) {
               if (run_callback(callback_id, "S->bSd", cur_file_name,
                                &file_opened, &fm_buffer, &fm_size)) {
                   if (file_opened) {
                       if (fm_size > 0) {
                           if (tracefilenames)
                               tex_printf("{%s", cur_file_name);
                           while (!fm_eof()) {
                               fm_scan_line();
                               mitem->lineno++;
                           }
                           if (tracefilenames)
                               tex_printf("}");
                           fm_file = NULL;
                       }
                   } else {
                       pdftex_warn("cannot open font map file (%s)", cur_file_name);
                   }
               } else {
                   pdftex_warn("cannot open font map file (%s)", cur_file_name);
               }
           } else {
               if (!fm_open(cur_file_name)) {
                   pdftex_warn("cannot open font map file (%s)", cur_file_name);
               } else {
                   fm_read_file();
                   tex_printf("{%s", cur_file_name);
                   while (!fm_eof()) {
                       fm_scan_line();
                       mitem->lineno++;
                   }
                   fm_close();
                   tex_printf("}");
                   fm_file = NULL;
               }
           }
           cur_file_name = NULL;
       }
       break;
   case MAPLINE:
       cur_file_name = NULL;   /* makes pdftex_warn() shorter */
       fm_scan_line();
       break;
   default:
       assert(0);
   }
   mitem->line = NULL;         /* done with this line */
   cur_file_name = NULL;
   return;
}

/**********************************************************************/

fm_entry *getfontmap(char *tfm_name)
{
   fm_entry *fm;
   fm_entry tmp;
   if (tfm_name == NULL)       /* wide, lua loaded fonts may not have a name */
       return NULL;
   if (tfm_tree == NULL)
       fm_read_info();         /* only to read default map file */
   tmp.tfm_name = tfm_name;    /* Look up for tfmname */
   fm = (fm_entry *) avl_find(tfm_tree, &tmp);
   if (fm == NULL)
       return NULL;
   set_inuse(fm);
   return fm;
}

/**********************************************************************/
/*
* Process map file given by its name or map line contents. Items not
* beginning with [+-=] flush default map file, if it has not yet been
* read. Leading blanks and blanks immediately following [+-=] are
* ignored.
*/

void process_map_item(char *s, int type)
{
   char *p;
   int mode;
   if (*s == ' ')
       s++;                    /* ignore leading blank */
   switch (*s) {
   case '+':                  /* +mapfile.map, +mapline */
       mode = FM_DUPIGNORE;    /* insert entry, if it is not duplicate */
       s++;
       break;
   case '=':                  /* =mapfile.map, =mapline */
       mode = FM_REPLACE;      /* try to replace earlier entry */
       s++;
       break;
   case '-':                  /* -mapfile.map, -mapline */
       mode = FM_DELETE;       /* try to delete entry */
       s++;
       break;
   default:
       mode = FM_DUPIGNORE;    /* like +, but also: */
       mitem->line = NULL;     /* flush default map file name */
   }
   if (*s == ' ')
       s++;                    /* ignore blank after [+-=] */
   p = s;                      /* map item starts here */
   switch (type) {
   case MAPFILE:              /* remove blank at end */
       while (*p != '\0' && *p != ' ')
           p++;
       *p = '\0';
       break;
   case MAPLINE:              /* blank at end allowed */
       break;
   default:
       assert(0);
   }
   if (mitem->line != NULL)    /* read default map file first */
       fm_read_info();
   if (*s != '\0') {           /* only if real item to process */
       mitem->mode = mode;
       mitem->type = type;
       mitem->line = s;
       fm_read_info();
   }
}

void pdfmapfile(int t)
{
   char *s = tokenlist_to_cstring(t, true, NULL);
   process_map_item(s, MAPFILE);
   free(s);
}

void pdfmapline(int t)
{
   char *s = tokenlist_to_cstring(t, true, NULL);
   process_map_item(s, MAPLINE);
   free(s);
}

void pdf_init_map_file(char *map_name)
{
   assert(mitem == NULL);
   mitem = xtalloc(1, mapitem);
   mitem->mode = FM_DUPIGNORE;
   mitem->type = MAPFILE;
   mitem->line = map_name;
}

/**********************************************************************/
/*
* Early check whether a font file exists. Search tree ff_tree is used
* in 1st instance, as it may be faster than the kpse_find_file(), and
* kpse_find_file() is called only once per font file name + expansion
* parameter. This might help keeping speed, if many PDF pages with
* same fonts are to be embedded.
*
* The ff_tree contains only font files, which are actually needed,
* so this tree typically is much smaller than the tfm_tree.
*/

ff_entry *check_ff_exist(char *ff_name, boolean is_tt)
{
   ff_entry *ff;
   ff_entry tmp;
   void **aa;
   int callback_id;
   char *filepath = NULL;

   assert(ff_name != NULL);
   tmp.ff_name = ff_name;
   ff = (ff_entry *) avl_find(ff_tree, &tmp);
   if (ff == NULL) {           /* not yet in database */
       ff = new_ff_entry();
       ff->ff_name = xstrdup(ff_name);
       if (is_tt) {
           callback_id = callback_defined(find_truetype_file_callback);
           if (callback_id > 0) {
               run_callback(callback_id, "S->S", ff_name, &filepath);
               if (filepath && strlen(filepath) == 0)
                   filepath = NULL;
               ff->ff_path = filepath;
           } else {
               ff->ff_path = kpse_find_file(ff_name, kpse_truetype_format, 0);
           }
       } else {
           callback_id = callback_defined(find_type1_file_callback);
           if (callback_id > 0) {
               run_callback(callback_id, "S->S", ff_name, &filepath);
               if (filepath && strlen(filepath) == 0)
                   filepath = NULL;
               ff->ff_path = filepath;
           } else {
               ff->ff_path = kpse_find_file(ff_name, kpse_type1_format, 0);
           }
       }
       aa = avl_probe(ff_tree, ff);
       assert(aa != NULL);
   }
   return ff;
}

/**********************************************************************/

int is_subsetable(fm_entry * fm)
{
   assert(is_included(fm));
   return is_subsetted(fm);
}

/**********************************************************************/
/* cleaning up... */

static void destroy_fm_entry_tfm(void *pa, void *pb)
{
   fm_entry *fm;
   (void) pb;
   fm = (fm_entry *) pa;
   delete_fm_entry(fm);
}

static void destroy_ff_entry(void *pa, void *pb)
{
   ff_entry *ff;
   (void) pb;
   ff = (ff_entry *) pa;
   delete_ff_entry(ff);
}

void fm_free(void)
{
   if (tfm_tree != NULL) {
       avl_destroy(tfm_tree, destroy_fm_entry_tfm);
       tfm_tree = NULL;
   }
   if (ff_tree != NULL) {
       avl_destroy(ff_tree, destroy_ff_entry);
       ff_tree = NULL;
   }
}