/* Copyright (C) 1995, 1996, 1998 artofcode LLC.  All rights reserved.
  
  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: gdevcgml.c,v 1.2.6.1.2.1 2003/01/17 00:49:00 giles Exp $ */
/* CGM-writing library */
#include "memory_.h"
#include "stdio_.h"
#include "gdevcgmx.h"

/* Forward references to command-writing procedures */
private void begin_command(P2(cgm_state *, cgm_op_index));

#define OP(op) begin_command(st, op)
private cgm_result end_command(P1(cgm_state *));

#define END_OP (void)end_command(st)
#define DONE return end_command(st)
/* Parameters */
private void put_int(P3(cgm_state *, cgm_int, int));

#define CI(ci) put_int(st, ci, st->metafile.color_index_precision)
#define I(i) put_int(st, i, st->metafile.integer_precision)
#define IX(ix) put_int(st, ix, st->metafile.index_precision)
#define E(e) put_int(st, (int)(e), 16)
private void put_real(P3(cgm_state *, cgm_real, const cgm_precision *));

#define R(r) put_real(st, r, &st->metafile.real_precision)
private void put_vdc(P2(cgm_state *, const cgm_vdc *));

#define VDC(vdc) put_vdc(st, vdc)
#define VDC2(vdc1, vdc2) VDC(vdc1); VDC(vdc2)
#define VDC4(vdc1, vdc2, vdc3, vdc4) VDC2(vdc1, vdc2); VDC2(vdc3, vdc4)
private void put_vdc_r(P3(cgm_state *, const cgm_line_marker_extent *, cgm_line_marker_specification_mode));

#define VDC_R(vdcr, mode) put_vdc_r(st, vdcr, mode)
private void put_point(P2(cgm_state *, const cgm_point *));

#define P(p) put_point(st, p)
private void put_points(P3(cgm_state *, const cgm_point *, int));

#define nP(p, n) put_points(st, p, n)
private void put_string(P3(cgm_state *, const char *, uint));

#define S(s, l) put_string(st, s, l)
private void put_color(P2(cgm_state *, const cgm_color *));

#define CO(co) put_color(st, co)
private void put_rgb(P2(cgm_state *, const cgm_rgb *));

#define CD(cd) put_rgb(st, cd)
/* Other data types */
#define put_byte(st, b)\
  if ( st->command_count == command_max_count ) write_command(st, false);\
  st->command[st->command_count++] = (byte)(b)
private void put_bytes(P3(cgm_state *, const byte *, uint));
private void write_command(P2(cgm_state *, bool));
private void put_real_precision(P2(cgm_state *, const cgm_precision *));

/* ================ Public routines ================ */

/* ---------------- Initialize/terminate ---------------- */

/* Initialize a CGM writer. */
cgm_state *
cgm_initialize(FILE * file, const cgm_allocator * cal)
{
    cgm_state *st = (*cal->alloc) (cal->private_data, sizeof(cgm_state));

    if (st == 0)
	return 0;
    st->file = file;
    st->allocator = *cal;
    /* Initialize metafile elements. */
    st->metafile.vdc_type = cgm_vdc_integer;
    st->metafile.integer_precision = 16;
    st->metafile.real_precision.representation = cgm_representation_fixed;
    st->metafile.real_precision.exponent_or_whole_width = 16;
    st->metafile.real_precision.fraction_width = 16;
    st->metafile.index_precision = 16;
    st->metafile.color_precision = 8;
    st->metafile.color_index_precision = 8;
    st->metafile.maximum_color_index = 63;
    /* color_value_extent */
    /*st->metafile.character_coding_announcer = 0; */
    /* Initialize picture elements. */
    st->picture.scaling_mode = cgm_scaling_abstract;
    st->picture.color_selection_mode = cgm_color_selection_indexed;
    st->picture.line_width_specification_mode = cgm_line_marker_absolute;
    st->picture.marker_size_specification_mode = cgm_line_marker_absolute;
    st->picture.edge_width_specification_mode = cgm_line_marker_absolute;
    /* vdc_extent */
    /* background_color */
    /* Initialize control elements. */
    st->vdc_integer_precision = st->metafile.integer_precision;
    st->vdc_real_precision = st->metafile.real_precision;
    st->transparency = cgm_transparency_on;
    /* clip_rectangle */
    st->clip_indicator = cgm_clip_on;
    /* Initialize other state elements. */
    st->line_bundle_index = 1;
    st->line_type = cgm_line_solid;
    /* line_width */
    /* line_color */
    st->marker_bundle_index = 1;
    st->marker_type = cgm_marker_asterisk;
    /* marker_size */
    /* marker_color */
    st->text_bundle_index = 1;
    st->text_font_index = 1;
    st->text_precision = cgm_text_precision_string;
    st->character_expansion_factor = 1.0;
    st->character_spacing = 0.0;
    /* text_color */
    /* character_height */
    /* character_orientation */
    st->text_path = cgm_text_path_right;
    /* text_alignment */
    st->character_set_index = 1;
    st->alternate_character_set_index = 1;
    st->fill_bundle_index = 1;
    st->interior_style = cgm_interior_style_hollow;
    st->hatch_index = cgm_hatch_horizontal;
    st->pattern_index = 1;
    st->edge_bundle_index = 1;
    st->edge_type = cgm_edge_solid;
    /* edge_width */
    st->edge_visibility = false;
    /* fill_reference_point */
    /* pattern_table */
    /* pattern_size */
    /* color_table */
    memset(st->source_flags, (byte) cgm_aspect_source_individual,
	   sizeof(st->source_flags));
    return st;
}

