/* Portions Copyright (C) 2001 artofcode LLC.
   Portions Copyright (C) 1996, 2001 Artifex Software Inc.
   Portions Copyright (C) 1988, 2000 Aladdin Enterprises.
   This software is based in part on the work of the Independent JPEG Group.
   All Rights Reserved.

   This software is distributed under license and may not be copied, modified
   or distributed except as expressly authorized under the terms of that
   license.  Refer to licensing information at http://www.artifex.com/ or
   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
   San Rafael, CA  94903, (415)492-9861, for further information. */
/*$Id: pgchar.c,v 1.16 2003/11/01 18:53:37 henrys Exp $ */

/* pgchar.c */
/* HP-GL/2 font and character commands */
#include "math_.h"
#include "stdio_.h"		/* for gdebug.h */
#include "gdebug.h"
#include "pcparse.h"
#include "pgmand.h"
#include "pgdraw.h"
#include "pginit.h"
#include "pggeom.h"
#include "pgmisc.h"
#include "pcfsel.h"
#include "pcpalet.h"

/* ------ Internal procedures ------ */

/* Define font parameters (AD, SD). */
private int
hpgl_font_definition(hpgl_args_t *pargs, hpgl_state_t *pgls, int index)
{	/*
	 * Since these commands take an arbitrary number of arguments,
	 * we reset the argument bookkeeping after each group.
	 * We reset phase to 1, 2, or 3 after seeing the first pair,
	 * so we can tell whether there were any arguments at all.
	 * (1 means no parameter changed, >1 means some parameter changed.)
	 */
	pcl_font_selection_t *pfs = &pgls->g.font_selection[index];
#define pfp (&pfs->params)
	int kind;
	pfs->selected_id = (uint)-1;
	for ( ; hpgl_arg_c_int(pgls->memory, pargs, &kind); pargs->phase |= 1 )
	  switch ( kind )
	    {
	    case 1:		/* symbol set */
	      { int32 sset;
	        if ( !hpgl_arg_int(pgls->memory, pargs, &sset) )
		  return e_Range;
		if ( pfp->symbol_set != (uint)sset )
		  pfp->symbol_set = (uint)sset,
		    pargs->phase |= 2;
	      }
	      break;
	    case 2:		/* spacing */
	      { int spacing;
	        if ( !hpgl_arg_c_int(pgls->memory, pargs, &spacing) )
		  return e_Range;
		if ( ((spacing == 1) || (spacing == 0)) && (pfp->proportional_spacing != spacing) )
		  pfp->proportional_spacing = spacing,
		    pargs->phase |= 2;
	      }
	      break;
	    case 3:		/* pitch */
	      { hpgl_real_t pitch;
	        if ( !hpgl_arg_c_real(pgls->memory, pargs, &pitch) )
		  return e_Range;
		if ( (pl_fp_pitch_per_inch(pfp) != pitch) && (pitch >= 0) )
		  pl_fp_set_pitch_per_inch(pfp, pitch),
		    pargs->phase |= 2;
	      }
	      break;
	    case 4:		/* height */
	      { hpgl_real_t height;
	        if ( !hpgl_arg_c_real(pgls->memory, pargs, &height) )
		  return e_Range;
		if ( (pfp->height_4ths != (uint)(height * 4)) && (height >= 0))
		  pfp->height_4ths = (uint)(height * 4),
		    pargs->phase |= 2;
	      }
	      break;
	    case 5:		/* posture */
	      { int posture;
	        if ( !hpgl_arg_c_int(pgls->memory, pargs, &posture) )
		    return e_Range;
		if ( pfp->style != posture )
		    pfp->style = posture,
			pargs->phase |= 2;

	      }
	      break;
	    case 6:		/* stroke weight */
	      { int weight;
	        if ( !hpgl_arg_c_int(pgls->memory, pargs, &weight) )
		     return e_Range;
		if ( pfp->stroke_weight != weight )
		    if ( ((weight >= -7 ) && (weight <= 7)) || (weight == 9999 ) )
			pfp->stroke_weight = weight,
			    pargs->phase |= 2;
	      }
	      break;
	    case 7:		/* typeface */
	      { int32 face;
	        if ( !hpgl_arg_int(pgls->memory, pargs, &face) )
		  return e_Range;
		if ( pfp->typeface_family != (uint)face )
		  pfp->typeface_family = (uint)face,
		    pargs->phase |= 2;
	      }
	      break;
	    default:
	      return e_Range;
	    }
	/* If there were no arguments at all, default all values. */
	if ( !pargs->phase )
	  hpgl_default_font_params(pfs);
	if ( pargs->phase != 1 )
	  { /* A value changed, or we are defaulting.  Decache the font. */
	    pfs->font = 0;
	    if ( index == pgls->g.font_selected )
	      pgls->g.font = 0;
	  }
	return 0;
}
/* Define label drawing direction (DI, DR). */
private int
hpgl_label_direction(hpgl_args_t *pargs, hpgl_state_t *pgls, bool relative)
{	hpgl_real_t run = 1, rise = 0;

	if ( hpgl_arg_c_real(pgls->memory, pargs, &run) )
	  { if ( !hpgl_arg_c_real(pgls->memory, pargs, &rise) || (run == 0 && rise == 0) )
	      return e_Range;
	    { double hyp = hypot(run, rise);
	      run /= hyp;
	      rise /= hyp;
	    }
	  }
	pgls->g.character.direction.x = run;
	pgls->g.character.direction.y = rise;
	pgls->g.character.direction_relative = relative;
	hpgl_call(hpgl_update_carriage_return_pos(pgls));
	return 0;
}

