/* Copyright (C) 1990, 1995, 1996, 1997 Aladdin Enterprises.  All rights reserved.
  
  This file is part of Aladdin Ghostscript.
  
  Aladdin Ghostscript is distributed with NO WARRANTY OF ANY KIND.  No author
  or distributor accepts any responsibility for the consequences of using it,
  or for whether it serves any particular purpose or works at all, unless he
  or she says so in writing.  Refer to the Aladdin Ghostscript Free Public
  License (the "License") for full details.
  
  Every copy of Aladdin Ghostscript must include a copy of the License,
  normally in a plain ASCII text file named PUBLIC.  The License grants you
  the right to copy, modify and redistribute Aladdin Ghostscript, but only
  under certain conditions described in the License.  Among other things, the
  License requires that the copyright notice and this notice be preserved on
  all copies.
*/

/* gstype1.c */
/* Adobe Type 1 charstring interpreter */
#include "math_.h"
#include "memory_.h"
#include "gx.h"
#include "gserrors.h"
#include "gsstruct.h"
#include "gxarith.h"
#include "gxfixed.h"
#include "gxmatrix.h"
#include "gxcoord.h"
#include "gxistate.h"
#include "gzpath.h"
#include "gxchar.h"
#include "gxfont.h"
#include "gxfont1.h"
#include "gxtype1.h"

/*
 * Define whether to always do Flex segments as curves.
 * This is only an issue because some old Adobe DPS fonts
 * seem to violate the Flex specification in a way that requires this.
 * We changed this from 1 to 0 in release 5.02: if it causes any
 * problems, we'll implement a more sophisticated test.
 */
#define ALWAYS_DO_FLEX_AS_CURVE 0

/* ------ Main interpreter ------ */

/* Define a pointer to the charstring interpreter stack. */
typedef fixed _ss *cs_ptr;

/*
 * Continue interpreting a Type 1 charstring.  If str != 0, it is taken as
 * the byte string to interpret.  Return 0 on successful completion, <0 on
 * error, or >0 when client intervention is required (or allowed).  The int*
 * argument is where the othersubr # is stored for callothersubr.
 */
