/* Copyright (C) 1992, 1993, 1994, 1997, 1999 artofcode LLC.  All rights reserved.
  
  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: gdevmgr.c,v 1.2.6.1.2.1 2003/01/17 00:49:01 giles Exp $*/
/* MGR device driver */
#include "gdevprn.h"
#include "gdevpccm.h"
#include "gdevmgr.h"

/* Structure for MGR devices, which extend the generic printer device. */
struct gx_device_mgr_s {
	gx_device_common;
	gx_prn_device_common;
	/* Add MGR specific variables */
	int mgr_depth;
};
typedef struct gx_device_mgr_s gx_device_mgr;

static struct nclut clut[256];

private unsigned int clut2mgr(P2(int, int));
private void swap_bwords(P2(unsigned char *, int));

/* ------ The device descriptors ------ */

/*
 * Default X and Y resolution.
 */
#define X_DPI 72
#define Y_DPI 72

/* Macro for generating MGR device descriptors. */
#define mgr_prn_device(procs, dev_name, num_comp, depth, mgr_depth,\
	max_gray, max_rgb, dither_gray, dither_rgb, print_page)\
{	prn_device_body(gx_device_mgr, procs, dev_name,\
	  DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS, X_DPI, Y_DPI,\
	  0, 0, 0, 0,\
	  num_comp, depth, max_gray, max_rgb, dither_gray, dither_rgb,\
	  print_page),\
	  mgr_depth\
}

/* For all mgr variants we do some extra things at opening time. */
/* private dev_proc_open_device(gdev_mgr_open); */
#define gdev_mgr_open gdev_prn_open		/* no we don't! */

/* And of course we need our own print-page routines. */
private dev_proc_print_page(mgr_print_page);
private dev_proc_print_page(mgrN_print_page);
private dev_proc_print_page(cmgrN_print_page);

/* The device procedures */
private gx_device_procs mgr_procs =
    prn_procs(gdev_mgr_open, gdev_prn_output_page, gdev_prn_close);
private gx_device_procs mgrN_procs =
    prn_color_procs(gdev_mgr_open, gdev_prn_output_page, gdev_prn_close,
	gx_default_gray_map_rgb_color, gx_default_gray_map_color_rgb);
private gx_device_procs cmgr4_procs =
    prn_color_procs(gdev_mgr_open, gdev_prn_output_page, gdev_prn_close,
	pc_4bit_map_rgb_color, pc_4bit_map_color_rgb);
private gx_device_procs cmgr8_procs =
    prn_color_procs(gdev_mgr_open, gdev_prn_output_page, gdev_prn_close,
	mgr_8bit_map_rgb_color, mgr_8bit_map_color_rgb);

/* The device descriptors themselves */
gx_device_mgr far_data gs_mgrmono_device =
  mgr_prn_device( mgr_procs,  "mgrmono", 1,  1, 1,   1,   0, 2, 0, mgr_print_page);
gx_device_mgr far_data gs_mgrgray2_device =
  mgr_prn_device(mgrN_procs,  "mgrgray2",1,  8, 2, 255,   0, 4, 0, mgrN_print_page);
gx_device_mgr far_data gs_mgrgray4_device =
  mgr_prn_device(mgrN_procs,  "mgrgray4",1,  8, 4, 255,   0,16, 0, mgrN_print_page);
gx_device_mgr far_data gs_mgrgray8_device =
  mgr_prn_device(mgrN_procs,  "mgrgray8",1,  8, 8, 255,   0, 0, 0, mgrN_print_page);
gx_device_mgr far_data gs_mgr4_device =
  mgr_prn_device(cmgr4_procs, "mgr4",    3,  8, 4,   1,   1, 4, 3, cmgrN_print_page);
gx_device_mgr far_data gs_mgr8_device =
  mgr_prn_device(cmgr8_procs, "mgr8",    3,  8, 8, 255, 255, 6, 5, cmgrN_print_page);

/* ------ Internal routines ------ */

/* Define a "cursor" that keeps track of where we are in the page. */
typedef struct mgr_cursor_s {
	gx_device_mgr *dev;
	int bpp;			/* bits per pixel */
	uint line_size;			/* bytes per scan line */
	byte *data;			/* output row buffer */
	int lnum;			/* row within page */
} mgr_cursor;

