/* Copyright (C) 1999, 2000, 2001 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: gdevpdfc.c,v 1.17.2.1.2.1 2003/01/17 00:49:01 giles Exp $ */
/* Color space management and writing for pdfwrite driver */
#include "math_.h"
#include "memory_.h"
#include "gx.h"
#include "gscspace.h"		/* for gscie.h */
#include "gscdevn.h"
#include "gscie.h"
#include "gscindex.h"
#include "gscsepr.h"
#include "stream.h"
#include "gsicc.h"
#include "gserrors.h"
#include "gdevpdfx.h"
#include "gdevpdfg.h"
#include "gdevpdfo.h"
#include "strimpl.h"
#include "sstring.h"

/* ------ CIE space testing ------ */

/* Test whether a cached CIE procedure is the identity function. */
#define CIE_CACHE_IS_IDENTITY(pc)\
  ((pc)->floats.params.is_identity)
#define CIE_CACHE3_IS_IDENTITY(pca)\
  (CIE_CACHE_IS_IDENTITY(&(pca)[0]) &&\
   CIE_CACHE_IS_IDENTITY(&(pca)[1]) &&\
   CIE_CACHE_IS_IDENTITY(&(pca)[2]))

/*
 * Test whether a cached CIE procedure is an exponential.  A cached
 * procedure is exponential iff f(x) = k*(x^p).  We make a very cursory
 * check for this: we require that f(0) = 0, set k = f(1), set p =
 * log[a](f(a)/k), and then require that f(b) = k*(b^p), where a and b are
 * two arbitrarily chosen values between 0 and 1.  Naturally all this is
 * done with some slop.
 */
#define CC_INDEX_A (gx_cie_cache_size / 3)
#define CC_INDEX_B (gx_cie_cache_size * 2 / 3)
#define CC_VALUE(i) ((i) / (double)(gx_cie_cache_size - 1))
#define CCX_VALUE_A CC_VALUE(CC_INDEX_A)
#define CCX_VALUE_B CC_VALUE(CC_INDEX_B)

private bool
cie_values_are_exponential(floatp va, floatp vb, floatp k, float *pexpt)
{
    double p;

    if (fabs(k) < 0.001)
	return false;
    if (va == 0 || (va > 0) != (k > 0))
	return false;
    p = log(va / k) / log(CCX_VALUE_A);
    if (fabs(vb - k * pow(CCX_VALUE_B, p)) >= 0.001)
	return false;
    *pexpt = p;
    return true;
}

private bool
cie_scalar_cache_is_exponential(const gx_cie_scalar_cache * pc, float *pexpt)
{
    if (fabs(pc->floats.values[0]) >= 0.001)
	return false;
    return cie_values_are_exponential(pc->floats.values[CC_INDEX_A],
				      pc->floats.values[CC_INDEX_B],
				      pc->floats.values[gx_cie_cache_size - 1],
				      pexpt);
}
#define CIE_SCALAR3_CACHE_IS_EXPONENTIAL(pca, expts)\
  (cie_scalar_cache_is_exponential(&(pca)[0], &(expts).u) &&\
   cie_scalar_cache_is_exponential(&(pca)[1], &(expts).v) &&\
   cie_scalar_cache_is_exponential(&(pca)[2], &(expts).w))

private bool
cie_vector_cache_is_exponential(const gx_cie_vector_cache * pc, float *pexpt)
{
    if (fabs(pc->vecs.values[0].u) >= 0.001)
	return false;
    return cie_values_are_exponential(pc->vecs.values[CC_INDEX_A].u,
				      pc->vecs.values[CC_INDEX_B].u,
				      pc->vecs.values[gx_cie_cache_size - 1].u,
				      pexpt);
}
#define CIE_VECTOR3_CACHE_IS_EXPONENTIAL(pca, expts)\
  (cie_vector_cache_is_exponential(&(pca)[0], &(expts).u) &&\
   cie_vector_cache_is_exponential(&(pca)[1], &(expts).v) &&\
   cie_vector_cache_is_exponential(&(pca)[2], &(expts).w))

