interpret.c   [plain text]


/*
 * "$Id: interpret.c 7852 2008-08-21 04:19:45Z mike $"
 *
 *   PPD command interpreter for CUPS.
 *
 *   Copyright 2007-2010 by Apple Inc.
 *   Copyright 1993-2007 by Easy Software Products.
 *
 *   These coded instructions, statements, and computer programs are the
 *   property of Apple Inc. and are protected by Federal copyright
 *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
 *   which should have been included with this file.  If this file is
 *   file is missing or damaged, see the license at "http://www.cups.org/".
 *
 *   This file is subject to the Apple OS-Developed Software exception.
 *
 * Contents:
 *
 *   cupsRasterInterpretPPD() - Interpret PPD commands to create a page header.
 *   _cupsRasterExecPS()      - Execute PostScript code to initialize a page
 *                              header.
 *   cleartomark_stack()      - Clear to the last mark ([) on the stack.
 *   copy_stack()             - Copy the top N stack objects.
 *   delete_stack()           - Free memory used by a stack.
 *   error_object()           - Add an object's value to the current error
 *                              message.
 *   error_stack()            - Add a stack to the current error message.
 *   index_stack()            - Copy the Nth value on the stack.
 *   new_stack()              - Create a new stack.
 *   pop_stock()              - Pop the top object off the stack.
 *   push_stack()             - Push an object on the stack.
 *   roll_stack()             - Rotate stack objects.
 *   scan_ps()                - Scan a string for the next PS object.
 *   setpagedevice()          - Simulate the PostScript setpagedevice operator.
 *   DEBUG_object()           - Print an object value.
 *   DEBUG_stack()            - Print a stack.
 */

/*
 * Include necessary headers...
 */

#include "image-private.h"


/*
 * Stack values for the PostScript mini-interpreter...
 */

typedef enum
{
  CUPS_PS_NAME,
  CUPS_PS_NUMBER,
  CUPS_PS_STRING,
  CUPS_PS_BOOLEAN,
  CUPS_PS_NULL,
  CUPS_PS_START_ARRAY,
  CUPS_PS_END_ARRAY,
  CUPS_PS_START_DICT,
  CUPS_PS_END_DICT,
  CUPS_PS_START_PROC,
  CUPS_PS_END_PROC,
  CUPS_PS_CLEARTOMARK,
  CUPS_PS_COPY,
  CUPS_PS_DUP,
  CUPS_PS_INDEX,
  CUPS_PS_POP,
  CUPS_PS_ROLL,
  CUPS_PS_SETPAGEDEVICE,
  CUPS_PS_STOPPED,
  CUPS_PS_OTHER
} _cups_ps_type_t;

typedef struct
{
  _cups_ps_type_t	type;		/* Object type */
  union
  {
    int		boolean;		/* Boolean value */
    char	name[64];		/* Name value */
    double	number;			/* Number value */
    char	other[64];		/* Other operator */
    char	string[64];		/* Sring value */
  }			value;		/* Value */
} _cups_ps_obj_t;

typedef struct
{
  int			num_objs,	/* Number of objects on stack */
			alloc_objs;	/* Number of allocated objects */
  _cups_ps_obj_t	*objs;		/* Objects in stack */
} _cups_ps_stack_t;


/*
 * Local functions...
 */

static int		cleartomark_stack(_cups_ps_stack_t *st);
static int		copy_stack(_cups_ps_stack_t *st, int count);
static void		delete_stack(_cups_ps_stack_t *st);
static void		error_object(_cups_ps_obj_t *obj);
static void		error_stack(_cups_ps_stack_t *st, const char *title);
static _cups_ps_obj_t	*index_stack(_cups_ps_stack_t *st, int n);
static _cups_ps_stack_t	*new_stack(void);
static _cups_ps_obj_t	*pop_stack(_cups_ps_stack_t *st);
static _cups_ps_obj_t	*push_stack(_cups_ps_stack_t *st,
			            _cups_ps_obj_t *obj);
static int		roll_stack(_cups_ps_stack_t *st, int c, int s);
static _cups_ps_obj_t	*scan_ps(_cups_ps_stack_t *st, char **ptr);
static int		setpagedevice(_cups_ps_stack_t *st,
			                cups_page_header2_t *h,
			                int *preferred_bits);
#ifdef DEBUG
static void		DEBUG_object(_cups_ps_obj_t *obj);
static void		DEBUG_stack(_cups_ps_stack_t *st);
#endif /* DEBUG */


/*
 * 'cupsRasterInterpretPPD()' - Interpret PPD commands to create a page header.
 *
 * This function is used by raster image processing (RIP) filters like
 * cgpdftoraster and imagetoraster when writing CUPS raster data for a page.
 * It is not used by raster printer driver filters which only read CUPS
 * raster data.
 *
 *
 * @code cupsRasterInterpretPPD@ does not mark the options in the PPD using
 * the "num_options" and "options" arguments.  Instead, mark the options with
 * @code cupsMarkOptions@ and @code ppdMarkOption@ prior to calling it -
 * this allows for per-page options without manipulating the options array.
 *
 * The "func" argument specifies an optional callback function that is
 * called prior to the computation of the final raster data.  The function
 * can make changes to the @link cups_page_header2_t@ data as needed to use a
 * supported raster format and then returns 0 on success and -1 if the
 * requested attributes cannot be supported.
 *
 *
 * @code cupsRasterInterpretPPD@ supports a subset of the PostScript language.
 * Currently only the @code [@, @code ]@, @code <<@, @code >>@, @code {@,
 * @code }@, @code cleartomark@, @code copy@, @code dup@, @code index@,
 * @code pop@, @code roll@, @code setpagedevice@, and @code stopped@ operators
 * are supported.
 *
 * @since CUPS 1.2/Mac OS X 10.5@
 */

