/*
 * This module will only work on platforms having 8-bit "char"s.
 * This requirement is tested at runtime by this module.
 */

/*LINTLIBRARY*/

/* Non-POSIX includes: */
#include <sys/types.h>		/* for uid_t */

/* POSIX includes: */
#include "udposix.h"
#include <stdio.h>		/* for *printf(), ftell(), & fseek() */
#include <stddef.h>		/* for offsetof() */
#include <stdlib.h>
#include <time.h>		/* for time(), localtime(), gmtime(), & 
				 * strftime() */
#include <sys/utsname.h>	/* for uname() */
#include <unistd.h>		/* for getlogin() */
#include <string.h>
#include <math.h>		/* for sqrt() */
#include <assert.h>
#include <limits.h>		/* for CHAR_BIT */
#include "gks_implem.h"
#include "cgm.h"

#undef MIN
#define MIN(a,b)	((a) < (b) ? (a) : (b))
#define ABS(x)		((x) < 0 ? -(x) : (x))
#define SIGN(a,b)	((a) < 0 ? -ABS(b) : ABS(b))

/*
 * Convert between a floating-point representation in the range from
 * zero through one (or minus one through one) and an integral representation.
 */
#define NORM_TO_INT(mod, fval)	SIGN(fval, ((long)(ABS(fval)*((mod)-1) + .5)))
#define UNORM_TO_INT(mod, fval)	MIN((mod)-1, (unsigned long)((fval)*(mod)))
#define INT_TO_NORM(mod, ival)	((double)(ival)/((mod)-1))
#define UINT_TO_NORM(mod, ival)	(.5/(mod) + (double)(ival)/(mod))
#define UNORM_TO_INT8(fval)	UNORM_TO_INT(256, fval)
#define NORM_TO_INT16(fval)	NORM_TO_INT((unsigned)(1<<15), fval)
#define UNORM_TO_INT16(fval)	UNORM_TO_INT((unsigned long)(1<<16), fval)
#define UINT8_TO_NORM(ival)	UINT_TO_NORM(256, ival)
#define UINT16_TO_NORM(ival)	UINT_TO_NORM((unsigned long)(1<<16), ival)

#ifndef lint
    static char afsid[]	= "$__Header$";
    static char rcsid[]	= "$Id: cgm.c,v 2.6 2000/08/01 16:38:20 steve Exp $";
#endif

/*
 * Convert between host and network byte orders for 16-bit quantities.
 */
#define NTOH16(n)	(((n)[0] << 8) | (n)[1])
#define HTON16(h, n)	GKS_STMT((n)[0] = MS_8(h); (n)[1] = LS_8(h);)

/*
 * Miscellaneous utility macros:
 */
#define	ABS(x)		((x) < 0 ? -(x) : (x))
#define HYPOT(x,y)	sqrt((double)((x)*(x) + (y)*(y)))
#define NUM_ELEMENTS(a)	(sizeof(a) / sizeof(a[0]))
#define MSB_16		(1 << 15)	/* 16-bit most significant bit */
#define MASK_16		0177777		/* 16-bit mask */
#define LS_8(val)	((val) & 0377)	/* least significant 8 bits */
#define MS_8(val)	LS_8((val) >> 8)/* most significant 8 bits */
#define ROUNDUP(x, y)	(((x + y - 1)/y)*y)
#define JUST_AFTER(ptr, type, align) \
			(align *)((char*)ptr + \
			    ROUNDUP(sizeof(type), sizeof(align)))

/*
 * Macros for converting between the host and two's-complement forms of a
 * 16-bit integer:
 */
#define HTOTS(s_shrt)	(MASK_16 & (unsigned short)(s_shrt))

#define TTOHS(u_shrt)	((u_shrt) & MSB_16 \
			    ? (long)0-(unsigned short) \
				(MASK_16 & (~(unsigned)(u_shrt) + 1)) \
			    : (short)(u_shrt))

/*
 * Miscellaneous, CGM-specific macros:
 */
#define MAX_SHORT_CMD_LENGTH	30	/* Max bytes in a short command */
#define LONG_CMD_LENGTH		31	/* Long command "length"-value */
#define MAX_SHORT_STR_LENGTH	254	/* Max bytes in a short string */
#define LONG_STR_LENGTH		255	/* Long string "length"-value */
#define MORE_DATA_BIT		(1 << 15)
#define BYTES_LEFT(mi)		(mi->total_left)
#define HASH_ID(class, id)	(((unsigned)(class) << 7) | (unsigned)(id))
#define	PACKED_LIST		1	/* Cell representation mode. */
#define CGM_CONTINUOUS_HORIZONTAL_ALIGNMENT \
				4
#define CGM_CONTINUOUS_VERTICAL_ALIGNMENT \
				6

/*
 * Defaults:
 */
#define DEFAULT_COLRPREC	8	/* Color precision */
#define DEFAULT_CELL_REP_MODE	PACKED_LIST
#define DEFAULT_VDCINTEGERPREC	16	/* VDC integer precision */
#define DEFAULT_COLRMODE	0	/* Indexed */

/*
 * Command class values:
 */
#define DELIMITER_CL		0
#define MF_DESCRIPTOR_CL	1
#define PIC_DESCRIPTOR_CL	2
#define CONTROL_CL		3
#define PRIMITIVE_CL		4
#define ATTRIBUTE_CL		5
#define ESCAPE_CL		6
#define EXTERN_CL		7

/*
 * Element-ID values.  Together with the command-class, these uniquely 
 * identify an individual command.  The names are taken from the CLEAR
 * TEXT encoding (with "_ID" appended).
 */
/* Delimiter Elements: */
#define BEGMF_ID		1
#define ENDMF_ID		2
#define BEGPIC_ID		3
#define BEGPICBODY_ID		4
#define ENDPIC_ID		5

/* Metafile Descriptor Elements: */
#define MFVERSION_ID		1
#define MFDESC_ID		2
#define MFELEMLIST_ID		11

/* Picture Descriptor Elements: */
#define COLRMODE_ID		2
#define VDCEXT_ID		6
#define BACKCOLR_ID		7

/* Control Elements: */
#define VDCINTEGERPREC_ID	1
#define CLIPRECT_ID		5
#define CLIP_ID			6

/* Graphical Primitive Elements: */
#define LINE_ID			1
#define MARKER_ID		3
#define TEXT_ID			4
#define POLYGON_ID		7
#define CELLARRAY_ID		9

/* Primitive Attribute Elements: */
#define LINEINDEX_ID		1
#define LINETYPE_ID		2
#define LINEWIDTH_ID		3
#define LINECOLR_ID		4
#define MARKERINDEX_ID		5
#define MARKERTYPE_ID		6
#define MARKERSIZE_ID		7
#define MARKERCOLR_ID		8
#define TEXTINDEX_ID		9
#define TEXTFONTINDEX_ID	10
#define TEXTPREC_ID		11
#define CHAREXPAN_ID		12
#define CHARSPACE_ID		13
#define TEXTCOLR_ID		14
#define CHARHEIGHT_ID		15
#define CHARORI_ID		16
#define TEXTPATH_ID		17
#define TEXTALIGN_ID		18
#define CHARSETINDEX_ID		19
#define ALTCHARSETINDEX_ID	20
#define FILLINDEX_ID		21
#define INTSTYLE_ID		22
#define FILLCOLR_ID		23
#define HATCHINDEX_ID		24
#define PATINDEX_ID		25
#define FILLREFPT_ID		31
#define PATTABLE_ID		32
#define PATSIZE_ID		33
#define COLRTABLE_ID		34
#define ASF_ID			35

/* Escape Elements: */
#define ESCAPE_ID		1

/* External Elements: */
#define MESSAGE_ID		1

/*
 * Internal functions that can be mapped to externally visible ones:
 */
#define mo_cliprect(cgmo, num)	(void) CGMsetClip((Metafile**)cgmo, num, \
						  (Glimit*)NULL)
#define mo_patsize(cgmo, num)	(void) CGMsetPatSize((Metafile**)cgmo, num)
#define mo_fillrefpt(cgmo, num)	(void) CGMsetPatRefpt((Metafile**)cgmo, num)
#define mo_asf(cgmo, num)	(void) CGMsetAsf((Metafile**)cgmo, num)

/*
 * CGM output-routines that are simple enough to be implemented as macros:
 */
#define mo_backcolr(cgmo, num)	\
    mo_element(cgmo, num, CGM_MASK_BACKCOLR, PIC_DESCRIPTOR_CL, BACKCOLR_ID, \
		mo_direct_colors, offsetof(mf_cgmo, backcolr), 1)
#define mo_lineindex(cgmo, num)	\
    mo_element(cgmo, num, CGM_MASK_LINEINDEX, ATTRIBUTE_CL, LINEINDEX_ID, \
		mo_indexes, offsetof(mf_cgmo, lineindex), 1)
#define mo_linetype(cgmo, num) \
    mo_element(cgmo, num, CGM_MASK_LINETYPE, ATTRIBUTE_CL, LINETYPE_ID, \
		mo_indexes, offsetof(mf_cgmo, linetype), 1)
#define mo_linewidth(cgmo, num)	\
    mo_element(cgmo, num, CGM_MASK_LINEWIDTH, ATTRIBUTE_CL, LINEWIDTH_ID, \
		mo_reals, offsetof(mf_cgmo, linewidth), 1)
#define mo_linecolr(cgmo, num) \
    mo_element(cgmo, num, CGM_MASK_LINECOLR, ATTRIBUTE_CL, LINECOLR_ID, \
		mo_indexed_colors, offsetof(mf_cgmo, linecolr), 1)
#define mo_markerindex(cgmo, num) \
    mo_element(cgmo, num, CGM_MASK_MARKERINDEX, ATTRIBUTE_CL, MARKERINDEX_ID, \
		mo_indexes, offsetof(mf_cgmo, markerindex), 1)
#define mo_markertype(cgmo, num) \
    mo_element(cgmo, num, CGM_MASK_MARKERTYPE, ATTRIBUTE_CL, MARKERTYPE_ID, \
		mo_indexes, offsetof(mf_cgmo, markertype), 1)
#define mo_markersize(cgmo, num) \
    mo_element(cgmo, num, CGM_MASK_MARKERSIZE, ATTRIBUTE_CL, MARKERSIZE_ID, \
		mo_reals, offsetof(mf_cgmo, markersize), 1)
#define mo_markercolr(cgmo, num) \
    mo_element(cgmo, num, CGM_MASK_MARKERCOLR, ATTRIBUTE_CL, MARKERCOLR_ID, \
		mo_indexed_colors, offsetof(mf_cgmo, markercolr), 1)
#define mo_textindex(cgmo, num) \
    mo_element(cgmo, num, CGM_MASK_TEXTINDEX, ATTRIBUTE_CL, TEXTINDEX_ID, \
		mo_indexes, offsetof(mf_cgmo, textindex), 1)
#define mo_textfontindex(cgmo, num) \
    mo_element(cgmo, num, CGM_MASK_TEXTFONTINDEX, ATTRIBUTE_CL, \
		TEXTFONTINDEX_ID, mo_indexes, offsetof(mf_cgmo, txfp.font), 1)
#define mo_textprec(cgmo, num)	\
    mo_element(cgmo, num, CGM_MASK_TEXTPREC, ATTRIBUTE_CL, TEXTPREC_ID, \
		mo_enums, offsetof(mf_cgmo, txfp.prec), 1)
#define mo_charexpan(cgmo, num)	\
    mo_element(cgmo, num, CGM_MASK_CHAREXPAN, ATTRIBUTE_CL, CHAREXPAN_ID, \
		mo_reals, offsetof(mf_cgmo, charexpan), 1)
#define mo_charspace(cgmo, num)	\
    mo_element(cgmo, num, CGM_MASK_CHARSPACE, ATTRIBUTE_CL, CHARSPACE_ID, \
		mo_reals, offsetof(mf_cgmo, charspace), 1)
#define mo_textcolr(cgmo, num) \
    mo_element(cgmo, num, CGM_MASK_TEXTCOLR, ATTRIBUTE_CL, TEXTCOLR_ID, \
		mo_indexed_colors, offsetof(mf_cgmo, textcolr), 1)
#define mo_charheight(cgmo, num) \
    mo_element(cgmo, num, CGM_MASK_CHARHEIGHT, ATTRIBUTE_CL, CHARHEIGHT_ID, \
		mo_vdcs, offsetof(mf_cgmo, charheight), 1)
