/******************************************************************************
  File:     $Id: eprnrend.c,v 1.15 2001/08/01 05:12:56 Martin Rel $
  Contents: Colour rendering functionality for the ghostscript device 'eprn'
  Author:   Martin Lottermoser, Greifswaldstrasse 28, 38124 Braunschweig,
            Germany. E-mail: Martin.Lottermoser@t-online.de.

*******************************************************************************
*									      *
*	Copyright (C) 2000, 2001 by Martin Lottermoser			      *
*	All rights reserved						      *
*									      *
*******************************************************************************

  Preprocessor variables:

    EPRN_TRACE
        Define this to enable tracing. Only useful for development.

    EPRN_TRAILING_BIT_BUG_FIXED
        Define this to deactivate compensation for a bug in ghostscript which
        leads to the last pixel in an RGB line being black instead of white.
        This occurs at least in gs 6.01 and 6.50. The correction covers only
        the one-bit-per-colorant case and is equivalent to clipping the pixel.

*******************************************************************************

  The eprn device uses 'gx_color_index' values with varying interpretations,
  depending on the colour model and the rendering method, and stores them at
  different pixmaps depths, normally using the smallest depth which can
  accommodate all colorants at the same number of bits per colorant.

  To simplify matters, a field for the black component is always included, even
  for RGB and CMY, i.e., there are either 1 or 4 bit fields in a
  'gx_color_index' value. If there are 4, the interpretation is either YMCK or
  BGRK, looking from left to right (most to least significant). The width of
  the fields can be found in the 'bits_per_colorant' variable in the eprn part
  of the device instance.

  Within each colorant field, not all bits need be used. Except when using the
  *_max() colour mapping functions, the values returned by
  eprn_bits_for_levels() for the parameters 'black_levels' and
  'non_black_levels' determine the number of bits which are actually
  meaningful. Only the last (least significant) bits are used.

******************************************************************************/

/*****************************************************************************/

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE	500
#endif

/* Special Aladdin header, must be included before <sys/types.h> on some
   platforms (e.g., FreeBSD). */
#include "std.h"

/* Standard headers */
#include "assert_.h"
#include <stdlib.h>

/* Ghostscript headers */
#ifdef EPRN_TRACE
#include "gdebug.h"
#endif	/* EPRN_TRACE */

/* Special headers */
#include "gdeveprn.h"

/*****************************************************************************/

/* Macros for 'gx_color_index' values used mainly for non-monochrome modes and
   a pixmap depth of 4 */

/* Colorants bits, numbered from 0 on the right to 3 on the left */
#define COLORANT_0_BIT	1U
#define COLORANT_1_BIT	2U
#define COLORANT_2_BIT	4U
#define COLORANT_3_BIT	8U

/* Alias names for the bits in particular colour models */
#define BLACK_BIT	COLORANT_0_BIT
#define CYAN_BIT	COLORANT_1_BIT
#define MAGENTA_BIT	COLORANT_2_BIT
#define YELLOW_BIT	COLORANT_3_BIT
#define RED_BIT		COLORANT_1_BIT
#define GREEN_BIT	COLORANT_2_BIT
#define BLUE_BIT	COLORANT_3_BIT

/* Bit plane indices for splitting */
#define COLORANT_0_INDEX	0
#define COLORANT_1_INDEX	1
#define COLORANT_2_INDEX	2
#define COLORANT_3_INDEX	3

/*  Macro to extract the dominant 8 bits from a 'gx_color_value'. This
    definition assumes that 'gx_color_value' uses the least significant 16 bits
    of the underlying type (unsigned short). Splitting this part off looks
    inefficient because left shifts will usually follow, but I'm relying on the
    compiler to be sufficiently intelligent to eliminate this inefficiency.
    This way the code is easier to check.
    The type cast is needed to prevent problems with negative values on
    platforms where 'gx_color_index' has more bits than 'int'.
 */
#define dominant_8bits(value)	((unsigned int)((value) >> 8))

/******************************************************************************

  Function: eprn_number_of_bitplanes

  Total number of bit planes returned by eprn_get_planes().
  This value is constant while the device is open.

******************************************************************************/

unsigned int eprn_number_of_bitplanes(eprn_Device *dev)
{
  return dev->eprn.output_planes;
}

/******************************************************************************

  Function: eprn_number_of_octets

  Maximal lengths, in terms of the number of 'eprn_Octet' instances, for each
  bit plane returned by eprn_get_planes() for this device. These values
  are constant while the device is open.

******************************************************************************/

void eprn_number_of_octets(eprn_Device *dev, unsigned int lenghts[])
{
  unsigned int j, length;

  length = (dev->eprn.octets_per_line + dev->color_info.depth - 1)/
      dev->color_info.depth;
   /* This results in length >= ceiling((number of pixels per line)/8)
      because:
              8 * octets_per_line >= pixels_per_line * depth
        <==>  octets_per_line/depth >= pixels_per_line/8
      where division is to be understood as exact.
    */

  for (j = 0; j < dev->eprn.output_planes; j++) lenghts[j] = length;

  return;
}

