/* Copyright (C) 1997, 2000 Aladdin Enterprises.  All rights reserved.
  
  This file is part of AFPL Ghostscript.
  
  AFPL Ghostscript is distributed with NO WARRANTY OF ANY KIND.  No author or
  distributor accepts any responsibility for the consequences of using it, or
  for whether it serves any particular purpose or works at all, unless he or
  she says so in writing.  Refer to the Aladdin Free Public License (the
  "License") for full details.
  
  Every copy of AFPL Ghostscript must include a copy of the License, normally
  in a plain ASCII text file named PUBLIC.  The License grants you the right
  to copy, modify and redistribute AFPL Ghostscript, but only under certain
  conditions described in the License.  Among other things, the License
  requires that the copyright notice and this notice be preserved on all
  copies.
*/

/*$Id: gdevpdfo.c,v 1.9 2001/08/14 02:29:16 lpd Exp $ */
/* Cos object support */
#include "memory_.h"
#include "string_.h"
#include "gx.h"
#include "gserrors.h"
#include "gsparam.h"
#include "gsutil.h"		/* for bytes_compare */
#include "gdevpdfx.h"
#include "gdevpdfo.h"
#include "strimpl.h"
#include "sa85x.h"
#include "slzwx.h"
#include "sstring.h"
#include "szlibx.h"

#define CHECK(expr)\
  BEGIN if ((code = (expr)) < 0) return code; END

/* ---------------- Structure definitions ---------------- */

/*
 * Define the generic structure for elements of arrays and
 * dictionaries/streams.
 */
#define cos_element_common(etype)\
    etype *next
struct cos_element_s {
    cos_element_common(cos_element_t);
};
#define private_st_cos_element()	/* in gdevpdfo.c */\
  gs_private_st_ptrs1(st_cos_element, cos_element_t, "cos_element_t",\
    cos_element_enum_ptrs, cos_element_reloc_ptrs, next)
#define cos_element_num_ptrs 1

/*
 * Define the structure for a piece of stream contents.
 */
struct cos_stream_piece_s {
    cos_element_common(cos_stream_piece_t);
    long position;		/* in streams file */
    uint size;
};
#define private_st_cos_stream_piece()	/* in gdevpdfo.c */\
  gs_private_st_suffix_add0_local(st_cos_stream_piece, cos_stream_piece_t,\
    "cos_stream_piece_t", cos_element_enum_ptrs, cos_element_reloc_ptrs,\
    st_cos_element)

/*
 * Define Cos arrays, dictionaries, and streams.
 */
     /* array */
struct cos_array_element_s {
    cos_element_common(cos_array_element_t);
    long index;
    cos_value_t value;
};
#define private_st_cos_array_element()	/* in gdevpdfo.c */\
  gs_private_st_composite(st_cos_array_element, cos_array_element_t,\
    "cos_array_element_t", cos_array_element_enum_ptrs, cos_array_element_reloc_ptrs)
    /* dict */
struct cos_dict_element_s {
    cos_element_common(cos_dict_element_t);
    gs_string key;
    bool owns_key;	/* if false, key is shared, do not trace or free */
    cos_value_t value;
};
#define private_st_cos_dict_element()	/* in gdevpdfo.c */\
  gs_private_st_composite(st_cos_dict_element, cos_dict_element_t,\
    "cos_dict_element_t", cos_dict_element_enum_ptrs, cos_dict_element_reloc_ptrs)

/* GC descriptors */
private_st_cos_element();
private_st_cos_stream_piece();
private_st_cos_object();
private_st_cos_value();
private_st_cos_array_element();
private_st_cos_dict_element();

/* GC procedures */
private
ENUM_PTRS_WITH(cos_value_enum_ptrs, cos_value_t *pcv) return 0;
 case 0:
    switch (pcv->value_type) {
    case COS_VALUE_SCALAR:
	return ENUM_STRING(&pcv->contents.chars);
    case COS_VALUE_CONST:
	break;
    case COS_VALUE_OBJECT:
    case COS_VALUE_RESOURCE:
	return ENUM_OBJ(pcv->contents.object);
    }
    return 0;
ENUM_PTRS_END
private
RELOC_PTRS_WITH(cos_value_reloc_ptrs, cos_value_t *pcv)
{
    switch (pcv->value_type) {
    case COS_VALUE_SCALAR:
	RELOC_STRING_VAR(pcv->contents.chars);
    case COS_VALUE_CONST:
	break;
    case COS_VALUE_OBJECT:
    case COS_VALUE_RESOURCE:
	RELOC_VAR(pcv->contents.object);
	break;
    }
}
RELOC_PTRS_END
private
ENUM_PTRS_WITH(cos_array_element_enum_ptrs, cos_array_element_t *pcae)
{
    return (index < cos_element_num_ptrs ?
	    ENUM_USING_PREFIX(st_cos_element, 0) :
	    ENUM_USING(st_cos_value, &pcae->value, sizeof(cos_value_t),
		       index - cos_element_num_ptrs));
}
ENUM_PTRS_END
private
RELOC_PTRS_WITH(cos_array_element_reloc_ptrs, cos_array_element_t *pcae)
{
    RELOC_PREFIX(st_cos_element);
    RELOC_USING(st_cos_value, &pcae->value, sizeof(cos_value_t));
}
RELOC_PTRS_END
private
ENUM_PTRS_WITH(cos_dict_element_enum_ptrs, cos_dict_element_t *pcde)
{
    return (index < cos_element_num_ptrs ?
	    ENUM_USING_PREFIX(st_cos_element, 0) :
	    (index -= cos_element_num_ptrs) > 0 ?
	    ENUM_USING(st_cos_value, &pcde->value, sizeof(cos_value_t),
		       index - 1) :
	    pcde->owns_key ? ENUM_STRING(&pcde->key) : ENUM_OBJ(NULL));
}
ENUM_PTRS_END
private
RELOC_PTRS_WITH(cos_dict_element_reloc_ptrs, cos_dict_element_t *pcde)
{
    RELOC_PREFIX(st_cos_element);
    if (pcde->owns_key)
	RELOC_STRING_VAR(pcde->key);
    RELOC_USING(st_cos_value, &pcde->value, sizeof(cos_value_t));
}
RELOC_PTRS_END