#undef CC_INDEX_A
#undef CC_INDEX_B
#undef CC_VALUE
#undef CCX_VALUE_A
#undef CCX_VALUE_B

/* ------ Lab space synthesis ------ */

/*
 * PDF doesn't have general CIEBased color spaces.  However, since the
 * transformation from L*a*b* space to XYZ space is invertible, we can
 * handle any PostScript CIEBased space by transforming color values in
 * that space to XYZ, then inverse-transforming them to L*a*b* and using
 * a L*a*b* space with the same WhitePoint and BlackPoint and appropriate
 * ranges for a* and b*.  This approach has two drawbacks:
 *
 *	- Y values outside the range [0..1] can't be represented: we clamp
 *	them.
 *
 *	- For shadings, color interpolation will occur in the Lab space
 *	rather in the original CIEBased space.  We aren't going to worry
 *	about this.
 */

/* Transform a CIEBased color to XYZ. */
private int
cie_to_xyz(const double *in, double out[3], const gs_color_space *pcs)
{
    /****** NOT IMPLEMENTED YET ******/
    out[0] = in[0], out[1] = in[1], out[2] = in[2];	/****** BOGUS ******/
    return 0;
}

/* Transform XYZ values to Lab. */
private double
lab_g_inverse(double v)
{
    if (v >= (6.0 * 6.0 * 6.0) / (29 * 29 * 29))
	return pow(v, 1.0 / 3);	/* use cbrt if available? */
    else
	return (v * (841.0 / 108) + 4.0 / 29);
}
private void
xyz_to_lab(const double xyz[3], double lab[3], const gs_cie_common *pciec)
{
    const gs_vector3 *const pWhitePoint = &pciec->points.WhitePoint;
    double L, lunit;

    /* Calculate L* first. */
    L = lab_g_inverse(xyz[1] / pWhitePoint->v) * 116 - 16;
    /* Clamp L* to the PDF range [0..100]. */
    if (L < 0)
	L = 0;
    else if (L > 100)
	L = 100;
    lab[1] = L;
    lunit = (L + 16) / 116;

    /* Calculate a* and b*. */
    lab[0] = (lab_g_inverse(xyz[0] / pWhitePoint->u) - lunit) * 500;
    lab[2] = (lab_g_inverse(xyz[2] / pWhitePoint->w) - lunit) * -200;
}

/* Create a PDF Lab color space corresponding to a CIEBased color space. */
private int
lab_range(double lab_min[3], double lab_max[3],
	  const gs_color_space *pcs, const gs_cie_common *pciec)
{
    /*
     * Determine the range of a* and b* by evaluating the color space
     * mapping at all of its extrema.
     */
    int ncomp = gs_color_space_num_components(pcs);
    int i, j;
    const gs_range *ranges;

    switch (gs_color_space_get_index(pcs)) {
    case gs_color_space_index_CIEDEFG:
	ranges = pcs->params.defg->RangeDEFG.ranges;
	break;
    case gs_color_space_index_CIEDEF:
	ranges = pcs->params.def->RangeDEF.ranges;
	break;
    case gs_color_space_index_CIEABC:
	ranges = pcs->params.abc->RangeABC.ranges;
	break;
    case gs_color_space_index_CIEA:
	ranges = &pcs->params.a->RangeA;
	break;
    default:
	return_error(gs_error_rangecheck);
    }

    for (j = 1; j < 3; ++j)
	lab_min[j] = 1000.0, lab_max[j] = -1000.0;
    for (i = 0; i < 1 << ncomp; ++i) {
	double in[4], xyz[3];

	for (j = 0; j < ncomp; ++j)
	    in[j] = (i & (1 << j) ? ranges[j].rmax : ranges[j].rmin);
	if (cie_to_xyz(in, xyz, pcs) >= 0) {
	    double lab[3];

	    xyz_to_lab(xyz, lab, pciec);
	    for (j = 1; j < 3; ++j) {
		lab_min[j] = min(lab_min[j], lab[j]);
		lab_max[j] = max(lab_max[j], lab[j]);
	    }
	}
    }
    return 0;
}
private int
pdf_lab_color_space(cos_array_t *pca, cos_dict_t *pcd,
		    const gs_color_space *pcs, const gs_cie_common *pciec)
{
    double lab_min[3], lab_max[3]; /* only 1 and 2 used */
    cos_array_t *pcar = cos_array_alloc(pca->pdev, "pdf_lab_color_space");
    cos_value_t v;
    int code;

    if (pcar == 0)
	return_error(gs_error_VMerror);
    if ((code = lab_range(lab_min, lab_max, pcs, pciec)) < 0 ||
	(code = cos_array_add(pca, cos_c_string_value(&v, "/Lab"))) < 0 ||
	(code = cos_array_add_real(pcar, lab_min[1])) < 0 ||
	(code = cos_array_add_real(pcar, lab_max[1])) < 0 ||
	(code = cos_array_add_real(pcar, lab_min[2])) < 0 ||
	(code = cos_array_add_real(pcar, lab_max[2])) < 0 ||
	(code = cos_dict_put_c_key_object(pcd, "/Range", COS_OBJECT(pcar))) < 0
	)
	return code;
    return 0;
}