/* Terminate a CGM writer. */
cgm_result
cgm_terminate(cgm_state * st)
{
    (*st->allocator.free) (st->allocator.private_data, st);
    return cgm_result_ok;
}

/* ---------------- Metafile elements ---------------- */

cgm_result
cgm_BEGIN_METAFILE(cgm_state * st, const char *str, uint len)
{
    OP(BEGIN_METAFILE);
    S(str, len);
    DONE;
}

cgm_result
cgm_set_metafile_elements(cgm_state * st, const cgm_metafile_elements * meta, long mask)
{
    if ((mask & cgm_set_METAFILE_VERSION)) {
	OP(METAFILE_VERSION);
	I(meta->metafile_version);
	END_OP;
	st->metafile.metafile_version = meta->metafile_version;
    }
    if ((mask & cgm_set_METAFILE_DESCRIPTION)) {
	OP(METAFILE_DESCRIPTION);
	S(meta->metafile_description.chars, meta->metafile_description.length);
	END_OP;
	st->metafile.metafile_description = meta->metafile_description;
    }
    if ((mask & cgm_set_VDC_TYPE)) {
	OP(VDC_TYPE);
	E(meta->vdc_type);
	END_OP;
	st->metafile.vdc_type = meta->vdc_type;
    }
    if ((mask & cgm_set_INTEGER_PRECISION)) {
	OP(INTEGER_PRECISION);
	I(meta->integer_precision);
	END_OP;
	st->metafile.integer_precision = meta->integer_precision;
    }
    if ((mask & cgm_set_REAL_PRECISION)) {
	OP(REAL_PRECISION);
	put_real_precision(st, &meta->real_precision);
	END_OP;
	st->metafile.real_precision = meta->real_precision;
    }
    if ((mask & cgm_set_INDEX_PRECISION)) {
	OP(INDEX_PRECISION);
	I(meta->index_precision);
	END_OP;
	st->metafile.index_precision = meta->index_precision;
    }
    if ((mask & cgm_set_COLOR_PRECISION)) {
	OP(COLOR_PRECISION);
	I(meta->color_precision);
	END_OP;
	st->metafile.color_index_precision = meta->color_index_precision;
    }
    if ((mask & cgm_set_COLOR_INDEX_PRECISION)) {
	OP(COLOR_INDEX_PRECISION);
	I(meta->color_index_precision);
	END_OP;
	st->metafile.color_index_precision = meta->color_index_precision;
    }
    if ((mask & cgm_set_MAXIMUM_COLOR_INDEX)) {
	OP(MAXIMUM_COLOR_INDEX);
	CI(meta->maximum_color_index);
	END_OP;
	st->metafile.maximum_color_index = meta->maximum_color_index;
    }
    if ((mask & cgm_set_METAFILE_ELEMENT_LIST)) {
	int i;
	const int *p;

	OP(METAFILE_ELEMENT_LIST);
	for (i = 0, p = meta->metafile_element_list;
	     i < meta->metafile_element_list_count;
	     i++, p += 2
	    ) {
	    I(p[0]);
	    I(p[1]);
	}
	END_OP;
	st->metafile.metafile_element_list =
	    meta->metafile_element_list;
	st->metafile.metafile_element_list_count =
	    meta->metafile_element_list_count;
    }
    /* element list */
    if ((mask & cgm_set_FONT_LIST)) {
	int i;

	OP(FONT_LIST);
	for (i = 0; i < meta->font_list_count; ++i)
	    S(meta->font_list[i].chars, meta->font_list[i].length);
	END_OP;
	st->metafile.font_list = meta->font_list;
	st->metafile.font_list_count = meta->font_list_count;
    }
    /* character set list */
    /* character coding announcer */
    return st->result;
}