#define mo_charori(cgmo, num) \
    mo_element(cgmo, num, CGM_MASK_CHARORI, ATTRIBUTE_CL, CHARORI_ID, \
		mo_vdcs, offsetof(mf_cgmo, charori[0]), 4)
#define mo_textpath(cgmo, num) \
    mo_element(cgmo, num, CGM_MASK_TEXTPATH, ATTRIBUTE_CL, TEXTPATH_ID, \
		mo_enums, offsetof(mf_cgmo, textpath), 1)
#define mo_fillindex(cgmo, num)	\
    mo_element(cgmo, num, CGM_MASK_FILLINDEX, ATTRIBUTE_CL, FILLINDEX_ID, \
		mo_indexes, offsetof(mf_cgmo, fillindex), 1)
#define mo_intstyle(cgmo, num)	\
    mo_element(cgmo, num, CGM_MASK_INTSTYLE, ATTRIBUTE_CL, INTSTYLE_ID, \
		mo_enums, offsetof(mf_cgmo, intstyle), 1)
#define mo_fillcolr(cgmo, num)	\
    mo_element(cgmo, num, CGM_MASK_FILLCOLR, ATTRIBUTE_CL, FILLCOLR_ID, \
		mo_indexed_colors, offsetof(mf_cgmo, fillcolr), 1)
#define mo_hatchindex(cgmo, num) \
    mo_element(cgmo, num, CGM_MASK_HATCHINDEX, ATTRIBUTE_CL, HATCHINDEX_ID, \
		mo_indexes, offsetof(mf_cgmo, hatchindex), 1)
#define mo_patindex(cgmo, num)	\
    mo_element(cgmo, num, CGM_MASK_PATINDEX, ATTRIBUTE_CL, PATINDEX_ID, \
		mo_indexes, offsetof(mf_cgmo, patindex), 1)

#define MO_BEGMF(cgmo, num)		GKS_STMT( \
	mo_header(DELIMITER_CL, BEGMF_ID); \
	mo_string(cgmo, num, "XGKS CGM"); \
	mo_flush(cgmo, num, 0); \
    )
#define MO_MFVERSION(cgmo, num)	GKS_STMT( \
	mo_header(MF_DESCRIPTOR_CL, MFVERSION_ID); \
	mo_int(cgmo, num, mfversion); \
	mo_flush(cgmo, num, 0); \
    )
#define MO_MFELEMLIST(cgmo, num)	GKS_STMT( \
	mo_header(MF_DESCRIPTOR_CL, MFELEMLIST_ID); \
	mo_int(cgmo, num, num_elements); \
	mo_ints(cgmo, num, (char*)mf_elements_list, 2*num_elements); \
	mo_flush(cgmo, num, 0); \
    )
#define MO_BEGPICBODY(cgmo, num)	GKS_STMT( \
	mo_header(DELIMITER_CL, BEGPICBODY_ID); \
	mo_flush(cgmo, num, 0); \
	mo_mode(cgmo, num, CGMO_IN_BODY); \
    )
#define MO_ENDPIC(cgmo, num)		GKS_STMT( \
	mo_header(DELIMITER_CL, ENDPIC_ID); \
	mo_flush(cgmo, num, 0); \
	mo_mode(cgmo, num, CGMO_IN_METAFILE); \
    )
#define MO_ENDMF(cgmo, num)		GKS_STMT( \
	mo_header(DELIMITER_CL, ENDMF_ID); \
	mo_flush(cgmo, num, 0); \
	mo_mode(cgmo, num, CGMO_UNSET); \
    )

/*
 * CGM element decoding mode:
 */
typedef enum decode_mode {
    RETURN_INFO,
    DECODE_VALUES
} decode_mode;

/*
 * Metafile element-list (for the METAFILE DESCRIPTION command):
 */
#define DRAWING_SET_CL	-1
#define DRAWING_SET_ID	 0
static int	mf_elements_list[][2]	= {DRAWING_SET_CL, DRAWING_SET_ID};
static int	num_elements		= NUM_ELEMENTS(mf_elements_list);

/*
 * Mapping from CGM ASF indexes to GKSM ASF indexes:
 *
 * NB: Only the first 13 are meaningful.  The last 3 are mapped to an unused
 * ASF slot.
 */
static int	gksm_iasf[18]	= {
   0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10, 12, 11, 11, 13, 13, 13
};

/*
 * CGM version:
 */
static int	mfversion	= 1;

/*
 * CGM package state variables:
 */
static int		debug;
static int		header_class;	/* Item class */
static int		header_id;	/* Item identifier */
static int		header_written;	/* Command-header has been written? */
static unsigned char	mo_buf[077776];	/* Parameter buffer */
static unsigned char	*mo_ptr	= mo_buf;
					/* Next available byte in buffer */
static unsigned char	*mo_outp	= mo_buf + sizeof(mo_buf);
					/* End-of-buffer pointer sentinel */


/*
 * Save header information.
 */
#define mo_header(class, id)	GKS_STMT( \
	assert(!header_written); \
	header_class	= class; \
	header_id	= id; \
    )


    static double
int16_to_norm(ival)
    int		ival;
{
    return INT_TO_NORM((unsigned)(1<<15), ival);
}


/*
 * Flush the (complete or partial) CGM-element parameter-buffer.
 */
    static void
mo_flush(cgmo, num, more_data)
    mf_cgmo	**cgmo;
    int		num;
    int		more_data;
{
    if (num > 0) {
	int		imf;
	int		pad;
	int		nbytes	= mo_ptr - mo_buf;
	static int	long_form;

	if (!header_written) {
	    unsigned short	header;
	    unsigned char	bytes[2];

	    if (debug)
		(void) fprintf(stderr, 
			       "mo_flush(): class=%d, id=%d, nbytes=%d\n",
			       header_class, header_id, nbytes);

	    long_form	= nbytes > MAX_SHORT_CMD_LENGTH;
	    header	= header_class << 12
			     | header_id << 5 
			     | (long_form
				    ? LONG_CMD_LENGTH
				    : nbytes);
	    HTON16(header, bytes);

	    for (imf = 0; imf < num; ++imf)
		(void) fwrite((voidp)bytes, (size_t)1, (size_t)2, 
			      cgmo[imf]->fp);
	    header_written	= 1;
	}

	if (long_form) {
	    /*
	     * Long-form command.  Write a partition control word.
	     */
	    unsigned short	pcw	= more_data
						? MORE_DATA_BIT | nbytes 
						: nbytes;
	    unsigned char	bytes[2];

	    HTON16(pcw, bytes);

	    for (imf = 0; imf < num; ++imf)
		(void) fwrite((voidp)bytes, (size_t)1, (size_t)2, 
			      cgmo[imf]->fp);
	}

	/* Pad the data if necessary */
	pad	= nbytes % 2;
	if (pad) {
	    assert(!more_data);		/* padding shall only occur at end of
					 * command element and not at the end
					 * of a non-terminal data-partition 
					 * (except for cell arrays, but that's
					 * taken care of before here) */
	    *mo_ptr	= 0;
	}

	/* Write the data. */
	for (imf = 0; imf < num; ++imf)
	    (void) fwrite((voidp)mo_buf, (size_t)1, (size_t)(nbytes+pad), 
			  cgmo[imf]->fp);

	/* Reset the buffer pointer */
	mo_ptr	= mo_buf;

	/* Reset the element state if appropriate */
	if (!more_data)
	    header_written	= 0;
    }
}

#define size_header()		(size_t)2


/*
 * Write a parameter octet.
 */
#define mo_octet(cgmo, num, byte)	GKS_STMT( \
	*mo_ptr++	= byte; \
	if (mo_ptr == mo_outp) \
	    mo_flush(cgmo, num, 1); \
    )


/*
 * Write parameter octets.
 */
    static void
mo_octets(cgmo, num, bytes, nbytes)
    mf_cgmo		**cgmo;
    int			num;
    unsigned char	*bytes;
    int			nbytes;
{
    while (nbytes--)
	mo_octet(cgmo, num, *bytes++);
}


/*
 * Write a 16-bit, integer parameter.
 */
#define mo_int16(cgmo, num, val)	GKS_STMT( \
	unsigned short 	value	= val; \
	unsigned char	bytes[2]; \
	HTON16(value, bytes); \
	mo_octets(cgmo, num, bytes, 2); \
    )

#define size_int16()		(size_t)2


/*
 * Write 16-bit, integer parameters.
 */
    static void
mo_int16s(cgmo, num, ptr, nval)
    mf_cgmo	**cgmo;
    int		num;
    char	*ptr;
    int		nval;
{
    int		*vals	= (int*)ptr;

    assert(nval >= 0);

    while (nval-- > 0)
	mo_int16(cgmo, num, *vals++);
}


/*
 * Write an integer parameter.
 */
#define mo_int(mo, n, val) 	mo_int16(mo, n, val)

#define size_int()		size_int16()


/*
 * Write integer parameters.
 */
    static void
mo_ints(cgmo, num, ptr, nval)
    mf_cgmo	**cgmo;
    int		num;
    char	*ptr;
    int		nval;
{
    int		*vals	= (int*)ptr;

    assert(nval >= 0);

    while (nval-- > 0)
	mo_int(cgmo, num, *vals++);
}


/*
 * Write a 16-bit, unsigned integer parameter.
 */
#define mo_uint16(cgmo, num, val)	GKS_STMT( \
	unsigned short 	value	= val; \
	unsigned char	bytes[2]; \
	HTON16(value, bytes); \
	mo_octets(cgmo, num, bytes, 2); \
    )


/*
 * Write an index parameter.
 */
#define mo_index(mo, n, val)	mo_int16(mo, n, val)

#define size_index()		size_int()


/*
 * Write index parameters.
 */
    static void
mo_indexes(cgmo, num, ptr, nval)
    mf_cgmo	**cgmo;
    int		num;
    char	*ptr;
    int		nval;
{
    int		*vals	= (int*)ptr;

    assert(nval >= 0);

    while (nval-- > 0)
	mo_index(cgmo, num, *vals++);
}


/*
 * Write an ennumeration parameter.
 */
#define mo_enum(mo, n, val)	mo_int16(mo, n, val)

#define mo_enums		mo_int16s

#define size_enum()		size_int16()


/*
 * Write a string parameter.
 */
    static void
mo_string(cgmo, num, string)
    mf_cgmo	**cgmo;
    int		num;
    char	*string;
{
    size_t	nchr	= strlen(string);

    if (nchr <= MAX_SHORT_STR_LENGTH) {
	mo_octet(cgmo, num, nchr);
	mo_octets(cgmo, num, (unsigned char*)string, nchr);
    } else {
	unsigned	length;

	mo_octet(cgmo, num, LONG_STR_LENGTH);

	while (nchr > 0) {
	    length	= MIN(32767, nchr);
	    nchr	-= length;
	    if (nchr > 0)
		length	|= MSB_16;
	    mo_uint16(cgmo, num, length);
	    mo_octets(cgmo, num, (unsigned char*)string, length);
	    string	+= length;
	}
    }
}


/*
 * Write a 32-bit, fixed-point, real parameter.
 */
#define mo_real32fx(cgmo, num, val)		GKS_STMT( \
	mf_cgmo		**mo	= cgmo; \
	int		nmo	= num; \
	double		x	= val; \
	short		swhole	= x < 0 ? x - 1 : x; \
	unsigned short	whole	= HTOTS(swhole); \
	unsigned short	frac	= UNORM_TO_INT16(x-swhole); \
	unsigned char	bytes[2]; \
	HTON16(whole, bytes); \
	mo_octets(mo, nmo, bytes, 2); \
	HTON16(frac, bytes); \
	mo_octets(mo, nmo, bytes, 2); \
	assert(x-swhole >= 0); \
	assert(x-swhole <= 1); \
    )

#define size_real32fx()		(size_t)4


/*
 * Write a real parameter.
 */
#define mo_real(mo, n, val)	mo_real32fx(mo, n, val)

#define size_real()		size_real32fx()


/*
 * Write real parameters.
 */
    static void
mo_reals(cgmo, num, ptr, nval)
    mf_cgmo	**cgmo;
    int		num;
    char	*ptr;
    int		nval;
{
    float	*vals	= (float*)ptr;

    assert(nval >= 0);

    while (nval-- > 0)
	mo_real(cgmo, num, *vals++);
}