/******************************************************************************

  Function: eprn_map_rgb_color_for_RGB

  Colour mapping function for the process colour model 'DeviceRGB' and
  2 intensity levels per colorant.

******************************************************************************/

gx_color_index eprn_map_rgb_color_for_RGB(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value red = cv[0], green = cv[1], blue = cv[2];
  static const gx_color_value half = gx_max_color_value/2;
  gx_color_index value = 0;

#ifdef EPRN_TRACE
  if_debug3(EPRN_TRACE_CHAR,
    "! eprn_map_rgb_color_for_RGB() called for RGB = (%hu, %hu, %hu),\n",
    red, green, blue);
#endif

  assert(((eprn_Device *)device)->eprn.colour_model == eprn_DeviceRGB);

  if (red   > half) value |= RED_BIT;
  if (green > half) value |= GREEN_BIT;
  if (blue  > half) value |= BLUE_BIT;

#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR, "  returning 0x%lX.\n", (unsigned long)value);
#endif
  return value;
}

/******************************************************************************

  Function: eprn_map_rgb_color_for_CMY_or_K

  Colour mapping function for the native colour spaces DeviceGray and DeviceRGB
  and a process colour model using a selection of CMYK colorants with at most
  2 intensity levels per colorant. This function must not be called for the
  process colour models 'DeviceRGB' and 'DeviceCMYK'.

******************************************************************************/

gx_color_index eprn_map_rgb_color_for_CMY_or_K(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value red = cv[0], green = cv[1], blue = cv[2];
  static const gx_color_value half = gx_max_color_value/2;
  gx_color_index value = (CYAN_BIT | MAGENTA_BIT | YELLOW_BIT);
  const eprn_Device *dev = (eprn_Device *)device;

#ifdef EPRN_TRACE
  if_debug3(EPRN_TRACE_CHAR,
    "! eprn_map_rgb_color_for_CMY_or_K() called for RGB = (%hu, %hu, %hu),\n",
    red, green, blue);
#endif

  assert((dev->eprn.colour_model == eprn_DeviceGray && red == green &&
          green == blue && (blue == 0 || blue == gx_max_color_value)) ||
         dev->eprn.colour_model == eprn_DeviceCMY ||
         dev->eprn.colour_model == eprn_DeviceCMY_plus_K);

  /* Map to CMY */
  if (red   > half) value &= ~CYAN_BIT;
  if (green > half) value &= ~MAGENTA_BIT;
  if (blue  > half) value &= ~YELLOW_BIT;

  /* Remap composite black to true black if available */
  if (dev->eprn.colour_model != eprn_DeviceCMY &&
      value == (CYAN_BIT | MAGENTA_BIT | YELLOW_BIT))
    value = BLACK_BIT;

#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR, "  returning 0x%lX.\n", (unsigned long)value);
#endif
  return value;
}

/******************************************************************************

  Function: eprn_map_rgb_color_for_RGB_flex

  This is a 'map_rgb_color' method for the process colour model 'DeviceRGB'
  supporting any number of intensity levels.

******************************************************************************/

gx_color_index eprn_map_rgb_color_for_RGB_flex(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value red = cv[0], green = cv[1], blue = cv[2];
  gx_color_index value = 0;
  gx_color_value step;
  gx_color_index level;
  const eprn_Eprn *eprn = &((eprn_Device *)device)->eprn;

#ifdef EPRN_TRACE
  if_debug3(EPRN_TRACE_CHAR,
    "! eprn_map_rgb_color_for_RGB_flex() called for RGB = (%hu, %hu, %hu),\n",
    red, green, blue);
#endif

  /* See the discussion in eprn_map_cmyk_color_flex() below. */

  step = gx_max_color_value/eprn->non_black_levels;

  /* The order has to be BGR from left to right */
  level = blue/step;
  if (level >= eprn->non_black_levels) level = eprn->non_black_levels - 1;
  value = level << eprn->bits_per_colorant;
  level = green/step;
  if (level >= eprn->non_black_levels) level = eprn->non_black_levels - 1;
  value = (value | level) << eprn->bits_per_colorant;
  level = red/step;
  if (level >= eprn->non_black_levels) level = eprn->non_black_levels - 1;
  value = (value | level) << eprn->bits_per_colorant;

#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR, "  returning 0x%lX.\n", (unsigned long)value);
#endif
  return value;
}

/******************************************************************************

  Function: eprn_map_rgb_color_for_CMY_or_K_flex

  This is a flexible 'map_rgb_color' method. It must not be called for the
  process colour models 'DeviceRGB' and 'DeviceCMYK'.

******************************************************************************/

