/*  $Header: /home/cvsroot/dvipdfmx/src/t1_load.c,v 1.9 2005/07/17 09:53:38 hirata Exp $

    This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks.

    Copyright (C) 2002 by Jin-Hwan Cho and Shunsaku Hirata,
    the dvipdfmx project team <dvipdfmx@project.ktug.or.kr>

    Copyright (C) 1998, 1999 by Mark A. Wicks <mwicks@kettering.edu>

    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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/

#if HAVE_CONFIG_H
#include "config.h"
#endif

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

#include "system.h"
#include "mfileio.h"
#include "mem.h"
#include "error.h"
#include "numbers.h"

#include "pdfobj.h"
#include "pdffont.h"

#include "pdfencoding.h"
#include "unicode.h"

#include "dpxutil.h"

#include "pst_obj.h"
#include "pst.h"

#include "cff_limits.h"
#include "cff_types.h"
#include "cff_dict.h"
#include "cff.h"

#include "t1_load.h"

/* Migrated from t1crypt */
#define T1_EEKEY   55665u
#define T1_CHARKEY 4330u

#if  0
/* We no longer need encryption. */
static unsigned char  t1_encrypt    (unsigned char  plain);
#endif /* 0 */
static void           t1_crypt_init (unsigned short key);
static unsigned char  t1_decrypt    (unsigned char  cipher);
static unsigned short r = T1_EEKEY, c1 = 52845, c2 = 22719;

#if  0
static unsigned char t1_encrypt (unsigned char plain)
{
  unsigned char cipher;
  cipher = (plain ^ (r >> 8));
  r = (cipher + r) * c1 + c2;
  return cipher;
}
#endif /* 0 */

static void t1_crypt_init (unsigned short key)
{
  r = key;
}

static unsigned char t1_decrypt (unsigned char cipher)
{
  unsigned char plain;
  plain = (cipher ^ (r >> 8));
  r = (cipher + r) * c1 + c2;
  return plain;
}
/* T1CRYPT */

#define MATCH_NAME(t,n) ((t) && PST_NAMETYPE((t))    && !strncmp(pst_data_ptr((t)),(n),strlen((n))))
#define MATCH_OP(t,n)   ((t) && PST_UNKNOWNTYPE((t)) && !strncmp(pst_data_ptr((t)),(n),strlen((n))))

#define RELEASE_TOK(t) if ((t) != NULL) {\
  pst_release_obj((t));\
  (t) = NULL;\
}

static char *
get_next_key (unsigned char **start, unsigned char *end)
{
  char *key = NULL;
  pst_obj *tok;

  while (*start < end &&
	 (tok = pst_get_token(start, end)) != NULL) {
    if (PST_NAMETYPE(tok)) {
      key = (char *) pst_getSV(tok);
      RELEASE_TOK(tok);
      break;
    }
    RELEASE_TOK(tok);
  }

  return key;
}

static int
seek_operator (unsigned char **start, unsigned char *end, const char *op)
{
  pst_obj *tok = NULL;

  while (*start < end &&
	 (tok = pst_get_token(start, end)) != NULL) {
    if (MATCH_OP(tok, op)) {
      break;
    }
    RELEASE_TOK(tok);
  }

  if (tok == NULL)
    return -1;
  RELEASE_TOK(tok);

  return 0;
}


static int
parse_svalue (unsigned char **start, unsigned char *end, char **value)
{
  pst_obj *tok;

  tok = pst_get_token(start, end);
  if (tok == NULL)
    return -1;
  else if (PST_NAMETYPE(tok) || PST_STRINGTYPE(tok))
    *value = (char *) pst_getSV(tok);
  else {
    RELEASE_TOK(tok);
    return -1;
  }
  RELEASE_TOK(tok);

  return 1;
}

static int
parse_bvalue (unsigned char **start, unsigned char *end, double *value)
{
  pst_obj *tok;

  tok = pst_get_token(start, end);
  if (tok == NULL)
    return -1;
  else if (PST_BOOLEANTYPE(tok))
    *value = (double) pst_getIV(tok);
  else {
    RELEASE_TOK(tok);
    return -1;
  }
  RELEASE_TOK(tok);

  return 1;
}

static int
parse_nvalue (unsigned char **start, unsigned char *end, double *value, int max)
{
  int argn = 0;
  pst_obj *tok;

  tok = pst_get_token(start, end);
  if (tok == NULL)
    return -1;
  /*
   * All array elements must be numeric token. (ATM compatible)
   */
  if (PST_NUMBERTYPE(tok) && max > 0) {
    value[0] = (double) pst_getRV(tok);
    argn = 1;
  } else if (PST_MARKTYPE(tok)) {
    /* It does not distinguish '[' and '{'... */
    RELEASE_TOK(tok);
    while (*start < end &&
	   (tok = pst_get_token(start, end)) != NULL &&
	   PST_NUMBERTYPE(tok) && argn < max) {
      value[argn++] = (double) pst_getRV(tok);
      RELEASE_TOK(tok);
    }
    if (tok == NULL)
      return -1;
    if (!MATCH_OP(tok, "]") && !MATCH_OP(tok, "}")) {
      argn = -1;
    }
  }
  RELEASE_TOK(tok);

  return argn;
}

