/* Copyright (C) 1991, 2000 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: gxclist.c,v 1.3.6.1.2.1 2003/01/17 00:49:03 giles Exp $ */
/* Command list document- and page-level code. */
#include "memory_.h"
#include "string_.h"
#include "gx.h"
#include "gp.h"
#include "gpcheck.h"
#include "gserrors.h"
#include "gxdevice.h"
#include "gxdevmem.h"		/* must precede gxcldev.h */
#include "gxcldev.h"
#include "gxclpath.h"
#include "gsparams.h"

/* GC information */
#define CLIST_IS_WRITER(cdev) ((cdev)->common.ymin < 0)
extern_st(st_imager_state);
private
ENUM_PTRS_WITH(device_clist_enum_ptrs, gx_device_clist *cdev)
    if (index < st_device_forward_max_ptrs) {
	gs_ptr_type_t ret = ENUM_USING_PREFIX(st_device_forward, 0);

	return (ret ? ret : ENUM_OBJ(0));
    }
    if (!CLIST_IS_WRITER(cdev))
	return 0;
    index -= st_device_forward_max_ptrs;
    switch (index) {
    case 0: return ENUM_OBJ((cdev->writer.image_enum_id != gs_no_id ?
			     cdev->writer.clip_path : 0));
    case 1: return ENUM_OBJ((cdev->writer.image_enum_id != gs_no_id ?
			     cdev->writer.color_space.space : 0));
    default:
	return ENUM_USING(st_imager_state, &cdev->writer.imager_state,
			  sizeof(gs_imager_state), index - 2);
    }
ENUM_PTRS_END
private
RELOC_PTRS_WITH(device_clist_reloc_ptrs, gx_device_clist *cdev)
{
    RELOC_PREFIX(st_device_forward);
    if (!CLIST_IS_WRITER(cdev))
	return;
    if (cdev->writer.image_enum_id != gs_no_id) {
	RELOC_VAR(cdev->writer.clip_path);
	RELOC_VAR(cdev->writer.color_space.space);
    }
    RELOC_USING(st_imager_state, &cdev->writer.imager_state,
		sizeof(gs_imager_state));
} RELOC_PTRS_END
public_st_device_clist();

/* Forward declarations of driver procedures */
private dev_proc_open_device(clist_open);
private dev_proc_output_page(clist_output_page);
private dev_proc_close_device(clist_close);
private dev_proc_get_band(clist_get_band);
/* Driver procedures defined in other files are declared in gxcldev.h. */

/* Other forward declarations */
private int clist_put_current_params(P1(gx_device_clist_writer *cldev));

/* The device procedures */
const gx_device_procs gs_clist_device_procs = {
    clist_open,
    gx_forward_get_initial_matrix,
    gx_default_sync_output,
    clist_output_page,
    clist_close,
    gx_forward_map_rgb_color,
    gx_forward_map_color_rgb,
    clist_fill_rectangle,
    gx_default_tile_rectangle,
    clist_copy_mono,
    clist_copy_color,
    gx_default_draw_line,
    gx_default_get_bits,
    gx_forward_get_params,
    gx_forward_put_params,
    gx_forward_map_cmyk_color,
    gx_forward_get_xfont_procs,
    gx_forward_get_xfont_device,
    gx_forward_map_rgb_alpha_color,
    gx_forward_get_page_device,
    gx_forward_get_alpha_bits,
    clist_copy_alpha,
    clist_get_band,
    gx_default_copy_rop,
    clist_fill_path,
    clist_stroke_path,
    clist_fill_mask,
    gx_default_fill_trapezoid,
    clist_fill_parallelogram,
    clist_fill_triangle,
    gx_default_draw_thin_line,
    gx_default_begin_image,
    gx_default_image_data,
    gx_default_end_image,
    clist_strip_tile_rectangle,
    clist_strip_copy_rop,
    gx_forward_get_clipping_box,
    clist_begin_typed_image,
    clist_get_bits_rectangle,
    gx_forward_map_color_rgb_alpha,
    clist_create_compositor,
    gx_forward_get_hardware_params,
    gx_default_text_begin,
    gx_default_finish_copydevice
};