gx_color_index eprn_map_rgb_color_for_CMY_or_K_flex(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value red = cv[0], green = cv[1], blue = cv[2];
  const eprn_Device *dev = (eprn_Device *)device;

#ifdef EPRN_TRACE
  if_debug3(EPRN_TRACE_CHAR,
    "! eprn_map_rgb_color_for_CMY_or_K_flex() called for "
      "RGB = (%hu, %hu, %hu).\n",
    red, green, blue);
#endif

  /* Treat pure grey levels differently if we have black. This implies that for
     CMY+K only "true" grey shades will be printed with black ink, all others
     will be mixed from CMY. */
  gx_color_value tmpcv[4];
  if (dev->eprn.colour_model != eprn_DeviceCMY && red == green && green == blue) {
    tmpcv[0] = 0; tmpcv[1] = 0; tmpcv[2] = 0;
    tmpcv[3] = gx_max_color_value - red;
    return eprn_map_cmyk_color_flex(device, tmpcv);

  }
  tmpcv[0] = gx_max_color_value - red;
  tmpcv[1] = gx_max_color_value - green;
  tmpcv[2] = gx_max_color_value - blue;
  tmpcv[3] = 0;
  return eprn_map_cmyk_color_flex(device, tmpcv);
}

/******************************************************************************

  Function: eprn_map_rgb_color_for_RGB_max

  Colour mapping function for the process colour model 'DeviceRGB' retaining as
  much information as possible.

******************************************************************************/

gx_color_index eprn_map_rgb_color_for_RGB_max(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value red = cv[0], green = cv[1], blue = cv[2];
  gx_color_index value;

#ifdef EPRN_TRACE
  if_debug3(EPRN_TRACE_CHAR,
    "! eprn_map_rgb_color_for_RGB_max() called for RGB = (%hu, %hu, %hu),\n",
    red, green, blue);
#endif

  value  = dominant_8bits(red)   <<  8;
  value |= dominant_8bits(green) << 16;
  value |= dominant_8bits(blue)  << 24;

#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR, "  returning 0x%08lX.\n", (unsigned long)value);
#endif
  return value;
}

/******************************************************************************

  Function: eprn_map_rgb_color_for_CMY_or_K_max

  "Maximal" colour mapping function for the process colour models "DeviceGray",
  "DeviceCMY", and "CMY+K".

******************************************************************************/

gx_color_index eprn_map_rgb_color_for_CMY_or_K_max(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value red = cv[0], green = cv[1], blue = cv[2];
  const eprn_Device *dev = (eprn_Device *)device;

#ifdef EPRN_TRACE
  if_debug3(EPRN_TRACE_CHAR,
    "! eprn_map_rgb_color_for_CMY_or_K_max() called for "
      "RGB = (%hu, %hu, %hu).\n",
    red, green, blue);
#endif

  gx_color_value tmpcv[4];
  if (dev->eprn.colour_model == eprn_DeviceGray) {
    tmpcv[0] = 0; tmpcv[1] = 0; tmpcv[2] = 0;
    tmpcv[3] = gx_max_color_value - red;
    return eprn_map_cmyk_color_max(device, tmpcv);
  }
  /* Note that the conversion from composite black to true black for CMY+K can
     only happen at the output pixel level, not here. */
  tmpcv[0] = gx_max_color_value - red;
  tmpcv[1] = gx_max_color_value - green;
  tmpcv[2] = gx_max_color_value - blue;
  tmpcv[3] = 0;
  return eprn_map_cmyk_color_max(device, tmpcv);
}

/******************************************************************************

  Function: eprn_map_color_rgb

  This function is a 'map_color_rgb' method.

  Such a method should return an RGB triple for the specified 'color'. This is
  apparently intended for devices supporting colour maps.

  The function param_HWColorMap() in gsdparam.c tries to compile such a
  colour map by calling this method repeatedly for all values from 0 to
  2^color_info.depth - 1, provided the depth is less than or equal to 8 and
  the process colour model is not DeviceCMYK. If the function for one of these
  values returns a negative code, the assumption seems to be that there is no
  such colour map. Because there is a default method which always returns zero,
  such a colour map is always constructed if a device does not implement its
  own 'map_color_rgb' method. This can be seen in the appearance of the
  "HWColorMap" page device parameter.

  The key purpose of this function is therefore to return a negative code.

******************************************************************************/

int eprn_map_color_rgb(gx_device *device, gx_color_index color,
  gx_color_value rgb[])
{
#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR,
    "! eprn_map_color_rgb() called for 0x%lX.\n", (unsigned long)color);
#endif

  /* Just to be safe we return defined values (white) */
  if (((eprn_Device *)device)->eprn.colour_model == eprn_DeviceRGB)
    rgb[0] = rgb[1] = rgb[2] = gx_max_color_value;
  else
    rgb[0] = rgb[1] = rgb[2] = 0;

  return -1;
}

/******************************************************************************

  Function: eprn_map_cmyk_color

  Colour mapping function for a process colour model of 'DeviceCMYK' with
  2 intensity levels for all colorants.

******************************************************************************/

gx_color_index eprn_map_cmyk_color(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value cyan = cv[0], magenta = cv[1], yellow = cv[2], black = cv[3];
  gx_color_index value = 0;
  static const gx_color_value threshold = gx_max_color_value/2;

#ifdef EPRN_TRACE
  if_debug4(EPRN_TRACE_CHAR,
    "! eprn_map_cmyk_color() called for CMYK = (%hu, %hu, %hu, %hu),\n",
      cyan, magenta, yellow, black);
#endif

  if (cyan    > threshold) value |= CYAN_BIT;
  if (magenta > threshold) value |= MAGENTA_BIT;
  if (yellow  > threshold) value |= YELLOW_BIT;
  if (black   > threshold) value |= BLACK_BIT;

#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR, "  returning 0x%lX.\n", (unsigned long)value);
#endif
  return value;
}