static const char *const StandardEncoding[256] = {
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", "space", "exclam", "quotedbl",
  "numbersign", "dollar", "percent", "ampersand", "quoteright",
  "parenleft", "parenright", "asterisk", "plus", "comma",
  "hyphen", "period", "slash", "zero", "one",
  "two", "three", "four", "five", "six",
  "seven", "eight", "nine", "colon", "semicolon",
  "less", "equal", "greater", "question", "at",
  "A", "B", "C", "D", "E",
  "F", "G", "H", "I", "J",
  "K", "L", "M", "N", "O",
  "P", "Q", "R", "S", "T",
  "U", "V", "W", "X", "Y",
  "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
  "underscore", "quoteleft", "a", "b", "c",
  "d", "e", "f", "g", "h",
  "i", "j", "k", "l", "m",
  "n", "o", "p", "q", "r",
  "s", "t", "u", "v", "w",
  "x", "y", "z", "braceleft", "bar",
  "braceright", "asciitilde", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", "exclamdown", "cent", "sterling", "fraction",
  "yen", "florin", "section", "currency", "quotesingle",
  "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi",
  "fl", ".notdef", "endash", "dagger", "daggerdbl",
  "periodcentered", ".notdef", "paragraph", "bullet", "quotesinglbase",
  "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand",
  ".notdef", "questiondown", ".notdef", "grave", "acute",
  "circumflex", "tilde", "macron", "breve", "dotaccent",
  "dieresis", ".notdef", "ring", "cedilla", ".notdef",
  "hungarumlaut", "ogonek", "caron", "emdash", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  "AE", ".notdef", "ordfeminine", ".notdef", ".notdef",
  ".notdef", ".notdef", "Lslash", "Oslash", "OE",
  "ordmasculine", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", "ae", ".notdef", ".notdef", ".notdef",
  "dotlessi", ".notdef", ".notdef", "lslash", "oslash",
  "oe", "germandbls", ".notdef", ".notdef", ".notdef",
  ".notdef"
};

static const char *const ISOLatin1Encoding[256] = {
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", "space", "exclam", "quotedbl",
  "numbersign", "dollar", "percent", "ampersand", "quotesingle",
  "parenleft", "parenright", "asterisk", "plus", "comma",
  "hyphen", "period", "slash", "zero", "one",
  "two", "three", "four", "five", "six",
  "seven", "eight", "nine", "colon", "semicolon",
  "less", "equal", "greater", "question", "at",
  "A", "B", "C", "D", "E",
  "F", "G", "H", "I", "J",
  "K", "L", "M", "N", "O",
  "P", "Q", "R", "S", "T",
  "U", "V", "W", "X", "Y",
  "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
  "underscore", "grave", "a", "b", "c",
  "d", "e", "f", "g", "h",
  "i", "j", "k", "l", "m",
  "n", "o", "p", "q", "r",
  "s", "t", "u", "v", "w",
  "x", "y", "z", "braceleft", "bar",
  "braceright", "asciitilde", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", ".notdef",
  ".notdef", ".notdef", ".notdef", ".notdef", "dotlessi",
  "quoteleft", "quoteright", "circumflex", "tilde", "macron",
  "breve", "dotaccent", "dieresis", ".notdef", "ring",
  "cedilla", ".notdef", "hungarumlaut", "ogonek", "caron",
  "space", "exclamdown", "cent", "sterling", "currency",
  "yen", "brokenbar", "section", "dieresis", "copyright",
  "ordfeminine", "guillemotleft", "logicalnot", "hyphen",
  "registered",
  "macron", "degree", "plusminus", "twosuperior", "threesuperior",
  "acute", "mu", "paragraph", "periodcentered", "cedilla",
  "onesuperior", "ordmasculine", "guillemotright", "onequarter",
  "onehalf",
  "threequarters", "questiondown", "Agrave", "Aacute", "Acircumflex",
  "Atilde", "Adieresis", "Aring", "AE", "Ccedilla",
  "Egrave", "Eacute", "Ecircumflex", "Edieresis", "Igrave",
  "Iacute", "Icircumflex", "Idieresis", "Eth", "Ntilde",
  "Ograve", "Oacute", "Ocircumflex", "Otilde", "Odieresis",
  "multiply", "Oslash", "Ugrave", "Uacute", "Ucircumflex",
  "Udieresis", "Yacute", "Thorn", "germandbls", "agrave",
  "aacute", "acircumflex", "atilde", "adieresis", "aring",
  "ae", "ccedilla", "egrave", "eacute", "ecircumflex",
  "edieresis", "igrave", "iacute", "icircumflex", "idieresis",
  "eth", "ntilde", "ograve", "oacute", "ocircumflex",
  "otilde", "odieresis", "divide", "oslash", "ugrave",
  "uacute", "ucircumflex", "udieresis", "yacute", "thorn",
  "ydieresis"
};