/* ------ Define the command set and syntax ------ */

/* Initialization for imager state. */
/* The initial scale is arbitrary. */
const gs_imager_state clist_imager_state_initial =
{gs_imager_state_initial(300.0 / 72.0)};

/*
 * The buffer area (data, data_size) holds a bitmap cache when both writing
 * and reading.  The rest of the space is used for the command buffer and
 * band state bookkeeping when writing, and for the rendering buffer (image
 * device) when reading.  For the moment, we divide the space up
 * arbitrarily, except that we allocate less space for the bitmap cache if
 * the device doesn't need halftoning.
 *
 * All the routines for allocating tables in the buffer are idempotent, so
 * they can be used to check whether a given-size buffer is large enough.
 */

/*
 * Calculate the desired size for the tile cache.
 */
private uint
clist_tile_cache_size(const gx_device * target, uint data_size)
{
    uint bits_size =
    (data_size / 5) & -align_cached_bits_mod;	/* arbitrary */

    if (!gx_device_must_halftone(target)) {	/* No halftones -- cache holds only Patterns & characters. */
	bits_size -= bits_size >> 2;
    }
#define min_bits_size 1024
    if (bits_size < min_bits_size)
	bits_size = min_bits_size;
#undef min_bits_size
    return bits_size;
}

/*
 * Initialize the allocation for the tile cache.  Sets: tile_hash_mask,
 * tile_max_count, tile_table, chunk (structure), bits (structure).
 */
private int
clist_init_tile_cache(gx_device * dev, byte * init_data, ulong data_size)
{
    gx_device_clist_writer * const cdev =
	&((gx_device_clist *)dev)->writer;
    byte *data = init_data;
    uint bits_size = data_size;
    /*
     * Partition the bits area between the hash table and the actual
     * bitmaps.  The per-bitmap overhead is about 24 bytes; if the
     * average character size is 10 points, its bitmap takes about 24 +
     * 0.5 * 10/72 * xdpi * 10/72 * ydpi / 8 bytes (the 0.5 being a
     * fudge factor to account for characters being narrower than they
     * are tall), which gives us a guideline for the size of the hash
     * table.
     */
    uint avg_char_size =
	(uint)(dev->HWResolution[0] * dev->HWResolution[1] *
	       (0.5 * 10 / 72 * 10 / 72 / 8)) + 24;
    uint hc = bits_size / avg_char_size;
    uint hsize;

    while ((hc + 1) & hc)
	hc |= hc >> 1;		/* make mask (power of 2 - 1) */
    if (hc < 0xff)
	hc = 0xff;		/* make allowance for halftone tiles */
    else if (hc > 0xfff)
	hc = 0xfff;		/* cmd_op_set_tile_index has 12-bit operand */
    /* Make sure the tables will fit. */
    while (hc >= 3 && (hsize = (hc + 1) * sizeof(tile_hash)) >= bits_size)
	hc >>= 1;
    if (hc < 3)
	return_error(gs_error_rangecheck);
    cdev->tile_hash_mask = hc;
    cdev->tile_max_count = hc - (hc >> 2);
    cdev->tile_table = (tile_hash *) data;
    data += hsize;
    bits_size -= hsize;
    gx_bits_cache_chunk_init(&cdev->chunk, data, bits_size);
    gx_bits_cache_init(&cdev->bits, &cdev->chunk);
    return 0;
}

/*
 * Initialize the allocation for the bands.  Requires: target.  Sets:
 * page_band_height (=page_info.band_params.BandHeight), nbands.
 */