/******************************************************************************

  Function: eprn_map_cmyk_color_flex

  This is a 'map_cmyk_color' method supporting arbitrary numbers of intensity
  levels. It may be called for every colour model except DeviceRGB. Colorants
  not present in the model will be ignored.

******************************************************************************/

gx_color_index eprn_map_cmyk_color_flex(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value cyan = cv[0], magenta = cv[1], yellow = cv[2], black = cv[3];
  gx_color_index value = 0;
  gx_color_value step;
  gx_color_index level;
  const eprn_Eprn *eprn = &((eprn_Device *)device)->eprn;

#ifdef EPRN_TRACE
  if_debug4(EPRN_TRACE_CHAR,
    "! eprn_map_cmyk_color_flex() called for CMYK = (%hu, %hu, %hu, %hu),\n",
      cyan, magenta, yellow, black);
#endif

  /*  I can think of three linear methods to extract discrete values from a
      continuous intensity in the range [0, 1]:
      (a) multiply by the number of levels minus 1 and truncate,
      (b) multiply by the number of levels minus 1 and round, and
      (c) multiply by the number of levels and truncate, except for an
          intensity of 1 in which case one returns the number of levels minus 1.
      For intensity values which can be represented exactly, i.e.,

        intensity = i/(levels-1)	for some non-negative i < levels,

      these three methods are identical. (a) is however inappropriate here
      because for less than 32 levels ghostscript already provides intensity
      values which have been adjusted to a representable level. A rounding
      error could now result in a level which is too small by one. I prefer (c)
      because it gives equal shares to all levels.

      I'm using integer arithmetic here although floating point numbers would
      be more accurate. This routine may, however, be called quite frequently,
      and the loss in accuray is acceptable as long as the values determined
      for 'step' are large compared to the number of levels. If you consider
      "large" as meaning "10 times as large", the critical boundary is at about
      81 levels. The highest number of intensity levels at present supported by
      HP DeskJets is apparently 4.

      A more accurate implementation would determine 'step' as a floating point
      value, divide the intensity by it, and take the floor (entier) of the
      result as the component intensity.
  */

  /* The order has to be (YMC)(K) from left to right */
  if (eprn->colour_model != eprn_DeviceGray) {
    step = gx_max_color_value/eprn->non_black_levels;

    level = yellow/step;
    if (level >= eprn->non_black_levels) level = eprn->non_black_levels - 1;
    value = level << eprn->bits_per_colorant;
    level = magenta/step;
    if (level >= eprn->non_black_levels) level = eprn->non_black_levels - 1;
    value = (value | level) << eprn->bits_per_colorant;
    level = cyan/step;
    if (level >= eprn->non_black_levels) level = eprn->non_black_levels - 1;
    value = (value | level) << eprn->bits_per_colorant;
  }
  if (eprn->colour_model != eprn_DeviceCMY) {
    step = gx_max_color_value/eprn->black_levels;
    level = black/step;
    if (level >= eprn->black_levels) level = eprn->black_levels - 1;
    value |= level;
  }

#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR, "  returning 0x%lX.\n", (unsigned long)value);
#endif
  return value;
}

/******************************************************************************

  Function: eprn_map_cmyk_color_max

  This is a 'map_cmyk_color' method retaining as much colour information as
  possible. The reduction to the printer's capabilities must happen in the
  output routine.

******************************************************************************/

gx_color_index eprn_map_cmyk_color_max(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value cyan = cv[0], magenta = cv[1], yellow = cv[2], black = cv[3];
  gx_color_index value;

#ifdef EPRN_TRACE
  if_debug4(EPRN_TRACE_CHAR,
    "! eprn_map_cmyk_color_max() called for CMYK = (%hu, %hu, %hu, %hu),\n",
      cyan, magenta, yellow, black);
#endif

  value = dominant_8bits(black);
  value |= dominant_8bits(cyan)    <<  8;
  value |= dominant_8bits(magenta) << 16;
  value |= dominant_8bits(yellow)  << 24;

#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR, "  returning 0x%08lX.\n", (unsigned long)value);
#endif
  return value;
}

/******************************************************************************

  Function: eprn_map_cmyk_color_glob

  Global eprn_map_cmyk_color for all cases handled above used to
  be able to work together with ghostscript 8.15 and CMYK model.

******************************************************************************/
gx_color_index eprn_map_cmyk_color_glob(gx_device *device, const gx_color_value cv[])
{
  const eprn_Eprn *eprn = &((eprn_Device *)device)->eprn;
  if (eprn->intensity_rendering == eprn_IR_FloydSteinberg)
    return eprn_map_cmyk_color_max(device, cv);
  else if (device->color_info.max_gray > 1 || device->color_info.max_color > 1)
    return eprn_map_cmyk_color_flex(device, cv);
  else
    return eprn_map_cmyk_color(device, cv);
}