cgm_result
cgm_END_METAFILE(cgm_state * st)
{
    OP(END_METAFILE);
    DONE;
}

/* ---------------- Picture elements ---------------- */

cgm_result
cgm_BEGIN_PICTURE(cgm_state * st, const char *str, uint len)
{
    OP(BEGIN_PICTURE);
    S(str, len);
    DONE;
}

cgm_result
cgm_set_picture_elements(cgm_state * st, const cgm_picture_elements * pic, long mask)
{
    if ((mask & cgm_set_SCALING_MODE)) {
	OP(SCALING_MODE);
	E(pic->scaling_mode);
	R(pic->scale_factor);
	st->picture.scaling_mode = pic->scaling_mode;
	st->picture.scale_factor = pic->scale_factor;
	END_OP;
    }
    if ((mask & cgm_set_COLOR_SELECTION_MODE)) {
	OP(COLOR_SELECTION_MODE);
	E(pic->color_selection_mode);
	END_OP;
	st->picture.color_selection_mode = pic->color_selection_mode;
    }
    if ((mask & cgm_set_LINE_WIDTH_SPECIFICATION_MODE)) {
	OP(LINE_WIDTH_SPECIFICATION_MODE);
	E(pic->line_width_specification_mode);
	END_OP;
	st->picture.line_width_specification_mode = pic->line_width_specification_mode;
    }
    if ((mask & cgm_set_MARKER_SIZE_SPECIFICATION_MODE)) {
	OP(MARKER_SIZE_SPECIFICATION_MODE);
	E(pic->marker_size_specification_mode);
	END_OP;
	st->picture.marker_size_specification_mode = pic->marker_size_specification_mode;
    }
    if ((mask & cgm_set_EDGE_WIDTH_SPECIFICATION_MODE)) {
	OP(EDGE_WIDTH_SPECIFICATION_MODE);
	E(pic->edge_width_specification_mode);
	END_OP;
	st->picture.edge_width_specification_mode = pic->edge_width_specification_mode;
    }
    if ((mask & cgm_set_VDC_EXTENT)) {
	OP(VDC_EXTENT);
	P(&pic->vdc_extent[0]);
	P(&pic->vdc_extent[1]);
	END_OP;
	st->picture.vdc_extent[0] = pic->vdc_extent[0];
	st->picture.vdc_extent[1] = pic->vdc_extent[1];
    }
    if ((mask & cgm_set_BACKGROUND_COLOR)) {
	OP(BACKGROUND_COLOR);
	CD(&pic->background_color.rgb);
	DONE;
	st->picture.background_color = pic->background_color;
    }
    return st->result;
}

cgm_result
cgm_BEGIN_PICTURE_BODY(cgm_state * st)
{
    OP(BEGIN_PICTURE_BODY);
    DONE;
}

cgm_result
cgm_END_PICTURE(cgm_state * st)
{
    OP(END_PICTURE);
    DONE;
}

/* ---------------- Control elements ---------------- */

cgm_result
cgm_VDC_INTEGER_PRECISION(cgm_state * st, int precision)
{
    if (st->vdc_integer_precision != precision) {
	OP(VDC_INTEGER_PRECISION);
	I(precision);
	st->vdc_integer_precision = precision;
	DONE;
    } else
	return cgm_result_ok;
}

cgm_result
cgm_VDC_REAL_PRECISION(cgm_state * st, const cgm_precision * precision)
{
    OP(VDC_REAL_PRECISION);
    put_real_precision(st, precision);
    st->vdc_real_precision = *precision;
    DONE;
}

cgm_result
cgm_AUXILIARY_COLOR(cgm_state * st, const cgm_color * color)
{
    OP(AUXILIARY_COLOR);
    CO(color);
    st->auxiliary_color = *color;
    DONE;
}

cgm_result
cgm_TRANSPARENCY(cgm_state * st, cgm_transparency transparency)
{
    OP(TRANSPARENCY);
    E(transparency);
    st->transparency = transparency;
    DONE;
}

