/*  $Header: /home/cvsroot/dvipdfmx/src/pdfcolor.c,v 1.13 2006/12/11 12:46:03 chofchof Exp $
    
    This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks.

    Copyright (C) 2002 by Jin-Hwan Cho and Shunsaku Hirata,
    the dvipdfmx project team <dvipdfmx@project.ktug.or.kr>
    
    Copyright (C) 1998, 1999 by Mark A. Wicks <mwicks@kettering.edu>

    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 USA.
*/

/* No page independence here...
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "system.h"
#include "mem.h"
#include "error.h"

#include "dpxfile.h"

#include "pdfdoc.h"
#include "pdfdev.h"

#include "pdfcolor.h"


static int verbose = 0;
void
pdf_color_set_verbose (void)
{
  verbose++;
}

int
pdf_color_rgbcolor (pdf_color *color, double r, double g, double b)
{
  ASSERT(color);

  if (r < 0.0 || r > 1.0) {
    WARN("Invalid color value specified: red=%g",   r);
    return -1;
  }
  if (g < 0.0 || g > 1.0) {
    WARN("Invalid color value specified: green=%g", g);
    return -1;
  }
  if (b < 0.0 || b > 1.0) {
    WARN("Invalid color value specified: blue=%g", b);
    return -1;
  }
  color->values[0] = r;
  color->values[1] = g;
  color->values[2] = b;

  color->num_components = 3;

  return 0;
}

int
pdf_color_cmykcolor (pdf_color *color,
		     double c, double m, double y, double k)
{
  ASSERT(color);

  if (c < 0.0 || c > 1.0) {
    WARN("Invalid color value specified: cyan=%g", c);
    return -1;
  }
  if (m < 0.0 || m > 1.0) {
    WARN("Invalid color value specified: magenta=%g", m);
    return -1;
  }
  if (y < 0.0 || y > 1.0) {
    WARN("Invalid color value specified: yellow=%g", y);
    return -1;
  }
  if (k < 0.0 || k > 1.0) {
    WARN("Invalid color value specified: black=%g", k);
    return -1;
  }

  color->values[0] = c;
  color->values[1] = m;
  color->values[2] = y;
  color->values[3] = k;

  color->num_components = 4;

  return 0;
}

int
pdf_color_graycolor (pdf_color *color, double g)
{
  ASSERT(color);

  if (g < 0.0 || g > 1.0) {
    WARN("Invalid color value specified: gray=%g", g);
    return -1;
  }

  color->values[0] = g;

  color->num_components = 1;

  return 0;
}


void
pdf_color_copycolor (pdf_color *color1, const pdf_color *color2)
{
  ASSERT(color1 && color2);

  memcpy(color1, color2, sizeof(pdf_color));
}

int
pdf_color_is_white (pdf_color *color)
{
  int  is_white = 0;

  ASSERT(color);

  switch (color->num_components) {
  case 1:
    if (color->values[0] == 1.0)
      is_white = 1;
    break;
  case 4:
    if (color->values[0] == 0.0 &&
	color->values[1] == 0.0 &&
	color->values[2] == 0.0 &&
	color->values[3] == 0.0) {
      is_white = 1;
    }
    break;
  case 3:
    if (color->values[0] == 1.0 &&
	color->values[1] == 1.0 &&
	color->values[2] == 1.0) {
      is_white = 1;
    }
    break;
  default:
    is_white = 0;
    break;
  }

  return is_white;
}

pdf_color current_fill   = {
  1,
  {0.0, 0.0, 0.0, 0.0}
};

pdf_color current_stroke = {
  1,
  {0.0, 0.0, 0.0, 0.0}
};

#if  0
/*
 * This routine is not a real color matching.
 */
static int
compare_color (const pdf_color *color1, const pdf_color *color2)
{
  if (color1->num_components != color2->num_components)
    return -1;

  switch (color1->num_components) {
  case  4:
    if (color1->values[0] == color2->values[0] &&
	color1->values[1] == color2->values[1] &&
	color1->values[2] == color2->values[2] &&
	color1->values[3] == color2->values[3])
      return 0;
    break;
  case  3:
    if (color1->values[0] == color2->values[0] &&
	color1->values[1] == color2->values[1] &&
	color1->values[2] == color2->values[2])
      return 0;
    break;
  case  1:
    if (color1->values[0] == color2->values[0])
      return 0;
    break;
  }

  return  -1;
}
#endif

static int
valid_color_values (pdf_color *validated, const pdf_color *color)
{
  int  i, num_components = 0;

  num_components = color->num_components;
  if (num_components != 1 &&
      num_components != 3 &&
      num_components != 4) {
    ERROR("Only RGB/CMYK/Gray currently supported.");
    return -1;
  }

  for (i = 0; i < num_components; i++) {
    if (color->values[i] < 0.0 || color->values[i] > 1.0) {
      WARN("Invalid color value: %g", color->values[i]);
      return -1;
    } else {
      validated->values[i] = color->values[i];
    }
  }
  validated->num_components = color->num_components;

  return 0;
}

int
pdf_dev_currentcolor (pdf_color *color, int is_fill)
{
  ASSERT(color);

  if (is_fill) {
    memcpy(color, &current_fill, sizeof(pdf_color));
  } else {
    memcpy(color, &current_stroke, sizeof(pdf_color));
  }

  return 0;
}