int					/* O - 0 on success, -1 on failure */
cupsRasterInterpretPPD(
    cups_page_header2_t *h,		/* O - Page header to create */
    ppd_file_t          *ppd,		/* I - PPD file */
    int                 num_options,	/* I - Number of options */
    cups_option_t       *options,	/* I - Options */
    cups_interpret_cb_t func)		/* I - Optional page header callback (@code NULL@ for none) */
{
  int		status;			/* Cummulative status */
  char		*code;			/* Code to run */
  const char	*val;			/* Option value */
  ppd_size_t	*size;			/* Current size */
  float		left,			/* Left position */
		bottom,			/* Bottom position */
		right,			/* Right position */
		top;			/* Top position */
  int		preferred_bits;		/* Preferred bits per color */


 /*
  * Range check input...
  */

  _cupsRasterClearError();

  if (!h)
  {
    _cupsRasterAddError("Page header cannot be NULL!\n");
    return (-1);
  }

 /*
  * Reset the page header to the defaults...
  */

  memset(h, 0, sizeof(cups_page_header2_t));

  h->NumCopies                   = 1;
  h->PageSize[0]                 = 612;
  h->PageSize[1]                 = 792;
  h->HWResolution[0]             = 100;
  h->HWResolution[1]             = 100;
  h->cupsBitsPerColor            =  1;
  h->cupsColorOrder              = CUPS_ORDER_CHUNKED;
  h->cupsColorSpace              = CUPS_CSPACE_K;
  h->cupsBorderlessScalingFactor = 1.0f;
  h->cupsPageSize[0]             = 612.0f;
  h->cupsPageSize[1]             = 792.0f;
  h->cupsImagingBBox[0]          = 0.0f;
  h->cupsImagingBBox[1]          = 0.0f;
  h->cupsImagingBBox[2]          = 612.0f;
  h->cupsImagingBBox[3]          = 792.0f;

  strcpy(h->cupsPageSizeName, "Letter");

#ifdef __APPLE__
 /*
  * cupsInteger0 is also used for the total page count on Mac OS X; set an
  * uncommon default value so we can tell if the driver is using cupsInteger0.
  */

  h->cupsInteger[0] = 0x80000000;
#endif /* __APPLE__ */

 /*
  * Apply patches and options to the page header...
  */

  status         = 0;
  preferred_bits = 0;

  if (ppd)
  {
   /*
    * Apply any patch code (used to override the defaults...)
    */

    if (ppd->patches)
      status |= _cupsRasterExecPS(h, &preferred_bits, ppd->patches);

   /*
    * Then apply printer options in the proper order...
    */

    if ((code = ppdEmitString(ppd, PPD_ORDER_DOCUMENT, 0.0)) != NULL)
    {
      status |= _cupsRasterExecPS(h, &preferred_bits, code);
      free(code);
    }

    if ((code = ppdEmitString(ppd, PPD_ORDER_ANY, 0.0)) != NULL)
    {
      status |= _cupsRasterExecPS(h, &preferred_bits, code);
      free(code);
    }

    if ((code = ppdEmitString(ppd, PPD_ORDER_PROLOG, 0.0)) != NULL)
    {
      status |= _cupsRasterExecPS(h, &preferred_bits, code);
      free(code);
    }

    if ((code = ppdEmitString(ppd, PPD_ORDER_PAGE, 0.0)) != NULL)
    {
      status |= _cupsRasterExecPS(h, &preferred_bits, code);
      free(code);
    }
  }

 /*
  * Allow option override for page scaling...
  */

  if ((val = cupsGetOption("cupsBorderlessScalingFactor", num_options,
                           options)) != NULL)
  {
    double sc = atof(val);		/* Scale factor */

    if (sc >= 0.1 && sc <= 2.0)
      h->cupsBorderlessScalingFactor = (float)sc;
  }

 /*
  * Get the margins for the current size...
  */

  if ((size = ppdPageSize(ppd, NULL)) != NULL)
  {
   /*
    * Use the margins from the PPD file...
    */

    left   = size->left;
    bottom = size->bottom;
    right  = size->right;
    top    = size->top;

    strlcpy(h->cupsPageSizeName, size->name, sizeof(h->cupsPageSizeName));

    h->cupsPageSize[0] = size->width;
    h->cupsPageSize[1] = size->length;
  }
  else
  {
   /*
    * Use the default margins...
    */

    left   = 0.0f;
    bottom = 0.0f;
    right  = 612.0f;
    top    = 792.0f;
  }

  h->PageSize[0]           = (unsigned)(h->cupsPageSize[0] *
                                        h->cupsBorderlessScalingFactor);
  h->PageSize[1]           = (unsigned)(h->cupsPageSize[1] *
                                        h->cupsBorderlessScalingFactor);
  h->Margins[0]            = (unsigned)(left *
                                        h->cupsBorderlessScalingFactor);
  h->Margins[1]            = (unsigned)(bottom *
                                        h->cupsBorderlessScalingFactor);
  h->ImagingBoundingBox[0] = (unsigned)(left *
                                        h->cupsBorderlessScalingFactor);
  h->ImagingBoundingBox[1] = (unsigned)(bottom *
                                        h->cupsBorderlessScalingFactor);
  h->ImagingBoundingBox[2] = (unsigned)(right *
                                        h->cupsBorderlessScalingFactor);
  h->ImagingBoundingBox[3] = (unsigned)(top *
                                        h->cupsBorderlessScalingFactor);
  h->cupsImagingBBox[0]    = (float)left;
  h->cupsImagingBBox[1]    = (float)bottom;
  h->cupsImagingBBox[2]    = (float)right;
  h->cupsImagingBBox[3]    = (float)top;

 /*
  * Use the callback to validate the page header...
  */

  if (func && (*func)(h, preferred_bits))
  {
    _cupsRasterAddError("Page header callback returned error.\n");
    return (-1);
  }

 /*
  * Check parameters...
  */

  if (!h->HWResolution[0] || !h->HWResolution[1] ||
      !h->PageSize[0] || !h->PageSize[1] ||
      (h->cupsBitsPerColor != 1 && h->cupsBitsPerColor != 2 &&
       h->cupsBitsPerColor != 4 && h->cupsBitsPerColor != 8 &&
       h->cupsBitsPerColor != 16) ||
      h->cupsBorderlessScalingFactor < 0.1 ||
      h->cupsBorderlessScalingFactor > 2.0)
  {
    _cupsRasterAddError("Page header uses unsupported values.\n");
    return (-1);
  }

 /*
  * Compute the bitmap parameters...
  */

  h->cupsWidth  = (int)((right - left) * h->cupsBorderlessScalingFactor *
                        h->HWResolution[0] / 72.0f + 0.5f);
  h->cupsHeight = (int)((top - bottom) * h->cupsBorderlessScalingFactor *
                        h->HWResolution[1] / 72.0f + 0.5f);

  switch (h->cupsColorSpace)
  {
    case CUPS_CSPACE_W :
    case CUPS_CSPACE_K :
    case CUPS_CSPACE_WHITE :
    case CUPS_CSPACE_GOLD :
    case CUPS_CSPACE_SILVER :
    case CUPS_CSPACE_SW :
        h->cupsNumColors    = 1;
        h->cupsBitsPerPixel = h->cupsBitsPerColor;
	break;

    default :
       /*
        * Ensure that colorimetric colorspaces use at least 8 bits per
	* component...
	*/

        if (h->cupsColorSpace >= CUPS_CSPACE_CIEXYZ &&
	    h->cupsBitsPerColor < 8)
	  h->cupsBitsPerColor = 8;

       /*
        * Figure out the number of bits per pixel...
	*/

	if (h->cupsColorOrder == CUPS_ORDER_CHUNKED)
	{
	  if (h->cupsBitsPerColor >= 8)
            h->cupsBitsPerPixel = h->cupsBitsPerColor * 3;
	  else
            h->cupsBitsPerPixel = h->cupsBitsPerColor * 4;
	}
	else
	  h->cupsBitsPerPixel = h->cupsBitsPerColor;

        h->cupsNumColors = 3;
	break;

    case CUPS_CSPACE_KCMYcm :
	if (h->cupsBitsPerColor == 1)
	{
	  if (h->cupsColorOrder == CUPS_ORDER_CHUNKED)
	    h->cupsBitsPerPixel = 8;
	  else
	    h->cupsBitsPerPixel = 1;

          h->cupsNumColors = 6;
          break;
	}

       /*
	* Fall through to CMYK code...
	*/

    case CUPS_CSPACE_RGBA :
    case CUPS_CSPACE_RGBW :
    case CUPS_CSPACE_CMYK :
    case CUPS_CSPACE_YMCK :
    case CUPS_CSPACE_KCMY :
    case CUPS_CSPACE_GMCK :
    case CUPS_CSPACE_GMCS :
	if (h->cupsColorOrder == CUPS_ORDER_CHUNKED)
          h->cupsBitsPerPixel = h->cupsBitsPerColor * 4;
	else
	  h->cupsBitsPerPixel = h->cupsBitsPerColor;

        h->cupsNumColors = 4;
	break;

    case CUPS_CSPACE_DEVICE1 :
    case CUPS_CSPACE_DEVICE2 :
    case CUPS_CSPACE_DEVICE3 :
    case CUPS_CSPACE_DEVICE4 :
    case CUPS_CSPACE_DEVICE5 :
    case CUPS_CSPACE_DEVICE6 :
    case CUPS_CSPACE_DEVICE7 :
    case CUPS_CSPACE_DEVICE8 :
    case CUPS_CSPACE_DEVICE9 :
    case CUPS_CSPACE_DEVICEA :
    case CUPS_CSPACE_DEVICEB :
    case CUPS_CSPACE_DEVICEC :
    case CUPS_CSPACE_DEVICED :
    case CUPS_CSPACE_DEVICEE :
    case CUPS_CSPACE_DEVICEF :
        h->cupsNumColors = h->cupsColorSpace - CUPS_CSPACE_DEVICE1 + 1;

        if (h->cupsColorOrder == CUPS_ORDER_CHUNKED)
          h->cupsBitsPerPixel = h->cupsBitsPerColor * h->cupsNumColors;
	else
	  h->cupsBitsPerPixel = h->cupsBitsPerColor;
	break;
  }

  h->cupsBytesPerLine = (h->cupsBitsPerPixel * h->cupsWidth + 7) / 8;

  if (h->cupsColorOrder == CUPS_ORDER_BANDED)
    h->cupsBytesPerLine *= h->cupsNumColors;

  return (status);
}