/*
 * Write a VDC parameter.
 */
#define MO_VDC(cgmo, num, val)	GKS_STMT( \
	int	x	= NORM_TO_INT16(val); \
	assert(val >= -1); \
	assert(val <= 1); \
	mo_int16(cgmo, num, x); \
    )

#define size_vdc()	size_int16()


/*
 * Write VDC parameters.
 */
    static void
mo_vdcs(cgmo, num, ptr, nval)
    mf_cgmo	**cgmo;
    int		num;
    char	*ptr;
    int		nval;
{
    Gfloat	*vals	= (Gfloat*)ptr;

    assert(size_vdc() == size_int());

    while (nval-- > 0) {
	Gfloat	val	= *vals++;

	MO_VDC(cgmo, num, val);
    }
}


/*
 * Write a point.
 */
#define MO_POINT(cgmo, num, ptr)	GKS_STMT( \
	MO_VDC(cgmo, num, (ptr)->x); \
	MO_VDC(cgmo, num, (ptr)->y); \
    )

#define size_point()		(2*size_vdc())


/*
 * Write point parameters.
 */
    static void
mo_points(cgmo, num, ptr, nval)
    mf_cgmo	**cgmo;
    int		num;
    char	*ptr;
    int		nval;
{
    Gpoint	*vals	= (Gpoint*)ptr;

    while (nval-- > 0) {
	Gpoint	*pt	= vals++;

	MO_POINT(cgmo, num, pt);
    }
}


/*
 * Write a direct color parameter.
 *
 * NB: The default direct color precision is used.
 */
#define mo_direct_color(cgmo, num, ptr)		GKS_STMT( \
	Gcobundl	*colr	= ptr; \
	unsigned char	rep[3]; \
	assert(colr->red >= 0); \
	assert(colr->red <= 1); \
	assert(colr->green >= 0); \
	assert(colr->green <= 1); \
	assert(colr->blue >= 0); \
	assert(colr->blue <= 1); \
	rep[0]	= UNORM_TO_INT8(colr->red); \
	rep[1]	= UNORM_TO_INT8(colr->green); \
	rep[2]	= UNORM_TO_INT8(colr->blue); \
	mo_octets(cgmo, num, rep, 3); \
    )

#define size_direct_color()	(size_t)3


/*
 * Write direct color parameters.
 *
 * NB: The default direct color precision is used.
 */
    static void
mo_direct_colors(cgmo, num, ptr, ncolor)
    mf_cgmo	**cgmo;
    int		num;
    char	*ptr;
    int		ncolor;
{
    Gcobundl	*colors	= (Gcobundl*)ptr;

    while (ncolor-- > 0)
	mo_direct_color(cgmo, num, colors++);
}


/*
 * Write an indexed-color parameter.
 */
#define mo_indexed_color(cgmo, num, val)	GKS_STMT( \
	mo_octet(cgmo, num, val); \
    )

#define size_indexed_color()	(size_t)1


/*
 * Write indexed-color parameters.
 */
    static void
mo_indexed_colors(cgmo, num, ptr, ncolor)
    mf_cgmo	**cgmo;
    int		num;
    char	*ptr;
    int		ncolor;
{
    int		*colors	= (int*)ptr;

    while (ncolor-- > 0)
	mo_indexed_color(cgmo, num, *colors++);
}


/*
 * Write a row of a cell-array to output CGM elements.
 */
    static void
mo_cellrow(cgmo, num, color, ncolor)
    mf_cgmo	**cgmo;
    int		num;
    Gint	*color;
    int		ncolor;
{
    int			pad	= ncolor % 2;

    assert(DEFAULT_COLRPREC == 8);

    while (ncolor-- > 0) {
	assert(*color <= 255);
	mo_octet(cgmo, num, *color++);
    }

    if (pad)
	mo_octet(cgmo, num, 0);
}

#define size_cell()		(size_t)(DEFAULT_COLRPREC/8)


/*
 * Write an element whose parameters are all the same.
 */
    static void
mo_element(cgmo, num, setmask, class, id, func, offset, nval)
    mf_cgmo		**cgmo;
    int			num;
    unsigned long	setmask;
    int			class;
    int			id;
    void		(*func)();
    unsigned		offset;
    int			nval;
{
    if (setmask != 0) {
	int	ii;

	for (ii = 0; ii < num; ++ii) {
	    if (cgmo[ii]->isset & setmask) {
		mo_header(class, id);
		(*func)(cgmo+ii, 1, (char*)cgmo[ii]+offset, nval);
		mo_flush(cgmo+ii, 1, 0);
	    }
	}
    } else {
	mo_header(class, id);
	(*func)(cgmo, num, (char*)cgmo[0]+offset, nval);
	mo_flush(cgmo, num, 0);
    }
}


/*
 * Return a string identifying the user, installation, and date.
 */
    static char*
AuthorDate()
{
    int			min_offset;
    char		*username	= getlogin();
    char		timebuf[24];
    static char		buffer[128];
    char		*cp		= buffer;
    char		*endp		= buffer + sizeof(buffer) - 1;
    time_t		clock		= time((time_t *) NULL);
    struct tm		local_tm;
    struct tm		utc_tm;
    struct utsname	name;

#   define ADD_STRING(string)	GKS_STMT( \
	    size_t	nchr	= strlen(string); \
	    nchr	= MIN(endp - cp, nchr); \
	    (void) strncpy(cp, string, nchr); \
	    cp	+= nchr; \
	)

    ADD_STRING("UCAR/Unidata XGKS/CGM $Revision: 2.6 $: ");

    if (username == NULL) {
	ADD_STRING("<unknown>");
    } else {
	ADD_STRING(username);
    }

    if (uname(&name) != -1) {
	ADD_STRING("@");
	ADD_STRING(name.nodename);
    }

    ADD_STRING(" ");

    local_tm	= *localtime(&clock);
    utc_tm	= *gmtime(&clock);

    (void) strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", 
		    &local_tm);
    ADD_STRING(timebuf);

    min_offset	= (local_tm.tm_hour - utc_tm.tm_hour)*60 +
		  (local_tm.tm_min  - utc_tm.tm_min);

    if (local_tm.tm_year > utc_tm.tm_year || 
	    local_tm.tm_yday > utc_tm.tm_yday) {
	min_offset	+= 24*60;
    } else if (local_tm.tm_year < utc_tm.tm_year || 
	    local_tm.tm_yday < utc_tm.tm_yday) {
	min_offset	-= 24*60;
    }

    if (min_offset != 0) {
	int	hour	= ABS(min_offset/60);
	int	min	= ABS(min_offset%60);

	(void) sprintf(timebuf, " UTC%c%02d%02d", 
		       min_offset < 0 ? '-' : '+',
		       hour,
		       min);

	ADD_STRING(timebuf);
    }

    *cp	= 0;

    return buffer;
}


/*
 * Set the mode of output CGMs.
 */
    static void
mo_mode(cgmo, num, mode)
    mf_cgmo	**cgmo;
    int		num;
    cgmo_mode	mode;
{
    while (num-- > 0)
	(*cgmo++)->mode	= mode;
}


/*
 * Write the Metafile description.
 */
    static void
mo_mfdesc(cgmo, num)
    mf_cgmo	**cgmo;
    int		num;
{
    char	*desc	= AuthorDate();

    mo_header(MF_DESCRIPTOR_CL, MFDESC_ID);
    mo_string(cgmo, num, desc);
    mo_flush(cgmo, num, 0);
}


/*
 * Write the VDC extent.
 *
 * This is equivalent to the workstation window.
 */
    static void
mo_vdcext(cgmo, num)
    mf_cgmo	**cgmo;
    int		num;
{
    mo_header(PIC_DESCRIPTOR_CL, VDCEXT_ID);

    for (; num-- > 0; ++cgmo) {
	Gpoint	lower_left, upper_right;

	lower_left.x	= cgmo[0]->wswindow.xmin;
	lower_left.y	= cgmo[0]->wswindow.ymin;
	upper_right.x	= cgmo[0]->wswindow.xmax;
	upper_right.y	= cgmo[0]->wswindow.ymax;

	MO_POINT(cgmo, 1, &lower_left);
	MO_POINT(cgmo, 1, &upper_right);
	mo_flush(cgmo, 1, 0);
    }
}


/*
 * Write a BEGIN PICTURE delimiter.
 *
 * The identifier is taken from the picture number;
 */
    static void
mo_begpic(cgmo, num)
    mf_cgmo	**cgmo;
    int		num;
{
    int		ii;

    for (ii = 0; ii < num; ++ii) {
	char	identifier[16];			/* Should be large enough */

	(void) sprintf(identifier, "XGKS %d", ++cgmo[ii]->picture_number);
	mo_header(DELIMITER_CL, BEGPIC_ID);
	mo_string(cgmo+ii, 1, identifier);
	mo_flush(cgmo, num, 0);
    }

    mo_mode(cgmo, num, CGMO_IN_PICTURE);
}


/*
 * Write the current text alignment.
 */
    static void
mo_textalign(cgmo, num)
    mf_cgmo	**cgmo;
    int		num;
{
    while (num-- > 0) {
	if ((cgmo[num]->isset & CGM_MASK_TEXTALIGN) != 0) {
	    float	dummy	= 0;

	    /*
	     * Fix a silly bug in the SunGKS 3.0 CGM->PostScript translator 
	     * that causes it to mishandle text when there are changes to the 
	     * Text Alignment in a metacode input file.  This patch forces a 
	     * entry in the metacode file to set the text alignment to normal 
	     * before changing it to something else.  Thanks to Harry Edmond 
	     * <harry@atmos.washington.edu> for the fix.
	     */
	    if (cgmo[num]->textalign.hor != GTH_NORMAL 
		    || cgmo[num]->textalign.ver != GTV_NORMAL) {
		int	normal	= 0;

		mo_header(ATTRIBUTE_CL, TEXTALIGN_ID);
		mo_enum(cgmo+num, 1, normal);
		mo_enum(cgmo+num, 1, normal);
		mo_real(cgmo+num, 1, dummy);
		mo_real(cgmo+num, 1, dummy);
		mo_flush(cgmo+num, 1, 0);
	    }

	    mo_header(ATTRIBUTE_CL, TEXTALIGN_ID);

	    mo_enum(cgmo+num, 1, cgmo[num]->textalign.hor);
	    mo_enum(cgmo+num, 1, cgmo[num]->textalign.ver);

	    mo_real(cgmo+num, 1, dummy);
	    mo_real(cgmo+num, 1, dummy);
	    mo_flush(cgmo+num, 1, 0);
	}
    }
}


/*
 * Write the current color table.
 */
    static void
mo_colrtable(cgmo, num)
    mf_cgmo	**cgmo;
    int		num;
{
    int		ii;

    for (ii = 0; ii < num; ++ii) {
	if ((cgmo[ii]->isset & CGM_MASK_COLRTABLE) != 0) {
	    Gintlist       indices;

	    if (ginqcolourindices(cgmo[ii]->ws->ws_id, &indices) == OK) {
		int	ncolors	= indices.number;
		int	*idxp	= indices.integers;

		for (; ncolors-- > 0; ++idxp) {
		    Gcobundl	rep;

		    if (ginqcolourrep(cgmo[ii]->ws->ws_id, *idxp, 
				      GSET, &rep) == OK)
			(void) CGMsetColRep((Metafile**)cgmo+ii, 1, *idxp, 
					    &rep);
		}

		GKS_FREE(indices.integers);
	    }
	}
    }
}


/*
 * Write the current attribute elements.
 */
    static void
mo_attributes(cgmo, num)
    mf_cgmo	**cgmo;
    int		num;
{
    mo_lineindex(cgmo, num);
    mo_linetype(cgmo, num);
    mo_linewidth(cgmo, num);
    mo_linecolr(cgmo, num);
    mo_markerindex(cgmo, num);
    mo_markertype(cgmo, num);
    mo_markersize(cgmo, num);
    mo_markercolr(cgmo, num);
    mo_textindex(cgmo, num);
    mo_textfontindex(cgmo, num);
    mo_textprec(cgmo, num);
    mo_charexpan(cgmo, num);
    mo_charspace(cgmo, num);
    mo_textcolr(cgmo, num);
    mo_charheight(cgmo, num);
    mo_charori(cgmo, num);
    mo_textpath(cgmo, num);
    mo_textalign(cgmo, num);
    mo_fillindex(cgmo, num);
    mo_intstyle(cgmo, num);
    mo_fillcolr(cgmo, num);
    mo_hatchindex(cgmo, num);
    mo_patindex(cgmo, num);
    mo_fillrefpt(cgmo, num);
    mo_colrtable(cgmo, num);
    /*
    mo_pattable(cgmo, num);
     */
    mo_asf(cgmo, num);
}


