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

    Copyright (C) 2002-2020 by Jin-Hwan Cho and Shunsaku Hirata,
    the dvipdfmx project team.

    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.
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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

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

#include "tfm.h"

#include "pdfobj.h"
#include "pdfparse.h"
#include "pdfdev.h"
#include "pdfdoc.h"

#include "pdfcolor.h"
#include "pdfdraw.h"

#include "fontmap.h"
#include "subfont.h"

#include "pdfximage.h"

#include "mpost.h"
#include "dvipdfmx.h"

/*
 * Define the origin as (llx, lly) in order to
 * match the new xetex.def and dvipdfmx.def
 */

static double Xorigin, Yorigin;
static int    translate_origin = 0;

void
mps_set_translate_origin (int v) {
  translate_origin = v;
}

/*
 * In PDF, current path is not a part of graphics state parameter.
 * Hence, current path is not saved by the "q" operator  and is not
 * recovered by the "Q" operator. This means that the following PS
 * code
 *
 *   <path construction> gsave <path painting> grestore ...
 *
 * can't be translated to PDF code
 *
 *   <path construction> q <path painting> Q ...
 *
 * . Only clipping path (which is graphics state parameter in PDF
 * too) is treated in the same way. So, we write clipping path
 * immediately and forget about it but remember current path.
 */

static int mp_parse_body (const char **start, const char *end, double x_user, double y_user);

static struct mp_font
{
  char   *font_name;
  int     font_id;
  int     tfm_id;     /* Used for text width calculation */
  int     subfont_id;
  double  pt_size;
} font_stack[PDF_GSAVE_MAX] = {
  {NULL, -1, -1, -1, 0}
};
static int currentfont = 0;

#define CURRENT_FONT() ((currentfont < 0) ? NULL : &font_stack[currentfont])
#define FONT_DEFINED(f) ((f) && (f)->font_name && ((f)->font_id >= 0))

static void
clear_mp_font_struct (struct mp_font *font)
{
  ASSERT(font);

  if (font->font_name)
    RELEASE(font->font_name);
  font->font_name  = NULL;
  font->font_id    = -1;
  font->tfm_id     = -1;
  font->subfont_id = -1;
  font->pt_size    = 0.0;
}

/* Compatibility */
#define MP_CMODE_MPOST    0
#define MP_CMODE_DVIPSK   1
#define MP_CMODE_PTEXVERT 2
static int mp_cmode = MP_CMODE_MPOST;

static int
mp_setfont (const char *font_name, double pt_size)
{
  const char     *name = font_name;
  struct mp_font *font;
  int             subfont_id = -1;
  fontmap_rec    *mrec;

  font = CURRENT_FONT();

  mrec = pdf_lookup_fontmap_record(font_name);
  if (mrec && mrec->charmap.sfd_name && mrec->charmap.subfont_id) {
    subfont_id = sfd_load_record(mrec->charmap.sfd_name, mrec->charmap.subfont_id);
  }

  /* See comments in dvi_locate_font() in dvi.c. */
  if (mrec && mrec->map_name) {
    name = mrec->map_name;
  } else {
    name = font_name;
  }

  if (font->font_name)
    RELEASE(font->font_name);
  font->font_name  = NEW(strlen(font_name) + 1, char);
  strcpy(font->font_name, font_name);
  font->subfont_id = subfont_id;
  font->pt_size    = pt_size;
  font->tfm_id     = tfm_open(font_name, 0); /* Need not exist in MP mode */
  font->font_id    = pdf_dev_locate_font(name,
                                         (spt_t) (pt_size * dev_unit_dviunit()));

  if (font->font_id < 0) {
    ERROR("MPOST: No physical font assigned for \"%s\".", font_name);
    return 1;
  }

  return  0;
}

static void
save_font (void)
{
  struct mp_font *current, *next;

  current = &font_stack[currentfont++];
  next    = &font_stack[currentfont  ];
  if (FONT_DEFINED(current)) {
    next->font_name = NEW(strlen(current->font_name)+1, char);
    strcpy(next->font_name, current->font_name);
    next->font_id    = current->font_id;
    next->pt_size    = current->pt_size;
    next->subfont_id = current->subfont_id;
    next->tfm_id     = current->tfm_id;    
  } else {
    next->font_name  = NULL;
    next->font_id    = -1;
    next->pt_size    = 0.0;
    next->subfont_id = -1;
    next->tfm_id     = -1;
  }
}

static void
restore_font (void)
{
  struct mp_font *current;

  current = CURRENT_FONT();
  if (current) {
    clear_mp_font_struct(current);
  }

  currentfont--;
}

static void
clear_fonts (void)
{
  while (currentfont > 0) {
    clear_mp_font_struct(&font_stack[currentfont]);
    currentfont--;
  }
}

static int
is_fontname (const char *token)
{
  fontmap_rec *mrec;

  mrec = pdf_lookup_fontmap_record(token);
  if (mrec)
    return  1;

  return  tfm_exists(token);
}

int
mps_scan_bbox (const char **pp, const char *endptr, pdf_rect *bbox)
{
  char  *number;
  double values[4];
  int    i;

  /* skip_white() skips lines starting '%'... */
  while (*pp < endptr && isspace((unsigned char)**pp))
    (*pp)++;

  /* Scan for bounding box record */
  while (*pp < endptr && **pp == '%') {
    if (*pp + 14 < endptr &&
	!strncmp(*pp, "%%BoundingBox:", 14)) {

      *pp += 14;

      for (i = 0; i < 4; i++) {
	skip_white(pp, endptr);
	number = parse_number(pp, endptr);
	if (!number) {
	  break;
	}
	values[i] = atof(number);
	RELEASE(number);
      }
      if (i < 4) {
	return -1;
      } else {
	/* The new xetex.def and dvipdfmx.def require bbox->llx = bbox->lly = 0.  */
        if (translate_origin) {
          bbox->llx = 0;
          bbox->lly = 0;
          bbox->urx = values[2] - values[0];
          bbox->ury = values[3] - values[1];

          Xorigin = (double)values[0];
          Yorigin = (double)values[1];
        } else {
          bbox->llx = values[0];
          bbox->lly = values[1];
          bbox->urx = values[2];
          bbox->ury = values[3];

          Xorigin = 0.0;
          Yorigin = 0.0;
        }
        return 0;
      }
    }
    pdfparse_skip_line (pp, endptr);
    while (*pp < endptr && isspace((unsigned char)**pp))
      (*pp)++;
  }

  return -1;
}