/*
 * '_cupsRasterExecPS()' - Execute PostScript code to initialize a page header.
 */

int					/* O - 0 on success, -1 on error */
_cupsRasterExecPS(
    cups_page_header2_t *h,		/* O - Page header */
    int                 *preferred_bits,/* O - Preferred bits per color */
    const char          *code)		/* I - PS code to execute */
{
  _cups_ps_stack_t	*st;		/* PostScript value stack */
  _cups_ps_obj_t	*obj;		/* Object from top of stack */
  char			*codecopy,	/* Copy of code */
			*codeptr;	/* Pointer into copy of code */


  DEBUG_printf(("_cupsRasterExecPS(h=%p, preferred_bits=%p, code=\"%s\")\n",
                h, preferred_bits, code ? code : "(null)"));

 /*
  * Copy the PostScript code and create a stack...
  */

  if ((codecopy = strdup(code)) == NULL)
  {
    _cupsRasterAddError("Unable to duplicate code string.\n");
    return (-1);
  }

  if ((st = new_stack()) == NULL)
  {
    _cupsRasterAddError("Unable to create stack.\n");
    free(codecopy);
    return (-1);
  }

 /*
  * Parse the PS string until we run out of data...
  */

  codeptr = codecopy;

  while ((obj = scan_ps(st, &codeptr)) != NULL)
  {
#ifdef DEBUG
    DEBUG_printf(("_cupsRasterExecPS: Stack (%d objects)\n", st->num_objs));
    DEBUG_object(obj);
#endif /* DEBUG */

    switch (obj->type)
    {
      default :
          /* Do nothing for regular values */
	  break;

      case CUPS_PS_CLEARTOMARK :
          pop_stack(st);

	  if (cleartomark_stack(st))
	    _cupsRasterAddError("cleartomark: Stack underflow!\n");

#ifdef DEBUG
          DEBUG_puts("    dup: ");
	  DEBUG_stack(st);
#endif /* DEBUG */
          break;

      case CUPS_PS_COPY :
          pop_stack(st);
	  if ((obj = pop_stack(st)) != NULL)
	  {
	    copy_stack(st, (int)obj->value.number);

#ifdef DEBUG
            DEBUG_puts("_cupsRasterExecPS: copy");
	    DEBUG_stack(st);
#endif /* DEBUG */
          }
          break;

      case CUPS_PS_DUP :
          pop_stack(st);
	  copy_stack(st, 1);

#ifdef DEBUG
          DEBUG_puts("_cupsRasterExecPS: dup");
	  DEBUG_stack(st);
#endif /* DEBUG */
          break;

      case CUPS_PS_INDEX :
          pop_stack(st);
	  if ((obj = pop_stack(st)) != NULL)
	  {
	    index_stack(st, (int)obj->value.number);

#ifdef DEBUG
            DEBUG_puts("_cupsRasterExecPS: index");
	    DEBUG_stack(st);
#endif /* DEBUG */
          }
          break;

      case CUPS_PS_POP :
          pop_stack(st);
          pop_stack(st);

#ifdef DEBUG
          DEBUG_puts("_cupsRasterExecPS: pop");
	  DEBUG_stack(st);
#endif /* DEBUG */
          break;

      case CUPS_PS_ROLL :
          pop_stack(st);
	  if ((obj = pop_stack(st)) != NULL)
	  {
            int		c;		/* Count */


            c = (int)obj->value.number;

	    if ((obj = pop_stack(st)) != NULL)
	    {
	      roll_stack(st, (int)obj->value.number, c);

#ifdef DEBUG
              DEBUG_puts("_cupsRasterExecPS: roll");
	      DEBUG_stack(st);
#endif /* DEBUG */
            }
	  }
          break;

      case CUPS_PS_SETPAGEDEVICE :
          pop_stack(st);
	  setpagedevice(st, h, preferred_bits);

#ifdef DEBUG
          DEBUG_puts("_cupsRasterExecPS: setpagedevice");
	  DEBUG_stack(st);
#endif /* DEBUG */
          break;

      case CUPS_PS_START_PROC :
      case CUPS_PS_END_PROC :
      case CUPS_PS_STOPPED :
          pop_stack(st);
	  break;

      case CUPS_PS_OTHER :
          _cupsRasterAddError("Unknown operator \"%s\"!\n", obj->value.other);
          DEBUG_printf(("_cupsRasterExecPS: Unknown operator \"%s\"!\n",
	                obj->value.other));
          break;
    }

    if (obj && obj->type == CUPS_PS_OTHER)
      break;
  }

 /*
  * Cleanup...
  */

  free(codecopy);

  if (st->num_objs > 0)
  {
    error_stack(st, "Stack not empty:");

#ifdef DEBUG
    DEBUG_puts("_cupsRasterExecPS: Stack not empty:");
    DEBUG_stack(st);
#endif /* DEBUG */

    delete_stack(st);

    return (-1);
  }

  delete_stack(st);

 /*
  * Return success...
  */

  return (0);
}


