/* Copyright (C) 1996, 1997 Aladdin Enterprises.  All rights reserved.
  
  This file is part of Aladdin Ghostscript.
  
  Aladdin 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 Ghostscript Free Public
  License (the "License") for full details.
  
  Every copy of Aladdin 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 Aladdin 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.
*/

/* gdevpdfi.c */
/* Image handling for PDF-writing driver */
#include "math_.h"
#include "memory_.h"
#include "gx.h"
#include "gserrors.h"
#include "gsflip.h"
#include "gdevpdfx.h"
#include "gxcspace.h"
#include "gscolor2.h"		/* for gscie.h */
#include "gscie.h"		/* requires gscspace.h */
#include "gxistate.h"
#include "strimpl.h"
#include "sa85x.h"
#include "scfx.h"
#include "srlx.h"

/* We need color space types for constructing temporary color spaces. */
extern const gs_color_space_type
  gs_color_space_type_DeviceGray,
  gs_color_space_type_DeviceRGB,
  gs_color_space_type_DeviceCMYK,
  gs_color_space_type_Indexed;

/* ---------------- Utilities ---------------- */

/* ------ Binary data ------ */

/* Define the structure for the filters for writing binary data. */
typedef struct pdf_binary_writer_s {
	stream *strm;
	stream es;		/* no state for A85E */
	byte encode_buf[256];
	stream cs;		/* client provides (initialized) state */
	byte compress_buf[256];
} pdf_binary_writer;

private const stream_procs filter_write_procs =
{	s_std_noavailable, s_std_noseek, s_std_write_reset,
	s_std_write_flush, s_filter_close
};

/* Begin writing binary data. */
/* If css is not NULL, it is the compressor stream state. */
private int
pdf_begin_binary(gx_device_pdf *pdev, pdf_binary_writer *pbw,
  stream_state *css)
{	stream *s = pdev->strm;

	/* If not binary, set up the encoding stream. */
	if ( !pdev->binary_ok )
	  { stream *es = &pbw->es;
	    s_std_init(es, pbw->encode_buf, sizeof(pbw->encode_buf),
		       &filter_write_procs, s_mode_write);
	    es->template = &s_A85E_template;
	    es->procs.process = es->template->process;
	    es->strm = s;
	    s = es;
	  }
	/* If compressing, set up the compression stream. */
	if ( css )
	  { stream *cs = (stream *)&pbw->cs;
	    const stream_template *template = css->template;
	    s_std_init(cs, pbw->compress_buf, sizeof(pbw->compress_buf),
		       &filter_write_procs, s_mode_write);
	    css->memory = pdev->pdf_memory;
	    cs->state = css;
	    cs->procs.process = template->process;
	    if ( template->init )
	      (*template->init)(css);
	    cs->strm = s;
	    s = cs;
	  }
	pbw->strm = s;
	return 0;
}

/* Finish writing binary data. */
private int
pdf_end_binary(gx_device_pdf *pdev, pdf_binary_writer *pbw)
{	stream *s = pbw->strm;

	/* Close the filters in reverse order. */
	/* Stop before we try to close the file stream. */
	while ( s != pdev->strm )
	  { stream *next = s->strm;
	    /* We have to open-code sclose, because we want to release */
	    /* the stream state but not try to free it. */
	    stream_state *st = s->state;
	    stream_proc_release((*release)) = st->template->release;
	    (*s->procs.close)(s);
	    if ( release != 0 )
	      (*release)(st);
	    s = next;
	  }
	sflush(s);	/* flush the file stream buffer */
	return 0;
}

/* ------ Images ------ */