static int
parse_encoding (char **enc_vec, unsigned char **start, unsigned char *end, int mode)
{
  pst_obj *tok;
  int      code;

  /*
   *  StandardEncoding def
   * or
   *  ISOLatin1Encoding def
   * or
   *  0 1 255 {1 index exch /.notdef put } for
   *  dup int name put
   *  ...
   *  [readonly] def
   */
  tok = pst_get_token(start, end);
  if (MATCH_OP(tok, "StandardEncoding")) {
    RELEASE_TOK(tok);
    if (enc_vec) {
      for (code = 0; code < 256; code++) {
	if (StandardEncoding[code] &&
	    strcmp(StandardEncoding[code], ".notdef") != 0) {
	  enc_vec[code] = NEW(strlen(StandardEncoding[code])+1, char);
	  strcpy(enc_vec[code], StandardEncoding[code]);
	} else {
	  enc_vec[code] = NULL;
	}
      }
    }
  } else if (MATCH_OP(tok, "ISOLatin1Encoding")) {
    RELEASE_TOK(tok);
    if (enc_vec) {
      for (code = 0; code < 256; code++) {
	if (ISOLatin1Encoding[code] &&
	    strcmp(ISOLatin1Encoding[code], ".notdef") != 0) {
	  enc_vec[code] = NEW(strlen(ISOLatin1Encoding[code])+1, char);
	  strcpy(enc_vec[code], ISOLatin1Encoding[code]);
	} else {
	  enc_vec[code] = NULL;
	}
      }
    }
  } else if (MATCH_OP(tok, "ExpertEncoding")) {
    RELEASE_TOK(tok);
    if (enc_vec) {
      WARN("ExpertEncoding not supported.");
      RELEASE_TOK(tok);
      return -1;
    }
    /*
     * Not supported yet.
     */
  } else {
    RELEASE_TOK(tok);
    seek_operator(start, end, "array");
    /*
     * Pick all seaquences that matches "dup n /Name put" until
     * occurrence of "def" or "readonly".
     */
    while (*start < end &&
	   (tok = pst_get_token(start, end)) != NULL) {
      if (MATCH_OP(tok, "def") || MATCH_OP(tok, "readonly")) {
	RELEASE_TOK(tok);
	break;
      } else if (!MATCH_OP(tok, "dup")) {
	RELEASE_TOK(tok);
	continue;
      }
      RELEASE_TOK(tok);

      tok = pst_get_token(start, end);
      if (!tok || !PST_INTEGERTYPE(tok) ||
	  (code = pst_getIV(tok)) > 255 || code < 0) {
	RELEASE_TOK(tok);
	continue;
      }
      RELEASE_TOK(tok);

      tok = pst_get_token(start, end);
      if (!tok || !PST_NAMETYPE(tok)) {
	RELEASE_TOK(tok);
	continue;
      }
      if (enc_vec) {
	if (enc_vec[code])
	  RELEASE(enc_vec[code]);
	enc_vec[code] = (char *) pst_getSV(tok);
      }
      RELEASE_TOK(tok);

      tok = pst_get_token(start, end);
      if (!MATCH_OP(tok, "put")) {
	if (enc_vec[code]) {
	  RELEASE(enc_vec[code]);
	  enc_vec[code] = NULL;
	}
	RELEASE_TOK(tok);
	continue;
      }
      RELEASE_TOK(tok);
    }
  }

  return 0;
}

#ifndef CS_STR_LEN_MAX
#define CS_STR_LEN_MAX 65536UL
#endif
#ifndef CFF_GLYPH_MAX
#define CFF_GLYPH_MAX  CFF_SID_MAX
#endif

