//
// PCLm/Raster-only PDF to Raster filter function for libcupsfilters.
//
// Copyright © 2020 by Vikrant Malik
// Copyright 2024-2026 Uddhav Phatak <uddhavphatak@gmail.com>
//
// Licensed under Apache License v2.0.  See the file "LICENSE" for more
// information.
//

//
// Include necessary headers...
//

#include <cupsfilters/filter.h>
#include <cupsfilters/image.h>
#include <cupsfilters/bitmap.h>
#include <cupsfilters/raster.h>
#include <cupsfilters/ipp.h>
#include <cupsfilters/libcups2-private.h>
#include <cups/raster.h>
#include <cups/cups.h>
#include <errno.h>

#include <pdfio.h>
#include <pdfio-content.h>


#define MAX_BYTES_PER_PIXEL 32

// Structure to hold filter data and state
typedef struct pclmtoraster_data_s
{
  int outformat;		// Output Format
  int numcolors;		// number of Colour components(e.g. 3 for RGB, 4 for CYMK..)
  int rowsize;			// size of a row
  cups_page_header_t header;	// CUPS page header
  char pageSizeRequested[64];	// Requested page size name
  int bi_level;			// flag for printing
  // image swapping
  int swap_image_x;
  int swap_image_y;
  // margin swapping
  int swap_margin_x;
  int swap_margin_y;
  unsigned int nplanes;		// Number of colour planes
  unsigned int nbands;		// Number of colour bands
  unsigned int bytesPerLine;	// bytes per line in output
  char colorspace[32]; 		// Colourspace string(Use fixed-size string)
  int pixel_count;		// Accumulated pixel byte count for bitmap
  unsigned char *bitmap;	// Accumulated image bitmap data
} pclmtoraster_data_t;

//
// 'init_pclmtoraster_data_t()' - initialize the data structure with default values.
//

void 							  // O - void output
init_pclmtoraster_data_t(pclmtoraster_data_t *data)	// I - pclm data to initialize
{
  data->outformat = CF_FILTER_OUT_FORMAT_PWG_RASTER;
  data->numcolors = 0;
  data->rowsize = 0;
  data->bi_level = 0;
  // image swapping
  data->swap_image_x = false;
  data->swap_image_y = false;
  // margin swapping
  data->swap_margin_x = false;
  data->swap_margin_y = false;
  // Note: When CUPS_ORDER_BANDED,
  //   cupsBytesPerLine = bytesPerLine * cupsNumColors
  strncpy(data->colorspace, "\0", sizeof(data->colorspace));
  data->pixel_count = 0;
  data->bitmap = NULL;
}

// function pointer for color space conversion
typedef unsigned char *(*convert_cspace_func)(unsigned char *src,
	       			 	 unsigned char *dst,
					 unsigned int row,
					 unsigned int pixels,
					 pclmtoraster_data_t *data);

// function pointer for line processing (handling planes/bands)
typedef unsigned char *(*convert_line_func)(unsigned char *src,
	       			       unsigned char *dst,
				       unsigned char *buf,
				       unsigned int row,
				       unsigned int plane,
				       pclmtoraster_data_t *data,
				       convert_cspace_func convertcspace);

// grouping the selected conversion functions
typedef struct pclm_conversion_function_s
{
  convert_cspace_func convertcspace;// Function for conversion of colorspaces
  convert_line_func convertline;    // Function tom modify raster data of a line
} pclm_conversion_function_t;


//
// 'parse_opts()' - Parse filter options and initialize headers
//

static int						  // O - 1 if successful
parse_opts(cf_filter_data_t *data,			// I - Job and Print data	
	   cf_filter_out_format_t outformat,		// I - output format
	   pclmtoraster_data_t *pclmtoraster_data)	// I - Raster Data
{
  int			num_options = 0;
  cups_option_t*	options = NULL;
  const char*		t = NULL;
  const char		*val;
  cf_logfunc_t		log = data->logfunc;
  void			*ld = data->logdata;
  cups_page_header_t	*header = &(pclmtoraster_data->header);
  cups_cspace_t         cspace = (cups_cspace_t)(-1);

  pclmtoraster_data->outformat = outformat;

  //
  // CUPS option list
  //

  num_options = cfJoinJobOptionsAndAttrs(data, num_options, &options);

  // Check for media-class option to determine if PWG raster is forced
  t = cupsGetOption("media-class", num_options, options);
  if (t == NULL)
    t = cupsGetOption("MediaClass", num_options, options);
  if (t != NULL)
  {
    if (strcasestr(t, "pwg"))
      pclmtoraster_data->outformat = CF_FILTER_OUT_FORMAT_PWG_RASTER;
  }

  // Prep the raster header on the basis of options
  cfRasterPrepareHeader(header, data, outformat, outformat, 0, &cspace);

  // handle the Duplex and Tumble logic to set swapping flags
  if(header->Duplex)
  {
    int backside;
    // analyze options relevant to Duplex
    // APDuplexRequiresFlippedMargin
    enum
    {
      FM_NO,
      FM_FALSE,
      FM_TRUE
    } flippedMargin = FM_NO;
    
    backside = cfGetBackSideOrientation(data);

    if (backside >= 0)
    {
      flippedMargin = (backside & 16 ? FM_TRUE :
		       (backside & 8 ? FM_FALSE :
			FM_NO));
      backside &= 7;

      // determine transformation requirements on the basis of backside orientation
      if(backside == CF_BACKSIDE_MANUAL_TUMBLE && header->Tumble)
      {
	pclmtoraster_data->swap_image_x = true;
       	pclmtoraster_data->swap_image_y = true;
       	pclmtoraster_data->swap_margin_x = true;
       	pclmtoraster_data->swap_margin_y = true;
       	if (flippedMargin == FM_TRUE)
	  pclmtoraster_data->swap_margin_y = false;
      }
      else if(backside == CF_BACKSIDE_ROTATED && !header->Tumble)
      {
	pclmtoraster_data->swap_image_x = true; 
	pclmtoraster_data->swap_image_y = true;
       	pclmtoraster_data->swap_margin_x = true;
       	pclmtoraster_data->swap_margin_y = true;
       	if (flippedMargin == FM_TRUE)
	  pclmtoraster_data->swap_margin_y = false;      
      }
      else if (backside == CF_BACKSIDE_FLIPPED)
      {
        if (header->Tumble)
        {
	  pclmtoraster_data->swap_image_x = true;
	  pclmtoraster_data->swap_margin_x = true;
	  pclmtoraster_data->swap_margin_y = true;
       	}
       	else
	  pclmtoraster_data->swap_image_y = true;
      
	if (flippedMargin == FM_FALSE)
	  pclmtoraster_data->swap_margin_y = !pclmtoraster_data->swap_margin_y;
      }
    }
  }

  // check for bi-level printing mode
  if ((val = cupsGetOption("print-color-mode", num_options, options)) != NULL &&
		  	   !strncasecmp(val, "bi-level", 8))
    pclmtoraster_data->bi_level = 1;

  strncpy(pclmtoraster_data->pageSizeRequested, header->cupsPageSizeName, 63);
  pclmtoraster_data->pageSizeRequested[63] = '\0';
  if (log) log(ld, CF_LOGLEVEL_DEBUG,
	       "cfFilterPCLmToRaster: Page size requested: %s.",
	       header->cupsPageSizeName);
  return 0;
}