/* 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 ia (gx_cie_cache_size / 3)
#define ib (gx_cie_cache_size * 2 / 3)
#define iv(i) ((i) / (double)(gx_cie_cache_size - 1))
#define a iv(ia)
#define b iv(ib)

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(a);
	if ( fabs(vb - k * pow(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)
{	double k, va, vb;

	if ( fabs(pc->floats.values[0]) >= 0.001 )
	  return false;
	k = pc->floats.values[gx_cie_cache_size - 1];
	va = pc->floats.values[ia];
	vb = pc->floats.values[ib];
	return cie_values_are_exponential(va, vb, k, pexpt);
}
#define cie_scalar3_cache_is_exponential(pca, expts)\
  (cie_scalar_cache_is_exponential(&(pca)[0], &(expts)[0]) &&\
   cie_scalar_cache_is_exponential(&(pca)[1], &(expts)[1]) &&\
   cie_scalar_cache_is_exponential(&(pca)[2], &(expts)[2]))

private bool
cie_vector_cache_is_exponential(const gx_cie_vector_cache *pc, float *pexpt)
{	double k, va, vb;

	if ( fabs(pc->vecs.values[0].u) >= 0.001 )
	  return false;
	k = pc->vecs.values[gx_cie_cache_size - 1].u;
	va = pc->vecs.values[ia].u;
	vb = pc->vecs.values[ib].u;
	return cie_values_are_exponential(va, vb, k, pexpt);
}
#define cie_vector3_cache_is_exponential(pca, expts)\
  (cie_vector_cache_is_exponential(&(pca)[0], &(expts)[0]) &&\
   cie_vector_cache_is_exponential(&(pca)[1], &(expts)[1]) &&\
   cie_vector_cache_is_exponential(&(pca)[2], &(expts)[2]))

#undef ia
#undef ib
#undef iv
#undef a
#undef b

/* Define the long and short versions of the keys in an image dictionary, */
/* and other strings for images. */
typedef struct pdf_image_names_s {
  const char *ASCII85Decode;
  const char *BitsPerComponent;
  const char *CalCMYK;
  const char *CalGray;
  const char *CalRGB;
  const char *CCITTFaxDecode;
  const char *ColorSpace;
  const char *Decode;
  const char *DecodeParms;
  const char *DeviceCMYK;
  const char *DeviceGray;
  const char *DeviceRGB;
  const char *Filter;
  const char *Height;
  const char *ImageMask;
  const char *Indexed;
  const char *Interpolate;
  const char *Width;
} pdf_image_names;
private const pdf_image_names image_names_full = {
  "/ASCII85Decode", "/BitsPerComponent",
  "/CalCMYK", "/CalGray", "/CalRGB", "/CCITTFaxDecode", "/ColorSpace",
  "/Decode", "/DecodeParms", "/DeviceCMYK", "/DeviceGray", "/DeviceRGB",
  "/Filter", "/Height", "/ImageMask", "/Indexed", "/Interpolate", "/Width",
};
private const pdf_image_names image_names_short = {
  "/A85", "/BPC",
	/* We need CalRGB to work around a bug in some Adobe products. */
  "/CC", "/CG", /*"/CR"*/ "/CalRGB", "/CCF", "/CS",
  "/D", "/DP", "/CMYK", "/G", "/RGB",
  "/F", "/H", "/IM", "/I", "/I", "/W",
};

