/*******************************************************************
 *
 *  ttinterp.c                                              2.3
 *
 *  TrueType bytecode intepreter.
 *
 *  Copyright 1996 David Turner, Robert Wilhelm and Werner Lemberg
 *
 *  This file is part of the FreeType project, and may only be used
 *  modified and distributed under the terms of the FreeType project
 *  license, LICENSE.TXT.  By continuing to use, modify, or distribute
 *  this file you indicate that you have read the license and
 *  understand and accept it fully.
 *
 *
 *  TODO:
 *
 *  - Fix the non-square pixel case (or how to manage the CVT to
 *    detect horizontal and vertical scaled FUnits ?)
 *
 *
 *  Changes between 2.3 and 2.2:
 *
 *  - added support for rotation, stretching, instruction control
 *
 *  - added support for non-square pixels.  However, this doesn't
 *    work perfectly yet...
 *
 *  Changes between 2.2 and 2.1:
 *
 *  - a small bugfix in the Push opcodes
 *
 *  Changes between 2.1 and 2.0:
 *
 *  - created the TTExec component to take care of all execution
 *    context management.  The interpreter has now one single
 *    function.
 *
 *  - made some changes to support re-entrancy.  The re-entrant
 *    interpreter is smaller!
 *
 ******************************************************************/

#include "freetype.h"
#include "tttypes.h"
#include "tterror.h"
#include "ttcalc.h"
#include "ttmemory.h"
#include "ttinterp.h"

#ifdef DEBUG
#include "ttdebug.h"

#ifdef HAVE_CONIO_H
#include "conio.h"
#endif

#endif /* DEBUG */


/* There are two kinds of implementations there:              */
/*                                                            */
/* a. static implementation:                                  */
/*                                                            */
/*    The current execution context is a static variable,     */
/*    which fields are accessed directly by the interpreter   */
/*    during execution.  The context is named 'cur'.          */
/*                                                            */
/*    This version is non-reentrant, of course.               */
/*                                                            */
/*                                                            */
/* b. indirect implementation:                                */
/*                                                            */
/*    The current execution context is passed to _each_       */
/*    function as its first argument, and each field is       */
/*    thus accessed indirectly.                               */
/*                                                            */
/*    This version is, however, fully re-entrant.             */
/*                                                            */
/*                                                            */
/*  The idea is that an indirect implementation may be        */
/*  slower to execute on the low-end processors that are      */
/*  used in some systems (like 386s or even 486s).            */
/*                                                            */
/*  When the interpreter started, we had no idea of the       */
/*  time that glyph hinting (i.e. executing instructions)     */
/*  could take in the whole process of rendering a glyph,     */
/*  and a 10 to 30% performance penalty on low-end systems    */
/*  didn't seem much of a good idea.  This question led us    */
/*  to provide two distinct builds of the C version from      */
/*  a single source, with the use of macros (again).          */
/*                                                            */
/*  Now that the engine is working (and working really        */
/*  well!), it seems that the greatest time-consuming         */
/*  factors are: file i/o, glyph loading, rasterizing and     */
/*  _then_ glyph hinting!                                     */
/*                                                            */
/*  Tests performed with two versions of the 'fttimer'        */
/*  program seem to indicate that hinting takes less than 5%  */
/*  of the rendering process, which is dominated by glyph     */
/*  loading and scan-line conversion by an high order of      */
/*  magnitude.                                                */
/*                                                            */
/*  As a consequence, the indirect implementation is now the  */
/*  default, as its performance costs can be considered       */
/*  negligible in our context. Note, however, that we         */
/*  kept the same source with macros because:                 */
/*                                                            */
/*    - the code is kept very close in design to the          */
/*      Pascal one used for development.                      */
/*                                                            */
/*    - it's much more readable that way!                     */
/*                                                            */
/*    - it's still open to later experimentation and tuning   */


#ifndef TT_STATIC_INTERPRETER      /* indirect implementation */

#define CUR (*exc)                 /* see ttobjs.h */

#else                              /* static implementation */

#define CUR cur

  static TExecution_Context  cur;  /* static exec. context variable */

  /* apparently, we have a _lot_ of direct indexing when accessing  */
  /* the static 'cur', which makes the code bigger (due to all the  */
  /* four bytes addresses).                                         */

#endif


#define INS_ARG         EXEC_OPS PStorage args  /* see ttexec.h */
 
#define SKIP_Code()     SkipCode( EXEC_ARG )
 
#define GET_ShortIns()  GetShortIns( EXEC_ARG )
 
#define COMPUTE_Funcs() Compute_Funcs( EXEC_ARG )
 
#define NORMalize( x, y, v )  Normalize( EXEC_ARGS x, y, v )
 
#define SET_SuperRound( scale, flags ) \
                        SetSuperRound( EXEC_ARGS scale, flags )
 
#define INS_Goto_CodeRange( range, ip ) \
                        Ins_Goto_CodeRange( EXEC_ARGS range, ip )
 
#define CUR_Func_project( x, y )   CUR.func_project( EXEC_ARGS x, y )
#define CUR_Func_move( z, p, d )   CUR.func_move( EXEC_ARGS z, p, d )
#define CUR_Func_dualproj( x, y )  CUR.func_dualproj( EXEC_ARGS x, y )
#define CUR_Func_freeProj( x, y )  CUR.func_freeProj( EXEC_ARGS x, y )
#define CUR_Func_round( d, c )     CUR.func_round( EXEC_ARGS d, c )

#define CUR_Func_read_cvt( index )       \
          CUR.func_read_cvt( EXEC_ARGS index )

#define CUR_Func_write_cvt( index, val ) \
          CUR.func_write_cvt( EXEC_ARGS index, val )

#define CUR_Func_move_cvt( index, val )  \
          CUR.func_move_cvt( EXEC_ARGS index, val )

#define CURRENT_Ratio()  Current_Ratio( EXEC_ARG )
#define CURRENT_Ppem()   Current_Ppem( EXEC_ARG )

#define CALC_Length()  Calc_Length( EXEC_ARG )
 
#define INS_SxVTL( a, b, c, d ) Ins_SxVTL( EXEC_ARGS a, b, c, d )
 
#define COMPUTE_Point_Displacement( a, b, c, d ) \
           Compute_Point_Displacement( EXEC_ARGS a, b, c, d )
 
#define MOVE_Zp2_Point( a, b, c, t )  Move_Zp2_Point( EXEC_ARGS a, b, c, t )

#define CUR_Ppem()  Cur_PPEM( EXEC_ARG )
 
  /* Instruction dispatch function, as used by the interpreter */
  typedef void  (*TInstruction_Function)( INS_ARG );


#ifndef ABS
#define ABS(x)  ( (x) < 0 ? -(x) : (x) )
#endif



/*********************************************************************/
/*                                                                   */
/*  Before an opcode is executed, the interpreter verifies that      */
/*  there are enough arguments on the stack, with the help of        */
/*  the Pop_Push_Count table.                                        */
/*                                                                   */
/*  For each opcode, the first column gives the number of arguments  */
/*  that are popped from the stack; the second one gives the number  */
/*  of those that are pushed in result.                              */
/*                                                                   */
/*  Note that for opcodes with a varying number of parameters,       */
/*  either 0 or 1 arg is verified before execution, depending        */
/*  on the nature of the instruction:                                */
/*                                                                   */
/*   - if the number of arguments is given by the bytecode           */
/*     stream or the loop variable, 0 is chosen.                     */
/*                                                                   */
/*   - if the first argument is a count n that is followed           */
/*     by arguments a1..an, then 1 is chosen.                        */
/*                                                                   */
/*********************************************************************/

  static unsigned char Pop_Push_Count[512] =
  {
    /* opcodes are gathered in groups of 16 */
    /* please keep the spaces as they are   */

    /*  SVTCA  y  */  0, 0,
    /*  SVTCA  x  */  0, 0,
    /*  SPvTCA y  */  0, 0,
    /*  SPvTCA x  */  0, 0,
    /*  SFvTCA y  */  0, 0,
    /*  SFvTCA x  */  0, 0,
    /*  SPvTL //  */  2, 0,
    /*  SPvTL +   */  2, 0,
    /*  SFvTL //  */  2, 0,
    /*  SFvTL +   */  2, 0,
    /*  SPvFS     */  2, 0,
    /*  SFvFS     */  2, 0,
    /*  GPV       */  0, 2,
    /*  GFV       */  0, 2,
    /*  SFvTPv    */  0, 0,
    /*  ISECT     */  5, 0,

    /*  SRP0      */  1, 0,
    /*  SRP1      */  1, 0,
    /*  SRP2      */  1, 0,
    /*  SZP0      */  1, 0,
    /*  SZP1      */  1, 0,
    /*  SZP2      */  1, 0,
    /*  SZPS      */  1, 0,
    /*  SLOOP     */  1, 0,
    /*  RTG       */  0, 0,
    /*  RTHG      */  0, 0,
    /*  SMD       */  1, 0,
    /*  ELSE      */  0, 0,
    /*  JMPR      */  1, 0,
    /*  SCvTCi    */  1, 0,
    /*  SSwCi     */  1, 0,
    /*  SSW       */  1, 0,
  
    /*  DUP       */  1, 2,
    /*  POP       */  1, 0,
    /*  CLEAR     */  0, 0,
    /*  SWAP      */  2, 2,
    /*  DEPTH     */  0, 1,
    /*  CINDEX    */  1, 1,
    /*  MINDEX    */  1, 0,
    /*  AlignPTS  */  2, 0,
    /*  INS_$28   */  0, 0,
    /*  UTP       */  1, 0,
    /*  LOOPCALL  */  2, 0,
    /*  CALL      */  1, 0,
    /*  FDEF      */  1, 0,
    /*  ENDF      */  0, 0,
    /*  MDAP[0]   */  1, 0,
    /*  MDAP[1]   */  1, 0,
  
    /*  IUP[0]    */  0, 0,
    /*  IUP[1]    */  0, 0,
    /*  SHP[0]    */  0, 0,
    /*  SHP[1]    */  0, 0,
    /*  SHC[0]    */  1, 0,
    /*  SHC[1]    */  1, 0,
    /*  SHZ[0]    */  1, 0,
    /*  SHZ[1]    */  1, 0,
    /*  SHPIX     */  1, 0,
    /*  IP        */  0, 0,
    /*  MSIRP[0]  */  2, 0,
    /*  MSIRP[1]  */  2, 0,
    /*  AlignRP   */  0, 0,
    /*  RTDG      */  0, 0,
    /*  MIAP[0]   */  2, 0,
    /*  MIAP[1]   */  2, 0,
  
    /*  NPushB    */  0, 0,
    /*  NPushW    */  0, 0,
    /*  WS        */  2, 0,
    /*  RS        */  1, 1,
    /*  WCvtP     */  2, 0,
    /*  RCvt      */  1, 1,
    /*  GC[0]     */  1, 1,
    /*  GC[1]     */  1, 1,
    /*  SCFS      */  2, 0,
    /*  MD[0]     */  2, 1,
    /*  MD[1]     */  2, 1,
    /*  MPPEM     */  0, 1,
    /*  MPS       */  0, 1,
    /*  FlipON    */  0, 0,
    /*  FlipOFF   */  0, 0,
    /*  DEBUG     */  1, 0,
  
    /*  LT        */  2, 1,
    /*  LTEQ      */  2, 1,
    /*  GT        */  2, 1,
    /*  GTEQ      */  2, 1,
    /*  EQ        */  2, 1,
    /*  NEQ       */  2, 1,
    /*  ODD       */  1, 1,
    /*  EVEN      */  1, 1,
    /*  IF        */  1, 0,
    /*  EIF       */  0, 0,
    /*  AND       */  2, 1,
    /*  OR        */  2, 1,
    /*  NOT       */  1, 1,
    /*  DeltaP1   */  1, 0,
    /*  SDB       */  1, 0,
    /*  SDS       */  1, 0,
  
    /*  ADD       */  2, 1,
    /*  SUB       */  2, 1,
    /*  DIV       */  2, 1,
    /*  MUL       */  2, 1,
    /*  ABS       */  1, 1,
    /*  NEG       */  1, 1,
    /*  FLOOR     */  1, 1,
    /*  CEILING   */  1, 1,
    /*  ROUND[0]  */  1, 1,
    /*  ROUND[1]  */  1, 1,
    /*  ROUND[2]  */  1, 1,
    /*  ROUND[3]  */  1, 1,
    /*  NROUND[0] */  1, 1,
    /*  NROUND[1] */  1, 1,
    /*  NROUND[2] */  1, 1,
    /*  NROUND[3] */  1, 1,
  
    /*  WCvtF     */  2, 0,
    /*  DeltaP2   */  1, 0,
    /*  DeltaP3   */  1, 0,
    /*  DeltaCn[0] */ 1, 0,
    /*  DeltaCn[1] */ 1, 0,
    /*  DeltaCn[2] */ 1, 0,
    /*  SROUND    */  1, 0,
    /*  S45Round  */  1, 0,
    /*  JROT      */  2, 0,
    /*  JROF      */  2, 0,
    /*  ROFF      */  0, 0,
    /*  INS_$7B   */  0, 0,
    /*  RUTG      */  0, 0,
    /*  RDTG      */  0, 0,
    /*  SANGW     */  1, 0,
    /*  AA        */  1, 0,
  
    /*  FlipPT    */  0, 0,
    /*  FlipRgON  */  2, 0,
    /*  FlipRgOFF */  2, 0,
    /*  INS_$83   */  0, 0,
    /*  INS_$84   */  0, 0,
    /*  ScanCTRL  */  1, 0,
    /*  SDVPTL[0] */  2, 0,
    /*  SDVPTL[1] */  2, 0,
    /*  GetINFO   */  1, 1,
    /*  IDEF      */  1, 0,
    /*  ROLL      */  3, 3,
    /*  MAX       */  2, 1,
    /*  MIN       */  2, 1,
    /*  ScanTYPE  */  1, 0,
    /*  InstCTRL  */  2, 0,
    /*  INS_$8F   */  0, 0,
  
    /*  INS_$90  */   0, 0,
    /*  INS_$91  */   0, 0,
    /*  INS_$92  */   0, 0,
    /*  INS_$93  */   0, 0,
    /*  INS_$94  */   0, 0,
    /*  INS_$95  */   0, 0,
    /*  INS_$96  */   0, 0,
    /*  INS_$97  */   0, 0,
    /*  INS_$98  */   0, 0,
    /*  INS_$99  */   0, 0,
    /*  INS_$9A  */   0, 0,
    /*  INS_$9B  */   0, 0,
    /*  INS_$9C  */   0, 0,
    /*  INS_$9D  */   0, 0,
    /*  INS_$9E  */   0, 0,
    /*  INS_$9F  */   0, 0,
  
    /*  INS_$A0  */   0, 0,
    /*  INS_$A1  */   0, 0,
    /*  INS_$A2  */   0, 0,
    /*  INS_$A3  */   0, 0,
    /*  INS_$A4  */   0, 0,
    /*  INS_$A5  */   0, 0,
    /*  INS_$A6  */   0, 0,
    /*  INS_$A7  */   0, 0,
    /*  INS_$A8  */   0, 0,
    /*  INS_$A9  */   0, 0,
    /*  INS_$AA  */   0, 0,
    /*  INS_$AB  */   0, 0,
    /*  INS_$AC  */   0, 0,
    /*  INS_$AD  */   0, 0,
    /*  INS_$AE  */   0, 0,
    /*  INS_$AF  */   0, 0,
  
    /*  PushB[0]  */  0, 1,
    /*  PushB[1]  */  0, 2,
    /*  PushB[2]  */  0, 3,
    /*  PushB[3]  */  0, 4,
    /*  PushB[4]  */  0, 5,
    /*  PushB[5]  */  0, 6,
    /*  PushB[6]  */  0, 7,
    /*  PushB[7]  */  0, 8,
    /*  PushW[0]  */  0, 1,
    /*  PushW[1]  */  0, 2,
    /*  PushW[2]  */  0, 3,
    /*  PushW[3]  */  0, 4,
    /*  PushW[4]  */  0, 5,
    /*  PushW[5]  */  0, 6,
    /*  PushW[6]  */  0, 7,
    /*  PushW[7]  */  0, 8,
  
    /*  MDRP[00]  */  1, 0,
    /*  MDRP[01]  */  1, 0,
    /*  MDRP[02]  */  1, 0,
    /*  MDRP[03]  */  1, 0,
    /*  MDRP[04]  */  1, 0,
    /*  MDRP[05]  */  1, 0,
    /*  MDRP[06]  */  1, 0,
    /*  MDRP[07]  */  1, 0,
    /*  MDRP[08]  */  1, 0,
    /*  MDRP[09]  */  1, 0,
    /*  MDRP[10]  */  1, 0,
    /*  MDRP[11]  */  1, 0,
    /*  MDRP[12]  */  1, 0,
    /*  MDRP[13]  */  1, 0,
    /*  MDRP[14]  */  1, 0,
    /*  MDRP[15]  */  1, 0,
  
    /*  MDRP[16]  */  1, 0,
    /*  MDRP[17]  */  1, 0,
    /*  MDRP[18]  */  1, 0,
    /*  MDRP[19]  */  1, 0,
    /*  MDRP[20]  */  1, 0,
    /*  MDRP[21]  */  1, 0,
    /*  MDRP[22]  */  1, 0,
    /*  MDRP[23]  */  1, 0,
    /*  MDRP[24]  */  1, 0,
    /*  MDRP[25]  */  1, 0,
    /*  MDRP[26]  */  1, 0,
    /*  MDRP[27]  */  1, 0,
    /*  MDRP[28]  */  1, 0,
    /*  MDRP[29]  */  1, 0,
    /*  MDRP[30]  */  1, 0,
    /*  MDRP[31]  */  1, 0,
  
    /*  MIRP[00]  */  2, 0,
    /*  MIRP[01]  */  2, 0,
    /*  MIRP[02]  */  2, 0,
    /*  MIRP[03]  */  2, 0,
    /*  MIRP[04]  */  2, 0,
    /*  MIRP[05]  */  2, 0,
    /*  MIRP[06]  */  2, 0,
    /*  MIRP[07]  */  2, 0,
    /*  MIRP[08]  */  2, 0,
    /*  MIRP[09]  */  2, 0,
    /*  MIRP[10]  */  2, 0,
    /*  MIRP[11]  */  2, 0,
    /*  MIRP[12]  */  2, 0,
    /*  MIRP[13]  */  2, 0,
    /*  MIRP[14]  */  2, 0,
    /*  MIRP[15]  */  2, 0,
  
    /*  MIRP[16]  */  2, 0,
    /*  MIRP[17]  */  2, 0,
    /*  MIRP[18]  */  2, 0,
    /*  MIRP[19]  */  2, 0,
    /*  MIRP[20]  */  2, 0,
    /*  MIRP[21]  */  2, 0,
    /*  MIRP[22]  */  2, 0,
    /*  MIRP[23]  */  2, 0,
    /*  MIRP[24]  */  2, 0,
    /*  MIRP[25]  */  2, 0,
    /*  MIRP[26]  */  2, 0,
    /*  MIRP[27]  */  2, 0,
    /*  MIRP[28]  */  2, 0,
    /*  MIRP[29]  */  2, 0,
    /*  MIRP[30]  */  2, 0,
    /*  MIRP[31]  */  2, 0
  };