/*
 * Write an item to output CGMs (not implemented yet).
 */
    int
CGMwriteItem(mf, num, type, length, data)
    /*ARGSUSED*/
    Metafile       **mf;	/* Metafile structure */
    int		    num;	/* number of output Metfiles */
    Gint	    type;	/* item type */
    Gint	    length;	/* item length */
    Gchar          *data;	/* item data-record */
{
    return OK;
}


/*
 * Open an output CGM.
 */
    int
CGMmoOpen(mf)
    Metafile	*mf;
{
    mf_cgmo	*cgmo	= &mf->cgm.mo;
    static char	me[]	= "CGMmoOpen()";

    if (CHAR_BIT != 8) {
	(void) fprintf(stderr, 
	    "%s: I can't work on platforms where CHAR_BIT != 8.  Sorry.\n",
	    me);
	return 1;
    }

    cgmo->mode			= CGMO_IN_METAFILE;
    cgmo->picture_number	= 0;
    cgmo->isset			= 0;

    MO_BEGMF(&cgmo, 1);				/* BEGIN METAFILE */
    MO_MFVERSION(&cgmo, 1);			/* METAFILE VERSION */
    MO_MFELEMLIST(&cgmo, 1);			/* METAFILE ELEMENT LIST */
    mo_mfdesc(&cgmo, 1);			/* METAFILE DESCRIPTION */
    mo_begpic(&cgmo, 1);			/* BEGIN PICTURE */
    MO_BEGPICBODY(&cgmo, 1);			/* BEGIN PICTURE BODY */

    return OK;
}


/*
 * Set the clear flag.
 *
 * According to Addendum E of the CGM standard, certain actions need only be
 * performed if the view surface is non-empty and the GKS functions imply a
 * dynamic change to the image.
 */
    int
CGMclear(mf, num, flag)
    Metafile	**mf;
    int		num;
    Gclrflag	flag;
{
    int		ii;

    for (ii = 0; ii < num; ++ii) {
	mf_cgmo	*cgmo	= &mf[ii]->cgm.mo;

#if 0
	if (flag == GCONDITIONALLY && cgmo->ws->wsdus.dspsurf == GNOTEMPTY
#endif
	if (flag == GCONDITIONALLY && cgmo->mode == CGMO_NOT_EMPTY
		|| flag == GALWAYS) {

	    MO_ENDPIC(&cgmo, 1);		/* END PICTURE */
	    mo_begpic(&cgmo, 1);		/* BEGIN PICTURE */
	    mo_vdcext(&cgmo, 1);		/* VDC EXTENT */
	    mo_backcolr(&cgmo, 1);		/* BACKGROUND COLOR */
	    MO_BEGPICBODY(&cgmo, 1);		/* BEGIN PICTURE BODY */
	    mo_attributes(&cgmo, 1);		/* attribute settings */
	    mo_cliprect(&cgmo, 1);		/* CLIP RECTANGLE */
	}
    }

    return OK;
}


/*
 * Redraw all segments in output CGMs.  
 *
 * Since this is a dynamic action, we don't do anything unless the view 
 * surface is non-empty -- in which case we clear the view surface and then 
 * call the XGKS workstation-redraw function.
 */
    int
CGMredrawAllSeg(mf, num)
    Metafile	**mf;
    int		num;
{
    int		ii;
    extern void	XgksDrawSegToWs PROTO((WS_STATE_PTR));

    for (ii = 0; ii < num; ++ii) {
	mf_cgmo	*cgmo	= &mf[ii]->cgm.mo;

	if (cgmo->mode == CGMO_NOT_EMPTY) {
	    CGMclear(mf+ii, 1, 1);
	    XgksDrawSegToWs(mf[ii]->cgm.mo.ws);
	}
    }

    return OK;
}


/*
 * Set the update flag in output CGMs.
 *
 * Since UPDATE has no graphical effect and doesn't affect Metafile contents,
 * we do nothing.
 */
     int
CGMupdate(mf, num, regenflag)
    /*ARGSUSED*/
    Metafile	**mf;
    int		num;
    Gregen	regenflag;
{
    return OK;
}


/*
 * Set the deferal state in output CGMs.
 */
    int
CGMdefer(mf, num, defer_mode, regen_mode)
    /*ARGSUSED*/
    Metafile	**mf;
    int		num;
    Gdefmode	defer_mode;
    Girgmode	regen_mode;
{
    return OK;
}


/*
 * Write a message to output CGMs.
 */
    int
CGMmessage(mf, num, string)
    Metafile	**mf;
    int		num;
    char	*string;
{
    mf_cgmo	**cgmo	= (mf_cgmo**)mf;
    int		action	= 0;		/* 0 => no action should be taken as
					 * a result of the message */

    mo_header(EXTERN_CL, MESSAGE_ID);
    mo_enum(cgmo, num, action);
    mo_string(cgmo, num, string);
    mo_flush(cgmo, num, 0);

    return OK;
}


/*
 * Write a graphic to an output CGM.
 *
 * This routine is suitable for POLYLINEs, POLYMARKERs, and FILLAREAs.
 */
    int
CGMoutputGraphic(mf, num, code, num_pt, pos)
    Metafile		**mf;
    int			num;
    int			code;
    Gint		num_pt;
    Gpoint		*pos;
{
    int			id;
    mf_cgmo		**cgmo	= (mf_cgmo**)mf;

    switch ((gksm_item_id)code) {
    case GKSM_POLYLINE:		id = LINE_ID;		break;
    case GKSM_POLYMARKER:	id = MARKER_ID;		break;
    case GKSM_FILL_AREA:	id = POLYGON_ID;	break;
    default:	assert(0);
		return 1;
    }

    mo_header(PRIMITIVE_CL, id);
    mo_points(cgmo, num, (char*)pos, num_pt);
    mo_flush(cgmo, num, 0);
    mo_mode(cgmo, num, CGMO_NOT_EMPTY);

    return OK;
}


/*
 * Write text.
 */
    int
CGMtext(mf, num, at, string)
    Metafile	**mf;
    int		num;
    Gpoint	*at;
    Gchar	*string;
{
    int		final	= 1;		/* 1 => final text */
    mf_cgmo	**cgmo	= (mf_cgmo**)mf;

    mo_header(PRIMITIVE_CL, TEXT_ID);
    MO_POINT(cgmo, num, at);
    mo_enum(cgmo, num, final);
    mo_string(cgmo, num, string);
    mo_flush(cgmo, num, 0);
    mo_mode(cgmo, num, CGMO_NOT_EMPTY);

    return OK;
}


/*
 * Write a cell array.
 */
    int
CGMcellArray(mf, num, ll, ur, lr, foldx, colour, dim)
    Metafile	**mf;
    int		num;
    Gpoint	*ll, *ur, *lr;
    Gint	foldx, *colour;
    Gipoint	*dim;
{
    int		colrprec	= size_cell()*8;
    int		cell_rep_mode	= DEFAULT_CELL_REP_MODE;
    Gint	*stopp;
    mf_cgmo	**cgmo	= (mf_cgmo**)mf;

    mo_header(PRIMITIVE_CL, CELLARRAY_ID);
    MO_POINT(cgmo, num, ll);
    MO_POINT(cgmo, num, ur);
    MO_POINT(cgmo, num, lr);
    mo_int(cgmo, num, dim->x);
    mo_int(cgmo, num, dim->y);
    mo_int(cgmo, num, colrprec);
    mo_enum(cgmo, num, cell_rep_mode);

    for (stopp = colour + foldx*dim->y; colour < stopp; colour += foldx)
	mo_cellrow(cgmo, num, colour, dim->x);

    mo_mode(cgmo, num, CGMO_NOT_EMPTY);

    return OK;
}


/*
 * Set the size of an individual graphics primitive attribute (i.e one having
 * a single real value).
 *
 * This routine is suitable for POLYLINEs, POLYMARKERs, CHARACTER EXPANSIONs,
 * and CHARACTER SPACEs.
 */
    int
CGMsetGraphSize(mf, num, code, size)
    Metafile		**mf;
    int			num;
    int			code;
    double		size;
{
    mf_cgmo	**cgmo	= (mf_cgmo**)mf;

    switch ((gksm_item_id)code) {

#	undef	SET_ATTR
#	define	SET_ATTR(name, mask, func)	GKS_STMT( \
		int	ii; \
		for (ii = 0; ii < num; ++ii) { \
		    cgmo[ii]->name	= size; \
		    cgmo[ii]->isset	|= mask; \
		} \
		func(cgmo, num); \
	    )

    case GKSM_LINEWIDTH_SCALE_FACTOR:
	SET_ATTR(linewidth, CGM_MASK_LINEWIDTH, mo_linewidth);
	break;
    case GKSM_MARKER_SIZE_SCALE_FACTOR:
	SET_ATTR(markersize, CGM_MASK_MARKERSIZE, mo_markersize);
	break;
    case GKSM_CHARACTER_EXPANSION_FACTOR:
	SET_ATTR(charexpan, CGM_MASK_CHAREXPAN, mo_charexpan);
	break;
    case GKSM_CHARACTER_SPACING:
	SET_ATTR(charspace, CGM_MASK_CHARSPACE, mo_charspace);
	break;
    default:	assert(0);
		return 1;
    }

    return OK;
}


/*
 * Close a segment.
 *
 * Since we're not implementing CGM Adendum 1 functionality yet, this is 
 * ignored.
 */
    int
CGMcloseSeg(mf, num)
    /*ARGSUSED*/
    Metafile	**mf;
    int		num;
{
    return OK;
}


/*
 * Set a graphics primitive index or enumeration attribute (i.e. set an
 * attribute whose value is a single integer).
 */
    int
CGMsetGraphAttr(mf, num, code, attr)
    Metafile		**mf;
    int			num;
    int			code;
    Gint		attr;
{
    mf_cgmo		**cgmo	= (mf_cgmo**)mf;

    switch ((gksm_item_id)code) {

#	undef	SET_ATTR
#	define	SET_ATTR(name, mask, func)	GKS_STMT( \
		int	ii; \
		for (ii = 0; ii < num; ++ii) { \
		    cgmo[ii]->name	= attr; \
		    cgmo[ii]->isset	|= mask; \
		} \
		func(cgmo, num); \
	    )

    case GKSM_POLYLINE_INDEX:
	SET_ATTR(lineindex, CGM_MASK_LINEINDEX, mo_lineindex);
	break;
    case GKSM_LINETYPE:
	SET_ATTR(linetype, CGM_MASK_LINETYPE, mo_linetype);
	break;
    case GKSM_POLYLINE_COLOUR_INDEX:
	SET_ATTR(linecolr.index, CGM_MASK_LINECOLR, mo_linecolr);
	break;
    case GKSM_POLYMARKER_INDEX:
	SET_ATTR(markerindex, CGM_MASK_MARKERINDEX, mo_markerindex);
	break;
    case GKSM_MARKER_TYPE:
	SET_ATTR(markertype, CGM_MASK_MARKERTYPE, mo_markertype);
	break;
    case GKSM_POLYMARKER_COLOUR_INDEX:
	SET_ATTR(markercolr.index, CGM_MASK_MARKERCOLR, mo_markercolr);
	break;
    case GKSM_TEXT_INDEX:
	SET_ATTR(textindex, CGM_MASK_TEXTINDEX, mo_textindex);
	break;
    case GKSM_TEXT_COLOUR_INDEX:
	SET_ATTR(textcolr.index, CGM_MASK_TEXTCOLR, mo_textcolr);
	break;
    case GKSM_FILL_AREA_INDEX:
	SET_ATTR(fillindex, CGM_MASK_FILLINDEX, mo_fillindex);
	break;
    case GKSM_FILL_AREA_STYLE_INDEX:
	if (attr >= 0)
	    SET_ATTR(patindex, CGM_MASK_PATINDEX, mo_patindex);
	SET_ATTR(hatchindex, CGM_MASK_HATCHINDEX, mo_hatchindex);
	break;
    case GKSM_FILL_AREA_COLOUR_INDEX:
	SET_ATTR(fillcolr.index, CGM_MASK_FILLCOLR, mo_fillcolr);
	break;
    case GKSM_PICK_IDENTIFIER:
    case GKSM_CREATE_SEGMENT:
    case GKSM_DELETE_SEGMENT:
	return OK;
    default:	assert(0);
		return 1;
    }

    return OK;
}