/* Write out image parameters for either an in-line image or image resource. */
/* decode_parms, if supplied, must start with /, [, or <<. */
private int
pdf_write_image_params(gx_device_pdf *pdev, const gs_image_t *pim,
  const char *filter_name, const char *decode_parms,
  const pdf_image_names *pin)
{	stream *s = pdev->strm;
	const gs_color_space *pcs = pim->ColorSpace;
	const char *cs_name;
	int num_components;
	float indexed_decode[2];
	const float *default_decode = NULL;
	
	if ( pim->ImageMask )
	  { pprints1(s, "%s true", pin->ImageMask);
	    pdev->procsets |= ImageB;
	    num_components = 1;
	  }
	else
	  { const gs_color_space *pbcs = pcs;
	    const gs_indexed_params *pip = 0;
	    const gs_cie_common *pciec;

	    pputs(s, pin->ColorSpace);
csw:	    switch ( gs_color_space_get_index(pbcs) )
	      {
	      case gs_color_space_index_DeviceGray:
		pdev->procsets |= ImageB;
		cs_name = pin->DeviceGray;
		break;
	      case gs_color_space_index_DeviceRGB:
		pdev->procsets |= ImageC;
		cs_name = pin->DeviceRGB;
		break;
	      case gs_color_space_index_DeviceCMYK:
		pdev->procsets |= ImageC;
		cs_name = pin->DeviceCMYK;
		break;
	      case gs_color_space_index_CIEA:
		pdev->procsets |= ImageB;
		pprints1(s, "[%s<<", pin->CalGray);
		{ const gs_cie_a *pcie = pbcs->params.a;
		  float expts[3];

		  if ( cie_cache3_is_identity(pcie->common.caches.DecodeLMN) )
		    cie_vector_cache_is_exponential(&pcie->caches.DecodeA, &expts[0]);
		  else
		    discard(cie_scalar3_cache_is_exponential(pcie->common.caches.DecodeLMN, expts));
		  if ( expts[0] != 1 )
		    pprintg1(s, "/Gamma %g", expts[0]);
		  pciec = (const gs_cie_common *)pcie;
		}
cal:		pprintg3(s, "/WhitePoint[%g %g %g]",
			 pciec->points.WhitePoint.u,
			 pciec->points.WhitePoint.v,
			 pciec->points.WhitePoint.w);
	        if ( pciec->points.BlackPoint.u != 0 ||
		     pciec->points.BlackPoint.v != 0 ||
		     pciec->points.BlackPoint.w != 0
		   )
		  pprintg3(s, "/BlackPoint[%g %g %g]",
			   pciec->points.BlackPoint.u,
			   pciec->points.BlackPoint.v,
			   pciec->points.BlackPoint.w);
	        pputs(s, ">>]");
		cs_name = 0;
		break;
	      case gs_color_space_index_CIEABC:
		pdev->procsets |= ImageC;
		pprints1(s, "[%s<<", pin->CalRGB);
		{ const gs_cie_abc *pcie = pbcs->params.abc;
		  const gs_matrix3 *pmat;
		  float expts[3];

		  if ( pcie->common.MatrixLMN.is_identity &&
		       cie_cache3_is_identity(pcie->common.caches.DecodeLMN)
		     ) {
		    discard(cie_vector3_cache_is_exponential(pcie->caches.DecodeABC, expts));
		    pmat = &pcie->MatrixABC;
		  } else {
		    discard(cie_scalar3_cache_is_exponential(pcie->common.caches.DecodeLMN, expts));
		    pmat = &pcie->common.MatrixLMN;
		  }
		  if ( expts[0] != 1 || expts[1] != 1 || expts[2] != 1 )
		    pprintg3(s, "/Gamma[%g %g %g]", expts[0], expts[1],
			     expts[2]);
		  if ( !pmat->is_identity ) {
		    pprintg3(s, "/Matrix[%g %g %g",
			     pmat->cu.u, pmat->cu.v, pmat->cu.w);
		    pprintg6(s, " %g %g %g %g %g %g]",
			     pmat->cv.u, pmat->cv.v, pmat->cv.w,
			     pmat->cw.u, pmat->cw.v, pmat->cw.w);
		  }
		  pciec = (const gs_cie_common *)pcie;
		}
		goto cal;
	      case gs_color_space_index_Indexed:
		pdev->procsets |= ImageI;
		pprints1(s, "[%s", pin->Indexed);
		pip = &pcs->params.indexed;
		pbcs = (const gs_color_space *)&pip->base_space;
		indexed_decode[0] = 0;
		indexed_decode[1] = (1 << pim->BitsPerComponent) - 1;
		default_decode = indexed_decode;
		goto csw;
	      default:		/* shouldn't happen */
		return_error(gs_error_rangecheck);
	      }
	    if ( cs_name )
	      pprints1(s, " %s", cs_name);
	    num_components = gs_color_space_num_components(pbcs);
	    if ( pip )
	      { register const char _ds *hex_digits = "0123456789abcdef";
		int i;

		pprintd1(s, " %d\n<", pip->hival);
		for ( i = 0; i < (pip->hival + 1) * num_components; ++i )
		  { byte b = pip->lookup.table.data[i];
		    pputc(s, hex_digits[b >> 4]);
		    pputc(s, hex_digits[b & 0xf]);
		  }
		pputs(s, ">\n]");
		num_components = 1;
	      }
	  }
/* Some compilers try to substitute macro args in string literals! */
#define pprintsd(strm, str, v)\
  (pputs(strm, str), pprintd1(strm, " %d", v))
	pprintsd(s, pin->Width, pim->Width);
	pprintsd(s, pin->Height, pim->Height);
	pprintsd(s, pin->BitsPerComponent, pim->BitsPerComponent);
#undef pprintsd
	{ int i;
	  for ( i = 0; i < num_components * 2; ++i )
	    if ( pim->Decode[i] !=
		 (default_decode ? default_decode[i] : i & 1)
	       )
	      break;
	  if ( i < num_components * 2 )
	    { char sepr = '[';
	      pputs(s, pin->Decode);
	      for ( i = 0; i < num_components * 2; sepr = ' ', ++i )
		{ pputc(s, sepr);
		  pprintg1(s, "%g", pim->Decode[i]);
		}
	      pputc(s, ']');
	    }
	}
	if ( pim->Interpolate )
	  pprints1(s, "%s true", pin->Interpolate);
	if ( filter_name )
	  { if ( pdev->binary_ok )
	      pprints2(s, "%s%s", pin->Filter, filter_name);
	    else
	      pprints3(s, "%s[%s%s]", pin->Filter, pin->ASCII85Decode,
		       filter_name);
	    if ( decode_parms )
	      pprints2(s,
		       (pdev->binary_ok ? "%s%s" : "%s[null%s]"),
		       pin->DecodeParms, decode_parms);
	  }
	else if ( !pdev->binary_ok )
	  pprints2(s, "%s%s", pin->Filter, pin->ASCII85Decode);
	return 0;
}

/* Fill in the image parameters for a device space bitmap. */
/* PDF images are always specified top-to-bottom. */
private void
pdf_make_bitmap_matrix(gs_matrix *pmat, int x, int y, int w, int h)
{	pmat->xx = w;
	pmat->xy = 0;
	pmat->yx = 0;
	pmat->yy = -h;
	pmat->tx = x;
	pmat->ty = y + h;
}
private void
pdf_make_bitmap_image(gs_image_t *pim, int x, int y, int w, int h)
{	pim->Width = w;
	pim->Height = h;
	pdf_make_bitmap_matrix(&pim->ImageMatrix, x, y, w, h);
}