static void
skip_prolog (const char **start, const char *end)
{
  int   found_prolog = 0;
  const char *save;

  save = *start;
  while (*start < end) {
    if (**start != '%')
      skip_white(start, end);
    if (*start >= end)
      break;
    if (!strncmp(*start, "%%EndProlog", 11)) {
      found_prolog = 1;
      pdfparse_skip_line(start, end);
      break;
    } else if (!strncmp(*start, "%%Page:", 7)) {
      pdfparse_skip_line(start, end);
      break;
    }
    pdfparse_skip_line(start, end);
  }
  if (!found_prolog) {
    *start = save;
  }

  return;
}

/* PostScript Operators */

/* Acoid conflict with SET... from <wingdi.h>.  */
#undef SETLINECAP
#undef SETLINEJOIN
#undef SETMITERLIMIT

#define ADD          	1
#define SUB		2
#define MUL		3
#define DIV		4
#define NEG    	        5
#define TRUNCATE	6

#define CLEAR		10
#define EXCH		11
#define POP		12

#define NEWPATH		31
#define CLOSEPATH    	32
#define MOVETO		33
#define RMOVETO         34
#define CURVETO   	35
#define RCURVETO        36
#define LINETO		37
#define RLINETO		38
#define ARC             39
#define ARCN            40

#define FILL		41
#define STROKE		42
#define SHOW		43

#define CLIP         	44
#define EOCLIP         	45

#define SHOWPAGE	49

#define GSAVE		50
#define GRESTORE	51

#define CONCAT       	52
#define SCALE		53
#define TRANSLATE	54
#define ROTATE          55

#define SETLINEWIDTH	60
#define SETDASH		61
#define SETLINECAP 	62
#define SETLINEJOIN	63
#define SETMITERLIMIT	64

#define SETGRAY		70
#define SETRGBCOLOR	71
#define SETCMYKCOLOR	72

#define CURRENTPOINT    80
#define IDTRANSFORM	81
#define DTRANSFORM	82

#define FINDFONT        201
#define SCALEFONT       202
#define SETFONT         203
#define CURRENTFONT     204

#define STRINGWIDTH     210

#define DEF             999

#define FSHOW		1001
#define STEXFIG         1002
#define ETEXFIG         1003
#define HLW             1004
#define VLW             1005
#define RD              1006
#define B               1007

static struct operators 
{
  const char *token;
  int         opcode;
} ps_operators[] = {
  {"add",          ADD},
  {"mul",          MUL},
  {"div",          DIV},
  {"neg",          NEG},
  {"sub",          SUB},  
  {"truncate",     TRUNCATE},

  {"clear",        CLEAR},
  {"exch",         EXCH},
  {"pop",          POP},

  {"clip",         CLIP},
  {"eoclip",       EOCLIP},
  {"closepath",    CLOSEPATH},
  {"concat",       CONCAT},

  {"newpath",      NEWPATH},
  {"moveto",       MOVETO},
  {"rmoveto",      RMOVETO},
  {"lineto",       LINETO},
  {"rlineto",      RLINETO},
  {"curveto",      CURVETO},
  {"rcurveto",     RCURVETO},
  {"arc",          ARC},
  {"arcn",         ARCN},

  {"stroke",       STROKE},  
  {"fill",         FILL},
  {"show",         SHOW},
  {"showpage",     SHOWPAGE},

  {"gsave",        GSAVE},
  {"grestore",     GRESTORE},
  {"translate",    TRANSLATE},
  {"rotate",       ROTATE},
  {"scale",        SCALE},

  {"setlinecap",    SETLINECAP},
  {"setlinejoin",   SETLINEJOIN},
  {"setlinewidth",  SETLINEWIDTH},
  {"setmiterlimit", SETMITERLIMIT},
  {"setdash",       SETDASH},

  {"setgray",      SETGRAY},
  {"setrgbcolor",  SETRGBCOLOR},
  {"setcmykcolor", SETCMYKCOLOR},

  {"currentpoint", CURRENTPOINT}, /* This is here for rotate support
				     in graphics package-not MP support */
  {"dtransform",   DTRANSFORM},
  {"idtransform",  IDTRANSFORM},

  {"findfont",     FINDFONT},
  {"scalefont",    SCALEFONT},
  {"setfont",      SETFONT},
  {"currentfont",  CURRENTFONT},

  {"stringwidth",  STRINGWIDTH},

  {"def", DEF} /* not implemented yet; just work with mptopdf */
};