int
pdf_dev_setcolor (const pdf_color *color, int is_fill)
{
  int       len = 0, colormode;
  int       i;
  char      format_buffer[128];
  pdf_color validated;

  if (!color) {
    WARN("No color specified.");
    return -1;
  }

  colormode = pdf_dev_get_param(PDF_DEV_PARAM_COLORMODE);
  if (!colormode) {
    WARN("setcolor ignored. (Ignore color option set)");
    return  0;
  }

  if (valid_color_values(&validated, color) < 0) {
    return -1;
  }

#if  0
  /* There are much problems in this :( */
  if (is_fill) {
    if (!compare_color(&validated, &current_fill)) {
      return 0;
    }
  } else {
    if (!compare_color(&validated, &current_stroke)) {
      return 0;
    }
  }
#endif

  graphics_mode();

  for (i = 0; i < validated.num_components; i++) {
    len = sprintf(format_buffer, " %g", ROUND(validated.values[i], 0.01));
    pdf_doc_add_page_content(format_buffer, len);
  }

  switch (validated.num_components) {
  case  3:
    pdf_doc_add_page_content((is_fill ? " rg" : " RG"), 3);
    break;
  case  4:
    pdf_doc_add_page_content((is_fill ? " k"  : " K" ), 2);
    break;
  case  1:
    pdf_doc_add_page_content((is_fill ? " g"  : " G" ), 2);
    break;
  default:
    ERROR("Only RGB/CMYK/Gray currently supported.");
    return -1;
    break;
  }

  if (is_fill) {
    memcpy(&current_fill,   &validated, sizeof(pdf_color));
  } else {
    memcpy(&current_stroke, &validated, sizeof(pdf_color));
  }

  return  0;
}


/* Dvipdfm special */
pdf_color default_color = {
  1,
  {0.0, 0.0, 0.0, 0.0}
};

void
pdf_color_set_default (const pdf_color *color)
{
  ASSERT(color);

  memcpy(&default_color, color, sizeof(pdf_color));
}


/*
 * Having color stack here is wrong.
 * dvi.c or XXX_special should treat this.
 */
#define DEV_COLOR_STACK_MAX 128

static struct {
  int       next;
  pdf_color stroke[DEV_COLOR_STACK_MAX];
  pdf_color fill[DEV_COLOR_STACK_MAX];
} color_stack = {
  0,
};

/* BUG (20060330): color change does not effect on the next page.
 *   The problem is due to the part of grestore because it restores
 *   the color values in the state of gsave which are not correct
 *   if the color values are changed inside of a page.
 */
void
pdf_dev_preserve_color (void)
{
  if (color_stack.next > 0) {
    current_stroke = color_stack.stroke[color_stack.next];
    current_fill   = color_stack.fill[color_stack.next];
  }
}

void
pdf_dev_reset_color (void)
{
  int  colormode;
  pdf_color fillcolor, strokecolor;

  /* Shouldn't give a warning */
  colormode = pdf_dev_get_param(PDF_DEV_PARAM_COLORMODE);
  if (!colormode) {
    return;
  }

  if (color_stack.next > 0) {
    strokecolor = current_stroke;
    fillcolor   = current_fill;
  } else {
    strokecolor = default_color;
    fillcolor   = default_color;
  }

  pdf_dev_setcolor(&strokecolor, 0);
  pdf_dev_setcolor(&fillcolor, 1);
 
  return;
}


void
pdf_color_clear (void)
{
  color_stack.next = 0;
}

void
pdf_color_push (void)
{
  int  current;

  current = color_stack.next;
  if (current >= DEV_COLOR_STACK_MAX) {
    WARN("Exceeded depth of color stack.");
    return;
  }

  memcpy(&color_stack.stroke[current],
	 &current_stroke, sizeof(pdf_color));
  memcpy(&color_stack.fill[current],
	 &current_fill  , sizeof(pdf_color));

  color_stack.next++;

  return;
}

void
pdf_color_pop (void)
{
  int  current;

  current = --(color_stack.next);

  if (current <  0) {
    WARN("End color with no corresponding begin color.");
    /*
     * In the case of mismatched push and pop, color_stack.next must set
     * to be the default value, 0. Otherwise causes segmentation fault
     * in the next push command.
     */
    pdf_color_clear();
    return;
  }

  pdf_dev_setcolor(&color_stack.stroke[current], 0);
  pdf_dev_setcolor(&color_stack.fill[current]  , 1);

  return;
}

/***************************** COLOR SPACE *****************************/

static int pdf_colorspace_defineresource (const char *ident,
					  int   subtype,
					  void *cdata, pdf_obj *resource);

static int pdf_colorspace_findresource   (const char *ident,
					  int   subtype, const void *cdata);

#if 0
struct calgray_cdata
{
  double white_point[3]; /* required, second component must
			  * be equal to 1.0
			  */
  double black_point[3]; /* optional, default: [0 0 0] */
  double gamma;          /* optional, default: 1.0     */
};

struct calrgb_cdata
{
  double white_point[3]; /* required, second component must
			  * be equal to 1.0
			  */
  double black_point[3]; /* optional, default: [0 0 0] */
  double gamma[3];       /* optional, default: [1 1 1] */
  double matrix[9];      /* optional, default: identity
			  * [1 0 0 0 1 0 0 0 1]
			  */
};

static void
release_calrgb (void *cdata)
{
  struct calrgb_cdata *calrgb;

  if (cdata) {
    calrgb = (struct calrgb_cdata *) cdata;
    RELEASE(calrgb);
  }
}

static int
compare_calrgb (const char *ident1, const void *cdata1,
		const char *ident2, const void *cdata2)
{
  struct calrgb_cdata *calrgb1;
  struct calrgb_cdata *calrgb2;

  if (ident1 && ident2 &&
      !strcmp(ident1, ident2)) {
    return 0;
  }
}

static void
init_calrgb (struct calrgb_cdata *calrgb)
{
  ASSERT(calrgb);

  calrgb->white_point[0] = 1.0;
  calrgb->white_point[1] = 1.0;
  calrgb->white_point[2] = 1.0;

  calrgb->black_point[0] = 0.0;
  calrgb->black_point[1] = 0.0;
  calrgb->black_point[2] = 0.0;

  calrgb->gamma[0]  = 1.0;
  calrgb->gamma[1]  = 1.0;
  calrgb->gamma[2]  = 1.0;

  calrgb->matrix[0] = 1.0;
  calrgb->matrix[1] = 0.0;
  calrgb->matrix[2] = 0.0;

  calrgb->matrix[3] = 0.0;
  calrgb->matrix[4] = 1.0;
  calrgb->matrix[5] = 0.0;

  calrgb->matrix[6] = 0.0;
  calrgb->matrix[7] = 0.0;
  calrgb->matrix[8] = 1.0;
}