/* ---------------- Generic support ---------------- */

/* Initialize a just-allocated cos object. */
private void
cos_object_init(cos_object_t *pco, gx_device_pdf *pdev,
		const cos_object_procs_t *procs)
{
    if (pco) {
	pco->cos_procs = procs;
	pco->id = 0;
	pco->elements = 0;
	pco->pieces = 0;
	pco->pdev = pdev;
	pco->pres = 0;
	pco->is_open = true;
	pco->is_graphics = false;
	pco->written = false;
    }
}

/* Get the allocator for a Cos object. */
gs_memory_t *
cos_object_memory(const cos_object_t *pco)
{
    return pco->pdev->pdf_memory;
}

/* Change a generic cos object into one of a specific type. */
int
cos_become(cos_object_t *pco, cos_type_t cotype)
{
    if (cos_type(pco) != cos_type_generic)
	return_error(gs_error_typecheck);
    cos_type(pco) = cotype;
    return 0;
}

/* Release a cos object. */
cos_proc_release(cos_release);	/* check prototype */
void
cos_release(cos_object_t *pco, client_name_t cname)
{
    pco->cos_procs->release(pco, cname);
}

/* Free a cos object. */
void
cos_free(cos_object_t *pco, client_name_t cname)
{
    cos_release(pco, cname);
    gs_free_object(cos_object_memory(pco), pco, cname);
}

/* Write a cos object on the output. */
cos_proc_write(cos_write);	/* check prototype */
int
cos_write(const cos_object_t *pco, gx_device_pdf *pdev)
{
    return pco->cos_procs->write(pco, pdev);
}

/* Write a cos object as a PDF object. */
int
cos_write_object(cos_object_t *pco, gx_device_pdf *pdev)
{
    int code;

    if (pco->id == 0 || pco->written)
	return_error(gs_error_Fatal);
    pdf_open_separate(pdev, pco->id);
    code = cos_write(pco, pdev);
    pdf_end_separate(pdev);
    pco->written = true;
    return code;
}

/* Make a value to store into a composite object. */
const cos_value_t *
cos_string_value(cos_value_t *pcv, const byte *data, uint size)
{
    /*
     * It's OK to break const here, because the value will be copied
     * before being stored in the collection.
     */
    pcv->contents.chars.data = (byte *)data;
    pcv->contents.chars.size = size;
    pcv->value_type = COS_VALUE_SCALAR;
    return pcv;
}
const cos_value_t *
cos_c_string_value(cos_value_t *pcv, const char *str)
{
    /*
     * We shouldn't break const here, because the value will not be copied
     * or freed (or traced), but that would require a lot of bothersome
     * casting elsewhere.
     */
    pcv->contents.chars.data = (byte *)str;
    pcv->contents.chars.size = strlen(str);
    pcv->value_type = COS_VALUE_CONST;
    return pcv;
}
const cos_value_t *
cos_object_value(cos_value_t *pcv, cos_object_t *pco)
{
    pcv->contents.object = pco;
    pcv->value_type = COS_VALUE_OBJECT;
    return pcv;
}
const cos_value_t *
cos_resource_value(cos_value_t *pcv, cos_object_t *pco)
{
    pcv->contents.object = pco;
    pcv->value_type = COS_VALUE_RESOURCE;
    return pcv;
}

/* Free a value. */
void
cos_value_free(const cos_value_t *pcv, const cos_object_t *pco,
	       client_name_t cname)
{
    switch (pcv->value_type) {
    case COS_VALUE_SCALAR:
	gs_free_string(cos_object_memory(pco), pcv->contents.chars.data,
		       pcv->contents.chars.size, cname);
    case COS_VALUE_CONST:
	break;
    case COS_VALUE_OBJECT:
	/* Free the object if this is the only reference to it. */
	if (!pcv->contents.object->id)
	    cos_free(pcv->contents.object, cname);
    case COS_VALUE_RESOURCE:
	break;
    }
}