/*
 * 'cleartomark_stack()' - Clear to the last mark ([) on the stack.
 */

static int				/* O - 0 on success, -1 on error */
cleartomark_stack(_cups_ps_stack_t *st)	/* I - Stack */
{
  _cups_ps_obj_t	*obj;		/* Current object on stack */


  while ((obj = pop_stack(st)) != NULL)
    if (obj->type == CUPS_PS_START_ARRAY)
      break;

  return (obj ? 0 : -1);
}


/*
 * 'copy_stack()' - Copy the top N stack objects.
 */

static int				/* O - 0 on success, -1 on error */
copy_stack(_cups_ps_stack_t *st,	/* I - Stack */
           int              c)		/* I - Number of objects to copy */
{
  int	n;				/* Index */


  if (c < 0)
    return (-1);
  else if (c == 0)
    return (0);

  if ((n = st->num_objs - c) < 0)
    return (-1);

  while (c > 0)
  {
    if (!push_stack(st, st->objs + n))
      return (-1);

    n ++;
    c --;
  }

  return (0);
}


/*
 * 'delete_stack()' - Free memory used by a stack.
 */

static void
delete_stack(_cups_ps_stack_t *st)	/* I - Stack */
{
  free(st->objs);
  free(st);
}


/*
 * 'error_object()' - Add an object's value to the current error message.
 */

static void
error_object(_cups_ps_obj_t *obj)	/* I - Object to add */
{
  switch (obj->type)
  {
    case CUPS_PS_NAME :
	_cupsRasterAddError(" /%s", obj->value.name);
	break;

    case CUPS_PS_NUMBER :
	_cupsRasterAddError(" %g", obj->value.number);
	break;

    case CUPS_PS_STRING :
	_cupsRasterAddError(" (%s)", obj->value.string);
	break;

    case CUPS_PS_BOOLEAN :
	if (obj->value.boolean)
	  _cupsRasterAddError(" true");
	else
	  _cupsRasterAddError(" false");
	break;

    case CUPS_PS_NULL :
	_cupsRasterAddError(" null");
	break;

    case CUPS_PS_START_ARRAY :
	_cupsRasterAddError(" [");
	break;

    case CUPS_PS_END_ARRAY :
	_cupsRasterAddError(" ]");
	break;

    case CUPS_PS_START_DICT :
	_cupsRasterAddError(" <<");
	break;

    case CUPS_PS_END_DICT :
	_cupsRasterAddError(" >>");
	break;

    case CUPS_PS_START_PROC :
	_cupsRasterAddError(" {");
	break;

    case CUPS_PS_END_PROC :
	_cupsRasterAddError(" }");
	break;

    case CUPS_PS_COPY :
	_cupsRasterAddError(" --copy--");
        break;

    case CUPS_PS_CLEARTOMARK :
	_cupsRasterAddError(" --cleartomark--");
        break;

    case CUPS_PS_DUP :
	_cupsRasterAddError(" --dup--");
        break;

    case CUPS_PS_INDEX :
	_cupsRasterAddError(" --index--");
        break;

    case CUPS_PS_POP :
	_cupsRasterAddError(" --pop--");
        break;

    case CUPS_PS_ROLL :
	_cupsRasterAddError(" --roll--");
        break;

    case CUPS_PS_SETPAGEDEVICE :
	_cupsRasterAddError(" --setpagedevice--");
        break;

    case CUPS_PS_STOPPED :
	_cupsRasterAddError(" --stopped--");
        break;

    case CUPS_PS_OTHER :
	_cupsRasterAddError(" --%s--", obj->value.other);
	break;
  }
}


/*
 * 'error_stack()' - Add a stack to the current error message...
 */

static void
error_stack(_cups_ps_stack_t *st,	/* I - Stack */
            const char       *title)	/* I - Title string */
{
  int			c;		/* Looping var */
  _cups_ps_obj_t	*obj;		/* Current object on stack */


  _cupsRasterAddError(title);

  for (obj = st->objs, c = st->num_objs; c > 0; c --, obj ++)
    error_object(obj);

  _cupsRasterAddError("\n");
}


/*
 * 'index_stack()' - Copy the Nth value on the stack.
 */