/* Begin an MGR output page. */
/* Write the header information and initialize the cursor. */
private int
mgr_begin_page(gx_device_mgr *bdev, FILE *pstream, mgr_cursor *pcur)
{	struct b_header head;
	uint line_size =
		gdev_prn_raster((gx_device_printer *)bdev) + 3;
	byte *data = (byte *)gs_malloc(line_size, 1, "mgr_begin_page");
	if ( data == 0 )
		return_error(gs_error_VMerror);

	/* Write the header */
	B_PUTHDR8(&head, bdev->width, bdev->height, bdev->mgr_depth);
	fprintf(pstream, "");
	if ( fwrite(&head, 1, sizeof(head), pstream) < sizeof(head) )
		return_error(gs_error_ioerror);
	fflush(pstream);

	/* Initialize the cursor. */
	pcur->dev = bdev;
	pcur->bpp = bdev->color_info.depth;
	pcur->line_size = line_size;
	pcur->data = data;
	pcur->lnum = 0;
	return 0;
}

/* Advance to the next row.  Return 0 if more, 1 if done. */
private int
mgr_next_row(mgr_cursor *pcur)
{	if ( pcur->lnum >= pcur->dev->height )
	   {	gs_free((char *)pcur->data, pcur->line_size, 1,
			"mgr_next_row(done)");
		return 1;
	   }
	gdev_prn_copy_scan_lines((gx_device_printer *)pcur->dev,
				 pcur->lnum++, pcur->data, pcur->line_size);
	return 0;
}

/* ------ Individual page printing routines ------ */

#define bdev ((gx_device_mgr *)pdev)

/* Print a monochrome page. */
private int
mgr_print_page(gx_device_printer *pdev, FILE *pstream)
{	mgr_cursor cur;
	int mgr_wide;
	int code = mgr_begin_page(bdev, pstream, &cur);
	if ( code < 0 ) return code;

	mgr_wide = bdev->width;
	if (mgr_wide & 7)
	   mgr_wide += 8 - (mgr_wide & 7);
	   
	while ( !(code = mgr_next_row(&cur)) )
	   {	if ( fwrite(cur.data, sizeof(char), mgr_wide / 8, pstream) <
                    mgr_wide / 8)
		return_error(gs_error_ioerror);
	   }
	return (code < 0 ? code : 0);
}