/*******************************************************************
 *
 *  Function    :  Norm
 *
 *  Description :  Returns the norm (length) of a vector.
 *
 *  Input  :  X, Y   vector
 *
 *  Output :  Returns length in F26dot6.
 *
 *****************************************************************/

  static TT_F26Dot6  Norm( TT_F26Dot6  X, TT_F26Dot6  Y )
  {
    Int64       T1, T2;


    MUL_64( X, X, T1 );
    MUL_64( Y, Y, T2 );

    ADD_64( T1, T2, T1 );

    return (TT_F26Dot6)SQRT_64( T1 );
  }


/*******************************************************************
 *
 *  Function    :  FUnits_To_Pixels
 *
 *  Description :  Scale a distance in FUnits to pixel coordinates.
 *
 *  Input  :  Distance in FUnits
 *
 *  Output :  Distance in 26.6 format.
 *
 *****************************************************************/

  static TT_F26Dot6  FUnits_To_Pixels( EXEC_OPS  Int  distance )
  {
    return MulDiv_Round( distance, 
                         CUR.metrics.scale1,
                         CUR.metrics.scale2 );
  }


/*******************************************************************
 *
 *  Function    :  Current_Ratio
 *
 *  Description :  Return the current aspect ratio scaling factor
 *                 depending on the projection vector's state and
 *                 device resolutions.
 *
 *  Input  :  None
 *
 *  Output :  Aspect ratio in 16.16 format, always <= 1.0 .
 *
 *****************************************************************/

  static Long  Current_Ratio( EXEC_OP )
  {
    if ( CUR.metrics.ratio )
      return CUR.metrics.ratio;

    if ( CUR.GS.projVector.y == 0 )
      CUR.metrics.ratio = CUR.metrics.x_ratio;

    else if ( CUR.GS.projVector.x == 0 )
      CUR.metrics.ratio = CUR.metrics.y_ratio;

    else
    {
      Long  x, y;


      x = MulDiv_Round( CUR.GS.projVector.x, CUR.metrics.x_ratio, 0x4000 );
      y = MulDiv_Round( CUR.GS.projVector.y, CUR.metrics.y_ratio, 0x4000 );
      CUR.metrics.ratio = Norm( x, y );
    }

    return CUR.metrics.ratio;
  }


  static Int  Current_Ppem( EXEC_OP )
  {
    return MulDiv_Round( CUR.metrics.ppem, CURRENT_Ratio(), 0x10000 );
  }


  static TT_F26Dot6  Read_CVT( EXEC_OPS Int  index )
  {
    return CUR.cvt[index];
  }


  static TT_F26Dot6  Read_CVT_Stretched( EXEC_OPS Int  index )
  {
    return MulDiv_Round( CUR.cvt[index], CURRENT_Ratio(), 0x10000 );
  }


  static void  Write_CVT( EXEC_OPS Int  index, TT_F26Dot6  value )
  {
    CUR.cvt[index] = value;
  }

  static void  Write_CVT_Stretched( EXEC_OPS Int  index, TT_F26Dot6  value )
  {
    CUR.cvt[index] = MulDiv_Round( value, 0x10000, CURRENT_Ratio() );
  }


  static void  Move_CVT( EXEC_OPS  Int index, TT_F26Dot6 value )
  {
    CUR.cvt[index] += value;
  }

  static void  Move_CVT_Stretched( EXEC_OPS  Int index, TT_F26Dot6  value )
  {
    CUR.cvt[index] += MulDiv_Round( value, 0x10000, CURRENT_Ratio() );
  }


/******************************************************************
 *
 *  Function    :  Calc_Length
 *
 *  Description :  Computes the length in bytes of current opcode.
 *
 *****************************************************************/

  static Bool  Calc_Length( EXEC_OP )
  {
    CUR.opcode = CUR.code[CUR.IP];

    switch ( CUR.opcode ) 
    {
    case 0x40:
      if ( CUR.IP + 1 >= CUR.codeSize )
        return FAILURE;

      CUR.length = CUR.code[CUR.IP + 1] + 2;
      break;

    case 0x41:
      if ( CUR.IP + 1 >= CUR.codeSize )
        return FAILURE;

      CUR.length = CUR.code[CUR.IP + 1] * 2 + 2;
      break;

    case 0xB0:
    case 0xB1:
    case 0xB2:
    case 0xB3:
    case 0xB4:
    case 0xB5:
    case 0xB6:
    case 0xB7:
      CUR.length = CUR.opcode - 0xB0 + 2;
      break;

    case 0xB8:
    case 0xB9:
    case 0xBA:
    case 0xBB:
    case 0xBC:
    case 0xBD:
    case 0xBE:
    case 0xBF:
      CUR.length = (CUR.opcode - 0xB8) * 2 + 3;
      break;

    default:
      CUR.length = 1;
      break;
    }

    /* make sure result is in range */

    if ( CUR.IP + CUR.length > CUR.codeSize )
      return FAILURE;

    return SUCCESS;
  }


/*******************************************************************
 *
 *  Function    :  GetShortIns
 *
 *  Description :  Returns a short integer taken from the instruction
 *                 stream at address IP.
 *
 *  Input  :  None
 *
 *  Output :  Short read at Code^[IP..IP+1]
 *
 *  Notes  :  This one could become a Macro in the C version.
 *
 *****************************************************************/

  static Short  GetShortIns( EXEC_OP )
  {
    /* Reading a byte stream so there is no endianess (DaveP) */
    CUR.IP += 2;
    return ( CUR.code[CUR.IP-2] << 8) +
             CUR.code[CUR.IP-1];
  }


/*******************************************************************
 *
 *  Function    :  Ins_Goto_CodeRange
 *
 *  Description :  Goes to a certain code range in the instruction
 *                 stream.
 *                
 *
 *  Input  :  aRange
 *            aIP
 *
 *  Output :  SUCCESS or FAILURE.
 *
 *****************************************************************/

  static Bool  Ins_Goto_CodeRange( EXEC_OPS Int  aRange, Int  aIP )
  {
    TCodeRange*  WITH;


    if ( aRange < 1 || aRange > 3 )
    {
      CUR.error = TT_Err_Bad_Argument;
      return FAILURE;
    }

    WITH = &CUR.codeRangeTable[aRange - 1];

    if ( WITH->Base == NULL )     /* invalid coderange */
    {
      CUR.error = TT_Err_Invalid_Coderange;
      return FAILURE;
    }

    /* NOTE: Because the last instruction of a program may be a CALL */
    /*       which will return to the first byte *after* the code    */
    /*       range, we test for AIP <= Size, instead of AIP < Size.  */

    if ( aIP > WITH->Size ) 
    {
      CUR.error = TT_Err_Code_Overflow;
      return FAILURE;
    }

    CUR.code     = WITH->Base;
    CUR.codeSize = WITH->Size;
    CUR.IP       = aIP;
    CUR.curRange = aRange;

    return SUCCESS;
  }


/*******************************************************************
 *
 *  Function    :  Direct_Move
 *
 *  Description :  Moves a point by a given distance along the
 *                 freedom vector.
 *
 *  Input  : Vx, Vy      point coordinates to move
 *           touch       touch flag to modify
 *           distance
 *
 *  Output :  None
 *
 *****************************************************************/

  static void  Direct_Move( EXEC_OPS PVecRecord  zone,
                                     Int         point,
                                     TT_F26Dot6  distance )
  {
    TT_F26Dot6 v;


    v = CUR.GS.freeVector.x;

    if ( v != 0 )
    {
      zone->cur_x[point] += MulDiv_Round( distance,
                                          v * 0x10000L,
                                          CUR.F_dot_P );

      zone->touch[point] |= TT_Flag_Touched_X;
    }

    v = CUR.GS.freeVector.y;

    if ( v != 0 ) 
    {
      zone->cur_y[point] += MulDiv_Round( distance,
                                          v * 0x10000L,
                                          CUR.F_dot_P );

      zone->touch[point] |= TT_Flag_Touched_Y;
    }
  }


/******************************************************************/
/*                                                                */
/* The following versions are used whenever both vectors are both */
/* along one of the coordinate unit vectors, i.e. in 90% cases.   */
/*                                                                */
/******************************************************************/

/*******************************************************************
 * Direct_Move_X
 * 
 *******************************************************************/

  static void  Direct_Move_X( EXEC_OPS PVecRecord  zone,
                                      Int         point,
                                      TT_F26Dot6  distance )
  {
    zone->cur_x[point] += distance;
    zone->touch[point] |= TT_Flag_Touched_X;
  }


/*******************************************************************
 * Direct_Move_Y
 *
 *******************************************************************/

  static void  Direct_Move_Y( EXEC_OPS PVecRecord  zone,
                                      Int         point,
                                      TT_F26Dot6  distance )
  {
    zone->cur_y[point] += distance;
    zone->touch[point] |= TT_Flag_Touched_Y;
  }


/*******************************************************************
 *
 *  Function    :  Round_None
 *
 *  Description :  Does not round, but adds engine compensation.
 *
 *  Input  :  distance      : distance to round
 *            compensation  : engine compensation
 *
 *  Output :  rounded distance.
 *
 *  NOTE : The spec says very few about the relationship between
 *         rounding and engine compensation.  However, it seems
 *         from the description of super round that we should
 *         should add the compensation before rounding.
 *
 ******************************************************************/

  static TT_F26Dot6  Round_None( EXEC_OPS TT_F26Dot6  distance,
                                          TT_F26Dot6  compensation )
  {
    TT_F26Dot6  val;


    if ( distance >= 0 )
    {
      val = distance + compensation;
      if ( val < 0 )
        val = 0;
    }
    else {
      val = distance - compensation;
      if ( val > 0 )
        val = 0;
    }

    return val;
  }


/*******************************************************************
 *
 *  Function    :  Round_To_Grid
 *
 *  Description :  Rounds value to grid after adding engine
 *                 compensation
 *
 *  Input  :  distance      : distance to round
 *            compensation  : engine compensation
 *
 *  Output :  Rounded distance.
 *
 *****************************************************************/

  static TT_F26Dot6  Round_To_Grid( EXEC_OPS TT_F26Dot6  distance,
                                             TT_F26Dot6  compensation )
  {
    TT_F26Dot6  val;


    if ( distance >= 0 )
    {
      val = (distance + compensation + 32) & (-64);
      if ( val < 0 )
        val = 0;
    }
    else 
    {
      val = -( (compensation - distance + 32) & (-64) );
      if ( val > 0 )
        val = 0;
    }


    return  val;
  }


/*******************************************************************
 *
 *  Function    :  Round_To_Half_Grid
 *
 *  Description :  Rounds value to half grid after adding engine
 *                 compensation.
 *
 *  Input  :  distance      : distance to round
 *            compensation  : engine compensation
 *
 *  Output :  Rounded distance.
 *
 *****************************************************************/

  static TT_F26Dot6  Round_To_Half_Grid( EXEC_OPS TT_F26Dot6  distance,
                                                  TT_F26Dot6  compensation )
  {
    TT_F26Dot6  val;


    if ( distance >= 0 )
    {
      val = ((distance + compensation) & (-64)) + 32;
      if ( val < 0 )
        val = 0;
    }
    else 
    {
      val = -( ((compensation - distance) & (-64)) + 32 );
      if ( val > 0 )
        val = 0;
    }

    return val;
  }


/*******************************************************************
 *
 *  Function    :  Round_Down_To_Grid
 *
 *  Description :  Rounds value down to grid after adding engine
 *                 compensation.
 *
 *  Input  :  distance      : distance to round
 *            compensation  : engine compensation
 *
 *  Output :  Rounded distance.
 *
 *****************************************************************/

  static TT_F26Dot6  Round_Down_To_Grid( EXEC_OPS TT_F26Dot6  distance,
                                                  TT_F26Dot6  compensation )
  {
    TT_F26Dot6  val;


    if ( distance >= 0 ) 
    {
      val = (distance + compensation) & (-64);
      if ( val < 0 )
        val = 0;
    }
    else 
    {
      val = -( (compensation - distance) & (-64) );
      if ( val > 0 )
        val = 0;
    }

    return val;
  }


/*******************************************************************
 *
 *  Function    :  Round_Up_To_Grid
 *
 *  Description :  Rounds value up to grid after adding engine
 *                 compensation.
 *
 *  Input  :  distance      : distance to round
 *            compensation  : engine compensation
 *
 *  Output :  Rounded distance.
 *
 *****************************************************************/

  static TT_F26Dot6  Round_Up_To_Grid( EXEC_OPS TT_F26Dot6  distance,
                                                TT_F26Dot6  compensation )
  {
    TT_F26Dot6  val;


    if ( distance >= 0 )
    {
      val = (distance + compensation + 63) & (-64);
      if ( val < 0 )
        val = 0;
    }
    else 
    {
      val = -( (compensation - distance + 63) & (-64) );
      if ( val > 0 )
        val = 0;
    }

    return val;
  }


/*******************************************************************
 *
 *  Function    :  Round_To_Double_Grid
 *
 *  Description :  Rounds value to double grid after adding engine
 *                 compensation.
 *
 *  Input  :  distance      : distance to round
 *            compensation  : engine compensation
 *
 *  Output :  Rounded distance.
 *
 *****************************************************************/

  static TT_F26Dot6  Round_To_Double_Grid( EXEC_OPS TT_F26Dot6  distance,
                                                    TT_F26Dot6  compensation )
  {
    TT_F26Dot6 val;


    if ( distance >= 0 )
    {
      val = (distance + compensation + 16) & (-32);
      if ( val < 0 )
        val = 0;
    }
    else 
    {
      val = -( (compensation - distance + 16) & (-32) );
      if ( val > 0 )
        val = 0;
    }

    return val;
  }


/*******************************************************************
 *
 *  Function    :  Round_Super
 *
 *  Description :  Super-rounds value to grid after adding engine
 *                 compensation.
 *
 *  Input  :  distance      : distance to round
 *            compensation  : engine compensation
 *
 *  Output :  Rounded distance.
 *
 *  NOTE : The spec says very few about the relationship between
 *         rounding and engine compensation.  However, it seems
 *         from the description of super round that we should
 *         should add the compensation before rounding.
 *
 *****************************************************************/

  static TT_F26Dot6  Round_Super( EXEC_OPS TT_F26Dot6  distance,
                                           TT_F26Dot6  compensation )
  {
    TT_F26Dot6  val;


    if ( distance >= 0 )
    {
      val = (distance - CUR.phase + CUR.threshold + compensation) &
              (-CUR.period);
      if ( val < 0 )
        val = 0;
      val += CUR.phase;
    }
    else
    {
      val = -( (CUR.threshold - CUR.phase - distance + compensation) &
               (-CUR.period) );
      if ( val > 0 )
        val = 0;
      val -= CUR.phase;
    }

    return val;
  }


/*******************************************************************
 *
 *  Function    :  Round_Super_45
 *
 *  Description :  Super-rounds value to grid after adding engine
 *                 compensation.
 *
 *  Input  :  distance      : distance to round
 *            compensation  : engine compensation
 *
 *  Output :  Rounded distance.
 *
 *  NOTE : There is a separate function for Round_Super_45 as we
 *         may need a greater precision.
 *
 *****************************************************************/

  static TT_F26Dot6  Round_Super_45( EXEC_OPS TT_F26Dot6  distance,
                                              TT_F26Dot6  compensation )
  {
    TT_F26Dot6  val;


    if ( distance >= 0 )
    {
      val = ( (distance - CUR.phase + CUR.threshold + compensation) / 
                CUR.period ) * CUR.period;
      if ( val < 0 )
        val = 0;
      val += CUR.phase;
    }
    else 
    {
      val = -( ( (CUR.threshold - CUR.phase - distance + compensation) / 
                   CUR.period ) * CUR.period );
      if ( val > 0 )
        val = 0;
      val -= CUR.phase;
    }

    return val;
  }


/*******************************************************************
 * Compute_Round
 * 
 *****************************************************************/

  static void  Compute_Round( EXEC_OPS Byte  round_mode )
  {
    switch ( round_mode )
    {
    case TT_Round_Off:
      CUR.func_round = (TRound_Function)Round_None;
      break;

    case TT_Round_To_Grid:
      CUR.func_round = (TRound_Function)Round_To_Grid;
      break;

    case TT_Round_Up_To_Grid:
      CUR.func_round = (TRound_Function)Round_Up_To_Grid;
      break;

    case TT_Round_Down_To_Grid:
      CUR.func_round = (TRound_Function)Round_Down_To_Grid;
      break;

    case TT_Round_To_Half_Grid:
      CUR.func_round = (TRound_Function)Round_To_Half_Grid;
      break;

    case TT_Round_To_Double_Grid:
      CUR.func_round = (TRound_Function)Round_To_Double_Grid;
      break;

    case TT_Round_Super:
      CUR.func_round = (TRound_Function)Round_Super;
      break;

    case TT_Round_Super_45:
      CUR.func_round = (TRound_Function)Round_Super_45;
      break;
    }
  }


/*******************************************************************
 *
 *  Function    :  SetSuperRound
 *
 *  Description :  Sets Super Round parameters.
 *
 *  Input  :  GridPeriod   Grid period
 *            OpCode       SROUND opcode
 *
 *  Output :  None.
 *
 *****************************************************************/

  static void  SetSuperRound( EXEC_OPS TT_F26Dot6  GridPeriod,
                                       Long        selector )
  {
    switch ( selector & 0xC0 )
    {
      case 0:
        CUR.period = GridPeriod / 2;
        break;
  
      case 0x40:
        CUR.period = GridPeriod;
        break;
  
      case 0x80:
        CUR.period = GridPeriod * 2;
        break;
  
      /* This opcode is reserved, but... */
  
      case 0xC0:
        CUR.period = GridPeriod;
        break;
    }

    switch ( selector & 0x30 )
    {
    case 0:
      CUR.phase = 0;
      break;
  
    case 0x10:
      CUR.phase = CUR.period / 4;
      break;
  
    case 0x20:
      CUR.phase = CUR.period / 2;
      break;
  
    case 0x30:
      CUR.phase = GridPeriod * 3 / 4;
      break;
    }

    if ( (selector & 0x0F) == 0 )
      CUR.threshold = CUR.period - 1;
    else
      CUR.threshold = ( (Int)(selector & 0x0F) - 4L ) * CUR.period / 8;

    CUR.period    /= 256;
    CUR.phase     /= 256;
    CUR.threshold /= 256;
  }