static int
parse_subrs (cff_font *font,
	     unsigned char **start, unsigned char *end, int lenIV, int mode)
{
  cff_index *subrs;
  pst_obj   *tok;
  long       i, count, offset, max_size;
  long      *offsets, *lengths;
  card8     *data;

  tok = pst_get_token(start, end);
  if (!PST_INTEGERTYPE(tok) || pst_getIV(tok) < 0) {
    WARN("Parsing Subrs failed.");
    RELEASE_TOK(tok);
    return -1;
  }

  count = pst_getIV(tok);
  RELEASE_TOK(tok);

  if (count == 0) {
    font->subrs[0] = NULL;
    return 0;
  }

  tok = pst_get_token(start, end);
  if (!MATCH_OP(tok, "array")) {
    RELEASE_TOK(tok);
    return -1;
  }
  RELEASE_TOK(tok);

  if (mode != 1) {
    max_size = CS_STR_LEN_MAX;
    data     = NEW(max_size, card8);
    offsets  = NEW(count, long);
    lengths  = NEW(count, long);
    memset(offsets, 0, sizeof(long)*count);
    memset(lengths, 0, sizeof(long)*count);
  } else {
    max_size = 0;
    data     = NULL;
    offsets  = NULL;
    lengths  = NULL;
  }

  offset = 0;
  /* dup subr# n-bytes RD n-binary-bytes NP */
  for (i = 0; i < count;) {
    long idx, len;

    tok = pst_get_token(start, end);
    if (!tok) {
      if (data)    RELEASE(data);
      if (offsets) RELEASE(offsets);
      if (lengths) RELEASE(lengths);
      return -1;
    } else if (MATCH_OP(tok, "ND") ||
	       MATCH_OP(tok, "|-") || MATCH_OP(tok, "def")) {
      RELEASE_TOK(tok);
      break;
    } else if (!MATCH_OP(tok, "dup")) {
      RELEASE_TOK(tok);
      continue;
    }
    RELEASE_TOK(tok);

    /* Found "dup" */
    tok = pst_get_token(start, end);
    if (!PST_INTEGERTYPE(tok) || pst_getIV(tok) < 0 ||
	pst_getIV(tok) >= count) {
      RELEASE_TOK(tok);
      if (data)    RELEASE(data);
      if (offsets) RELEASE(offsets);
      if (lengths) RELEASE(lengths);
      return -1;
    }
    idx = pst_getIV(tok);
    RELEASE_TOK(tok);

    tok = pst_get_token(start, end);
    if (!PST_INTEGERTYPE(tok) || pst_getIV(tok) < 0 ||
	pst_getIV(tok) > CS_STR_LEN_MAX) {
      RELEASE_TOK(tok);
      return -1;
    }
    len = pst_getIV(tok);
    RELEASE_TOK(tok);

    tok = pst_get_token(start, end);
    if (!MATCH_OP(tok, "RD") && !MATCH_OP(tok, "-|") &&
	seek_operator(start, end, "readstring") < 0) {
      RELEASE_TOK(tok);
      if (data)    RELEASE(data);
      if (offsets) RELEASE(offsets);
      if (lengths) RELEASE(lengths);
      return -1;
    }
    RELEASE_TOK(tok);

    *start += 1;
    if (*start + len >= end) {
      if (data)    RELEASE(data);
      if (offsets) RELEASE(offsets);
      if (lengths) RELEASE(lengths);
      return -1;
    }
    if (mode != 1) {
      if (offset + len >= max_size) {
	max_size += CS_STR_LEN_MAX;
	data = RENEW(data, max_size, card8);
      }
      if (lenIV >= 0) {
	long j;

	offsets[idx] = offset;
	lengths[idx] = len - lenIV;
	t1_crypt_init(T1_CHARKEY);
	for (j = 0; j < lenIV; j++)
	  t1_decrypt(*((*start)+j));
	for (j = lenIV; j < len  ; j++) {
	  data[offset] = (card8) t1_decrypt(*((*start)+j));
	  offset++;
	}
      } else if (len > 0) {
	offsets[idx] = offset;
	lengths[idx] = len;
	memcpy(&data[offset], *start, len);
	offset += len;
      }
    }
    *start += len;
    i++;
  }

  if (mode != 1) {
    subrs = font->subrs[0] = cff_new_index(count);
    subrs->data = NEW(offset, card8);
    offset = 0;
    for (i = 0; i < count; i++) {
      subrs->offset[i] = offset + 1;
      if (lengths[i] > 0) {
	memcpy(subrs->data + offset, data + offsets[i], lengths[i]);
	offset += lengths[i];
      }
    }
    subrs->offset[count] = offset + 1;

    RELEASE(data);
    RELEASE(offsets);
    RELEASE(lengths);
  }

  return 0;
}