/******************************************************************************

  Function: eprn_finalize

  This function fills the last octet in a set of bit planes with white if
  needed and sets the lengths of all these planes.

  'is_RGB' denotes whether the colour model is eprn_DeviceRGB,
  'non_black_levels' is the number of intensity levels for non-black colorants,
  'planes' the number of bit planes,
  'plane' points to an array of at least 'planes' bit planes,
  'ptr' points to an array of at least 'planes' pointers into the bit planes,
  'pixels' is the number of pixels written to the group of bit planes.

  If 'pixels' is divisible by 8, the 'ptr' pointers point to octets after
  the last completely written octets. Otherwise, they point to the last
  incompletely written octet in which the pixels written so far occupy the
  least significant bits.

******************************************************************************/

void eprn_finalize(bool is_RGB, unsigned int non_black_levels,
  int planes, eprn_OctetString *plane, eprn_Octet **ptr, int pixels)
{
  int j;

  /* Execute remaining left shifts in the last octet of the output planes when
     the number of pixels is not a multiple of 8, and fill with white on the
     right */
  if (pixels % 8 != 0) {
    int shift = 8 - pixels % 8;

    if (is_RGB) {
      /* White may be any intensity, but it's the same for all three colorants,
         and it's the highest. */
      eprn_Octet imax = non_black_levels - 1;
      int c, rgb_planes = eprn_bits_for_levels(non_black_levels);

      j = 0;	/* next output plane */

      /* Loop over RGB */
      for (c = 0; c < 3; c++) {
        eprn_Octet value = imax;
        int m;

        /* Loop over all planes for this colorant */
        for (m = 0; m < rgb_planes; m++, j++) {
          eprn_Octet bit = value & 1;
          int p;

          value = value >> 1;

          /* Put the bit into all remaining pixels for this plane */
          for (p = 0; p < shift; p++)
            *ptr[j] = (*ptr[j] << 1) | bit;
        }
      }
    }
    else /* White is zero */
      for (j = 0; j < planes; j++)
        *ptr[j] = *ptr[j] << shift;

    /* Advance all plane pointers by 1 */
    for (j = 0; j < planes; j++) ptr[j]++;
  }

  /* Set the lengths of the bit plane strings */
  for (j = 0; j < planes; j++) {
    if (pixels == 0) plane[j].length = 0;
    else plane[j].length = ptr[j] - plane[j].str;
  }

  return;
}

/******************************************************************************

  Function: split_line_le8

  This is the first of the "split_line implementations". See the body of
  eprn_get_planes() for the calling conventions common to them.

  This particular implementation has the following restrictions:
  - The pixmap depth must be a divisor of 8.

******************************************************************************/

static void split_line_le8(eprn_Device *dev, const eprn_Octet *line,
  int length, eprn_OctetString plane[])
{
  gx_color_index
    pixel;
  int
    black_planes,	/* number of planes to send for black */
    non_black_planes,	/* number of planes to send for each of CMY/RGB */
    j,
    k,
    pixels,		/* number of pixels transferred to bit planes */
    planes;		/* number of planes to send */
  eprn_Octet
    comp_mask = 0,	/* 'bits_per_component' 1s in the lowest part */
    pixel_mask = 0,	/* 'depth' 1s in the lowest part */
    *ptr[8];		/* pointers into planes (next octet to write to) */

  /* Number of planes to send */
  black_planes = eprn_bits_for_levels(dev->eprn.black_levels);
  non_black_planes = eprn_bits_for_levels(dev->eprn.non_black_levels);
  planes = black_planes + 3*non_black_planes;

  /* Initialize the bit plane pointers */
  for (j = 0; j < planes; j++) ptr[j] = plane[j].str;

  /* Determine some bit masks */
  for (j = 0; j < dev->color_info.depth; j++)
    pixel_mask = (pixel_mask << 1) | 1;
  for (j = 0; j < dev->eprn.bits_per_colorant; j++)
    comp_mask = (comp_mask << 1) | 1;

  /* Copy from 'line' to 'plane[]', converting Z format to XY format */
  pixels = 0;
  k = 0; /* Next octet index in the input line */
  while (k < length) {
    int l, m, p;

    /* Initialize plane storage if it's a new output octet */
    if (pixels % 8 == 0) for (j = 0; j < planes; j++) *ptr[j] = 0;

    /* Loop over pixels within the input octet, starting at the leftmost
       pixel (highest-order bits) */
    p = 8/dev->color_info.depth - 1;
    do {
      eprn_Octet comp;

      /* Extract pixel */
      pixel = (line[k] >> p*dev->color_info.depth) & pixel_mask;

      /* Extract components from the pixel and distribute each over its planes
       */
      comp = pixel & comp_mask;	/* black */
      for (j = 0; j < black_planes; j++) {
        *ptr[j] = (*ptr[j] << 1) | (comp & 1);
        comp >>= 1;
      }
      if (non_black_planes > 0) for (l = 1; l < 4; l++) {
        comp = (pixel >> l*dev->eprn.bits_per_colorant) & comp_mask;
        for (m = 0; m < non_black_planes; m++, j++) {
          *ptr[j] = (*ptr[j] << 1) | (comp & 1);
          comp >>= 1;
        }
      }

      pixels++;
      p--;
    } while (p >= 0);
    k++;

    /* Increase plane pointers if an output octet boundary has been reached */
    if (pixels % 8 == 0) for (j = 0; j < planes; j++) ptr[j]++;
  }

  eprn_finalize(dev->eprn.colour_model == eprn_DeviceRGB,
    dev->eprn.non_black_levels, planes, plane, ptr, pixels);

  return;
}