cgm_result
cgm_CLIP_RECTANGLE(cgm_state * st, const cgm_point rectangle[2])
{
    OP(CLIP_RECTANGLE);
    P(&rectangle[0]);
    st->clip_rectangle[0] = rectangle[0];
    P(&rectangle[1]);
    st->clip_rectangle[1] = rectangle[1];
    DONE;
}

cgm_result
cgm_CLIP_INDICATOR(cgm_state * st, cgm_clip_indicator clip)
{
    OP(CLIP_INDICATOR);
    E(clip);
    st->clip_indicator = clip;
    DONE;
}

/* ---------------- Graphical primitive elements ---------------- */

cgm_result
cgm_POLYLINE(cgm_state * st, const cgm_point * vertices, int count)
{
    OP(POLYLINE);
    nP(vertices, count);
    DONE;
}

cgm_result
cgm_DISJOINT_POLYLINE(cgm_state * st, const cgm_point * endpoints, int count)
{
    OP(DISJOINT_POLYLINE);
    nP(endpoints, count);
    DONE;
}

cgm_result
cgm_POLYMARKER(cgm_state * st, const cgm_point * positions, int count)
{
    OP(POLYMARKER);
    nP(positions, count);
    DONE;
}

cgm_result
cgm_TEXT(cgm_state * st, const cgm_point * position, bool final, const char *str, uint len)
{
    OP(TEXT);
    P(position);
    E(final);
    S(str, len);
    DONE;
}

cgm_result
cgm_RESTRICTED_TEXT(cgm_state * st, const cgm_vdc * delta_width, const cgm_vdc * delta_height, const cgm_point * position, bool final, const char *str, uint len)
{
    OP(RESTRICTED_TEXT);
    VDC2(delta_width, delta_height);
    P(position);
    E(final);
    S(str, len);
    DONE;
}

cgm_result
cgm_APPEND_TEXT(cgm_state * st, bool final, const char *str, uint len)
{
    OP(APPEND_TEXT);
    E(final);
    S(str, len);
    DONE;
}

cgm_result
cgm_POLYGON(cgm_state * st, const cgm_point * vertices, int count)
{
    OP(POLYGON);
    nP(vertices, count);
    DONE;
}

cgm_result
cgm_POLYGON_SET(cgm_state * st, const cgm_polygon_edge * vertices, int count)
{
    int i;

    OP(POLYGON);
    for (i = 0; i < count; ++i) {
	P(&vertices[i].vertex);
	E(vertices[i].edge_out);
    }
    DONE;
}

cgm_result
cgm_CELL_ARRAY(cgm_state * st, const cgm_point * pqr /*[3] */ , cgm_int nx, cgm_int ny, cgm_int local_color_precision, cgm_cell_representation_mode mode, const byte * values, uint source_bit, uint raster)
{
    int precision = local_color_precision;
    int bits_per_pixel;
    uint row_bytes;
    const byte *row = values + (source_bit >> 3);
    int bit = source_bit & 7;
    int y;

    /* Currently we ignore the cell representation_mode, and always */
    /* produce cell arrays in 'packed' format. */
    mode = cgm_cell_mode_packed;
    OP(CELL_ARRAY);
    nP(pqr, 3);
    I(nx);
    I(ny);
    I(local_color_precision);
    E(mode);
    if (precision == 0)
	precision = (st->picture.color_selection_mode ==
		     cgm_color_selection_indexed ?
		     st->metafile.color_index_precision :
		     st->metafile.color_precision);
    bits_per_pixel =
	(st->picture.color_selection_mode == cgm_color_selection_indexed ?
	 precision : precision * 3);
    row_bytes = (bits_per_pixel * nx + 7) >> 3;
    for (y = 0; y < ny; y++, row += raster) {
	if (bit == 0)
	    put_bytes(st, row, row_bytes);
	else {
	    uint i;

	    for (i = 0; i < row_bytes; i++) {
		byte b = (row[i] << bit) +
		(row[i + 1] >> (8 - bit));

		put_byte(st, b);
	    }
	}
	if ((row_bytes & 1)) {
	    put_byte(st, 0);
	}
    }
    DONE;
}

cgm_result
cgm_RECTANGLE(cgm_state * st, const cgm_point * corner1, const cgm_point * corner2)
{
    OP(RECTANGLE);
    P(corner1);
    P(corner2);
    DONE;
}

cgm_result
cgm_CIRCLE(cgm_state * st, const cgm_point * center, const cgm_vdc * radius)
{
    OP(CIRCLE);
    P(center);
    VDC(radius);
    DONE;
}