/*******************************************************************
 *
 *  Function    :  Project
 *
 *  Description :  Computes the projection of (Vx,Vy) along the
 *                 current projection vector.
 *
 *  Input  :  Vx, Vy    input vector
 *
 *  Output :  Returns distance in F26dot6.
 *
 *****************************************************************/

  static TT_F26Dot6  Project( EXEC_OPS TT_F26Dot6  Vx, TT_F26Dot6  Vy )
  {
    Int64  T1, T2;


    MUL_64( Vx, CUR.GS.projVector.x, T1 );
    MUL_64( Vy, CUR.GS.projVector.y, T2 );

    ADD_64( T1, T2, T1 );

    return (TT_F26Dot6)DIV_64( T1, 0x4000L );
  }


/*******************************************************************
 *
 *  Function    :  Dual_Project
 *
 *  Description :  Computes the projection of (Vx,Vy) along the
 *                 current dual vector.
 *
 *  Input  :  Vx, Vy    input vector
 *
 *  Output :  Returns distance in F26dot6.
 *
 *****************************************************************/
 
  static TT_F26Dot6  Dual_Project( EXEC_OPS TT_F26Dot6  Vx, TT_F26Dot6  Vy )
  {
    Int64  T1, T2;


    MUL_64( Vx, CUR.GS.dualVector.x, T1 );
    MUL_64( Vy, CUR.GS.dualVector.y, T2 );

    ADD_64( T1, T2, T1 );

    return (TT_F26Dot6)DIV_64( T1, 0x4000L );
  }


/*******************************************************************
 *
 *  Function    :  Free_Project
 *
 *  Description :  Computes the projection of (Vx,Vy) along the
 *                 current freedom vector.
 *
 *  Input  :  Vx, Vy    input vector
 *
 *  Output :  Returns distance in F26dot6.
 *
 *****************************************************************/

  static TT_F26Dot6  Free_Project( EXEC_OPS TT_F26Dot6  Vx, TT_F26Dot6  Vy )
  {
    Int64  T1, T2;

    MUL_64( Vx, CUR.GS.freeVector.x, T1 );
    MUL_64( Vy, CUR.GS.freeVector.y, T2 );

    ADD_64( T1, T2, T1 );

    return (TT_F26Dot6)DIV_64( T1, 0x4000L );
  }


/*******************************************************************
 *
 *  Function    :  Project_x
 *
 *  Input  :  Vx, Vy    input vector
 *
 *  Output :  Returns Vx.
 *
 *  Note :    Used as a dummy function.
 *
 *****************************************************************/

  static TT_F26Dot6  Project_x( EXEC_OPS TT_F26Dot6  Vx, TT_F26Dot6  Vy )
  {
    return Vx;
  }


/*******************************************************************
 *
 *  Function    :  Project_y
 *
 *  Input  :  Vx, Vy    input vector
 *
 *  Output :  Returns Vy.
 *
 *  Note :    Used as a dummy function.
 *
 *****************************************************************/

  static TT_F26Dot6  Project_y( EXEC_OPS TT_F26Dot6  Vx, TT_F26Dot6  Vy )
  {
    return Vy;
  }


/*******************************************************************
 *
 *  Function    :  Compute_Funcs
 *
 *  Description :  Computes the projections and movement function
 *                 pointers according to the current graphics state.
 *
 *  Input  :  None
 *
 *****************************************************************/

  static void  Compute_Funcs( EXEC_OP )
  {
    if ( CUR.GS.freeVector.x == 0x4000 )
    {
      CUR.func_freeProj = (TProject_Function)Project_x;
      CUR.F_dot_P       = CUR.GS.projVector.x * 0x10000L;
    }
    else
    {
      if ( CUR.GS.freeVector.y == 0x4000 )
      {
        CUR.func_freeProj = (TProject_Function)Project_y;
        CUR.F_dot_P       = CUR.GS.projVector.y * 0x10000L;
      }
      else
      {
        CUR.func_move     = (TMove_Function)Direct_Move;
        CUR.func_freeProj = (TProject_Function)Free_Project;
        CUR.F_dot_P = (Long)CUR.GS.projVector.x * CUR.GS.freeVector.x * 4 +
                      (Long)CUR.GS.projVector.y * CUR.GS.freeVector.y * 4;
      }
    }

    CUR.cached_metrics = FALSE;

    if ( CUR.GS.projVector.x == 0x4000 )
      CUR.func_project = (TProject_Function)Project_x;
    else
    {
      if ( CUR.GS.projVector.y == 0x4000 )
        CUR.func_project = (TProject_Function)Project_y;
      else
        CUR.func_project = (TProject_Function)Project;
    }

    if ( CUR.GS.dualVector.x == 0x4000 )
      CUR.func_dualproj = (TProject_Function)Project_x;
    else
    {
      if ( CUR.GS.dualVector.y == 0x4000 )
        CUR.func_dualproj = (TProject_Function)Project_y;
      else
        CUR.func_dualproj = (TProject_Function)Dual_Project;
    }

    CUR.func_move = (TMove_Function)Direct_Move;

    if ( CUR.F_dot_P == 0x40000000L )
    {
      if ( CUR.GS.freeVector.x == 0x4000 )
        CUR.func_move = (TMove_Function)Direct_Move_X;
      else
      {
        if ( CUR.GS.freeVector.y == 0x4000 )
          CUR.func_move = (TMove_Function)Direct_Move_Y;
      }
    }

    /* at small sizes, F_dot_P can become too small, resulting   */
    /* in overflows and 'spikes' in a number of glyphs like 'w'. */

    if ( ABS( CUR.F_dot_P ) < 0x4000000L )
      CUR.F_dot_P = 0x40000000L;

    /* Disable cached aspect ratio */
    CUR.metrics.ratio = 0;
  }


/*******************************************************************
 *
 *  Function    :  Normalize
 *
 *  Description :  Norms a vector
 *
 *  Input  :  Vx, Vy    input vector
 *            R         unit vector
 *
 *  Output :  Returns FAILURE if a vector parameter is zero.
 *
 *****************************************************************/

  static Bool  Normalize( EXEC_OPS TT_F26Dot6      Vx,
                                   TT_F26Dot6      Vy,
                                   TT_UnitVector*  R )
  {
    TT_F26Dot6  W;
    Bool        S1, S2;


    if ( ABS( Vx ) < 0x10000L && ABS( Vy ) < 0x10000L )
    {
      Vx *= 0x100;
      Vy *= 0x100;

      W = Norm( Vx, Vy );

      if ( W == 0 )
      {
        /* XXX : Undocumented. It seems that it's possible to try  */
        /*       to normalize the vector (0,0). Return immediately */
        return SUCCESS;
      }

      R->x = (TT_F2Dot14)MulDiv_Round( Vx, 0x4000L, W );
      R->y = (TT_F2Dot14)MulDiv_Round( Vy, 0x4000L, W );

      return SUCCESS;
    }

    W = Norm( Vx, Vy );

    if ( W <= 0 )
    {
      CUR.error = TT_Err_Divide_By_Zero;
      return FAILURE;
    }
    
    Vx = MulDiv_Round( Vx, 0x4000L, W );
    Vy = MulDiv_Round( Vy, 0x4000L, W );

    W = Vx * Vx + Vy * Vy;

    /* Now, we want that Sqrt( W ) = 0x4000 */
    /* Or 0x1000000 <= W < 0x1004000        */

    if ( Vx < 0 )
    {
      Vx = -Vx;
      S1 = TRUE;
    }
    else
      S1 = FALSE;
 
    if ( Vy < 0 )
    {
      Vy = -Vy;
      S2 = TRUE;
    }
    else
      S2 = FALSE;

    while ( W < 0x1000000L )
    {
      /* We need to increase W, by a minimal amount */
      if ( Vx < Vy )
        Vx++;
      else
        Vy++;

      W = Vx * Vx + Vy * Vy;
    }

    while ( W >= 0x1004000L )
    {
      /* We need to decrease W, by a minimal amount */
      if ( Vx < Vy )
        Vx--;
      else
        Vy--;

      W = Vx * Vx + Vy * Vy;
    }

    /* Note that in various cases, we can only  */
    /* compute a Sqrt(W) of 0x3FFF, eg. Vx = Vy */

    if ( S1 )
      Vx = -Vx;

    if ( S2 )
      Vy = -Vy;

    R->x = (TT_F2Dot14)Vx;   /* Type conversion */
    R->y = (TT_F2Dot14)Vy;   /* Type conversion */

    return SUCCESS;
  }



/****************************************************************/
/*                                                              */
/* MANAGING THE STACK                                           */
/*                                                              */
/*  Instructions appear in the specs' order.                    */
/*                                                              */
/****************************************************************/

/*******************************************/
/* DUP[]     : Duplicate top stack element */
/* CodeRange : $20                         */

  static void  Ins_DUP( INS_ARG )
  {
    args[1] = args[0];
  }


/*******************************************/
/* POP[]     : POPs the stack's top elt.   */
/* CodeRange : $21                         */

  static void  Ins_POP( INS_ARG )
  {
    /* nothing to do */
  }


/*******************************************/
/* CLEAR[]   : Clear the entire stack      */
/* CodeRange : $22                         */

  static void  Ins_CLEAR( INS_ARG )
  {
    CUR.new_top = 0;
  }


/*******************************************/
/* SWAP[]    : Swap the top two elements   */
/* CodeRange : $23                         */

  static void  Ins_SWAP( INS_ARG )
  {
    Long  L;


    L       = args[0];
    args[0] = args[1];
    args[1] = L;
  }


/*******************************************/
/* DEPTH[]   : return the stack depth      */
/* CodeRange : $24                         */

  static void  Ins_DEPTH( INS_ARG )
  {
    args[0] = CUR.top;
  }


/*******************************************/
/* CINDEX[]  : copy indexed element        */
/* CodeRange : $25                         */

  static void  Ins_CINDEX( INS_ARG )
  {
    Long  L;


    L = args[0];

    if ( (unsigned)L > CUR.args )
      CUR.error = TT_Err_Invalid_Reference;
    else
      args[0] = CUR.stack[CUR.args - L];
  }


/*******************************************/
/* MINDEX[]  : move indexed element        */
/* CodeRange : $26                         */

  static void  Ins_MINDEX( INS_ARG )
  {
    Long  L, K;


    L = args[0];

    if ( (unsigned)L > CUR.args )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }
    
    K = CUR.stack[CUR.args - L];

    MEM_Copy( (&CUR.stack[CUR.args - L    ]),
              (&CUR.stack[CUR.args - L + 1]),
              (L - 1) * sizeof ( Long ) );

    CUR.stack[ CUR.args-1 ] = K;
  }


/*******************************************/
/* ROLL[]    : roll top three elements     */
/* CodeRange : $8A                         */

  static void  Ins_ROLL( INS_ARG )
  {
    Long  A, B, C;


    A = args[2];
    B = args[1];
    C = args[0];

    args[2] = C;
    args[1] = A;
    args[0] = B;
  }



/****************************************************************/
/*                                                              */
/* MANAGING THE FLOW OF CONTROL                                 */
/*                                                              */
/*  Instructions appear in the specs' order.                    */
/*                                                              */
/****************************************************************/

  static Bool  SkipCode( EXEC_OP )
  {
    CUR.IP += CUR.length;

    if ( CUR.IP < CUR.codeSize )
      if ( CALC_Length() == SUCCESS )
        return SUCCESS;

    CUR.error = TT_Err_Code_Overflow;
    return FAILURE;
  }


/*******************************************/
/* IF[]      : IF test                     */
/* CodeRange : $58                         */

  static void  Ins_IF( INS_ARG )
  {
    Int   nIfs;
    Bool  Out;


    if ( args[0] != 0 )
      return;

    nIfs = 1;
    Out = 0;

    do 
    {
      if ( SKIP_Code() == FAILURE )
        return;

      switch ( CUR.opcode )
      {
      case 0x58:      /* IF */
        nIfs++;
        break;
  
      case 0x1b:      /* ELSE */
        Out = (nIfs == 1);
        break;
  
      case 0x59:      /* EIF */
        nIfs--;
        Out = (nIfs == 0);
        break;
      }
    } while ( Out == 0 );
  }


/*******************************************/
/* ELSE[]    : ELSE                        */
/* CodeRange : $1B                         */

  static void  Ins_ELSE( INS_ARG )
  {
    Int  nIfs;


    nIfs = 1;

    do 
    {
      if ( SKIP_Code() == FAILURE )
        return;

      switch ( CUR.opcode )
      {
      case 0x58:    /* IF */
        nIfs++;
        break;
  
      case 0x59:    /* EIF */
        nIfs--;
        break;
      }
    } while ( nIfs != 0 );
  }


/*******************************************/
/* EIF[]     : End IF                      */
/* CodeRange : $59                         */

  static void  Ins_EIF( INS_ARG )
  {
    /* nothing to do */
  }


/*******************************************/
/* JROT[]    : Jump Relative On True       */
/* CodeRange : $78                         */

  static void  Ins_JROT( INS_ARG )
  {
    if ( args[1] != 0 )
    {
      CUR.IP      += (Int)(args[0]);
      CUR.step_ins = FALSE;
    }
  }


/*******************************************/
/* JMPR[]    : JuMP Relative               */
/* CodeRange : $1C                         */

  static void  Ins_JMPR( INS_ARG )
  {
    CUR.IP      += (Int)(args[0]);
    CUR.step_ins = FALSE;
  }


/*******************************************/
/* JROF[]    : Jump Relative On False      */
/* CodeRange : $79                         */

  static void  Ins_JROF( INS_ARG )
  {
    if ( args[1] == 0 )
    {
      CUR.IP      += (Int)(args[0]);
      CUR.step_ins = FALSE;
    }
  }



/****************************************************************/
/*                                                              */
/* LOGICAL FUNCTIONS                                            */
/*                                                              */
/*  Instructions appear in the specs' order.                    */
/*                                                              */
/****************************************************************/

/*******************************************/
/* LT[]      : Less Than                   */
/* CodeRange : $50                         */

  static void  Ins_LT( INS_ARG )
  {
    if ( args[0] < args[1] )
      args[0] = 1;
    else
      args[0] = 0;
  }


/*******************************************/
/* LTEQ[]    : Less Than or EQual          */
/* CodeRange : $51                         */

  static void  Ins_LTEQ( INS_ARG )
  {
    if ( args[0] <= args[1] )
      args[0] = 1;
    else
      args[0] = 0;
  }


/*******************************************/
/* GT[]      : Greater Than                */
/* CodeRange : $52                         */

  static void  Ins_GT( INS_ARG )
  {
    if ( args[0] > args[1] )
      args[0] = 1;
    else
      args[0] = 0;
  }


/*******************************************/
/* GTEQ[]    : Greater Than or EQual       */
/* CodeRange : $53                         */

  static void  Ins_GTEQ( INS_ARG )
  {
    if ( args[0] >= args[1] )
      args[0] = 1;
    else
      args[0] = 0;
  }


/*******************************************/
/* EQ[]      : EQual                       */
/* CodeRange : $54                         */

  static void  Ins_EQ( INS_ARG )
  {
    if ( args[0] == args[1] )
      args[0] = 1;
    else
      args[0] = 0;
  }


/*******************************************/
/* NEQ[]     : Not EQual                   */
/* CodeRange : $55                         */

  static void  Ins_NEQ( INS_ARG )
  {
    if ( args[0] != args[1] )
      args[0] = 1;
    else
      args[0] = 0;
  }


/*******************************************/
/* ODD[]     : Odd                         */
/* CodeRange : $56                         */

  static void  Ins_ODD( INS_ARG )
  {
    if ( (CUR_Func_round( args[0], 0L ) & 127) == 64 )
      args[0] = 1;
    else
      args[0] = 0;
  }


/*******************************************/
/* EVEN[]    : Even                        */
/* CodeRange : $57                         */

  static void  Ins_EVEN( INS_ARG )
  {
    if ( (CUR_Func_round( args[0], 0L ) & 127) == 0 )
      args[0] = 1;
    else
      args[0] = 0;
  }


/*******************************************/
/* AND[]     : logical AND                 */
/* CodeRange : $5A                         */

  static void  Ins_AND( INS_ARG )
  {
    if ( args[0] != 0 && args[1] != 0 )
      args[0] = 1;
    else
      args[0] = 0;
  }


/*******************************************/
/* OR[]      : logical OR                  */
/* CodeRange : $5B                         */

  static void  Ins_OR( INS_ARG )
  {
    if ( args[0] != 0 || args[1] != 0 )
      args[0] = 1;
    else
      args[0] = 0;
  }


/*******************************************/
/* NOT[]     : logical NOT                 */
/* CodeRange : $5C                         */

  static void  Ins_NOT( INS_ARG )
  {
    if ( args[0] != 0 )
      args[0] = 0;
    else
      args[0] = 1;
  }



/****************************************************************/
/*                                                              */
/* ARITHMETIC AND MATH INSTRUCTIONS                             */
/*                                                              */
/*  Instructions appear in the specs' order.                    */
/*                                                              */
/****************************************************************/

/*******************************************/
/* ADD[]     : ADD                         */
/* CodeRange : $60                         */

  static void  Ins_ADD( INS_ARG )
  {
    args[0] += args[1];
  }


/*******************************************/
/* SUB[]     : SUBstract                   */
/* CodeRange : $61                         */

  static void  Ins_SUB( INS_ARG )
  {
    args[0] -= args[1];
  }


/*******************************************/
/* DIV[]     : DIVide                      */
/* CodeRange : $62                         */

  static void  Ins_DIV( INS_ARG )
  {
    if ( args[1] == 0 )
    {
      CUR.error = TT_Err_Divide_By_Zero;
      return;
    }

    args[0] = MulDiv_Round( args[0], 64L, args[1] );
  }


/*******************************************/
/* MUL[]     : MULtiply                    */
/* CodeRange : $63                         */

  static void  Ins_MUL( INS_ARG )
  {
    args[0] = MulDiv_Round( args[0], args[1], 64L );
  }


/*******************************************/
/* ABS[]     : ABSolute value              */
/* CodeRange : $64                         */

  static void  Ins_ABS( INS_ARG )
  {
    args[0] = ABS( args[0] );
  }


/*******************************************/
/* NEG[]     : NEGate                      */
/* CodeRange : $65                         */

  static void  Ins_NEG( INS_ARG )
  {
    args[0] = -args[0];
  }


/*******************************************/
/* FLOOR[]   : FLOOR                       */
/* CodeRange : $66                         */

  static void  Ins_FLOOR( INS_ARG )
  {
    args[0] &= -64;
  }


/*******************************************/
/* CEILING[] : CEILING                     */
/* CodeRange : $67                         */

  static void  Ins_CEILING( INS_ARG )
  {
    args[0] = (args[0] + 63) & (-64);
  }


/*******************************************/
/* MAX[]     : MAXimum                     */
/* CodeRange : $68                         */

  static void  Ins_MAX( INS_ARG )
  {
    if ( args[1] > args[0] )
      args[0] = args[1];
  }