static int
parse_charstrings (cff_font *font,
		   unsigned char **start, unsigned char *end, int lenIV, int mode)
{
  cff_index    *charstrings;
  cff_charsets *charset;
  pst_obj      *tok;
  long          i, count, have_notdef;
  long          max_size, offset;

  /* /CharStrings n dict dup begin
   * /GlyphName n-bytes RD -n-binary-bytes- ND
   * ...
   * end
   *  - stack - ... /CharStrings dict
   */
  tok = pst_get_token(start, end);
  if (!PST_INTEGERTYPE(tok) ||
      pst_getIV(tok) < 0 || pst_getIV(tok) > CFF_GLYPH_MAX) {
    RELEASE_TOK(tok);
    return -1;
  }
  count = pst_getIV(tok);
  RELEASE_TOK(tok);

  if (mode != 1) {
    charstrings = cff_new_index(count);
    max_size    = CS_STR_LEN_MAX;
    charstrings->data = NEW(max_size, card8);
  } else {
    charstrings = NULL;
    max_size    = 0;
  }
  font->cstrings = charstrings;

  charset = font->charsets = NEW(1, cff_charsets);
  charset->format = 0;
  charset->num_entries = count-1;
  charset->data.glyphs = NEW(count-1, s_SID);
  memset(charset->data.glyphs, 0, sizeof(s_SID)*(count-1));

  offset      = 0;
  have_notdef = 0; /* .notdef must be at gid = 0 in CFF */

  seek_operator(start, end, "begin");
  for (i = 0; i < count; i++) {
    char *glyph_name;
    long  len, gid, j;

    glyph_name = get_next_key(start, end);
    if (!glyph_name) {
      return -1;
    } else if (!strcmp(glyph_name, ".notdef")) {
      gid = 0;
      have_notdef = 1;
    } else if (have_notdef) {
      gid = i;
    } else if (i == count - 1) {
      WARN("No .notdef glyph???");
      return -1;
    } else {
      gid = i+1;
    }

    if (gid > 0)
      charset->data.glyphs[gid-1] = cff_add_string(font, glyph_name);
    RELEASE(glyph_name);

    tok = pst_get_token(start, end);
    if (!PST_INTEGERTYPE(tok) ||
	pst_getIV(tok) < 0 || pst_getIV(tok) > CS_STR_LEN_MAX) {
      RELEASE_TOK(tok);
      return -1;
    }
    len = pst_getIV(tok);
    RELEASE_TOK(tok);

    tok = pst_get_token(start, end);
    if (!MATCH_OP(tok, "RD") &&
	!MATCH_OP(tok, "-|") &&
	seek_operator(start, end, "readstring") < 0) {
      RELEASE_TOK(tok);
      return -1;
    }
    RELEASE_TOK(tok);

    if (*start + len + 1 >= end) {
      return -1;
    }
    if (mode != 1) {
      if (offset + len >= max_size) {
	max_size += MAX(len, CS_STR_LEN_MAX);
	charstrings->data = RENEW(charstrings->data, max_size, card8);
      }
      if (gid == 0) {
	if (lenIV >= 0) {
	  memmove(charstrings->data + len - lenIV, charstrings->data, offset);
	  for (j = 1; j <= i; j++) {
	    charstrings->offset[j] += len - lenIV;
	  }
	} else {
	  memmove(charstrings->data + len, charstrings->data, offset);
	  for (j = 1; j <= i; j++) {
	    charstrings->offset[j] += len;
	  }
	}
      }
    }

    *start += 1;
    if (mode != 1) {
      if (lenIV >= 0) {
	t1_crypt_init(T1_CHARKEY);
	for (j = 0; j < lenIV; j++)
	  t1_decrypt(*((*start)+j));
	if (gid == 0) {
	  charstrings->offset[gid] = 1; /* start at 1 */
	  for (j = lenIV; j < len; j++) {
	    charstrings->data[j-lenIV] = t1_decrypt(*((*start)+j));
	  }
	} else {
	  charstrings->offset[gid] = offset + 1; /* start at 1 */
	  for (j = lenIV; j < len; j++) {
	    charstrings->data[offset+j-lenIV] = t1_decrypt(*((*start)+j));
	  }
	}
	offset += len - lenIV;
      } else {
	if (gid == 0) {
	  charstrings->offset[gid] = 1;
	  memcpy(&charstrings->data[0], *start, len);
	} else {
	  charstrings->offset[gid] = offset + 1;
	  memcpy(&charstrings->data[offset], *start, len);
	}
	offset += len;
      }
    }
    *start += len;
  }
  if (mode != 1)
    charstrings->offset[count] = offset + 1;
  font->num_glyphs = count;

  return 0;
}

#define CHECK_ARGN_EQ(n) if (argn != (n)) {\
  WARN("%d values expected but only %d read.", (n), argn);\
  RELEASE(key);\
  return -1;\
}
#define CHECK_ARGN_GE(n) if (argn < (n)) {\
  WARN("%d values expected but only %d read.", (n), argn);\
  RELEASE(key);\
  return -1;\
}