cgm_result
cgm_CIRCULAR_ARC_3_POINT(cgm_state * st, const cgm_point * start, const cgm_point * intermediate, const cgm_point * end)
{
    OP(CIRCULAR_ARC_3_POINT);
    P(start);
    P(intermediate);
    P(end);
    DONE;
}

cgm_result
cgm_CIRCULAR_ARC_3_POINT_CLOSE(cgm_state * st, const cgm_point * start, const cgm_point * intermediate, const cgm_point * end, cgm_arc_closure closure)
{
    OP(CIRCULAR_ARC_3_POINT_CLOSE);
    P(start);
    P(intermediate);
    P(end);
    E(closure);
    DONE;
}

cgm_result
cgm_CIRCULAR_ARC_CENTER(cgm_state * st, const cgm_point * center, const cgm_vdc * dx_start, const cgm_vdc * dy_start, const cgm_vdc * dx_end, const cgm_vdc * dy_end, const cgm_vdc * radius)
{
    OP(CIRCULAR_ARC_CENTER);
    P(center);
    VDC4(dx_start, dy_start, dx_end, dy_end);
    VDC(radius);
    DONE;
}

cgm_result
cgm_CIRCULAR_ARC_CENTER_CLOSE(cgm_state * st, const cgm_point * center, const cgm_vdc * dx_start, const cgm_vdc * dy_start, const cgm_vdc * dx_end, const cgm_vdc * dy_end, const cgm_vdc * radius, cgm_arc_closure closure)
{
    OP(CIRCULAR_ARC_CENTER_CLOSE);
    P(center);
    VDC4(dx_start, dy_start, dx_end, dy_end);
    VDC(radius);
    E(closure);
    DONE;
}

cgm_result
cgm_ELLIPSE(cgm_state * st, const cgm_point * center, const cgm_point * cd1_end, const cgm_point * cd2_end)
{
    OP(ELLIPSE);
    P(center);
    P(cd1_end);
    P(cd2_end);
    DONE;
}

cgm_result
cgm_ELLIPTICAL_ARC(cgm_state * st, const cgm_point * center, const cgm_point * cd1_end, const cgm_point * cd2_end, const cgm_vdc * dx_start, const cgm_vdc * dy_start, const cgm_vdc * dx_end, const cgm_vdc * dy_end)
{
    OP(ELLIPTICAL_ARC);
    P(center);
    P(cd1_end);
    P(cd2_end);
    VDC4(dx_start, dy_start, dx_end, dy_end);
    DONE;
}

cgm_result
cgm_ELLIPTICAL_ARC_CLOSE(cgm_state * st, const cgm_point * center, const cgm_point * cd1_end, const cgm_point * cd2_end, const cgm_vdc * dx_start, const cgm_vdc * dy_start, const cgm_vdc * dx_end, const cgm_vdc * dy_end, cgm_arc_closure closure)
{
    OP(ELLIPTICAL_ARC_CLOSE);
    P(center);
    P(cd1_end);
    P(cd2_end);
    VDC4(dx_start, dy_start, dx_end, dy_end);
    E(closure);
    DONE;
}

/* ---------------- Attribute elements ---------------- */

cgm_result
cgm_LINE_BUNDLE_INDEX(cgm_state * st, cgm_int index)
{
    OP(LINE_BUNDLE_INDEX);
    IX(index);
    st->line_bundle_index = index;
    DONE;
}

cgm_result
cgm_LINE_TYPE(cgm_state * st, cgm_line_type line_type)
{
    OP(LINE_TYPE);
    IX((int)line_type);
    st->line_type = line_type;
    DONE;
}

cgm_result
cgm_LINE_WIDTH(cgm_state * st, const cgm_line_width * line_width)
{
    OP(LINE_WIDTH);
    VDC_R(line_width, st->picture.line_width_specification_mode);
    st->line_width = *line_width;
    DONE;
}

cgm_result
cgm_LINE_COLOR(cgm_state * st, const cgm_color * color)
{
    OP(LINE_COLOR);
    CO(color);
    st->line_color = *color;
    DONE;
}

cgm_result
cgm_MARKER_BUNDLE_INDEX(cgm_state * st, cgm_int index)
{
    OP(MARKER_BUNDLE_INDEX);
    IX(index);
    st->marker_bundle_index = index;
    DONE;
}

cgm_result
cgm_MARKER_TYPE(cgm_state * st, cgm_marker_type marker_type)
{
    OP(MARKER_TYPE);
    IX((int)marker_type);
    st->marker_type = marker_type;
    DONE;
}