/*******************************************/
/* MIN[]     : MINimum                     */
/* CodeRange : $69                         */

  static void  Ins_MIN( INS_ARG )
  {
    if ( args[1] < args[0] )
      args[0] = args[1];
  }



/****************************************************************/
/*                                                              */
/* COMPENSATING FOR THE ENGINE CHARACTERISTICS                  */
/*                                                              */
/*  Instructions appear in the specs' order.                    */
/*                                                              */
/****************************************************************/

/*******************************************/
/* ROUND[ab] : ROUND value                 */
/* CodeRange : $68-$6B                     */

  static void  Ins_ROUND( INS_ARG )
  {
    args[0] = CUR_Func_round( args[0],
                              CUR.metrics.compensations[CUR.opcode - 0x68] );
  }


/*******************************************/
/* NROUND[ab]: No ROUNDing of value        */
/* CodeRange : $6C-$6F                     */

  static void  Ins_NROUND( INS_ARG )
  {
    args[0] = Round_None( EXEC_ARGS
                          args[0],
                          CUR.metrics.compensations[CUR.opcode - 0x6C] );
  }



/****************************************************************/
/*                                                              */
/* DEFINING AND USING FUNCTIONS AND INSTRUCTIONS                */
/*                                                              */
/*  Instructions appear in the specs' order.                    */
/*                                                              */
/****************************************************************/

/*******************************************/
/* FDEF[]    : Function DEFinition         */
/* CodeRange : $2C                         */

  static void  Ins_FDEF( INS_ARG )
  {
    PDefRecord  pRec;


    if ( args[0] < 0 || args[0] >= CUR.numFDefs )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    pRec = &CUR.FDefs[args[0]];

    pRec->Range  = CUR.curRange;
    pRec->Opc    = (Byte)(args[0]);
    pRec->Start  = CUR.IP + 1;
    pRec->Active = TRUE;
  
  /* Now skip the whole function definition. */
  /* We don't allow nested IDEFS & FDEFs.    */

    while ( SKIP_Code() == SUCCESS ) 
    {
      switch ( CUR.opcode )
      {
      case 0x89:    /* IDEF */
      case 0x2c:    /* FDEF */
        CUR.error = TT_Err_Nested_DEFS;
        return;
  
      case 0x2d:   /* ENDF */
        return;
      }
    }
  }


/*******************************************/
/* ENDF[]    : END Function definition     */
/* CodeRange : $2D                         */

  static void  Ins_ENDF( INS_ARG )
  {
    PCallRecord  pRec;


    if ( CUR.callTop <= 0 )     /* We encountered an ENDF without a call */
    {
      CUR.error = TT_Err_ENDF_In_Exec_Stream;
      return;
    }

    CUR.callTop--;

    pRec = &CUR.callStack[CUR.callTop];

    pRec->Cur_Count--;

    CUR.step_ins = FALSE;

    if ( pRec->Cur_Count > 0 )
    {
      CUR.callTop++;
      CUR.IP = pRec->Cur_Restart;
    }
    else
      /* Loop through the current function */
      INS_Goto_CodeRange( pRec->Caller_Range, 
                          pRec->Caller_IP );

    /* Exit the current call frame.                       */

    /* NOTE: When the last intruction of a program        */
    /*       is a CALL or LOOPCALL, the return address    */
    /*       is always out of the code range.  This is    */
    /*       a valid address, and it's why we do not test */
    /*       the result of Ins_Goto_CodeRange() here!     */
  }


/*******************************************/
/* CALL[]    : CALL function               */
/* CodeRange : $2B                         */

  static void  Ins_CALL( INS_ARG )
  {
    PCallRecord  pCrec;


    if ( args[0] < 0             ||
         args[0] >= CUR.numFDefs ||
         !CUR.FDefs[args[0]].Active ) 
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    if ( CUR.callTop >= CUR.callSize )
    {
      CUR.error = TT_Err_Stack_Overflow;
      return;
    }

    pCrec = &CUR.callStack[CUR.callTop];

    pCrec->Caller_Range = CUR.curRange;
    pCrec->Caller_IP    = CUR.IP + 1;
    pCrec->Cur_Count    = 1;
    pCrec->Cur_Restart  = CUR.FDefs[args[0]].Start;

    CUR.callTop++;

    INS_Goto_CodeRange( CUR.FDefs[args[0]].Range,
                        CUR.FDefs[args[0]].Start );

    CUR.step_ins = FALSE;
  }


/*******************************************/
/* LOOPCALL[]: LOOP and CALL function      */
/* CodeRange : $2A                         */

  static void  Ins_LOOPCALL( INS_ARG )
  {
    PCallRecord  pTCR;

    if ( args[1] < 0             ||
         args[1] >= CUR.numFDefs ||
         !CUR.FDefs[args[1]].Active )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    if ( CUR.callTop >= CUR.callSize )
    {
      CUR.error = TT_Err_Stack_Overflow;
      return;
    }

    if ( args[0] > 0 )
    {
      pTCR = &CUR.callStack[CUR.callTop];
  
      pTCR->Caller_Range = CUR.curRange;
      pTCR->Caller_IP    = CUR.IP + 1;
      pTCR->Cur_Count    = (Int)(args[0]);
      pTCR->Cur_Restart  = CUR.FDefs[args[1]].Start;
  
      CUR.callTop++;
  
      INS_Goto_CodeRange( CUR.FDefs[args[1]].Range, 
                          CUR.FDefs[args[1]].Start );
  
      CUR.step_ins = FALSE;
    }
  }


/*******************************************/
/* IDEF[]    : Instruction DEFinition      */
/* CodeRange : $89                         */

  static void Ins_IDEF( INS_ARG )
  {
    Int         A;
    PDefRecord  pTDR;


    A = 0;

    while ( A < CUR.numIDefs )
    {
      pTDR = &CUR.IDefs[A];

      if ( pTDR->Active == 0 ) 
      {
        pTDR->Opc    = (Byte)(args[0]);
        pTDR->Start  = CUR.IP + 1;
        pTDR->Range  = CUR.curRange;
        pTDR->Active = TRUE;
  
        A = CUR.numIDefs;
  
        /* Now skip the whole function definition */
        /* We don't allow nested IDEFs & FDEFs.   */
  
        while ( SKIP_Code() == SUCCESS ) 
        {
          switch ( CUR.opcode )
          {
          case 0x89:   /* IDEF */
          case 0x2c:   /* FDEF */
            CUR.error = TT_Err_Nested_DEFS;
            return;
          case 0x2d:   /* ENDF */
            return;
          }
        }
      }
      else
        A++;
    }
  }



/****************************************************************/
/*                                                              */
/* PUSHING DATA ONTO THE INTERPRETER STACK                      */
/*                                                              */
/*  Instructions appear in the specs' order.                    */
/*                                                              */
/****************************************************************/

/*******************************************/
/* NPUSHB[]  : PUSH N Bytes                */
/* CodeRange : $40                         */

  static void  Ins_NPUSHB( INS_ARG )
  {
    Int  L, K;

    L = (Int)CUR.code[CUR.IP + 1];

    if ( CUR.top + L > CUR.stackSize )
    {
      CUR.error = TT_Err_Stack_Overflow;
      return;
    }

    for ( K = 1; K <= L; K++ )
      args[K - 1] = CUR.code[CUR.IP + K + 1];

    CUR.new_top += L;
  }


/*******************************************/
/* NPUSHW[]  : PUSH N Words                */
/* CodeRange : $41                         */

  static void  Ins_NPUSHW( INS_ARG )
  {
    Int  L, K;


    L = (Int)CUR.code[CUR.IP + 1];

    if ( CUR.top+L > CUR.stackSize )
    {
      CUR.error = TT_Err_Stack_Overflow;
      return;
    }

    CUR.IP += 2;

    for ( K = 0; K < L; K++ )
      args[K] = GET_ShortIns();

    CUR.step_ins = FALSE;

    CUR.new_top += L;
  }


/*******************************************/
/* PUSHB[abc]: PUSH Bytes                  */
/* CodeRange : $B0-$B7                     */

  static void  Ins_PUSHB( INS_ARG )
  {
    Int  L, K;

    L = ((Int)CUR.opcode - 0xB0 + 1);

    if ( CUR.top + L > CUR.stackSize ) 
    {
      CUR.error = TT_Err_Stack_Overflow;
      return;
    }

    for ( K = 1; K <= L; K++ )
      args[K - 1] = CUR.code[CUR.IP + K];
  }


/*******************************************/
/* PUSHW[abc]: PUSH Words                  */
/* CodeRange : $B8-$BF                     */

  static void  Ins_PUSHW( INS_ARG )
  {
    Int  L, K;


    L = CUR.opcode - 0xB8 + 1;

    if ( CUR.top + L > CUR.stackSize ) 
    {
      CUR.error = TT_Err_Stack_Overflow;
      return;
    }

    CUR.IP++;

    for ( K = 0; K < L; K++ )
      args[K] = GET_ShortIns();

    CUR.step_ins = FALSE;
  }



/****************************************************************/
/*                                                              */
/* MANAGING THE STORAGE AREA                                    */
/*                                                              */
/*  Instructions appear in the specs' order.                    */
/*                                                              */
/****************************************************************/

/*******************************************/
/* RS[]      : Read Store                  */
/* CodeRange : $43                         */

  static void  Ins_RS( INS_ARG )
  {
    if ( (unsigned)args[0] >= CUR.storeSize )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    args[0] = CUR.storage[args[0]];
  }


/*******************************************/
/* WS[]      : Write Store                 */
/* CodeRange : $42                         */

  static void  Ins_WS( INS_ARG )
  {
    if ( (unsigned)args[0] >= CUR.storeSize ) 
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    CUR.storage[args[0]] = args[1];
  }


/*******************************************/
/* WCVTP[]   : Write CVT in Pixel units    */
/* CodeRange : $44                         */

  static void  Ins_WCVTP( INS_ARG )
  {
    if ( (unsigned)args[0] >= CUR.cvtSize ) 
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    CUR_Func_write_cvt( args[0], args[1] );
  }


/*******************************************/
/* WCVTF[]   : Write CVT in FUnits         */
/* CodeRange : $70                         */

  static void  Ins_WCVTF( INS_ARG )
  {
    if ( (unsigned)args[0] >= CUR.cvtSize )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    CUR.cvt[args[0]] = FUnits_To_Pixels( EXEC_ARGS args[1] );
  }


/*******************************************/
/* RCVT[]    : Read CVT                    */
/* CodeRange : $45                         */

  static void  Ins_RCVT( INS_ARG )
  {
    if ( (unsigned)args[0] >= CUR.cvtSize )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    args[0] = CUR_Func_read_cvt( args[0] );
  }



/****************************************************************/
/*                                                              */
/* MANAGING THE GRAPHICS STATE                                  */
/*                                                              */
/*  Instructions appear in the specs' order.                    */
/*                                                              */
/****************************************************************/

/*******************************************/
/* SVTCA[a]  : Set F and P vectors to axis */
/* CodeRange : $00-$01                     */

  static void  Ins_SVTCA( INS_ARG )
  {
    Short  A, B;


    if ( CUR.opcode & 1 )
        A = 0x4000;
    else
        A = 0;

    B = A ^ 0x4000;

    CUR.GS.freeVector.x = A;
    CUR.GS.projVector.x = A;
    CUR.GS.dualVector.x = A;

    CUR.GS.freeVector.y = B;
    CUR.GS.projVector.y = B;
    CUR.GS.dualVector.y = B;

    COMPUTE_Funcs();
  }


/*******************************************/
/* SPVTCA[a] : Set PVector to Axis         */
/* CodeRange : $02-$03                     */

  static void  Ins_SPVTCA( INS_ARG )
  {
    Short  A, B;

    if ( CUR.opcode & 1 )
      A = 0x4000;
    else
      A = 0;

    B = A ^ 0x4000;

    CUR.GS.projVector.x = A;
    CUR.GS.dualVector.x = A;

    CUR.GS.projVector.y = B;
    CUR.GS.dualVector.y = B;

    COMPUTE_Funcs();
  }


/*******************************************/
/* SFVTCA[a] : Set FVector to Axis         */
/* CodeRange : $04-$05                     */

  static void  Ins_SFVTCA( INS_ARG )
  {
    Short  A, B;


    if ( CUR.opcode & 1 )
      A = 0x4000;
    else
      A = 0;

    B = A ^ 0x4000;

    CUR.GS.freeVector.x = A;
    CUR.GS.freeVector.y = B;

    COMPUTE_Funcs();
  }


  static Bool  Ins_SxVTL( EXEC_OPS  Int             aIdx1, 
                                    Int             aIdx2, 
                                    Int             aOpc,
                                    TT_UnitVector*  Vec )
  {
    Long  A, B, C;


    if ( (unsigned)aIdx2 >= CUR.zp1.n || 
         (unsigned)aIdx1 >= CUR.zp2.n )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return FAILURE;
    }

    A = CUR.zp1.cur_x[aIdx2] - CUR.zp2.cur_x[aIdx1];
    B = CUR.zp1.cur_y[aIdx2] - CUR.zp2.cur_y[aIdx1];

    if ( (aOpc & 1) != 0 )
    {
      C =  B;   /* CounterClockwise rotation */
      B =  A;
      A = -C;
    }

    if ( NORMalize( A, B, Vec ) == FAILURE )
    {
      /* When the vector is too small or zero! */

      CUR.error = TT_Err_Ok;
      Vec->x = 0x4000;
      Vec->y = 0;
    }

    return SUCCESS;
  }


/*******************************************/
/* SPVTL[a]  : Set PVector to Line         */
/* CodeRange : $06-$07                     */

  static void  Ins_SPVTL( INS_ARG )
  {
    if ( INS_SxVTL( args[1],
                    args[0],
                    CUR.opcode,
                    &CUR.GS.projVector) == FAILURE )
      return;

    CUR.GS.dualVector = CUR.GS.projVector;
    COMPUTE_Funcs();
  }


/*******************************************/
/* SFVTL[a]  : Set FVector to Line         */
/* CodeRange : $08-$09                     */

  static void  Ins_SFVTL( INS_ARG )
  {
    if ( INS_SxVTL( (Int)(args[1]),
                    (Int)(args[0]),
                    CUR.opcode,
                    &CUR.GS.freeVector) == FAILURE )
      return;

    COMPUTE_Funcs();
  }


/*******************************************/
/* SFVTPV[]  : Set FVector to PVector      */
/* CodeRange : $0E                         */

  static void  Ins_SFVTPV( INS_ARG )
  {
    CUR.GS.freeVector = CUR.GS.projVector;
    COMPUTE_Funcs();
  }


/*******************************************/
/* SDPVTL[a] : Set Dual PVector to Line    */
/* CodeRange : $86-$87                     */

  static void  Ins_SDPVTL( INS_ARG )
  {
    Long  A, B, C;
    Long  p1, p2;   /* was Int in pas type ERROR */


    p1 = args[1];
    p2 = args[0];

    if ( (unsigned)p2 >= CUR.zp1.n ||
         (unsigned)p1 >= CUR.zp2.n )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    A = CUR.zp1.org_x[p2] - CUR.zp2.org_x[p1];
    B = CUR.zp1.org_y[p2] - CUR.zp2.org_y[p1];

    if ( (CUR.opcode & 1) != 0 )
    {
      C =  B;   /* CounterClockwise rotation */
      B =  A;
      A = -C;
    }

    if ( NORMalize( A, B, &CUR.GS.dualVector ) == FAILURE )
      return;

    A = CUR.zp1.cur_x[p2] - CUR.zp2.cur_x[p1];
    B = CUR.zp1.cur_y[p2] - CUR.zp2.cur_y[p1];

    if ( (CUR.opcode & 1) != 0 )
    {
      C =  B;   /* CounterClockwise rotation */
      B =  A;
      A = -C;
    }

    if ( NORMalize( A, B, &CUR.GS.projVector ) == FAILURE )
      return;

    COMPUTE_Funcs();
  }


/*******************************************/
/* SPVFS[]   : Set PVector From Stack      */
/* CodeRange : $0A                         */

  static void  Ins_SPVFS( INS_ARG )
  {
    Short  S;
    Long   X, Y;


    /* Only use low 16bits, then sign extend */
    S = (Short)args[1];
    Y = (Long)S;
    S = (Short)args[0];
    X = (Long)S;

    if ( NORMalize( X, Y, &CUR.GS.projVector ) == FAILURE )
      return;

    CUR.GS.dualVector = CUR.GS.projVector;

    COMPUTE_Funcs();
  }


/*******************************************/
/* SFVFS[]   : Set FVector From Stack      */
/* CodeRange : $0B                         */

  static void  Ins_SFVFS( INS_ARG )
  {
    Short  S;
    Long   X, Y;


    /* Only use low 16bits, then sign extend */
    S = (Short)args[1];
    Y = (Long)S;
    S = (Short)args[0];
    X = S;

    if ( NORMalize( X, Y, &CUR.GS.freeVector ) == FAILURE )
      return;

    COMPUTE_Funcs();
  }


/*******************************************/
/* GPV[]     : Get Projection Vector       */
/* CodeRange : $0C                         */

  static void  Ins_GPV( INS_ARG )
  {
    args[0] = CUR.GS.projVector.x;
    args[1] = CUR.GS.projVector.y;
  }


/*******************************************/
/* GFV[]     : Get Freedom Vector          */
/* CodeRange : $0D                         */

  static void  Ins_GFV( INS_ARG )
  {
    args[0] = CUR.GS.freeVector.x;
    args[1] = CUR.GS.freeVector.y;
  }


/*******************************************/
/* SRP0[]    : Set Reference Point 0       */
/* CodeRange : $10                         */

  static void  Ins_SRP0( INS_ARG )
  {
    CUR.GS.rp0 = (Int)(args[0]);
  }


/*******************************************/
/* SRP1[]    : Set Reference Point 1       */
/* CodeRange : $11                         */

  static void  Ins_SRP1( INS_ARG )
  {
    CUR.GS.rp1 = (Int)(args[0]);
  }


/*******************************************/
/* SRP2[]    : Set Reference Point 2       */
/* CodeRange : $12                         */

  static void  Ins_SRP2( INS_ARG )
  {
    CUR.GS.rp2 = (Int)(args[0]);
  }


/*******************************************/
/* SZP0[]    : Set Zone Pointer 0          */
/* CodeRange : $13                         */

  static void  Ins_SZP0( INS_ARG )
  {
    switch ( args[0] ) 
    {
    case 0:
      CUR.zp0 = CUR.twilight;
      break;
  
    case 1:
      CUR.zp0 = CUR.pts;
      break;
  
    default:
      CUR.error = TT_Err_Invalid_Reference;
      return;
      break;
    }

    CUR.GS.gep0 = (Int)(args[0]);
  }