private int
clist_init_bands(gx_device * dev, gx_device_memory *bdev, uint data_size,
		 int band_width, int band_height)
{
    gx_device_clist_writer * const cdev =
	&((gx_device_clist *)dev)->writer;
    int nbands;

    if (gdev_mem_data_size(bdev, band_width, band_height) > data_size)
	return_error(gs_error_rangecheck);
    cdev->page_band_height = band_height;
    nbands = (cdev->target->height + band_height - 1) / band_height;
    cdev->nbands = nbands;
#ifdef DEBUG
    if (gs_debug_c('l') | gs_debug_c(':'))
	dlprintf4("[:]width=%d, band_width=%d, band_height=%d, nbands=%d\n",
		  bdev->width, band_width, band_height, nbands);
#endif
    return 0;
}

/*
 * Initialize the allocation for the band states, which are used only
 * when writing.  Requires: nbands.  Sets: states, cbuf, cend.
 */
private int
clist_init_states(gx_device * dev, byte * init_data, uint data_size)
{
    gx_device_clist_writer * const cdev =
	&((gx_device_clist *)dev)->writer;
    ulong state_size = cdev->nbands * (ulong) sizeof(gx_clist_state);

    /*
     * The +100 in the next line is bogus, but we don't know what the
     * real check should be. We're effectively assuring that at least 100
     * bytes will be available to buffer command operands.
     */
    if (state_size + sizeof(cmd_prefix) + cmd_largest_size + 100 > data_size)
	return_error(gs_error_rangecheck);
    cdev->states = (gx_clist_state *) init_data;
    cdev->cbuf = init_data + state_size;
    cdev->cend = init_data + data_size;
    return 0;
}

/*
 * Initialize all the data allocations.  Requires: target.  Sets:
 * page_tile_cache_size, page_info.band_params.BandWidth,
 * page_info.band_params.BandBufferSpace, + see above.
 */
private int
clist_init_data(gx_device * dev, byte * init_data, uint data_size)
{
    gx_device_clist_writer * const cdev =
	&((gx_device_clist *)dev)->writer;
    gx_device *target = cdev->target;
    const int band_width =
	cdev->page_info.band_params.BandWidth =
	(cdev->band_params.BandWidth ? cdev->band_params.BandWidth :
	 target->width);
    int band_height = cdev->band_params.BandHeight;
    const uint band_space =
    cdev->page_info.band_params.BandBufferSpace =
	(cdev->band_params.BandBufferSpace ?
	 cdev->band_params.BandBufferSpace : data_size);
    byte *data = init_data;
    uint size = band_space;
    uint bits_size;
    gx_device_memory bdev;
    gx_device *pbdev = (gx_device *)&bdev;
    int code;

    /* Call create_buf_device to get the memory planarity set up. */
    cdev->buf_procs.create_buf_device(&pbdev, target, NULL, NULL, true);
    if (band_height) {
	/*
	 * The band height is fixed, so the band buffer requirement
	 * is completely determined.
	 */
	uint band_data_size =
	    gdev_mem_data_size(&bdev, band_width, band_height);

	if (band_data_size >= band_space)
	    return_error(gs_error_rangecheck);
	bits_size = min(band_space - band_data_size, data_size >> 1);
    } else {
	/*
	 * Choose the largest band height that will fit in the
	 * rendering-time buffer.
	 */
	bits_size = clist_tile_cache_size(target, band_space);
	bits_size = min(bits_size, data_size >> 1);
	band_height = gdev_mem_max_height(&bdev, band_width,
					  band_space - bits_size);
	if (band_height == 0)
	    return_error(gs_error_rangecheck);
    }
    code = clist_init_tile_cache(dev, data, bits_size);
    if (code < 0)
	return code;
    cdev->page_tile_cache_size = bits_size;
    data += bits_size;
    size -= bits_size;
    code = clist_init_bands(dev, &bdev, size, band_width, band_height);
    if (code < 0)
	return code;
    return clist_init_states(dev, data, data_size - bits_size);
}
/*
 * Reset the device state (for writing).  This routine requires only
 * data, data_size, and target to be set, and is idempotent.
 */