/* Put out the gsave and matrix for an image. */
private void
pdf_put_image_matrix(gx_device_pdf *pdev, const gs_matrix *pmat)
{	pdf_put_matrix(pdev, "q\n", pmat, "cm\n");
}

/* ------ Image writing ------ */

/* Define the structure for writing an image. */
typedef struct pdf_image_writer_s {
  pdf_binary_writer binary;
  const pdf_image_names *pin;
  const char *begin_data;
  pdf_resource *pres;		/* XObject resource iff not in-line */
  long length_id;		/* id of length object (forward reference) */
  long start_pos;		/* starting file position of data */
} pdf_image_writer;

/* Begin writing an image. */
private int
pdf_begin_write_image(gx_device_pdf *pdev, pdf_image_writer *piw, bool in_line)
{	if ( in_line )
	  { stream *s = pdev->strm;

	    piw->pres = 0;
	    pputs(s, "BI\n");
	    piw->pin = &image_names_short;
	    piw->begin_data = (pdev->binary_ok ? "ID " : "ID\n");
	  }
	else
	  { int code = pdf_begin_resource(pdev, resourceXObject, gs_no_id,
					  &piw->pres);
	    stream *s = pdev->strm;

	    if ( code < 0 )
	      return code;
	    piw->length_id = pdf_obj_ref(pdev);
	    pprintld1(s, " /Subtype /Image /Length %ld 0 R\n",
		      piw->length_id);
	    piw->pin = &image_names_full;
	    piw->begin_data = ">>\nstream\n";
	  }
	return 0;
}

/* Begin writing the image data. */
private int
pdf_begin_image_data(gx_device_pdf *pdev, pdf_image_writer *piw,
  const gs_image_t *pim, const char *filter_name, const char *decode_parms)
{	stream *s = pdev->strm;
	int code = pdf_write_image_params(pdev, pim, filter_name, decode_parms,
					  piw->pin);

	if ( code < 0 )
	  return code;
	pprints1(s, "\n%s", piw->begin_data);
	piw->start_pos = pdf_stell(pdev);
	return 0;
}

/* Finish writing an image. */
/* Return 0 if resource, 1 if in-line, or an error code. */
private int
pdf_end_write_image(gx_device_pdf *pdev, pdf_image_writer *piw)
{	stream *s = pdev->strm;

	if ( piw->pres )		/* image resource */
	  { long length;

	    pputs(s, "\n");
	    length = pdf_stell(pdev) - piw->start_pos;
	    pputs(s, "endstream\n");
	    pdf_end_resource(pdev);
	    pdf_open_separate(pdev, piw->length_id);
	    s = pdev->strm;
	    pprintld1(s, "%ld\n", length);
	    pdf_end_separate(pdev);
	    return 0;
	  }
	else		/* in-line image */
	  { pputs(s, "\nEI\nQ\n");
	    return 1;
	  }
}

/* Put out a reference to an image resource. */
private int
pdf_do_image(gx_device_pdf *pdev, const pdf_resource *pres,
  const gs_matrix *pimat)
{	int code = pdf_open_contents(pdev, pdf_in_stream);

	if ( code < 0 )
	  return code;
	if ( pimat )
	  pdf_put_image_matrix(pdev, pimat);
	pprintld1(pdev->strm, "/R%ld Do\nQ\n", pres->id);
	return 0;
}

/* ---------------- Driver procedures ---------------- */

/* ------ Low-level calls ------ */