/* Select font by ID (FI, FN). */
private int
hpgl_select_font_by_id(hpgl_args_t *pargs, hpgl_state_t *pgls, int index)
{	pcl_font_selection_t *pfs = &pgls->g.font_selection[index];
	int32 id;
	int code;

	if ( !hpgl_arg_c_int(pgls->memory, pargs, &id) || id < 0 )
	  return e_Range;
	code = pcl_select_font_by_id(pfs, id, pgls /****** NOTA BENE ******/);
	switch ( code )
	  {
	  default:		/* error */
	    return code;
	  case 1:		/* ID not found, no effect */
	    return 0;
	  case 0:		/* ID found */
	    break;
	  }
	pgls->g.font = pfs->font;
	pgls->g.map = pfs->map;
	/*
	 * If we just selected a bitmap font, force the equivalent of SB1.
	 * See TRM 23-65 and 23-81.
	 */
	if ( pfs->font->scaling_technology == plfst_bitmap )
	  pgls->g.bitmap_fonts_allowed = true;
	return 0;
}

/* Select font (SA, SS). */
private int
hpgl_select_font(hpgl_state_t *pgls, int index)
{
	if ( pgls->g.font_selected != index )
	  { pgls->g.font_selected = index;
	    pgls->g.font = pgls->g.font_selection[index].font;
	    pgls->g.map = pgls->g.font_selection[index].map;
	  }
	return 0;
}

/* ------ Commands ------ */

/* AD [kind,value...]; */
 int
hpgl_AD(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	
	return hpgl_font_definition(pargs, pgls, 1);
}

#define CHAR_EDGE_PEN_UNSET -1

/* return the current edge pen based on whethere or not the interpreter specifically set the pen */
 int32
hpgl_get_character_edge_pen(
    hpgl_state_t *  pgls
)
{
    /* if the character edge pen has not been set then we return the
       current pen number, otherwise the state value as set in the CF
       command is used.  (see hpgl_CF) */
    return (pgls->g.character.edge_pen == CHAR_EDGE_PEN_UNSET ? 
	    hpgl_get_selected_pen(pgls) :
	    pgls->g.character.edge_pen);

}
    
/*
 * CF [mode[,pen]];
 */
 int
hpgl_CF(
    hpgl_args_t *   pargs,
    hpgl_state_t *  pgls
)
{
    int             mode = 0;
    int             npen = pcl_palette_get_num_entries(pgls->ppalet);
    int32           pen = 0;

    if (hpgl_arg_c_int(pgls->memory, pargs, &mode)) {
        if ((mode & ~3) != 0)
	    return e_Range;
	/* With only 1 argument, we "unset" the current pen.  This
           causes the drawing machinery to use the current pen when
           the stroke is rendered (i.e. a subsequent SP will change
           the character edge pen */
	if (hpgl_arg_int(pgls->memory, pargs, &pen)) {
            if ((pen < 0) || (pen >= npen))
		return e_Range;
	} else
	    pen = CHAR_EDGE_PEN_UNSET;
    }
    pgls->g.character.fill_mode = mode;
    pgls->g.character.edge_pen = pen;
    return 0;
}

#undef CHAR_EDGE_PEN_UNSET

/* DI [run,rise]; */
 int
hpgl_DI(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	
	return hpgl_label_direction(pargs, pgls, false);
}

/* DR [run,rise]; */
 int
hpgl_DR(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	
	return hpgl_label_direction(pargs, pgls, true);
}

/* DT terminator[,mode]; */
 int