#define MAX_ARGS 127
static int
parse_part2 (cff_font *font, unsigned char **start, unsigned char *end, int mode)
{
  char  *key;
  double argv[MAX_ARGS];
  int    argn, lenIV = 4;

  while (*start < end &&
	 (key = get_next_key(start, end)) != NULL) {
    if (!strcmp(key, "Subrs")) {
      /* levIV must appear before Subrs */
      if (parse_subrs(font, start, end, lenIV, mode) < 0) {
	RELEASE(key);
	return -1;
      }
    } else if (!strcmp(key, "CharStrings")) {
      if (parse_charstrings(font, start, end, lenIV, mode) < 0) {
	RELEASE(key);
	return -1;
      }
    } else if (!strcmp(key, "lenIV")) {
      argn = parse_nvalue(start, end, argv, 1);
      CHECK_ARGN_EQ(1);
      lenIV = (int) argv[0];
    } else if (!strcmp(key, "BlueValues") ||
	       !strcmp(key, "OtherBlues") ||
	       !strcmp(key, "FamilyBlues") ||
	       !strcmp(key, "FamilyOtherBlues") ||
	       !strcmp(key, "StemSnapH") ||
	       !strcmp(key, "StemSnapV")) {
      /*
       * Operand values are delta in CFF font dictionary encoding.
       */
      argn = parse_nvalue(start, end, argv, MAX_ARGS);
      CHECK_ARGN_GE(0);
      cff_dict_add(font->private[0], key, argn);
      while (argn-- > 0) {
	cff_dict_set(font->private[0], key, argn,
		     (argn == 0) ? argv[argn] : argv[argn] - argv[argn-1]);
      }
    } else if (!strcmp(key, "StdHW") ||
	       !strcmp(key, "StdVW") ||
	       !strcmp(key, "BlueScale") ||
	       !strcmp(key, "BlueShift") ||
	       !strcmp(key, "BlueFuzz")  ||
	       !strcmp(key, "LanguageGroup") ||
	       !strcmp(key, "ExpansionFactor")) {
      /*
       * Value of StdHW and StdVW is described as an array in the
       * Type 1 Font Specification but is a number in CFF format.
       */
      argn = parse_nvalue(start, end, argv, 1);
      CHECK_ARGN_EQ(1);
      cff_dict_add(font->private[0], key, 1);
      cff_dict_set(font->private[0], key, 0, argv[0]);
    } else if (!strcmp(key, "ForceBold")) {
      argn = parse_bvalue(start, end, &(argv[0]));
      CHECK_ARGN_EQ(1);
      if (argv[0] != 0) {
	cff_dict_add(font->private[0], key, 1);
	cff_dict_set(font->private[0], key, 0, 1);
      }
    }
    /*
     * MinFeature, RndStemUp, UniqueID, Password ignored.
     */
    RELEASE(key);
  }

  return 0;
}

#ifndef TYPE1_NAME_LEN_MAX
#define TYPE1_NAME_LEN_MAX 127
#endif