static _cups_ps_obj_t	*		/* O - New object */
index_stack(_cups_ps_stack_t *st,	/* I - Stack */
            int              n)		/* I - Object index */
{
  if (n < 0 || (n = st->num_objs - n - 1) < 0)
    return (NULL);

  return (push_stack(st, st->objs + n));
}


/*
 * 'new_stack()' - Create a new stack.
 */

static _cups_ps_stack_t	*		/* O - New stack */
new_stack(void)
{
  _cups_ps_stack_t	*st;		/* New stack */


  if ((st = calloc(1, sizeof(_cups_ps_stack_t))) == NULL)
    return (NULL);

  st->alloc_objs = 32;

  if ((st->objs = calloc(32, sizeof(_cups_ps_obj_t))) == NULL)
  {
    free(st);
    return (NULL);
  }
  else
    return (st);
}


/*
 * 'pop_stock()' - Pop the top object off the stack.
 */

static _cups_ps_obj_t	*		/* O - Object */
pop_stack(_cups_ps_stack_t *st)		/* I - Stack */
{
  if (st->num_objs > 0)
  {
    st->num_objs --;

    return (st->objs + st->num_objs);
  }
  else
    return (NULL);
}


/*
 * 'push_stack()' - Push an object on the stack.
 */

static _cups_ps_obj_t	*		/* O - New object */
push_stack(_cups_ps_stack_t *st,	/* I - Stack */
           _cups_ps_obj_t   *obj)	/* I - Object */
{
  _cups_ps_obj_t	*temp;		/* New object */


  if (st->num_objs >= st->alloc_objs)
  {


    st->alloc_objs += 32;

    if ((temp = realloc(st->objs, st->alloc_objs *
                                  sizeof(_cups_ps_obj_t))) == NULL)
      return (NULL);

    st->objs = temp;
    memset(temp + st->num_objs, 0, 32 * sizeof(_cups_ps_obj_t));
  }

  temp = st->objs + st->num_objs;
  st->num_objs ++;

  memcpy(temp, obj, sizeof(_cups_ps_obj_t));

  return (temp);
}


/*
 * 'roll_stack()' - Rotate stack objects.
 */

static int				/* O - 0 on success, -1 on error */
roll_stack(_cups_ps_stack_t *st,	/* I - Stack */
	   int              c,		/* I - Number of objects */
           int              s)		/* I - Amount to shift */
{
  _cups_ps_obj_t	*temp;		/* Temporary array of objects */
  int			n;		/* Index into array */


  DEBUG_printf(("    roll_stack(st=%p, s=%d, c=%d)\n", st, s, c));

 /*
  * Range check input...
  */

  if (c < 0)
    return (-1);
  else if (c == 0)
    return (0);

  if ((n = st->num_objs - c) < 0)
    return (-1);

  s %= c;

  if (s == 0)
    return (0);

 /*
  * Copy N objects and move things around...
  */

  if (s < 0)
  {
   /*
    * Shift down...
    */

    s = -s;

    if ((temp = calloc(s, sizeof(_cups_ps_obj_t))) == NULL)
      return (-1);

    memcpy(temp, st->objs + n, s * sizeof(_cups_ps_obj_t));
    memmove(st->objs + n, st->objs + n + s, (c - s) * sizeof(_cups_ps_obj_t));
    memcpy(st->objs + n + c - s, temp, s * sizeof(_cups_ps_obj_t));
  }
  else
  {
   /*
    * Shift up...
    */

    if ((temp = calloc(s, sizeof(_cups_ps_obj_t))) == NULL)
      return (-1);

    memcpy(temp, st->objs + n + c - s, s * sizeof(_cups_ps_obj_t));
    memmove(st->objs + n + s, st->objs + n,
            (c - s) * sizeof(_cups_ps_obj_t));
    memcpy(st->objs + n, temp, s * sizeof(_cups_ps_obj_t));
  }

  free(temp);

  return (0);
}


/*
 * 'scan_ps()' - Scan a string for the next PS object.
 */