hpgl_DT(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	const byte *p = pargs->source.ptr;
	const byte *rlimit = pargs->source.limit;
	byte ch = (byte)pargs->phase;
	int mode = 1;

	/* We use phase to remember the terminator character */
	/* in case we had to restart execution. */
	if ( p >= rlimit )
	  return e_NeedData;
	if ( !ch )
	  switch ( (ch = *++p) )
	    {
	    case ';':
	      pargs->source.ptr = p;
	      pgls->g.label.terminator = 3;
	      pgls->g.label.print_terminator = false;
	      return 0;
	    case 0: case 10: case 27:
	      return e_Range;
	    default:
	      if ( p >= rlimit )
		return e_NeedData;
	      if ( *++p ==',' )
		{ pargs->source.ptr = p;
		  pargs->phase = ch;
		}
	    }
	if ( hpgl_arg_c_int(pgls->memory, pargs, &mode) && (mode & ~1) )
	  return e_Range;
	pgls->g.label.terminator = ch;
	pgls->g.label.print_terminator = !mode;
	return 0;
}

/* DV [path[,line]]; */
 int
hpgl_DV(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	int path = 0, line = 0;

	hpgl_arg_c_int(pgls->memory, pargs, &path);
	hpgl_arg_c_int(pgls->memory, pargs, &line);
	if ( (path & ~3) | (line & ~1) )
	  return e_Range;
	pgls->g.character.text_path = path;
	pgls->g.character.line_feed_direction = (line ? -1 : 1);
	hpgl_call(hpgl_update_carriage_return_pos(pgls));
	return 0;
}

/* ES [width[,height]]; */
 int
hpgl_ES(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	hpgl_real_t width = 0, height = 0;

	hpgl_arg_c_real(pgls->memory, pargs, &width);
	hpgl_arg_c_real(pgls->memory, pargs, &height);
	pgls->g.character.extra_space.x = width;
	pgls->g.character.extra_space.y = height;
	return 0;
}

/* FI fontid; */
 int
hpgl_FI(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	
	return hpgl_select_font_by_id(pargs, pgls, 0);
}

/* FN fontid; */
 int
hpgl_FN(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	
	return hpgl_select_font_by_id(pargs, pgls, 1);
}

/* The following is an extension documented in the Comparison Guide. */
/* LM [mode[, row number]]; */
 int
hpgl_LM(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	int mode = 0, row_number = 0;
	int old_mode =
	  (pgls->g.label.double_byte ? 1 : 0) +
	  (pgls->g.label.write_vertical ? 2 : 0);

	hpgl_arg_c_int(pgls->memory, pargs, &mode);
	hpgl_arg_c_int(pgls->memory, pargs, &row_number);
	pgls->g.label.row_offset =
	  (row_number < 0 ? 0 : row_number > 255 ? 255 : row_number) << 8;
	mode &= 3;
	pgls->g.label.double_byte = (mode & 1) != 0;
	pgls->g.label.write_vertical = (mode & 2) != 0;
	/*
	 * The documentation says "When LM switches modes, it turns off
	 * symbol mode."  We take this literally: LM only turns off
	 * symbol mode if the new label mode differs from the old one.
	 */
	if ( mode != old_mode )
	  pgls->g.symbol_mode = 0;
	return 0;
}

/* LO [origin]; */
 int
hpgl_LO(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	int origin = 1;

	hpgl_arg_c_int(pgls->memory, pargs, &origin);
	if ( origin < 1 || origin == 10 || origin == 20 || origin > 21 )
	  return e_Range;
	pgls->g.label.origin = origin;
	hpgl_call(hpgl_update_carriage_return_pos(pgls));
	return 0;
}

/* SA; */
 int
hpgl_SA(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	
	return hpgl_select_font(pgls, 1);
}

/* SB [mode]; */
 int
hpgl_SB(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	int mode = 0;

	if ( hpgl_arg_c_int(pgls->memory, pargs, &mode) && (mode & ~1) )
	  return e_Range;
	if ( pgls->g.bitmap_fonts_allowed != mode )
	  { int i;

	    pgls->g.bitmap_fonts_allowed = mode;
	    /*
	     * A different set of fonts is now available for consideration.
	     * Decache any affected font(s): those selected by parameter,
	     * and bitmap fonts selected by ID if bitmap fonts are now
	     * disallowed.
	     */
	    for ( i = 0; i < countof(pgls->g.font_selection); ++i )
	      { pcl_font_selection_t *pfs = &pgls->g.font_selection[i];
	        if ( ((int)pfs->selected_id < 0) ||
		     (!mode && pfs->font != 0 &&
		      pfs->font->scaling_technology == plfst_bitmap)
		   )
		  { pfs->font = 0;
		    if ( i == pgls->g.font_selected )
		      pgls->g.font = 0;
		  }
	      }
	  }
	return 0;
}

/* SD [kind,value...]; */
 int
hpgl_SD(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	
	return hpgl_font_definition(pargs, pgls, 0);
}