/* ------ Color space writing ------ */

/* Define standard and short color space names. */
const pdf_color_space_names_t pdf_color_space_names = {
    PDF_COLOR_SPACE_NAMES
};
const pdf_color_space_names_t pdf_color_space_names_short = {
    PDF_COLOR_SPACE_NAMES_SHORT
};

/*
 * Create a local Device{Gray,RGB,CMYK} color space corresponding to the
 * given number of components.
 */
int
pdf_cspace_init_Device(gs_color_space *pcs, int num_components)
{
    switch (num_components) {
    case 1: gs_cspace_init_DeviceGray(pcs); break;
    case 3: gs_cspace_init_DeviceRGB(pcs); break;
    case 4: gs_cspace_init_DeviceCMYK(pcs); break;
    default: return_error(gs_error_rangecheck);
    }
    return 0;
}

/* Add a 3-element vector to a Cos array or dictionary. */
private int
cos_array_add_vector3(cos_array_t *pca, const gs_vector3 *pvec)
{
    int code = cos_array_add_real(pca, pvec->u);

    if (code >= 0)
	code = cos_array_add_real(pca, pvec->v);
    if (code >= 0)
	code = cos_array_add_real(pca, pvec->w);
    return code;
}
private int
cos_dict_put_c_key_vector3(cos_dict_t *pcd, const char *key,
			   const gs_vector3 *pvec)
{
    cos_array_t *pca = cos_array_alloc(pcd->pdev, "cos_array_from_vector3");
    int code;

    if (pca == 0)
	return_error(gs_error_VMerror);
    code = cos_array_add_vector3(pca, pvec);
    if (code < 0) {
	COS_FREE(pca, "cos_array_from_vector3");
	return code;
    }
    return cos_dict_put_c_key_object(pcd, key, COS_OBJECT(pca));
}

/* Create a Separation or DeviceN color space (internal). */
private int
pdf_separation_color_space(gx_device_pdf *pdev,
			   cos_array_t *pca, const char *csname,
			   const cos_value_t *snames,
			   const gs_color_space *alt_space,
			   const gs_function_t *pfn,
			   const pdf_color_space_names_t *pcsn)
{
    cos_value_t v;
    int code;

    if ((code = cos_array_add(pca, cos_c_string_value(&v, csname))) < 0 ||
	(code = cos_array_add_no_copy(pca, snames)) < 0 ||
	(code = pdf_color_space(pdev, &v, alt_space, pcsn, false)) < 0 ||
	(code = cos_array_add(pca, &v)) < 0 ||
	(code = pdf_function(pdev, pfn, &v)) < 0 ||
	(code = cos_array_add(pca, &v)) < 0
	)
	return code;
    return 0;
}