/* Write a value on the output. */
private int
cos_value_write_spaced(const cos_value_t *pcv, gx_device_pdf *pdev,
		       bool do_space)
{
    stream *s = pdev->strm;

    switch (pcv->value_type) {
    case COS_VALUE_SCALAR:
    case COS_VALUE_CONST:
	if (do_space)
	    switch (pcv->contents.chars.data[0]) {
	    case '/': case '(': case '<': break;
	    default: stream_putc(s, ' ');
	    }
	pdf_write_value(pdev, pcv->contents.chars.data,
			pcv->contents.chars.size);
	break;
    case COS_VALUE_RESOURCE:
	pprintld1(s, "/R%ld", pcv->contents.object->id);
	break;
    case COS_VALUE_OBJECT: {
	const cos_object_t *pco = pcv->contents.object;

	if (!pco->id) {
	    if (do_space &&
		!(pco->cos_procs == cos_type_array ||
		  pco->cos_procs == cos_type_dict)
		) {
		/* Arrays and dictionaries (only) are self-delimiting. */
		stream_putc(s, ' ');
	    }
	    return cos_write(pco, pdev);
	}
	if (do_space)
	    stream_putc(s, ' ');
	pprintld1(s, "%ld 0 R", pco->id);
	break;
    }
    default:			/* can't happen */
	return_error(gs_error_Fatal);
    }
    return 0;
}
int
cos_value_write(const cos_value_t *pcv, gx_device_pdf *pdev)
{
    return cos_value_write_spaced(pcv, pdev, false);
}

/* Copy a value if necessary for putting into an array or dictionary. */
private int
cos_copy_element_value(cos_value_t *pcv, gs_memory_t *mem,
		       const cos_value_t *pvalue, bool copy)
{
    *pcv = *pvalue;
    if (pvalue->value_type == COS_VALUE_SCALAR && copy) {
	byte *value_data = gs_alloc_string(mem, pvalue->contents.chars.size,
					   "cos_copy_element_value");

	if (value_data == 0)
	    return_error(gs_error_VMerror);
	memcpy(value_data, pvalue->contents.chars.data,
	       pvalue->contents.chars.size);
	pcv->contents.chars.data = value_data;
    }
    return 0;
}

/* Release a value copied for putting, if the operation fails. */
private void
cos_uncopy_element_value(cos_value_t *pcv, gs_memory_t *mem, bool copy)
{
    if (pcv->value_type == COS_VALUE_SCALAR && copy)
	gs_free_string(mem, pcv->contents.chars.data, pcv->contents.chars.size,
		       "cos_uncopy_element_value");
}

/* ---------------- Specific object types ---------------- */

/* ------ Generic objects ------ */

private cos_proc_release(cos_generic_release);
private cos_proc_write(cos_generic_write);
const cos_object_procs_t cos_generic_procs = {
    cos_generic_release, cos_generic_write
};

cos_object_t *
cos_object_alloc(gx_device_pdf *pdev, client_name_t cname)
{
    gs_memory_t *mem = pdev->pdf_memory;
    cos_object_t *pco =
	gs_alloc_struct(mem, cos_object_t, &st_cos_object, cname);

    cos_object_init(pco, pdev, &cos_generic_procs);
    return pco;
}

private void
cos_generic_release(cos_object_t *pco, client_name_t cname)
{
    /* Do nothing. */
}

private int
cos_generic_write(const cos_object_t *pco, gx_device_pdf *pdev)
{
    return_error(gs_error_Fatal);
}

/* ------ Arrays ------ */

private cos_proc_release(cos_array_release);
private cos_proc_write(cos_array_write);
const cos_object_procs_t cos_array_procs = {
    cos_array_release, cos_array_write
};

cos_array_t *
cos_array_alloc(gx_device_pdf *pdev, client_name_t cname)
{
    gs_memory_t *mem = pdev->pdf_memory;
    cos_array_t *pca =
	gs_alloc_struct(mem, cos_array_t, &st_cos_object, cname);

    cos_object_init((cos_object_t *)pca, pdev, &cos_array_procs);
    return pca;
}

cos_array_t *
cos_array_from_floats(gx_device_pdf *pdev, const float *pf, uint size,
		      client_name_t cname)
{
    cos_array_t *pca = cos_array_alloc(pdev, cname);
    uint i;

    if (pca == 0)
	return 0;
    for (i = 0; i < size; ++i) {
	int code = cos_array_add_real(pca, pf[i]);

	if (code < 0) {
	    COS_FREE(pca, cname);
	    return 0;
	}
    }
    return pca;
}

private void
cos_array_release(cos_object_t *pco, client_name_t cname)
{
    gs_memory_t *mem = cos_object_memory(pco);
    cos_array_t *const pca = (cos_array_t *)pco;
    cos_array_element_t *cur;
    cos_array_element_t *next;

    for (cur = pca->elements; cur; cur = next) {
	next = cur->next;
	cos_value_free(&cur->value, pco, cname);
	gs_free_object(mem, cur, cname);
    }
    pca->elements = 0;
}