/* Print a gray-mapped page. */
static unsigned char bgreytable[16], bgreybacktable[16];
static unsigned char bgrey256table[256], bgrey256backtable[256];        
private int
mgrN_print_page(gx_device_printer *pdev, FILE *pstream)
{	mgr_cursor cur;
	int i = 0, j, k, mgr_wide;
	uint mgr_line_size;
	byte *bp, *data = NULL, *dp;
        
	int code = mgr_begin_page(bdev, pstream, &cur);
	if ( code < 0 ) return code;

	mgr_wide = bdev->width;
	if ( bdev->mgr_depth == 2 && mgr_wide & 3 )
            mgr_wide += 4 - (mgr_wide & 3);
	if ( bdev->mgr_depth == 4 && mgr_wide & 1 )
            mgr_wide++;
	mgr_line_size = mgr_wide / ( 8 / bdev->mgr_depth );

	if ( bdev->mgr_depth == 4 )
            for ( i = 0; i < 16; i++ ) {
		bgreytable[i] = mgrlut[LUT_BGREY][RGB_RED][i];
		bgreybacktable[bgreytable[i]] = i;
            }

	if ( bdev->mgr_depth == 8 ) {
            for ( i = 0; i < 16; i++ ) {
		bgrey256table[i] = mgrlut[LUT_BGREY][RGB_RED][i] << 4;
		bgrey256backtable[bgrey256table[i]] = i;
            }
            for ( i = 16,j = 0; i < 256; i++ ) {
		for ( k = 0; k < 16; k++ )
                  if ( j == mgrlut[LUT_BGREY][RGB_RED][k] << 4 ) {
                    j++;
                    break;
                  }
		bgrey256table[i] = j;
		bgrey256backtable[j++] = i;
            }
	}

	if ( bdev->mgr_depth != 8 )
        	data = (byte *)gs_malloc(mgr_line_size, 1, "mgrN_print_page");
        
	while ( !(code = mgr_next_row(&cur)) )
	   {
		switch (bdev->mgr_depth) {
			case 2:
				for (i = 0,dp = data,bp = cur.data; i < mgr_line_size; i++) {
					*dp =	*(bp++) & 0xc0;
					*dp |= (*(bp++) & 0xc0) >> 2;
					*dp |= (*(bp++) & 0xc0) >> 4;
                                    *(dp++) |= (*(bp++) & 0xc0) >> 6;
				}
                		if ( fwrite(data, sizeof(byte), mgr_line_size, pstream) < mgr_line_size )
                                	return_error(gs_error_ioerror);
				break;
	                        
			case 4:
				for (i = 0,dp = data, bp = cur.data; i < mgr_line_size; i++) {
					*dp =  bgreybacktable[*(bp++) >> 4] << 4;
                                    *(dp++) |= bgreybacktable[*(bp++) >> 4];
				}
                		if ( fwrite(data, sizeof(byte), mgr_line_size, pstream) < mgr_line_size )
                                	return_error(gs_error_ioerror);
				break;
	                        
			case 8:
				for (i = 0,bp = cur.data; i < mgr_line_size; i++, bp++)
	                              *bp = bgrey256backtable[*bp];
                		if ( fwrite(cur.data, sizeof(cur.data[0]), mgr_line_size, pstream)
					< mgr_line_size )
                                	return_error(gs_error_ioerror);
				break;
		}  
	   }
	if (bdev->mgr_depth != 8)
        	gs_free((char *)data, mgr_line_size, 1, "mgrN_print_page(done)");

	if (bdev->mgr_depth == 2) {
            for (i = 0; i < 4; i++) {
               clut[i].colnum = i;
               clut[i].red    = clut[i].green = clut[i].blue = clut2mgr(i, 2);
	    }
   	}
	if (bdev->mgr_depth == 4) {
            for (i = 0; i < 16; i++) {
               clut[i].colnum = i;
               clut[i].red    = clut[i].green = clut[i].blue = clut2mgr(bgreytable[i], 4);
	    }
   	}
	if (bdev->mgr_depth == 8) {
            for (i = 0; i < 256; i++) {
               clut[i].colnum = i;
               clut[i].red    = clut[i].green = clut[i].blue = clut2mgr(bgrey256table[i], 8);
	    }
   	}
#if !arch_is_big_endian
	swap_bwords( (unsigned char *) clut, sizeof( struct nclut ) * i );
#endif
	if ( fwrite(&clut, sizeof(struct nclut), i, pstream) < i )
            return_error(gs_error_ioerror);
	return (code < 0 ? code : 0);
}