private int
clist_reset(gx_device * dev)
{
    gx_device_clist_writer * const cdev =
	&((gx_device_clist *)dev)->writer;
    int code = clist_init_data(dev, cdev->data, cdev->data_size);
    int nbands;

    if (code < 0)
	return (cdev->permanent_error = code);
    /* Now initialize the rest of the state. */
    cdev->permanent_error = 0;
    nbands = cdev->nbands;
    cdev->ymin = cdev->ymax = -1;	/* render_init not done yet */
    memset(cdev->tile_table, 0, (cdev->tile_hash_mask + 1) *
	   sizeof(*cdev->tile_table));
    cdev->cnext = cdev->cbuf;
    cdev->ccl = 0;
    cdev->band_range_list.head = cdev->band_range_list.tail = 0;
    cdev->band_range_min = 0;
    cdev->band_range_max = nbands - 1;
    {
	int band;
	gx_clist_state *states = cdev->states;

	for (band = 0; band < nbands; band++, states++) {
	    static const gx_clist_state cls_initial =
	    {cls_initial_values};

	    *states = cls_initial;
	}
    }
    /*
     * Round up the size of the per-tile band mask so that the bits,
     * which follow it, stay aligned.
     */
    cdev->tile_band_mask_size =
	((nbands + (align_bitmap_mod * 8 - 1)) >> 3) &
	~(align_bitmap_mod - 1);
    /*
     * Initialize the all-band parameters to impossible values,
     * to force them to be written the first time they are used.
     */
    memset(&cdev->tile_params, 0, sizeof(cdev->tile_params));
    cdev->tile_depth = 0;
    cdev->tile_known_min = nbands;
    cdev->tile_known_max = -1;
    cdev->imager_state = clist_imager_state_initial;
    cdev->clip_path = NULL;
    cdev->clip_path_id = gs_no_id;
    cdev->color_space.byte1 = 0;
    cdev->color_space.id = gs_no_id;
    cdev->color_space.space = 0;
    {
	int i;

	for (i = 0; i < countof(cdev->transfer_ids); ++i)
	    cdev->transfer_ids[i] = gs_no_id;
    }
    cdev->black_generation_id = gs_no_id;
    cdev->undercolor_removal_id = gs_no_id;
    cdev->device_halftone_id = gs_no_id;
    cdev->image_enum_id = gs_no_id;
    return 0;
}
/*
 * Initialize the device state (for writing).  This routine requires only
 * data, data_size, and target to be set, and is idempotent.
 */
private int
clist_init(gx_device * dev)
{
    gx_device_clist_writer * const cdev =
	&((gx_device_clist *)dev)->writer;
    int code = clist_reset(dev);

    if (code >= 0) {
	cdev->image_enum_id = gs_no_id;
	cdev->error_is_retryable = 0;
	cdev->driver_call_nesting = 0;
	cdev->ignore_lo_mem_warnings = 0;
    }
    return code;
}

/* (Re)init open band files for output (set block size, etc). */
private int	/* ret 0 ok, -ve error code */
clist_reinit_output_file(gx_device *dev)
{    gx_device_clist_writer * const cdev =
	&((gx_device_clist *)dev)->writer;
    int code = 0;

    /* bfile needs to guarantee cmd_blocks for: 1 band range, nbands */
    /*  & terminating entry */
    int b_block = sizeof(cmd_block) * (cdev->nbands + 2);

    /* cfile needs to guarantee one writer buffer */
    /*  + one end_clip cmd (if during image's clip path setup) */
    /*  + an end_image cmd for each band (if during image) */
    /*  + end_cmds for each band and one band range */
    int c_block =
	cdev->cend - cdev->cbuf + 2 + cdev->nbands * 2 + (cdev->nbands + 1);

    /* All this is for partial page rendering's benefit, do only */
    /* if partial page rendering is available */
    if ( clist_test_VMerror_recoverable(cdev) )
	{ if (cdev->page_bfile != 0)
	    code = clist_set_memory_warning(cdev->page_bfile, b_block);
	if (code >= 0 && cdev->page_cfile != 0)
	    code = clist_set_memory_warning(cdev->page_cfile, c_block);
	}
    return code;
}