//
// 'media_box_lookup()' - Helper function look up MediaBox from PDF dictionary
//

static bool					  // O - 1 if mediabox is found, 0 if not
media_box_lookup(pdfio_obj_t *object,		// I - Page Object to look for mediabox
	       	 float rect[4])			// O - rectangle for mediabox output
{
  pdfio_rect_t mediaBox;
  pdfio_dict_t *object_dict = pdfioObjGetDict(object);
  if(pdfioDictGetRect(object_dict, "MediaBox", &mediaBox))
    return false;

 pdfioDictGetRect(object_dict, "MediaBox", &mediaBox);

 rect[0] = mediaBox.x1;
 rect[1] = mediaBox.y1;
 rect[2] = mediaBox.x2;
 rect[3] = mediaBox.y2;

 return true;
}	

//
// 'rotate_bitmap()' - Function to rotate a bitmap
//                     (assumed that bits-per-component of the bitmap is 8).
//

static unsigned char *		       // O - Output Bitmap
rotate_bitmap(unsigned char *src,    // I - Input string
	      unsigned char *dst,    // O - Destination string
	      unsigned int rotate,   // I - Rotate value (0, 90, 180, 270)
	      unsigned int height,   // I - Height of raster image in pixels
	      unsigned int width,    // I - Width of raster image in pixels
	      int rowsize,	     // I - Length of one row of pixels
	      char* colorspace,// I - Colorspace of input bitmap
	      cf_logfunc_t log,      // I - Log function
	      void *ld)		     // I - Aux. data for log function
{
  unsigned char *bp = src;
  unsigned char *dp = dst;
  unsigned char *temp = dst;

  if(rotate == 0)
    return (src);
  else if(rotate == 180)
  {	
    // rotate page 180 degree
    if (strcmp(colorspace, "/DeviceGray") == 0)
    {
      // 1 byte per pixel(because output is b&w & 1 colour is used)
      bp = src + height * rowsize - 1;
      dp = dst;
      for (unsigned int h = 0; h < height; h++)
	for (unsigned int w = 0; w < width; w++, bp--, dp++)
	  *dp = *bp;
    }  
    else if (strcmp(colorspace, "/DeviceCMYK") == 0)
    {
      // 4 bytes per pixel(because output is colour & 4 colours are used)
      bp = src + height * rowsize - 4;
      dp = dst;
      for (unsigned int h = 0; h < height; h ++)
      {
	for (unsigned int w = 0; w < width; w ++, bp -= 4, dp += 4)
	{
	  dp[0] = bp[0];
	  dp[1] = bp[1];
	  dp[2] = bp[2];
	  dp[3] = bp[3];
	}
      }
    }
    else if (strcmp(colorspace, "/DeviceRGB") == 0)
    {
      // 3 bytes per pixel(because output is colour & 3 colours are used)
      bp = src + height * rowsize - 3;
      dp = dst;
      for (unsigned int h = 0; h < height; h ++)
      {
	for (unsigned int w = 0; w < width; w ++, bp -= 3, dp += 3)
	{
	  dp[0] = bp[0];
	  dp[1] = bp[1];
	  dp[2] = bp[2];
	}
      }
    }
  }
  else if (rotate == 270)
  {
    // Rotate 270 degrees (Counter-clockwise)
    if (strcmp(colorspace, "/DeviceGray") == 0)
    {
      // 1 byte per pixel(because output is b&w & 1 colour is used)
      bp = src;
      dp = dst;
      for (unsigned int h = 0; h < height; h ++)
      {
        bp = src + (height - h) - 1;
        for (unsigned int w = 0; w < width; w ++, bp += height , dp ++)
          *dp = *bp;
      }
    }
    else if (strcmp(colorspace, "/DeviceCMYK") == 0)
    {
      // 4 bytes per pixel(because output is colour & 4 colours are used)
      for (unsigned int h = 0; h < height; h++)
      {
        bp = src + (height - h) * 4 - 4;
        for (unsigned int i = 0; i < width; i ++, bp += height * 4 , dp += 4)
	{
          dp[0] = bp[0];
          dp[1] = bp[1];
          dp[2] = bp[2];
          dp[3] = bp[3];
        }
      }
    }
    else if (strcmp(colorspace, "/DeviceRGB") == 0)
    {
      // 3 bytes per pixel(because output is colour & 3 colours are used)
      bp = src;
      dp = dst;
      for (unsigned int h = 0; h < height; h ++)
      {
        bp = src + (height - h) * 3 - 3;
        for (unsigned int i = 0; i < width; i ++, bp += height * 3 , dp += 3)
	{
          dp[0] = bp[0];
          dp[1] = bp[1];
          dp[2] = bp[2];
        }
      }
    }
  }
  else if (rotate == 90)
  {
    // Rotate 90 degrees (Clockwise)
    if (strcmp(colorspace, "/DeviceGray") == 0)
    {
      // 1 byte per pixel(because output is b&w & 1 colour is used)
      for (unsigned int h = 0; h < height; h ++)
      {
        bp = src + (width - 1) * height + h;
        for (unsigned int i = 0; i < width; i ++, bp -= height , dp ++)
          *dp = *bp;
      }
    }
    else if (strcmp(colorspace, "/DeviceCMYK") == 0)
    {
      // 4 bytes per pixel(because output is colour & 4 colours are used)
      for (unsigned int h = 0; h < height; h ++)
      {
        bp = src + (width - 1) * height * 4 + 4 * h;
        for (unsigned int i = 0; i < width; i ++, bp -= height * 4 , dp += 4)
	{
          dp[0] = bp[0];
          dp[1] = bp[1];
          dp[2] = bp[2];
          dp[3] = bp[3];
        }
      }
    }
    else if (strcmp(colorspace, "/DeviceRGB") == 0)
    {
      // 3 bytes per pixel(because output is colour & 3 colours are used)
      for (unsigned int h = 0; h < height; h ++)
      {
	bp = src + (width - 1) * height * 3 + 3 * h;
        for (unsigned int i = 0; i < width; i ++, bp -= height * 3 , dp += 3)
	{
          dp[0] = bp[0];
          dp[1] = bp[1];
          dp[2] = bp[2];
        }
      }
    }
  }
  else
  {
    if (log) log(ld, CF_LOGLEVEL_ERROR,
		 "cfFilterPCLmToRaster: Incorrect Rotate Value %d, not rotating",
		 rotate);
    return (src);
  }	  

  return (temp);
}