/* Copy a monochrome bitmap or mask. */
int
gdev_pdf_copy_mono(gx_device *dev,
  const byte *base, int sourcex, int raster, gx_bitmap_id id,
  int x, int y, int w, int h, gx_color_index zero, gx_color_index one)
{	gx_device_pdf *pdev = (gx_device_pdf *)dev;
	int code;
	gs_color_space cs;
	byte palette[6];
	gs_image_t image;
	int yi;
	pdf_image_writer writer;
	pdf_stream_position ipos;
	pdf_resource *pres = 0;
	byte invert = 0;

	if ( w <= 0 || h <= 0 )
	  return 0;
	/* Make sure we aren't being clipped. */
	if ( pdf_must_put_clip_path(pdev, NULL) )
	  { code = pdf_open_page(pdev, pdf_in_stream);
	    if ( code < 0 )
	      return code;
	    pdf_put_clip_path(pdev, NULL);
	  }
	/* We have 3 cases: mask, inverse mask, and solid. */
	if ( zero == gx_no_color_index )
	  { if ( one == gx_no_color_index )
	      return 0;
	    /* If a mask has an id, assume it's a character. */
	    if ( id != gx_no_bitmap_id && sourcex == 0 )
	      { pdf_set_color(pdev, one, &pdev->fill_color, "rg");
		pres = pdf_find_resource_by_gs_id(pdev, resourceCharProc, id);
		if ( pres == 0 )
		  { /* Define the character in an embedded font. */
		    pdf_char_proc *pcp;
		    int y_offset;
		    int max_y_offset =
		      (pdev->open_font == 0 ? 0 :
		       pdev->open_font->max_y_offset);

		    gs_image_t_init_mask(&image, false);
		    invert = 0xff;
		    pdf_make_bitmap_image(&image, x, y, w, h);
		    y_offset =
		      image.ImageMatrix.ty - (int)(pdev->text.current.y + 0.5);
		    if ( x < pdev->text.current.x ||
			 y_offset < -max_y_offset || y_offset > max_y_offset
		       )
		      y_offset = 0;
		    /*
		     * The Y axis of the text matrix is inverted,
		     * so we need to negate the Y offset appropriately.
		     */
		    code = pdf_begin_char_proc(pdev, w, h, 0, y_offset, id,
					       &pcp, &ipos);
		    if ( code < 0 )
		      return code;
		    y_offset = -y_offset;
		    pprintd3(pdev->strm, "0 0 0 %d %d %d d1\n", y_offset,
			     w, h + y_offset);
		    pprintd3(pdev->strm, "%d 0 0 %d 0 %d cm\n", w, h,
			     y_offset);
		    code = pdf_begin_write_image(pdev, &writer, true);
		    if ( code < 0 )
		      return code;
		    pcp->rid = id;
		    pres = (pdf_resource *)pcp;
		    goto wr;
		  }
		pdf_make_bitmap_matrix(&image.ImageMatrix, x, y, w, h);
		goto rx;
	      }
	    pdf_set_color(pdev, one, &pdev->fill_color, "rg");
	    gs_image_t_init_mask(&image, false);
	    invert = 0xff;
	  }
	else if ( one == gx_no_color_index )
	  { gs_image_t_init_mask(&image, false);
	    pdf_set_color(pdev, zero, &pdev->fill_color, "rg");
	  }
	else if ( zero == 0 && one == 0xffffff )
	  { gs_image_t_init_gray(&image);
	  }
	else if ( zero == 0xffffff && one == 0 )
	  { gs_image_t_init_gray(&image);
	    invert = 0xff;
	  }
	else
	  { gs_image_t_init_color(&image);
	    cs.type = &gs_color_space_type_Indexed;
	    cs.params.indexed.hival = 1;
	    palette[0] = (byte)(zero >> 16);
	    palette[1] = (byte)(zero >> 8);
	    palette[2] = (byte)(zero);
	    palette[3] = (byte)(one >> 16);
	    palette[4] = (byte)(one >> 8);
	    palette[5] = (byte)(one);
	    cs.params.indexed.lookup.table.data = palette;
	    cs.params.indexed.lookup.table.size = 6;
	    cs.params.indexed.use_proc = false;
	    image.BitsPerComponent = 1;
	    image.ColorSpace = &cs;
	  }
	pdf_make_bitmap_image(&image, x, y, w, h);
	{ ulong nbytes = (ulong)((w + 7) >> 3) * h;
	  bool in_line = nbytes <= 4000;

	  if ( in_line )
	    pdf_put_image_matrix(pdev, &image.ImageMatrix);
	  code = pdf_open_page(pdev, pdf_in_stream);
	  if ( code < 0 )
	    return code;
	  code = pdf_begin_write_image(pdev, &writer, in_line);
	  if ( code < 0 )
	    return code;
	}
wr:	/*
	 * There are 3 different cases at this point:
	 *	- Writing an in-line image (pres == 0, writer.pres == 0);
	 *	- Writing an XObject image (pres == 0, writer.pres != 0);
	 *	- Writing the image for a CharProc (pres != 0).
	 * We handle them with in-line code followed by a switch,
	 * rather than making the shared code into a procedure,
	 * simply because there would be an awful lot of parameters
	 * that would need to be passed.
	 */
	{ char decode_parms[80];

	  sprintf(decode_parms,
		  "<</K -1/Columns %d%s>>",
		  w, (invert ? "" : "/BlackIs1 true"));
	  pdf_begin_image_data(pdev, &writer, &image,
			       writer.pin->CCITTFaxDecode, decode_parms);
	}
	{ stream_CFE_state csstate;
	  csstate.template = &s_CFE_template;
	  (*csstate.template->set_defaults)((stream_state *)&csstate);
	  csstate.K = -1;
	  csstate.Columns = w;
	  csstate.Rows = h;
	  csstate.BlackIs1 = invert == 0;
	  pdf_begin_binary(pdev, &writer.binary, (stream_state *)&csstate);
	  for ( yi = 0; yi < h; ++yi )
	    { const byte *data = base + yi * raster + (sourcex >> 3);
	      int sbit = sourcex & 7;

	      if ( sbit == 0 )
		{ int nbytes = (w + 7) >> 3;
		  int i;

		  for ( i = 0; i < nbytes; ++data, ++i )
		    sputc(writer.binary.strm, *data ^ invert);
		}
	      else
		{ int wleft = w;
		  int rbit = 8 - sbit;

		  for ( ; wleft + sbit > 8; ++data, wleft -= 8 )
		    sputc(writer.binary.strm,
			  ((*data << sbit) + (data[1] >> rbit)) ^ invert);
		  if ( wleft > 0 )
		    sputc(writer.binary.strm,
			  ((*data << sbit) ^ invert) &
			    (byte)(0xff00 >> wleft));
		}
	    }
	  pdf_end_binary(pdev, &writer.binary);
	}
	if ( !pres )
	  { switch ( pdf_end_write_image(pdev, &writer) )
	      {
	      default:		/* error */
		return code;
	      case 1:
		return 0;
	      case 0:
		return pdf_do_image(pdev, writer.pres, &image.ImageMatrix);
	      }
	  }
	pputs(pdev->strm, "\nEI\n");
	code = pdf_end_char_proc(pdev, &ipos);
	if ( code < 0 )
	  return code;
rx:	{ gs_matrix imat;

	  imat = image.ImageMatrix;
	  imat.xx /= w;
	  imat.xy /= h;
	  imat.yx /= w;
	  imat.yy /= h;
	  return pdf_do_char_image(pdev, (const pdf_char_proc *)pres, &imat);
	}
}