/*
 * Create a PDF color space corresponding to a PostScript color space.
 * For parameterless color spaces, set *pvalue to a (literal) string with
 * the color space name; for other color spaces, create a cos_dict_t if
 * necessary and set *pvalue to refer to it.
 */
int
pdf_color_space(gx_device_pdf *pdev, cos_value_t *pvalue,
		const gs_color_space *pcs,
		const pdf_color_space_names_t *pcsn,
		bool by_name)
{
    gs_memory_t *mem = pdev->pdf_memory;
    gs_color_space_index csi = gs_color_space_get_index(pcs);
    cos_array_t *pca;
    cos_dict_t *pcd;
    cos_value_t v;
    const gs_cie_common *pciec;
    gs_function_t *pfn;
    int code;

    switch (csi) {
    case gs_color_space_index_DeviceGray:
	cos_c_string_value(pvalue, pcsn->DeviceGray);
	return 0;
    case gs_color_space_index_DeviceRGB:
	cos_c_string_value(pvalue, pcsn->DeviceRGB);
	return 0;
    case gs_color_space_index_DeviceCMYK:
	cos_c_string_value(pvalue, pcsn->DeviceCMYK);
	return 0;
    case gs_color_space_index_Pattern:
	if (!pcs->params.pattern.has_base_space) {
	    cos_c_string_value(pvalue, "/Pattern");
	    return 0;
	}
	break;
    case gs_color_space_index_CIEICC:
        /*
	 * Take a special early exit for unrecognized ICCBased color spaces,
	 * or for PDF 1.2 output (ICCBased color spaces date from PDF 1.3).
	 */
        if (pcs->params.icc.picc_info->picc == 0 ||
	    pdev->CompatibilityLevel < 1.3
	    )
            return pdf_color_space( pdev, pvalue,
                                    (const gs_color_space *)
                                        &pcs->params.icc.alt_space,
                                    pcsn, by_name);
        break;
    default:
	break;
    }

    /* Space has parameters -- create an array. */
    pca = cos_array_alloc(pdev, "pdf_color_space");
    if (pca == 0)
	return_error(gs_error_VMerror);
    switch (csi) {
    case gs_color_space_index_CIEICC: {
        /* this would arise only in a pdf ==> pdf translation, but we
         * should allow for it anyway */
        const gs_icc_params * picc_params = &pcs->params.icc;
        const gs_cie_icc * picc_info = picc_params->picc_info;
        int ncomps = picc_info->num_components;
        cos_stream_t * pcstrm;
        int i;
        gs_color_space_index alt_csi;

        /* ICCBased color spaces are essentially copied to the output */
        if ((code = cos_array_add(pca, cos_c_string_value(&v, "/ICCBased"))) < 0)
            return code;

        /* create a stream for the output */
        if ((pcstrm = cos_stream_alloc( pdev, "pdf_color_space(stream)")) == 0)
            return_error(gs_error_VMerror);

        /* indicate the number of components */
        code = cos_dict_put_c_key_int(cos_stream_dict(pcstrm), "/N", ncomps);

        /* indicate the range, if appropriate */
        for (i = 0; i < ncomps && picc_info->Range.ranges[i].rmin == 0.0 &&
             picc_info->Range.ranges[i].rmax == 1.0; i++)
            ;
        if (code >= 0 && i != ncomps) {
            cos_array_t * prngca = cos_array_alloc(pdev,
                                                   "pdf_color_space(Range)");

            if (prngca == 0)
                return_error(gs_error_VMerror);
            for (i = 0; code >= 0 && i < ncomps; i++) {
                code = cos_array_add_int(prngca,
                                         picc_info->Range.ranges[i].rmin);
                if (code >= 0)
                    code = cos_array_add_int(prngca,
                                             picc_info->Range.ranges[i].rmax);
            }
            if (code < 0 || 
                (code = cos_dict_put_c_key_object(cos_stream_dict(pcstrm),
                                                  "/Range",
                                                  COS_OBJECT(prngca))) < 0)
                COS_FREE(prngca, "pcf_colos_space(Range)");
        }

        /* output the alternate color space, if necessary */
        alt_csi = gs_color_space_get_index(pcs);
        if (code >= 0 && alt_csi != gs_color_space_index_DeviceGray &&
            alt_csi != gs_color_space_index_DeviceRGB &&
            alt_csi != gs_color_space_index_DeviceCMYK) {
            code = pdf_color_space(pdev, pvalue, 
                               (const gs_color_space *)&picc_params->alt_space,
                               &pdf_color_space_names, false);
            if (code >= 0)
                code = cos_dict_put_c_key(cos_stream_dict(pcstrm), 
                                          "/Alternate", pvalue);
        }

        /* transfer the profile stream */
        while (code >= 0) {
            byte    sbuff[256];
            uint    cnt;

            code = sgets(picc_info->instrp, sbuff, sizeof(sbuff), &cnt);
            if (cnt == 0)
                break;
            if (code >= 0)
                code = cos_stream_add_bytes(pcstrm, sbuff, cnt);
        }
        if (code >= 0)
            code = cos_array_add(pca, COS_OBJECT_VALUE(&v, pcstrm));
        else
            COS_FREE(pcstrm, "pcf_color_space(ICCBased dictionary)");
        }
        break;

    case gs_color_space_index_CIEA: {
	/* Check that we can represent this as a CalGray space. */
	const gs_cie_a *pcie = pcs->params.a;
	gs_vector3 expts;

	pciec = (const gs_cie_common *)pcie;
	if (!(pcie->MatrixA.u == 1 && pcie->MatrixA.v == 1 &&
	      pcie->MatrixA.w == 1 &&
	      pcie->common.MatrixLMN.is_identity))
	    return_error(gs_error_rangecheck);
	if (CIE_CACHE_IS_IDENTITY(&pcie->caches.DecodeA) &&
	    CIE_SCALAR3_CACHE_IS_EXPONENTIAL(pcie->common.caches.DecodeLMN, expts) &&
	    expts.v == expts.u && expts.w == expts.u
	    ) {
	    DO_NOTHING;
	} else if (CIE_CACHE3_IS_IDENTITY(pcie->common.caches.DecodeLMN) &&
		   cie_vector_cache_is_exponential(&pcie->caches.DecodeA, &expts.u)
		   ) {
	    DO_NOTHING;
	} else
	    goto lab;
	code = cos_array_add(pca, cos_c_string_value(&v, "/CalGray"));
	if (code < 0)
	    return code;
	pcd = cos_dict_alloc(pdev, "pdf_color_space(dict)");
	if (pcd == 0)
	    return_error(gs_error_VMerror);
	if (expts.u != 1) {
	    code = cos_dict_put_c_key_real(pcd, "/Gamma", expts.u);
	    if (code < 0)
		return code;
	}
    }
    cal:
    code = cos_dict_put_c_key_vector3(pcd, "/WhitePoint",
				      &pciec->points.WhitePoint);
    if (code < 0)
	return code;
    if (pciec->points.BlackPoint.u != 0 ||
	pciec->points.BlackPoint.v != 0 ||
	pciec->points.BlackPoint.w != 0
	) {
	code = cos_dict_put_c_key_vector3(pcd, "/BlackPoint",
					  &pciec->points.BlackPoint);
	if (code < 0)
	    return code;
    }
    code = cos_array_add(pca, COS_OBJECT_VALUE(&v, pcd));
    break;
    case gs_color_space_index_CIEABC: {
	/* Check that we can represent this as a CalRGB space. */
	const gs_cie_abc *pcie = pcs->params.abc;
	gs_vector3 expts;
	const gs_matrix3 *pmat;

	pciec = (const gs_cie_common *)pcie;
	if (pcie->common.MatrixLMN.is_identity &&
	    CIE_CACHE3_IS_IDENTITY(pcie->common.caches.DecodeLMN) &&
	    CIE_VECTOR3_CACHE_IS_EXPONENTIAL(pcie->caches.DecodeABC, expts)
	    )
	    pmat = &pcie->MatrixABC;
	else if (pcie->MatrixABC.is_identity &&
		 CIE_CACHE3_IS_IDENTITY(pcie->caches.DecodeABC) &&
		 CIE_SCALAR3_CACHE_IS_EXPONENTIAL(pcie->common.caches.DecodeLMN, expts)
		 )
	    pmat = &pcie->common.MatrixLMN;
	else
	    goto lab;
	code = cos_array_add(pca, cos_c_string_value(&v, "/CalRGB"));
	if (code < 0)
	    return code;
	pcd = cos_dict_alloc(pdev, "pdf_color_space(dict)");
	if (pcd == 0)
	    return_error(gs_error_VMerror);
	if (expts.u != 1 || expts.v != 1 || expts.w != 1) {
	    code = cos_dict_put_c_key_vector3(pcd, "/Gamma", &expts);
	    if (code < 0)
		return code;
	}
	if (!pmat->is_identity) {
	    cos_array_t *pcma =
		cos_array_alloc(pdev, "pdf_color_space(Matrix)");

	    if (pcma == 0)
		return_error(gs_error_VMerror);
	    if ((code = cos_array_add_vector3(pcma, &pmat->cu)) < 0 ||
		(code = cos_array_add_vector3(pcma, &pmat->cv)) < 0 ||
		(code = cos_array_add_vector3(pcma, &pmat->cw)) < 0 ||
		(code = cos_dict_put(pcd, (const byte *)"/Matrix", 7,
				     COS_OBJECT_VALUE(&v, pcma))) < 0
		)
		return code;
	}
    }
    goto cal;
    case gs_color_space_index_CIEDEF:
	pciec = (const gs_cie_common *)pcs->params.def;
	goto lab;
    case gs_color_space_index_CIEDEFG:
	pciec = (const gs_cie_common *)pcs->params.defg;
    lab:
	/* Convert all other CIEBased spaces to a Lab space. */
	/****** NOT IMPLEMENTED YET, REQUIRES TRANSFORMING VALUES ******/
	if (1) return_error(gs_error_rangecheck);
	pcd = cos_dict_alloc(pdev, "pdf_color_space(dict)");
	if (pcd == 0)
	    return_error(gs_error_VMerror);
	code = pdf_lab_color_space(pca, pcd, pcs, pciec);
	if (code < 0)
	    return code;
	goto cal;
    case gs_color_space_index_Indexed: {
	const gs_indexed_params *pip = &pcs->params.indexed;
	const gs_color_space *base_space =
	    (const gs_color_space *)&pip->base_space;
	int num_entries = pip->hival + 1;
	int num_components = gs_color_space_num_components(base_space);
	uint table_size = num_entries * num_components;
	/* Guess at the extra space needed for ASCII85 encoding. */
	uint string_size = 1 + table_size * 2 + table_size / 30 + 2;
	uint string_used;
	byte buf[100];		/* arbitrary */
	stream_AXE_state st;
	stream s, es;
	byte *table =
	    gs_alloc_string(mem, string_size, "pdf_color_space(table)");
	byte *palette =
	    gs_alloc_string(mem, table_size, "pdf_color_space(palette)");
	gs_color_space cs_gray;

	if (table == 0 || palette == 0) {
	    gs_free_string(mem, palette, table_size,
			   "pdf_color_space(palette)");
	    gs_free_string(mem, table, string_size,
			   "pdf_color_space(table)");
	    return_error(gs_error_VMerror);
	}
	swrite_string(&s, table, string_size);
	s_init(&es, NULL);
	s_init_state((stream_state *)&st, &s_AXE_template, NULL);
	s_init_filter(&es, (stream_state *)&st, buf, sizeof(buf), &s);
	sputc(&s, '<');
	if (pcs->params.indexed.use_proc) {
	    gs_client_color cmin, cmax;
	    byte *pnext = palette;

	    int i, j;

	    /* Find the legal range for the color components. */
	    for (j = 0; j < num_components; ++j)
		cmin.paint.values[j] = min_long,
		    cmax.paint.values[j] = max_long;
	    gs_color_space_restrict_color(&cmin, base_space);
	    gs_color_space_restrict_color(&cmax, base_space);
	    /*
	     * Compute the palette values, with the legal range for each
	     * one mapped to [0 .. 255].
	     */
	    for (i = 0; i < num_entries; ++i) {
		gs_client_color cc;

		gs_cspace_indexed_lookup(&pcs->params.indexed, i, &cc);
		for (j = 0; j < num_components; ++j) {
		    float v = (cc.paint.values[j] - cmin.paint.values[j])
			* 255 / (cmax.paint.values[j] - cmin.paint.values[j]);

		    *pnext++ = (v <= 0 ? 0 : v >= 255 ? 255 : (byte)v);
		}
	    }
	} else
	    memcpy(palette, pip->lookup.table.data, table_size);
	if (gs_color_space_get_index(base_space) ==
	    gs_color_space_index_DeviceRGB
	    ) {
	    /* Check for an all-gray palette. */
	    int i;

	    for (i = table_size; (i -= 3) >= 0; )
		if (palette[i] != palette[i + 1] ||
		    palette[i] != palette[i + 2]
		    )
		    break;
	    if (i < 0) {
		/* Change the color space to DeviceGray. */
		for (i = 0; i < num_entries; ++i)
		    palette[i] = palette[i * 3];
		table_size = num_entries;
		gs_cspace_init_DeviceGray(&cs_gray);
		base_space = &cs_gray;
	    }
	}
	stream_write(&es, palette, table_size);
	gs_free_string(mem, palette, table_size, "pdf_color_space(palette)");
	sclose(&es);
	sflush(&s);
	string_used = (uint)stell(&s);
	table = gs_resize_string(mem, table, string_size, string_used,
				 "pdf_color_space(table)");
	/*
	 * Since the array is always referenced by name as a resource
	 * rather than being written as a value, even for in-line images,
	 * always use the full name for the color space.
	 */
	if ((code = pdf_color_space(pdev, pvalue, base_space,
				    &pdf_color_space_names, false)) < 0 ||
	    (code = cos_array_add(pca,
			cos_c_string_value(&v, 
					   pdf_color_space_names.Indexed
					   /*pcsn->Indexed*/))) < 0 ||
	    (code = cos_array_add(pca, pvalue)) < 0 ||
	    (code = cos_array_add_int(pca, pip->hival)) < 0 ||
	    (code = cos_array_add_no_copy(pca,
					  cos_string_value(&v, table,
							   string_used))) < 0
	    )
	    return code;
    }
    break;
    case gs_color_space_index_DeviceN:
	pfn = gs_cspace_get_devn_function(pcs);
	/****** CURRENTLY WE ONLY HANDLE Functions ******/
	if (pfn == 0)
	    return_error(gs_error_rangecheck);
	{
	    cos_array_t *psna = 
		cos_array_alloc(pdev, "pdf_color_space(DeviceN)");
	    int i;

	    if (psna == 0)
		return_error(gs_error_VMerror);
	    for (i = 0; i < pcs->params.device_n.num_components; ++i) {
		code = pdf_separation_name(pdev, &v,
					   pcs->params.device_n.names[i]);
		if (code < 0 ||
		    (code = cos_array_add_no_copy(psna, &v)) < 0)
		    return code;
	    }
	    COS_OBJECT_VALUE(&v, psna);
	    if ((code = pdf_separation_color_space(pdev, pca, "/DeviceN", &v,
						   (const gs_color_space *)
					&pcs->params.device_n.alt_space,
					pfn, &pdf_color_space_names)) < 0)
		return code;
	}
	break;
    case gs_color_space_index_Separation:
	pfn = gs_cspace_get_sepr_function(pcs);
	/****** CURRENTLY WE ONLY HANDLE Functions ******/
	if (pfn == 0)
	    return_error(gs_error_rangecheck);
	if ((code = pdf_separation_name(pdev, &v,
					pcs->params.separation.sname)) < 0 ||
	    (code = pdf_separation_color_space(pdev, pca, "/Separation", &v,
					       (const gs_color_space *)
					&pcs->params.separation.alt_space,
					pfn, &pdf_color_space_names)) < 0)
	    return code;
	break;
    case gs_color_space_index_Pattern:
	if ((code = pdf_color_space(pdev, pvalue,
				    (const gs_color_space *)
				    &pcs->params.pattern.base_space,
				    &pdf_color_space_names, false)) < 0 ||
	    (code = cos_array_add(pca,
				  cos_c_string_value(&v, "/Pattern"))) < 0 ||
	    (code = cos_array_add(pca, pvalue)) < 0
	    )
	    return code;
	break;
    default:
	return_error(gs_error_rangecheck);
    }
    /*
     * Register the color space as a resource, since it must be referenced
     * by name rather than directly.
     */
    {
	pdf_resource_t *pres;

	code =
	    pdf_alloc_resource(pdev, resourceColorSpace, gs_no_id, &pres, 0L);
	if (code < 0) {
	    COS_FREE(pca, "pdf_color_space");
	    return code;
	}
	pca->id = pres->object->id;
	COS_FREE(pres->object, "pdf_color_space");
	pres->object = (cos_object_t *)pca;
	cos_write_object(COS_OBJECT(pca), pdev);
    }
    if (by_name) {
	/* Return a resource name rather than an object reference. */
	discard(COS_RESOURCE_VALUE(pvalue, pca));
    } else
	discard(COS_OBJECT_VALUE(pvalue, pca));
    return 0;
}