cgm_result
cgm_MARKER_SIZE(cgm_state * st, const cgm_marker_size * marker_size)
{
    OP(MARKER_SIZE);
    VDC_R(marker_size, st->picture.marker_size_specification_mode);
    st->marker_size = *marker_size;
    DONE;
}

cgm_result
cgm_MARKER_COLOR(cgm_state * st, const cgm_color * color)
{
    OP(MARKER_COLOR);
    CO(color);
    st->marker_color = *color;
    DONE;
}

cgm_result
cgm_TEXT_BUNDLE_INDEX(cgm_state * st, cgm_int index)
{
    OP(TEXT_BUNDLE_INDEX);
    IX(index);
    st->text_bundle_index = index;
    DONE;
}

cgm_result
cgm_TEXT_FONT_INDEX(cgm_state * st, cgm_int index)
{
    OP(TEXT_FONT_INDEX);
    IX(index);
    st->text_font_index = index;
    DONE;
}

cgm_result
cgm_TEXT_PRECISION(cgm_state * st, cgm_text_precision precision)
{
    OP(TEXT_PRECISION);
    E(precision);
    st->text_precision = precision;
    DONE;
}

cgm_result
cgm_CHARACTER_EXPANSION_FACTOR(cgm_state * st, cgm_real factor)
{
    OP(CHARACTER_EXPANSION_FACTOR);
    R(factor);
    st->character_expansion_factor = factor;
    DONE;
}

cgm_result
cgm_CHARACTER_SPACING(cgm_state * st, cgm_real spacing)
{
    OP(CHARACTER_SPACING);
    R(spacing);
    st->character_spacing = spacing;
    DONE;
}

cgm_result
cgm_TEXT_COLOR(cgm_state * st, const cgm_color * color)
{
    OP(TEXT_COLOR);
    CO(color);
    st->text_color = *color;
    DONE;
}

cgm_result
cgm_CHARACTER_HEIGHT(cgm_state * st, const cgm_vdc * height)
{
    OP(CHARACTER_HEIGHT);
    VDC(height);
    st->character_height = *height;
    DONE;
}

cgm_result
cgm_CHARACTER_ORIENTATION(cgm_state * st, const cgm_vdc * x_up, const cgm_vdc * y_up, const cgm_vdc * x_base, const cgm_vdc * y_base)
{
    OP(CHARACTER_ORIENTATION);
    VDC4(x_up, y_up, x_base, y_base);
    st->character_orientation[0] = *x_up;
    st->character_orientation[1] = *y_up;
    st->character_orientation[2] = *x_base;
    st->character_orientation[3] = *y_base;
    DONE;
}

cgm_result
cgm_TEXT_PATH(cgm_state * st, cgm_text_path text_path)
{
    OP(TEXT_PATH);
    E(text_path);
    st->text_path = text_path;
    DONE;
}

cgm_result
cgm_TEXT_ALIGNMENT(cgm_state * st, cgm_text_alignment_horizontal align_h, cgm_text_alignment_vertical align_v, cgm_real align_cont_h, cgm_real align_cont_v)
{
    OP(TEXT_ALIGNMENT);
    E(align_h);
    E(align_v);
    R(align_cont_h);
    R(align_cont_v);
    DONE;
}

cgm_result
cgm_CHARACTER_SET_INDEX(cgm_state * st, cgm_int index)
{
    OP(CHARACTER_SET_INDEX);
    IX(index);
    st->character_set_index = index;
    DONE;
}

/* See gdevcgml.c for why this isn't named cgm_ALTERNATE_.... */
cgm_result
cgm_ALT_CHARACTER_SET_INDEX(cgm_state * st, cgm_int index)
{
    OP(ALTERNATE_CHARACTER_SET_INDEX);
    IX(index);
    st->alternate_character_set_index = index;
    DONE;
}

cgm_result
cgm_FILL_BUNDLE_INDEX(cgm_state * st, cgm_int index)
{
    OP(FILL_BUNDLE_INDEX);
    IX(index);
    st->fill_bundle_index = index;
    DONE;
}

cgm_result
cgm_INTERIOR_STYLE(cgm_state * st, cgm_interior_style interior_style)
{
    OP(INTERIOR_STYLE);
    E(interior_style);
    st->interior_style = interior_style;
    DONE;
}

cgm_result
cgm_FILL_COLOR(cgm_state * st, const cgm_color * color)
{
    OP(FILL_COLOR);
    CO(color);
    st->fill_color = *color;
    DONE;
}