/* Copy a color bitmap. */
int
gdev_pdf_copy_color(gx_device *dev,
  const byte *base, int sourcex, int raster, gx_bitmap_id id,
  int x, int y, int w, int h)
{	gx_device_pdf *pdev = (gx_device_pdf *)dev;
	int depth = dev->color_info.depth;
	int bytes_per_pixel = depth >> 3;
	int code = pdf_open_page(pdev, pdf_in_stream);
	int yi;
	gs_image_t image;
	gs_color_space cs;
	pdf_image_writer writer;
	ulong nbytes;

	if ( code < 0 )
	  return code;
	if ( w <= 0 || h <= 0 )
	  return 0;
	/* Make sure we aren't being clipped. */
	pdf_put_clip_path(pdev, NULL);
	gs_image_t_init_color(&image);
	pdf_make_bitmap_image(&image, x, y, w, h);
	image.BitsPerComponent = 8;
	cs.type = (bytes_per_pixel == 3 ? &gs_color_space_type_DeviceRGB :
		   bytes_per_pixel == 4 ? &gs_color_space_type_DeviceCMYK :
		   &gs_color_space_type_DeviceGray);
	image.ColorSpace = &cs;
	nbytes = (ulong)w * bytes_per_pixel * h;
	pdf_put_image_matrix(pdev, &image.ImageMatrix);
	code = pdf_begin_write_image(pdev, &writer, nbytes <= 4000);
	if ( code < 0 )
	  return code;
	code = pdf_begin_image_data(pdev, &writer, &image, NULL, NULL);
	if ( code < 0 )
	  return code;
	pdf_begin_binary(pdev, &writer.binary, NULL);
	for ( yi = 0; yi < h; ++yi )
	  { uint ignore;
	    sputs(writer.binary.strm,
		  base + sourcex * bytes_per_pixel + yi * raster,
		  w * bytes_per_pixel, &ignore);
	  }
	pdf_end_binary(pdev, &writer.binary);
	code = pdf_end_write_image(pdev, &writer);
	switch ( code )
	  {
	  default: return code;		/* error */
	  case 1: return 0;
	  case 0: ;
	  }
	return pdf_do_image(pdev, writer.pres, &image.ImageMatrix);
}

/* Fill a mask. */
int
gdev_pdf_fill_mask(gx_device *dev,
  const byte *data, int data_x, int raster, gx_bitmap_id id,
  int x, int y, int width, int height,
  const gx_drawing_color *pdcolor, int depth,
  gs_logical_operation_t lop, const gx_clip_path *pcpath)
{	gx_device_pdf *pdev = (gx_device_pdf *)dev;
	int code;

	if ( width <= 0 || height <= 0 )
	  return 0;
	if ( depth > 1 || !gx_dc_is_pure(pdcolor) != 0 )
	  return gx_default_fill_mask(dev, data, data_x, raster, id,
				      x, y, width, height, pdcolor, depth, lop,
				      pcpath);
	if ( pdf_must_put_clip_path(pdev, pcpath) )
	  { code = pdf_open_page(pdev, pdf_in_stream);
	    if ( code < 0 )
	      return code;
	    pdf_put_clip_path(pdev, pcpath);
	  }
	return gdev_pdf_copy_mono(dev, data, data_x, raster, id,
				  x, y, width, height,
				  gx_no_color_index,
				  gx_dc_pure_color(pdcolor));
}