static _cups_ps_obj_t	*		/* O  - New object or NULL on EOF */
scan_ps(_cups_ps_stack_t *st,		/* I  - Stack */
        char             **ptr)		/* IO - String pointer */
{
  _cups_ps_obj_t	obj;		/* Current object */
  char			*start,		/* Start of object */
			*cur,		/* Current position */
			*valptr,	/* Pointer into value string */
			*valend;	/* End of value string */
  int			parens;		/* Parenthesis nesting level */


 /*
  * Skip leading whitespace...
  */

  for (cur = *ptr; *cur; cur ++)
  {
    if (*cur == '%')
    {
     /*
      * Comment, skip to end of line...
      */

      for (cur ++; *cur && *cur != '\n' && *cur != '\r'; cur ++);

      if (!*cur)
        cur --;
    }
    else if (!isspace(*cur & 255))
      break;
  }

  if (!*cur)
  {
    *ptr = NULL;

    return (NULL);
  }

 /*
  * See what we have...
  */

  memset(&obj, 0, sizeof(obj));

  switch (*cur)
  {
    case '(' :				/* (string) */
        obj.type = CUPS_PS_STRING;
	start    = cur;

	for (cur ++, parens = 1, valptr = obj.value.string,
	         valend = obj.value.string + sizeof(obj.value.string) - 1;
             *cur;
	     cur ++)
	{
	  if (*cur == ')' && parens == 1)
	    break;

          if (*cur == '(')
	    parens ++;
	  else if (*cur == ')')
	    parens --;

          if (valptr >= valend)
	  {
	    *ptr = start;

	    return (NULL);
	  }

	  if (*cur == '\\')
	  {
	   /*
	    * Decode escaped character...
	    */

	    cur ++;

            if (*cur == 'b')
	      *valptr++ = '\b';
	    else if (*cur == 'f')
	      *valptr++ = '\f';
	    else if (*cur == 'n')
	      *valptr++ = '\n';
	    else if (*cur == 'r')
	      *valptr++ = '\r';
	    else if (*cur == 't')
	      *valptr++ = '\t';
	    else if (*cur >= '0' && *cur <= '7')
	    {
	      int ch = *cur - '0';

              if (cur[1] >= '0' && cur[1] <= '7')
	      {
	        cur ++;
		ch = (ch << 3) + *cur - '0';
	      }

              if (cur[1] >= '0' && cur[1] <= '7')
	      {
	        cur ++;
		ch = (ch << 3) + *cur - '0';
	      }

	      *valptr++ = ch;
	    }
	    else if (*cur == '\r')
	    {
	      if (cur[1] == '\n')
	        cur ++;
	    }
	    else if (*cur != '\n')
	      *valptr++ = *cur;
	  }
	  else
	    *valptr++ = *cur;
	}

	if (*cur != ')')
	{
	  *ptr = start;

	  return (NULL);
	}

	cur ++;
        break;

    case '[' :				/* Start array */
        obj.type = CUPS_PS_START_ARRAY;
	cur ++;
        break;

    case ']' :				/* End array */
        obj.type = CUPS_PS_END_ARRAY;
	cur ++;
        break;

    case '<' :				/* Start dictionary or hex string */
        if (cur[1] == '<')
	{
	  obj.type = CUPS_PS_START_DICT;
	  cur += 2;
	}
	else
	{
          obj.type = CUPS_PS_STRING;
	  start    = cur;

	  for (cur ++, valptr = obj.value.string,
	           valend = obj.value.string + sizeof(obj.value.string) - 1;
               *cur;
	       cur ++)
	  {
	    int	ch;			/* Current character */



            if (*cur == '>')
	      break;
	    else if (valptr >= valend || !isxdigit(*cur & 255))
	    {
	      *ptr = start;
	      return (NULL);
	    }

            if (*cur >= '0' && *cur <= '9')
	      ch = (*cur - '0') << 4;
	    else
	      ch = (tolower(*cur) - 'a' + 10) << 4;

	    if (isxdigit(cur[1] & 255))
	    {
	      cur ++;

              if (*cur >= '0' && *cur <= '9')
		ch |= *cur - '0';
	      else
		ch |= tolower(*cur) - 'a' + 10;
            }

	    *valptr++ = ch;
          }

          if (*cur != '>')
	  {
	    *ptr = start;
	    return (NULL);
	  }

	  cur ++;
	}
        break;

    case '>' :				/* End dictionary? */
        if (cur[1] == '>')
	{
	  obj.type = CUPS_PS_END_DICT;
	  cur += 2;
	}
	else
	{
	  obj.type           = CUPS_PS_OTHER;
	  obj.value.other[0] = *cur;

	  cur ++;
	}
        break;

    case '{' :				/* Start procedure */
        obj.type = CUPS_PS_START_PROC;
	cur ++;
        break;

    case '}' :				/* End procedure */
        obj.type = CUPS_PS_END_PROC;
	cur ++;
        break;

    case '-' :				/* Possible number */
    case '+' :
        if (!isdigit(cur[1] & 255) && cur[1] != '.')
	{
	  obj.type           = CUPS_PS_OTHER;
	  obj.value.other[0] = *cur;

	  cur ++;
	  break;
	}

    case '0' :				/* Number */
    case '1' :
    case '2' :
    case '3' :
    case '4' :
    case '5' :
    case '6' :
    case '7' :
    case '8' :
    case '9' :
    case '.' :
        obj.type = CUPS_PS_NUMBER;

        start = cur;
	for (cur ++; *cur; cur ++)
	  if (!isdigit(*cur & 255))
	    break;

        if (*cur == '#')
	{
	 /*
	  * Integer with radix...
	  */

          obj.value.number = strtol(cur + 1, &cur, atoi(start));
	  break;
	}
	else if (strchr(".Ee()<>[]{}/%", *cur) || isspace(*cur & 255))
	{
	 /*
	  * Integer or real number...
	  */

	  obj.value.number = _cupsStrScand(start, &cur, localeconv());
          break;
	}
	else
	  cur = start;

    default :				/* Operator/variable name */
        start = cur;

	if (*cur == '/')
	{
	  obj.type = CUPS_PS_NAME;
          valptr   = obj.value.name;
          valend   = obj.value.name + sizeof(obj.value.name) - 1;
	  cur ++;
	}
	else
	{
	  obj.type = CUPS_PS_OTHER;
          valptr   = obj.value.other;
          valend   = obj.value.other + sizeof(obj.value.other) - 1;
	}

	while (*cur)
	{
	  if (strchr("()<>[]{}/%", *cur) || isspace(*cur & 255))
	    break;
	  else if (valptr < valend)
	    *valptr++ = *cur++;
	  else
	  {
	    *ptr = start;
	    return (NULL);
	  }
	}

        if (obj.type == CUPS_PS_OTHER)
	{
          if (!strcmp(obj.value.other, "true"))
	  {
	    obj.type          = CUPS_PS_BOOLEAN;
	    obj.value.boolean = 1;
	  }
	  else if (!strcmp(obj.value.other, "false"))
	  {
	    obj.type          = CUPS_PS_BOOLEAN;
	    obj.value.boolean = 0;
	  }
	  else if (!strcmp(obj.value.other, "null"))
	    obj.type = CUPS_PS_NULL;
	  else if (!strcmp(obj.value.other, "cleartomark"))
	    obj.type = CUPS_PS_CLEARTOMARK;
	  else if (!strcmp(obj.value.other, "copy"))
	    obj.type = CUPS_PS_COPY;
	  else if (!strcmp(obj.value.other, "dup"))
	    obj.type = CUPS_PS_DUP;
	  else if (!strcmp(obj.value.other, "index"))
	    obj.type = CUPS_PS_INDEX;
	  else if (!strcmp(obj.value.other, "pop"))
	    obj.type = CUPS_PS_POP;
	  else if (!strcmp(obj.value.other, "roll"))
	    obj.type = CUPS_PS_ROLL;
	  else if (!strcmp(obj.value.other, "setpagedevice"))
	    obj.type = CUPS_PS_SETPAGEDEVICE;
	  else if (!strcmp(obj.value.other, "stopped"))
	    obj.type = CUPS_PS_STOPPED;
	}
	break;
  }

 /*
  * Save the current position in the string and return the new object...
  */

  *ptr = cur;

  return (push_stack(st, &obj));
}


/*
 * 'setpagedevice()' - Simulate the PostScript setpagedevice operator.
 */