cgm_result
cgm_HATCH_INDEX(cgm_state * st, cgm_hatch_index hatch_index)
{
    OP(HATCH_INDEX);
    IX((int)hatch_index);
    st->hatch_index = hatch_index;
    DONE;
}

cgm_result
cgm_PATTERN_INDEX(cgm_state * st, cgm_int index)
{
    OP(PATTERN_INDEX);
    IX(index);
    st->pattern_index = index;
    DONE;
}

cgm_result
cgm_EDGE_BUNDLE_INDEX(cgm_state * st, cgm_int index)
{
    OP(EDGE_BUNDLE_INDEX);
    IX(index);
    st->edge_bundle_index = index;
    DONE;
}

cgm_result
cgm_EDGE_TYPE(cgm_state * st, cgm_edge_type edge_type)
{
    OP(EDGE_TYPE);
    IX((int)edge_type);
    st->edge_type = edge_type;
    DONE;
}

cgm_result
cgm_EDGE_WIDTH(cgm_state * st, const cgm_edge_width * edge_width)
{
    OP(EDGE_WIDTH);
    VDC_R(edge_width, st->picture.edge_width_specification_mode);
    st->edge_width = *edge_width;
    DONE;
}

cgm_result
cgm_EDGE_COLOR(cgm_state * st, const cgm_color * color)
{
    OP(EDGE_COLOR);
    CO(color);
    DONE;
}

cgm_result
cgm_EDGE_VISIBILITY(cgm_state * st, bool visibility)
{
    OP(EDGE_VISIBILITY);
    E(visibility);
    st->edge_visibility = visibility;
    DONE;
}

cgm_result
cgm_FILL_REFERENCE_POINT(cgm_state * st, const cgm_point * reference_point)
{
    OP(FILL_REFERENCE_POINT);
    P(reference_point);
    st->fill_reference_point = *reference_point;
    DONE;
}

/* PATTERN_TABLE */

cgm_result
cgm_PATTERN_SIZE(cgm_state * st, const cgm_vdc * x_height, const cgm_vdc * y_height, const cgm_vdc * x_width, const cgm_vdc * y_width)
{
    OP(PATTERN_SIZE);
    VDC4(x_height, y_height, x_width, y_width);
    st->pattern_size[0] = *x_height;
    st->pattern_size[1] = *y_height;
    st->pattern_size[2] = *x_width;
    st->pattern_size[3] = *y_width;
    DONE;
}

cgm_result
cgm_COLOR_TABLE(cgm_state * st, cgm_int index, const cgm_color * values, int count)
{
    int i;

    OP(COLOR_TABLE);
    CI(index);
    for (i = 0; i < count; ++i)
	CD(&values[i].rgb);
    DONE;
}

cgm_result 
cgm_ASPECT_SOURCE_FLAGS(cgm_state * st, const cgm_aspect_source_flag * flags, int count)
{
    int i;

    OP(ASPECT_SOURCE_FLAGS);
    for (i = 0; i < count; ++i) {
	E(flags[i].type);
	E(flags[i].source);
	st->source_flags[flags[i].type] = (byte) flags[i].source;
    }
    DONE;
}

/* ================ Internal routines ================ */

/* Begin a command. */
private void
begin_command(cgm_state * st, cgm_op_index op)
{
    uint op_word = (uint) op << cgm_op_id_shift;

    st->command[0] = (byte) (op_word >> 8);
    st->command[1] = (byte) (op_word);
    st->command_count = 4;	/* leave room for extension */
    st->command_first = true;
    st->result = cgm_result_ok;
}

/* Write the buffer for a partial command. */
/* Note that we always write an even number of bytes. */
private void
write_command(cgm_state * st, bool last)
{
    byte *command = st->command;
    int count = st->command_count;

    if (st->command_first) {
	if (count <= 34) {
	    command[2] = command[0];
	    command[3] = command[1] + count - 4;
	    command += 2, count -= 2;
	} else {
	    int pcount = count - 4;

	    command[1] |= 31;
	    command[2] = (byte) (pcount >> 8);
	    if (!last)
		command[2] |= 0x80;
	    command[3] = (byte) pcount;
	}
	st->command_first = false;
    } else {
	int pcount = count - 2;

	command[0] = (byte) (pcount >> 8);
	if (!last)
	    command[0] |= 0x80;
	command[1] = (byte) pcount;
    }
    fwrite(command, sizeof(byte), count + (count & 1), st->file);
    st->command_count = 2;	/* leave room for extension header */
    if (ferror(st->file))
	st->result = cgm_result_io_error;
}