/*******************************************/
/* SZP1[]    : Set Zone Pointer 1          */
/* CodeRange : $14                         */

  static void  Ins_SZP1( INS_ARG )
  {
    switch ( args[0] )
    {
    case 0:
      CUR.zp1 = CUR.twilight;
      break;
  
    case 1:
      CUR.zp1 = CUR.pts;
      break;
  
    default:
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    CUR.GS.gep1 = (Int)(args[0]);
  }


/*******************************************/
/* SZP2[]    : Set Zone Pointer 2          */
/* CodeRange : $15                         */

  static void  Ins_SZP2( INS_ARG )
  {
    switch ( args[0] )
    {
    case 0:
      CUR.zp2 = CUR.twilight;
      break;
  
    case 1:
      CUR.zp2 = CUR.pts;
      break;
  
    default:
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    CUR.GS.gep2 = (Int)(args[0]);
  }


/*******************************************/
/* SZPS[]    : Set Zone Pointers           */
/* CodeRange : $16                         */

  static void  Ins_SZPS( INS_ARG )
  {
    switch ( args[0] )
    {
    case 0:
      CUR.zp0 = CUR.twilight;
      break;
  
    case 1:
      CUR.zp0 = CUR.pts;
      break;
  
    default:
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    CUR.zp1 = CUR.zp0;
    CUR.zp2 = CUR.zp0;

    CUR.GS.gep0 = (Int)(args[0]);
    CUR.GS.gep1 = (Int)(args[0]);
    CUR.GS.gep2 = (Int)(args[0]);
  }


/*******************************************/
/* RTHG[]    : Round To Half Grid          */
/* CodeRange : $19                         */

  static void  Ins_RTHG( INS_ARG )
  {
    CUR.GS.round_state = TT_Round_To_Half_Grid;

    CUR.func_round = (TRound_Function)Round_To_Half_Grid;
  }


/*******************************************/
/* RTG[]     : Round To Grid               */
/* CodeRange : $18                         */

  static void  Ins_RTG( INS_ARG )
  {
    CUR.GS.round_state = TT_Round_To_Grid;

    CUR.func_round = (TRound_Function)Round_To_Grid;
  }


/*******************************************/
/* RTDG[]    : Round To Double Grid        */
/* CodeRange : $3D                         */

  static void  Ins_RTDG( INS_ARG )
  {
    CUR.GS.round_state = TT_Round_To_Double_Grid;

    CUR.func_round = (TRound_Function)Round_To_Double_Grid;
  }


/*******************************************/
/* RUTG[]    : Round Up To Grid            */
/* CodeRange : $7C                         */

  static void  Ins_RUTG( INS_ARG )
  {
    CUR.GS.round_state = TT_Round_Up_To_Grid;

    CUR.func_round = (TRound_Function)Round_Up_To_Grid;
  }


/*******************************************/
/* RDTG[]    : Round Down To Grid          */
/* CodeRange : $7D                         */

  static void  Ins_RDTG( INS_ARG )
  {
    CUR.GS.round_state = TT_Round_Down_To_Grid;

    CUR.func_round = (TRound_Function)Round_Down_To_Grid;
  }


/*******************************************/
/* ROFF[]    : Round OFF                   */
/* CodeRange : $7A                         */

  static void  Ins_ROFF( INS_ARG )
  {
    CUR.GS.round_state = TT_Round_Off;

    CUR.func_round = (TRound_Function)Round_None;
  }


/*******************************************/
/* SROUND[]  : Super ROUND                 */
/* CodeRange : $76                         */

  static void  Ins_SROUND( INS_ARG )
  {
    SET_SuperRound( 0x4000L, args[0] );
    CUR.GS.round_state = TT_Round_Super;

    CUR.func_round = (TRound_Function)Round_Super;
  }


/*******************************************/
/* S45ROUND[]: Super ROUND 45 degrees      */
/* CodeRange : $77                         */

  static void  Ins_S45ROUND( INS_ARG )
  {
    SET_SuperRound( 0x2D41L, args[0] );
    CUR.GS.round_state = TT_Round_Super_45;

    CUR.func_round = (TRound_Function)Round_Super_45;
  }


/*******************************************/
/* SLOOP[]   : Set LOOP variable           */
/* CodeRange : $17                         */

  static void  Ins_SLOOP( INS_ARG )
  {
    CUR.GS.loop = args[0];
  }


/*******************************************/
/* SMD[]     : Set Minimum Distance        */
/* CodeRange : $1A                         */

  static void  Ins_SMD( INS_ARG )
  {
    CUR.GS.minimum_distance = args[0];
  }


/*******************************************/
/* INSTCTRL[]: INSTruction ConTRol         */
/* CodeRange : $8e                         */

  static void  Ins_INSTCTRL( INS_ARG )
  {
    Long  K, L;


    K = args[1];
    L = args[0];

    if ( K < 1 || K > 2 )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    if( L != 0 )
        L = K;

    CUR.GS.instruct_control = (Int)((CUR.GS.instruct_control & (~K)) | L);
  }


/*******************************************/
/* SCANCTRL[]: SCAN ConTRol                */
/* CodeRange : $85                         */

  static void  Ins_SCANCTRL( INS_ARG )
  {
    Int  A;


    /* Get Threshold */
    A = (Int)(args[0] & 0xFF);

    if ( A == 0xFF )
    {
      CUR.GS.scan_control = TRUE;
      return;
    }
    else if ( A == 0 )
    {
      CUR.GS.scan_control = FALSE;
      return;
    }
    
    A *= 64;

    if ( (args[0] & 0x100) != 0 && CUR.metrics.pointSize <= A )
      CUR.GS.scan_control = TRUE;

    if ( (args[0] & 0x200) != 0 && CUR.metrics.rotated )
      CUR.GS.scan_control = TRUE;

    if ( (args[0] & 0x400) != 0 && CUR.metrics.stretched )
      CUR.GS.scan_control = TRUE;

    if ( (args[0] & 0x800) != 0 && CUR.metrics.pointSize > A )
      CUR.GS.scan_control = FALSE;

    if ( (args[0] & 0x1000) != 0 && CUR.metrics.rotated )
      CUR.GS.scan_control = FALSE;

    if ( (args[0] & 0x2000) != 0 && CUR.metrics.stretched )
      CUR.GS.scan_control = FALSE;
}


/*******************************************/
/* SCANTYPE[]: SCAN TYPE                   */
/* CodeRange : $8D                         */

  static void  Ins_SCANTYPE( INS_ARG )
  {
    /* For compatibility with future enhancements, */
    /* we must ignore new modes                    */

    if ( args[0] >= 0 && args[0] <= 5 )
    {
      if ( args[0] == 3 )
        args[0] = 2;

      CUR.GS.scan_type = (Int)args[0];
    }
  }


/**********************************************/
/* SCVTCI[]  : Set Control Value Table Cut In */
/* CodeRange : $1D                            */

  static void  Ins_SCVTCI( INS_ARG )
  {
    CUR.GS.control_value_cutin = (TT_F26Dot6)args[0];
  }


/**********************************************/
/* SSWCI[]   : Set Single Width Cut In        */
/* CodeRange : $1E                            */

  static void  Ins_SSWCI( INS_ARG )
  {
    CUR.GS.single_width_cutin = (TT_F26Dot6)args[0];
  }


/**********************************************/
/* SSW[]     : Set Single Width               */
/* CodeRange : $1F                            */

  static void  Ins_SSW( INS_ARG )
  {
    /* XXX : Undocumented or bug in the Windows engine ?  */
    /*                                                    */
    /*  The cvt program of the 'goudy.ttf' font seems     */
    /*  broken in many ways. When pointsize is below 20   */
    /*  its sets a single width cutin of 32, and a single */
    /*  width value of 0x7FFF !! We detect this kind of   */
    /*  overflow, and cancel it (it seems the cutin also  */
    /*  needs to be put to 0 !! )                         */
    /*                                                    */
    /*  I don't like adding a 'fix' in the engine for a   */
    /*  single font family, but this ones seems to come   */
    /*  woth some versions of Windows 95..                */
    /*                                                    */
    if ( args[0] > 0x100 )
    {
      CUR.GS.single_width_cutin = 0;
      CUR.GS.single_width_value = 0;
    }
    else
      CUR.GS.single_width_value = (TT_F26Dot6)args[0];
  }


/**********************************************/
/* FLIPON[]  : Set Auto_flip to On            */
/* CodeRange : $4D                            */

  static void  Ins_FLIPON( INS_ARG )
  {
    CUR.GS.auto_flip = TRUE;
  }


/**********************************************/
/* FLIPOFF[] : Set Auto_flip to Off           */
/* CodeRange : $4E                            */

  static void  Ins_FLIPOFF( INS_ARG )
  {
    CUR.GS.auto_flip = FALSE;
  }


/**********************************************/
/* SANGW[]   : Set Angle Weight               */
/* CodeRange : $7E                            */

  static void  Ins_SANGW( INS_ARG )
  {
    /* instruction not supported anymore */
  }


/**********************************************/
/* SDB[]     : Set Delta Base                 */
/* CodeRange : $5E                            */

  static void  Ins_SDB( INS_ARG )
  {
    CUR.GS.delta_base = (Int)args[0];
  }


/**********************************************/
/* SDS[]     : Set Delta Shift                */
/* CodeRange : $5F                            */

  static void  Ins_SDS( INS_ARG )
  {
    CUR.GS.delta_shift = (Int)args[0];
  }


/**********************************************/
/* GC[a]     : Get Coordinate projected onto  */
/* CodeRange : $46-$47                        */

/* BULLSHIT: Measures from the original glyph must to be taken */
/*           along the dual projection vector!                 */

  static void  Ins_GC( INS_ARG )
  {
    Long  L;


    L = args[0];

    if ( (unsigned)L >= CUR.zp2.n )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    switch ( CUR.opcode & 1 )
    {
    case 0:
      L = CUR_Func_project( CUR.zp2.cur_x[L],
                            CUR.zp2.cur_y[L] );
      break;
  
    case 1:
      L = CUR_Func_dualproj( CUR.zp2.org_x[L],
                             CUR.zp2.org_y[L] );
      break;
    }

    args[0] = L;
  }


/**********************************************/
/* SCFS[]    : Set Coordinate From Stack      */
/* CodeRange : $48                            */
/*                                            */
/* Formula:                                   */
/*                                            */
/*   OA := OA + ( value - OA.p )/( f.p ) * f  */
/*                                            */

  static void  Ins_SCFS( INS_ARG )
  {
    Long  K;
    Int   L;


    L = (Int)args[0];

    if ( args[0] < 0 || args[0] >= CUR.zp2.n )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    K = CUR_Func_project( CUR.zp2.cur_x[L],
                          CUR.zp2.cur_y[L] );

    CUR_Func_move( &CUR.zp2, L, args[1] - K );

    /* not part of the specs, but here for safety */

    if ( CUR.GS.gep2 == 0 )
    {
      CUR.zp2.org_x[L] = CUR.zp2.cur_x[L];
      CUR.zp2.org_y[L] = CUR.zp2.cur_y[L];
    }
  }


/**********************************************/
/* MD[a]     : Measure Distance               */
/* CodeRange : $49-$4A                        */

/* BULLSHIT: Measure taken in the original glyph must be along */
/*           the dual projection vector.                       */

/* Second BULLSHIT: Flag attributes are inverted!                */
/*                  0 => measure distance in original outline    */
/*                  1 => measure distance in grid-fitted outline */

  static void  Ins_MD( INS_ARG )
  {
    Long       K, L;
    TT_F26Dot6 D;

    K = args[1];
    L = args[0];

    if( (unsigned)args[0] >= CUR.zp2.n ||
        (unsigned)args[1] >= CUR.zp1.n )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    if ( CUR.opcode & 1 )
      D = CUR_Func_project( CUR.zp2.cur_x[L] - CUR.zp1.cur_x[K],
                            CUR.zp2.cur_y[L] - CUR.zp1.cur_y[K] );
    else
      D = CUR_Func_dualproj( CUR.zp2.org_x[L] - CUR.zp1.org_x[K],
                             CUR.zp2.org_y[L] - CUR.zp1.org_y[K] );

    args[0] = D;
  }


/**********************************************/
/* MPPEM[]   : Measure Pixel Per EM           */
/* CodeRange : $4B                            */

  static void  Ins_MPPEM( INS_ARG )
  {
    args[0] = CURRENT_Ppem();
  }


/**********************************************/
/* MPS[]     : Measure PointSize              */
/* CodeRange : $4C                            */

  static void  Ins_MPS( INS_ARG )
  {
    args[0] = CUR.metrics.pointSize;
  }



/****************************************************************/
/*                                                              */
/* MANAGING OUTLINES                                            */
/*                                                              */
/*  Instructions appear in the specs' order.                    */
/*                                                              */
/****************************************************************/

/**********************************************/
/* FLIPPT[]  : FLIP PoinT                     */
/* CodeRange : $80                            */

  static void  Ins_FLIPPT( INS_ARG )
  {
    Long  point;


    if ( CUR.top < CUR.GS.loop )
    {
      CUR.error = TT_Err_Too_Few_Arguments;
      return;
    }

    while ( CUR.GS.loop > 0 )
    {
      CUR.args--;

      point = CUR.stack[CUR.args];

      if ( point < 0 || point >= CUR.pts.n )
      {
        CUR.error = TT_Err_Invalid_Reference;
       return;
      }

      CUR.pts.touch[point] ^= TT_Flag_On_Curve;

      CUR.GS.loop--;
    }

    CUR.GS.loop = 1;
    CUR.new_top = CUR.args;
  }


/**********************************************/
/* FLIPRGON[]: FLIP RanGe ON                  */
/* CodeRange : $81                            */

  static void  Ins_FLIPRGON( INS_ARG )
  {
    Long  I, K, L;


    K = args[1];
    L = args[0];

    if ( (unsigned)K >= CUR.pts.n ||
         (unsigned)L >= CUR.pts.n )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    for ( I = L; I <= K; I++ )
      CUR.pts.touch[I] |= TT_Flag_On_Curve;
  }


/**********************************************/
/* FLIPRGOFF : FLIP RanGe OFF                 */
/* CodeRange : $82                            */

  static void  Ins_FLIPRGOFF( INS_ARG )
  {
    Long  I, K, L;


    K = args[1];
    L = args[0];

    if ( (unsigned)K >= CUR.pts.n ||
         (unsigned)L >= CUR.pts.n )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    for ( I = L; I <= K; I++ )
      CUR.pts.touch[I] &= ~TT_Flag_On_Curve;
}


  static Bool  Compute_Point_Displacement( EXEC_OPS
                                           PCoordinates  x,
                                           PCoordinates  y,
                                           PVecRecord    zone,
                                           Int*          refp )
  {
    TVecRecord  zp;
    Int         p;
    TT_F26Dot6  d;


    if ( CUR.opcode & 1 )
    {
      zp = CUR.zp0;
      p  = CUR.GS.rp1;
    }
    else 
    {
      zp = CUR.zp1;
      p  = CUR.GS.rp2;
    }

    if ( (unsigned)p >= zp.n )
    {
      CUR.error = TT_Err_Invalid_Displacement;
      return FAILURE;
    }

    *zone = zp;
    *refp = p;

    d = CUR_Func_project( zp.cur_x[p] - zp.org_x[p],
                          zp.cur_y[p] - zp.org_y[p] );

    *x = MulDiv_Round(d, (Long)CUR.GS.freeVector.x * 0x10000L, CUR.F_dot_P );
    *y = MulDiv_Round(d, (Long)CUR.GS.freeVector.y * 0x10000L, CUR.F_dot_P );

    return SUCCESS;
  }


  static void  Move_Zp2_Point( EXEC_OPS
                               Long        point,
                               TT_F26Dot6  dx,
                               TT_F26Dot6  dy,
                               Bool        touch )
  {
    if ( CUR.GS.freeVector.x != 0 )
    {
      CUR.zp2.cur_x[point] += dx;
      if ( touch )
        CUR.zp2.touch[point] |= TT_Flag_Touched_X;
    }

    if ( CUR.GS.freeVector.y != 0 )
    {
      CUR.zp2.cur_y[point] += dy;
      if ( touch )
        CUR.zp2.touch[point] |= TT_Flag_Touched_Y;
    }
  }


/**********************************************/
/* SHP[a]    : SHift Point by the last point  */
/* CodeRange : $32-33                         */

  static void  Ins_SHP( INS_ARG )
  {
    TVecRecord  zp;
    Int         refp;

    TT_F26Dot6  dx,
                dy;
    Long        point;


    if ( CUR.top < CUR.GS.loop )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    } 

    if ( COMPUTE_Point_Displacement( &dx, &dy, &zp, &refp ) )
      return;

    while ( CUR.GS.loop > 0 )
    {
      CUR.args--;
      point = CUR.stack[CUR.args];

      if ( (unsigned)point >= CUR.zp2.n )
      {
        CUR.error = TT_Err_Invalid_Reference;
        return;
      }

      /* undocumented: SHP touches the points */
      MOVE_Zp2_Point( point, dx, dy, TRUE );

      CUR.GS.loop--;
    }

    CUR.GS.loop = 1;
    CUR.new_top = CUR.args;
  }


/**********************************************/
/* SHC[a]    : SHift Contour                  */
/* CodeRange : $34-35                         */

  static void  Ins_SHC( INS_ARG )
  {
    TVecRecord  zp;
    Int         refp;
    TT_F26Dot6  dx,
                dy;

    Long        contour, i;
    Int         first_point, last_point;


    contour = args[0];

    if ( (unsigned)args[0] >= CUR.numContours )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    if ( COMPUTE_Point_Displacement( &dx, &dy, &zp, &refp ) )
      return;

    if ( contour == 0 )
      first_point = 0;
    else
      first_point = CUR.endContours[contour - 1] + 1;

    last_point = CUR.endContours[contour];

    /* undocumented: SHC doesn't touch the points */
    for ( i = first_point; i <= last_point; i++ )
    {
      if ( zp.cur_x != CUR.zp2.cur_x || refp != i )
        MOVE_Zp2_Point( i, dx, dy, FALSE );
    }
  }


/**********************************************/
/* SHZ[a]    : SHift Zone                     */
/* CodeRange : $36-37                         */

  static void  Ins_SHZ( INS_ARG )
  {
    TVecRecord  zp;
    Int         refp;
    TT_F26Dot6  dx,
                dy;

    Int  last_point;
    Long i;


    if ( (unsigned)args[0] > 1 )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    if ( COMPUTE_Point_Displacement( &dx, &dy, &zp, &refp ) )
      return;

    last_point = zp.n - 1;

    /* undocumented: SHZ doesn't touch the points */
    for ( i = 0; i <= last_point; i++ )
    {
      if ( zp.cur_x != CUR.zp2.cur_x || refp != i )
        MOVE_Zp2_Point( i, dx, dy, FALSE );
    }
  }


/**********************************************/
/* SHPIX[]   : SHift points by a PIXel amount */
/* CodeRange : $38                            */

  static void  Ins_SHPIX( INS_ARG )
  {
    TT_F26Dot6  dx, dy;
    Long        point;


    if ( CUR.top < CUR.GS.loop )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    dx = MulDiv_Round( args[0],
                       (Long)CUR.GS.freeVector.x,
                       0x4000 );
    dy = MulDiv_Round( args[0],
                       (Long)CUR.GS.freeVector.y,
                       0x4000 );

    while ( CUR.GS.loop > 0 )
    {
      CUR.args--;

      point = CUR.stack[CUR.args];

      if ( (unsigned)point >= CUR.zp2.n )
      {
        CUR.error = TT_Err_Invalid_Reference;
        return;
      }

      MOVE_Zp2_Point( point, dx, dy, TRUE );

      CUR.GS.loop--;
    }

    CUR.GS.loop = 1;
    CUR.new_top = CUR.args;
  }


/**********************************************/
/* MSIRP[a]  : Move Stack Indirect Relative   */
/* CodeRange : $3A-$3B                        */

  static void  Ins_MSIRP( INS_ARG )
  {
    Int         point;
    TT_F26Dot6  distance;


    point = (Int)args[0];

    if ( (unsigned)args[0] >= CUR.zp1.n )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    /* XXX: Undocumented behaviour */

    if ( CUR.GS.gep0 == 0 )   /* if in twilight zone */
    {
      CUR.zp1.org_x[point] = CUR.zp0.org_x[CUR.GS.rp0];
      CUR.zp1.org_y[point] = CUR.zp0.org_y[CUR.GS.rp0];
      CUR.zp1.cur_x[point] = CUR.zp1.org_x[point];
      CUR.zp1.cur_y[point] = CUR.zp1.org_y[point];
    }

    distance = CUR_Func_project( CUR.zp1.cur_x[point] -
                                   CUR.zp0.cur_x[CUR.GS.rp0],
                                 CUR.zp1.cur_y[point] - 
                                   CUR.zp0.cur_y[CUR.GS.rp0] );

    CUR_Func_move( &CUR.zp1, point, args[1] - distance );

    CUR.GS.rp1 = CUR.GS.rp0;
    CUR.GS.rp2 = point;

    if ( (CUR.opcode & 1) != 0 )
      CUR.GS.rp0 = point;
  }


/**********************************************/
/* MDAP[a]   : Move Direct Absolute Point     */
/* CodeRange : $2E-$2F                        */

  static void  Ins_MDAP( INS_ARG )
  {
    Int         point;
    TT_F26Dot6  cur_dist,
                distance;


    point = (Int)args[0];

    if ( (unsigned)args[0] >= CUR.zp0.n )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    /* XXX: Is there some undocumented feature while in the */
    /*      twilight zone? ?                                */

    if ( (CUR.opcode & 1) != 0 ) 
    {
      cur_dist = CUR_Func_project( CUR.zp0.cur_x[point],
                                   CUR.zp0.cur_y[point] );
      distance = CUR_Func_round( cur_dist,
                                 CUR.metrics.compensations[0] ) - cur_dist;
    }
    else
      distance = 0;

    CUR_Func_move( &CUR.zp0, point, distance );

    CUR.GS.rp0 = point;
    CUR.GS.rp1 = point;
  }


/**********************************************/
/* MIAP[a]   : Move Indirect Absolute Point   */
/* CodeRange : $3E-$3F                        */

  static void  Ins_MIAP( INS_ARG )
  {
    Int         cvtEntry, point;
    TT_F26Dot6  distance,
                org_dist;


    cvtEntry = (Int)args[1];
    point    = (Int)args[0];

    if ( (unsigned)args[0] >= CUR.zp0.n ||
         (unsigned)args[1] >= CUR.cvtSize )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    /* Undocumented:                                     */
    /*                                                   */
    /* The behaviour of an MIAP instruction is quite     */
    /* different when used in the twilight zone.         */
    /*                                                   */
    /* First, no control value cutin test is performed   */
    /* as it would fail anyway.  Second, the original    */
    /* point, i.e. (org_x,org_y) of zp0.point, is set    */
    /* to the absolute, unrounded distance found in      */
    /* the CVT.                                          */
    /*                                                   */
    /* This is used in the CVT programs of the Microsoft */
    /* fonts Arial, Times, etc., in order to re-adjust   */
    /* some key font heights.  It allows the use of the  */
    /* IP instruction in the twilight zone, which        */
    /* otherwise would be "illegal" according to the     */
    /* specs :)                                          */
    /*                                                   */
    /* We implement it with a special sequence for the   */
    /* twilight zone. This is a bad hack, but it seems   */
    /* to work.                                          */

    distance = CUR_Func_read_cvt( cvtEntry );

    if ( CUR.GS.gep0 == 0 )   /* If in twilight zone */
    {
      CUR.zp0.org_x[point] = MulDiv_Round( CUR.GS.freeVector.x,
                                           distance, 0x4000L );
      CUR.zp0.cur_x[point] = CUR.zp0.org_x[point];

      CUR.zp0.org_y[point] = MulDiv_Round( CUR.GS.freeVector.y,
                                           distance, 0x4000L );
      CUR.zp0.cur_y[point] = CUR.zp0.org_y[point];
    }

    org_dist = CUR_Func_project( CUR.zp0.cur_x[point],
                                 CUR.zp0.cur_y[point] );

    if ( (CUR.opcode & 1) != 0 )   /* rounding and control cutin flag */
    {
      if ( ABS(distance - org_dist) > CUR.GS.control_value_cutin )
        distance = org_dist;

      distance = CUR_Func_round( distance, CUR.metrics.compensations[0] );
    }

    CUR_Func_move( &CUR.zp0, point, distance - org_dist );

    CUR.GS.rp0 = point;
    CUR.GS.rp1 = point;
  }


/**********************************************/
/* MDRP[abcde] : Move Direct Relative Point   */
/* CodeRange   : $C0-$DF                      */

  static void  Ins_MDRP( INS_ARG )
  {
    Int         point;
    TT_F26Dot6  distance,
                org_dist;


    point = (Int)args[0];

    if ( (unsigned)args[0] >= CUR.zp1.n )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    /* XXX: Is there some undocumented feature while in the */
    /*      twilight zone?                                  */

    org_dist = CUR_Func_dualproj( CUR.zp1.org_x[point] -
                                    CUR.zp0.org_x[CUR.GS.rp0],
                                  CUR.zp1.org_y[point] -
                                    CUR.zp0.org_y[CUR.GS.rp0] );

    /* single width cutin test */

    if ( ABS(org_dist) < CUR.GS.single_width_cutin )
    {
      if ( org_dist >= 0 )
        org_dist = CUR.GS.single_width_value;
      else
        org_dist = -CUR.GS.single_width_value;
    }

    /* round flag */

    if ( (CUR.opcode & 4) != 0 )
      distance = CUR_Func_round( org_dist,
                                 CUR.metrics.compensations[CUR.opcode & 3] );
    else
      distance = Round_None( EXEC_ARGS
                             org_dist,
                             CUR.metrics.compensations[CUR.opcode & 3]  );

    /* minimum distance flag */

    if ( (CUR.opcode & 8) != 0 )
    {
      if ( org_dist >= 0 )
      {
        if ( distance < CUR.GS.minimum_distance )
          distance = CUR.GS.minimum_distance;
      }
      else 
      {
        if ( distance > -CUR.GS.minimum_distance )
          distance = -CUR.GS.minimum_distance;
      }
    }

    /* now move the point */

    org_dist = CUR_Func_project( CUR.zp1.cur_x[point] - 
                                   CUR.zp0.cur_x[CUR.GS.rp0],
                                 CUR.zp1.cur_y[point] - 
                                   CUR.zp0.cur_y[CUR.GS.rp0] );

    CUR_Func_move( &CUR.zp1, point, distance - org_dist );

    CUR.GS.rp1 = CUR.GS.rp0;
    CUR.GS.rp2 = point;

    if ( (CUR.opcode & 16) != 0 )
      CUR.GS.rp0 = point;
  }


/**********************************************/
/* MIRP[abcde] : Move Indirect Relative Point */
/* CodeRange   : $E0-$FF                      */

  static void  Ins_MIRP( INS_ARG )
  {
    Int         point,
                cvtEntry;

    TT_F26Dot6  cvt_dist,
                distance,
                cur_dist,
                org_dist;


    point    = (Int)args[0];
    cvtEntry = (Int)args[1];

    /* XXX: UNDOCUMENTED! cvt[-1] = 0 always */

    if ( (unsigned)args[0] >= CUR.zp1.n              ||
         (unsigned)(args[1] + 1 ) >= CUR.cvtSize + 1 )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    if ( args[1] < 0 )
      cvt_dist = 0;
    else
      cvt_dist = CUR_Func_read_cvt( cvtEntry );

    /* single width test */

    if ( ABS( cvt_dist ) < CUR.GS.single_width_cutin )
    {
      if ( cvt_dist >= 0 )
        cvt_dist =  CUR.GS.single_width_value;
      else
        cvt_dist = -CUR.GS.single_width_value;
    }

    /* XXX : Undocumented - twilight zone */

    if ( CUR.GS.gep1 == 0 )
    {
      CUR.zp1.org_x[point] = CUR.zp0.org_x[CUR.GS.rp0] +
                             MulDiv_Round( cvt_dist,
                                           CUR.GS.freeVector.x,
                                           0x4000 );

      CUR.zp1.org_y[point] = CUR.zp0.org_y[CUR.GS.rp0] +
                             MulDiv_Round( cvt_dist,
                                           CUR.GS.freeVector.y,
                                           0x4000 );

      CUR.zp1.cur_x[point] = CUR.zp1.org_x[point];
      CUR.zp1.cur_y[point] = CUR.zp1.org_y[point];
    }

    org_dist = CUR_Func_dualproj( CUR.zp1.org_x[point] - 
                                    CUR.zp0.org_x[CUR.GS.rp0],
                                  CUR.zp1.org_y[point] - 
                                    CUR.zp0.org_y[CUR.GS.rp0] );

    cur_dist = CUR_Func_project( CUR.zp1.cur_x[point] -
                                   CUR.zp0.cur_x[CUR.GS.rp0],
                                 CUR.zp1.cur_y[point] - 
                                   CUR.zp0.cur_y[CUR.GS.rp0] );

    /* auto-flip test */

    if ( CUR.GS.auto_flip )
    {
      if ( (org_dist ^ cvt_dist) < 0 )
        cvt_dist = -cvt_dist;
    }

    /* control value cutin and round */

    if ( (CUR.opcode & 4) != 0 )
    {
      /* XXX: Undocumented : only perform cut-in test when both points */
      /*      refer to the same zone.                                  */

      if ( CUR.GS.gep0 == CUR.GS.gep1 )
        if ( ABS( cvt_dist - org_dist ) >= CUR.GS.control_value_cutin )
          cvt_dist = org_dist;

      distance = CUR_Func_round( cvt_dist,
                                 CUR.metrics.compensations[CUR.opcode & 3] );
    }
    else
      distance = Round_None( EXEC_ARGS
                             cvt_dist,
                             CUR.metrics.compensations[CUR.opcode & 3] );

    /* minimum distance test */

    if ( (CUR.opcode & 8) != 0 )
    {
      if ( org_dist >= 0 )
      {
        if ( distance < CUR.GS.minimum_distance )
          distance = CUR.GS.minimum_distance;
      }
      else
      {
        if ( distance > -CUR.GS.minimum_distance )
          distance = -CUR.GS.minimum_distance;
      }
    }

    CUR_Func_move( &CUR.zp1, point, distance - cur_dist );

    CUR.GS.rp1 = CUR.GS.rp0;

    if ( (CUR.opcode & 16) != 0 )
      CUR.GS.rp0 = point;

    /* UNDOCUMENTED! */

    CUR.GS.rp2 = point;
  }


/**********************************************/
/* ALIGNRP[]   : ALIGN Relative Point         */
/* CodeRange   : $3C                          */

  static void  Ins_ALIGNRP( INS_ARG )
  {
    Int         point;
    TT_F26Dot6  distance;


    if ( CUR.top < CUR.GS.loop )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    while ( CUR.GS.loop > 0 )
    {
      CUR.args--;

      point = (Int)CUR.stack[CUR.args];

      if ( (unsigned)point >= CUR.zp1.n )
      {
        CUR.error = TT_Err_Invalid_Reference;
        return;
      }

      distance = CUR_Func_project( CUR.zp1.cur_x[point] - 
                                     CUR.zp0.cur_x[CUR.GS.rp0],
                                   CUR.zp1.cur_y[point] - 
                                     CUR.zp0.cur_y[CUR.GS.rp0] );
    
      CUR_Func_move( &CUR.zp1, point, -distance );
      CUR.GS.loop--;
    }

    CUR.GS.loop = 1;
    CUR.new_top = CUR.args;
  }


/**********************************************/
/* AA[]        : Adjust Angle                 */
/* CodeRange   : $7F                          */

  static void  Ins_AA( INS_ARG )
  {
    /* Intentional - no longer supported */
  }


/**********************************************/
/* ISECT[]     : moves point to InterSECTion  */
/* CodeRange   : $0F                          */

  static void  Ins_ISECT( INS_ARG )
  {
    Long  point,           /* are these Ints or Longs? */
          a0, a1, 
          b0, b1;

    TT_F26Dot6  discriminant;
   
    TT_F26Dot6  dx,  dy, 
                dax, day, 
                dbx, dby;

    TT_F26Dot6  val;

    TT_Vector   R;


    point = args[0];

    a0 = args[1];
    a1 = args[2];
    b0 = args[3];
    b1 = args[4];

    if ( (unsigned)b0 >= CUR.zp0.n ||
         (unsigned)b1 >= CUR.zp0.n ||
         (unsigned)a0 >= CUR.zp1.n ||
         (unsigned)a1 >= CUR.zp1.n || 
         (unsigned)point >= CUR.zp0.n )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    dbx = CUR.zp0.cur_x[b1] - CUR.zp0.cur_x[b0];
    dby = CUR.zp0.cur_y[b1] - CUR.zp0.cur_y[b0];

    dax = CUR.zp1.cur_x[a1] - CUR.zp1.cur_x[a0];
    day = CUR.zp1.cur_y[a1] - CUR.zp1.cur_y[a0];

    dx = CUR.zp0.cur_x[b0] - CUR.zp1.cur_x[a0];
    dy = CUR.zp0.cur_y[b0] - CUR.zp1.cur_y[a0];

    CUR.zp2.touch[point] |= TT_Flag_Touched_Both;

    discriminant = MulDiv_Round( dax, -dby, 0x40L ) +
                   MulDiv_Round( day, dbx, 0x40L );

    if ( ABS( discriminant ) >= 0x40 )
    {
      val = MulDiv_Round( dx, -dby, 0x40L ) + MulDiv_Round( dy, dbx, 0x40L );

      R.x = MulDiv_Round( val, dax, discriminant );
      R.y = MulDiv_Round( val, day, discriminant );
  
      CUR.zp2.cur_x[point] = CUR.zp1.cur_x[a0] + R.x;
      CUR.zp2.cur_y[point] = CUR.zp1.cur_y[a0] + R.y;
    }
    else
    {
      /* else, take the middle of the middles of A and B */

      CUR.zp2.cur_x[point] = ( CUR.zp1.cur_x[a0] +
                               CUR.zp1.cur_x[a1] +
                               CUR.zp0.cur_x[b0] + 
                               CUR.zp1.cur_x[b1] ) / 4;
      CUR.zp2.cur_y[point] = ( CUR.zp1.cur_y[a0] + 
                               CUR.zp1.cur_y[a1] +
                               CUR.zp0.cur_y[b0] + 
                               CUR.zp1.cur_y[b1] ) / 4;
    }
  }


/**********************************************/
/* ALIGNPTS[]  : ALIGN PoinTS                 */
/* CodeRange   : $27                          */

  static void  Ins_ALIGNPTS( INS_ARG )
  {
    Int         p1, p2;
    TT_F26Dot6  distance;


    p1 = (Int)args[0];
   p2 = (Int)args[1];

    if ( (unsigned)args[0] >= CUR.zp1.n ||
         (unsigned)args[1] >= CUR.zp0.n )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    distance = CUR_Func_project( CUR.zp0.cur_x[p2] - 
                                   CUR.zp1.cur_x[p1],
                                 CUR.zp0.cur_y[p2] - 
                                   CUR.zp1.cur_x[p1] ) / 2;

    CUR_Func_move( &CUR.zp1, p1, distance );
  
    CUR_Func_move( &CUR.zp0, p2, -distance );
  }


/**********************************************/
/* IP[]        : Interpolate Point            */
/* CodeRange   : $39                          */

  static void  Ins_IP( INS_ARG )
  {
    TT_F26Dot6  org_a, org_b, org_x, 
                cur_a, cur_b, cur_x, 
                distance;
    Int         point;


    if ( CUR.top < CUR.GS.loop )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    org_a = CUR_Func_dualproj( CUR.zp0.org_x[CUR.GS.rp1],
                               CUR.zp0.org_y[CUR.GS.rp1] );
                                      
    org_b = CUR_Func_dualproj( CUR.zp1.org_x[CUR.GS.rp2],
                               CUR.zp1.org_y[CUR.GS.rp2] );

    cur_a = CUR_Func_project( CUR.zp0.cur_x[CUR.GS.rp1],
                              CUR.zp0.cur_y[CUR.GS.rp1] );
 
    cur_b = CUR_Func_project( CUR.zp1.cur_x[CUR.GS.rp2],
                              CUR.zp1.cur_y[CUR.GS.rp2] );

    while ( CUR.GS.loop > 0 )
    {
      CUR.args--;

      point = (Int)CUR.stack[CUR.args];

      org_x = CUR_Func_dualproj( CUR.zp2.org_x[point],
                                 CUR.zp2.org_y[point] );
    
      cur_x = CUR_Func_project( CUR.zp2.cur_x[point],
                                CUR.zp2.cur_y[point] );

      if ( ( org_a <= org_b && org_x <= org_a ) ||
           ( org_a >  org_b && org_x >= org_a ) )
 
        distance = ( cur_a - org_a ) + ( org_x - cur_x );
 
      else if ( ( org_a <= org_b  &&  org_x >= org_b ) ||
                ( org_a >  org_b  &&  org_x <  org_b ) )

        distance = ( cur_b - org_b ) + ( org_x - cur_x );

      else
         /* note: it seems that rounding this value isn't a good */
         /*       idea (cf. width of capital 'S' in Times)       */

         distance = MulDiv_Round( cur_b - cur_a,
                                  org_x - org_a,
                                  org_b - org_a ) + ( cur_a - cur_x );

      CUR_Func_move( &CUR.zp2, point, distance );

      CUR.GS.loop--;
    }

    CUR.GS.loop = 1;
    CUR.new_top = CUR.args;
  }


/**********************************************/
/* UTP[a]      : UnTouch Point                */
/* CodeRange   : $29                          */

  static void  Ins_UTP( INS_ARG )
  {
    Byte  mask;

    if ( (unsigned)args[0] >= CUR.zp0.n )
    {
      CUR.error = TT_Err_Invalid_Reference;
      return;
    }

    mask = 0xFF;

    if ( CUR.GS.freeVector.x != 0 )
      mask &= ~TT_Flag_Touched_X;

    if ( CUR.GS.freeVector.y != 0 )
      mask &= ~TT_Flag_Touched_Y;

    CUR.zp0.touch[args[0]] &= mask;
  }


  /* Local variables for Ins_IUP: */
  struct LOC_Ins_IUP
  {
    PCoordinates  orgs;   /* original and current coordinate */
    PCoordinates  curs;   /* arrays                          */
  };


  static void  Shift( Int  p1,
                      Int  p2,
                      Int  p,
                      struct LOC_Ins_IUP*  LINK )
  {
    Int         i;
    TT_F26Dot6  x;


    x = LINK->curs[p] - LINK->orgs[p];

    for ( i = p1; i < p; i++ )
      LINK->curs[i] += x;
 
    for ( i = p + 1; i <= p2; i++ )
      LINK->curs[i] += x;
  }


  static void  Interp( Int  p1, Int  p2,
                       Int  ref1, Int  ref2,
                       struct LOC_Ins_IUP*  LINK )
  {
    Long        i;
    TT_F26Dot6  x, x1, x2, d1, d2;


    if ( p1 > p2 )
      return;

    x1 = LINK->orgs[ref1];
    d1 = LINK->curs[ref1] - LINK->orgs[ref1];
    x2 = LINK->orgs[ref2];
    d2 = LINK->curs[ref2] - LINK->orgs[ref2];

    if ( x1 == x2 )
    {
      for ( i = p1; i <= p2; i++ )
      {
        x = LINK->orgs[i];

        if ( x <= x1 )
          x += d1;
        else
          x += d2;

        LINK->curs[i] = x;
      }
      return;
    }

    if ( x1 < x2 )
    {
      for ( i = p1; i <= p2; i++ )
      {
        x = LINK->orgs[i];

        if ( x <= x1 )
          x += d1;
        else   
        {
          if ( x >= x2 )
            x += d2;
          else
            x = LINK->curs[ref1] +
                  MulDiv_Round( x - x1,
                                LINK->curs[ref2] - LINK->curs[ref1],
                                x2 - x1 );
        }
        LINK->curs[i] = x;
      }
      return;
    }

    /* x2 < x1 */

    for ( i = p1; i <= p2; i++ )
    {
      x = LINK->orgs[i];
      if ( x <= x2 )
        x += d2;
      else
      {
        if ( x >= x1 )
          x += d1;
        else
          x = LINK->curs[ref1] +
                MulDiv_Round( x - x1,
                              LINK->curs[ref2] - LINK->curs[ref1],
                              x2 - x1 );
      }
      LINK->curs[i] = x;
    }
  }


/**********************************************/
/* IUP[a]      : Interpolate Untouched Points */
/* CodeRange   : $30-$31                      */

  static void  Ins_IUP( INS_ARG )
  {
    struct LOC_Ins_IUP  V;
    unsigned char       mask;

    Long first_point;   /* first point of contour        */
    Long end_point;     /* end point (last+1) of contour */

    Long first_touched; /* first touched point in contour   */
    Long cur_touched;   /* current touched point in contour */

    Long point;         /* current point   */
    Long contour;       /* current contour */


    if ( CUR.opcode & 1 )
    {
      mask   = TT_Flag_Touched_X;
      V.orgs = CUR.pts.org_x;
      V.curs = CUR.pts.cur_x;
    }
    else
    {
      mask   = TT_Flag_Touched_Y;
      V.orgs = CUR.pts.org_y;
      V.curs = CUR.pts.cur_y;
    }

    contour = 0;
    point   = 0;

    do 
    {
      end_point   = CUR.endContours[contour];
      first_point = point;

      while ( point <= end_point && (CUR.pts.touch[point] & mask) == 0 )
        point++;

      if ( point <= end_point )
      {
        first_touched = point;
        cur_touched   = point;

        point++;

        while ( point <= end_point )
        {
          if ( (CUR.pts.touch[point] & mask) != 0 )
          {
            Interp( (Int)(cur_touched + 1),
                    (Int)(point - 1),
                    (Int)cur_touched,
                    (Int)point,
                    &V );
            cur_touched = point;
          }

          point++;
        }

        if ( cur_touched == first_touched )
          Shift( (Int)first_point, (Int)end_point, (Int)cur_touched, &V );
        else
        {
          Interp((Int)(cur_touched + 1),
                 (Int)(end_point),
                 (Int)(cur_touched),
                 (Int)(first_touched),
                 &V );

          Interp((Int)(first_point),
                 (Int)(first_touched - 1),
                 (Int)(cur_touched),
                 (Int)(first_touched),
                 &V );
        }
      }
      contour++;
    } while ( contour < CUR.numContours );
  }


/**********************************************/
/* DELTAPn[]   : DELTA Exceptions P1, P2, P3  */
/* CodeRange   : $5D,$71,$72                  */

  static void  Ins_DELTAP( INS_ARG )
  {
    Int   k;
    Long  A, B, C, nump;


    nump = args[0];

    for ( k = 1; k <= nump; k++ )
    {
      if ( CUR.args < 2 )
      {
        CUR.error = TT_Err_Too_Few_Arguments;
        return;
      }

      CUR.args -= 2;

      A = CUR.stack[CUR.args + 1];
      B = CUR.stack[CUR.args];

      if ( A >= CUR.zp0.n )
      {
        CUR.error = TT_Err_Invalid_Reference;
        return;
      }

      C = (B & 0xF0) >> 4;

      switch ( CUR.opcode )
      {
      case 0x5d:
        break;
  
      case 0x71:
        C += 16;
        break;
  
      case 0x72:
        C += 32;
        break;
      }

      C += CUR.GS.delta_base;

      if ( CURRENT_Ppem() == C )
      {
        B = (B & 0xF) - 8;
        if ( B >= 0 )
          B++;
        B = B * 64 / (1L << CUR.GS.delta_shift);

        CUR_Func_move( &CUR.zp0, (Int)A, (Int)B );
      }
    }

    CUR.new_top = CUR.args;
  }


/**********************************************/
/* DELTACn[]   : DELTA Exceptions C1, C2, C3  */
/* CodeRange   : $73,$74,$75                  */

  static void  Ins_DELTAC( INS_ARG )
  {
    Long  nump, k;
    Long  A, B, C;


    nump = args[0];

    for ( k = 1; k <= nump; k++ )
    {
      if ( CUR.args < 2 )
      {
        CUR.error = TT_Err_Too_Few_Arguments;
        return;
      }

      CUR.args -= 2;

      A = CUR.stack[CUR.args + 1];
      B = CUR.stack[CUR.args];

      if ( A >= CUR.cvtSize )
      {
        CUR.error = TT_Err_Invalid_Reference;
        return;
      }

      C = ((unsigned long)(B & 0xF0)) >> 4;

      switch ( CUR.opcode )
      {
      case 0x73:
        break;
  
      case 0x74:
        C += 16;
        break;
  
      case 0x75:
        C += 32;
        break;
      }

      C += CUR.GS.delta_base;

      if ( CURRENT_Ppem() == C )
      {
        B = (B & 0xF) - 8;
        if ( B >= 0 )
          B++;
        B = B * 64 / (1L << CUR.GS.delta_shift);

        CUR_Func_move_cvt( A, B );
      }
    }

    CUR.new_top = CUR.args;
  }



/****************************************************************/
/*                                                              */
/* MISC. INSTRUCTIONS                                           */
/*                                                              */
/****************************************************************/

/***********************************************************/
/* DEBUG[]     : DEBUG. Unsupported                        */
/* CodeRange   : $4F                                       */

/* NOTE : The original instruction pops a value from the stack */

  static void  Ins_DEBUG( INS_ARG )
  {
    CUR.error = TT_Err_Debug_OpCode;
  }


/**********************************************/
/* GETINFO[]   : GET INFOrmation              */
/* CodeRange   : $88                          */

  static void  Ins_GETINFO( INS_ARG )
  {
    Long  K;


    K = 0;

    /* We return then Windows 3.1 version number */
    /* for the font scaler                       */
    if ( (args[0] & 1) != 0 )
      K = 3;

    /* Has the glyph been rotated ? */
    if ( CUR.metrics.rotated )
      K |= 0x80;

    /* Has the glyph been stretched ? */
    if ( CUR.metrics.stretched )
      K |= 0x100;

    args[0] = K;
  }


  static void  Ins_UNKNOWN( INS_ARG )
  {
    CUR.error = TT_Err_Invalid_Opcode;
  }


  static TInstruction_Function  Instruct_Dispatch[256] =
  {
    /* Opcodes are gathered in groups of 16. */
    /* Please keep the spaces as they are.   */

    /*  SVTCA  y  */  Ins_SVTCA,
    /*  SVTCA  x  */  Ins_SVTCA,
    /*  SPvTCA y  */  Ins_SPVTCA,
    /*  SPvTCA x  */  Ins_SPVTCA,
    /*  SFvTCA y  */  Ins_SFVTCA,
    /*  SFvTCA x  */  Ins_SFVTCA,
    /*  SPvTL //  */  Ins_SPVTL,
    /*  SPvTL +   */  Ins_SPVTL,
    /*  SFvTL //  */  Ins_SFVTL,
    /*  SFvTL +   */  Ins_SFVTL,
    /*  SPvFS     */  Ins_SPVFS,
    /*  SFvFS     */  Ins_SFVFS,
    /*  GPV       */  Ins_GPV,
    /*  GFV       */  Ins_GFV,
    /*  SFvTPv    */  Ins_SFVTPV,
    /*  ISECT     */  Ins_ISECT,
  
    /*  SRP0      */  Ins_SRP0,
    /*  SRP1      */  Ins_SRP1,
    /*  SRP2      */  Ins_SRP2,
    /*  SZP0      */  Ins_SZP0,
    /*  SZP1      */  Ins_SZP1,
    /*  SZP2      */  Ins_SZP2,
    /*  SZPS      */  Ins_SZPS,
    /*  SLOOP     */  Ins_SLOOP,
    /*  RTG       */  Ins_RTG,
    /*  RTHG      */  Ins_RTHG,
    /*  SMD       */  Ins_SMD,
    /*  ELSE      */  Ins_ELSE,
    /*  JMPR      */  Ins_JMPR,
    /*  SCvTCi    */  Ins_SCVTCI,
    /*  SSwCi     */  Ins_SSWCI,
    /*  SSW       */  Ins_SSW,
  
    /*  DUP       */  Ins_DUP,
    /*  POP       */  Ins_POP,
    /*  CLEAR     */  Ins_CLEAR,
    /*  SWAP      */  Ins_SWAP,
    /*  DEPTH     */  Ins_DEPTH,
    /*  CINDEX    */  Ins_CINDEX,
    /*  MINDEX    */  Ins_MINDEX,
    /*  AlignPTS  */  Ins_ALIGNPTS,
    /*  INS_$28   */  Ins_UNKNOWN,
    /*  UTP       */  Ins_UTP,
    /*  LOOPCALL  */  Ins_LOOPCALL,
    /*  CALL      */  Ins_CALL,
    /*  FDEF      */  Ins_FDEF,
    /*  ENDF      */  Ins_ENDF,
    /*  MDAP[0]   */  Ins_MDAP,
    /*  MDAP[1]   */  Ins_MDAP,
  
    /*  IUP[0]    */  Ins_IUP,
    /*  IUP[1]    */  Ins_IUP,
    /*  SHP[0]    */  Ins_SHP,
    /*  SHP[1]    */  Ins_SHP,
    /*  SHC[0]    */  Ins_SHC,
    /*  SHC[1]    */  Ins_SHC,
    /*  SHZ[0]    */  Ins_SHZ,
    /*  SHZ[1]    */  Ins_SHZ,
    /*  SHPIX     */  Ins_SHPIX,
    /*  IP        */  Ins_IP,
    /*  MSIRP[0]  */  Ins_MSIRP,
    /*  MSIRP[1]  */  Ins_MSIRP,
    /*  AlignRP   */  Ins_ALIGNRP,
    /*  RTDG      */  Ins_RTDG,
    /*  MIAP[0]   */  Ins_MIAP,
    /*  MIAP[1]   */  Ins_MIAP,
  
    /*  NPushB    */  Ins_NPUSHB,
    /*  NPushW    */  Ins_NPUSHW,
    /*  WS        */  Ins_WS,
    /*  RS        */  Ins_RS,
    /*  WCvtP     */  Ins_WCVTP,
    /*  RCvt      */  Ins_RCVT,
    /*  GC[0]     */  Ins_GC,
    /*  GC[1]     */  Ins_GC,
    /*  SCFS      */  Ins_SCFS,
    /*  MD[0]     */  Ins_MD,
    /*  MD[1]     */  Ins_MD,
    /*  MPPEM     */  Ins_MPPEM,
    /*  MPS       */  Ins_MPS,
    /*  FlipON    */  Ins_FLIPON,
    /*  FlipOFF   */  Ins_FLIPOFF,
    /*  DEBUG     */  Ins_DEBUG,
  
    /*  LT        */  Ins_LT,
    /*  LTEQ      */  Ins_LTEQ,
    /*  GT        */  Ins_GT,
    /*  GTEQ      */  Ins_GTEQ,
    /*  EQ        */  Ins_EQ,
    /*  NEQ       */  Ins_NEQ,
    /*  ODD       */  Ins_ODD,
    /*  EVEN      */  Ins_EVEN,
    /*  IF        */  Ins_IF,
    /*  EIF       */  Ins_EIF,
    /*  AND       */  Ins_AND,
    /*  OR        */  Ins_OR,
    /*  NOT       */  Ins_NOT,
    /*  DeltaP1   */  Ins_DELTAP,
    /*  SDB       */  Ins_SDB,
    /*  SDS       */  Ins_SDS,
  
    /*  ADD       */  Ins_ADD,
    /*  SUB       */  Ins_SUB,
    /*  DIV       */  Ins_DIV,
    /*  MUL       */  Ins_MUL,
    /*  ABS       */  Ins_ABS,
    /*  NEG       */  Ins_NEG,
    /*  FLOOR     */  Ins_FLOOR,
    /*  CEILING   */  Ins_CEILING,
    /*  ROUND[0]  */  Ins_ROUND,
    /*  ROUND[1]  */  Ins_ROUND,
    /*  ROUND[2]  */  Ins_ROUND,
    /*  ROUND[3]  */  Ins_ROUND,
    /*  NROUND[0] */  Ins_NROUND,
    /*  NROUND[1] */  Ins_NROUND,
    /*  NROUND[2] */  Ins_NROUND,
    /*  NROUND[3] */  Ins_NROUND,
  
    /*  WCvtF     */  Ins_WCVTF,
    /*  DeltaP2   */  Ins_DELTAP,
    /*  DeltaP3   */  Ins_DELTAP,
    /*  DeltaCn[0] */ Ins_DELTAC,
    /*  DeltaCn[1] */ Ins_DELTAC,
    /*  DeltaCn[2] */ Ins_DELTAC,
    /*  SROUND    */  Ins_SROUND,
    /*  S45Round  */  Ins_S45ROUND,
    /*  JROT      */  Ins_JROT,
    /*  JROF      */  Ins_JROF,
    /*  ROFF      */  Ins_ROFF,
    /*  INS_$7B   */  Ins_UNKNOWN,
    /*  RUTG      */  Ins_RUTG,
    /*  RDTG      */  Ins_RDTG,
    /*  SANGW     */  Ins_SANGW,
    /*  AA        */  Ins_AA,
  
    /*  FlipPT    */  Ins_FLIPPT,
    /*  FlipRgON  */  Ins_FLIPRGON,
    /*  FlipRgOFF */  Ins_FLIPRGOFF,
    /*  INS_$83   */  Ins_UNKNOWN,
    /*  INS_$84   */  Ins_UNKNOWN,
    /*  ScanCTRL  */  Ins_SCANCTRL,
    /*  SDPVTL[0] */  Ins_SDPVTL,
    /*  SDPVTL[1] */  Ins_SDPVTL,
    /*  GetINFO   */  Ins_GETINFO,
    /*  IDEF      */  Ins_IDEF,
    /*  ROLL      */  Ins_ROLL,
    /*  MAX       */  Ins_MAX,
    /*  MIN       */  Ins_MIN,
    /*  ScanTYPE  */  Ins_SCANTYPE,
    /*  InstCTRL  */  Ins_INSTCTRL,
    /*  INS_$8F   */  Ins_UNKNOWN,
  
    /*  INS_$90  */   Ins_UNKNOWN,
    /*  INS_$91  */   Ins_UNKNOWN,
    /*  INS_$92  */   Ins_UNKNOWN,
    /*  INS_$93  */   Ins_UNKNOWN,
    /*  INS_$94  */   Ins_UNKNOWN,
    /*  INS_$95  */   Ins_UNKNOWN,
    /*  INS_$96  */   Ins_UNKNOWN,
    /*  INS_$97  */   Ins_UNKNOWN,
    /*  INS_$98  */   Ins_UNKNOWN,
    /*  INS_$99  */   Ins_UNKNOWN,
    /*  INS_$9A  */   Ins_UNKNOWN,
    /*  INS_$9B  */   Ins_UNKNOWN,
    /*  INS_$9C  */   Ins_UNKNOWN,
    /*  INS_$9D  */   Ins_UNKNOWN,
    /*  INS_$9E  */   Ins_UNKNOWN,
    /*  INS_$9F  */   Ins_UNKNOWN,
  
    /*  INS_$A0  */   Ins_UNKNOWN,
    /*  INS_$A1  */   Ins_UNKNOWN,
    /*  INS_$A2  */   Ins_UNKNOWN,
    /*  INS_$A3  */   Ins_UNKNOWN,
    /*  INS_$A4  */   Ins_UNKNOWN,
    /*  INS_$A5  */   Ins_UNKNOWN,
    /*  INS_$A6  */   Ins_UNKNOWN,
    /*  INS_$A7  */   Ins_UNKNOWN,
    /*  INS_$A8  */   Ins_UNKNOWN,
    /*  INS_$A9  */   Ins_UNKNOWN,
    /*  INS_$AA  */   Ins_UNKNOWN,
    /*  INS_$AB  */   Ins_UNKNOWN,
    /*  INS_$AC  */   Ins_UNKNOWN,
    /*  INS_$AD  */   Ins_UNKNOWN,
    /*  INS_$AE  */   Ins_UNKNOWN,
    /*  INS_$AF  */   Ins_UNKNOWN,
  
    /*  PushB[0]  */  Ins_PUSHB,
    /*  PushB[1]  */  Ins_PUSHB,
    /*  PushB[2]  */  Ins_PUSHB,
    /*  PushB[3]  */  Ins_PUSHB,
    /*  PushB[4]  */  Ins_PUSHB,
    /*  PushB[5]  */  Ins_PUSHB,
    /*  PushB[6]  */  Ins_PUSHB,
    /*  PushB[7]  */  Ins_PUSHB,
    /*  PushW[0]  */  Ins_PUSHW,
    /*  PushW[1]  */  Ins_PUSHW,
    /*  PushW[2]  */  Ins_PUSHW,
    /*  PushW[3]  */  Ins_PUSHW,
    /*  PushW[4]  */  Ins_PUSHW,
    /*  PushW[5]  */  Ins_PUSHW,
    /*  PushW[6]  */  Ins_PUSHW,
    /*  PushW[7]  */  Ins_PUSHW,
  
    /*  MDRP[00]  */  Ins_MDRP,
    /*  MDRP[01]  */  Ins_MDRP,
    /*  MDRP[02]  */  Ins_MDRP,
    /*  MDRP[03]  */  Ins_MDRP,
    /*  MDRP[04]  */  Ins_MDRP,
    /*  MDRP[05]  */  Ins_MDRP,
    /*  MDRP[06]  */  Ins_MDRP,
    /*  MDRP[07]  */  Ins_MDRP,
    /*  MDRP[08]  */  Ins_MDRP,
    /*  MDRP[09]  */  Ins_MDRP,
    /*  MDRP[10]  */  Ins_MDRP,
    /*  MDRP[11]  */  Ins_MDRP,
    /*  MDRP[12]  */  Ins_MDRP,
    /*  MDRP[13]  */  Ins_MDRP,
    /*  MDRP[14]  */  Ins_MDRP,
    /*  MDRP[15]  */  Ins_MDRP,
  
    /*  MDRP[16]  */  Ins_MDRP,
    /*  MDRP[17]  */  Ins_MDRP,
    /*  MDRP[18]  */  Ins_MDRP,
    /*  MDRP[19]  */  Ins_MDRP,
    /*  MDRP[20]  */  Ins_MDRP,
    /*  MDRP[21]  */  Ins_MDRP,
    /*  MDRP[22]  */  Ins_MDRP,
    /*  MDRP[23]  */  Ins_MDRP,
    /*  MDRP[24]  */  Ins_MDRP,
    /*  MDRP[25]  */  Ins_MDRP,
    /*  MDRP[26]  */  Ins_MDRP,
    /*  MDRP[27]  */  Ins_MDRP,
    /*  MDRP[28]  */  Ins_MDRP,
    /*  MDRP[29]  */  Ins_MDRP,
    /*  MDRP[30]  */  Ins_MDRP,
    /*  MDRP[31]  */  Ins_MDRP,
  
    /*  MIRP[00]  */  Ins_MIRP,
    /*  MIRP[01]  */  Ins_MIRP,
    /*  MIRP[02]  */  Ins_MIRP,
    /*  MIRP[03]  */  Ins_MIRP,
    /*  MIRP[04]  */  Ins_MIRP,
    /*  MIRP[05]  */  Ins_MIRP,
    /*  MIRP[06]  */  Ins_MIRP,
    /*  MIRP[07]  */  Ins_MIRP,
    /*  MIRP[08]  */  Ins_MIRP,
    /*  MIRP[09]  */  Ins_MIRP,
    /*  MIRP[10]  */  Ins_MIRP,
    /*  MIRP[11]  */  Ins_MIRP,
    /*  MIRP[12]  */  Ins_MIRP,
    /*  MIRP[13]  */  Ins_MIRP,
    /*  MIRP[14]  */  Ins_MIRP,
    /*  MIRP[15]  */  Ins_MIRP,
  
    /*  MIRP[16]  */  Ins_MIRP,
    /*  MIRP[17]  */  Ins_MIRP,
    /*  MIRP[18]  */  Ins_MIRP,
    /*  MIRP[19]  */  Ins_MIRP,
    /*  MIRP[20]  */  Ins_MIRP,
    /*  MIRP[21]  */  Ins_MIRP,
    /*  MIRP[22]  */  Ins_MIRP,
    /*  MIRP[23]  */  Ins_MIRP,
    /*  MIRP[24]  */  Ins_MIRP,
    /*  MIRP[25]  */  Ins_MIRP,
    /*  MIRP[26]  */  Ins_MIRP,
    /*  MIRP[27]  */  Ins_MIRP,
    /*  MIRP[28]  */  Ins_MIRP,
    /*  MIRP[29]  */  Ins_MIRP,
    /*  MIRP[30]  */  Ins_MIRP,
    /*  MIRP[31]  */  Ins_MIRP
  };



/****************************************************************/
/*                                                              */
/*                    RUN                                       */
/*                                                              */
/*  This function executes a run of opcodes.  It will exit      */
/*  in the following cases:                                     */
/*                                                              */
/*   - Errors (in which case it returns FALSE)                  */
/*                                                              */
/*   - Reaching the end of the main code range (returns TRUE).  */
/*     Reaching the end of a code range within a function       */
/*     call is an error.                                        */
/*                                                              */
/*   - After executing one single opcode, if the flag           */
/*     'Instruction_Trap' is set to TRUE (returns TRUE).        */
/*                                                              */
/*  On exit whith TRUE, test IP < CodeSize to know wether it    */
/*  comes from a instruction trap or a normal termination.      */
/*                                                              */
/*                                                              */
/*     Note:  The documented DEBUG opcode pops a value from     */
/*            the stack.  This behaviour is unsupported, here   */
/*            a DEBUG opcode is always an error.                */
/*                                                              */
/*                                                              */
/* THIS IS THE INTERPRETER'S MAIN LOOP                          */
/*                                                              */
/*  Instructions appear in the specs' order.                    */
/*                                                              */
/****************************************************************/

#ifndef DEBUG
  TT_Error  RunIns( PExecution_Context  exc ) 
#else
  TT_Error  RunIns2( PExecution_Context  exc )
#endif
  {
    Bool         Result;
    Int          A;
    PDefRecord   WITH;
    PCallRecord  WITH1;


    /* set CVT functions */
    CUR.metrics.ratio = 0;
    if ( CUR.metrics.x_ppem != CUR.metrics.y_ppem )
    {
      /* non-square pixels, use the stretched routines */
      CUR.func_read_cvt  = Read_CVT_Stretched;
      CUR.func_write_cvt = Write_CVT_Stretched;
      CUR.func_move_cvt  = Move_CVT_Stretched;
    }
    else
    {
      /* square pixels, use normal routines */
      CUR.func_read_cvt  = Read_CVT;
      CUR.func_write_cvt = Write_CVT;
      CUR.func_move_cvt  = Move_CVT;
    }

    COMPUTE_Funcs();
    Compute_Round( EXEC_ARGS exc->GS.round_state );

    do 
    {
      CALC_Length();

      /* First, let's check for empty stack and overflow */

      CUR.args = CUR.top - Pop_Push_Count[CUR.opcode * 2];

      /* `args' is the top of the stack once arguments have been popped. */
      /* One can also interpret it as the index of the last argument.    */

      if ( CUR.args < 0 )
      {
        CUR.error = TT_Err_Too_Few_Arguments;
        goto _LErrorLabel;
      }

      CUR.new_top = CUR.args + Pop_Push_Count[CUR.opcode * 2 + 1];

      /* `new_top' is the new top of the stack, after the instruction's */
      /* execution.  `top' will be set to `new_top' after the 'switch'  */
      /* statement.                                                     */

      if ( CUR.new_top > CUR.stackSize )
      {
        CUR.error = TT_Err_Stack_Overflow;
        goto _LErrorLabel;
      }

      CUR.step_ins = TRUE;
      CUR.error    = TT_Err_Ok;

      Instruct_Dispatch[CUR.opcode]( EXEC_ARGS &CUR.stack[CUR.args] );

      if ( CUR.error != TT_Err_Ok )
      {
        switch ( CUR.error )
        {
        case TT_Err_Invalid_Opcode: /* looking for redefined instructions */
          A = 0;

          while ( A < CUR.numIDefs )
          {
            WITH = &CUR.IDefs[A];

            if ( WITH->Active && CUR.opcode == WITH->Opc )
            {
              if ( CUR.callTop >= CUR.callSize )
              {
                CUR.error = TT_Err_Invalid_Reference;
                goto _LErrorLabel;
              }
        
              WITH1 = &CUR.callStack[CUR.callTop];
        
              WITH1->Caller_Range = CUR.curRange;
              WITH1->Caller_IP    = CUR.IP + 1;
              WITH1->Cur_Count    = 1;
              WITH1->Cur_Restart  = WITH->Start;

              if ( INS_Goto_CodeRange( WITH->Range, WITH->Start ) == FAILURE )
                goto _LErrorLabel;
        
              goto _LSuiteLabel;
            }
            else 
            {
              A++;
              continue;
            }
          }

          CUR.error = TT_Err_Invalid_Opcode;
          goto _LErrorLabel;
          break;

        default:
          CUR.error = CUR.error;
          goto _LErrorLabel;
          break;
        }
      }

      CUR.top = CUR.new_top;

      if ( CUR.step_ins )
        CUR.IP += CUR.length;

  _LSuiteLabel:

      if ( CUR.IP >= CUR.codeSize )
      {
        if ( CUR.callTop > 0 )
        {
          CUR.error = TT_Err_Code_Overflow;
          goto _LErrorLabel;
        } 
        else
          goto _LNo_Error;
      }
    } while ( !CUR.instruction_trap );

  _LNo_Error:
    Result = SUCCESS;

    /* Load_Instance_Context( aIns ); */

    return TT_Err_Ok;

  _LErrorLabel:
    Result = FAILURE;

    /* Load_Instance_Context(aIns); */

    return CUR.error;
  }


#ifdef DEBUG

  TT_Error  RunIns( PExecution_Context  exc ) 
  {
    Bool  Result;
    Int   A, next_IP, diff;
    char  ch, *temp;

    TT_Error  error;

    TVecRecord  save;
    TVecRecord  pts;

#define TT_Round_Off             5
#define TT_Round_To_Half_Grid    0
#define TT_Round_To_Grid         1
#define TT_Round_To_Double_Grid  2
#define TT_Round_Up_To_Grid      4
#define TT_Round_Down_To_Grid    3
#define TT_Round_Super           6
#define TT_Round_Super_45        7

    const char*  round_str[8] =
    {
      "to half-grid",
      "to grid",
      "to double grid",
      "down to grid",
      "up to grid",
      "off",
      "super",
      "super 45"
    };

#if 0
    if ( !Goto_CodeRange( exc, TT_CodeRange_Glyph, 0 ) )
      return FAILURE;

    exc->pts = ((PInstance)exc->owner)->pts;
    exc->numContours = ((PInstance)exc)->numContours;

    exc->zp0 = exc->pts;
    exc->zp1 = exc->pts;
    exc->zp2 = exc->pts;

    exc->GS  = exc->default_GS;

    exc->GS.gep0 = 1;
    exc->GS.gep1 = 1;
    exc->GS.gep2 = 1;

    exc->GS.projVector.x = 0x4000;
    exc->GS.projVector.y = 0x0000;
    exc->GS.freeVector.x = 0x4000;
    exc->GS.freeVector.y = 0x0000;
    exc->GS.dualVector.x = 0x4000;
    exc->GS.dualVector.y = 0x0000;

    exc->GS.round_state = 1;

    exc->top = 0;
    /* Some glyphs leave something on the stack! */
    /* We must empty it.                         */
#endif

    if ( exc->curRange != TT_CodeRange_Glyph )
      return RunIns2( exc );

    pts = exc->pts;

    save.n = pts.n;

    save.org_x = (PCoordinates)malloc( sizeof( TT_F26Dot6 ) * save.n );
    save.org_y = (PCoordinates)malloc( sizeof( TT_F26Dot6 ) * save.n );
    save.cur_x = (PCoordinates)malloc( sizeof( TT_F26Dot6 ) * save.n );
    save.cur_y = (PCoordinates)malloc( sizeof( TT_F26Dot6 ) * save.n );
    save.touch = (PByte)malloc( save.n );

    exc->instruction_trap = 1;

    do 
    {
      if ( CUR.IP < CUR.codeSize )
      {
        CALC_Length();
  
        CUR.args = CUR.top - Pop_Push_Count[CUR.opcode * 2];
  
        /* `args' is the top of the stack once arguments have been popped. */
        /* One can also interpret it as the index of the last argument.    */
  
        /* First print the current stack */
  
        A = CUR.args - 4;

        if ( A < 0 )
          A = 0;
  
        while ( A < CUR.top )
        {
          DebugTrace(( "%04lx ", CUR.stack[A] ));
          A++;
          if ( A == CUR.args )
            DebugTrace(( "* " ));
        }
        DebugTrace(( "\n" ));
  
        /* Now print the current line */

#if 1
        DebugTrace(( "%s\n", (char*)Cur_U_Line( &CUR ) ));
#endif
  
        /* First, check for empty stack and overflow */
  
        if ( CUR.args < 0 )
        {
          DebugTrace(( "ERROR : Too Few Arguments\n" ));
          CUR.error = TT_Err_Too_Few_Arguments;
          goto _LErrorLabel;
        }
  
        CUR.new_top = CUR.args + Pop_Push_Count[CUR.opcode * 2 + 1];
  
      /* new_top  is the new top of the stack, after the instruction's */
      /* execution. top will be set to new_top after the 'case'        */
  
        if ( CUR.new_top > CUR.stackSize )
        {
          DebugTrace(( "ERROR : Stack overflow\n" ));
          CUR.error = TT_Err_Stack_Overflow;
          goto _LErrorLabel;
        }
      }
      else
        DebugTrace(( "End of program reached.\n" ));
  
      do
      {
#ifdef OS2
        ch = getch();
        if ( ch > ' ' )
          DebugTrace(( "%c\n", ch ));
#else
        ch = getchar();
#endif
        switch ( ch )
        {
        case '\n':
          ch = '\0';
          break;

        case '?':
          DebugTrace(( "Help\n\n" ));
          DebugTrace(( "?   Show this page\n" ));
          DebugTrace(( "q   Quit debugger\n" ));
          DebugTrace(( "n   Next instruction\n" ));
          DebugTrace(( "s   Step into\n" ));
          DebugTrace(( "v   Show vector info\n" ));
          DebugTrace(( "p   Show points zone\n\n" ));
          ch = '\0';
          break;

        case 'v':
          DebugTrace(( "freedom    (%04hx,%04hx)\n", 
                       exc->GS.freeVector.x,
                       exc->GS.freeVector.y ));
          DebugTrace(( "projection (%04hx,%04hx)\n", 
                       exc->GS.projVector.x,
                       exc->GS.projVector.y ));
          DebugTrace(( "dual       (%04hx,%04hx)\n\n", 
                       exc->GS.dualVector.x,
                       exc->GS.dualVector.y ));
          ch = '\0';
          break;

        case 'g':
          DebugTrace(( "rounding   %s\n", round_str[exc->GS.round_state] ));
          DebugTrace(( "min dist   %04lx\n", exc->GS.minimum_distance ));
          DebugTrace(( "cvt_cutin  %04lx\n", exc->GS.control_value_cutin ));
          ch = '\0';
          break;

        case 'p':
          for ( A = 0; A < exc->pts.n; A++ )
          {
            DebugTrace(( "%02hx  ", A ));

            DebugTrace(( "%08lx,%08lx - ", 
                         pts.org_x[A],
                         pts.org_y[A] ));

            DebugTrace(( "%08lx,%08lx\n", 
                         pts.cur_x[A],
                         pts.cur_y[A] ));
          }
          DebugTrace(( "\n" ));
          ch = '\0';
          break;
        }
      } while ( ch == '\0' );

      MEM_Copy( save.org_x, pts.org_x, pts.n * sizeof ( TT_F26Dot6 ) );
      MEM_Copy( save.org_y, pts.org_y, pts.n * sizeof ( TT_F26Dot6 ) );
      MEM_Copy( save.cur_x, pts.cur_x, pts.n * sizeof ( TT_F26Dot6 ) );
      MEM_Copy( save.cur_y, pts.cur_y, pts.n * sizeof ( TT_F26Dot6 ) );
      MEM_Copy( save.touch, pts.touch, pts.n );

      switch ( ch )
      {
      case 'q':
        goto _LErrorLabel;

      case 's':
        if ( CUR.IP < CUR.codeSize )
          if ( ( error = RunIns2( exc ) ) )
            goto _LErrorLabel;
        break;

      case 'n':
        if ( CUR.IP < CUR.codeSize )
        {
          next_IP = CUR.IP + CUR.length;
          while ( CUR.IP != next_IP )
          {
            if ( ( error = RunIns2( exc ) ) )
              goto _LErrorLabel;
          }
        }
        break;

      default:
        DebugTrace(( "unknown command. Press ? for help\n" ));
      }

      for ( A = 0; A < pts.n; A++ )
      {
        diff = 0;
        if ( save.org_x[A] != pts.org_x[A] ) diff |= 1;
        if ( save.org_y[A] != pts.org_y[A] ) diff |= 2;
        if ( save.cur_x[A] != pts.cur_x[A] ) diff |= 4;
        if ( save.cur_y[A] != pts.cur_y[A] ) diff |= 8;
        if ( save.touch[A] != pts.touch[A] ) diff |= 16;

        if ( diff )
        {
          DebugTrace(( "%02hx  ", A ));

          if ( diff & 16 ) temp = "(%01hx)"; else temp = " %01hx ";
          DebugTrace(( temp, save.touch[A] & 7 ));

          if ( diff & 1 ) temp = "(%08lx)"; else temp = " %08lx ";
          DebugTrace(( temp, save.org_x[A] ));

          if ( diff & 2 ) temp = "(%08lx)"; else temp = " %08lx ";
          DebugTrace(( temp, save.org_y[A] ));

          if ( diff & 4 ) temp = "(%08lx)"; else temp = " %08lx ";
          DebugTrace(( temp, save.cur_x[A] ));

          if ( diff & 8 ) temp = "(%08lx)"; else temp = " %08lx ";
          DebugTrace(( temp, save.cur_y[A] ));

          DebugTrace(( "\n" ));

          DebugTrace(( "%02hx  ", A ));

          if ( diff & 16 ) temp = "[%01hx]"; else temp = " %01hx ";
          DebugTrace(( temp, pts.touch[A] & 7 ));

          if ( diff & 1 ) temp = "[%08lx]"; else temp = " %08lx ";
          DebugTrace(( temp, pts.org_x[A] ));

          if ( diff & 2 ) temp = "[%08lx]"; else temp = " %08lx ";
          DebugTrace(( temp, pts.org_y[A] ));

          if ( diff & 4 ) temp = "[%08lx]"; else temp = " %08lx ";
          DebugTrace(( temp, pts.cur_x[A] ));

          if ( diff & 8 ) temp = "[%08lx]"; else temp = " %08lx ";
          DebugTrace(( temp, pts.cur_y[A] ));

          DebugTrace(( "\n\n" ));
        }
      }
    } while ( TRUE );

  _LErrorLabel:
    Result = FAILURE;

    /* Load_Instance_Context(aIns); */

    return error;
  }

#endif /* DEBUG */


/* END */