/* ------ High-level calls ------ */

/* Define the structure for keeping track of progress through an image. */
typedef struct pdf_image_enum_s {
	gs_memory_t *memory;
	void *default_info;
	int width;
	int num_planes;
	int bits_per_pixel;	/* bits per pixel (per plane) */
	int rows_left;
	pdf_image_writer writer;
} pdf_image_enum;
/* We can disregard the pointers in the writer by allocating */
/* the image enumerator as immovable.  This is a hack, of course. */
gs_private_st_ptrs1(st_pdf_image_enum, pdf_image_enum, "pdf_image_enum",
  pdf_image_enum_enum_ptrs, pdf_image_enum_reloc_ptrs, default_info);

/* Test whether we can handle a given color space. */
private bool
pdf_can_handle_color_space(const gs_color_space *pcs)
{	gs_color_space_index index = gs_color_space_get_index(pcs);

	if ( index == gs_color_space_index_Indexed )
	  { if ( pcs->params.indexed.use_proc )
	      return false;
	    index =
	      gs_color_space_get_index(gs_color_space_indexed_base_space(pcs));
	  }
	switch ( index ) {
	  case gs_color_space_index_DeviceGray:
	  case gs_color_space_index_DeviceRGB:
	  case gs_color_space_index_DeviceCMYK:
	    return true;
	  case gs_color_space_index_Separation:
	  case gs_color_space_index_Pattern:
	    return false;	/****** OK in PDF 1.2 ******/
	  case gs_color_space_index_CIEA:
	    { /* Check that we can represent this as a CalGray space. */
	      const gs_cie_a *pcie = pcs->params.a;
	      float expts[3];

	      return (pcie->MatrixA.u == 1 && pcie->MatrixA.v == 1 &&
		      pcie->MatrixA.w == 1 &&
		      pcie->common.MatrixLMN.is_identity &&
		      ((cie_cache_is_identity(&pcie->caches.DecodeA) &&
			cie_scalar3_cache_is_exponential(pcie->common.caches.DecodeLMN, expts) &&
			expts[1] == expts[0] && expts[2] == expts[0]) ||
		       (cie_vector_cache_is_exponential(&pcie->caches.DecodeA, &expts[0]) &&
			cie_cache3_is_identity(pcie->common.caches.DecodeLMN)))
		     );
	    }
 	  case gs_color_space_index_CIEABC:
	    { /* Check that we can represent this as a CalRGB space. */
	      const gs_cie_abc *pcie = pcs->params.abc;
	      float expts[3];

	      return ((cie_cache3_is_identity(pcie->caches.DecodeABC) &&
		       pcie->MatrixABC.is_identity &&
		       cie_scalar3_cache_is_exponential(pcie->common.caches.DecodeLMN, expts)) ||
		      (cie_vector3_cache_is_exponential(pcie->caches.DecodeABC, expts) &&
		       cie_cache3_is_identity(pcie->common.caches.DecodeLMN) &&
		       pcie->common.MatrixLMN.is_identity)
		     );
	    }
	  default:		/* CIEBasedDEF[G], LL3 spaces */
	    return false;
	}
}