// 
// Color space conversion functions below, 
//
// This converts a line of pixels from the source color space 
// to the destination
//

static unsigned char*				  // O - output path
rgb_to_rgbw_line(unsigned char *src,		// I - source path
		 unsigned char *dst,		// I - output path(output of func)
		 unsigned int row,		// I - row value
		 unsigned int pixels,		// I - number of pixels
		 pclmtoraster_data_t *data)	// I - (unused) conversion data
{
  cfImageRGBToCMYK(src, dst, pixels);
  // Invert CMYK to get RGBW-like behavior (often needed for specific raster modes)
  for (unsigned int i = 0; i < 4 * pixels; i ++)
    dst[i] = ~dst[i];
  return (dst);
}

static unsigned char*				  // O - output Path
rgb_to_cmyk_line(unsigned char *src,		// I - source path
		 unsigned char *dst,		// I - output path(output of func)
		 unsigned int row,		// I - row value
		 unsigned int pixels,		// I - number of pixels
		 pclmtoraster_data_t *data)	// I - (unused) conversion data
{
  cfImageRGBToCMYK(src, dst, pixels);
  return (dst);
}

static unsigned char*				  // O - output Path
rgb_to_cmy_line(unsigned char *src,		// I - source path
		unsigned char *dst,		// I - output path(output of func)
		unsigned int row,		// I - row value
		unsigned int pixels,		// I - number of pixels
		pclmtoraster_data_t *data)	// I - (unused) conversion data
{
  cfImageRGBToCMY(src, dst, pixels);
  return (dst);
}

static unsigned char*				  // O - output Path
rgb_to_white_line(unsigned char *src,		// I - source path
		  unsigned char *dst,		// I - output path(output of func)
		  unsigned int row,		// I - row value
		  unsigned int pixels,		// I - number of pixels
		  pclmtoraster_data_t *data)	// I - conversion data
{
  if (data->header.cupsBitsPerColor != 1)
    cfImageRGBToWhite(src, dst, pixels);
  else
  {
    cfImageRGBToWhite(src, src, pixels);
    cfOneBitLine(src, dst, data->header.cupsWidth, row, data->bi_level);
  }
  return (dst);
}

static unsigned char*				  // O - output path
rgb_to_black_line(unsigned char *src,		// I - source path
		  unsigned char *dst,		// I - output path(output of func)
		  unsigned int row,		// I - row value
		  unsigned int pixels,		// I - number of pixels
		  pclmtoraster_data_t *data)	// I - conversion data
{
  if (data->header.cupsBitsPerColor != 1)
    cfImageRGBToBlack(src, dst, pixels);
  else
  {
    cfImageRGBToBlack(src, src, pixels);
    cfOneBitLine(src, dst, data->header.cupsWidth, row, data->bi_level);
  }
  return (dst);
}

static unsigned char*				  // O - output path
cmyk_to_rgb_line(unsigned char *src,		// I - source path
		 unsigned char *dst,		// I - output path(output of func)
		 unsigned int row,		// I - row value
		 unsigned int pixels,		// I - number of pixels
		 pclmtoraster_data_t *data)	// I - (unused) conversion data
{
  cfImageCMYKToRGB(src, dst, pixels);
  return (dst);
}

static unsigned char*				  // O - output path
cmyk_to_rgbw_line(unsigned char *src,		// I - source path
		  unsigned char *dst,		// I - output path(output of func)
		  unsigned int row,		// I - row value
		  unsigned int pixels,		// I - number of pixels
		  pclmtoraster_data_t *data)	// I - (unused) conversion data
{
  for (unsigned int i = 0; i < 4 * pixels; i ++)
    dst[i] = ~src[i];
  return (dst);
}

static unsigned char*				  // O - output path
cmyk_to_cmy_line(unsigned char *src,		// I - source path
		 unsigned char *dst,		// I - output path(output of func)
		 unsigned int row,		// I - row value
		 unsigned int pixels,		// I - number of pixels
		 pclmtoraster_data_t *data)	// I - (unused) conversion data
{
  // Converted first to RGB and then to cmy for better outputs.
  cfImageCMYKToRGB(src, src, pixels);
  cfImageRGBToCMY(src, dst, pixels);
  return (dst);
}