private int
cos_array_write(const cos_object_t *pco, gx_device_pdf *pdev)
{
    stream *s = pdev->strm;
    const cos_array_t *const pca = (const cos_array_t *)pco;
    cos_array_element_t *first = cos_array_reorder(pca, NULL);
    cos_array_element_t *pcae;
    uint last_index = 0;

    stream_puts(s, "[");
    for (pcae = first; pcae; ++last_index, pcae = pcae->next) {
	if (pcae != first)
	    stream_putc(s, '\n');
	for (; pcae->index > last_index; ++last_index)
	    stream_puts(s, "null\n");
	cos_value_write(&pcae->value, pdev);
    }
    DISCARD(cos_array_reorder(pca, first));
    stream_puts(s, "]");
    return 0;
}

/* Put/add an element in/to an array. */
int
cos_array_put(cos_array_t *pca, long index, const cos_value_t *pvalue)
{
    gs_memory_t *mem = COS_OBJECT_MEMORY(pca);
    cos_value_t value;
    int code = cos_copy_element_value(&value, mem, pvalue, true);

    if (code >= 0) {
	code = cos_array_put_no_copy(pca, index, &value);
	if (code < 0)
	    cos_uncopy_element_value(&value, mem, true);
    }
    return code;
}
int
cos_array_put_no_copy(cos_array_t *pca, long index, const cos_value_t *pvalue)
{
    gs_memory_t *mem = COS_OBJECT_MEMORY(pca);
    cos_array_element_t **ppcae = &pca->elements;
    cos_array_element_t *pcae;
    cos_array_element_t *next;

    while ((next = *ppcae) != 0 && next->index > index)
	ppcae = &next->next;
    if (next && next->index == index) {
	/* We're replacing an existing element. */
	cos_value_free(&next->value, COS_OBJECT(pca),
		       "cos_array_put(old value)");
	pcae = next;
    } else {
	/* Create a new element. */
	pcae = gs_alloc_struct(mem, cos_array_element_t, &st_cos_array_element,
			       "cos_array_put(element)");
	if (pcae == 0)
	    return_error(gs_error_VMerror);
	pcae->index = index;
	pcae->next = next;
	*ppcae = pcae;
    }
    pcae->value = *pvalue;
    return 0;
}
private long
cos_array_next_index(const cos_array_t *pca)
{
    return (pca->elements ? pca->elements->index + 1 : 0L);
}
int
cos_array_add(cos_array_t *pca, const cos_value_t *pvalue)
{
    return cos_array_put(pca, cos_array_next_index(pca), pvalue);
}
int
cos_array_add_no_copy(cos_array_t *pca, const cos_value_t *pvalue)
{
    return cos_array_put_no_copy(pca, cos_array_next_index(pca), pvalue);
}
int
cos_array_add_c_string(cos_array_t *pca, const char *str)
{
    cos_value_t value;

    return cos_array_add(pca, cos_c_string_value(&value, str));
}
int
cos_array_add_int(cos_array_t *pca, int i)
{
    char str[sizeof(int) * 8 / 3 + 3]; /* sign, rounding, 0 terminator */
    cos_value_t v;

    sprintf(str, "%d", i);
    return cos_array_add(pca, cos_string_value(&v, (byte *)str, strlen(str)));
}
int
cos_array_add_real(cos_array_t *pca, floatp r)
{
    byte str[50];		/****** ADHOC ******/
    stream s;
    cos_value_t v;

    swrite_string(&s, str, sizeof(str));
    pprintg1(&s, "%g", r);
    return cos_array_add(pca, cos_string_value(&v, str, stell(&s)));
}
int
cos_array_add_object(cos_array_t *pca, cos_object_t *pco)
{
    cos_value_t value;

    return cos_array_add(pca, cos_object_value(&value, pco));
}

/* Get the first / next element for enumerating an array. */
const cos_array_element_t *
cos_array_element_first(const cos_array_t *pca)
{
    return pca->elements;
}
const cos_array_element_t *
cos_array_element_next(const cos_array_element_t *pca, long *pindex,
		       const cos_value_t **ppvalue)
{
    *pindex = pca->index;
    *ppvalue = &pca->value;
    return pca->next;
}

/*
 * Reorder the elements of an array for writing or after writing.  Usage:
 *	first_element = cos_array_reorder(pca, NULL);
 *	...
 *	cos_array_reorder(pca, first_element);
 */
cos_array_element_t *
cos_array_reorder(const cos_array_t *pca, cos_array_element_t *first)
{
    cos_array_element_t *last;
    cos_array_element_t *next;
    cos_array_element_t *pcae;

    for (pcae = (first ? first : pca->elements), last = NULL; pcae;
	 pcae = next)
	next = pcae->next, pcae->next = last, last = pcae;
    return last;
}

/* ------ Dictionaries ------ */

private cos_proc_release(cos_dict_release);
private cos_proc_write(cos_dict_write);
const cos_object_procs_t cos_dict_procs = {
    cos_dict_release, cos_dict_write
};

cos_dict_t *
cos_dict_alloc(gx_device_pdf *pdev, client_name_t cname)
{
    gs_memory_t *mem = pdev->pdf_memory;
    cos_dict_t *pcd =
	gs_alloc_struct(mem, cos_dict_t, &st_cos_object, cname);

    cos_object_init((cos_object_t *)pcd, pdev, &cos_dict_procs);
    return pcd;
}