/* Write out the current parameters that must be at the head of each page */
/* if async rendering is in effect */
private int
clist_emit_page_header(gx_device *dev)
{
    gx_device_clist_writer * const cdev =
	&((gx_device_clist *)dev)->writer;
    int code = 0;

    if ((cdev->disable_mask & clist_disable_pass_thru_params)) {
	do
	    if ((code = clist_put_current_params(cdev)) >= 0)
	        break;
	while ((code = clist_VMerror_recover(cdev, code)) >= 0);
	cdev->permanent_error = (code < 0 ? code : 0);
	if (cdev->permanent_error < 0)
	    cdev->error_is_retryable = 0;
    }
    return code;
}

/* Reset parameters for the beginning of a page. */
private void
clist_reset_page(gx_device_clist_writer *cwdev)
{
    cwdev->page_bfile_end_pos = 0;
    /* Indicate that the colors_used information hasn't been computed. */
    cwdev->page_info.scan_lines_per_colors_used = 0;
    memset(cwdev->page_info.band_colors_used, 0,
	   sizeof(cwdev->page_info.band_colors_used));
}

/* Open the device's bandfiles */
private int
clist_open_output_file(gx_device *dev)
{
    gx_device_clist_writer * const cdev =
	&((gx_device_clist *)dev)->writer;
    char fmode[4];
    int code;

    if (cdev->do_not_open_or_close_bandfiles)
	return 0; /* external bandfile open/close managed externally */
    cdev->page_cfile = 0;	/* in case of failure */
    cdev->page_bfile = 0;	/* ditto */
    code = clist_init(dev);
    if (code < 0)
	return code;
    strcpy(fmode, "w+");
    strcat(fmode, gp_fmode_binary_suffix);
    cdev->page_cfname[0] = 0;	/* create a new file */
    cdev->page_bfname[0] = 0;	/* ditto */
    clist_reset_page(cdev);
    if ((code = clist_fopen(cdev->page_cfname, fmode, &cdev->page_cfile,
			    cdev->bandlist_memory, cdev->bandlist_memory,
			    true)) < 0 ||
	(code = clist_fopen(cdev->page_bfname, fmode, &cdev->page_bfile,
			    cdev->bandlist_memory, cdev->bandlist_memory,
			    true)) < 0 ||
	(code = clist_reinit_output_file(dev)) < 0
	) {
	clist_close_output_file(dev);
	cdev->permanent_error = code;
	cdev->error_is_retryable = 0;
    }
    return code;
}

/* Close, and free the contents of, the temporary files of a page. */
/* Note that this does not deallocate the buffer. */
int
clist_close_page_info(gx_band_page_info_t *ppi)
{
    if (ppi->cfile != NULL) {
	clist_fclose(ppi->cfile, ppi->cfname, true);
	ppi->cfile = NULL;
    }
    if (ppi->bfile != NULL) {
	clist_fclose(ppi->bfile, ppi->bfname, true);
	ppi->bfile = NULL;
    }
    return 0;
}

/* Close the device by freeing the temporary files. */
/* Note that this does not deallocate the buffer. */
int
clist_close_output_file(gx_device *dev)
{
    gx_device_clist_writer * const cdev =
	&((gx_device_clist *)dev)->writer;

    return clist_close_page_info(&cdev->page_info);
}

/* Open the device by initializing the device state and opening the */
/* scratch files. */
private int
clist_open(gx_device *dev)
{
    gx_device_clist_writer * const cdev =
	&((gx_device_clist *)dev)->writer;
    int code;

    cdev->permanent_error = 0;
    code = clist_init(dev);
    if (code < 0)
	return code;
    code = clist_open_output_file(dev);
    if ( code >= 0)
	code = clist_emit_page_header(dev);
    return code;
}