static int				/* O - 0 on success, -1 on error */
setpagedevice(
    _cups_ps_stack_t    *st,		/* I - Stack */
    cups_page_header2_t *h,		/* O - Page header */
    int                 *preferred_bits)/* O - Preferred bits per color */
{
  int			i;		/* Index into array */
  _cups_ps_obj_t	*obj,		/* Current object */
			*end;		/* End of dictionary */
  const char		*name;		/* Attribute name */


 /*
  * Make sure we have a dictionary on the stack...
  */

  if (st->num_objs == 0)
    return (-1);

  obj = end = st->objs + st->num_objs - 1;

  if (obj->type != CUPS_PS_END_DICT)
    return (-1);

  obj --;

  while (obj > st->objs)
  {
    if (obj->type == CUPS_PS_START_DICT)
      break;

    obj --;
  }

  if (obj < st->objs)
    return (-1);

 /*
  * Found the start of the dictionary, empty the stack to this point...
  */

  st->num_objs = obj - st->objs;

 /*
  * Now pull /name and value pairs from the dictionary...
  */

  DEBUG_puts("setpagedevice: Dictionary:");

  for (obj ++; obj < end; obj ++)
  {
   /*
    * Grab the name...
    */

    if (obj->type != CUPS_PS_NAME)
      return (-1);

    name = obj->value.name;
    obj ++;

#ifdef DEBUG
    DEBUG_printf(("setpagedevice: /%s ", name));
    DEBUG_object(obj);
#endif /* DEBUG */

   /*
    * Then grab the value...
    */

    if (!strcmp(name, "MediaClass") && obj->type == CUPS_PS_STRING)
      strlcpy(h->MediaClass, obj->value.string, sizeof(h->MediaClass));
    else if (!strcmp(name, "MediaColor") && obj->type == CUPS_PS_STRING)
      strlcpy(h->MediaColor, obj->value.string, sizeof(h->MediaColor));
    else if (!strcmp(name, "MediaType") && obj->type == CUPS_PS_STRING)
      strlcpy(h->MediaType, obj->value.string, sizeof(h->MediaType));
    else if (!strcmp(name, "OutputType") && obj->type == CUPS_PS_STRING)
      strlcpy(h->OutputType, obj->value.string, sizeof(h->OutputType));
    else if (!strcmp(name, "AdvanceDistance") && obj->type == CUPS_PS_NUMBER)
      h->AdvanceDistance = (unsigned)obj->value.number;
    else if (!strcmp(name, "AdvanceMedia") && obj->type == CUPS_PS_NUMBER)
      h->AdvanceMedia = (unsigned)obj->value.number;
    else if (!strcmp(name, "Collate") && obj->type == CUPS_PS_BOOLEAN)
      h->Collate = (unsigned)obj->value.boolean;
    else if (!strcmp(name, "CutMedia") && obj->type == CUPS_PS_NUMBER)
      h->CutMedia = (cups_cut_t)(unsigned)obj->value.number;
    else if (!strcmp(name, "Duplex") && obj->type == CUPS_PS_BOOLEAN)
      h->Duplex = (unsigned)obj->value.boolean;
    else if (!strcmp(name, "HWResolution") && obj->type == CUPS_PS_START_ARRAY)
    {
      if (obj[1].type == CUPS_PS_NUMBER && obj[2].type == CUPS_PS_NUMBER &&
          obj[3].type == CUPS_PS_END_ARRAY)
      {
        h->HWResolution[0] = (unsigned)obj[1].value.number;
	h->HWResolution[1] = (unsigned)obj[2].value.number;
	obj += 3;
      }
      else
        return (-1);
    }
    else if (!strcmp(name, "InsertSheet") && obj->type == CUPS_PS_BOOLEAN)
      h->InsertSheet = (unsigned)obj->value.boolean;
    else if (!strcmp(name, "Jog") && obj->type == CUPS_PS_NUMBER)
      h->Jog = (unsigned)obj->value.number;
    else if (!strcmp(name, "LeadingEdge") && obj->type == CUPS_PS_NUMBER)
      h->LeadingEdge = (unsigned)obj->value.number;
    else if (!strcmp(name, "ManualFeed") && obj->type == CUPS_PS_BOOLEAN)
      h->ManualFeed = (unsigned)obj->value.boolean;
    else if ((!strcmp(name, "cupsMediaPosition") ||
              !strcmp(name, "MediaPosition")) && obj->type == CUPS_PS_NUMBER)
    {
     /*
      * cupsMediaPosition is supported for backwards compatibility only.
      * We added it back in the Ghostscript 5.50 days to work around a
      * bug in Ghostscript WRT handling of MediaPosition and setpagedevice.
      *
      * All new development should set MediaPosition...
      */

      h->MediaPosition = (unsigned)obj->value.number;
    }
    else if (!strcmp(name, "MediaWeight") && obj->type == CUPS_PS_NUMBER)
      h->MediaWeight = (unsigned)obj->value.number;
    else if (!strcmp(name, "MirrorPrint") && obj->type == CUPS_PS_BOOLEAN)
      h->MirrorPrint = (unsigned)obj->value.boolean;
    else if (!strcmp(name, "NegativePrint") && obj->type == CUPS_PS_BOOLEAN)
      h->NegativePrint = (unsigned)obj->value.boolean;
    else if (!strcmp(name, "NumCopies") && obj->type == CUPS_PS_NUMBER)
      h->NumCopies = (unsigned)obj->value.number;
    else if (!strcmp(name, "Orientation") && obj->type == CUPS_PS_NUMBER)
      h->Orientation = (unsigned)obj->value.number;
    else if (!strcmp(name, "OutputFaceUp") && obj->type == CUPS_PS_BOOLEAN)
      h->OutputFaceUp = (unsigned)obj->value.boolean;
    else if (!strcmp(name, "PageSize") && obj->type == CUPS_PS_START_ARRAY)
    {
      if (obj[1].type == CUPS_PS_NUMBER && obj[2].type == CUPS_PS_NUMBER &&
          obj[3].type == CUPS_PS_END_ARRAY)
      {
        h->cupsPageSize[0] = (float)obj[1].value.number;
	h->cupsPageSize[1] = (float)obj[2].value.number;

        h->PageSize[0] = (unsigned)obj[1].value.number;
	h->PageSize[1] = (unsigned)obj[2].value.number;

	obj += 3;
      }
      else
        return (-1);
    }
    else if (!strcmp(name, "Separations") && obj->type == CUPS_PS_BOOLEAN)
      h->Separations = (unsigned)obj->value.boolean;
    else if (!strcmp(name, "TraySwitch") && obj->type == CUPS_PS_BOOLEAN)
      h->TraySwitch = (unsigned)obj->value.boolean;
    else if (!strcmp(name, "Tumble") && obj->type == CUPS_PS_BOOLEAN)
      h->Tumble = (unsigned)obj->value.boolean;
    else if (!strcmp(name, "cupsMediaType") && obj->type == CUPS_PS_NUMBER)
      h->cupsMediaType = (unsigned)obj->value.number;
    else if (!strcmp(name, "cupsBitsPerColor") && obj->type == CUPS_PS_NUMBER)
      h->cupsBitsPerColor = (unsigned)obj->value.number;
    else if (!strcmp(name, "cupsPreferredBitsPerColor") &&
             obj->type == CUPS_PS_NUMBER)
      *preferred_bits = (int)obj->value.number;
    else if (!strcmp(name, "cupsColorOrder") && obj->type == CUPS_PS_NUMBER)
      h->cupsColorOrder = (cups_order_t)(unsigned)obj->value.number;
    else if (!strcmp(name, "cupsColorSpace") && obj->type == CUPS_PS_NUMBER)
      h->cupsColorSpace = (cups_cspace_t)(unsigned)obj->value.number;
    else if (!strcmp(name, "cupsCompression") && obj->type == CUPS_PS_NUMBER)
      h->cupsCompression = (unsigned)obj->value.number;
    else if (!strcmp(name, "cupsRowCount") && obj->type == CUPS_PS_NUMBER)
      h->cupsRowCount = (unsigned)obj->value.number;
    else if (!strcmp(name, "cupsRowFeed") && obj->type == CUPS_PS_NUMBER)
      h->cupsRowFeed = (unsigned)obj->value.number;
    else if (!strcmp(name, "cupsRowStep") && obj->type == CUPS_PS_NUMBER)
      h->cupsRowStep = (unsigned)obj->value.number;
    else if (!strcmp(name, "cupsBorderlessScalingFactor") &&
             obj->type == CUPS_PS_NUMBER)
      h->cupsBorderlessScalingFactor = (float)obj->value.number;
    else if (!strncmp(name, "cupsInteger", 11) && obj->type == CUPS_PS_NUMBER)
    {
      if ((i = atoi(name + 11)) < 0 || i > 15)
        return (-1);

      h->cupsInteger[i] = (unsigned)obj->value.number;
    }
    else if (!strncmp(name, "cupsReal", 8) && obj->type == CUPS_PS_NUMBER)
    {
      if ((i = atoi(name + 8)) < 0 || i > 15)
        return (-1);

      h->cupsReal[i] = (float)obj->value.number;
    }
    else if (!strncmp(name, "cupsString", 10) && obj->type == CUPS_PS_STRING)
    {
      if ((i = atoi(name + 10)) < 0 || i > 15)
        return (-1);

      strlcpy(h->cupsString[i], obj->value.string, sizeof(h->cupsString[i]));
    }
    else if (!strcmp(name, "cupsMarkerType") && obj->type == CUPS_PS_STRING)
      strlcpy(h->cupsMarkerType, obj->value.string, sizeof(h->cupsMarkerType));
    else if (!strcmp(name, "cupsPageSizeName") && obj->type == CUPS_PS_STRING)
      strlcpy(h->cupsPageSizeName, obj->value.string,
              sizeof(h->cupsPageSizeName));
    else if (!strcmp(name, "cupsRenderingIntent") &&
             obj->type == CUPS_PS_STRING)
      strlcpy(h->cupsRenderingIntent, obj->value.string,
              sizeof(h->cupsRenderingIntent));
    else
    {
     /*
      * Ignore unknown name+value...
      */

      DEBUG_printf(("    Unknown name (\"%s\") or value...\n", name));

      while (obj[1].type != CUPS_PS_NAME && obj < end)
        obj ++;
    }
  }

  return (0);
}