static struct operators mps_operators[] = {
  {"fshow",       FSHOW}, /* exch findfont exch scalefont setfont show */
  {"startTexFig", STEXFIG},
  {"endTexFig",   ETEXFIG},
  {"hlw",         HLW}, /* 0 dtransform exch truncate exch idtransform pop setlinewidth */
  {"vlw",         VLW}, /* 0 exch dtransform truncate idtransform pop setlinewidth pop */
  {"l",           LINETO},
  {"r",           RLINETO},
  {"c",           CURVETO},
  {"m",           MOVETO},
  {"p",           CLOSEPATH},
  {"n",           NEWPATH},
  {"C",           SETCMYKCOLOR},
  {"G",           SETGRAY},
  {"R",           SETRGBCOLOR},
  {"lj",          SETLINEJOIN},
  {"ml",          SETMITERLIMIT},
  {"lc",          SETLINECAP},
  {"S",           STROKE},
  {"F",           FILL},
  {"q",           GSAVE},
  {"Q",           GRESTORE},
  {"s",           SCALE},
  {"t",           CONCAT},
  {"sd",          SETDASH},
  {"rd",          RD}, /* [] 0 setdash */
  {"P",           SHOWPAGE},
  {"B",           B}, /* gsave fill grestore */
  {"W",           CLIP}
};

#define NUM_PS_OPERATORS  (sizeof(ps_operators)/sizeof(ps_operators[0]))
#define NUM_MPS_OPERATORS (sizeof(mps_operators)/sizeof(mps_operators[0]))
static int
get_opcode (const char *token)
{
  int   i;

  for (i = 0; i < NUM_PS_OPERATORS; i++) {
    if (!strcmp(token, ps_operators[i].token)) {
      return ps_operators[i].opcode;
    }
  }

  for (i = 0; i < NUM_MPS_OPERATORS; i++) {
    if (!strcmp(token, mps_operators[i].token)) {
      return mps_operators[i].opcode;
    }
  }

  return -1;
}

#define PS_STACK_SIZE 1024

static pdf_obj *stack[PS_STACK_SIZE];
static unsigned top_stack = 0;

#define POP_STACK()     ((top_stack > 0) ? stack[--top_stack] : NULL)
#define PUSH_STACK(o,e) { \
  if (top_stack < PS_STACK_SIZE) { \
    stack[top_stack++] = (o); \
  } else { \
    WARN("PS stack overflow including MetaPost file or inline PS code"); \
    *(e) = 1; \
  } \
}

static int
do_exch (void)
{
  pdf_obj *tmp;

  if (top_stack < 2)
    return -1;

  tmp = stack[top_stack-1];
  stack[top_stack-1] = stack[top_stack-2];
  stack[top_stack-2] = tmp;

  return 0;
}

static int
do_clear (void)
{
  pdf_obj *tmp;

  while (top_stack > 0) {
    tmp = POP_STACK();
    if (tmp)
      pdf_release_obj(tmp);
  }

  return 0;
}

/* This should be set_bottom and clear (or
 * have independent stack) to ensure stack
 * depth do not go below real stack bottom.
 */
static void
mps_stack_clear_to (int depth)
{
  pdf_obj *tmp;

  while (top_stack > depth) {
    tmp = POP_STACK();
    if (tmp)
      pdf_release_obj(tmp);
  }

  return;
}

static int
pop_get_numbers (double *values, int count)
{
  pdf_obj *tmp;

  while (count-- > 0) {
    tmp = POP_STACK();
    if (!tmp) {
      WARN("mpost: Stack underflow.");
      break;
    } else if (!PDF_OBJ_NUMBERTYPE(tmp)) {
      WARN("mpost: Not a number!");
      pdf_release_obj(tmp);
      break;
    }
    values[count] = pdf_number_value(tmp);
    pdf_release_obj(tmp);
  }

  return (count + 1);
}

static int
cvr_array (pdf_obj *array, double *values, int count)
{
  if (!PDF_OBJ_ARRAYTYPE(array)) {
    WARN("mpost: Not an array!");
  } else {
    pdf_obj *tmp;

    while (count-- > 0) {
      tmp = pdf_get_array(array, count);
      if (!PDF_OBJ_NUMBERTYPE(tmp)) {
	WARN("mpost: Not a number!");
	break;
      }
      values[count] = pdf_number_value(tmp);
    }
  }
  if (array)
    pdf_release_obj(array);

  return (count + 1);
}

static int
is_fontdict (pdf_obj *dict)
{
  pdf_obj *tmp;

  if (!PDF_OBJ_DICTTYPE(dict))
    return 0;

  tmp = pdf_lookup_dict(dict, "Type");
  if (!tmp || !PDF_OBJ_NAMETYPE(tmp) ||
      strcmp(pdf_name_value(tmp), "Font")) {
    return 0;
  }

  tmp = pdf_lookup_dict(dict, "FontName");
  if (!tmp || !PDF_OBJ_NAMETYPE(tmp)) {
    return 0;
  }

  tmp = pdf_lookup_dict(dict, "FontScale");
  if (!tmp || !PDF_OBJ_NUMBERTYPE(tmp)) {
    return 0;
  }

  return 1;
}

static int
do_findfont (void)
{
  int error = 0;
  pdf_obj *font_dict, *font_name;

  font_name = POP_STACK();
  if (!font_name)
    return 1;
  else if (PDF_OBJ_STRINGTYPE(font_name) ||
	   PDF_OBJ_NAMETYPE(font_name)) {
    /* Do not check the existence...
     * The reason for this is that we cannot locate PK font without
     * font scale.
     */
    font_dict = pdf_new_dict();
    pdf_add_dict(font_dict,
		 pdf_new_name("Type"), pdf_new_name("Font"));
    if (PDF_OBJ_STRINGTYPE(font_name)) {
      pdf_add_dict(font_dict,
		   pdf_new_name("FontName"),
		   pdf_new_name(pdf_string_value(font_name)));
      pdf_release_obj(font_name);
    } else {
      pdf_add_dict(font_dict,
		   pdf_new_name("FontName"), font_name);
    }
    pdf_add_dict(font_dict,
		 pdf_new_name("FontScale"), pdf_new_number(1.0));

    if (top_stack < PS_STACK_SIZE) {
      stack[top_stack++] = font_dict;
    } else {
      WARN("PS stack overflow including MetaPost file or inline PS code");
      pdf_release_obj(font_dict);
      error = 1;
    }
  } else {
    error = 1;
  }

  return error;
}