private int
gs_type1_charstring_interpret(gs_type1_state *pcis,
  const gs_const_string *str, int *pindex)
{	gs_font_type1 *pfont = pcis->pfont;
	gs_type1_data *pdata = &pfont->data;
	bool encrypted = pdata->lenIV >= 0;
	gs_op1_state s;
	fixed cstack[ostack_size];
#define cs0 cstack[0]
#define ics0 fixed2int_var(cs0)
#define cs1 cstack[1]
#define ics1 fixed2int_var(cs1)
#define cs2 cstack[2]
#define ics2 fixed2int_var(cs2)
#define cs3 cstack[3]
#define ics3 fixed2int_var(cs3)
#define cs4 cstack[4]
#define ics4 fixed2int_var(cs4)
#define cs5 cstack[5]
#define ics5 fixed2int_var(cs5)
	cs_ptr csp;
#define clear csp = cstack - 1
	ip_state *ipsp = &pcis->ipstack[pcis->ips_count - 1];
	register const byte *cip;
	register crypt_state state;
	register int c;
	int code = 0;
	fixed ftx = pcis->origin.x, fty = pcis->origin.y;

	switch ( pcis->init_done )
	  {
	  case -1:
	    break;
	  case 0: 
	    gs_type1_finish_init(pcis, &s);  /* sets sfc, ptx, pty, origin */
	    ftx = pcis->origin.x, fty = pcis->origin.y;
	    break;
	  default /*case 1*/:
	    ptx = pcis->position.x;
	    pty = pcis->position.y;
	    sfc = pcis->fc;
	  }
	sppath = pcis->path;
	s.pcis = pcis;
	init_cstack(cstack, csp, pcis);

	if ( str == 0 )
	  goto cont;
	ipsp->char_string = *str;
	cip = str->data;
call:	state = crypt_charstring_seed;
	if ( encrypted )
	   {	int skip = pdata->lenIV;
		/* Skip initial random bytes */
		for ( ; skip > 0; ++cip, --skip )
		  decrypt_skip_next(*cip, state);
	   }
	goto top;
cont:	cip = ipsp->ip;
	state = ipsp->dstate;
top:	for ( ; ; )
	 { uint c0 = *cip++;

	   charstring_next(c0, state, c, encrypted);
	   if ( c >= c_num1 )
	     {
	       /* This is a number, decode it and push it on the stack. */

	       if ( c < c_pos2_0 )
		 {				/* 1-byte number */
		   decode_push_num1(csp, c);
		 }
	       else if ( c < cx_num4 )
		 {				/* 2-byte number */
		   decode_push_num2(csp, c, cip, state, encrypted);
		 }
	       else if ( c == cx_num4 )
		 {				/* 4-byte number */
		   long lw;

		   decode_num4(lw, cip, state, encrypted);
		   *++csp = int2fixed(lw);
		   if ( lw != fixed2long(*csp) )
		     return_error(gs_error_rangecheck);
		 }
	       else			/* not possible */
		 return_error(gs_error_invalidfont);
pushed:	       if_debug3('1', "[1]%d: (%d) %f\n",
			 (int)(csp - cstack), c, fixed2float(*csp));
	       continue;
	     }

#ifdef DEBUG
	   if ( gs_debug['1'] )
	     { static const char *c1names[] =
	        { char1_command_names };
	       if ( c1names[c] == 0 )
		 dprintf2("[1]0x%lx: %02x??\n", (ulong)(cip - 1), c);
	       else
		 dprintf3("[1]0x%lx: %02x %s\n", (ulong)(cip - 1), c,
			  c1names[c]);
	     }
#endif
	   switch ( (char_command)c )
	     {
#define cnext clear; goto top
#define inext goto top

		/* Commands with identical functions in Type 1 and Type 2, */
		/* except for 'escape'. */

	case c_undef0:
	case c_undef2:
	case c_undef17:
		return_error(gs_error_invalidfont);
	case c_callsubr:
		c = fixed2int_var(*csp) + pdata->subroutineNumberBias;
		code = (*pdata->subr_proc)
		  (pfont, c, false, &ipsp[1].char_string);
		if ( code < 0 )
		  return_error(code);
		--csp;
		ipsp->ip = cip, ipsp->dstate = state;
		++ipsp;
		cip = ipsp->char_string.data;
		goto call;
	case c_return:
		--ipsp;
		goto cont;
	case c_undoc15:
		/* See gstype1.h for information on this opcode. */
		cnext;
		
		/* Commands with similar but not identical functions */
		/* in Type 1 and Type 2 charstrings. */

	case cx_hstem:
		apply_path_hints(pcis, false);
		type1_hstem(pcis, cs0, cs1);
		cnext;
	case cx_vstem:
		apply_path_hints(pcis, false);
		type1_vstem(pcis, cs0, cs1);
		cnext;
	case cx_vmoveto:
		cs1 = cs0;
		cs0 = 0;
		accum_y(cs1);
move:		/* cs0 = dx, cs1 = dy for hint checking. */
		if ( (pcis->hint_next != 0 || path_is_drawing(sppath)) &&
		     pcis->flex_count == flex_max
		   )
		  apply_path_hints(pcis, true);
		code = gx_path_add_point(sppath, ptx, pty);
		goto cc;
	case cx_rlineto:
		accum_xy(cs0, cs1);
line:		/* cs0 = dx, cs1 = dy for hint checking. */
		code = gx_path_add_line(sppath, ptx, pty);
cc:		if ( code < 0 )
		  return code;
pp:		if_debug2('1', "[1]pt=(%g,%g)\n",
			  fixed2float(ptx), fixed2float(pty));
		cnext;
	case cx_hlineto:
		accum_x(cs0);
		cs1 = 0;
		goto line;
	case cx_vlineto:
		cs1 = cs0;
		cs0 = 0;
		accum_y(cs1);
		goto line;
	case cx_rrcurveto:
		code = gs_op1_rrcurveto(&s, cs0, cs1, cs2, cs3, cs4, cs5);
		goto cc;
	case cx_endchar:
		if ( pcis->seac_base >= 0 )
		   {	/* We just finished the accent of a seac. */
			/* Do the base character. */
			gs_const_string bstr;
			int bchar = pcis->seac_base;
			pcis->seac_base = -1;
			/* Restore the coordinate system origin */
			pcis->asb_diff = pcis->adxy.x = pcis->adxy.y = 0;
			sppath->position.x = pcis->position.x = ftx;
			sppath->position.y = pcis->position.y = fty;
			pcis->os_count = 0;	/* clear */
			/* Clear the ipstack, in case the accent ended */
			/* inside a subroutine. */
			pcis->ips_count = 1;
			/* Remove any accent hints. */
			reset_stem_hints(pcis);
			/* Ask the caller to provide a new string. */
			code = (*pdata->seac_proc)(pfont, bchar, &bstr);
			if ( code != 0 )
			  {	*pindex = bchar;
				return code;
			  }
			/* Continue with the supplied string. */
			clear;
			ptx = ftx, pty = fty;
			ipsp = &pcis->ipstack[0];
			ipsp->char_string = bstr;
			cip = bstr.data;
			goto call;
		  }
		/* This is a real endchar.  Handle it below. */
		return gs_type1_endchar(pcis);
	case cx_rmoveto:
		accum_xy(cs0, cs1);
		goto move;
	case cx_hmoveto:
		accum_x(cs0);
		cs1 = 0;
		goto move;
	case cx_vhcurveto:
	  {	gs_fixed_point pt1, pt2;
		fixed ax0 = sppath->position.x - ptx;
		fixed ay0 = sppath->position.y - pty;
		accum_y(cs0);
		pt1.x = ptx + ax0, pt1.y = pty + ay0;
		accum_xy(cs1, cs2);
		pt2.x = ptx, pt2.y = pty;
		accum_x(cs3);
		code = gx_path_add_curve(sppath, pt1.x, pt1.y, pt2.x, pt2.y, ptx, pty);
	  }	goto cc;
	case cx_hvcurveto:
	  {	gs_fixed_point pt1, pt2;
		fixed ax0 = sppath->position.x - ptx;
		fixed ay0 = sppath->position.y - pty;
		accum_x(cs0);
		pt1.x = ptx + ax0, pt1.y = pty + ay0;
		accum_xy(cs1, cs2);
		pt2.x = ptx, pt2.y = pty;
		accum_y(cs3);
		code =  gx_path_add_curve(sppath, pt1.x, pt1.y, pt2.x, pt2.y, ptx, pty);
	  }	goto cc;

		/* Commands only recognized in Type 1 charstrings, */
		/* plus 'escape'. */

	case c1_closepath:
		code = gs_op1_closepath(&s);
		apply_path_hints(pcis, true);
		goto cc;
	case c1_hsbw:
		gs_type1_sbw(pcis, cs0, fixed_0, cs1, fixed_0);
rsbw:		/* Give the caller the opportunity to intervene. */
		pcis->os_count = 0;	/* clear */
		ipsp->ip = cip, ipsp->dstate = state;
		pcis->ips_count = ipsp - &pcis->ipstack[0] + 1;
		/* If we aren't in a seac, do nothing else now; */
		/* finish_init will take care of the rest. */
		if ( pcis->init_done < 0 )
		  {	/* Finish init when we return. */
			pcis->init_done = 0;
		  }
		else
		  {	/* Do accumulate the side bearing now. */
			accum_xy(pcis->lsb.x, pcis->lsb.y);
			pcis->position.x = ptx;
			pcis->position.y = pty;
		  }
		return type1_result_sbw;
	case cx_escape:
		charstring_next(*cip, state, c, encrypted); ++cip;
#ifdef DEBUG
		if ( gs_debug['1'] && c < char1_extended_command_count )
		  { static const char *ce1names[] =
		     { char1_extended_command_names };
		    if ( ce1names[c] == 0 )
		      dprintf2("[1]0x%lx: %02x??\n", (ulong)(cip - 1), c);
		    else
		      dprintf3("[1]0x%lx: %02x %s\n", (ulong)(cip - 1), c,
			       ce1names[c]);
		  }
#endif
		switch ( (char1_extended_command)c )
		   {
		case ce1_dotsection:
			pcis->dotsection_flag ^=
			  (dotsection_in ^ dotsection_out);
			cnext;
		case ce1_vstem3:
			apply_path_hints(pcis, false);
			if ( !pcis->vstem3_set && pcis->fh.use_x_hints )
			{	center_vstem(pcis, pcis->lsb.x + cs2, cs3);
				/* Adjust the current point */
				/* (center_vstem handles everything else). */
				ptx += pcis->vs_offset.x;
				pty += pcis->vs_offset.y;
				pcis->vstem3_set = true;
			}
			type1_vstem(pcis, cs0, cs1);
			type1_vstem(pcis, cs2, cs3);
			type1_vstem(pcis, cs4, cs5);
			cnext;
		case ce1_hstem3:
			apply_path_hints(pcis, false);
			type1_hstem(pcis, cs0, cs1);
			type1_hstem(pcis, cs2, cs3);
			type1_hstem(pcis, cs4, cs5);
			cnext;
		case ce1_seac:
		  {	gs_const_string acstr;
			/* Do the accent now.  When it finishes */
			/* (detected in endchar), do the base character. */
			pcis->seac_base = ics3;
			/* Adjust the origin of the coordinate system */
			/* for the accent (endchar puts it back). */
			ptx = ftx, pty = fty;
			pcis->asb_diff = cs0 - pcis->lsb.x;
			pcis->adxy.x = cs1;
			pcis->adxy.y = cs2;
			accum_xy(cs1, cs2);
			sppath->position.x = pcis->position.x = ptx;
			sppath->position.y = pcis->position.y = pty;
			pcis->os_count = 0;	/* clear */
			/* Ask the caller to provide a new string. */
			code = (*pdata->seac_proc)(pfont, ics4, &acstr);
			if ( code != 0 )
			  {	*pindex = ics4;
				return code;
			  }
			/* Continue with the supplied string. */
			clear;
			ipsp->char_string = acstr;
			cip = acstr.data;
			goto call;
		  }
		case ce1_sbw:
			gs_type1_sbw(pcis, cs0, cs1, cs2, cs3);
			goto rsbw;
		case ce1_div:
			csp[-1] = float2fixed((float)csp[-1] / (float)*csp);
			--csp; goto pushed;
		case ce1_undoc15:
			/* See gstype1.h for information on this opcode. */
			cnext;
		case ce1_callothersubr:
		  {	int num_results;
#define fpts pcis->flex_points
			/* We must remember to pop both the othersubr # */
			/* and the argument count off the stack. */
			switch ( *pindex = fixed2int_var(*csp) )
			{
			case 0:
			{	/* We have to do something really sleazy */
				/* here, namely, make it look as though */
				/* the rmovetos never really happened, */
				/* because we don't want to interrupt */
				/* the current subpath. */
				gs_fixed_point ept;
#if defined(DEBUG) || !ALWAYS_DO_FLEX_AS_CURVE
				fixed fheight = csp[-4];
				gs_fixed_point hpt;
#endif

				if ( pcis->flex_count != 8 )
				  return_error(gs_error_invalidfont);
				/* Assume the next two opcodes */
				/* are `pop' `pop'.  Unfortunately, some */
				/* Monotype fonts put these in a Subr, */
				/* so we can't just look ahead in the */
				/* opcode stream. */
				pcis->ignore_pops = 2;
				csp[-4] = csp[-3] - pcis->asb_diff;
				csp[-3] = csp[-2];
				csp -= 3;
				gx_path_current_point(sppath, &ept);
				gx_path_add_point(sppath, fpts[0].x, fpts[0].y);
				sppath->state_flags =	/* <--- sleaze */
				  pcis->flex_path_state_flags;
#if defined(DEBUG) || !ALWAYS_DO_FLEX_AS_CURVE
				/* Decide whether to do the flex as a curve. */
				hpt.x = fpts[1].x - fpts[4].x;
				hpt.y = fpts[1].y - fpts[4].y;
				if_debug3('1',
					  "[1]flex: d=(%g,%g), height=%g\n",
					  fixed2float(hpt.x), fixed2float(hpt.y),
					  fixed2float(fheight) / 100);
#endif
#if !ALWAYS_DO_FLEX_AS_CURVE			/* See beginning of file. */
				if ( any_abs(hpt.x) + any_abs(hpt.y) <
				     fheight / 100
				   )
				{	/* Do the flex as a line. */
					code = gx_path_add_line(sppath,
								ept.x, ept.y);
				}
				else
#endif
				{	/* Do the flex as a curve. */
					code = gx_path_add_curve(sppath,
						fpts[2].x, fpts[2].y,
						fpts[3].x, fpts[3].y,
						fpts[4].x, fpts[4].y);
					if ( code < 0 )
					  return code;
					code = gx_path_add_curve(sppath,
						fpts[5].x, fpts[5].y,
						fpts[6].x, fpts[6].y,
						fpts[7].x, fpts[7].y);
				}
			}
				if ( code < 0 )
				  return code;
				pcis->flex_count = flex_max;	/* not inside flex */
				inext;
			case 1:
				gx_path_current_point(sppath, &fpts[0]);
				pcis->flex_path_state_flags = /* <--- more sleaze */
				  sppath->state_flags;
				pcis->flex_count = 1;
				csp -= 2;
				inext;
			case 2:
				if ( pcis->flex_count >= flex_max )
				  return_error(gs_error_invalidfont);
				gx_path_current_point(sppath,
						&fpts[pcis->flex_count++]);
				csp -= 2;
				inext;
			case 3:
				/* Assume the next opcode is a `pop'. */
				/* See above as to why we don't just */
				/* look ahead in the opcode stream. */
				pcis->ignore_pops = 1;
				replace_stem_hints(pcis);
				csp -= 2;
				inext;
			case 14:
				num_results = 1;
blend:			  {	int num_values = fixed2int_var(csp[-1]);
				int k1 = num_values / num_results - 1;
				int i, j;
				cs_ptr base, deltas;

				if ( num_values < num_results ||
				     num_values % num_results != 0
				   )
				  return_error(gs_error_invalidfont);
				base = csp - 1 - num_values;
				deltas = base + num_results - 1;
				for ( j = 0; j < num_results;
				     j++, base++, deltas += k1
				    )
				  for ( i = 1; i <= k1; i++ )
				    *base += deltas[i] *
				      pdata->WeightVector.values[i];
				csp = base - 1;
			  }
				pcis->ignore_pops = num_results;
				inext;
			case 15:
				num_results = 2;
				goto blend;
			case 16:
				num_results = 3;
				goto blend;
			case 17:
				num_results = 4;
				goto blend;
			case 18:
				num_results = 6;
				goto blend;
			}
		  }
#undef fpts
			/* Not a recognized othersubr, */
			/* let the client handle it. */
		{	int scount = csp - cstack;
			int n;
			/* Copy the arguments to the caller's stack. */
			if ( scount < 1 || csp[-1] < 0 ||
			     csp[-1] > int2fixed(scount - 1)
			   )
			  return_error(gs_error_invalidfont);
			n = fixed2int_var(csp[-1]);
			code = (*pdata->push_proc)(pfont, csp - (n + 1), n);
			if ( code < 0 )
			  return_error(code);
			scount -= n + 1;
			pcis->position.x = ptx;
			pcis->position.y = pty;
			apply_path_hints(pcis, false);
			/* Exit to caller */
			ipsp->ip = cip, ipsp->dstate = state;
			pcis->os_count = scount;
			pcis->ips_count = ipsp - &pcis->ipstack[0] + 1;
			if ( scount )
			  memcpy(pcis->ostack, cstack, scount * sizeof(fixed));
			return type1_result_callothersubr;
		}
		case ce1_pop:
			/* Check whether we're ignoring the pops after */
			/* a known othersubr. */
			if ( pcis->ignore_pops != 0 )
			  {	pcis->ignore_pops--;
				inext;
			  }
			++csp;
			code = (*pdata->pop_proc)(pfont, csp);
			if ( code < 0 )
			  return_error(code);
			goto pushed;
		case ce1_setcurrentpoint:
			ptx = ftx, pty = fty;
			cs0 += pcis->adxy.x;
			cs1 += pcis->adxy.y;
			accum_xy(cs0, cs1);
			goto pp;
		default:
			return_error(gs_error_invalidfont);
		   }
		/*NOTREACHED*/

		/* Fill up the dispatch up to 32. */

	case_c1_undefs:
	default:		/* pacify compiler */
		return_error(gs_error_invalidfont);
	     }
	 }
}

/* Register the interpreter. */
void
gs_gstype1_init(gs_memory_t *mem)
{	gs_charstring_interpreter[1] = gs_type1_charstring_interpret;
}