#ifdef DEBUG
/*
 * 'DEBUG_object()' - Print an object's value...
 */

static void
DEBUG_object(_cups_ps_obj_t *obj)	/* I - Object to print */
{
  switch (obj->type)
  {
    case CUPS_PS_NAME :
	DEBUG_printf(("/%s\n", obj->value.name));
	break;

    case CUPS_PS_NUMBER :
	DEBUG_printf(("%g\n", obj->value.number));
	break;

    case CUPS_PS_STRING :
	DEBUG_printf(("(%s)\n", obj->value.string));
	break;

    case CUPS_PS_BOOLEAN :
	if (obj->value.boolean)
	  DEBUG_puts("true");
	else
	  DEBUG_puts("false");
	break;

    case CUPS_PS_NULL :
	DEBUG_puts("null");
	break;

    case CUPS_PS_START_ARRAY :
	DEBUG_puts("[");
	break;

    case CUPS_PS_END_ARRAY :
	DEBUG_puts("]");
	break;

    case CUPS_PS_START_DICT :
	DEBUG_puts("<<");
	break;

    case CUPS_PS_END_DICT :
	DEBUG_puts(">>");
	break;

    case CUPS_PS_START_PROC :
	DEBUG_puts("{");
	break;

    case CUPS_PS_END_PROC :
	DEBUG_puts("}");
	break;

    case CUPS_PS_CLEARTOMARK :
	DEBUG_puts("--cleartomark--");
        break;

    case CUPS_PS_COPY :
	DEBUG_puts("--copy--");
        break;

    case CUPS_PS_DUP :
	DEBUG_puts("--dup--");
        break;

    case CUPS_PS_INDEX :
	DEBUG_puts("--index--");
        break;

    case CUPS_PS_POP :
	DEBUG_puts("--pop--");
        break;

    case CUPS_PS_ROLL :
	DEBUG_puts("--roll--");
        break;

    case CUPS_PS_SETPAGEDEVICE :
	DEBUG_puts("--setpagedevice--");
        break;

    case CUPS_PS_STOPPED :
	DEBUG_puts("--stopped--");
        break;

    case CUPS_PS_OTHER :
	DEBUG_printf(("--%s--\n", obj->value.other));
	break;
  }
}


/*
 * 'DEBUG_stack()' - Print a stack...
 */

static void
DEBUG_stack(_cups_ps_stack_t *st)	/* I - Stack */
{
  int			c;		/* Looping var */
  _cups_ps_obj_t	*obj;		/* Current object on stack */


  for (obj = st->objs, c = st->num_objs; c > 0; c --, obj ++)
    DEBUG_object(obj);
}
#endif /* DEBUG */


/*
 * End of "$Id: interpret.c 7852 2008-08-21 04:19:45Z mike $".
 */