/*
 * Set the text font and precision.
 */
    int
CGMsetTextFP(mf, num, txfp)
    Metafile	**mf;
    int		num;
    Gtxfp	*txfp;
{
    int		i;

    for (i = 0; i < num; ++i) {
	mf[i]->cgm.mo.txfp	= *txfp;
	mf[i]->cgm.mo.isset	|= CGM_MASK_TEXTFONTINDEX | CGM_MASK_TEXTPREC;
    }

    mo_textfontindex((mf_cgmo**)mf, num);
    mo_textprec((mf_cgmo**)mf, num);

    return OK;
}


/*
 * Set the character up- and base-vectors.
 *
 * This function sets the CGM element CHARACTER ORIENTATION.
 */
    int
CGMsetCharUp(mf, num, up, base)
    Metafile	**mf;
    int		num;
    Gpoint	*up,
		*base;
{
    int		ii;
    Gpoint	ndc_up,
		ndc_base;

    if (up == NULL) {
	XgksComputeVec(&ndc_up, &ndc_base);
    } else {
	ndc_up		= *up;
	ndc_base	= *base;
    }

    for (ii = 0; ii < num; ++ii) {
	mf[ii]->cgm.mo.charheight	= HYPOT(ndc_up.x, ndc_up.y);
	mf[ii]->cgm.mo.charori[0]	= ndc_up.x;
	mf[ii]->cgm.mo.charori[1]	= ndc_up.y;
	mf[ii]->cgm.mo.charori[2]	= ndc_base.x;
	mf[ii]->cgm.mo.charori[3]	= ndc_base.y;
	mf[ii]->cgm.mo.isset		|= CGM_MASK_CHARORI | CGM_MASK_CHARHEIGHT;
    }

    mo_charori((mf_cgmo**)mf, num);
    mo_charheight((mf_cgmo**)mf, num);

    return OK;
}


/*
 * Set the text-path.
 */
    int
CGMsetTextPath(mf, num, path)
    Metafile	**mf;
    int		num;
    Gtxpath	path;
{
    int		i;

    for (i = 0; i < num; ++i) {
	mf[i]->cgm.mo.textpath	= path;
	mf[i]->cgm.mo.isset	|= CGM_MASK_TEXTPATH;
    }

    mo_textpath((mf_cgmo**)mf, num);

    return OK;
}


/*
 * Set the text alignment.
 */
    int
CGMsetTextAlign(mf, num, align)
    Metafile	**mf;
    int		num;
    Gtxalign	*align;
{
    int		ii;

    for (ii = 0; ii < num; ++ii) {
	mf[ii]->cgm.mo.textalign	= *align;
	mf[ii]->cgm.mo.isset	|= CGM_MASK_TEXTALIGN;
    }

    mo_textalign((mf_cgmo**)mf, num);

    return OK;
}


/*
 * Set the interior fill-style (hollow, solid, etc.).
 */
    int
CGMsetFillStyle(mf, num, style)
    Metafile	**mf;
    int		num;
    Gflinter	style;
{
    int		ii;

    for (ii = 0; ii < num; ++ii) {
	mf[ii]->cgm.mo.intstyle	= style;
	mf[ii]->cgm.mo.isset	|= CGM_MASK_INTSTYLE;
    }

    mo_intstyle((mf_cgmo**)mf, num);

    return OK;
}


/*
 * Set the pattern size.
 */
    int
CGMsetPatSize(mf, num)
    Metafile	**mf;
    int		num;
{
    mf_cgmo	**cgmo	= (mf_cgmo**)mf;

    mo_header(ATTRIBUTE_CL, PATSIZE_ID);
    MO_VDC(cgmo, num, xgks_state.gks_ptattr.heightvec.x);
    MO_VDC(cgmo, num, xgks_state.gks_ptattr.heightvec.y);
    MO_VDC(cgmo, num, xgks_state.gks_ptattr.widthvec.x);
    MO_VDC(cgmo, num, xgks_state.gks_ptattr.widthvec.y);
    mo_flush(cgmo, num, 0);

    return OK;
}


/*
 * Set the pattern reference-point.
 */
    int
CGMsetPatRefpt(mf, num)
    Metafile	**mf;
    int		num;
{
    mf_cgmo	**cgmo	= (mf_cgmo**)mf;

    mo_header(ATTRIBUTE_CL, FILLREFPT_ID);
    MO_POINT(cgmo, num, &xgks_state.gks_ptattr.ptp);
    mo_flush(cgmo, num, 0);

    return OK;
}



/*
 * Set the ASFs.
 *
 * Only 15 ASFs are written: the 3 edge ASFs are omitted.  Both the hatch
 * and pattern ASFs are set from the GKS fill-style ASF.
 */
    int
CGMsetAsf(mf, num)
    Metafile	**mf;
    int		num;
{
    int		iasf		= 0;
    int		bundled		= 1,
		individual	= 0;
    mf_cgmo	**cgmo		= (mf_cgmo**)mf;

#   undef	PUT_ASF
#   define	PUT_ASF(member)		GKS_STMT( \
	    mo_enum(cgmo, num, iasf); \
	    ++iasf; \
	    mo_enum(cgmo, num, \
		     xgks_state.member == GBUNDLED \
			? bundled  \
			: individual); \
	)

    mo_header(ATTRIBUTE_CL, ASF_ID);

    PUT_ASF(gks_lnattr.type);			/* line type */
    PUT_ASF(gks_lnattr.width);			/* line width */
    PUT_ASF(gks_lnattr.colour);			/* line color */

    PUT_ASF(gks_mkattr.type);			/* marker type */
    PUT_ASF(gks_mkattr.size);			/* marker size */
    PUT_ASF(gks_mkattr.colour);			/* marker color */

    PUT_ASF(gks_txattr.fp);			/* text font index */
    PUT_ASF(gks_txattr.fp);			/* text precision */
    PUT_ASF(gks_txattr.tx_exp);			/* character expansion factor */
    PUT_ASF(gks_txattr.space);			/* character spacing */
    PUT_ASF(gks_txattr.colour);			/* text color */

    PUT_ASF(gks_flattr.inter);			/* interior fill style */
    PUT_ASF(gks_flattr.colour);			/* interior fill color */
    PUT_ASF(gks_flattr.style);			/* hatch index */

    /*
     * The DomainOS compiler warns about an unused value being assigned
     * to variable "iasf" in the following line.  This warning may be
     * safely ignored.
     */
    PUT_ASF(gks_flattr.style);			/* pattern index */

    mo_flush(cgmo, num, 0);

    return OK;
}


/*
 * Set the line or marker representation.
 *
 * Although CGM Addendum 1 specifies how to modify the bundle table, we haven't
 * implement this capability just yet.
 */
    int
CGMsetLineMarkRep(mf, num, code, idx, type, size, colour)
    /*ARGSUSED*/
    Metafile		**mf;
    int			num;
    int			code;
    Gint		idx, type, colour;
    double		size;
{
    return OK;
}


/*
 * Set the text representation.
 *
 * Not implemented yet.
 */
    int
CGMsetTextRep(mf, num, idx, rep)
    /*ARGSUSED*/
    Metafile	**mf;
    int		num;
    Gint	idx;
    Gtxbundl	*rep;
{
    return OK;
}


/*
 * Set the fill representation.  
 *
 * Not implemented yet.
 */
    int
CGMsetFillRep(mf, num, idx, rep)
    /*ARGSUSED*/
    Metafile	**mf;
    int		num;
    Gint	idx;
    Gflbundl	*rep;
{
    return OK;
}


/*
 * Set a pattern representation.
 *
 * Not implemented yet.
 */
    int
CGMsetPatRep(mf, num, idx, rep)
    /*ARGSUSED*/
    Metafile	**mf;
    int		num;
    Gint	idx;
    Gptbundl	*rep;
{
    return OK;
}


/*
 * Set the representation of a colour.
 */
    int
CGMsetColRep(mf, num, idx, rep)
    Metafile	**mf;
    int		num;
    Gint	idx;		/* Origin-0 color index */
    /*ARGSUSED*/
    Gcobundl	*rep;
{
    int		ii;
    mf_cgmo	**cgmo	= (mf_cgmo**)mf;

    for (ii = 0; ii < num; ++ii) {
	if (idx == 0)
	    cgmo[ii]->isset	|= CGM_MASK_BACKCOLR;
	cgmo[ii]->isset	|= CGM_MASK_COLRTABLE;

	mo_header(ATTRIBUTE_CL, COLRTABLE_ID);
	mo_index(cgmo+ii, 1, idx);
	mo_direct_color(cgmo+ii, 1, rep);
	mo_flush(cgmo+ii, 1, 0);
    }

    return OK;
}


/*
 * Set the clip rectangle.
 *
 * According to Addendum E of the CGM standard, a CLIP RECTANGLE element is
 * written to the metafile with value (0.,0.,1.,1.) if the `clipping indicator'
 * entry in the GKS state list is `noclip', or with the value of the `clipping
 * rectangle' in the GKS state list if the `clipping indicator' entry in the
 * GKS state list is `clip'.
 */
    int
CGMsetClip(mf, num, rect)
    Metafile	**mf;
    int		num;
    Glimit	*rect;
{
    Gcliprec	clipping;

    if (ginqclip(&clipping) == OK) {
	mf_cgmo		**cgmo		= (mf_cgmo**)mf;
	Gpoint		lower_left,
			upper_right;
	static Glimit	no_clipping	= {0., 1., 0., 1.};

	if (clipping.ind == GNOCLIP) {
	    rect	= &no_clipping;
	} else if (rect == NULL) {
	    rect	= &clipping.rec;
	}

	lower_left.x	= rect->xmin;
	lower_left.y	= rect->ymin;
	upper_right.x	= rect->xmax;
	upper_right.y	= rect->ymax;

	mo_header(CONTROL_CL, CLIPRECT_ID);
	MO_POINT(cgmo, num, &lower_left);
	MO_POINT(cgmo, num, &upper_right);
	mo_flush(cgmo, num, 0);
    }

    return OK;
}


/*
 * Set either the Workstation window or the Workstation viewport.
 *
 * According to Addendum E of the CGM standard, certain actions need only be
 * performed if the view surface is non-empty or the GKS functions imply a
 * dynamic change to the image.  Consequently, we check whether the view
 * surface is empty or not, and start a new picture if it's non-empty.
 */
    int
CGMsetLimit(mf, num, code, rect)
    Metafile		**mf;
    int			num;
    int			code;
    Glimit		*rect;
{
    int		ii;

    for (ii = 0; ii < num; ++ii) {
	mf_cgmo	*cgmo	= ((mf_cgmo**)mf)[ii];

	if ((gksm_item_id)code == GKSM_WORKSTATION_WINDOW)
	    cgmo->wswindow	= *rect;

	if (cgmo->mode == CGMO_NOT_EMPTY) {
	    MO_ENDPIC(&cgmo, 1);
	    mo_begpic(&cgmo, 1);
	    mo_vdcext(&cgmo, 1);
	    mo_backcolr(&cgmo, 1);
	    MO_BEGPICBODY(&cgmo, 1);
	    mo_attributes(&cgmo, 1);
	    mo_cliprect(&cgmo, 1);
	} else {
	    mo_vdcext(&cgmo, 1);
	}
    }

    return OK;
}


/*
 * Rename a segment.
 *
 * Not implemented yet.
 */
    int
CGMrenameSeg(mf, num, old, new)
    /*ARGSUSED*/
    Metafile	**mf;
    int		num;
    Gint	old, new;
{
    return OK;
}


/*
 * Set a segment transformation.
 *
 * Not implemented yet.
 */
    int
CGMsetSegTran(mf, num, name, matrix)
    /*ARGSUSED*/
    Metafile	**mf;
    int		num;
    Gint	name;
    Gfloat	matrix[2][3];
{
    return OK;
}


/*
 * Set a segment (non-transformation) attribute.
 *
 * Not implemented yet.
 */
    int