static unsigned char*				  // O - output path
cmyk_to_white_line(unsigned char *src,		// I - source path
		   unsigned char *dst,		// I - output path(output of func)
		   unsigned int row,		// I - row value
		   unsigned int pixels,		// I - number of pixels
		   pclmtoraster_data_t *data)	// I - conversion data
{
  if (data->header.cupsBitsPerColor != 1)
    cfImageCMYKToWhite(src, dst, pixels);
  else
  {
    cfImageCMYKToWhite(src, src, pixels);
    cfOneBitLine(src, dst, data->header.cupsWidth, row, data->bi_level);
  }
  return (dst);
}

static unsigned char*				  // O - output path
cmyk_to_black_line(unsigned char *src,		// I - source path
		   unsigned char *dst,		// I - output path(output of func)
		   unsigned int row,		// I - row value
		   unsigned int pixels,		// I - number of pixels
		   pclmtoraster_data_t *data)	// I - conversion data
{
  if (data->header.cupsBitsPerColor != 1)
    cfImageCMYKToBlack(src, dst, pixels);
  else
  {
    cfImageCMYKToBlack(src, src, pixels);
    cfOneBitLine(src, dst, data->header.cupsWidth, row, data->bi_level);
  }
  return (dst);
}

static unsigned char*				  // O - output path
gray_to_rgb_line(unsigned char *src,		// I - source path
		 unsigned char *dst,		// I - output path(output of func)
		 unsigned int row,		// I - row value
		 unsigned int pixels,		// I - number of pixels
		 pclmtoraster_data_t *data)	// I - (unused) conversion data
{
  cfImageWhiteToRGB(src, dst, pixels);
  return (dst);
}

static unsigned char*				  // O - output path
gray_to_rgbw_line(unsigned char *src,		// I - source path
		  unsigned char *dst,		// I - output path(output of func)
		  unsigned int row,		// I - row value
		  unsigned int pixels,		// I - number of pixels
		  pclmtoraster_data_t *data)	// I - (unused) conversion data
{
  cfImageWhiteToCMYK(src, dst, pixels);
  for (unsigned int i = 0; i < 4 * pixels; i ++)
    dst[i] = ~dst[i];
  return (dst);
}

static unsigned char*				  // O - output path
gray_to_cmyk_line(unsigned char *src,		// I - source path 
		  unsigned char *dst,		// I - output path(output of func)
		  unsigned int row,		// I - row value
		  unsigned int pixels,		// I - number of pixels
		  pclmtoraster_data_t *data)	// I - (unused) conversion data
{
  cfImageWhiteToCMYK(src, dst, pixels);
  return (dst);
}

static unsigned char*				  // O - output path
gray_to_cmy_line(unsigned char *src,		// I - source path
		 unsigned char *dst,		// I - output path(output of func)
		 unsigned int row,		// I - row value
		 unsigned int pixels,		// I - number of pixels
		 pclmtoraster_data_t *data)	// I - (unused) conversion data
{
  cfImageWhiteToCMY(src, dst, pixels);
  return (dst);
}

static unsigned char*				  // O - output path
gray_to_black_line(unsigned char *src,		// I - source path
		   unsigned char *dst,		// I - output path(output of func)
		   unsigned int row,		// I - row value
		   unsigned int pixels,		// I - number of pixels
		   pclmtoraster_data_t *data)	// I - conversion data
{
  if (data->header.cupsBitsPerColor != 1)
    cfImageWhiteToBlack(src, dst, pixels);
  else
  {
    cfImageWhiteToBlack(src, src, pixels);
    cfOneBitLine(src, dst, data->header.cupsWidth, row, data->bi_level);
  }
  return (dst);
}

static unsigned char*				  // O - ouptut path
convert_cspace_no_op(unsigned char *src,	// I - source path
		     unsigned char *dst,	// I - output path (output of func)
		     unsigned int row,		// I - row value
		     unsigned int pixels,	// I - number of pixels
		     pclmtoraster_data_t *data)	// I - (unused) conversion data
{
  return (src);
}

//
// 'convert_line()' - Function to convert colorspace and bits-per-pixel
//                    of a single line of raster data.
//

static unsigned char *			// O - Output string
convert_line(unsigned char 	*src,	// I - Input line
	     unsigned char 	*dst,	// O - Destination string
	     unsigned char 	*buf,	// I - Buffer string
	     unsigned int 	row,	// I - Current Row
	     unsigned int 	plane,	// I - Plane/Band
	     pclmtoraster_data_t *data,
	     convert_cspace_func convertcspace)
{
  //
  // Use only convertcspace if conversion of bits and conversion of color order
  // is not required, or if dithering is required, for faster processing of
  // raster output.
  //

  unsigned int pixels = data->header.cupsWidth;
  if ((data->header.cupsBitsPerColor == 1 &&
       data->header.cupsNumColors == 1) ||
      (data->header.cupsBitsPerColor == 8 &&
       data->header.cupsColorOrder == CUPS_ORDER_CHUNKED))
    dst = convertcspace(src, dst, row, pixels, data);
  else
  {
    // Handle bit depth conversion if necessary
    for (unsigned int i = 0; i < pixels; i ++)
    {
      unsigned char pixelBuf1[MAX_BYTES_PER_PIXEL];
      unsigned char pixelBuf2[MAX_BYTES_PER_PIXEL];
      unsigned char *pb;
      pb = convertcspace(src + i * data->numcolors, pixelBuf1, row, 1, data);
      pb = cfConvertBits(pb, pixelBuf2, i, row, data->header.cupsNumColors,
			 data->header.cupsBitsPerColor);
      cfWritePixel(dst, plane, i, pb, data->header.cupsNumColors,
		   data->header.cupsBitsPerColor, data->header.cupsColorOrder);
    }
  }
  return (dst);
}


//
// 'convert_reverse_line()' - Function to convert colorspace and bits-per-pixel
//                            of a single line of raster data and reverse the
//                            line.
//