static int
do_scalefont (void)
{
  int error = 0;
  pdf_obj *font_dict;
  pdf_obj *font_scale;
  double   scale;

  error = pop_get_numbers(&scale, 1);
  if (error)
    return error;

  font_dict = POP_STACK();
  if (!font_dict)
    error = 1;
  else if (is_fontdict(font_dict)) {
    font_scale  = pdf_lookup_dict(font_dict, "FontScale");
    pdf_set_number(font_scale, pdf_number_value(font_scale)*scale);
    if (top_stack < PS_STACK_SIZE) {
      stack[top_stack++] = font_dict;
    } else {
      WARN("PS stack overflow including MetaPost file or inline PS code");
      pdf_release_obj(font_dict);
      error = 1;
    }
  } else {
    error = 1;
  }

  return error;
}

static int
do_setfont (void)
{
  int      error = 0;
  char    *font_name;
  double   font_scale;
  pdf_obj *font_dict;

  font_dict = POP_STACK();
  if (!is_fontdict(font_dict))
    error = 1;
  else {
    /* Subfont support prevent us from managing
     * font in a single place...
     */
    font_name  = pdf_name_value  (pdf_lookup_dict(font_dict, "FontName"));
    font_scale = pdf_number_value(pdf_lookup_dict(font_dict, "FontScale"));

    error = mp_setfont(font_name, font_scale);
  }
  pdf_release_obj(font_dict);

  return error;
}

/* Push dummy font dict onto PS stack */
static int
do_currentfont (void)
{
  int             error = 0;
  struct mp_font *font;
  pdf_obj        *font_dict;

  font = CURRENT_FONT();
  if (!FONT_DEFINED(font)) {
    WARN("Currentfont undefined...");
    return 1;
  } else {
    font_dict = pdf_new_dict();
    pdf_add_dict(font_dict,
		 pdf_new_name("Type"),
		 pdf_new_name("Font"));
    pdf_add_dict(font_dict,
		 pdf_new_name("FontName"),
		 pdf_new_name(font->font_name));
    pdf_add_dict(font_dict,
		 pdf_new_name("FontScale"),
		 pdf_new_number(font->pt_size));
    if (top_stack < PS_STACK_SIZE) {
      stack[top_stack++] = font_dict;
    } else {
      WARN("PS stack overflow...");
      pdf_release_obj(font_dict);
      error = 1;
    }
  }

  return error;
}

static int
do_show (void)
{
  struct mp_font *font;
  pdf_coord       cp;
  pdf_obj        *text_str;
  int             length;
  unsigned char  *strptr;
  double          text_width;

  font = CURRENT_FONT();
  if (!FONT_DEFINED(font)) {
    WARN("Currentfont not set."); /* Should not be error... */
    return 1;
  }

  pdf_dev_currentpoint(&cp);

  text_str = POP_STACK();
  if (!PDF_OBJ_STRINGTYPE(text_str)) {
    if (text_str)
      pdf_release_obj(text_str);
    return 1;
  }
  if (font->font_id < 0) {
    WARN("mpost: not set."); /* Should not be error... */
    pdf_release_obj(text_str);
    return 1;
  }

  strptr = pdf_string_value (text_str);
  length = pdf_string_length(text_str);

  if (font->tfm_id < 0) {
    WARN("mpost: TFM not found for \"%s\".", font->font_name);
    WARN("mpost: Text width not calculated...");
  }

  text_width = 0.0;
  if (font->subfont_id >= 0) {
    unsigned short  uch;
    unsigned char  *ustr;
    int      i;

    ustr = NEW(length * 2, unsigned char);
    for (i = 0; i < length; i++) {
      uch = lookup_sfd_record(font->subfont_id, strptr[i]);
      ustr[2*i  ] = uch >> 8;
      ustr[2*i+1] = uch & 0xff;
      if (font->tfm_id >= 0) {
	text_width += tfm_get_width(font->tfm_id, strptr[i]);
      }
    }
    text_width *= font->pt_size;

    pdf_dev_set_string((spt_t)(cp.x * dev_unit_dviunit()),
		       (spt_t)(cp.y * dev_unit_dviunit()),
		       ustr, length * 2,
		       (spt_t)(text_width*dev_unit_dviunit()),
		       font->font_id);
    RELEASE(ustr);
  } else {
#define FWBASE ((double) (1<<20))
    if (font->tfm_id >= 0) {
      text_width = (double) tfm_string_width(font->tfm_id, strptr, length)/FWBASE;
      text_width *= font->pt_size;
    }
    pdf_dev_set_string((spt_t)(cp.x * dev_unit_dviunit()),
		       (spt_t)(cp.y * dev_unit_dviunit()),
		       strptr, length,
		       (spt_t)(text_width*dev_unit_dviunit()),
		       font->font_id);
  }

  if (pdf_dev_get_font_wmode(font->font_id)) {
    pdf_dev_rmoveto(0.0, -text_width);
  } else {
    pdf_dev_rmoveto(text_width, 0.0);
  }

  graphics_mode();
  pdf_release_obj(text_str);

  return 0;
}

static int
do_mpost_bind_def (const char *ps_code, double x_user, double y_user)
{
  int   error = 0;
  const char *start, *end;

  start = ps_code;
  end   = start + strlen(start);

  error = mp_parse_body(&start, end, x_user, y_user);

  return error;
}