/* End a command. */
private cgm_result
end_command(cgm_state * st)
{
    write_command(st, true);
    return st->result;
}

/* Put an integer value. */
private void
put_int(cgm_state * st, cgm_int value, int precision)
{
    switch (precision) {
	case 32:
	    put_byte(st, value >> 24);
	case 24:
	    put_byte(st, value >> 16);
	case 16:
	    put_byte(st, value >> 8);
	case 8:
	    put_byte(st, value);
    }
}

/* Put a real value. */
private void
put_real(cgm_state * st, cgm_real value, const cgm_precision * pr)
{
    if (pr->representation == cgm_representation_floating) {
    } else {			/* Casting to integer simply discards the fraction, */
	/* so we need to be careful with negative values. */
	long whole = (long)value;
	double fpart;

	if (value < whole)
	    --whole;
	fpart = value - whole;
	put_int(st, whole, pr->exponent_or_whole_width);
	if (pr->fraction_width == 16) {
	    uint fraction = (uint) (fpart * (1.0 * 0x10000));

	    put_int(st, fraction, 16);
	} else {		/* pr->fraction_width == 32 */
	    ulong fraction =
	    (ulong) (fpart * (1.0 * 0x10000 * 0x10000));

	    put_int(st, fraction, 32);
	}
    }
}

/* Put a real precision. */
private void
put_real_precision(cgm_state * st, const cgm_precision * precision)
{
    I((int)precision->representation);
    I(precision->exponent_or_whole_width);
    I(precision->fraction_width);
}

/* Put a VDC. */
private void
put_vdc(cgm_state * st, const cgm_vdc * pvdc)
{
    if (st->metafile.vdc_type == cgm_vdc_integer)
	put_int(st, pvdc->integer, st->vdc_integer_precision);
    else
	put_real(st, pvdc->real, &st->vdc_real_precision);
}

/* Put a VDC or a real. */
private void
put_vdc_r(cgm_state * st, const cgm_line_marker_extent * extent,
	  cgm_line_marker_specification_mode mode)
{
    if (mode == cgm_line_marker_absolute)
	VDC(&extent->absolute);
    else
	R(extent->scaled);
}

/* Put a point (pair of VDCs). */
private void
put_point(cgm_state * st, const cgm_point * ppt)
{
    if (st->metafile.vdc_type == cgm_vdc_integer) {
	put_int(st, ppt->integer.x, st->vdc_integer_precision);
	put_int(st, ppt->integer.y, st->vdc_integer_precision);
    } else {
	put_real(st, ppt->real.x, &st->vdc_real_precision);
	put_real(st, ppt->real.y, &st->vdc_real_precision);
    }
}

/* Put a list of points. */
private void
put_points(cgm_state * st, const cgm_point * ppt, int count)
{
    int i;

    for (i = 0; i < count; i++)
	P(ppt + i);
}

/* Put bytes. */
private void
put_bytes(cgm_state * st, const byte * data, uint length)
{
    int count;

    while (length > (count = command_max_count - st->command_count)) {
	memcpy(st->command + st->command_count, data, count);
	st->command_count += count;
	write_command(st, false);
	data += count;
	length -= count;
    }
    memcpy(st->command + st->command_count, data, length);
    st->command_count += length;
}

/* Put a string. */
private void
put_string(cgm_state * st, const char *data, uint length)
{				/* The CGM specification seems to imply that the continuation */
    /* mechanism for commands and the mechanism for strings */
    /* are orthogonal; we take this interpretation. */
    if (length >= 255) {
	put_byte(st, 255);
	while (length > 32767) {
	    put_int(st, 65535, 2);
	    put_bytes(st, (const byte *)data, 32767);
	    data += 32767;
	    length -= 32767;
	}
    }
    put_byte(st, length);
    put_bytes(st, (const byte *)data, length);
}

/* Put a color. */
private void
put_color(cgm_state * st, const cgm_color * color)
{
    if (st->picture.color_selection_mode == cgm_color_selection_indexed)
	CI(color->index);
    else
	CD(&color->rgb);
}

/* Put an RGB value. */
private void
put_rgb(cgm_state * st, const cgm_rgb * rgb)
{
    put_int(st, rgb->r, st->metafile.color_precision);
    put_int(st, rgb->g, st->metafile.color_precision);
    put_int(st, rgb->b, st->metafile.color_precision);
}