static long
parse_part1 (cff_font *font, char **enc_vec,
	     unsigned char **start, unsigned char *end, int mode)
{
  char  *key, *strval;
  double argv[MAX_ARGS];
  int    argn; /* Macro CHECK_ARGN_XX assume 'argn' is used. */

  /*
   * We skip PostScript code inserted before the beginning of 
   * font dictionary so that parser will not be confused with
   * it. See LMRoman10-Regular (lmr10.pfb) for example.
   */
  if (seek_operator(start, end, "begin") < 0)
    return -1;

  while (*start < end &&
	 (key = get_next_key(start, end)) != NULL) {
    if (!strcmp(key, "Encoding")) {
      if (parse_encoding(enc_vec, start, end, mode) < 0) {
	RELEASE(key);
	return -1;
      }
    } else if (!strcmp(key, "FontName")) {
      argn = parse_svalue(start, end, &strval);
      CHECK_ARGN_EQ(1);
      if (strlen(strval) > TYPE1_NAME_LEN_MAX) {
	WARN("FontName too long: %s (%d bytes)", strval, strlen(strval));
	strval[TYPE1_NAME_LEN_MAX] = '\0';
      }
      cff_set_name(font, strval);
      RELEASE(strval);
    } else if (!strcmp(key, "FontType")) {
      argn = parse_nvalue(start, end, argv, 1);
      CHECK_ARGN_EQ(1);
      if (argv[0] != 1.0) {
	WARN("FontType %d not supported.", (int) argv[0]);
	RELEASE(key);
	return -1;
      }
#if 0
      /* DISABLED:
       *
       * Subsetted font shouldn't have UniqueID.
       */
    } else if (!strcmp(key, "UniqueID")) {
      argn = parse_nvalue(start, end, argv, 1);
      CHECK_ARGN_EQ(1);
      cff_dict_add(font->topdict, key, 1);
      cff_dict_set(font->topdict, key, 0, argv[0]);
#endif
    } else if (!strcmp(key, "ItalicAngle") ||
	       !strcmp(key, "StrokeWidth") ||
	       !strcmp(key, "PaintType")) {
      argn = parse_nvalue(start, end, argv, 1);
      CHECK_ARGN_EQ(1);
      if (argv[0] != 0.0) {
#if 0
	/*
	 * Positive value in Bitstream CharterBT-Italic ???
	 */
	if (!strcmp(key, "ItalicAngle") && argv[0] > 0) {
	  WARN("Positive ItalicAngle value: %g", argv[0]);
	  argv[0] *= -1;
	}
#endif
	cff_dict_add(font->topdict, key, 1);
	cff_dict_set(font->topdict, key, 0, argv[0]);
      }
    } else if (!strcmp(key, "UnderLinePosition") ||
	       !strcmp(key, "UnderLineThickness")) {
      argn = parse_nvalue(start, end, argv, 1);
      CHECK_ARGN_EQ(1);
      cff_dict_add(font->topdict, key, 1);
      cff_dict_set(font->topdict, key, 0, argv[0]);
    } else if (!strcmp(key, "FontBBox")) {
      argn = parse_nvalue(start, end, argv, 4);
      CHECK_ARGN_EQ(4);
      cff_dict_add(font->topdict, key, 4);
      while (argn-- > 0) {
	cff_dict_set(font->topdict, key, argn, argv[argn]);
      }
    } else if (!strcmp(key, "FontMatrix")) {
      argn = parse_nvalue(start, end, argv, 6);
      CHECK_ARGN_EQ(6);
      if (argv[0] != 0.001 || argv[1] != 0.0 || argv[2] != 0.0 ||
	  argv[3] != 0.001 || argv[4] != 0.0 || argv[5] != 0.0) {
	cff_dict_add(font->topdict, key, 6);
	while (argn-- > 0) {
	  cff_dict_set(font->topdict, key, argn, argv[argn]);
	}
      }
    } else if (!strcmp(key, "version")  || !strcmp(key, "Notice") ||
	       !strcmp(key, "FullName") || !strcmp(key, "FamilyName") ||
	       !strcmp(key, "Weight")   || !strcmp(key, "Copyright")) {
      /*
       * FontInfo
       */
      argn = parse_svalue(start, end, &strval);
      CHECK_ARGN_EQ(1);
      {
	s_SID sid;

	cff_dict_add(font->topdict, key, 1);
	if ((sid = cff_get_sid(font, strval)) == CFF_STRING_NOTDEF)
	  sid = cff_add_string(font, strval); /* FIXME */
	cff_dict_set(font->topdict, key, 0, sid);
      }
      RELEASE(strval);
    } else if (!strcmp(key, "IsFixedPitch")) {
      argn = parse_bvalue(start, end, &(argv[0]));
      CHECK_ARGN_EQ(1);
      if (argv[0] != 0.0) {
	cff_dict_add(font->private[0], key, 1);
	cff_dict_set(font->private[0], key, 0, 1);
      }
    }
    RELEASE(key);
  }

  return 0;
}

int
is_pfb (FILE *fp)
{
  char sig[15];
  int i, ch;

  rewind(fp);
  if ((ch = fgetc(fp)) != 128 ||
      (ch = fgetc (fp)) < 0 || ch > 3) {
    return 0;
  }
  for (i = 0; i < 4; i++) {
    if ((ch = fgetc(fp)) < 0) {
      return 0;
    }
  }
  for (i = 0; i < 14; i++) {
    if ((ch = fgetc(fp)) < 0) {
      return 0;
    }
    sig[i] = (char) ch;
  }
  if (!memcmp(sig, "%!PS-AdobeFont", 14) ||
      !memcmp(sig, "%!FontType1", 11)) {
    return 1;
  } else if (!memcmp(sig, "%!PS", 4)) {
    sig[14] = '\0';
    WARN("Ambiguous PostScript resource type: %s", sig);
    return 1;
  } else {
    WARN("Not a PFB font file?");
    return 0;
  }

  return 0;
}


#define PFB_SEG_TYPE_ASCII  1
#define PFB_SEG_TYPE_BINARY 2

static unsigned char *
get_pfb_segment (FILE *fp, int expected_type, long *length)
{
  unsigned char *buffer;
  long bytesread;

  buffer = NULL; bytesread = 0;
  for (;;) {
    int ch;

    ch = fgetc(fp);
    if (ch < 0) {
      break;
    } else if (ch != 128) {
      ERROR("Not a pfb file?");
    }
    ch = fgetc(fp);
    if (ch < 0 || ch != expected_type) {
      seek_relative(fp, -2);
      break;
    }
    {
      long slen, rlen;
      int  i;

      slen = 0;
      for (i = 0; i < 4; i++) {
	if ((ch = fgetc(fp)) < 0) {
	  if (buffer)
	    RELEASE(buffer);
	  return NULL;
	}
	slen = slen + (ch << (8*i));
      }
      buffer = RENEW(buffer, bytesread + slen, unsigned char);
      while (slen > 0) {
	rlen = fread(buffer + bytesread, sizeof(unsigned char), slen, fp);
	if (rlen < 0) {
	  if (buffer)
	    RELEASE(buffer);
	  return NULL;
	}
	slen -= rlen;
	bytesread += rlen;
      }
    }
  }
  if (bytesread == 0) {
    ERROR("PFB segment length zero?");
  }

  if (length)
    *length = bytesread;
  return buffer;
}