static int
valid_calrgb (struct calrgb_cdata *calrgb)
{
  if (calrgb->white_point[1] != 1.0 ||
      calrgb->white_point[0] <= 0.0 ||
      calrgb->white_point[2] <= 0.0)
    return 0;

  if (calrgb->black_point[0] < 0.0 ||
      calrgb->black_point[1] < 0.0 ||
      calrgb->black_point[2] < 0.0)
    return 0;

  if (calrgb->gamma[0] < 0.0 ||
      calrgb->gamma[1] < 0.0 ||
      calrgb->gamma[2] < 0.0)
    return 0;

  /* matrix should be invertible? */

  return 1;
}

static pdf_obj *
pdf_color_make_calrgb_resource (struct calrgb_cdata *calrgb)
{
  pdf_obj *colorspace;
  pdf_obj *calparams, *tmp_array;

  ASSERT(calrgb);

  if (!valid_calrgb(calrgb))
    return NULL;

  colorspace = pdf_new_array();
  calparams  = pdf_new_dict();

  tmp_array  = pdf_new_array();
  pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->white_point[0], 0.001)));
  pdf_add_array(tmp_array, pdf_new_number(1.0));
  pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->white_point[2], 0.001)));
  pdf_add_dict(calparams, pdf_new_name("WhitePoint"), tmp_array);

  if (calrgb->black_point[0] != 0.0 ||
      calrgb->black_point[1] != 0.0 ||
      calrgb->black_point[2] != 0.0) {
    tmp_array  = pdf_new_array();
    pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->black_point[0], 0.001)));
    pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->black_point[1], 0.001)));
    pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->black_point[2], 0.001)));
    pdf_add_dict(calparams, pdf_new_name("BlackPoint"), tmp_array);
  }

  if (calrgb->gamma[0] != 1.0 ||
      calrgb->gamma[1] != 1.0 ||
      calrgb->gamma[2] != 1.0) {
    tmp_array  = pdf_new_array();
    pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->gamma[0], 0.001)));
    pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->gamma[1], 0.001)));
    pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->gamma[2], 0.001)));
    pdf_add_dict(calparams, pdf_new_name("Gamma"), tmp_array);
  }

  if (calrgb->matrix[0] != 1.0 ||
      calrgb->matrix[1] != 0.0 ||
      calrgb->matrix[2] != 0.0 ||
      calrgb->matrix[3] != 0.0 ||
      calrgb->matrix[4] != 1.0 ||
      calrgb->matrix[5] != 0.0 ||
      calrgb->matrix[6] != 0.0 ||
      calrgb->matrix[7] != 0.0 ||
      calrgb->matrix[8] != 1.0) {
    tmp_array  = pdf_new_array();
    pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->matrix[0], 0.001)));
    pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->matrix[1], 0.001)));
    pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->matrix[2], 0.001)));
    pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->matrix[3], 0.001)));
    pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->matrix[4], 0.001)));
    pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->matrix[5], 0.001)));
    pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->matrix[6], 0.001)));
    pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->matrix[7], 0.001)));
    pdf_add_array(tmp_array, pdf_new_number(ROUND(calrgb->matrix[8], 0.001)));
    pdf_add_dict(calparams,  pdf_new_name("Matrix"), tmp_array);
  }

  pdf_add_array(colorspace, pdf_new_name("CalRGB"));
  pdf_add_array(colorspace, calparams);

  return colorspace;
}
#endif