static int
do_texfig_operator (int opcode, double x_user, double y_user)
{
  static transform_info fig_p;
  static int in_tfig = 0;
  static int xobj_id = -1;
  static int count   = 0;
  double values[6];
  int    error = 0;

  switch (opcode) {
  case STEXFIG:
    error = pop_get_numbers(values, 6);
    if (!error) {
      double   dvi2pts;
      char     resname[256];

      transform_info_clear(&fig_p);
      dvi2pts = 1.0/dev_unit_dviunit();

      fig_p.width    =  values[0] * dvi2pts;
      fig_p.height   =  values[1] * dvi2pts;
      fig_p.bbox.llx =  values[2] * dvi2pts;
      fig_p.bbox.lly = -values[3] * dvi2pts;
      fig_p.bbox.urx =  values[4] * dvi2pts;
      fig_p.bbox.ury = -values[5] * dvi2pts;
      fig_p.flags   |= INFO_HAS_USER_BBOX;

      sprintf(resname, "__tf%d__", count);
      xobj_id = pdf_doc_begin_grabbing(resname, fig_p.bbox.llx, fig_p.bbox.ury, &fig_p.bbox);
      
      in_tfig = 1;
      count++;
    }
    break;
  case ETEXFIG:
    if (!in_tfig)
      ERROR("endTexFig without valid startTexFig!.");

    pdf_doc_end_grabbing(NULL);
    pdf_dev_put_image(xobj_id, &fig_p, x_user, y_user, NULL); /* FIXME */
    in_tfig = 0;
    break;
  default:
    error = 1;
  }

  return error;
}

/*
 * buggy...
 */

/*
 * CTM(Current Transformation Matrix) means the transformation of User Space
 * to Device Space coordinates. Because DVIPDFMx does not know the resolution
 * of Device Space, we assume that the resolution is 1/1000.
 */
#define DEVICE_RESOLUTION 1000
static int
ps_dev_CTM (pdf_tmatrix *M)
{
  pdf_dev_currentmatrix(M);
  M->a *= DEVICE_RESOLUTION; M->b *= DEVICE_RESOLUTION;
  M->c *= DEVICE_RESOLUTION; M->d *= DEVICE_RESOLUTION;
  M->e *= DEVICE_RESOLUTION; M->f *= DEVICE_RESOLUTION;

  return 0;
}

/*
 * Again, the only piece that needs x_user and y_user is
 * that piece dealing with texfig.
 */