/* SI [width,height]; */
 int
hpgl_SI(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	hpgl_real_t width_cm, height_cm;

	if ( hpgl_arg_c_real(pgls->memory, pargs, &width_cm) )
	  { if ( !hpgl_arg_c_real(pgls->memory, pargs, &height_cm) )
	      return e_Range;
	    pgls->g.character.size.x = mm_2_plu(width_cm * 10);
	    pgls->g.character.size.y = mm_2_plu(height_cm * 10);
	    pgls->g.character.size_mode = hpgl_size_absolute;
	  }
	else
	  pgls->g.character.size_mode = hpgl_size_not_set;
	return 0;
}

/* SL [slant]; */
 int
hpgl_SL(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	hpgl_real_t slant = 0;

	hpgl_arg_c_real(pgls->memory, pargs, &slant);
	pgls->g.character.slant = slant;
	return 0;
}

/* SR [width,height]; */
 int
hpgl_SR(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	hpgl_real_t width_pct, height_pct;

	if ( hpgl_arg_c_real(pgls->memory, pargs, &width_pct) )
	  { if ( !hpgl_arg_c_real(pgls->memory, pargs, &height_pct) )
	      return e_Range;
	    pgls->g.character.size.x = width_pct / 100;
	    pgls->g.character.size.y = height_pct / 100;
	  }
	else
	  { pgls->g.character.size.x = 0.0075;
	    pgls->g.character.size.y = 0.015;
	  }
	pgls->g.character.size_mode = hpgl_size_relative;
	return 0;
}

/* SS; */
 int
hpgl_SS(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	
	return hpgl_select_font(pgls, 0);
}

/* TD [mode]; */
 int
hpgl_TD(hpgl_args_t *pargs, hpgl_state_t *pgls)
{	int mode = 0;

	if ( hpgl_arg_c_int(pgls->memory, pargs, &mode) && (mode & ~1) )
	  return e_Range;
	pgls->g.transparent_data = mode;
	return 0;
}

/* Initialization */
private int
pgchar_do_registration(
    pcl_parser_state_t *pcl_parser_state,
    gs_memory_t *mem)
{		/* Register commands */
    DEFINE_HPGL_COMMANDS(mem)
	  HPGL_COMMAND('A', 'D', hpgl_AD, hpgl_cdf_pcl_rtl_both),		/* kind/value pairs */
	  HPGL_COMMAND('C', 'F', hpgl_CF, hpgl_cdf_pcl_rtl_both),
	  HPGL_COMMAND('D', 'I', hpgl_DI, hpgl_cdf_pcl_rtl_both),
	  HPGL_COMMAND('D', 'R', hpgl_DR, hpgl_cdf_pcl_rtl_both),
	  /* DT has special argument parsing, so it must handle skipping */
	  /* in polygon mode itself. */
	  HPGL_COMMAND('D', 'T', hpgl_DT, hpgl_cdf_polygon|hpgl_cdf_pcl_rtl_both),
	  HPGL_COMMAND('D', 'V', hpgl_DV, hpgl_cdf_pcl_rtl_both),
	  HPGL_COMMAND('E', 'S', hpgl_ES, hpgl_cdf_pcl_rtl_both),
	  HPGL_COMMAND('F', 'I', hpgl_FI, hpgl_cdf_pcl),
	  HPGL_COMMAND('F', 'N', hpgl_FN, hpgl_cdf_pcl),
	  HPGL_COMMAND('L', 'M', hpgl_LM, hpgl_cdf_pcl_rtl_both),
	  HPGL_COMMAND('L', 'O', hpgl_LO, hpgl_cdf_pcl_rtl_both),
	  HPGL_COMMAND('S', 'A', hpgl_SA, hpgl_cdf_pcl_rtl_both),
	  HPGL_COMMAND('S', 'B', hpgl_SB, hpgl_cdf_pcl),
	  HPGL_COMMAND('S', 'D', hpgl_SD, hpgl_cdf_pcl_rtl_both),		/* kind/value pairs */
	  HPGL_COMMAND('S', 'I', hpgl_SI, hpgl_cdf_pcl_rtl_both),
	  HPGL_COMMAND('S', 'L', hpgl_SL, hpgl_cdf_pcl_rtl_both),
	  HPGL_COMMAND('S', 'R', hpgl_SR, hpgl_cdf_pcl_rtl_both),
	  HPGL_COMMAND('S', 'S', hpgl_SS, hpgl_cdf_pcl_rtl_both),
	  HPGL_COMMAND('T', 'D', hpgl_TD, hpgl_cdf_pcl_rtl_both),
	END_HPGL_COMMANDS
	return 0;
}
const pcl_init_t pgchar_init = {
  pgchar_do_registration, 0
};