static unsigned char *					// O - Output string
convert_reverse_line(unsigned char	*src,		// I - Input line
		     unsigned char	*dst,		// O - Destination
		                                        //     string
		     unsigned char	*buf,		// I - Buffer string
		     unsigned int	row,		// I - Current Row
		     unsigned int	plane,		// I - Plane/Band
		     pclmtoraster_data_t *data,		// I - pclmtoraster
							//     filter data
		     convert_cspace_func convertcspace)	// I - Function for
							//     conversion of
							//     colorspace
{
  //
  // Use only convertcspace if conversion of bits and conversion of color order
  // is not required, or if dithering is required, for faster processing of
  // raster output.
  //
  
  unsigned int pixels = data->header.cupsWidth;
  if (data->header.cupsBitsPerColor == 1 && data->header.cupsNumColors == 1)
  {
    // Reverse 1-bit line
    buf = convertcspace(src, buf, row, pixels, data);
    dst = cfReverseOneBitLine(buf, dst, pixels, data->bytesPerLine);
  }
  else if (data->header.cupsBitsPerColor == 8 &&
	   data->header.cupsColorOrder == CUPS_ORDER_CHUNKED)
  {
    unsigned char *dp = dst;
    // Assign each pixel of buf to dst in the reverse order.
    buf = convertcspace(src, buf, row, pixels, data) +
      (data->header.cupsWidth - 1) * data->header.cupsNumColors;
    for (unsigned int i = 0; i < pixels;
	 i ++, buf-=data->header.cupsNumColors, dp+=data->header.cupsNumColors)
      for (unsigned int j = 0; j < data->header.cupsNumColors; j ++)
	dp[j] = buf[j];
  }
  else
  {
    // General reverse with bit conversion
    for (unsigned int i = 0; i < pixels; i ++)
    {
      unsigned char pixelBuf1[MAX_BYTES_PER_PIXEL];
      unsigned char pixelBuf2[MAX_BYTES_PER_PIXEL];
      unsigned char *pb;
      pb = convertcspace(src + (pixels - i - 1) * (data->numcolors), pixelBuf1,
			 row, 1, data);
      pb = cfConvertBits(pb, pixelBuf2, i, row, data->header.cupsNumColors,
			 data->header.cupsBitsPerColor);
      cfWritePixel(dst, plane, i, pb, data->header.cupsNumColors,
		   data->header.cupsBitsPerColor, data->header.cupsColorOrder);
    }
  }
  return (dst);
}

//
// 'select_convert_func()' - Select the appropriate conversion function 
// 			     based on input/output color spaces
//

static void					   // O - Exit status
select_convert_func(int			pgno,	 // I - Page number
		    cf_logfunc_t	log,	 // I - Log function
		    void		*ld,	 // I - Aux. data for log
						 //     function
		    pclmtoraster_data_t	*data,	 // I - pclmtoraster filter
						 //     data
		    pclm_conversion_function_t *convert)// I - Conversion function
{
  // Set rowsize and numcolors based on colorspace of raster data
  cups_page_header_t header = data->header;
  char *colorspace = data->colorspace;
  if (strcmp(colorspace, "/DeviceRGB") == 0)
  {
    data->rowsize = header.cupsWidth * 3;
    data->numcolors = 3;
  }
  else if (strcmp(colorspace, "/DeviceCMYK") == 0)
  {
    data->rowsize = header.cupsWidth * 4;
    data->numcolors = 4;
  }
  else if (strcmp(colorspace, "/DeviceGray") == 0)
  {
    data->rowsize = header.cupsWidth;
    data->numcolors = 1;
  }
  else
  {
    if (log) log(ld, CF_LOGLEVEL_ERROR,
		 "cfFilterPCLmToRaster: Colorspace %s not supported, "
		 "defaulting to /deviceRGB",
		 colorspace);
    strcpy(data->colorspace, "/DeviceRGB");  
    data->rowsize = header.cupsWidth * 3;
    data->numcolors = 3;
  }

  convert->convertcspace = convert_cspace_no_op; //Default function
  // Select convertcspace function
  switch (header.cupsColorSpace)
  {
    case CUPS_CSPACE_K:
  	if (strcmp(colorspace, "/DeviceRGB") == 0)
	  convert->convertcspace = rgb_to_black_line;
  	else if (strcmp(colorspace, "/DeviceCMYK") == 0)
	  convert->convertcspace = cmyk_to_black_line;
  	else if (strcmp(colorspace, "/DeviceGray") == 0)
	  convert->convertcspace = gray_to_black_line;
	break;
    case CUPS_CSPACE_W:
    case CUPS_CSPACE_SW:
  	if (strcmp(colorspace, "/DeviceRGB") == 0)
	  convert->convertcspace = rgb_to_white_line;
  	else if (strcmp(colorspace, "/DeviceCMYK") == 0)
	  convert->convertcspace = cmyk_to_white_line;
	break;
    case CUPS_CSPACE_CMY:
  	if (strcmp(colorspace, "/DeviceRGB") == 0)
	  convert->convertcspace = rgb_to_cmy_line;
  	else if (strcmp(colorspace, "/DeviceCMYK") == 0)
	  convert->convertcspace = cmyk_to_cmy_line;
  	else if (strcmp(colorspace, "/DeviceGray") == 0)
	  convert->convertcspace = gray_to_cmy_line;
	break;
    case CUPS_CSPACE_CMYK:
  	if (strcmp(colorspace, "/DeviceRGB") == 0)
	  convert->convertcspace = rgb_to_cmyk_line;
  	else if (strcmp(colorspace, "/DeviceGray") == 0)
	  convert->convertcspace = gray_to_cmyk_line;
	break;
    case CUPS_CSPACE_RGBW:
  	if (strcmp(colorspace, "/DeviceRGB") == 0)
	  convert->convertcspace = rgb_to_rgbw_line;
  	else if (strcmp(colorspace, "/DeviceCMYK") == 0)
	  convert->convertcspace = cmyk_to_rgbw_line;
  	else if (strcmp(colorspace, "/DeviceGray") == 0)
	  convert->convertcspace = gray_to_rgbw_line;
	break;
    case CUPS_CSPACE_RGB:
    case CUPS_CSPACE_ADOBERGB:
    case CUPS_CSPACE_SRGB:
    default:
  	if (strcmp(colorspace, "/DeviceCMYK") == 0)
	  convert->convertcspace = cmyk_to_rgb_line;
  	else if (strcmp(colorspace, "/DeviceGray") == 0)
	  convert->convertcspace = gray_to_rgb_line;
	break;
  }

  // Select convertline function
  if (header.Duplex && (pgno & 1) && data->swap_image_x)
    convert->convertline = convert_reverse_line;
  else
    convert->convertline = convert_line;
}