CGMsetSegAttr(mf, num, name, code, attr)
    /*ARGSUSED*/
    Metafile		**mf;
    int			num;
    Gint		name;
    int			code;
    Gint		attr;
{
    return OK;
}


/*
 * Set the segment visibility in an output Metafile.
 *
 * Not implemented yet.
 */
    int
CGMsetSegVis(mf, num, name, vis)
    /*ARGSUSED*/
    Metafile	**mf;
    int		num;
    Gint	name;
    Gsegvis	vis;
{
    return OK;
}


/*
 * Set segment highlighting.
 *
 * Not implemented yet.
 */
    int
CGMsetSegHilight(mf, num, name, hilight)
    /*ARGSUSED*/
    Metafile	**mf;
    int		num;
    Gint	name;
    Gseghi	hilight;
{
    return OK;
}


/*
 * Set segment priority.
 *
 * Not implemented yet.
 */
    int
CGMsetSegPri(mf, num, name, pri)
    /*ARGSUSED*/
    Metafile	**mf;
    int		num;
    Gint	name;
    double	pri;
{
    return OK;
}


/*
 * Set segment detectability.
 *
 * Not implemented yet.
 */
    int
CGMsetSegDetect(mf, num, name, det)
    /*ARGSUSED*/
    Metafile	**mf;
    int		num;
    Gint	name;
    Gsegdet	det;
{
    return OK;
}


/*
 * Close an output CGM.
 */
    int
CGMmoClose(mf)
    Metafile	*mf;
{
    mf_cgmo	*cgmo	= &mf->cgm.mo;

    debug=1;
    MO_ENDPIC(&cgmo, 1);
    MO_ENDMF(&cgmo, 1);

    return OK;
}


/*
 * Get a partition control word (PCW).
 */
    static void
mi_pcw(fp, more_data, length)
    FILE	*fp;
    int		*more_data;
    int		*length;
{
    unsigned short	pcw	= 0;
    unsigned char	bytes[2];

    (void) fread((voidp)bytes, (size_t)1, (size_t)2, fp);
    pcw		= NTOH16(bytes);
    *more_data	= pcw &  MORE_DATA_BIT;
    *length	= pcw & ~MORE_DATA_BIT;
}


/*
 * Return the next octet of data.
 *
 * This routine will read the next partition(s) if necessary.
 */
    static unsigned
mi_octet(mi)
    mf_cgmi		*mi;
{
    unsigned char	data;

    --mi->total_left;

    if (mi->partition_left == 0) {
	if (mi->partition_length % 2)			/* If padding */
	    (void) fseek(mi->fp, (long)1, SEEK_CUR);	/* Skip */

	do {
	    int	more_data;

	    mi_pcw(mi->fp, &more_data, &mi->partition_length);

	    assert(mi->partition_length > 0 || more_data);

	} while (mi->partition_length == 0);

	mi->partition_left	= mi->partition_length;
    }

    (void) fread((voidp)&data, (size_t)1, (size_t)1, mi->fp);

    --mi->partition_left;

    return data;
}


/*
 * Get the next octets of data.
 */
#define mi_octets(mi, data, num) 	GKS_STMT( \
	int	n		= num; \
	unsigned char	*ptr	= data; \
	while (n-- > 0) \
	    *ptr++	= mi_octet(mi); \
    )


#if 0
/*
 * Go to the start of the current element.
 */
    static void
mi_reset(mi)
    mf_cgmi	*mi;
{
    (void) fseek(mi->fp, mi->start_this_element, SEEK_SET);
}
#endif

/*
 * Return a 16-bit, integer parameter.
 */
    static int
mi_int16(mi)
    mf_cgmi		*mi;
{
    unsigned char	bytes[2];

    mi_octets(mi, bytes, 2);

    return TTOHS((unsigned short)NTOH16(bytes));
}


/*
 * Return an 16-bit, unsigned integer parameter.
 */
    static unsigned
mi_uint16(mi)
    mf_cgmi	*mi;
{
    unsigned char 	bytes[2];

    mi_octets(mi, bytes, 2);

    return (unsigned short)NTOH16(bytes);
}


/*
 * Return an integer parameter.
 */
#define mi_int(mi)	mi_int16(mi)


/*
 * Return an index parameter.
 */
#define mi_index(mi)		mi_int16(mi)


/*
 * Return an ennumeration parameter.
 */
#define mi_enum(mi)		mi_int16(mi)


/*
 * Get a direct color parameter.
 */
    static void
mi_direct_colors(mi, colors, ncolor)
    mf_cgmi		*mi;
    XGKSMCOLOURREP	*colors;
    int			ncolor;
{
    for (; ncolor-- > 0; ++colors) {
	unsigned char	rep[3];

	mi_octets(mi, rep, 3);

	colors->red	= UINT8_TO_NORM(rep[0]);
	colors->green	= UINT8_TO_NORM(rep[1]);
	colors->blue	= UINT8_TO_NORM(rep[2]);
    }
}


/*
 * Get a string parameter.
 */
    static void
mi_string(mi, string, nchr)
    mf_cgmi	*mi;
    char	*string;
    int		*nchr;
{
    int		length	= mi_octet(mi);

    if (length == LONG_STR_LENGTH) {
	 int	more;

	*nchr	= 0;

	do {
	     unsigned	pattern	= mi_uint16(mi);

	     more	= pattern & MSB_16;
	     length	= pattern & ~MSB_16;
	     mi_octets(mi, (unsigned char*)string, length);
	     *nchr	+= length;
	     string	+= length;
	     *string	= 0;
	} while (more);
    } else {
	mi_octets(mi, (unsigned char*)string, length);
	string[length]	= 0;
	*nchr	= length;
    }
}


/*
 * Return a VDC co-ordinate parameter.
 */
#define mi_vdc(mi)	int16_to_norm(mi_int16(mi));


/*
 * Get point parameters.
 */
mi_point(mi, points, npoint)
    mf_cgmi	*mi;
    Gpoint	*points;
    int		npoint;
{
    for (; npoint-- > 0; ++points) {
	points->x	= mi_vdc(mi);
	points->y	= mi_vdc(mi);
    }
}


/*
 * Get a message parameter.
 */
    static void
mi_mesg(mi, msg)
    mf_cgmi	*mi;
    XGKSMMESG	*msg;
{
    msg->string	= (char*)msg + sizeof(XGKSMMESG);
    mi_string(mi, msg->string, &msg->strlen);
}


/*
 * Get a limit rectangle (e.g. workstation widow or clipping rectangle)
 * parameter.
 */
    static void
mi_rect(mi, limit)
    mf_cgmi	*mi;
    XGKSMLIMIT	*limit;
{
    Gpoint	lower_left, upper_right;

    mi_point(mi, &lower_left, 1);
    mi_point(mi, &upper_right, 1);

    limit->rect.xmin	= lower_left.x;
    limit->rect.ymin	= lower_left.y;
    limit->rect.xmax	= upper_right.x;
    limit->rect.ymax	= upper_right.y;
}


/*
 * Get a graphic (i.e. polyline, polymarker, or fill-area) parameter.
 */
    static void
mi_graph(mi, graph)
    mf_cgmi	*mi;
    XGKSMGRAPH	*graph;
{
    graph->num_pts	= BYTES_LEFT(mi)/size_point();
    graph->pts		= JUST_AFTER(graph, XGKSMGRAPH, Gpoint);
    mi_point(mi, graph->pts, graph->num_pts);
}


/*
 * Get a row of a cell-array parameters.
 */
    static void
mi_cellrow(mi, color, ncolor)
    mf_cgmi	*mi;
    Gint	*color;
    int		ncolor;
{
    while (ncolor-- > 0)
	*color++	= mi_octet(mi);

    if (ncolor % 2)
	(void) mi_octet(mi);
}


/*
 * Return a 32-bit, fixed-point, real parameter.
 */
    static double
mi_real32fx(mi)
    mf_cgmi	*mi;
{
    unsigned char	bytes[2];
    unsigned short	whole;
    unsigned short	frac;

    mi_octets(mi, bytes, 2);
    whole	= NTOH16(bytes);

    mi_octets(mi, bytes, 2);
    frac	= NTOH16(bytes);

    return TTOHS(whole) + UINT16_TO_NORM(frac);
}


/*
 * Return a real parameter.
 */
#define mi_real(mi)		mi_real32fx(mi)


/*
 * Return an indexed-color parameter.
 */
#define mi_indexed_color(mi)	mi_octet(mi)


/*
 * Ingest character vector information.
 */
    static void
ingest_charvec(mi, vec)
    mf_cgmi		*mi;
    XGKSMCHARVEC	*vec;
{
    double		factor	= mi->char_height /
				      HYPOT(mi->char_up.x, mi->char_up.y);

    mi->char_up.x	*= factor;
    mi->char_up.y	*= factor;
    mi->char_base.x	*= factor;
    mi->char_base.y	*= factor;

    vec->up	= mi->char_up;
    vec->base	= mi->char_base;
}


/*
 * Get the parameters of the current element.
 *
 * This is where the translation from CGM elements to GKSM items occurs.
 */
    static int