static unsigned char  nullbytes16[16] = {
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

static struct
{
  int  major;
  int  minor;
} icc_versions[] = {
  {0, 0}, /* PDF-1.0, we don't support them */
  {0, 0}, /* PDF-1.1, we don't support them */
  {0, 0}, /* PDF-1.2, we don't support them */
  {0x02, 0x10}, /* PDF-1.3 */
  {0x02, 0x20}, /* PDF-1.4 */
  {0x04, 0x00}  /* PDF-1.5 */
};

static int
iccp_version_supported (int major, int minor)
{
  int  pdf_ver;

  pdf_ver = pdf_get_version();
  if (pdf_ver < 6) {
    if (icc_versions[pdf_ver].major < major)
      return 0;
    else if (icc_versions[pdf_ver].major == major &&
	     icc_versions[pdf_ver].minor <  minor)
      return 0;
    else {
      return 1;
    }
  }

  return 0;
}

typedef unsigned long iccSig;
static iccSig
str2iccSig (const void *s)
{
  char  *p;

  p = (char *) s;

  return (iccSig) ((p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3]);
}

typedef struct
{
  long X, Y, Z; /* s15Fixed16Numeber */
} iccXYZNumber;

typedef struct
{
  long          size;
  iccSig        CMMType;
  long          version;
  iccSig        devClass;
  iccSig        colorSpace;
  iccSig        PCS;    /* Profile Connection Space */
  char          creationDate[12];
  iccSig        acsp;
  iccSig        platform;
  char          flags[4];
  iccSig        devMnfct;
  iccSig        devModel;
  char          devAttr[8];
  long          intent;
  iccXYZNumber  illuminant;
  iccSig        creator;
  unsigned char ID[16]; /* MD5 checksum with Rendering intent,
			 * Header attrs, Profile ID fields are
			 * set to zeros.
			 */
  /* 28 bytes reserved - must be set to zeros */
} iccHeader;

#define iccNullSig 0
static void
iccp_init_iccHeader (iccHeader *icch)
{
  ASSERT(icch);

  icch->size       = 0;
  icch->CMMType    = iccNullSig;
  icch->version    = 0xFFFFFF;
  icch->devClass   = iccNullSig;
  icch->colorSpace = iccNullSig;
  icch->PCS        = iccNullSig;
  memset(icch->creationDate, 0, 12);
  icch->acsp       = str2iccSig("ascp");
  icch->platform   = iccNullSig;
  memset(icch->flags, 0, 4);
  icch->devMnfct   = iccNullSig;
  icch->devModel   = iccNullSig;
  memset(icch->devAttr, 0, 8);
  icch->intent     = 0;
  icch->illuminant.X = 0;
  icch->illuminant.Y = 0;
  icch->illuminant.Z = 0;
  icch->creator      = iccNullSig;
  memset(icch->ID, 0, 16);
}

#define ICC_INTENT_TYPE(n) ((int) (((n) >> 16) & 0xff))
#define ICC_INTENT_PERCEPTUAL 0
#define ICC_INTENT_RELATIVE   1
#define ICC_INTENT_SATURATION 2
#define ICC_INTENT_ABSOLUTE   3

/*
 * In ICC profile stream dicrionary, there is /Range whose values must
 * "match the information in the profile". But where is those values in?
 *
 * How should I treat rendering intent?
 */
struct iccbased_cdata
{
  long           sig; /* 'i' 'c' 'c' 'b' */

  unsigned char  checksum[16]; /* 16 bytes MD5 Checksum   */
  int            colorspace;   /* input colorspace:
				*   RGB, Gray, CMYK, (Lab?)
				*/
  int            alternate;    /* alternate colorspace (id), unused */
};

#define check_sig(d,p,q,r,s) ((d) && (d)->sig == ((p)<<24|(q)<<16|(r)<<8|(s)))

static void
init_iccbased_cdata (struct iccbased_cdata *cdata)
{
  ASSERT(cdata);

  cdata->sig = ('i' << 24|'c' << 16|'c' << 8|'b');
  memset(cdata->checksum, 0, 16);
  cdata->colorspace = PDF_COLORSPACE_TYPE_INVALID;
  cdata->alternate  = -1;

  return;
}

static void
release_iccbased_cdata (struct iccbased_cdata *cdata)
{
  ASSERT(check_sig(cdata, 'i', 'c', 'c', 'b'));

  RELEASE(cdata);
}

static int
get_num_components_iccbased (const struct iccbased_cdata *cdata)
{
  int  num_components = 0;

  ASSERT(check_sig(cdata, 'i', 'c', 'c', 'b'));

  switch (cdata->colorspace) {
  case PDF_COLORSPACE_TYPE_RGB:
    num_components = 3;
    break;
  case PDF_COLORSPACE_TYPE_CMYK:
    num_components = 4;
    break;
  case PDF_COLORSPACE_TYPE_GRAY:
    num_components = 1;
    break;
  case PDF_COLORSPACE_TYPE_CIELAB:
    num_components = 3;
    break;
  }

  return num_components;
}

static int
compare_iccbased (const char *ident1, const struct iccbased_cdata *cdata1,
		  const char *ident2, const struct iccbased_cdata *cdata2)
{
  if (cdata1 && cdata2) {

    ASSERT(check_sig(cdata1, 'i', 'c', 'c', 'b'));
    ASSERT(check_sig(cdata2, 'i', 'c', 'c', 'b'));

    if (memcmp(cdata1->checksum, nullbytes16, 16) &&
	memcmp(cdata2->checksum, nullbytes16, 16)) {
      return memcmp(cdata1->checksum, cdata2->checksum, 16);
    }
    if (cdata1->colorspace != cdata2->colorspace) {
      return (cdata1->colorspace - cdata2->colorspace);
    }

    /* Continue if checksum unknown and colorspace is same. */
  }

  if (ident1 && ident2)
    return strcmp(ident1, ident2);

  /* No way to compare */
  return -1;
}

int
iccp_check_colorspace (int colortype, const void *profile, long proflen)
{
  iccSig  colorspace;
  unsigned char  *p;

  if (!profile || proflen < 128)
    return -1;

  p = (unsigned char *) profile;

  colorspace = str2iccSig(p + 16);

  switch (colortype) {
  case PDF_COLORSPACE_TYPE_CALRGB:
  case PDF_COLORSPACE_TYPE_RGB:
    if (colorspace != str2iccSig("RGB ")) {
      return -1;
    }
    break;
  case PDF_COLORSPACE_TYPE_CALGRAY:
  case PDF_COLORSPACE_TYPE_GRAY:
    if (colorspace != str2iccSig("GRAY")) {
      return -1;
    }
    break;
  case PDF_COLORSPACE_TYPE_CMYK:
    if (colorspace != str2iccSig("CMYK")) {
      return -1;
    }
    break;
  default:
    return -1;
    break;
  }

  return 0;
}

pdf_obj *
iccp_get_rendering_intent (const void *profile, long proflen)
{
  pdf_obj       *ri = NULL;
  unsigned char *p;
  long           intent;

  if (!profile || proflen < 128)
    return NULL;

  p = (unsigned char *) profile;

  intent = (p[64] << 24)|(p[65] << 16)|(p[66] << 8)|p[67];
  switch (ICC_INTENT_TYPE(intent)) {
  case ICC_INTENT_SATURATION:
    ri = pdf_new_name("Saturation");
    break;
  case ICC_INTENT_PERCEPTUAL:
    ri = pdf_new_name("Perceptual");
    break;
  case ICC_INTENT_ABSOLUTE:
    ri = pdf_new_name("AbsoluteColorimetric");
    break;
  case ICC_INTENT_RELATIVE:
    ri = pdf_new_name("RelativeColorimetric");
    break;
  default:
    WARN("Invalid rendering intent type: %d", ICC_INTENT_TYPE(intent));
    ri = NULL;
  }

  return ri;
}

#define sget_signed_long(p)  ((long)   ((p)[0] << 24|(p)[1] << 16|(p)[2] << 8|(p)[3]))
#define sget_signed_short(p) ((short)  ((p)[0] << 8|(p)[1]))
#define get_iccSig(p)        ((iccSig) ((p)[0] << 24|(p)[1] << 16|(p)[2] << 8|(p)[3]))

static int
iccp_unpack_header (iccHeader *icch,
		    const void *profile, long proflen, int check_size)
{
  unsigned char *p, *endptr;

  if (check_size) {
    if (!profile || proflen < 128 ||
	proflen % 4 != 0) {
      WARN("Profile size: %ld", proflen);
      return -1;
    }
  }

  p      = (unsigned char *) profile;
  endptr = p + 128;

  icch->size = sget_signed_long(p);
  if (check_size) {
    if (icch->size != proflen) {
      WARN("ICC Profile size: %ld(header) != %ld", icch->size, proflen);
      return -1;
    }
  }
  p += 4;

  icch->CMMType    = str2iccSig(p);
  p += 4;
  icch->version    = sget_signed_long(p);
  p += 4;
  icch->devClass   = str2iccSig(p);
  p += 4;
  icch->colorSpace = str2iccSig(p);
  p += 4;
  icch->PCS        = str2iccSig(p);
  p += 4;
  memcpy(icch->creationDate, p, 12);
  p += 12;
  icch->acsp = str2iccSig(p); /* acsp */
  if (icch->acsp != str2iccSig("acsp")) {
    WARN("Invalid ICC profile: not \"acsp\" - %c%c%c%c ",
	 p[0], p[1], p[2], p[3]);
    return -1;
  }
  p += 4;
  icch->platform = str2iccSig(p);
  p += 4;
  memcpy(icch->flags, p, 4);
  p += 4;
  icch->devMnfct = str2iccSig(p);
  p += 4;
  icch->devModel = str2iccSig(p);
  p += 4;
  memcpy(icch->devAttr,  p, 8);
  p += 8;
  icch->intent = (p[0] << 24)|(p[1] << 16)|(p[2] << 8)|p[3];
  p += 4;
  icch->illuminant.X = sget_signed_long(p);
  p += 4;
  icch->illuminant.Y = sget_signed_long(p);
  p += 4;
  icch->illuminant.Z = sget_signed_long(p);
  p += 4;
  icch->creator = str2iccSig(p);
  p += 4;
  memcpy(icch->ID, p, 16);
  p += 16;

  /* 28 bytes reserved - must be set to zeros */
  for (; p < endptr; p++) {
    if (*p != '\0') {
      WARN("Reserved pad not zero: %02x (at offset %ld in ICC profile header.)",
	   *p, 128 - ((long) (endptr - p)));
      return -1;
    }
  }

  return 0;
}

/* MD5 checksum with Rendering intent,
 * Header attrs, Profile ID fields are
 * set to zeros.
 */
#define ICC_HEAD_SECT1_START  0
#define ICC_HEAD_SECT1_LENGTH 56
/* 8 bytes devAttr, 4 bytes intent */
#define ICC_HEAD_SECT2_START  68
#define ICC_HEAD_SECT2_LENGTH 16
/* 16 bytes ID (checksum) */
#define ICC_HEAD_SECT3_START  100
#define ICC_HEAD_SECT3_LENGTH 28

#include "dpxcrypt.h"
static void
iccp_get_checksum (unsigned char *checksum, const void *profile, long proflen)
{
  unsigned char *p;
  MD5_CONTEXT    md5;

  p = (unsigned char *) profile;

  MD5_init (&md5);
  MD5_write(&md5, p + ICC_HEAD_SECT1_START, ICC_HEAD_SECT1_LENGTH);
  MD5_write(&md5, nullbytes16, 12);
  MD5_write(&md5, p + ICC_HEAD_SECT2_START, ICC_HEAD_SECT2_LENGTH);
  MD5_write(&md5, nullbytes16, 16);
  MD5_write(&md5, p + ICC_HEAD_SECT3_START, ICC_HEAD_SECT3_LENGTH);

  /* body */
  MD5_write(&md5, p + 128, proflen - 128);

  MD5_final(checksum, &md5);
}

static void
print_iccp_header (iccHeader *icch, unsigned char *checksum)
{
  int   i;

  ASSERT(icch);

#define print_iccSig(s,t) if ((s) == 0) {\
    MESG("pdf_color>> %s:\t(null)\n", (t)); \
  } else if (!isprint(((s) >> 24) & 0xff) || \
             !isprint(((s) >> 16) & 0xff) || \
             !isprint(((s) >>  8) & 0xff) || \
             !isprint((s) & 0xff)) { \
    MESG("pdf_color>> %s:\t(invalid)\n", (t)); \
  } else { \
    MESG("pdf_color>> %s:\t%c%c%c%c\n",  (t), \
         ((s) >> 24) & 0xff, ((s) >> 16) & 0xff, \
         ((s) >>  8) & 0xff, (s) & 0xff); \
}

  MESG("\n");
  MESG("pdf_color>> ICC Profile Info\n");
  MESG("pdf_color>> Profile Size:\t%ld bytes\n", icch->size);
  print_iccSig(icch->CMMType, "CMM Type");
  MESG("pdf_color>> Profile Version:\t%d.%01d.%01d\n",
       (icch->version >> 24) & 0xff,
       (icch->version >> 20) & 0x0f,
       (icch->version >> 16) & 0x0f);
  print_iccSig(icch->devClass,   "Device Class");
  print_iccSig(icch->colorSpace, "Color Space");
  print_iccSig(icch->PCS, "Connection Space");
  MESG("pdf_color>> Creation Date:\t");
  for (i = 0; i < 12; i += 2) {
    if (i == 0)
      MESG("%04u",
	   sget_unsigned_pair((unsigned char *) icch->creationDate));
    else {
      MESG(":%02u",
	   sget_unsigned_pair((unsigned char *) (&icch->creationDate[i])));
    }
  }
  MESG("\n");
  print_iccSig(icch->platform, "Primary Platform");
  MESG("pdf_color>> Profile Flags:\t%02x:%02x:%02x:%02x\n",
       icch->flags[0], icch->flags[1], icch->flags[2], icch->flags[3]);
  print_iccSig(icch->devMnfct, "Device Mnfct");
  print_iccSig(icch->devModel, "Device Model");
  MESG("pdf_color>> Device Attr:\t");
  for (i = 0; i < 8; i++) {
    if (i == 0)
      MESG("%02x",  icch->devAttr[i]);
    else
      MESG(":%02x", icch->devAttr[i]);
  }
  MESG("\n");
  MESG("pdf_color>> Rendering Intent:\t");
  switch (ICC_INTENT_TYPE(icch->intent)) {
  case ICC_INTENT_SATURATION:
    MESG("Saturation");
    break;
  case ICC_INTENT_PERCEPTUAL:
    MESG("Perceptual");
    break;
  case ICC_INTENT_ABSOLUTE:
    MESG("Absolute Colorimetric");
    break;
  case ICC_INTENT_RELATIVE:
    MESG("Relative Colorimetric");
    break;
  default:
    MESG("(invalid)");
    break;
  }
  MESG("\n");
  print_iccSig(icch->creator, "Creator");
  MESG("pdf_color>> Illuminant (XYZ):\t");
  MESG("%.3f %.3f %.3f\n",
       (double) icch->illuminant.X / 0x10000,
       (double) icch->illuminant.Y / 0x10000,
       (double) icch->illuminant.Z / 0x10000);
  MESG("pdf_color>> Checksum:\t");
  if (!memcmp(icch->ID, nullbytes16, 16)) {
    MESG("(null)");
  } else {
    for (i = 0; i < 16; i++) {
      if (i == 0)
	MESG("%02x",  icch->ID[i]);
      else
	MESG(":%02x", icch->ID[i]);
    }
  }
  MESG("\n");
  if (checksum) {
    MESG("pdf_color>> Calculated:\t");
    for (i = 0; i < 16; i++) {
      if (i == 0)
	MESG("%02x", checksum[i]);
      else
	MESG(":%02x", checksum[i]);
    }
    MESG("\n");
  }

  return;
}