//
// 'process_image()' - Callback for processing images found in PDF dictionaries
// 		       NOTE: no error code, be sure to upload image object only.
//

static bool					  // O - 1 if success, 0 if fail
process_image(pdfio_dict_t *dict, 		// I - dictionary where images are there
	      const char *key, 			// I - key names of xobjects
	      void *cb_data)			// I - conversion data
{
  pclmtoraster_data_t *data = (pclmtoraster_data_t *)cb_data;
  char *buffer[4096];
  pdfio_obj_t *image = pdfioDictGetObj(dict, key);

  if (strcmp(pdfioObjGetType(image), "image") == 0)
  {
    pdfio_dict_t *imgdict = pdfioObjGetDict(image);
    if (!imgdict)
       return true;

    // Read the raw image stream
    pdfio_stream_t *img_str = pdfioObjOpenStream(image, true);
    size_t bufsize = pdfioStreamRead(img_str, buffer, sizeof(buffer));

    int width = pdfioDictGetNumber(imgdict, "Width");
    int height = pdfioDictGetNumber(imgdict, "Height");

    data->header.cupsHeight += height;

    // Allocate memory for the bitmap data
    if (data->pixel_count == 0)
      data->bitmap = (unsigned char *)malloc(bufsize);
    else
      data->bitmap = (unsigned char *)realloc(data->bitmap,
                                              data->pixel_count + bufsize);

    memcpy(data->bitmap + data->pixel_count, buffer, bufsize);
    data->pixel_count += bufsize;

    // Track maximum width
    if (width > data->header.cupsWidth)
      data->header.cupsWidth = width;
  }

  return (true);
}

//
// 'out_page()' - Function to convert a single page of raster-only PDF/PCLm
//                input to CUPS/PWG Raster.
//