char *
t1_get_standard_glyph (int code)
{
  if (!StandardEncoding[code])
    return NULL;

  return (char *) StandardEncoding[code];
}

int
t1_get_fontname (FILE *fp, char *fontname)
{
  unsigned char *buffer, *start, *end;
  long  length;
  char *key;
  int   fn_found = 0;

  rewind(fp);
  buffer = get_pfb_segment(fp, PFB_SEG_TYPE_ASCII, &length);
  if (buffer == NULL || length == 0)
    ERROR("Reading PFB (ASCII part) file failed.");
  start = buffer;
  end   = buffer + length;

  if (seek_operator(&start, end, "begin") < 0) {
    RELEASE(buffer);
    return -1;
  }

  while (!fn_found && start < end &&
	 (key = get_next_key(&start, end)) != NULL) {
    if (!strcmp(key, "FontName")) {
      char *strval;
      if (parse_svalue(&start, end, &strval) == 1) {
	if (strlen(strval) > TYPE1_NAME_LEN_MAX) {
	  WARN("FontName \"%s\" too long. (%d bytes)", strval, strlen(strval));
	  strval[TYPE1_NAME_LEN_MAX] = '\0';
	}
	strcpy(fontname, strval);
	RELEASE(strval);
	fn_found = 1;
      }
    }
    RELEASE(key);
  }
  RELEASE(buffer);

  return 0;
}

static void
init_cff_font (cff_font *cff)
{
  cff->stream = NULL;
  cff->filter = 0;
  cff->fontname = NULL;
  cff->index    = 0;
  cff->flag = FONTTYPE_FONT;

  cff->header.major = 1;
  cff->header.minor = 0;
  cff->header.hdr_size = 4;
  cff->header.offsize  = 4;
  cff->name     = cff_new_index(1);
  cff->topdict  = cff_new_dict();
  cff->string   = NULL;
  cff->gsubr    = cff_new_index(0); /* No Global Subr */
  cff->encoding = NULL;
  cff->charsets = NULL;
  cff->fdselect = NULL;
  cff->cstrings = NULL;
  cff->fdarray  = NULL;
  cff->private  = NEW(1, cff_dict *);
  cff->private[0] = cff_new_dict();
  cff->subrs = NEW(1, cff_index *);
  cff->subrs[0] = NULL;

  cff->offset  = 0;
  cff->gsubr_offset = 0;
  cff->num_glyphs   = 0;
  cff->num_fds      = 1;
  cff->_string = cff_new_index(0);
}

cff_font *
t1_load_font (char **enc_vec, int mode, FILE *fp)
{
  long length;
  cff_font *cff;
  unsigned char *buffer, *start, *end;

  rewind(fp);
  /* ASCII section */
  buffer = get_pfb_segment(fp, PFB_SEG_TYPE_ASCII, &length);
  if (buffer == NULL || length == 0) {
    ERROR("Reading PFB (ASCII part) file failed.");
    return NULL;
  }

  cff = NEW(1, cff_font);
  init_cff_font(cff);

  start = buffer; end = buffer + length;
  if (parse_part1(cff, enc_vec, &start, end, mode) < 0) {
    cff_close(cff);
    RELEASE(buffer);
    ERROR("Reading PFB (ASCII part) file failed.");
    return NULL;
  }
  RELEASE(buffer);

  /* Binary section */
  buffer = get_pfb_segment(fp, PFB_SEG_TYPE_BINARY, &length);
  if (buffer == NULL || length == 0) {
    cff_close(cff);
    RELEASE(buffer);
    ERROR("Reading PFB (BINARY part) file failed.");
    return NULL;
  } else {
    long i;

    t1_crypt_init(T1_EEKEY);
    for (i = 0; i < length; i++) {
      buffer[i] = (unsigned char) t1_decrypt(buffer[i]);
    }
  }
  start = buffer + 4; end = buffer + length;
  if (parse_part2(cff, &start, end, mode) < 0) {
    cff_close(cff);
    RELEASE(buffer);
    ERROR("Reading PFB (BINARY part) file failed.");
    return NULL;
  }
  RELEASE(buffer);

  cff_update_string(cff);

  /* Remaining section ignored. */

  return cff;
}