private int
clist_close(gx_device *dev)
{
    gx_device_clist_writer * const cdev =
	&((gx_device_clist *)dev)->writer;

    if (cdev->do_not_open_or_close_bandfiles)
	return 0;	
    return clist_close_output_file(dev);
}

/* The output_page procedure should never be called! */
private int
clist_output_page(gx_device * dev, int num_copies, int flush)
{
    return_error(gs_error_Fatal);
}

/* Reset (or prepare to append to) the command list after printing a page. */
int
clist_finish_page(gx_device *dev, bool flush)
{
    gx_device_clist_writer * const cdev =
	&((gx_device_clist *)dev)->writer;
    int code;

    if (flush) {
	if (cdev->page_cfile != 0)
	    clist_rewind(cdev->page_cfile, true, cdev->page_cfname);
	if (cdev->page_bfile != 0)
	    clist_rewind(cdev->page_bfile, true, cdev->page_bfname);
	clist_reset_page(cdev);
    } else {
	if (cdev->page_cfile != 0)
	    clist_fseek(cdev->page_cfile, 0L, SEEK_END, cdev->page_cfname);
	if (cdev->page_bfile != 0)
	    clist_fseek(cdev->page_bfile, 0L, SEEK_END, cdev->page_bfname);
    }
    code = clist_init(dev);		/* reinitialize */
    if (code >= 0)
	code = clist_reinit_output_file(dev);
    if (code >= 0)
	code = clist_emit_page_header(dev);

    return code;
}

/* ------ Writing ------ */

/* End a page by flushing the buffer and terminating the command list. */
int	/* ret 0 all-ok, -ve error code, or +1 ok w/low-mem warning */
clist_end_page(gx_device_clist_writer * cldev)
{
    int code = cmd_write_buffer(cldev, cmd_opv_end_page);
    cmd_block cb;
    int ecode = 0;

    if (code >= 0) {
	/*
	 * Write the terminating entry in the block file.
	 * Note that because of copypage, there may be many such entries.
	 */
	cb.band_min = cb.band_max = cmd_band_end;
	cb.pos = (cldev->page_cfile == 0 ? 0 : clist_ftell(cldev->page_cfile));
	code = clist_fwrite_chars(&cb, sizeof(cb), cldev->page_bfile);
	if (code > 0)
	    code = 0;
    }
    if (code >= 0) {
	clist_compute_colors_used(cldev);
	ecode |= code;
	cldev->page_bfile_end_pos = clist_ftell(cldev->page_bfile);
    }
    if (code < 0)
	ecode = code;

    /* Reset warning margin to 0 to release reserve memory if mem files */
    if (cldev->page_bfile != 0)
	clist_set_memory_warning(cldev->page_bfile, 0);
    if (cldev->page_cfile != 0)
	clist_set_memory_warning(cldev->page_cfile, 0);

#ifdef DEBUG
    if (gs_debug_c('l') | gs_debug_c(':'))
	dlprintf2("[:]clist_end_page at cfile=%ld, bfile=%ld\n",
		  cb.pos, cldev->page_bfile_end_pos);
#endif
    return 0;
}

/* Compute the set of used colors in the page_info structure. */
void
clist_compute_colors_used(gx_device_clist_writer *cldev)
{
    int nbands = cldev->nbands;
    int bands_per_colors_used =
	(nbands + PAGE_INFO_NUM_COLORS_USED - 1) /
	PAGE_INFO_NUM_COLORS_USED;
    int band;

    cldev->page_info.scan_lines_per_colors_used =
	cldev->page_band_height * bands_per_colors_used;
    memset(cldev->page_info.band_colors_used, 0,
	   sizeof(cldev->page_info.band_colors_used));
    for (band = 0; band < nbands; ++band) {
	int entry = band / bands_per_colors_used;

	cldev->page_info.band_colors_used[entry].or |=
	    cldev->states[band].colors_used.or;
	cldev->page_info.band_colors_used[entry].slow_rop |=
	    cldev->states[band].colors_used.slow_rop;
    }
}