/******************************************************************************

  Function: split_line_ge8

  This split_line function has the following restrictions:
  - The pixmap depth must be a multiple of 8.
  - There may be at most 8 bits per colorant.
    (Hence the depth is at most 32.)
  - 'length' must be divisible by depth/8.

******************************************************************************/

static void split_line_ge8(eprn_Device *dev, const eprn_Octet *line,
  int length, eprn_OctetString plane[])
{
  gx_color_index
    pixel;
  int
    black_planes,	/* number of planes to send for black */
    non_black_planes,	/* number of planes to send for each of CMY/RGB */
    j,
    k,
    octets_per_pixel = dev->color_info.depth/8,
    pixels,		/* number of pixels transferred to bit planes */
    planes;		/* number of planes to send */
  eprn_Octet
    comp_mask = 0,	/* bits_per_component 1s in the lowest part */
    *ptr[32];		/* pointers into planes (next octet to write to) */

  /* Number of planes to send */
  black_planes = eprn_bits_for_levels(dev->eprn.black_levels);
  non_black_planes = eprn_bits_for_levels(dev->eprn.non_black_levels);
  planes = black_planes + 3*non_black_planes;

  /* Initialize the bit plane pointers */
  for (j = 0; j < planes; j++) ptr[j] = plane[j].str;

  /* Determine the component mask */
  for (j = 0; j < dev->eprn.bits_per_colorant; j++)
    comp_mask = (comp_mask << 1) | 1;

  /* Copy from 'line' to 'plane[]', converting Z format to XY format */
  pixels = 0;
  k = 0; /* Next octet index in the input line */
  while (k < length) {
    eprn_Octet comp;
    int l, m;

    /* Initialize plane storage if it's a new octet */
    if (pixels % 8 == 0) for (j = 0; j < planes; j++) *ptr[j] = 0;

    /* Reconstruct pixel from several octets */
    j = 0;
    pixel = line[k];
    do {
      j++; k++;
      if (j >= octets_per_pixel) break;
      pixel = (pixel << 8) | line[k];	/* MSB (Big Endian) */
    } while (1);

    /* Split and distribute over planes */
    comp = pixel & comp_mask;	/* black */
    for (j = 0; j < black_planes; j++) {
      *ptr[j] = (*ptr[j] << 1) | (comp & 1);
      comp >>= 1;
    }
    for (l = 1; l < 4; l++) {
      comp = (pixel >> l*dev->eprn.bits_per_colorant) & comp_mask;
      for (m = 0; m < non_black_planes; m++, j++) {
        *ptr[j] = (*ptr[j] << 1) | (comp & 1);
        comp >>= 1;
      }
    }

    pixels++;

    /* Increase plane pointers if an octet boundary has been reached */
    if (pixels % 8 == 0) for (j = 0; j < planes; j++) ptr[j]++;
  }

  eprn_finalize(dev->eprn.colour_model == eprn_DeviceRGB,
    dev->eprn.non_black_levels, planes, plane, ptr, pixels);

  return;
}

/******************************************************************************

  Function: split_line_3or4x1

  This function is a split_line() implementation for a non-monochrome colour
  model (3 or 4 components) with 1 bit per component.

******************************************************************************/