static int
do_operator (const char *token, double x_user, double y_user)
{
  int         error  = 0;
  int         opcode = 0;
  double      values[12];
  pdf_obj    *tmp = NULL;
  pdf_tmatrix matrix;
  pdf_coord   cp;
  pdf_color   color;

#define PUSH(o) { \
  if (top_stack < PS_STACK_SIZE) { \
    stack[top_stack++] = (o); \
  } else { \
    WARN("PS stack overflow including MetaPost file or inline PS code"); \
    error=1; \
    break;\
  } \
}

  opcode = get_opcode(token);

  switch (opcode) {
    
    /*
     * Arithmetic operators
     */
  case ADD:
    error = pop_get_numbers(values, 2);
    if (!error)
      PUSH(pdf_new_number(values[0] + values[1]));
    break;
  case MUL:
    error = pop_get_numbers(values, 2);
    if (!error)
      PUSH(pdf_new_number(values[0]*values[1]));
    break;
  case NEG:
    error = pop_get_numbers(values, 1);
    if (!error)
      PUSH(pdf_new_number(-values[0]));
    break;
  case SUB:
    error = pop_get_numbers(values, 2);
    if (!error)
      PUSH(pdf_new_number(values[0] - values[1]));
    break;
  case DIV:
    error = pop_get_numbers(values, 2);
    if (!error)
      PUSH(pdf_new_number(values[0]/values[1]));
    break;
  case TRUNCATE: /* Round toward zero. */
    error = pop_get_numbers(values, 1);
    if (!error)
      PUSH(pdf_new_number(((values[0] > 0) ? floor(values[0]) : ceil(values[0]))));
    break;

    /* Stack operation */
  case CLEAR:
    error = do_clear(); 
    break;
  case POP:
    tmp = POP_STACK();
    if (tmp)
      pdf_release_obj(tmp);
    break;
  case EXCH:
    error = do_exch();  
    break;

    /* Path construction */
  case MOVETO:
    error = pop_get_numbers(values, 2);
    if (!error)
      error = pdf_dev_moveto(values[0], values[1]);
    break;
  case RMOVETO:
    error = pop_get_numbers(values, 2);
    if (!error)
      error = pdf_dev_rmoveto(values[0], values[1]);
    break;
  case LINETO:
    error = pop_get_numbers(values, 2);
    if (!error)
      error = pdf_dev_lineto(values[0], values[1]);
    break;
  case RLINETO:
    error = pop_get_numbers(values, 2);
    if (!error)
      error = pdf_dev_rlineto(values[0], values[1]);
    break;
  case CURVETO:
    error = pop_get_numbers(values, 6);
    if (!error)
      error = pdf_dev_curveto(values[0], values[1],
			      values[2], values[3],
			      values[4], values[5]);
    break;
  case RCURVETO:
    error = pop_get_numbers(values, 6);
    if (!error)
      error = pdf_dev_rcurveto(values[0], values[1],
			       values[2], values[3],
			       values[4], values[5]);
    break;
  case CLOSEPATH:
    error = pdf_dev_closepath();
    break;
  case ARC:
    error = pop_get_numbers(values, 5);
    if (!error)
      error = pdf_dev_arc(values[0], values[1],
			  values[2], /* rad */
			  values[3], values[4]);
    break;
  case ARCN:
    error = pop_get_numbers(values, 5);
    if (!error)
      error = pdf_dev_arcn(values[0], values[1],
			   values[2], /* rad */
			   values[3], values[4]);
    break;
    
  case NEWPATH:
    pdf_dev_newpath();
    break;
  case STROKE:
    /* fill rule not supported yet */
    pdf_dev_flushpath('S', PDF_FILL_RULE_NONZERO);
    break;
  case FILL:
    pdf_dev_flushpath('f', PDF_FILL_RULE_NONZERO);
    break;

  case CLIP:
    error = pdf_dev_clip();
    break;
  case EOCLIP:
    error = pdf_dev_eoclip();
    break;

    /* Graphics state operators: */
  case GSAVE:
    error = pdf_dev_gsave();
    save_font();
    break;
  case GRESTORE:
    error = pdf_dev_grestore();
    restore_font();
    break;

  case CONCAT:
    tmp   = POP_STACK();
    error = cvr_array(tmp, values, 6); /* This does pdf_release_obj() */
    tmp   = NULL;
    if (error)
      WARN("Missing array before \"concat\".");
    else {
      pdf_setmatrix(&matrix,
		    values[0], values[1],
		    values[2], values[3],
		    values[4], values[5]);
      error = pdf_dev_concat(&matrix);
    }
    break;
  case SCALE:
    error = pop_get_numbers(values, 2);
    if (!error) {
      switch (mp_cmode) {
#ifndef WITHOUT_ASCII_PTEX
      case MP_CMODE_PTEXVERT:
	pdf_setmatrix(&matrix,
		      values[1], 0.0,
		      0.0      , values[0],
		      0.0      , 0.0);
	break;
#endif /* !WITHOUT_ASCII_PTEX */
      default:
	pdf_setmatrix(&matrix,
		      values[0], 0.0,
		      0.0      , values[1],
		      0.0      , 0.0);
	break;
      }

      error = pdf_dev_concat(&matrix);
    }
    break;
    /* Positive angle means clock-wise direction in graphicx-dvips??? */
  case ROTATE:
    error = pop_get_numbers(values, 1);
    if (!error) {
      values[0] = values[0] * M_PI / 180;

      switch (mp_cmode) {
      case MP_CMODE_DVIPSK:
      case MP_CMODE_MPOST: /* Really? */
#ifndef WITHOUT_ASCII_PTEX
      case MP_CMODE_PTEXVERT:
#endif /* !WITHOUT_ASCII_PTEX */
	pdf_setmatrix(&matrix,
		      cos(values[0]), -sin(values[0]),
		      sin(values[0]),  cos(values[0]),
		      0.0,             0.0);
	break;
      default:
	pdf_setmatrix(&matrix,
		      cos(values[0]) , sin(values[0]),
		      -sin(values[0]), cos(values[0]),
		      0.0,             0.0);
	break;
      }
      error = pdf_dev_concat(&matrix);
    }
    break;
  case TRANSLATE:
    error = pop_get_numbers(values, 2);
    if (!error) {
      pdf_setmatrix(&matrix,
		    1.0,       0.0,
		    0.0,       1.0,
		    values[0], values[1]);
      error = pdf_dev_concat(&matrix);
    }
    break;

  case SETDASH:
    error = pop_get_numbers(values, 1);
    if (!error) {
      pdf_obj *pattern, *dash;
      int      i, num_dashes;
      double   dash_values[PDF_DASH_SIZE_MAX];
      double   offset;

      offset  = values[0];
      pattern = POP_STACK();
      if (!PDF_OBJ_ARRAYTYPE(pattern)) {
	if (pattern)
	  pdf_release_obj(pattern);
	error = 1;
	break;
      }
      num_dashes = pdf_array_length(pattern);
      if (num_dashes > PDF_DASH_SIZE_MAX) {
	WARN("Too many dashes...");
	pdf_release_obj(pattern);
	error = 1;
	break;
      }
      for (i = 0;
	   i < num_dashes && !error ; i++) {
	dash = pdf_get_array(pattern, i);
	if (!PDF_OBJ_NUMBERTYPE(dash))
	  error = 1;
	else {
	  dash_values[i] = pdf_number_value(dash);
	}
      }
      pdf_release_obj(pattern);
      if (!error) {
	error = pdf_dev_setdash(num_dashes, dash_values, offset);
      }
    }
    break;
  case SETLINECAP:
    error = pop_get_numbers(values, 1);
    if (!error)
      error = pdf_dev_setlinecap((int)values[0]);
    break;
  case SETLINEJOIN:
    error = pop_get_numbers(values, 1);
    if (!error)
      error = pdf_dev_setlinejoin((int)values[0]);
    break;
  case SETLINEWIDTH:
    error = pop_get_numbers(values, 1);
    if (!error)
      error = pdf_dev_setlinewidth(values[0]);
    break;
  case SETMITERLIMIT:
    error = pop_get_numbers(values, 1);
    if (!error)
      error = pdf_dev_setmiterlimit(values[0]);
    break;

  case SETCMYKCOLOR:
    error = pop_get_numbers(values, 4);
    /* Not handled properly */
    if (!error) {
      pdf_color_cmykcolor(&color,
			  values[0], values[1],
			  values[2], values[3]);
      pdf_dev_set_strokingcolor(&color);
      pdf_dev_set_nonstrokingcolor(&color);
    }
    break;
  case SETGRAY:
    /* Not handled properly */
    error = pop_get_numbers(values, 1);
    if (!error) {
      pdf_color_graycolor(&color, values[0]);
      pdf_dev_set_strokingcolor(&color);
      pdf_dev_set_nonstrokingcolor(&color);
    }
    break;
  case SETRGBCOLOR:
    error = pop_get_numbers(values, 3);
    if (!error) {
      pdf_color_rgbcolor(&color,
			 values[0], values[1], values[2]);
      pdf_dev_set_strokingcolor(&color);
      pdf_dev_set_nonstrokingcolor(&color);
    }
    break;

  case SHOWPAGE: /* Let's ignore this for now */
    break;

  case CURRENTPOINT:
    error = pdf_dev_currentpoint(&cp);
    if (!error) {
      PUSH(pdf_new_number(cp.x));
      PUSH(pdf_new_number(cp.y));
    }
    break;

  case DTRANSFORM:
    {
      int  has_matrix = 0;

      tmp = POP_STACK();
      if (PDF_OBJ_ARRAYTYPE(tmp)) {
	error = cvr_array(tmp, values, 6); /* This does pdf_release_obj() */
	tmp   = NULL;
	if (error)
	  break;
	pdf_setmatrix(&matrix,
		      values[0], values[1],
		      values[2], values[3],
		      values[4], values[5]);
	tmp = POP_STACK();
	has_matrix = 1;
      }
      
      if (!PDF_OBJ_NUMBERTYPE(tmp)) {
	error = 1;
	break;
      }
      cp.y = pdf_number_value(tmp);
      pdf_release_obj(tmp);

      tmp = POP_STACK();
      if (!PDF_OBJ_NUMBERTYPE(tmp)) {
	error = 1;
	break;
      }
      cp.x = pdf_number_value(tmp);
      pdf_release_obj(tmp);

      if (!has_matrix) {
	ps_dev_CTM(&matrix); /* Here, we need real PostScript CTM */
      }
      pdf_dev_dtransform(&cp, &matrix);
      PUSH(pdf_new_number(cp.x));
      PUSH(pdf_new_number(cp.y));
    }
    break;

  case IDTRANSFORM:
    {
      int  has_matrix = 0;

      tmp = POP_STACK();
      if (PDF_OBJ_ARRAYTYPE(tmp)) {
	error = cvr_array(tmp, values, 6); /* This does pdf_release_obj() */
	tmp   = NULL;
	if (error)
	  break;
	pdf_setmatrix(&matrix,
		      values[0], values[1],
		      values[2], values[3],
		      values[4], values[5]);
	tmp = POP_STACK();
	has_matrix = 1;
      }
      
      if (!PDF_OBJ_NUMBERTYPE(tmp)) {
	error = 1;
	break;
      }
      cp.y = pdf_number_value(tmp);
      pdf_release_obj(tmp);

      tmp = POP_STACK();
      if (!PDF_OBJ_NUMBERTYPE(tmp)) {
	error = 1;
	break;
      }
      cp.x = pdf_number_value(tmp);
      pdf_release_obj(tmp);

      if (!has_matrix) {
	ps_dev_CTM(&matrix); /* Here, we need real PostScript CTM */
      }
      pdf_dev_idtransform(&cp, &matrix);
      PUSH(pdf_new_number(cp.x));
      PUSH(pdf_new_number(cp.y));
      break;
    }

  case FINDFONT:
    error = do_findfont();
    break;
  case SCALEFONT:
    error = do_scalefont();
    break;
  case SETFONT:
    error = do_setfont();
    break;
  case CURRENTFONT:
    error = do_currentfont();
    break;

  case SHOW:
    error = do_show();
    break;

  case STRINGWIDTH:
    error = 1;
    break;

    /* Extensions */
  case FSHOW:
    error = do_mpost_bind_def("exch findfont exch scalefont setfont show", x_user, y_user);
    break;
  case STEXFIG:
  case ETEXFIG:
    error = do_texfig_operator(opcode, x_user, y_user);
    break;
  case HLW:
    error = do_mpost_bind_def("0 dtransform exch truncate exch idtransform pop setlinewidth", x_user, y_user);
    break;
  case VLW:
    error = do_mpost_bind_def("0 exch dtransform truncate idtransform setlinewidth pop", x_user, y_user);
    break;
  case RD:
    error = do_mpost_bind_def("[] 0 setdash", x_user, y_user);
    break;
  case B:
    error = do_mpost_bind_def("gsave fill grestore", x_user, y_user);
    break;

  case DEF:
    tmp = POP_STACK();
    tmp = POP_STACK();
    /* do nothing; not implemented yet */
    break;

  default:
    if (is_fontname(token)) {
      PUSH(pdf_new_name(token));
    } else {
      WARN("Unknown token \"%s\"", token);
      error = 1;
    }
    break;
  }

  return error;
}