/* Start processing an image. */
int
gdev_pdf_begin_image(gx_device *dev,
  const gs_imager_state *pis, const gs_image_t *pim,
  gs_image_format_t format, const gs_int_rect *prect,
  const gx_drawing_color *pdcolor, const gx_clip_path *pcpath,
  gs_memory_t *mem, void **pinfo)
{	gx_device_pdf *pdev = (gx_device_pdf *)dev;
	int code = pdf_open_page(pdev, pdf_in_stream);
	pdf_image_enum *pie;
	const gs_color_space *pcs = pim->ColorSpace;
	int num_components =
	  (pim->ImageMask ? 1 : gs_color_space_num_components(pcs));
	gs_int_rect rect;
	ulong nbytes;

	if ( code < 0 )
	  return code;
	if ( prect )
	  rect = *prect;
	else
	  { rect.p.x = rect.p.y = 0;
	    rect.q.x = pim->Width, rect.q.y = pim->Height;
	  }
	/* See above for why we allocate the enumerator as immovable. */
	pie = gs_alloc_struct_immovable(mem, pdf_image_enum,
					&st_pdf_image_enum,
					"pdf_begin_image");
	if ( pie == 0 )
	  return_error(gs_error_VMerror);
	pie->memory = mem;
	*pinfo = pie;
	if ( (pim->ImageMask ?
	      (!gx_dc_is_pure(pdcolor) || pim->CombineWithColor) :
	      !pdf_can_handle_color_space(pim->ColorSpace)) ||
	     prect
	   )
	    { int code = gx_default_begin_image(dev, pis, pim, format, prect,
						pdcolor, pcpath, mem,
						&pie->default_info);
	      if ( code < 0 )
		gs_free_object(mem, pie, "pdf_begin_image");
	      return code;
	    }
	pie->default_info = 0;
	pie->width = rect.q.x - rect.p.x;
	switch ( format )
	  {
	  case gs_image_format_chunky:
	    pie->num_planes = 1; break;
	  case gs_image_format_component_planar:
	    pie->num_planes = num_components; break;
	  case gs_image_format_bit_planar:
	    pie->num_planes = num_components * pim->BitsPerComponent; break;
	  }
	pie->bits_per_pixel =
	  pim->BitsPerComponent * num_components / pie->num_planes;
	pie->rows_left = rect.q.y - rect.p.y;
	pdf_put_clip_path(pdev, pcpath);
	if ( pim->ImageMask )
	  pdf_set_color(pdev, gx_dc_pure_color(pdcolor), &pdev->fill_color,
			"rg");
	/****** DOESN'T DO COMPRESSION YET ******/
	{ gs_matrix mat;
	  gs_matrix bmat;
	  int code;

	  pdf_make_bitmap_matrix(&bmat, -rect.p.x, -rect.p.y,
				 pim->Width, pim->Height);
	  if ( (code = gs_matrix_invert(&pim->ImageMatrix, &mat)) < 0 ||
	       (code = gs_matrix_multiply(&bmat, &mat, &mat)) < 0 ||
	       (code = gs_matrix_multiply(&mat, &ctm_only(pis), &mat)) < 0
	     )
	    { gs_free_object(mem, pie, "pdf_begin_image");
	      return code;
	    }
	  pdf_put_image_matrix(pdev, &mat);
	}
	nbytes = (((ulong)pie->width * pie->bits_per_pixel + 7) >> 3) *
	  pie->rows_left;
	code = pdf_begin_write_image(pdev, &pie->writer, nbytes <= 4000);
	if ( code < 0 )
	  return code;
	code = pdf_begin_image_data(pdev, &pie->writer, pim, NULL, NULL);
	if ( code < 0 )
	  return code;
	pdf_begin_binary(pdev, &pie->writer.binary, NULL);
	return 0;
}

/* Process the next piece of an image. */
int
gdev_pdf_image_data(gx_device *dev,
  void *info, const byte **planes, int data_x, uint raster, int height)
{	pdf_image_enum *pie = info;
	int h = height;
	uint y_offset = 0;
	uint bcount;
	uint ignore;
	int nplanes = pie->num_planes;
#define row_bytes 180		/* must be 0 mod 3, 4, 6, 9 */
	byte row[row_bytes];

	if ( pie->default_info )
	  return gx_default_image_data(dev, pie->default_info, planes, data_x,
				       raster, height);
	if ( h > pie->rows_left )
	  h = pie->rows_left;
	pie->rows_left -= h;
	bcount = ((data_x + pie->width) * pie->bits_per_pixel + 7) >> 3;
	for ( ; h > 0; y_offset += raster, --h )
	  { if ( nplanes > 1 )
	      { /* Flip the data in blocks before writing. */
		uint offset = y_offset;
		uint count = bcount;

		while ( count )
		  { uint flip_count = min(count, row_bytes / nplanes);
		    image_flip_planes(row, planes, offset, flip_count, nplanes,
				      pie->bits_per_pixel);
		    sputs(pie->writer.binary.strm, row, flip_count * nplanes,
			  &ignore);
		    count -= flip_count;
		    offset += flip_count;
		  }
	      }
	    else
	      sputs(pie->writer.binary.strm, planes[0] + y_offset, bcount,
		    &ignore);
	  }
	return !pie->rows_left;
#undef row_bytes
}

/* Clean up by releasing the buffers. */
int
gdev_pdf_end_image(gx_device *dev, void *info, bool draw_last)
{	gx_device_pdf *pdev = (gx_device_pdf *)dev;
	pdf_image_enum *pie = info;
	int code;

	if ( pie->default_info )
	  code = gx_default_end_image(dev, pie->default_info, draw_last);
	else
	  { code = pdf_end_binary(pdev, &pie->writer.binary);
	    if ( code < 0 )
	      return code;
	    code = pdf_end_write_image(pdev, &pie->writer);
	    switch ( code )
	      {
	      default: return code;		/* error */
	      case 1: return 0;
	      case 0: ;
	      }
	    code = pdf_do_image(pdev, pie->writer.pres, NULL);
	  }
	gs_free_object(pie->memory, pie, "pdf_end_image");
	return code;
}