static int
iccp_devClass_allowed (int dev_class)
{
  int    colormode;

  colormode = pdf_dev_get_param(PDF_DEV_PARAM_COLORMODE);

  switch (colormode) {
#if 0
  case PDF_DEV_COLORMODE_PDFX1:
    break;
  case PDF_DEV_COLORMODE_PDFX3:
    if (dev_class != str2iccSig("prtr")) {
      return 0;
    }
    break;
#endif
  default:
    if (dev_class != str2iccSig("scnr") &&
	dev_class != str2iccSig("mntr") &&
	dev_class != str2iccSig("prtr") &&
	dev_class != str2iccSig("spac")) {
      return 0;
    }
    break;
  }


  return 1;
}

int
iccp_load_profile (const char *ident,
		   const void *profile, long proflen)
{
  int       cspc_id;
  pdf_obj  *resource;
  pdf_obj  *stream;
  pdf_obj  *stream_dict;
  iccHeader icch;
  int       colorspace;
  unsigned char checksum[16];
  struct iccbased_cdata *cdata;

  iccp_init_iccHeader(&icch);
  if (iccp_unpack_header(&icch, profile, proflen, 1) < 0) { /* check size */
    WARN("Invalid ICC profile header in \"%s\"", ident);
    print_iccp_header(&icch, NULL);
    return -1;
  }

  if (!iccp_version_supported((icch.version >> 24) & 0xff,
			      (icch.version >> 16) & 0xff)) {
    WARN("ICC profile format spec. version %d.%01d.%01d"
	 " not supported in current PDF version setting.",
	 (icch.version >> 24) & 0xff,
	 (icch.version >> 20) & 0x0f,
	 (icch.version >> 16) & 0x0f);
    WARN("ICC profile not embedded.");
    print_iccp_header(&icch, NULL);
    return -1;
  }

  if (!iccp_devClass_allowed(icch.devClass)) {
    WARN("Unsupported ICC Profile Device Class:");
    print_iccp_header(&icch, NULL);
    return -1;
  }

  if (icch.colorSpace == str2iccSig("RGB ")) {
    colorspace = PDF_COLORSPACE_TYPE_RGB;
  } else if (icch.colorSpace == str2iccSig("GRAY")) {
    colorspace = PDF_COLORSPACE_TYPE_GRAY;
  } else if (icch.colorSpace == str2iccSig("CMYK")) {
    colorspace = PDF_COLORSPACE_TYPE_CMYK;
  } else {
    WARN("Unsupported input color space.");
    print_iccp_header(&icch, NULL);
    return -1;
  }

  iccp_get_checksum(checksum, profile, proflen);
  if (memcmp(icch.ID,  nullbytes16, 16) &&
      memcmp(icch.ID,  checksum, 16)) {
    WARN("Invalid ICC profile: Inconsistent checksum.");
    print_iccp_header(&icch, checksum);
    return -1;
  }

  cdata = NEW(1, struct iccbased_cdata);
  init_iccbased_cdata(cdata);
  cdata->colorspace = colorspace;
  memcpy(cdata->checksum, checksum, 16);

  cspc_id = pdf_colorspace_findresource(ident,
					PDF_COLORSPACE_TYPE_ICCBASED, cdata);
  if (cspc_id >= 0) {
    if (verbose)
      MESG("(ICCP:[id=%d])", cspc_id);
    release_iccbased_cdata(cdata);
    return cspc_id;
  }
  if (verbose > 1) {
    print_iccp_header(&icch, checksum);
  }

  resource = pdf_new_array();

  stream = pdf_new_stream(STREAM_COMPRESS);
  pdf_add_array(resource, pdf_new_name("ICCBased"));
  pdf_add_array(resource, pdf_ref_obj (stream));

  stream_dict = pdf_stream_dict(stream);
  pdf_add_dict(stream_dict, pdf_new_name("N"),
	       pdf_new_number(get_num_components_iccbased(cdata)));

  pdf_add_stream (stream, profile, proflen);
  pdf_release_obj(stream);

  cspc_id = pdf_colorspace_defineresource(ident,
					  PDF_COLORSPACE_TYPE_ICCBASED,
					  cdata, resource);

  return cspc_id;
}