private void
cos_dict_release(cos_object_t *pco, client_name_t cname)
{
    gs_memory_t *mem = cos_object_memory(pco);
    cos_dict_t *const pcd = (cos_dict_t *)pco;
    cos_dict_element_t *cur;
    cos_dict_element_t *next;

    for (cur = pcd->elements; cur; cur = next) {
	next = cur->next;
	cos_value_free(&cur->value, pco, cname);
	if (cur->owns_key)
	    gs_free_string(mem, cur->key.data, cur->key.size, cname);
	gs_free_object(mem, cur, cname);
    }
    pcd->elements = 0;
}

/* Write the elements of a dictionary. */
private int
cos_elements_write(stream *s, const cos_dict_element_t *pcde,
		   gx_device_pdf *pdev, bool do_space)
{
    if (pcde) {
	/* Temporarily replace the output stream in pdev. */
	stream *save = pdev->strm;

	pdev->strm = s;
	for (;;) {
	    pdf_write_value(pdev, pcde->key.data, pcde->key.size);
	    cos_value_write_spaced(&pcde->value, pdev, true);
	    pcde = pcde->next;
	    if (pcde || do_space)
		stream_putc(s, '\n');
	    if (!pcde)
		break;
	}
	pdev->strm = save;
    }
    return 0;
}
int
cos_dict_elements_write(const cos_dict_t *pcd, gx_device_pdf *pdev)
{
    return cos_elements_write(pdev->strm, pcd->elements, pdev, true);
}

private int
cos_dict_write(const cos_object_t *pco, gx_device_pdf *pdev)
{
    stream *s = pdev->strm;

    stream_puts(s, "<<");
    cos_elements_write(s, ((const cos_dict_t *)pco)->elements, pdev, false);
    stream_puts(s, ">>");
    return 0;
}

/* Write/delete definitions of named objects. */
/* This is a special-purpose facility for pdf_close. */
int
cos_dict_objects_write(const cos_dict_t *pcd, gx_device_pdf *pdev)
{
    const cos_dict_element_t *pcde = pcd->elements;

    for (; pcde; pcde = pcde->next)
	if (COS_VALUE_IS_OBJECT(&pcde->value) &&
	    pcde->value.contents.object->id)
	    cos_write_object(pcde->value.contents.object, pdev);
    return 0;
}
int
cos_dict_objects_delete(cos_dict_t *pcd)
{
    cos_dict_element_t *pcde = pcd->elements;

    /*
     * Delete the objects' IDs so that freeing the dictionary will
     * free them.
     */
    for (; pcde; pcde = pcde->next)
	pcde->value.contents.object->id = 0;
    return 0;
}