mi_parameters(mi, pars, mode)
    mf_cgmi	*mi;
    char	*pars;
    decode_mode	mode;
{
    union cgm_data {
	XGKSMONE       one;
	XGKSMTWO       two;
	XGKSMMESG      msg;
	XGKSMGRAPH     graph;
	XGKSMTEXT      text;
	XGKSMSIZE      size;
	XGKSMCHARVEC   vec;
	XGKSMASF       asf;
	XGKSMLMREP     lmrep;
	XGKSMTEXTREP   txrep;
	XGKSMFILLREP   flrep;
	XGKSMPATREP    patrep;
	XGKSMCOLOURREP corep;
	XGKSMLIMIT     limit;
	XGKSMSEGTRAN   tran;
	XGKSMSEGPRI    pri;
	XGKSMCELLARRAY cell;
	XGKSMPATREF    patref;
	XGKSMPATSIZ    patsiz;
    }		*data	= (union cgm_data*)pars;
    static char	me[]	= "mi_parameters";

    /*
     * The following is a hack.  Since one CGM COLORTABLE element can be 
     * equivalent to more than one GKS COLOR REPRESENTATION items, we
     * special-case this element.
     */
    if (mi->mode == READING_COLOR_TABLE) {
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (Gint)GKSM_COLOUR_REPRESENTATION;
	    mi->CurItem.length	= sizeof(data->corep);
	} else {
	    data->corep.idx	= ++mi->color_index;
	    mi_direct_colors(mi, &data->corep, 1);
	    if (BYTES_LEFT(mi) < size_direct_color())
		mi->mode	= NORMAL_MODE;
	}
	return METAFILE_OK;
    }

    mi->mode	= NORMAL_MODE;

    switch (mi->hash_id) {

    case HASH_ID(DELIMITER_CL, BEGMF_ID):
	if (mode == RETURN_INFO) {
#ifdef DEBUG
	    XGKSMMESG	*msg	= (XGKSMMESG*)mo_buf;

	    mi_mesg(mi, msg);
	    (void) fprintf(stderr, "%s(): begin metafile \"%s\"\n",
			   me, msg->string);
#endif
	    mi->CurItem.type	= (int)GKSM_CGM_ELEMENT;
	    mi->CurItem.length	= 0;
	}
	break;

    case HASH_ID(DELIMITER_CL, ENDMF_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_END_ITEM;
	    mi->CurItem.length	= 0;
	    mi->GksmEmpty	= 1;
#ifdef DEBUG
	    (void) fprintf(stderr, "%s(): end metafile\n", me);
#endif
	}
	break;

    case HASH_ID(DELIMITER_CL, BEGPIC_ID):
	if (mode == RETURN_INFO) {
#ifdef DEBUG
	    XGKSMMESG	*msg	= (XGKSMMESG*)mo_buf;

	    mi_mesg(mi, msg);
	    (void) fprintf(stderr, "%s(): begin picture \"%s\"\n",
			   me, msg->string);
#endif
	    mi->CurItem.type	= (int)GKSM_CLEAR_WORKSTATION;
	    mi->CurItem.length	= sizeof(data->one);
	} else {
	    data->one.flag	= (Gint)GCONDITIONALLY;
	}
	break;

    case HASH_ID(DELIMITER_CL, BEGPICBODY_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_CGM_ELEMENT;
	    mi->CurItem.length	= 0;
#ifdef DEBUG
	    (void) fprintf(stderr, "%s(): begin picture body\n", me);
#endif
	}
	break;

    case HASH_ID(DELIMITER_CL, ENDPIC_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_CGM_ELEMENT;
	    mi->CurItem.length	= 0;
#ifdef DEBUG
	    (void) fprintf(stderr, "%s(): end picture body\n", me);
#endif
	}
	break;

    case HASH_ID(MF_DESCRIPTOR_CL, MFVERSION_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_CGM_ELEMENT;
	    mi->CurItem.length	= sizeof(data->one);
	} else {
	    data->one.flag	= mi_int(mi);
	    if (data->one.flag != mfversion) {
		(void) fprintf(stderr, 
			       "%s(): metafile version is %d (I'm %d)\n",
			       me, data->one.flag, mfversion);
		(void) fputs("This might cause problems\n", stderr);
	    }
	}
	break;

    case HASH_ID(MF_DESCRIPTOR_CL, MFDESC_ID):
	if (mode == RETURN_INFO) {
#ifdef DEBUG
	    XGKSMMESG	*msg	= (XGKSMMESG*)mo_buf;

	    mi_mesg(mi, msg);
	    (void) fprintf(stderr,
			   "%s(): metafile description: \"%s\"\n",
			   me, msg->string);
#endif
	    mi->CurItem.type	= (int)GKSM_CGM_ELEMENT;
	    mi->CurItem.length	= 0;
	}
	break;

    case HASH_ID(MF_DESCRIPTOR_CL, MFELEMLIST_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_CGM_ELEMENT;
	    mi->CurItem.length	= 0;
#ifdef DEBUG
	    (void) fprintf(stderr, "%s(): metafile element-list ignored\n",
			   me);
#endif
	}
	break;

    case HASH_ID(PIC_DESCRIPTOR_CL, COLRMODE_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_CGM_ELEMENT;
	    mi->CurItem.length	= sizeof(data->one);
	} else {
	    data->one.flag	= mi_enum(mi);
	    if (data->one.flag != DEFAULT_COLRMODE) {
		(void) fprintf(stderr,
			       "%s(): color selection mode is %d (I'm %d)\n",
			       me, data->one.flag, DEFAULT_COLRMODE);
		return MF_ITEM_ERR;
	    }
	}
	break;

    case HASH_ID(PIC_DESCRIPTOR_CL, VDCEXT_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_WORKSTATION_WINDOW;
	    mi->CurItem.length	= sizeof(data->limit);
	} else {
	    mi_rect(mi, &data->limit);
	}
	break;

    case HASH_ID(CONTROL_CL, CLIPRECT_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_CLIPPING_RECTANGLE;
	    mi->CurItem.length	= sizeof(data->limit);
	} else {
	    mi_rect(mi, &data->limit);
	    mi->clip_rect		= data->limit.rect;
	}
	break;

    case HASH_ID(PIC_DESCRIPTOR_CL, BACKCOLR_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_COLOUR_REPRESENTATION;
	    mi->CurItem.length	= sizeof(data->corep);
	} else {
	    data->corep.idx	= 0;
	    mi_direct_colors(mi, &data->corep, 1);
	}
	break;

    case HASH_ID(CONTROL_CL, VDCINTEGERPREC_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_CGM_ELEMENT;
	    mi->CurItem.length	= sizeof(data->one);
	} else {
	    data->one.flag	= mi_enum(mi);
	    if (data->one.flag != DEFAULT_VDCINTEGERPREC) {
		(void) fprintf(stderr,
			       "%s(): VDC integer precision is %d (I'm %d)\n",
			       me, data->one.flag, DEFAULT_VDCINTEGERPREC);
		return MF_ITEM_ERR;
	    }
	}
	break;

    case HASH_ID(CONTROL_CL, CLIP_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_CLIPPING_RECTANGLE;
	    mi->CurItem.length	= sizeof(data->limit);
	} else {
	    data->one.flag	= mi_enum(mi);
	    if ((Gclip)data->one.flag == GCLIP) {
		data->limit.rect	= mi->clip_rect;
	    } else {
		data->limit.rect.xmin	= 0.;
		data->limit.rect.ymin	= 0.;
		data->limit.rect.xmax	= 1.;
		data->limit.rect.ymax	= 1.;
	    }
	}
	break;

    case HASH_ID(PRIMITIVE_CL, LINE_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_POLYLINE;
	    mi->CurItem.length	= sizeof(XGKSMGRAPH) + sizeof(Gpoint)*
				    (BYTES_LEFT(mi)/size_point());
	} else {
	    mi_graph(mi, &data->graph);
	}
	break;

    case HASH_ID(PRIMITIVE_CL, MARKER_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_POLYMARKER;
	    mi->CurItem.length	= sizeof(XGKSMGRAPH) + sizeof(Gpoint)*
				    (BYTES_LEFT(mi)/size_point());
	} else {
	    mi_graph(mi, &data->graph);
	}
	break;

    case HASH_ID(PRIMITIVE_CL, POLYGON_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_FILL_AREA;
	    mi->CurItem.length	= sizeof(XGKSMGRAPH) + sizeof(Gpoint)*
				    (BYTES_LEFT(mi)/size_point());
	} else {
	    mi_graph(mi, &data->graph);
	}
	break;

    case HASH_ID(PRIMITIVE_CL, TEXT_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_TEXT;
	    mi->CurItem.length	= sizeof(data->text) + BYTES_LEFT(mi) + 1;
	} else {
	    mi_point(mi, &data->text.location, 1);
	    (void) mi_enum(mi);
	    data->text.string	= JUST_AFTER(data, XGKSMTEXT, char);
	    mi_string(mi, data->text.string, &data->text.strlen);
	}
	break;

    case HASH_ID(PRIMITIVE_CL, CELLARRAY_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_CELLARRAY;
	    mi->CurItem.length	= sizeof(XGKSMCELLARRAY) + sizeof(Gint)*
				    (BYTES_LEFT(mi)/size_cell());
	} else {
	    int		colrprec;	/* Local color precision */
	    int		cell_rep_mode;	/* Local color representation mode */
	    Gint	*pixel, *stopp;

	    mi_point(mi, &data->cell.ll, 1);
	    mi_point(mi, &data->cell.ur, 1);
	    mi_point(mi, &data->cell.lr, 1);

	    data->cell.dim.x	= mi_int(mi);
	    data->cell.dim.y	= mi_int(mi);

	    colrprec	= mi_int(mi);
	    if (colrprec != 0 && colrprec != DEFAULT_COLRPREC) {
		(void) fprintf(stderr,
			       "%s(): local color precision is %d (I'm %d)\n",
			       me, colrprec, DEFAULT_COLRPREC);
		return MF_DATA_ERR;
	    }

	    cell_rep_mode	= mi_enum(mi);
	    if (cell_rep_mode != DEFAULT_CELL_REP_MODE) {
		(void) fprintf(stderr, 
			    "%s(): local cell representation is %d (I'm %d)\n",
			    me, cell_rep_mode, DEFAULT_CELL_REP_MODE);
		return MF_DATA_ERR;
	    }

	    pixel = data->cell.colour = JUST_AFTER(data, XGKSMCELLARRAY, Gint);

	    for (stopp = pixel + data->cell.dim.x*data->cell.dim.y;
		    pixel < stopp;
		    pixel += data->cell.dim.x)
		mi_cellrow(mi, pixel, data->cell.dim.x);
	    }
	break;

#	define GET_INT_ATTR(func, gksm_id)	GKS_STMT( \
		if (mode == RETURN_INFO) { \
		    mi->CurItem.type	= (int)gksm_id; \
		    mi->CurItem.length	= sizeof(data->one); \
		} else { \
		    data->one.flag	= func(mi); \
		} \
	    )

