/* Copyright (C) 2001-2006 Artifex Software, Inc.
   All Rights Reserved.
  
   This software is provided AS-IS with no warranty, either express or
   implied.

   This software is distributed under license and may not be copied, modified
   or distributed except as expressly authorized under the terms of that
   license.  Refer to licensing information at http://www.artifex.com/
   or contact Artifex Software, Inc.,  7 Mt. Lassen Drive - Suite A-134,
   San Rafael, CA  94903, U.S.A., +1(415)492-9861, for further information.
*/
/* $Id: gdevcgml.c 8250 2007-09-25 13:31:24Z giles $ */
/* CGM-writing library */
#include "memory_.h"
#include "stdio_.h"
#include "gdevcgmx.h"

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

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

#define END_OP (void)end_command(st)
#define DONE return end_command(st)
/* Parameters */
static void put_int(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)
static void put_real(cgm_state *, cgm_real, const cgm_precision *);

#define R(r) put_real(st, r, &st->metafile.real_precision)
static void put_vdc(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)
static void put_vdc_r(cgm_state *, const cgm_line_marker_extent *, cgm_line_marker_specification_mode);

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

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

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

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

#define CO(co) put_color(st, co)
static void put_rgb(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)
static void put_bytes(cgm_state *, const byte *, uint);
static void write_command(cgm_state *, bool);
static void put_real_precision(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. */
static 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. */
static 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. */
static cgm_result
end_command(cgm_state * st)
{
    write_command(st, true);
    return st->result;
}

/* Put an integer value. */
static 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. */
static 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. */
static 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. */
static 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. */
static 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). */
static 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. */
static 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. */
static 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. */
static 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. */
static 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. */
static 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);
}