/* Put an element in a dictionary. */
#define DICT_COPY_KEY 1
#define DICT_COPY_VALUE 2
#define DICT_FREE_KEY 4
#define DICT_COPY_ALL (DICT_COPY_KEY | DICT_COPY_VALUE | DICT_FREE_KEY)
private int
cos_dict_put_copy(cos_dict_t *pcd, const byte *key_data, uint key_size,
		  const cos_value_t *pvalue, int flags)
{
    gs_memory_t *mem = COS_OBJECT_MEMORY(pcd);
    cos_dict_element_t **ppcde = &pcd->elements;
    cos_dict_element_t *pcde;
    cos_dict_element_t *next;
    cos_value_t value;
    int code;

    while ((next = *ppcde) != 0 &&
	   bytes_compare(next->key.data, next->key.size, key_data, key_size)
	   )
	ppcde = &next->next;
    if (next) {
	/* We're replacing an existing element. */
	code = cos_copy_element_value(&value, mem, pvalue,
				      (flags & DICT_COPY_VALUE) != 0);
	if (code < 0)
	    return code;
	if (flags & DICT_FREE_KEY)
	    gs_free_const_string(mem, key_data, key_size,
				 "cos_dict_put(new key)");
	cos_value_free(&next->value, COS_OBJECT(pcd),
		       "cos_dict_put(old value)");
	pcde = next;
    } else {
	/* Create a new element. */
	byte *copied_key_data;

	if (flags & DICT_COPY_KEY) {
	    copied_key_data = gs_alloc_string(mem, key_size,
					      "cos_dict_put(key)");
	    if (copied_key_data == 0)
		return_error(gs_error_VMerror);
	    memcpy(copied_key_data, key_data, key_size);
	} else
	    copied_key_data = (byte *)key_data;	/* OK to break const */
	pcde = gs_alloc_struct(mem, cos_dict_element_t, &st_cos_dict_element,
			       "cos_dict_put(element)");
	code = cos_copy_element_value(&value, mem, pvalue,
				      (flags & DICT_COPY_VALUE) != 0);
	if (pcde == 0 || code < 0) {
	    if (code >= 0)
		cos_uncopy_element_value(&value, mem,
					 (flags & DICT_COPY_VALUE) != 0);
	    gs_free_object(mem, pcde, "cos_dict_put(element)");
	    if (flags & DICT_COPY_KEY)
		gs_free_string(mem, copied_key_data, key_size,
			       "cos_dict_put(key)");
	    return (code < 0 ? code : gs_note_error(gs_error_VMerror));
	}
	pcde->key.data = copied_key_data;
	pcde->key.size = key_size;
	pcde->owns_key = (flags & DICT_FREE_KEY) != 0;
	pcde->next = next;
	*ppcde = pcde;
    }
    pcde->value = value;
    return 0;
}
int
cos_dict_put(cos_dict_t *pcd, const byte *key_data, uint key_size,
	     const cos_value_t *pvalue)
{
    return cos_dict_put_copy(pcd, key_data, key_size, pvalue, DICT_COPY_ALL);
}
int
cos_dict_put_no_copy(cos_dict_t *pcd, const byte *key_data, uint key_size,
		     const cos_value_t *pvalue)
{
    return cos_dict_put_copy(pcd, key_data, key_size, pvalue,
			     DICT_COPY_KEY | DICT_FREE_KEY);
}
int
cos_dict_put_c_key(cos_dict_t *pcd, const char *key, const cos_value_t *pvalue)
{
    return cos_dict_put_copy(pcd, (const byte *)key, strlen(key), pvalue,
			     DICT_COPY_VALUE);
}
int
cos_dict_put_c_key_string(cos_dict_t *pcd, const char *key,
			  const byte *data, uint size)
{
    cos_value_t value;

    cos_string_value(&value, data, size);
    return cos_dict_put_c_key(pcd, key, &value);
}
int
cos_dict_put_c_key_int(cos_dict_t *pcd, const char *key, int value)
{
    char str[sizeof(int) * 8 / 3 + 3]; /* sign, rounding, 0 terminator */

    sprintf(str, "%d", value);
    return cos_dict_put_c_key_string(pcd, key, (byte *)str, strlen(str));
}
int
cos_dict_put_c_key_real(cos_dict_t *pcd, const char *key, floatp value)
{
    byte str[50];		/****** ADHOC ******/
    stream s;

    swrite_string(&s, str, sizeof(str));
    pprintg1(&s, "%g", value);
    return cos_dict_put_c_key_string(pcd, key, str, stell(&s));
}
int
cos_dict_put_c_key_floats(cos_dict_t *pcd, const char *key, const float *pf,
			  uint size)
{
    cos_array_t *pca = cos_array_from_floats(pcd->pdev, pf, size,
					     "cos_dict_put_c_key_floats");
    int code;

    if (pca == 0)
	return_error(gs_error_VMerror);
    code = cos_dict_put_c_key_object(pcd, key, COS_OBJECT(pca));
    if (code < 0)
	COS_FREE(pca, "cos_dict_put_c_key_floats");
    return code;
}
int
cos_dict_put_c_key_object(cos_dict_t *pcd, const char *key, cos_object_t *pco)
{
    cos_value_t value;

    return cos_dict_put_c_key(pcd, key, cos_object_value(&value, pco));
}
int
cos_dict_put_string(cos_dict_t *pcd, const byte *key_data, uint key_size,
		    const byte *value_data, uint value_size)
{
    cos_value_t cvalue;

    return cos_dict_put(pcd, key_data, key_size,
			cos_string_value(&cvalue, value_data, value_size));
}
int
cos_dict_put_c_strings(cos_dict_t *pcd, const char *key, const char *value)
{
    cos_value_t cvalue;

    return cos_dict_put_c_key(pcd, key, cos_c_string_value(&cvalue, value));
}

/* Look up a key in a dictionary. */
const cos_value_t *
cos_dict_find(const cos_dict_t *pcd, const byte *key_data, uint key_size)
{
    cos_dict_element_t *pcde = pcd->elements;

    for (; pcde; pcde = pcde->next)
	if (!bytes_compare(key_data, key_size, pcde->key.data, pcde->key.size))
	    return &pcde->value;
    return 0;
}
const cos_value_t *
cos_dict_find_c_key(const cos_dict_t *pcd, const char *key)
{
    return cos_dict_find(pcd, (const byte *)key, strlen(key));
}

/* Set up a parameter list that writes into a Cos dictionary. */