static int				  // O - Exit status
out_page(cups_raster_t*	 raster, 	// I - Raster stream
	 pdfio_obj_t* 	 page,		// I - PDFio Page Object
	 int		 pgno,		// I - Page number
	 cf_logfunc_t    log,		// I - Log function
	 void*		 ld,		// I - Aux. data for log function
	 pclmtoraster_data_t *data,	// I - pclmtoraster filter data
	 cf_filter_data_t *filter_data,	// I - filter data
	 pclm_conversion_function_t *convert)// I - Conversion functions
{
  int                   i;
  long long		rotate = 0;
  float			paperdimensions[2], margins[4], l, swap;
  int			temp = 0;
  float 		mediaBox[4];
  unsigned char 	*colordata = NULL,
			*lineBuf = NULL,
			*line = NULL,
			*dp = NULL;
  pdfio_obj_t		*colorspace_obj;


  // Check if page is rotated.
  pdfio_dict_t *pageDict = pdfioObjGetDict(page);
  if(pdfioDictGetNumber(pageDict, "Rotate"))
  {
    rotate = pdfioDictGetNumber(pageDict, "Rotate");
  }

  // Get pagesize by the mediabox key of the page.
  if (!media_box_lookup(page, mediaBox))
  {
    if (log) log(ld, CF_LOGLEVEL_ERROR,
		 "cfFilterPCLmToRaster: PDF page %d doesn't contain a valid mediaBox",
		 pgno + 1);
    return (1);
  }
  else
  {
    if (log) log(ld, CF_LOGLEVEL_DEBUG,
		 "cfFilterPCLmToRaster: mediaBox = [%f %f %f %f]: ",
		 mediaBox[0], mediaBox[1], mediaBox[2], mediaBox[3]);
    l = mediaBox[2] - mediaBox[0];
    if (l < 0)
      l = -l;
    if (rotate == 90 || rotate == 270)
      data->header.PageSize[1] = (unsigned)l;
    else
      data->header.PageSize[0] = (unsigned)l;
    l = mediaBox[3] - mediaBox[1];
    if (l < 0)
      l = -l;
    if (rotate == 90 || rotate == 270)
      data->header.PageSize[0] = (unsigned)l;
    else
      data->header.PageSize[1] = (unsigned)l;
  }

  memset(paperdimensions, 0, sizeof(paperdimensions));
  for (i = 0; i < 4; i ++)
    margins[i] = -1.0;
  if (filter_data != NULL)
  {
    cfGetPageDimensions(filter_data->printer_attrs, filter_data->job_attrs,
			filter_data->num_options, filter_data->options,
			&(data->header), 0,
			&(paperdimensions[0]), &(paperdimensions[1]),
			&(margins[0]), &(margins[1]),
			&(margins[2]), &(margins[3]), NULL, NULL);

    cfSetPageDimensionsToDefault(&(paperdimensions[0]), &(paperdimensions[1]),
				 &(margins[0]), &(margins[1]),
				 &(margins[2]), &(margins[3]),
				 log, ld);

    if (data->outformat != CF_FILTER_OUT_FORMAT_CUPS_RASTER)
      memset(margins, 0, sizeof(margins));
  }
  else
  {
    for (int i = 0; i < 2; i ++)
      paperdimensions[i] = data->header.PageSize[i];
    if (data->header.cupsImagingBBox[3] > 0.0)
    {
      // Set margins if we have a bounding box defined ...
      if (data->outformat == CF_FILTER_OUT_FORMAT_CUPS_RASTER)
      {
	margins[0] = data->header.cupsImagingBBox[0];
	margins[1] = data->header.cupsImagingBBox[1];
	margins[2] = paperdimensions[0] - data->header.cupsImagingBBox[2];
	margins[3] = paperdimensions[1] - data->header.cupsImagingBBox[3];
      }
    }
    else
      // ... otherwise use zero margins
      for (int i = 0; i < 4; i ++)
	margins[i] = 0.0;
  }

  if (data->header.Duplex && (pgno & 1))
  {
    // backside: change margin if needed
    if (data->swap_margin_x)
    {
      swap = margins[2]; margins[2] = margins[0]; margins[0] = swap;
    }
    if (data->swap_margin_y)
    {
      swap = margins[3]; margins[3] = margins[1]; margins[1] = swap;
    }
  }

  // write page header
  for (int i = 0; i < 2; i ++)
  {
    data->header.cupsPageSize[i] = paperdimensions[i];
    data->header.PageSize[i] = (unsigned int)(data->header.cupsPageSize[i] +
					      0.5);
    if (data->outformat == CF_FILTER_OUT_FORMAT_CUPS_RASTER)
      data->header.Margins[i] = margins[i] + 0.5;
    else
      data->header.Margins[i] = 0;
  }
  if (data->outformat == CF_FILTER_OUT_FORMAT_CUPS_RASTER)
  {
    data->header.cupsImagingBBox[0] = margins[0];
    data->header.cupsImagingBBox[1] = margins[1];
    data->header.cupsImagingBBox[2] = paperdimensions[0] - margins[2];
    data->header.cupsImagingBBox[3] = paperdimensions[1] - margins[3];
    for (int i = 0; i < 4; i ++)
      data->header.ImagingBoundingBox[i] =
	(unsigned int)(data->header.cupsImagingBBox[i] + 0.5);
  }
  else
  {
    for (int i = 0; i < 4; i ++)
    {
      data->header.cupsImagingBBox[i] = 0.0;
      data->header.ImagingBoundingBox[i] = 0;
    }
  }

  data->header.cupsWidth = 0;
  data->header.cupsHeight = 0;

  // Loop over all raster images in a page and store them in bitmap.
  
  pdfio_dict_t *resources = pdfioDictGetDict(pdfioObjGetDict(page), "Resources");
  pdfio_dict_t *xobjects = pdfioDictGetDict(resources, "XObject");
 
  // Iterate over the XObject dictionary to find images
  data->pixel_count = 0;
  data->bitmap = NULL;
  pdfioDictIterateKeys(xobjects, process_image, data);

  // Swap width and height in landscape images
  if(rotate == 270 || rotate == 90)
  {
    temp = data->header.cupsHeight;
    data->header.cupsHeight = data->header.cupsWidth;
    data->header.cupsWidth = temp;
  }
	
  data->bytesPerLine = data->header.cupsBytesPerLine =
    (data->header.cupsBitsPerPixel * data->header.cupsWidth + 7) / 8;
  if (data->header.cupsColorOrder == CUPS_ORDER_BANDED)
    data->header.cupsBytesPerLine *= data->header.cupsNumColors;

  if (!cupsRasterWriteHeader(raster, &(data->header)))
  {
    if (log) log(ld, CF_LOGLEVEL_ERROR,
		 "cfFilterPCLmToRaster: Can't write page %d header", pgno + 1);
    return (1);
  }

  colorspace_obj = pdfioDictGetObj(pdfioObjGetDict(page), "ColorSpace");

    if (colorspace_obj) {
      if (strcmp(pdfioObjGetType(colorspace_obj), "Name") == 0) 
	strncpy(data->colorspace, pdfioDictGetName(pdfioObjGetDict(colorspace_obj), "Type"), sizeof(data->colorspace) - 1);	
      else
	strncpy(data->colorspace, "DeviceRGB", sizeof(data->colorspace) - 1);
    }


  // Select convertline and convertscpace function
  select_convert_func(pgno, log, ld, data, convert);

  // If page is to be swapped in both x and y, rotate it by 180 degress
  if (data->header.Duplex && (pgno & 1) && data->swap_image_y &&
      data->swap_image_x)
  {
    rotate = (rotate + 180) % 360;
    data->swap_image_y = false;
    data->swap_image_x = false;
  }

  // Rotate Bitmap
  if (rotate)
  {
    unsigned char *bitmap2 = (unsigned char *) malloc(data->pixel_count);
    bitmap2 = rotate_bitmap(data->bitmap, bitmap2, rotate,
			    data->header.cupsHeight,
			    data->header.cupsWidth, data->rowsize,
			    data->colorspace, log, ld);
    free(data->bitmap);
    data->bitmap = bitmap2;
  }

  colordata = data->bitmap;

  // Write page image
  lineBuf = (unsigned char *)malloc(data->bytesPerLine * sizeof(unsigned char));
  line = (unsigned char *)malloc(data->bytesPerLine * sizeof(unsigned char));

  if (data->header.Duplex && (pgno & 1) && data->swap_image_y)
  {
    for (unsigned int plane = 0; plane < data->nplanes; plane ++)
    {
      unsigned char *bp = colordata +
	(data->header.cupsHeight - 1) * (data->rowsize);
      for (unsigned int h = data->header.cupsHeight; h > 0; h--)
      {
        for (unsigned int band = 0; band < data->nbands; band ++)
	{
          dp = convert->convertline(bp, line, lineBuf, h - 1, plane + band,
				    data, convert->convertcspace);
          cupsRasterWritePixels(raster, dp, data->bytesPerLine);
        }
        bp -= data->rowsize;
      }
    }
  }
  else
  {
    for (unsigned int plane = 0; plane < data->nplanes; plane ++)
    {
      unsigned char *bp = colordata;
      for (unsigned int h = 0; h < data->header.cupsHeight; h ++)
      {
        for (unsigned int band = 0; band < data->nbands; band ++)
	{
          dp = convert->convertline(bp, line, lineBuf, h, plane + band,
				    data, convert->convertcspace);
          cupsRasterWritePixels(raster, dp, data->bytesPerLine);
        }
        bp += data->rowsize;
      }
    }
  }
  free(lineBuf);
  free(line);
  free(data->bitmap);
  data->bitmap = NULL;
  data->pixel_count = 0;

  return (0);
}

//
// 'cfFilterPCLmToRaster()' - Filter function to convert raster-only PDF/PCLm
//                            input to CUPS/PWG Raster output.
//