/* Recover recoverable VM error if possible without flushing */
int	/* ret -ve err, >= 0 if recovered w/# = cnt pages left in page queue */
clist_VMerror_recover(gx_device_clist_writer *cldev,
		      int old_error_code)
{
    int code = old_error_code;
    int pages_remain;

    if (!clist_test_VMerror_recoverable(cldev) ||
	!cldev->error_is_retryable ||
	old_error_code != gs_error_VMerror
	)
	return old_error_code;

    /* Do some rendering, return if enough memory is now free */
    do {
	pages_remain =
	    (*cldev->free_up_bandlist_memory)( (gx_device *)cldev, false );
	if (pages_remain < 0) {
	    code = pages_remain;	/* abort, error or interrupt req */
	    break;
	}
	if (clist_reinit_output_file( (gx_device *)cldev ) == 0) {
	    code = pages_remain;	/* got enough memory to continue */
	    break;
	}
    } while (pages_remain);

    if_debug1('L', "[L]soft flush of command list, status: %d\n", code);
    return code;
}

/* If recoverable VM error, flush & try to recover it */
int	/* ret 0 ok, else -ve error */
clist_VMerror_recover_flush(gx_device_clist_writer *cldev,
			    int old_error_code)
{
    int free_code = 0;
    int reset_code = 0;
    int code;

    /* If the device has the ability to render partial pages, flush
     * out the bandlist, and reset the writing state. Then, get the
     * device to render this band. When done, see if there's now enough
     * memory to satisfy the minimum low-memory guarantees. If not, 
     * get the device to render some more. If there's nothing left to
     * render & still insufficient memory, declare an error condition.
     */
    if (!clist_test_VMerror_recoverable(cldev) ||
	old_error_code != gs_error_VMerror
	)
	return old_error_code;	/* sorry, don't have any means to recover this error */
    free_code = (*cldev->free_up_bandlist_memory)( (gx_device *)cldev, true );

    /* Reset the state of bands to "don't know anything" */
    reset_code = clist_reset( (gx_device *)cldev );
    if (reset_code >= 0)
	reset_code = clist_open_output_file( (gx_device *)cldev );
    if ( reset_code >= 0 &&
	 (cldev->disable_mask & clist_disable_pass_thru_params)
	 )
	reset_code = clist_put_current_params(cldev);
    if (reset_code < 0) {
	cldev->permanent_error = reset_code;
	cldev->error_is_retryable = 0;
    }
 
    code = (reset_code < 0 ? reset_code : free_code < 0 ? old_error_code : 0);
    if_debug1('L', "[L]hard flush of command list, status: %d\n", code);
    return code;
}

/* Write the target device's current parameter list */
private int	/* ret 0 all ok, -ve error */
clist_put_current_params(gx_device_clist_writer *cldev)
{
    gx_device *target = cldev->target;
    gs_c_param_list param_list;
    int code;

    /*
     * If a put_params call fails, the device will be left in a closed
     * state, but higher-level code won't notice this fact.  We flag this by
     * setting permanent_error, which prevents writing to the command list.
     */

    if (cldev->permanent_error)
	return cldev->permanent_error;
    gs_c_param_list_write(&param_list, cldev->memory);
    code = (*dev_proc(target, get_params))
	(target, (gs_param_list *)&param_list);
    if (code >= 0) {
	gs_c_param_list_read(&param_list);
	code = cmd_put_params( cldev, (gs_param_list *)&param_list );
    }
    gs_c_param_list_release(&param_list);

    return code;
}

/* ---------------- Driver interface ---------------- */

private int
clist_get_band(gx_device * dev, int y, int *band_start)
{
    gx_device_clist_writer * const cdev =
	&((gx_device_clist *)dev)->writer;
    int band_height = cdev->page_band_height;
    int start;

    if (y < 0)
	y = 0;
    else if (y >= dev->height)
	y = dev->height;
    *band_start = start = y - y % band_height;
    return min(dev->height - start, band_height);
}