static void split_line_3or4x1(eprn_Device *dev, const eprn_Octet *line,
  int length, eprn_OctetString plane[])
{
  int
    from = (dev->eprn.colour_model == eprn_DeviceRGB ||
      dev->eprn.colour_model == eprn_DeviceCMY? 1: 0),
    j,
    k,
    l;
  eprn_Octet *ptr[4];	/* pointers into planes, indexed KCMY/-RGB */

  ptr[0] = NULL;	/* defensive programming */
  for (j = from; j < 4; j++) ptr[j] = plane[j-from].str;

  /*  Loop over the input line, taking four octets (8 pixels) at a time, as far
      as available, and split them into four output octets, one for each
      colorant.
  */
  k = 0;
  while (k < length) {
    eprn_Octet octet[4] = {0, 0, 0, 0};

    for (l = 0; l < 4 && k < length; l++, k++) {
      eprn_Octet part;
#define treat_quartet()						\
      octet[COLORANT_0_INDEX] <<= 1;				\
      if (part & COLORANT_0_BIT) octet[COLORANT_0_INDEX] |= 1;	\
      octet[COLORANT_1_INDEX] <<= 1;				\
      if (part & COLORANT_1_BIT) octet[COLORANT_1_INDEX] |= 1;	\
      octet[COLORANT_2_INDEX] <<= 1;				\
      if (part & COLORANT_2_BIT) octet[COLORANT_2_INDEX] |= 1;	\
      octet[COLORANT_3_INDEX] <<= 1;				\
      if (part & COLORANT_3_BIT) octet[COLORANT_3_INDEX] |= 1;

      /* Upper four bits */
      part = (line[k] >> 4) & 0x0F;
      treat_quartet()

      /* Lower four bits */
      part = line[k] & 0x0F;
      treat_quartet()

#undef treat_quartet
    }
    if (l < 4) {
      for (j = from; j < 4; j++) octet[j] <<= 8 - 2*l;
      if (dev->eprn.colour_model == eprn_DeviceRGB) {
        /* Add white in the last 8 - 2*l pixels */
        for (j = 1; j < 4; j++) {
          int k;
          /* We add two pixels at a time */
          for (k = 3 - l; k >= 0; k--) octet[j] |= 0x03 << k;
        }
      }
    }
    for (j = from; j < 4; j++) *(ptr[j]++) = octet[j];
  }

  /* Set the lengths of the bit plane strings */
  for (j = 0; j < dev->eprn.output_planes; j++) {
    if (length == 0) plane[j].length = 0;
    else plane[j].length = ptr[from + j] - plane[j].str;
  }

  return;
}

/******************************************************************************

  Function: split_line_4x2

  This is a split_line() implementation for 4 colour components (i.e., CMYK)
  with 3 or 4 levels each (2 bit planes to send for each component).

******************************************************************************/

static void split_line_4x2(eprn_Device *dev, const eprn_Octet *line,
  int length, eprn_OctetString plane[])
{
  gx_color_index
    pixel;
  int
    j,
    k;
  eprn_Octet
    *ptr[8];		/* pointers into planes (next octet to write to) */

  /* Initialize the bit plane pointers */
  for (j = 0; j < 8; j++) ptr[j] = plane[j].str;

  /* Copy from 'line' to 'plane[]', converting Z format to XY format */
  for (k = 0; k < length; k++) {
    /* k is the index of the next octet in the input line and the number of
       pixels processed so far. */

    /* Initialize plane storage if it's a new octet */
    if (k % 8 == 0) for (j = 0; j < 8; j++) *ptr[j] = 0;

    /* Fetch pixel */
    pixel = line[k];

    /* Split and distribute over planes */
    *ptr[0] = (*ptr[0] << 1) | (pixel & 0x01);
#define assign_bit(index) \
    *ptr[index] = (*ptr[index] << 1) | ((pixel >> index) & 0x01)
    assign_bit(1);
    assign_bit(2);
    assign_bit(3);
    assign_bit(4);
    assign_bit(5);
    assign_bit(6);
    assign_bit(7);
#undef assign_bit

    /* Increase plane pointers if an output octet boundary has been reached */
    if (k % 8 == 7) for (j = 0; j < 8; j++) ptr[j]++;
  }

  /* Execute remaining left shifts in the last octet of the output planes when
     the number of pixels is not a multiple of 8 */
  k = length % 8;
  if (k != 0) {
    int shift = 8 - k;
    for (j = 0; j < 8; j++)
      *(ptr[j]++) <<= shift;
  }

  /* Set the lengths of the bit plane strings */
  for (j = 0; j < 8; j++) {
    if (length == 0) plane[j].length = 0;
    else plane[j].length = ptr[j] - plane[j].str;
  }

  return;
}

/******************************************************************************

  Function: eprn_fetch_scan_line

  If there is a next scan line with y coordinate 'dev->eprn.next_y', this
  function fetches it into '*line' and returns zero. Otherwise the function
  returns a non-zero value.

  The storage allocated for 'line->str' must be at least of size
  'dev->eprn.octets_per_line'.

  On success, the 'length' field in 'line' does not include trailing zero
  pixels.

******************************************************************************/

int eprn_fetch_scan_line(eprn_Device *dev, eprn_OctetString *line)
{
  int rc;
  const eprn_Octet *str;

  rc = gdev_prn_copy_scan_lines((gx_device_printer *)dev, dev->eprn.next_y,
    line->str, dev->eprn.octets_per_line);
   /* gdev_prn_copy_scan_lines() returns the number of scan lines it fetched
      or a negative value on error. The number of lines to fetch is the value
      of the last argument divided by the length of a single line, hence in
      this case 1. */
  if (rc != 1) return 1;

  /* Set the length to ignore trailing zero octets in the scan line */
  str = line->str + (dev->eprn.octets_per_line - 1);
  while (str > line->str && *str == 0) str--;
  if (*str == 0) line->length = 0;
  else line->length = str - line->str + 1;

  /* Ensure we have an integral number of pixels in the line */
  if (dev->color_info.depth > 8) {
    int rem;
    unsigned int octets_per_pixel = dev->color_info.depth/8;
      /* If the depth is larger than 8, it is a multiple of 8. */
    rem = line->length % octets_per_pixel;
    if (rem != 0) line->length += octets_per_pixel - rem;
  }

#if 0 && defined(EPRN_TRACE)
  if (gs_debug_c(EPRN_TRACE_CHAR)) {
    int j;
    dmlprintf(dev->memory, "! eprn_fetch_scan_line(): Fetched ");
    if (line->length == 0) dmprintf(dev->memory, "empty scan line.");
    else {
      dmprintf(dev->memory, "scan line: 0x");
      for (j = 0; j < line->length; j++) dmprintf1(dev->memory, "%02X", line->str[j]);
    }
    dmlprintf(dev->memory, "\n");
  }
#endif

  return 0;
}