int					    // O - Error status
cfFilterPCLmToRaster(int inputfd,         // I - File descriptor input stream
		     int outputfd,        // I - File descriptor output stream
		     int inputseekable,   // I - Is input stream seekable?
					  //     (unused)
		     cf_filter_data_t *data, // I - Job and printer data
		     void *parameters)    // I - Filter-specific parameters
					  //     (unused)
{
  cf_filter_out_format_t outformat;
  FILE			*inputfp;		// Input file pointer
  int			fd = 0;			// Copy file descriptor
  char			*filename,		// PDF file to convert
			tempfile[1024];		// Temporary file
  char			buffer[8192];		// Copy buffer
  int			bytes;			// Bytes copied
  int			npages = 0;
  pdfio_file_t		*pdf = NULL;
  cups_raster_t		*raster;
  pclmtoraster_data_t	pclmtoraster_data;
  pclm_conversion_function_t convert;
  cf_logfunc_t		log = data->logfunc;
  void			*ld = data->logdata;
  cf_filter_iscanceledfunc_t iscanceled = data->iscanceledfunc;
  void			*icd = data->iscanceleddata;

  if (parameters)
  {
    outformat = *(cf_filter_out_format_t *)parameters;
    if (outformat != CF_FILTER_OUT_FORMAT_CUPS_RASTER &&
	outformat != CF_FILTER_OUT_FORMAT_PWG_RASTER &&
	outformat != CF_FILTER_OUT_FORMAT_APPLE_RASTER)
      outformat = CF_FILTER_OUT_FORMAT_PWG_RASTER;
  }
  else
    outformat = CF_FILTER_OUT_FORMAT_PWG_RASTER;

  if (log) log(ld, CF_LOGLEVEL_DEBUG,
	       "cfFilterPCLmToRaster: Output format: %s",
	       (outformat == CF_FILTER_OUT_FORMAT_CUPS_RASTER ? "CUPS Raster" :
		(outformat == CF_FILTER_OUT_FORMAT_PWG_RASTER ? "PWG Raster" :
		 "Apple Raster")));

  // Open the input data stream specified by the inputfd...
  if ((inputfp = fdopen(inputfd, "r")) == NULL)
  {
    if (!iscanceled || !iscanceled(icd))
    {
      if (log) log(ld, CF_LOGLEVEL_DEBUG,
		   "cfFilterPCLmToRaster: Unable to open input data stream.");
    }
    return (1);
  }

  if ((fd = cupsCreateTempFd(NULL, NULL, tempfile, sizeof(tempfile))) < 0)
  {
    if (log) log(ld, CF_LOGLEVEL_ERROR,
		 "cfFilterPCLmToRaster: Unable to copy PDF file: %s",
		 strerror(errno));
    fclose(inputfp);
    return (1);
  }

  if (log) log(ld, CF_LOGLEVEL_DEBUG,
	       "cfFilterPCLmToRaster: Copying input to temp file \"%s\"",
	       tempfile);

  while ((bytes = fread(buffer, 1, sizeof(buffer), inputfp)) > 0)
    bytes = write(fd, buffer, bytes);

  fclose(inputfp);
  close(fd);

  // as the pdf is temporary, we will treat tempfile as filename 
  filename = tempfile;
  pdf = pdfioFileOpen(filename, NULL, NULL, NULL, NULL);

  if (parse_opts(data, outformat, &pclmtoraster_data) != 0)
  {
    pdfioFileClose(pdf);
    unlink(tempfile);
    return (1);
  }

  if (pclmtoraster_data.header.cupsBitsPerColor != 1
     && pclmtoraster_data.header.cupsBitsPerColor != 2
     && pclmtoraster_data.header.cupsBitsPerColor != 4
     && pclmtoraster_data.header.cupsBitsPerColor != 8
     && pclmtoraster_data.header.cupsBitsPerColor != 16)
  {
    if(log) log(ld, CF_LOGLEVEL_ERROR,
		"cfFilterPCLmToRaster: Specified color format is not supported: %s",
		strerror(errno));
    pdfioFileClose(pdf);
    unlink(tempfile);
    return (1);
  }

  if (pclmtoraster_data.header.cupsColorOrder == CUPS_ORDER_PLANAR)
    pclmtoraster_data.nplanes = pclmtoraster_data.header.cupsNumColors;
  else
    pclmtoraster_data.nplanes = 1;
  if (pclmtoraster_data.header.cupsColorOrder == CUPS_ORDER_BANDED)
    pclmtoraster_data.nbands = pclmtoraster_data.header.cupsNumColors;
  else
    pclmtoraster_data.nbands = 1;

  if ((raster = cupsRasterOpen(outputfd,
			       (pclmtoraster_data.outformat ==
				  CF_FILTER_OUT_FORMAT_CUPS_RASTER ?
				  CUPS_RASTER_WRITE :
				(pclmtoraster_data.outformat ==
				   CF_FILTER_OUT_FORMAT_PWG_RASTER ?
				   CUPS_RASTER_WRITE_PWG :
				 CUPS_RASTER_WRITE_APPLE)))) == 0)
  {
    if(log) log(ld, CF_LOGLEVEL_ERROR,
		"cfFilterPCLmToRaster: Can't open raster stream: %s",
		strerror(errno));
    pdfioFileClose(pdf);
    unlink(tempfile);
    return (1);
  }


  npages = pdfioFileGetNumPages(pdf); 

  for (int i = 0; i < npages; ++i)
  {
    pdfio_obj_t *pages = pdfioFileGetPage(pdf, i);

    if (iscanceled && iscanceled(icd))
    {
      if (log) log(ld, CF_LOGLEVEL_DEBUG,
		   "cfFilterPCLmToRaster: Job canceled");
      break;
    }

    if (log) log(ld, CF_LOGLEVEL_INFO,
		 "cfFilterPCLmToRaster: Starting page %d.", i + 1);
    if (out_page(raster, pages, i, log, ld, &pclmtoraster_data,data,
		 &convert) != 0)
      break;

    if (log) log(ld, CF_LOGLEVEL_INFO,
		 "cfFilterPCLmToRaster: Starting page %d.", (i + 1));
    if (out_page(raster, pages, i, log, ld, &pclmtoraster_data,data,
		 &convert) != 0)
      break;
  }

  cupsRasterClose(raster);
  pdfioFileClose(pdf);
  unlink(tempfile);
  return (0);
}