/*
 * The only sections that need to know x_user and y _user are those
 * dealing with texfig.
 */
static int
mp_parse_body (const char **start, const char *end, double x_user, double y_user)
{
  char    *token;
  pdf_obj *obj;
  int      error = 0;

  skip_white(start, end);
  while (*start < end && !error) {
    if (isdigit((unsigned char)**start) ||
	(*start < end - 1 &&
	 (**start == '+' || **start == '-' || **start == '.' ))) {
      double value;
      char  *next;

      value = strtod(*start, &next);
      if (next < end && !strchr("<([{/%", *next) && !isspace((unsigned char)*next)) {
	WARN("Unkown PostScript operator.");
	dump(*start, next);
	error = 1;
      } else {
	PUSH(pdf_new_number(value));
	*start = next;
      }
      /*
       * PDF parser can't handle PS operator inside arrays.
       * This shouldn't use parse_pdf_array().
       */
    } else if (**start == '[' &&
	       (obj = parse_pdf_array(start, end, NULL))) {
      PUSH(obj);
      /* This cannot handle ASCII85 string. */
    } else if (*start < end - 1 &&
	       (**start == '<' && *(*start+1) == '<') &&
	       (obj = parse_pdf_dict(start, end, NULL))) {
      PUSH(obj);
    } else if ((**start == '(' || **start == '<') &&
	       (obj = parse_pdf_string (start, end))) {
      PUSH(obj);
    } else if (**start == '/' &&
	       (obj = parse_pdf_name(start, end))) {
      PUSH(obj);
    } else {
      token = parse_ident(start, end);
      if (!token)
	error = 1;
      else {
	error = do_operator(token, x_user, y_user);
	RELEASE(token);
      }
    }
    skip_white(start, end);
  }

  return error;
}