/* Print a color page. */
private int
cmgrN_print_page(gx_device_printer *pdev, FILE *pstream)
{	mgr_cursor cur;
	int i = 0, j, mgr_wide, r, g, b, colors8 = 0;
	uint mgr_line_size;
	byte *bp, *data, *dp;
	ushort prgb[3];
	unsigned char table[256], backtable[256];
        
	int code = mgr_begin_page(bdev, pstream, &cur);
	if ( code < 0 ) return code;

	mgr_wide = bdev->width;
	if (bdev->mgr_depth == 4 && mgr_wide & 1)
            mgr_wide++;
	mgr_line_size = mgr_wide / (8 / bdev->mgr_depth);
       	data = (byte *)gs_malloc(mgr_line_size, 1, "cmgrN_print_page");

       	if ( bdev->mgr_depth == 8 ) {
            memset( table, 0, sizeof(table) );
            for ( r = 0; r <= 6; r++ )
		for ( g = 0; g <= 6; g++ )
       	            for ( b = 0; b <= 6; b++ )
       			if ( r == g && g == b )
                            table[ r + (256-7) ] = 1;
			else
                            table[ (r << 5) + (g << 2) + (b >> 1) ] = 1;
            for ( i = j = 0; i < sizeof(table); i++ )
		if ( table[i] == 1 ) {
                    backtable[i] = j;
                    table[j++] = i;
		}
            colors8 = j;
	}
	while ( !(code = mgr_next_row(&cur)) )
	   {
		switch (bdev->mgr_depth) {
			case 4:
				for (i = 0,dp = data, bp = cur.data; i < mgr_line_size; i++) {
					*dp =  *(bp++) << 4;
                                    *(dp++) |= *(bp++) & 0x0f;
				}
                		if ( fwrite(data, sizeof(byte), mgr_line_size, pstream) < mgr_line_size )
                                	return_error(gs_error_ioerror);
				break;
	                        
			case 8:
				for (i = 0,bp = cur.data; i < mgr_line_size; i++, bp++)
	                              *bp = backtable[*bp] + MGR_RESERVEDCOLORS;
                		if ( fwrite(cur.data, sizeof(cur.data[0]), mgr_line_size, pstream) < mgr_line_size )
                                	return_error(gs_error_ioerror);
				break;
		}  
	   }
       	gs_free((char *)data, mgr_line_size, 1, "cmgrN_print_page(done)");
       	
	if (bdev->mgr_depth == 4) {
            for (i = 0; i < 16; i++) {
               pc_4bit_map_color_rgb((gx_device *)0, (gx_color_index) i, prgb);
               clut[i].colnum = i;
               clut[i].red    = clut2mgr(prgb[0], 16);
               clut[i].green  = clut2mgr(prgb[1], 16);
               clut[i].blue   = clut2mgr(prgb[2], 16);
	    }
   	}
	if (bdev->mgr_depth == 8) {
            for (i = 0; i < colors8; i++) {
               mgr_8bit_map_color_rgb((gx_device *)0, (gx_color_index)
                   table[i], prgb);
               clut[i].colnum = MGR_RESERVEDCOLORS + i;
               clut[i].red    = clut2mgr(prgb[0], 16);
               clut[i].green  = clut2mgr(prgb[1], 16);
               clut[i].blue   = clut2mgr(prgb[2], 16);
	    }
   	}
#if !arch_is_big_endian
	swap_bwords( (unsigned char *) clut, sizeof( struct nclut ) * i );
#endif    
	if ( fwrite(&clut, sizeof(struct nclut), i, pstream) < i )
            return_error(gs_error_ioerror);
	return (code < 0 ? code : 0);
}


/* Color mapping routines for 8-bit color with a fixed palette */
/* (3 bits of R, 3 bits of G, 2 bits of B). */
/* We have to trade off even spacing of colors along each axis */
/* against the desire to have real gray shades; */
/* MGR compromises by using a 7x7x4 "cube" with extra gray shades */
/* (1/6, 1/2, and 5/6), instead of the obvious 8x8x4. */

gx_color_index
mgr_8bit_map_rgb_color(gx_device *dev, gx_color_value r, gx_color_value g,
  gx_color_value b)
{	uint rv = r / (gx_max_color_value / 7 + 1);
	uint gv = g / (gx_max_color_value / 7 + 1);
	uint bv = b / (gx_max_color_value / 7 + 1);
	return (gx_color_index)
		(rv == gv && gv == bv ? rv + (256-7) :
		 (rv << 5) + (gv << 2) + (bv >> 1));
}
int
mgr_8bit_map_color_rgb(gx_device *dev, gx_color_index color,
  gx_color_value prgb[3])
{	static const gx_color_value ramp[8] =
	{	0, gx_max_color_value / 6, gx_max_color_value / 3,
		gx_max_color_value / 2, 2 * (gx_max_color_value / 3),
		5 * (gx_max_color_value / 6), gx_max_color_value,
		/* The 8th entry is not actually ever used, */
		/* except to fill out the palette. */
		gx_max_color_value
	};
#define icolor (uint)color
	if ( icolor >= 256-7 )
	{	prgb[0] = prgb[1] = prgb[2] = ramp[icolor - (256-7)];
	}
	else
	{	prgb[0] = ramp[(icolor >> 5) & 7];
		prgb[1] = ramp[(icolor >> 2) & 7];
		prgb[2] = ramp[(icolor & 3) << 1];
	}
#undef icolor
	return 0;
}


/* convert the 8-bit look-up table into the standard MGR look-up table */
private unsigned int
clut2mgr(
  register int v,		/* value in clut */
  register int bits		/* number of bits in clut */
)
{
  register unsigned int i;

  i = (unsigned int) 0xffffffff / ((1<<bits)-1);
  return((v*i)/0x10000);
}


/*
 * s w a p _ b w o r d s
 */
private void
swap_bwords(register unsigned char *p, int n)
{
  register unsigned char c;

  n /= 2;
  
  for (; n > 0; n--, p += 2) {
    c    = p[0];
    p[0] = p[1];
    p[1] = c;
  }
}