#	define GET_REAL_ATTR(gksm_id)		GKS_STMT( \
		if (mode == RETURN_INFO) { \
		    mi->CurItem.type	= (int)gksm_id; \
		    mi->CurItem.length	= sizeof(data->size); \
		} else { \
		    data->size.size	= mi_real(mi); \
		} \
	    )

    case HASH_ID(ATTRIBUTE_CL, LINEINDEX_ID):
	GET_INT_ATTR(mi_index, GKSM_POLYLINE_INDEX);
	break;

    case HASH_ID(ATTRIBUTE_CL, LINETYPE_ID):
	GET_INT_ATTR(mi_index, GKSM_LINETYPE);
	break;

    case HASH_ID(ATTRIBUTE_CL, LINEWIDTH_ID):
	GET_REAL_ATTR(GKSM_LINEWIDTH_SCALE_FACTOR);
	break;

    case HASH_ID(ATTRIBUTE_CL, LINECOLR_ID):
	GET_INT_ATTR(mi_indexed_color, GKSM_POLYLINE_COLOUR_INDEX);
	break;

    case HASH_ID(ATTRIBUTE_CL, MARKERINDEX_ID):
	GET_INT_ATTR(mi_index, GKSM_POLYMARKER_INDEX);
	break;

    case HASH_ID(ATTRIBUTE_CL, MARKERTYPE_ID):
	GET_INT_ATTR(mi_index, GKSM_MARKER_TYPE);
	break;

    case HASH_ID(ATTRIBUTE_CL, MARKERSIZE_ID):
	GET_REAL_ATTR(GKSM_MARKER_SIZE_SCALE_FACTOR);
	break;

    case HASH_ID(ATTRIBUTE_CL, MARKERCOLR_ID):
	GET_INT_ATTR(mi_indexed_color, GKSM_POLYMARKER_COLOUR_INDEX);
	break;

    case HASH_ID(ATTRIBUTE_CL, TEXTINDEX_ID):
	GET_INT_ATTR(mi_index, GKSM_TEXT_INDEX);
	break;

    case HASH_ID(ATTRIBUTE_CL, TEXTFONTINDEX_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_TEXT_FONT_AND_PRECISION;
	    mi->CurItem.length	= sizeof(data->two);
	} else {
	    data->two.item1	= mi_index(mi);
	    mi->txfp.font	= data->two.item1;
	    data->two.item2	= (int)mi->txfp.prec;
	}
	break;

    case HASH_ID(ATTRIBUTE_CL, TEXTPREC_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_TEXT_FONT_AND_PRECISION;
	    mi->CurItem.length	= sizeof(data->two);
	} else {
	    data->two.item2	= mi_index(mi);
	    mi->txfp.prec	= (Gtxprec)data->two.item2;
	    data->two.item1	= mi->txfp.font;
	}
	break;

    case HASH_ID(ATTRIBUTE_CL, CHAREXPAN_ID):
	GET_REAL_ATTR(GKSM_CHARACTER_EXPANSION_FACTOR);
	break;

    case HASH_ID(ATTRIBUTE_CL, CHARSPACE_ID):
	GET_REAL_ATTR(GKSM_CHARACTER_SPACING);
	break;

    case HASH_ID(ATTRIBUTE_CL, TEXTCOLR_ID):
	GET_INT_ATTR(mi_indexed_color, GKSM_TEXT_COLOUR_INDEX);
	break;

    case HASH_ID(ATTRIBUTE_CL, CHARHEIGHT_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_CHARACTER_VECTORS;
	    mi->CurItem.length	= sizeof(data->vec);
	} else {
	    mi->char_height	= mi_vdc(mi);
	    ingest_charvec(mi, &data->vec);
	}
	break;

    case HASH_ID(ATTRIBUTE_CL, CHARORI_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_CHARACTER_VECTORS;
	    mi->CurItem.length	= sizeof(data->vec);
	} else {
	    mi->char_up.x	= mi_vdc(mi);
	    mi->char_up.y	= mi_vdc(mi);
	    mi->char_base.x	= mi_vdc(mi);
	    mi->char_base.y	= mi_vdc(mi);
	    ingest_charvec(mi, &data->vec);
	}
	break;

    case HASH_ID(ATTRIBUTE_CL, TEXTPATH_ID):
	GET_INT_ATTR(mi_enum, GKSM_TEXT_PATH);
	break;

    case HASH_ID(ATTRIBUTE_CL, TEXTALIGN_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_TEXT_ALIGNMENT;
	    mi->CurItem.length	= sizeof(data->two);
	} else {
	    data->two.item1	= mi_enum(mi);
	    data->two.item2	= mi_enum(mi);

	    if (data->two.item1 == CGM_CONTINUOUS_HORIZONTAL_ALIGNMENT ||
		    data->two.item2 == CGM_CONTINUOUS_VERTICAL_ALIGNMENT) {
		(void) fprintf(stderr, 
		       "%s(): can't handle continuous text alignment request\n",
		       me);
		return MF_DATA_ERR;
	    }
	}
	break;

    case HASH_ID(ATTRIBUTE_CL, CHARSETINDEX_ID):
    case HASH_ID(ATTRIBUTE_CL, ALTCHARSETINDEX_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_CGM_ELEMENT;
	    mi->CurItem.length	= 0;
#ifdef DEBUG
	    (void) fprintf(stderr,
			   "%s(): (alt)character set index ignored\n", me);
#endif
	}
	break;

    case HASH_ID(ATTRIBUTE_CL, FILLINDEX_ID):
	GET_INT_ATTR(mi_index, GKSM_FILL_AREA_INDEX);
	break;

    case HASH_ID(ATTRIBUTE_CL, INTSTYLE_ID):
	GET_INT_ATTR(mi_enum, GKSM_FILL_AREA_INTERIOR_STYLE);
	break;

    case HASH_ID(ATTRIBUTE_CL, FILLCOLR_ID):
	GET_INT_ATTR(mi_indexed_color, GKSM_FILL_AREA_COLOUR_INDEX);
	break;

    case HASH_ID(ATTRIBUTE_CL, HATCHINDEX_ID):
    case HASH_ID(ATTRIBUTE_CL, PATINDEX_ID):
	GET_INT_ATTR(mi_index, GKSM_FILL_AREA_STYLE_INDEX);
	break;

    case HASH_ID(ATTRIBUTE_CL, FILLREFPT_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_PATTERN_REFERENCE_POINT;
	    mi->CurItem.length	= sizeof(data->patref);
	} else {
	    mi_point(mi, &data->patref.ref, 1);
	}
	break;

    case HASH_ID(ATTRIBUTE_CL, PATTABLE_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_CGM_ELEMENT;
	    mi->CurItem.length	= 0;
#ifdef DEBUG
	    (void) fprintf(stderr, "%s(): pattern table ignored\n", me);
#endif
	}
	break;

    case HASH_ID(ATTRIBUTE_CL, PATSIZE_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_PATTERN_SIZE;
	    mi->CurItem.length	= sizeof(data->patsiz);
	} else {
	    data->patsiz.hgt.x	= mi_vdc(mi);
	    data->patsiz.hgt.y	= mi_vdc(mi);
	    data->patsiz.wid.x	= mi_vdc(mi);
	    data->patsiz.wid.y	= mi_vdc(mi);
	}
	break;

    case HASH_ID(ATTRIBUTE_CL, COLRTABLE_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_COLOUR_REPRESENTATION;
	    mi->CurItem.length	= sizeof(data->corep);
	} else {
	    mi->color_index	= mi_index(mi);
	    data->corep.idx	= mi->color_index;
	    mi_direct_colors(mi, &data->corep, 1);
	    if (BYTES_LEFT(mi) >= size_direct_color())
		mi->mode	= READING_COLOR_TABLE;
	}
	break;

    case HASH_ID(ATTRIBUTE_CL, ASF_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_ASPECT_SOURCE_FLAGS;
	    mi->CurItem.length	= sizeof(data->asf);
	} else {
	    int	iasf;
	    int	cgm_asf[18];

	    for (iasf = 0; iasf < NUM_ELEMENTS(cgm_asf); ++iasf)
		cgm_asf[iasf]	= 0;

	    while (BYTES_LEFT(mi) > 0) {
		iasf		= mi_enum(mi);
		cgm_asf[iasf]	= mi_enum(mi);
	    }

	    for (iasf = 0; iasf < NUM_ELEMENTS(cgm_asf); ++iasf)
		data->asf.asf[gksm_iasf[iasf]]	= cgm_asf[iasf] == 0
							? (int)GINDIVIDUAL
							: (int)GBUNDLED;
	}
	break;

    case HASH_ID(ESCAPE_CL, ESCAPE_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_CGM_ELEMENT;
	    mi->CurItem.length	= 0;
#ifdef DEBUG
	    (void) fprintf(stderr, "%s(): escape request ignored\n", me);
#endif
	}
	break;

    case HASH_ID(EXTERN_CL, MESSAGE_ID):
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_MESSAGE;
	    mi->CurItem.length	= sizeof(data->msg) + BYTES_LEFT(mi) + 1;
	} else {
	    mi_mesg(mi, &data->msg);
	}
	break;

    default:
	if (mode == RETURN_INFO) {
	    mi->CurItem.type	= (int)GKSM_UNKNOWN_ITEM;
	    mi->CurItem.length	= BYTES_LEFT(mi);
	} else {
	    (void) fprintf(stderr, 
			   "%s(): can't decode class=%d, id=%d element\n",
			   me, mi->class, mi->id);
	}
    }						/* hash_id switch */

    return METAFILE_OK;
}


/*
 * Get a command header.
 */
    static int
mi_header(fp, class, id, length)
    FILE	*fp;
    int		*class;
    int		*id;
    unsigned	*length;
{
    unsigned char	bytes[2];
    unsigned short	header;

    if (fread((voidp)bytes, (size_t)1, (size_t)2, fp) != 2)
	return 0;

    header	= NTOH16(bytes);

    *class	= (header >> 12) & 017;
    *id		= (header >>  5) & 0177;
    *length	= header & 037;

    return 1;
}


/*
 * Get a description of the next GKSM item.
 *
 * Note that this routine returns errors or true GKSM items -- CGM elements
 * are handled internally and are not returned.
 */
    static int
mi_item(mi)
    mf_cgmi	*mi;
{
    unsigned	header_length;
    static char	me[]	= "mi_item";

    if (mi->GksmEmpty)
	return 0;

    if (mi->mode == READING_COLOR_TABLE)
	return 1;

    for (;;) {
	(void) fseek(mi->fp, mi->start_next_element, SEEK_SET);

	if (!mi_header(mi->fp, &mi->class, &mi->id, &header_length)) {
	    mi->filestat	= MF_FILE_ERR;
	    return 0;
	}

#if 0
	mi->start_this_element	= mi->start_next_element;
#endif
	mi->hash_id		= HASH_ID(mi->class, mi->id);

	if (header_length == LONG_CMD_LENGTH) {
	    /*
	     * Long-form CGM element.  Accumulate all partition lengths so that
	     * we can return the total length of the data-record.
	     */
	    int		more_data;	/* More data flag */
	    long 	total	= 0;	/* Total data-record length (bytes) */
	    long	first_part	= ftell(mi->fp);
					/* Start of first partition */

	    do {
		int	length;		/* Current partition length */

		mi_pcw(mi->fp, &more_data, &length);
		total	+= length;
		(void) fseek(mi->fp, (long)(length + length%2), SEEK_CUR);
	    } while (more_data);

	    mi->total_length	= total;
	    mi->start_next_element	= ftell(mi->fp);

	    /* Return to 1st partition */
	    (void) fseek(mi->fp, first_part, SEEK_SET);

	    mi_pcw(mi->fp, &more_data, &mi->partition_length);

	} else {
	    /*
	     * Short-form CGM element.
	     */
	    mi->partition_length	= mi->total_length
					= header_length;
	    mi->start_next_element	= ftell(mi->fp) + header_length + 
					    header_length%2;
	}

	mi->total_left		= mi->total_length;
	mi->partition_left	= mi->partition_length;

	mi_parameters(mi, (char*)NULL, RETURN_INFO);

	switch (mi->CurItem.type) {
	case GKSM_CGM_ELEMENT:
	    mi_parameters(mi, (char*)mo_buf, DECODE_VALUES);
	    break;
	case GKSM_UNKNOWN_ITEM:
	    (void) fprintf(stderr, "%s(): element ignored (class=%d, id=%d)\n",
			   me, mi->class, mi->id);
	    break;
	default:				/* Should be GKSM item */
	    return 1;
	}
    }						/* Until-something-to-return
						 * loop */
}


/*
 * Open an input CGM.  Read up through the header of the first non-delimiter
 * and non-metafile-descriptor element (which should be just after the first
 * BEGIN PICTURE element).
 */
    int
CGMmiOpen(mf)
    Metafile	*mf;		/* Metafile structure */
{
    mf_cgmi	*mi	= &mf->cgm.mi;
    static char	me[]	= "CGMmiOpen()";

    if (CHAR_BIT != 8) {
	(void) fprintf(stderr, 
	    "%s: I can't work on platforms where CHAR_BIT != 8.  Sorry.\n",
	    me);
	return 1;
    }

    mi->filestat	= METAFILE_OK;
    mi->mode		= NORMAL_MODE;
    mi->GksmEmpty	= 0;
    mi->txfp.font	= 1;
    mi->txfp.prec	= GSTROKE;
    mi->char_height	= 0.01;
    mi->char_up.x	= 0.;
    mi->char_up.y	= 1.;
    mi->char_base.x	= 1.;
    mi->char_base.y	= 0.;
    mi->start_next_element	= ftell(mi->fp);

#if 0
    (void) mi_item(mi);				/* Get first GKSM item */
    mi_reset(mi);				/* Return to start of item */
#endif

    return mi->filestat == METAFILE_OK ? OK : 1;
}


/*
 * Return the identity of the next item in a Metafile and the amount of storage
 * necessary to contain its data.
 */
    Gint
CGMnextItem(mf)
    Metafile	*mf;		/* Metafile structure */
{
    mf_cgmi	*mi	= &mf->cgm.mi;

    if (feof(mi->fp)) {
	mi->GksmEmpty		= TRUE;
	mi->CurItem.type	= INVALID;
	mi->CurItem.length	= INVALID;
	return METAFILE_OK;
    }

    if (!mi_item(mi)) {
	mi->GksmEmpty		= TRUE;
	mi->CurItem.type	= INVALID;
	mi->CurItem.length	= INVALID;
	return MF_FILE_ERR;
    }

    mi->GksmEmpty	= FALSE;

    return METAFILE_OK;
}


/*
 * Read the data-portion of a Metafile's current item.
 *
 * The filestat field has been added to the workstation state structure to
 * retain MI error information between calls to ggetgksm and greadgksm.  The
 * field has one of four possible integer values (defined in metafile.h):
 *    METAFILE_OK -- no errors so far reading from metafile
 *    MF_DATA_ERR -- type and length of latest item read (current item) are
 *                   ok, but XgksReadData couldn't read the data (eg. non-
 *                   numeric characters found when trying to read an integer)
 *                   The item may be skipped (via greadgksm w/ length 0) and
 *                   MI processing can continue.
 *    MF_ITEM_ERR -- something more serious than a data error found in latest
 *                   item; eg. type invalid, length invalid, data read ter-
 *                   minated prematurely.  This error condition can be detected
 *                   while going on to the next item, so the current item is
 *                   returned correctly, but subsequent attempts to get/read
 *                   will fail.  Since the exact cause of the error is unknown,
 *                   this is not a recoverable condition.
 *    MF_FILE_ERR -- the system reported an I/O error during a read attempt.
 *                   This error is not recoverable.
 * The first function to detect the error will report it, while attempting to
 * process the item it applies to.  In other words, if greadgksm encounters a
 * file error while trying to go on to the next item after successfully reading
 * the current item, the error will not be reported until the next get/read
 * call.  After a fatal error has been reported (via GKS error 163, item is
 * invalid), subsequent get/read attempts will return error 162, no items left
 * in MI, since the error is unrecoverable and no more reading is allowed.
 */
    int
CGMreadItem(mf, record)
    Metafile	*mf;		/* Metafile structure  */
    char        *record;	/* input data-record */
{
    mf_cgmi	*mi	= &mf->cgm.mi;

    if (record != NULL) {
	mi->filestat	= mi_parameters(mi, record, DECODE_VALUES);

	if (feof(mi->fp)) {
	    mi->GksmEmpty	= TRUE;
	    (void) gerrorhand(162, errgreadgksm, xgks_state.gks_err_file);
	    return 162;
	}
	GKSERROR((mi->filestat == MF_ITEM_ERR) || (mi->filestat == MF_FILE_ERR),
		 163, errgreadgksm);
	GKSERROR((mi->filestat == MF_DATA_ERR), 165, errgreadgksm);
    }

    return OK;
}