void
mps_eop_cleanup (void)
{
  clear_fonts();
  do_clear();
}

int
mps_stack_depth (void)
{
  return top_stack;
}

int
mps_exec_inline (const char **p, const char *endptr,
		 double x_user, double y_user)
{
  int  error;
  int  dirmode, autorotate;

  /* Compatibility for dvipsk. */
  dirmode = pdf_dev_get_dirmode();
  if (dirmode) {
    mp_cmode = MP_CMODE_PTEXVERT;
  } else {
    mp_cmode = MP_CMODE_DVIPSK;
  }

  autorotate = pdf_dev_get_param(PDF_DEV_PARAM_AUTOROTATE);
  pdf_dev_set_param(PDF_DEV_PARAM_AUTOROTATE, 0);
  //pdf_color_push(); /* ... */

  /* Comment in dvipdfm:
   * Remember that x_user and y_user are off by 0.02 %
   */
  pdf_dev_moveto(x_user, y_user);
  error = mp_parse_body(p, endptr, x_user, y_user);

  //pdf_color_pop(); /* ... */
  pdf_dev_set_param(PDF_DEV_PARAM_AUTOROTATE, autorotate);
  pdf_dev_set_dirmode(dirmode);

  return error;
}

/* mp inclusion is a bit of a hack.  The routine
 * starts a form at the lower left corner of
 * the page and then calls begin_form_xobj telling
 * it to record the image drawn there and bundle it
 * up in an xojbect.  This allows us to use the coordinates
 * in the MP file directly.  This appears to be the
 * easiest way to be able to use the pdf_dev_set_string()
 * command (with its scaled and extended fonts) without
 * getting all confused about the coordinate system.
 * After the xobject is created, the whole thing can
 * be scaled any way the user wants
 */
 
/* Should implement save and restore. */
int
mps_include_page (const char *ident, FILE *fp)
{
  int        form_id;
  xform_info info;
  int        st_depth, gs_depth;
  char      *buffer;
  const char *p, *endptr;
  int        length, nb_read;
  int        dirmode, autorotate, error;

  rewind(fp);

  length = file_size(fp);
  if (length < 1) {
    WARN("Can't read any byte in the MPS file.");
    return -1;
  }

  buffer = NEW(length + 1, char);
  buffer[length] = '\0';
  p      = buffer;
  endptr = p + length;

  while (length > 0) {
    nb_read = fread(buffer, sizeof(char), length, fp);
    if (nb_read < 0) {
      RELEASE(buffer);
      WARN("Reading file failed...");
      return -1;
    }
    length -= nb_read;
  }

  error = mps_scan_bbox(&p, endptr, &(info.bbox));
  if (error) {
    WARN("Error occured while scanning MetaPost file headers: Could not find BoundingBox.");
    RELEASE(buffer);
    return -1;
  }
  skip_prolog(&p, endptr);

  dirmode    = pdf_dev_get_dirmode();
  autorotate = pdf_dev_get_param(PDF_DEV_PARAM_AUTOROTATE);
  pdf_dev_set_param(PDF_DEV_PARAM_AUTOROTATE, 0);
  //pdf_color_push();

  form_id  = pdf_doc_begin_grabbing(ident, Xorigin, Yorigin, &(info.bbox));

  mp_cmode = MP_CMODE_MPOST;
  gs_depth = pdf_dev_current_depth();
  st_depth = mps_stack_depth();
  /* At this point the gstate must be initialized, since it starts a new
   * XObject. Note that it increase gs_depth by 1. */
  pdf_dev_push_gstate();

  error = mp_parse_body(&p, endptr, 0.0, 0.0);
  RELEASE(buffer);

  if (error) {
    WARN("Errors occured while interpreting MPS file.");
    /* WARN("Leaving garbage in output PDF file."); */
    form_id = -1;
  }

  /* It's time to pop the new gstate above. */
  pdf_dev_pop_gstate();
  mps_stack_clear_to (st_depth);
  pdf_dev_grestore_to(gs_depth);

  pdf_doc_end_grabbing(NULL);

  //pdf_color_pop();
  pdf_dev_set_param(PDF_DEV_PARAM_AUTOROTATE, autorotate);
  pdf_dev_set_dirmode(dirmode);

  return form_id;
}

int
mps_do_page (FILE *image_file)
{
  int       error = 0;
  pdf_rect  bbox;
  char     *buffer;
  const char *start, *end;
  int       size;
  int       dir_mode;

  rewind(image_file);
  if ((size = file_size(image_file)) == 0) {
    WARN("Can't read any byte in the MPS file.");
    return -1;
  }

  buffer = NEW(size+1, char);
  fread(buffer, sizeof(char), size, image_file);
  buffer[size] = 0;
  start = buffer;
  end   = buffer + size;

  error = mps_scan_bbox(&start, end, &bbox);
  if (error) {
    WARN("Error occured while scanning MetaPost file headers: Could not find BoundingBox.");
    RELEASE(buffer);
    return -1;
  }

  mp_cmode = MP_CMODE_MPOST;

  pdf_doc_begin_page  (1.0, -Xorigin, -Yorigin); /* scale, xorig, yorig */
  pdf_doc_set_mediabox(pdf_doc_current_page_number(), &bbox);

  dir_mode = pdf_dev_get_dirmode();
  pdf_dev_set_autorotate(0);

  skip_prolog(&start, end);

  error = mp_parse_body(&start, end, 0.0, 0.0);

  if (error) {
    WARN("Errors occured while interpreting MetaPost file.");
  }

  pdf_dev_set_autorotate(1);
  pdf_dev_set_dirmode(dir_mode);

  pdf_doc_end_page();

  RELEASE(buffer);

  /*
   * The reason why we don't return XObject itself is
   * PDF inclusion may not be made so.
   */
  return (error ? -1 : 0);
}