/* We'll implement the other printers later if we have to. */
private param_proc_xmit_typed(cos_param_put_typed);
private const gs_param_list_procs cos_param_list_writer_procs = {
    cos_param_put_typed,
    NULL /* begin_collection */ ,
    NULL /* end_collection */ ,
    NULL /* get_next_key */ ,
    gs_param_request_default,
    gs_param_requested_default
};
private int
cos_param_put_typed(gs_param_list * plist, gs_param_name pkey,
		    gs_param_typed_value * pvalue)
{
    cos_param_list_writer_t *const pclist =
	(cos_param_list_writer_t *)plist;
    gx_device_pdf *pdev = pclist->pcd->pdev;
    gs_memory_t *mem = pclist->memory;
    cos_value_t value;
    cos_array_t *pca;
    int key_len = strlen(pkey);
    byte key_chars[100];		/****** ADHOC ******/
    int code;

    if (key_len > sizeof(key_chars) - 1)
	return_error(gs_error_limitcheck);
    switch (pvalue->type) {
    default: {
	param_printer_params_t ppp;
	printer_param_list_t pplist;
	stream s;
	int len, skip;
	byte *str;

	ppp = param_printer_params_default;
	ppp.prefix = ppp.suffix = ppp.item_prefix = ppp.item_suffix = 0;
	ppp.print_ok = pclist->print_ok;
	s_init_param_printer(&pplist, &ppp, &s);
	swrite_position_only(&s);
	param_write_typed((gs_param_list *)&pplist, "", pvalue);
	len = stell(&s);
	str = gs_alloc_string(mem, len, "cos_param_put(string)");
	if (str == 0)
	    return_error(gs_error_VMerror);
	swrite_string(&s, str, len);
	param_write_typed((gs_param_list *)&pplist, "", pvalue);
	/*
	 * The string starts with an initial / or /<space>, which
	 * we need to remove.
	 */
	skip = (str[1] == ' ' ? 2 : 1);
	memmove(str, str + skip, len - skip);
	str = gs_resize_string(mem, str, len, len - skip,
			       "cos_param_put(string)");
	cos_string_value(&value, str, len - skip);
    }
	break;
    case gs_param_type_int_array: {
	uint i;

	pca = cos_array_alloc(pdev, "cos_param_put(array)");
	if (pca == 0)
	    return_error(gs_error_VMerror);
	for (i = 0; i < pvalue->value.ia.size; ++i)
	    CHECK(cos_array_add_int(pca, pvalue->value.ia.data[i]));
    }
    av:
	cos_object_value(&value, COS_OBJECT(pca));
	break;
    case gs_param_type_float_array: {
	uint i;

	pca = cos_array_alloc(pdev, "cos_param_put(array)");
	if (pca == 0)
	    return_error(gs_error_VMerror);
	for (i = 0; i < pvalue->value.ia.size; ++i)
	    CHECK(cos_array_add_real(pca, pvalue->value.fa.data[i]));
    }
	goto av;
    case gs_param_type_string_array:
    case gs_param_type_name_array:
	/****** NYI ******/
	return_error(gs_error_typecheck);
    }
    memcpy(key_chars + 1, pkey, key_len);
    key_chars[0] = '/';
    return cos_dict_put_no_copy(pclist->pcd, key_chars, key_len + 1, &value);
}

int
cos_param_list_writer_init(cos_param_list_writer_t *pclist, cos_dict_t *pcd,
			   int print_ok)
{
    gs_param_list_init((gs_param_list *)pclist, &cos_param_list_writer_procs,
		       COS_OBJECT_MEMORY(pcd));
    pclist->pcd = pcd;
    pclist->print_ok = print_ok;
    return 0;
}

/* ------ Streams ------ */

private cos_proc_release(cos_stream_release);
private cos_proc_write(cos_stream_write);
const cos_object_procs_t cos_stream_procs = {
    cos_stream_release, cos_stream_write
};

cos_stream_t *
cos_stream_alloc(gx_device_pdf *pdev, client_name_t cname)
{
    gs_memory_t *mem = pdev->pdf_memory;
    cos_stream_t *pcs =
	gs_alloc_struct(mem, cos_stream_t, &st_cos_object, cname);

    cos_object_init((cos_object_t *)pcs, pdev, &cos_stream_procs);
    return pcs;
}

private void
cos_stream_release(cos_object_t *pco, client_name_t cname)
{
    gs_memory_t *mem = cos_object_memory(pco);
    cos_stream_t *const pcs = (cos_stream_t *)pco;
    cos_stream_piece_t *cur;
    cos_stream_piece_t *next;

    for (cur = pcs->pieces; cur; cur = next) {
	next = cur->next;
	gs_free_object(mem, cur, cname);
    }
    pcs->pieces = 0;
    cos_dict_release(pco, cname);
}

/* Find the total length of a stream. */
private long
cos_stream_length(const cos_stream_t *pcs)
{
    const cos_stream_piece_t *pcsp = pcs->pieces;
    long length;

    for (length = 0; pcsp; pcsp = pcsp->next)
	length += pcsp->size;
    return length;
}

/* Write the (dictionary) elements of a stream. */
/* (This procedure is exported.) */
int
cos_stream_elements_write(const cos_stream_t *pcs, gx_device_pdf *pdev)
{
    return cos_elements_write(pdev->strm, pcs->elements, pdev, true);
}

/* Write the contents of a stream.  (This procedure is exported.) */
int
cos_stream_contents_write(const cos_stream_t *pcs, gx_device_pdf *pdev)
{
    stream *s = pdev->strm;
    cos_stream_piece_t *pcsp;
    cos_stream_piece_t *last;
    cos_stream_piece_t *next;
    FILE *sfile = pdev->streams.file;
    long end_pos;
    int code;

    sflush(pdev->streams.strm);
    end_pos = ftell(sfile);

    /* Reverse the elements temporarily. */
    for (pcsp = pcs->pieces, last = NULL; pcsp; pcsp = next)
	next = pcsp->next, pcsp->next = last, last = pcsp;
    for (pcsp = last, code = 0; pcsp && code >= 0; pcsp = pcsp->next) {
	fseek(sfile, pcsp->position, SEEK_SET);
	pdf_copy_data(s, sfile, pcsp->size);
    }
    /* Reverse the elements back. */
    for (pcsp = last, last = NULL; pcsp; pcsp = next)
	next = pcsp->next, pcsp->next = last, last = pcsp;

    fseek(sfile, end_pos, SEEK_SET);
    return code;
}