/* Create colored and uncolored Pattern color spaces. */
private int
pdf_pattern_space(gx_device_pdf *pdev, cos_value_t *pvalue,
		  pdf_resource_t **ppres, const char *cs_name)
{
    if (!*ppres) {
	int code = pdf_begin_resource_body(pdev, resourceColorSpace, gs_no_id,
					   ppres);

	if (code < 0)
	    return code;
	pprints1(pdev->strm, "%s\n", cs_name);
	pdf_end_resource(pdev);
	(*ppres)->object->written = true; /* don't write at end */
    }
    cos_resource_value(pvalue, (*ppres)->object);
    return 0;
}
int
pdf_cs_Pattern_colored(gx_device_pdf *pdev, cos_value_t *pvalue)
{
    return pdf_pattern_space(pdev, pvalue, &pdev->cs_Patterns[0],
			     "[/Pattern]");
}
int
pdf_cs_Pattern_uncolored(gx_device_pdf *pdev, cos_value_t *pvalue)
{
    int ncomp = pdev->color_info.num_components;
    static const char *const pcs_names[5] = {
	0, "[/Pattern /DeviceGray]", 0, "[/Pattern /DeviceRGB]",
	"[/Pattern /DeviceCMYK]"
    };

    return pdf_pattern_space(pdev, pvalue, &pdev->cs_Patterns[ncomp],
			     pcs_names[ncomp]);
}

/* ---------------- Miscellaneous ---------------- */

/* Set the ProcSets bits corresponding to an image color space. */
void
pdf_color_space_procsets(gx_device_pdf *pdev, const gs_color_space *pcs)
{
    const gs_color_space *pbcs = pcs;

 csw:
    switch (gs_color_space_get_index(pbcs)) {
    case gs_color_space_index_DeviceGray:
    case gs_color_space_index_CIEA:
	/* We only handle CIEBasedA spaces that map to CalGray. */
	pdev->procsets |= ImageB;
	break;
    case gs_color_space_index_Indexed:
	pdev->procsets |= ImageI;
	pbcs = (const gs_color_space *)&pcs->params.indexed.base_space;
	goto csw;
    default:
	pdev->procsets |= ImageC;
	break;
    }
}