#define WBUF_SIZE 4096
static unsigned char wbuf[WBUF_SIZE];

static pdf_obj *
iccp_load_file_stream (unsigned char *checksum, long length, FILE *fp)
{
  pdf_obj       *stream;
  MD5_CONTEXT    md5;
  long           nb_read;

  rewind(fp);

  if (fread(wbuf, 1, 128, fp) != 128) {
    return NULL;
  }
  length -= 128;

  stream = pdf_new_stream(STREAM_COMPRESS);

  MD5_init (&md5);
  MD5_write(&md5, wbuf + ICC_HEAD_SECT1_START, ICC_HEAD_SECT1_LENGTH);
  MD5_write(&md5, nullbytes16, 12);
  MD5_write(&md5, wbuf + ICC_HEAD_SECT2_START, ICC_HEAD_SECT2_LENGTH);
  MD5_write(&md5, nullbytes16, 16);
  MD5_write(&md5, wbuf + ICC_HEAD_SECT3_START, ICC_HEAD_SECT3_LENGTH);

  pdf_add_stream(stream, wbuf, 128);

  /* body */
  while (length > 0) {
    nb_read = fread(wbuf, 1, MIN(length, WBUF_SIZE), fp);
    MD5_write(&md5, wbuf, nb_read);
    pdf_add_stream(stream, wbuf, nb_read);
    length -= nb_read;
  }

  MD5_final(checksum, &md5);


  return stream;
}