private int
cos_stream_write(const cos_object_t *pco, gx_device_pdf *pdev)
{
    stream *s = pdev->strm;
    const cos_stream_t *const pcs = (const cos_stream_t *)pco;
    int code;

    stream_puts(s, "<<");
    cos_elements_write(s, pcs->elements, pdev, false);
    pprintld1(s, "/Length %ld>>stream\n", cos_stream_length(pcs));
    code = cos_stream_contents_write(pcs, pdev);
    stream_puts(s, "\nendstream\n");

    return code;
}

/* Return a stream's dictionary (just a cast). */
cos_dict_t *
cos_stream_dict(cos_stream_t *pcs)
{
    return (cos_dict_t *)pcs;
}

/* Add a contents piece to a stream object: size bytes just written on */
/* streams.strm. */
int
cos_stream_add(cos_stream_t *pcs, uint size)
{
    gx_device_pdf *pdev = pcs->pdev;
    stream *s = pdev->streams.strm;
    long position = stell(s);
    cos_stream_piece_t *prev = pcs->pieces;

    /* Check for consecutive writing -- just an optimization. */
    if (prev != 0 && prev->position + prev->size + size == position) {
	prev->size += size;
    } else {
	gs_memory_t *mem = pdev->pdf_memory;
	cos_stream_piece_t *pcsp =
	    gs_alloc_struct(mem, cos_stream_piece_t, &st_cos_stream_piece,
			    "cos_stream_add");

	if (pcsp == 0)
	    return_error(gs_error_VMerror);
	pcsp->position = position - size;
	pcsp->size = size;
	pcsp->next = pcs->pieces;
	pcs->pieces = pcsp;
    }
    return 0;
}
int
cos_stream_add_since(cos_stream_t *pcs, long start_pos)
{
    return cos_stream_add(pcs,
			  (uint)(stell(pcs->pdev->streams.strm) - start_pos));
}

/* Add bytes to a stream object. */
int
cos_stream_add_bytes(cos_stream_t *pcs, const byte *data, uint size)
{
    stream_write(pcs->pdev->streams.strm, data, size);
    return cos_stream_add(pcs, size);
}

/* Create a stream that writes into a Cos stream. */
/* Closing the stream will free it. */
/* Note that this is not a filter. */
typedef struct cos_write_stream_state_s {
    stream_state_common;
    cos_stream_t *pcs;
    gx_device_pdf *pdev;
    stream *s;			/* pointer back to stream */
    stream *target;		/* use this instead of strm */
} cos_write_stream_state_t;
gs_private_st_suffix_add4(st_cos_write_stream_state, cos_write_stream_state_t,
			  "cos_write_stream_state_t",
			  cos_ws_state_enum_ptrs, cos_ws_state_reloc_ptrs,
			  st_stream_state, pcs, pdev, s, target);
private int
cos_write_stream_process(stream_state * st, stream_cursor_read * pr,
			 stream_cursor_write * ignore_pw, bool last)
{
    uint count = pr->limit - pr->ptr;
    cos_write_stream_state_t *ss = (cos_write_stream_state_t *)st;
    gx_device_pdf *pdev = ss->pdev;
    stream *target = ss->target;
    long start_pos = stell(pdev->streams.strm);
    int code;

    stream_write(target, pr->ptr + 1, count);
    pr->ptr = pr->limit;
    sflush(target);
    code = cos_stream_add_since(ss->pcs, start_pos);
    return (code < 0 ? ERRC : 0);
}
private int
cos_write_stream_close(stream *s)
{
    cos_write_stream_state_t *ss = (cos_write_stream_state_t *)s->state;
    int status;

    sflush(s);
    status = s_close_filters(&ss->target, ss->pdev->streams.strm);
    return (status < 0 ? status : s_std_close(s));
}

private const stream_procs cos_s_procs = {
    s_std_noavailable, s_std_noseek, s_std_write_reset,
    s_std_write_flush, cos_write_stream_close, cos_write_stream_process
};
private const stream_template cos_write_stream_template = {
    &st_cos_write_stream_state, 0, cos_write_stream_process, 1, 1
};
stream *
cos_write_stream_alloc(cos_stream_t *pcs, gx_device_pdf *pdev,
		       client_name_t cname)
{
    gs_memory_t *mem = pdev->pdf_memory;
    stream *s = s_alloc(mem, cname);
    cos_write_stream_state_t *ss = (cos_write_stream_state_t *)
	s_alloc_state(mem, &st_cos_write_stream_state, cname);
#define CWS_BUF_SIZE 512	/* arbitrary */
    byte *buf = gs_alloc_bytes(mem, CWS_BUF_SIZE, cname);

    if (s == 0 || ss == 0 || buf == 0)
	goto fail;
    ss->template = &cos_write_stream_template;
    ss->pcs = pcs;
    ss->pdev = pdev;
    ss->s = s;
    ss->target = pdev->streams.strm; /* not s->strm */
    s_std_init(s, buf, CWS_BUF_SIZE, &cos_s_procs, s_mode_write);
    s->state = (stream_state *)ss;
    return s;
#undef CWS_BUF_SIZE
 fail:
    gs_free_object(mem, buf, cname);
    gs_free_object(mem, ss, cname);
    gs_free_object(mem, s, cname);
    return 0;
}