/******************************************************************************

  Function: eprn_get_planes

  For the description of this function, see gdeveprn.h.

******************************************************************************/

int eprn_get_planes(eprn_Device *dev, eprn_OctetString bitplanes[])
{
  eprn_OctetString *line; /* where to copy the scan line from the prn device */
  int
    j,
    rc;

  /* Avoid a copying step for a depth of 1 */
  if (dev->color_info.depth == 1) line = bitplanes;
  else line = &dev->eprn.scan_line;

  /* Fetch the scan line if available */
  if (dev->eprn.intensity_rendering == eprn_IR_FloydSteinberg &&
      dev->eprn.next_y == 0) return 1;
  rc = eprn_fetch_scan_line(dev, line);
  if (rc == 0) dev->eprn.next_y++;
  else {
    if (dev->eprn.intensity_rendering != eprn_IR_FloydSteinberg) return 1;
    dev->eprn.next_y = 0;
  }

  if (dev->color_info.depth == 1) return 0;

  if (dev->eprn.intensity_rendering == eprn_IR_FloydSteinberg) {
    eprn_OctetString tmp;

    eprn_split_FS(&dev->eprn.next_scan_line, &dev->eprn.scan_line,
      dev->eprn.octets_per_line,
      dev->eprn.colour_model,
      dev->eprn.black_levels, dev->eprn.non_black_levels,
      bitplanes);

    /* Switch 'next_scan_line' to refer to what is currently 'scan_line' */
    tmp = dev->eprn.next_scan_line;
    dev->eprn.next_scan_line = dev->eprn.scan_line;
    dev->eprn.scan_line = tmp;
  }
  else {
    /*  Here we split multi-bit pixels which are already adapted to the
        printer's capabilities.

        All the functions called here have the following signature:

          static void split_line...(eprn_Device *dev, const eprn_Octet *line,
            int length, eprn_OctetString plane[])

        Such a "split_line implementation" must take the scan line of length
        'length', pointed to by 'line', split it into bit planes according to
        the state of 'dev', and return these planes via 'plane'. The length
        fields of the planes must be set. Trailing zero octets should not be
        removed because it's done here afterwards anyway.
    */

    if (dev->eprn.colour_model == eprn_DeviceGray)
      split_line_le8(dev, line->str, line->length, bitplanes);
    else {
      if (dev->eprn.bits_per_colorant == 1) {
#ifndef EPRN_TRAILING_BIT_BUG_FIXED
        if (dev->eprn.colour_model == eprn_DeviceRGB &&
            line->length == dev->eprn.octets_per_line) {
         /* At least gs 6.01 and 6.50 sometimes generate pixel lines where the
            last pixel is not white but black (last octet in 'line' is 0xE0
            instead of 0xEE; with pcl3 it shows up for A6 and A4, but not for
            A3, A5, US Letter, or US Legal).
            I'm overwriting it with white. */
#ifdef EPRN_TRACE
          if (gs_debug_c(EPRN_TRACE_CHAR)) {
            static bool already_noted = false;
            if (!already_noted && line->str[line->length - 1] != 0xEE) {
              dmlprintf1(dev->memory, "! eprn_get_planes(): "
                         "Line-terminating octet is 0x%02X.\n",
                         line->str[line->length - 1]);
              already_noted = true;
            }
          }
#endif	/* EPRN_TRACE */
          line->str[line->length - 1] |= RED_BIT | GREEN_BIT | BLUE_BIT;
        }
#endif	/* EPRN_TRAILING_BIT_BUG_FIXED */
        split_line_3or4x1(dev, line->str, line->length, bitplanes);
      }
      else if (dev->eprn.bits_per_colorant == 2 && dev->eprn.black_levels > 2 &&
          dev->eprn.non_black_levels > 2)
        split_line_4x2(dev, line->str, line->length, bitplanes);
      else if (dev->color_info.depth < 8)
        split_line_le8(dev, line->str, line->length, bitplanes);
      else split_line_ge8(dev, line->str, line->length, bitplanes);
    }
  }

  /* Reduce the lengths of the individual bit planes to not include trailing
     zero octets */
  for (j = 0; j < dev->eprn.output_planes; j++) {
    if (bitplanes[j].length > 0) {
      const eprn_Octet *str = bitplanes[j].str + (bitplanes[j].length - 1);
      while (str > bitplanes[j].str && *str == 0) str--;
      if (*str == 0) bitplanes[j].length = 0;
      else bitplanes[j].length = (str - bitplanes[j].str) + 1;
    }
  }

  return 0;
}