int
pdf_colorspace_load_ICCBased (const char *ident, const char *filename)
{
  int       cspc_id;
  FILE     *fp;
  pdf_obj  *resource;
  pdf_obj  *stream;
  pdf_obj  *stream_dict;
  iccHeader icch;
  int       colorspace;
  long      size;
  unsigned char checksum[16];
  struct iccbased_cdata *cdata;


  fp = DPXFOPEN(filename, DPX_RES_TYPE_ICCPROFILE);
  if (!fp)
    return -1;

  size = file_size(fp);
  if (size < 128) {
    MFCLOSE(fp);
    return -1;
  }
  if (fread(wbuf, 1, 128, fp) != 128) {
    DPXFCLOSE(fp);
    return -1;
  }

  iccp_init_iccHeader(&icch);
  if (iccp_unpack_header(&icch, wbuf, 128, 0) < 0) {
    WARN("Invalid ICC profile header in \"%s\"", ident);
    print_iccp_header(&icch, NULL);
    DPXFCLOSE(fp);
    return -1;
  }
  if (icch.size > size) {
    WARN("File size smaller than recorded in header: %ld %ld",
	 icch.size, size);
    DPXFCLOSE(fp);
    return -1;
  }

  if (!iccp_version_supported((icch.version >> 24) & 0xff,
			      (icch.version >> 16) & 0xff)) {
    WARN("ICC profile format spec. version %d.%01d.%01d"
	 " not supported in current PDF version setting.",
	 (icch.version >> 24) & 0xff,
	 (icch.version >> 20) & 0x0f,
	 (icch.version >> 16) & 0x0f);
    WARN("ICC profile not embedded.");
    print_iccp_header(&icch, NULL);
    DPXFCLOSE(fp);
    return -1;
  }

  if (!iccp_devClass_allowed(icch.devClass)) {
    WARN("Unsupported ICC Profile Device Class:");
    print_iccp_header(&icch, NULL);
    DPXFCLOSE(fp);
    return -1;
  }

  if (icch.colorSpace == str2iccSig("RGB ")) {
    colorspace = PDF_COLORSPACE_TYPE_RGB;
  } else if (icch.colorSpace == str2iccSig("GRAY")) {
    colorspace = PDF_COLORSPACE_TYPE_GRAY;
  } else if (icch.colorSpace == str2iccSig("CMYK")) {
    colorspace = PDF_COLORSPACE_TYPE_CMYK;
  } else {
    WARN("Unsupported input color space.");
    print_iccp_header(&icch, NULL);
    DPXFCLOSE(fp);
    return -1;
  }

  stream = iccp_load_file_stream(checksum, icch.size, fp);
  DPXFCLOSE(fp);

  if (!stream) {
    WARN("Loading ICC Profile failed...: %s", filename);
    return -1;
  }

  if (memcmp(icch.ID,  nullbytes16, 16) &&
      memcmp(icch.ID,  checksum, 16)) {
    WARN("Invalid ICC profile: Inconsistent checksum.");
    print_iccp_header(&icch, NULL);
    pdf_release_obj(stream);
    return -1;
  }

  cdata = NEW(1, struct iccbased_cdata);
  init_iccbased_cdata(cdata);
  cdata->colorspace = colorspace;
  memcpy(cdata->checksum, checksum, 16);

  cspc_id = pdf_colorspace_findresource(ident,
					PDF_COLORSPACE_TYPE_ICCBASED, cdata);
  if (cspc_id >= 0) {
    if (verbose)
      MESG("(ICCP:[id=%d])", cspc_id);
    release_iccbased_cdata(cdata);
    pdf_release_obj(stream);
    return cspc_id;
  }
  if (verbose > 1) {
    print_iccp_header(&icch, checksum);
  }

  resource = pdf_new_array();

  pdf_add_array(resource, pdf_new_name("ICCBased"));
  pdf_add_array(resource, pdf_ref_obj (stream));

  stream_dict = pdf_stream_dict(stream);
  pdf_add_dict(stream_dict, pdf_new_name("N"),
	       pdf_new_number(get_num_components_iccbased(cdata)));
  pdf_release_obj(stream);

  cspc_id = pdf_colorspace_defineresource(ident,
					  PDF_COLORSPACE_TYPE_ICCBASED,
					  cdata, resource);

  return cspc_id;
}

typedef struct {
  char    *ident;
  int      subtype;

  pdf_obj *resource;
  pdf_obj *reference;

  void    *cdata;
} pdf_colorspace;

static struct {
  int  count;
  int  capacity;
  pdf_colorspace *colorspaces;
} cspc_cache = {
  0, 0, NULL
};

int
pdf_colorspace_findresource (const char *ident,
			     int type, const void *cdata)
{
  pdf_colorspace *colorspace;
  int  cspc_id, cmp = -1;

  for (cspc_id = 0;
       cmp && cspc_id < cspc_cache.count; cspc_id++) {
    colorspace = &cspc_cache.colorspaces[cspc_id];
    if (colorspace->subtype != type)
      continue;

    switch (colorspace->subtype) {
    case PDF_COLORSPACE_TYPE_ICCBASED:
      cmp = compare_iccbased(ident, cdata,
			     colorspace->ident, colorspace->cdata);
      break;
    }
    if (!cmp)
      return cspc_id;
  }

  return -1; /* not found */
}

static void
pdf_init_colorspace_struct (pdf_colorspace *colorspace)
{
  ASSERT(colorspace);

  colorspace->ident     = NULL;
  colorspace->subtype   = PDF_COLORSPACE_TYPE_INVALID;

  colorspace->resource  = NULL;
  colorspace->reference = NULL;
  colorspace->cdata     = NULL;

  return;
}

static void
pdf_clean_colorspace_struct (pdf_colorspace *colorspace)
{
  ASSERT(colorspace);

  if (colorspace->ident)
    RELEASE(colorspace->ident);
  if (colorspace->resource)
    pdf_release_obj(colorspace->resource);
  if (colorspace->reference)
    pdf_release_obj(colorspace->reference);
  colorspace->resource  = NULL;
  colorspace->reference = NULL;

  if (colorspace->cdata) {
    switch (colorspace->subtype) {
    case PDF_COLORSPACE_TYPE_ICCBASED:
      release_iccbased_cdata(colorspace->cdata);
      break;
    }
  }
  colorspace->cdata     = NULL;
  colorspace->subtype   = PDF_COLORSPACE_TYPE_INVALID;

  return;
}

static void
pdf_flush_colorspace (pdf_colorspace *colorspace)
{
  ASSERT(colorspace);

  if (colorspace->resource)
    pdf_release_obj(colorspace->resource);
  if (colorspace->reference)
    pdf_release_obj(colorspace->reference);

  colorspace->resource  = NULL;
  colorspace->reference = NULL;
}

int
pdf_colorspace_defineresource (const char *ident,
			       int subtype, void *cdata, pdf_obj *resource)
{
  int  cspc_id;
  pdf_colorspace *colorspace;

  if (cspc_cache.count >= cspc_cache.capacity) {
    cspc_cache.capacity   += 16;
    cspc_cache.colorspaces = RENEW(cspc_cache.colorspaces,
				   cspc_cache.capacity, pdf_colorspace);
  }
  cspc_id    = cspc_cache.count;
  colorspace = &cspc_cache.colorspaces[cspc_id];

  pdf_init_colorspace_struct(colorspace);
  if (ident) {
    colorspace->ident = NEW(strlen(ident) + 1, char);
    strcpy(colorspace->ident, ident);
  }
  colorspace->subtype  = subtype;
  colorspace->cdata    = cdata;
  colorspace->resource = resource;

  if (verbose) {
    MESG("(ColorSpace:%s", ident);
    if (verbose > 1) {
      switch (subtype) {
      case PDF_COLORSPACE_TYPE_ICCBASED:
	MESG("[ICCBased]");
	break;
      case PDF_COLORSPACE_TYPE_CALRGB:
	MESG("[CalRGB]");
	break;
      case PDF_COLORSPACE_TYPE_CALGRAY:
	MESG("[CalGray]");
	break;
      }
    }
    MESG(")");
  }

  cspc_cache.count++;

  return cspc_id;
}

pdf_obj *
pdf_get_colorspace_reference (int cspc_id)
{
  pdf_colorspace *colorspace;

  colorspace = &cspc_cache.colorspaces[cspc_id];
  if (!colorspace->reference) {
    colorspace->reference = pdf_ref_obj(colorspace->resource);
    pdf_release_obj(colorspace->resource); /* .... */
    colorspace->resource = NULL;
  }

  return pdf_link_obj(colorspace->reference);
}

int
pdf_get_colorspace_num_components (int cspc_id)
{
  pdf_colorspace *colorspace;
  int  num_components;

  colorspace = &cspc_cache.colorspaces[cspc_id];

  switch (colorspace->subtype) {
  case PDF_COLORSPACE_TYPE_ICCBASED:
    num_components = get_num_components_iccbased(colorspace->cdata);
    break;
  case PDF_COLORSPACE_TYPE_DEVICEGRAY:
    num_components = 1;
    break;
  case PDF_COLORSPACE_TYPE_DEVICERGB:
    num_components = 3;
    break;
  case PDF_COLORSPACE_TYPE_DEVICECMYK:
    num_components = 4;
    break;
  case PDF_COLORSPACE_TYPE_CALRGB:
    num_components = 3;
    break;
  case PDF_COLORSPACE_TYPE_CALGRAY:
    num_components = 1;
    break;
  default:
    num_components = 0;
    break;
  }

  return num_components;
}

int
pdf_get_colorspace_subtype (int cspc_id)
{
  pdf_colorspace *colorspace;

  colorspace = &cspc_cache.colorspaces[cspc_id];

  return colorspace->subtype;
}

void
pdf_init_colors (void)
{
  cspc_cache.count    = 0;
  cspc_cache.capacity = 0;
  cspc_cache.colorspaces = NULL;
}

void
pdf_close_colors (void)
{
  int  i;

  for (i = 0; i < cspc_cache.count; i++) {
    pdf_colorspace *colorspace;

    colorspace = &cspc_cache.colorspaces[i];
    pdf_flush_colorspace(colorspace);
    pdf_clean_colorspace_struct(colorspace);
  }
  RELEASE(cspc_cache.colorspaces);
  cspc_cache.colorspaces = NULL;
  cspc_cache.count = cspc_cache.capacity = 0;

}

#define PDF_COLORSPACE_FAMILY_DEVICE   0
#define PDF_COLORSPACE_FAMILY_CIEBASED 1
#define PDF_COLORSPACE_FAMILY_SPECIAL  2
