/*
 * "$Id$"
 *
 *   Print plug-in CANON BJL driver for the GIMP.
 *
 *   Copyright 1997-2000 Michael Sweet (mike@easysw.com) and
 *	Robert Krawitz (rlk@alum.mit.edu)
 *
 *   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, USA.
 */

/*
 * This file must include only standard C header files.  The core code must
 * compile on generic platforms that don't support glib, gimp, gtk, etc.
 */


/* TODO-LIST
 *
 *   * implement the left border
 *
 */

#include <stdarg.h>
#include "print.h"

/*
 * Local functions...
 */

typedef struct {
  int model;
  int max_width;      /* maximum printable paper size */
  int max_height;
  int max_xdpi;
  int max_ydpi;
  int max_quality;
  int border_left;
  int border_right;
  int border_top;
  int border_bottom;
  int inks;           /* installable cartridges (CANON_INK_*) */
  int slots;          /* available paperslots */
  int features;       /* special bjl settings */
} canon_cap_t;

static void canon_write_line(FILE *, canon_cap_t, int,
			     unsigned char *, int,
			     unsigned char *, int,
			     unsigned char *, int,
			     unsigned char *, int,
			     unsigned char *, int,
			     unsigned char *, int,
			     unsigned char *, int,
			     int, int, int, int);

#define PHYSICAL_BPI 1440
#define MAX_OVERSAMPLED 4
#define MAX_BPP 2
#define BITS_PER_BYTE 8
#define COMPBUFWIDTH (PHYSICAL_BPI * MAX_OVERSAMPLED * MAX_BPP * \
	MAX_CARRIAGE_WIDTH / BITS_PER_BYTE)


/* Codes for possible ink-tank combinations.
 * Each combo is represented by the colors that can be used with
 * the installed ink-tank(s)
 * Combinations of the codes represent the combinations allowed for a model
 * Note that only preferrable combinations should be used 
 */
#define CANON_INK_K           1
#define CANON_INK_CMY         2
#define CANON_INK_CMYK        4
#define CANON_INK_CcMmYK      8
#define CANON_INK_CcMmYy     16
#define CANON_INK_CcMmYyK    32

#define CANON_INK_BLACK_MASK (CANON_INK_K|CANON_INK_CMYK|\
                              CANON_INK_CcMmYK|CANON_INK_CcMmYyK)

#define CANON_INK_PHOTO_MASK (CANON_INK_CcMmYy|CANON_INK_CcMmYK|\
                              CANON_INK_CcMmYyK)

/* document feeding */
#define CANON_SLOT_ASF1    1
#define CANON_SLOT_ASF2    2
#define CANON_SLOT_MAN1    4
#define CANON_SLOT_MAN2    8

/* model peculiarities */
#define CANON_CAP_DMT       1<<0    /* Drop Modulation Technology */
#define CANON_CAP_MSB_FIRST 1<<1    /* how to send data           */
#define CANON_CAP_CMD61     1<<2    /* uses command #0x61         */
#define CANON_CAP_CMD6d     1<<3    /* uses command #0x6d         */
#define CANON_CAP_CMD70     1<<4    /* uses command #0x70         */
#define CANON_CAP_CMD72     1<<5    /* uses command #0x72         */


static canon_cap_t canon_model_capabilities[] =
{
  /* default settings for unkown models */

  {   -1, 8*72,11*72,180,180,20,20,20,20, CANON_INK_K, CANON_SLOT_ASF1, 0 },

  /* tested models */

  { /* Canon BJC 4300 */
    4300,
    618, 936,      /* 8.58" x 13 " */
    1440, 720, 2,
    11, 9, 10, 18,
    CANON_INK_CMYK | CANON_INK_CcMmYK,
    CANON_SLOT_ASF1 | CANON_SLOT_MAN1,
    CANON_CAP_DMT
  },

  { /* Canon BJC 4400 */
    4400,
    9.5*72, 14*72,
    720, 360, 2,
    11, 9, 10, 18,
    CANON_INK_K | CANON_INK_CMYK | CANON_INK_CcMmYK,
    CANON_SLOT_ASF1,
    CANON_CAP_CMD61 | CANON_CAP_DMT
  },

  { /* Canon BJC 6000 */
    6000,
    618, 936,      /* 8.58" x 13 " */
    1440, 720, 2,
    11, 9, 10, 18,
    CANON_INK_CMYK | CANON_INK_CcMmYK,
    CANON_SLOT_ASF1 | CANON_SLOT_MAN1,
    CANON_CAP_DMT
  },

  { /* Canon BJC 8200 */
    8200,
    11*72, 17*72,
    1200,1200, 4,
    11, 9, 10, 18,
    CANON_INK_CMYK | CANON_INK_CcMmYK,
    CANON_SLOT_ASF1,
    CANON_CAP_DMT | CANON_CAP_CMD72
  },

  /* untested models */

  { /* Canon BJC 1000 */
    1000,
    11*72, 17*72,
    360, 360, 2,
    11, 9, 10, 18,
    CANON_INK_K | CANON_INK_CMY,
    CANON_SLOT_ASF1,
    CANON_CAP_CMD61
  },
  { /* Canon BJC 2000 */
    2000,
    11*72, 17*72,
    720, 360, 2,
    11, 9, 10, 18,
    CANON_INK_CMYK,
    CANON_SLOT_ASF1,
    CANON_CAP_CMD61
  },
  { /* Canon BJC 3000 */
    3000,
    11*72, 17*72,
    1440, 720, 2,
    11, 9, 10, 18,
    CANON_INK_CMYK | CANON_INK_CcMmYK,
    CANON_SLOT_ASF1,
    CANON_CAP_CMD61 | CANON_CAP_DMT
  },
  { /* Canon BJC 6100 */
    6100,
    11*72, 17*72,
    1440, 720, 2,
    11, 9, 10, 18,
    CANON_INK_CMYK | CANON_INK_CcMmYK,
    CANON_SLOT_ASF1,
    CANON_CAP_CMD61
  },
  { /* Canon BJC 7000 */
    7000,
    11*72, 17*72,
    1200, 600, 2,
    11, 9, 10, 18,
    CANON_INK_CMYK | CANON_INK_CcMmYyK,
    CANON_SLOT_ASF1,
    0
  },
  { /* Canon BJC 7100 */
    7100,
    11*72, 17*72,
    1200, 600, 2,
    11, 9, 10, 18,
    CANON_INK_CMYK | CANON_INK_CcMmYyK,
    CANON_SLOT_ASF1,
    0
  },

  /* extremely fuzzy models */

  { /* Canon BJC 5100 */
    5100,
    17*72, 22*72,
    1440, 720, 2,
    11, 9, 10, 18,
    CANON_INK_CMYK | CANON_INK_CcMmYK,
    CANON_SLOT_ASF1,
    CANON_CAP_DMT
  },
  { /* Canon BJC 5500 */
    5500,
    22*72, 34*72,
    720, 360, 2,
    11, 9, 10, 18,
    CANON_INK_CMYK | CANON_INK_CcMmYK,
    CANON_SLOT_ASF1,
    CANON_CAP_CMD61
  },
  { /* Canon BJC 6500 */
    6500,
    17*72, 22*72,
    1440, 720, 2,
    11, 9, 10, 18,
    CANON_INK_CMYK | CANON_INK_CcMmYK,
    CANON_SLOT_ASF1,
    CANON_CAP_CMD61 | CANON_CAP_DMT
  },
  { /* Canon BJC 8500 */
    8500,
    17*72, 22*72,
    1200,1200, 2,
    11, 9, 10, 18,
    CANON_INK_CMYK | CANON_INK_CcMmYK,
    CANON_SLOT_ASF1,
    0
  },
};




static canon_cap_t canon_get_model_capabilities(int model)
{
  int i;
  int models= sizeof(canon_model_capabilities) / sizeof(canon_cap_t);
  for (i=0; i<models; i++) {
    if (canon_model_capabilities[i].model == model) {
      return canon_model_capabilities[i];
    }
  }
#ifdef DEBUG
  fprintf(stderr,"canon: model %d not found in capabilities list.\n",model);
#endif
  return canon_model_capabilities[0];
}

static int
canon_media_type(const char *name, canon_cap_t caps)
{
  if (!strcmp(name,"Plain Paper"))           return  1;
  if (!strcmp(name,"Transparencies"))        return  2;
  if (!strcmp(name,"Back Print Film"))       return  3;
  if (!strcmp(name,"Fabric Sheets"))         return  4;
  if (!strcmp(name,"Envelope"))              return  5;
  if (!strcmp(name,"High Resolution Paper")) return  6;
  if (!strcmp(name,"T-Shirt Transfers"))     return  7;
  if (!strcmp(name,"High Gloss Film"))       return  8;
  if (!strcmp(name,"Glossy Photo Paper"))    return  9;
  if (!strcmp(name,"Glossy Photo Cards"))    return 10;
  if (!strcmp(name,"Photo Paper Pro"))       return 11;

#ifdef DEBUG
  fprintf(stderr,"canon: Unknown media type '%s' - reverting to plain\n",name);
#endif
  return 1;
}

static int
canon_source_type(const char *name, canon_cap_t caps)
{
  if (!strcmp(name,"Auto Sheet Feeder"))    return 4;
  if (!strcmp(name,"Manual with Pause"))    return 0;
  if (!strcmp(name,"Manual without Pause")) return 1;

#ifdef DEBUG
  fprintf(stderr,"canon: Unknown source type '%s' - reverting to auto\n",name);
#endif
  return 4;
}

static int
canon_printhead_type(const char *name, canon_cap_t caps)
{
  if (!strcmp(name,"Black"))             return 0;
  if (!strcmp(name,"Color"))             return 1;
  if (!strcmp(name,"Black/Color"))       return 2;
  if (!strcmp(name,"Photo/Color"))       return 3;
  if (!strcmp(name,"Photo"))             return 4;
  if (!strcmp(name,"Black/Photo Color")) return 5;

  if (*name == 0) {
    if (caps.inks & CANON_INK_CMYK) return 2;
    if (caps.inks & CANON_INK_CMY)  return 1;
    if (caps.inks & CANON_INK_K)    return 0;
  }

#ifdef DEBUG
  fprintf(stderr,"canon: Unknown head combo '%s' - reverting to black\n",name);
#endif
  return 0;
}

static unsigned char
canon_size_type(const vars_t *v, canon_cap_t caps)
{
  const papersize_t *pp = get_papersize_by_size(v->page_height, v->page_width);
  if (pp)
    {
      const char *name = pp->name;
      /* built ins: */
      if (!strcmp(name,"A5"))          return 0x01;
      if (!strcmp(name,"A4"))          return 0x03;
      if (!strcmp(name,"B5"))          return 0x08;
      if (!strcmp(name,"Letter"))      return 0x0d;
      if (!strcmp(name,"Legal"))       return 0x0f;
      if (!strcmp(name,"Envelope 10")) return 0x16;
      if (!strcmp(name,"Envelope DL")) return 0x17;
      if (!strcmp(name,"Letter+"))     return 0x2a;
      if (!strcmp(name,"A4+"))         return 0x2b;
      if (!strcmp(name,"Canon 4x2"))   return 0x2d;
      /* custom */

#ifdef DEBUG
      fprintf(stderr,"canon: Unknown paper size '%s' - using custom\n",name);
    } else {
      fprintf(stderr,"canon: Couldn't look up paper size %dx%d - "
	      "using custom\n",v->page_height, v->page_width);
#endif
    }
  return 0;
}

static char *
c_strdup(const char *s)
{
  char *ret = malloc(strlen(s) + 1);
  strcpy(ret, s);
  return ret;
}

const char *
canon_default_resolution(const printer_t *printer)
{
  canon_cap_t caps= canon_get_model_capabilities(printer->model);
  if (!(caps.max_xdpi%300))
    return "300x300 DPI";
  else
    return "180x180 DPI";
}

/*
 * 'canon_parameters()' - Return the parameter values for the given parameter.
 */

char **					/* O - Parameter values */
canon_parameters(const printer_t *printer,	/* I - Printer model */
                 char *ppd_file,	/* I - PPD file (not used) */
                 char *name,		/* I - Name of parameter */
                 int  *count)		/* O - Number of values */
{
  int		i;
  char		**p= 0,
                **valptrs= 0;

  static char   *media_types[] =
                {
                  ("Plain Paper"),
                  ("Transparencies"),
                  ("Back Print Film"),
                  ("Fabric Sheets"),
                  ("Envelope"),
                  ("High Resolution Paper"),
                  ("T-Shirt Transfers"),
                  ("High Gloss Film"),
                  ("Glossy Photo Paper"),
                  ("Glossy Photo Cards"),
                  ("Photo Paper Pro")
                };
  static char   *media_sources[] =
                {
                  ("Auto Sheet Feeder"),
                  ("Manual with Pause"),
                  ("Manual without Pause"),
                };

  canon_cap_t caps= canon_get_model_capabilities(printer->model);

  if (count == NULL)
    return (NULL);

  *count = 0;

  if (name == NULL)
    return (NULL);

  if (strcmp(name, "PageSize") == 0) {
    int length_limit, width_limit;
    const papersize_t *papersizes = get_papersizes();
    valptrs = malloc(sizeof(char *) * known_papersizes());
    *count = 0;

    width_limit = caps.max_width;
    length_limit = caps.max_height;

    for (i = 0; i < known_papersizes(); i++) {
      if (strlen(papersizes[i].name) > 0 &&
	  papersizes[i].width <= width_limit &&
	  papersizes[i].length <= length_limit) {
	valptrs[*count] = malloc(strlen(papersizes[i].name) + 1);
	strcpy(valptrs[*count], papersizes[i].name);
	(*count)++;
      }
    }
    return (valptrs);
  }
  else if (strcmp(name, "Resolution") == 0)
  {
    int x= caps.max_xdpi, y= caps.max_ydpi;
    int c= 0;
    valptrs = malloc(sizeof(char *) * 10);
    if (!(caps.max_xdpi%300)) {

      if ( 300<=x && 300<=y)
	valptrs[c++]= c_strdup("300x300 DPI");
      if ( 300<=x && 300<=y && (caps.features&CANON_CAP_DMT))
	valptrs[c++]= c_strdup("300x300 DPI DMT");
      if ( 600<=x && 600<=y)
	valptrs[c++]= c_strdup("600x600 DPI");
      if ( 600<=x && 600<=y && (caps.features&CANON_CAP_DMT))
	valptrs[c++]= c_strdup("600x600 DPI DMT");
      if (1200==x && 600==y)
	valptrs[c++]= c_strdup("1200x600 DPI");
      if (1200<=x && 1200<=y)
	valptrs[c++]= c_strdup("1200x1200 DPI");

    } else if (!(caps.max_xdpi%180)) {

      if ( 180<=x && 180<=y)
	valptrs[c++]= c_strdup("180x180 DPI");
      if ( 360<=x && 360<=y)
	valptrs[c++]= c_strdup("360x360 DPI");
      if ( 360<=x && 360<=y && (caps.features&CANON_CAP_DMT))
	valptrs[c++]= c_strdup("360x360 DPI DMT");
      if ( 720==x && 360==y)
	valptrs[c++]= c_strdup("720x360 DPI");
      if ( 720<=x && 720<=y)
	valptrs[c++]= c_strdup("720x720 DPI");
      if (1440==x && 720==y)
	valptrs[c++]= c_strdup("1440x720 DPI");
      if (1440<=x &&1440<=y)
	valptrs[c++]= c_strdup("1440x1440 DPI");

    } else {
#ifdef DEBUG
      fprintf(stderr,"canon: unknown resolution multiplier for model %d\n",
	      caps.model);
#endif
      return 0;
    }
    *count= c;
    p= valptrs;
  }
  else if (strcmp(name, "InkType") == 0)
  {
    int c= 0;
    valptrs = malloc(sizeof(char *) * 5);
    if ((caps.inks & CANON_INK_K))
      valptrs[c++]= c_strdup("Black");
    if ((caps.inks & CANON_INK_CMY))
      valptrs[c++]= c_strdup("Color");
    if ((caps.inks & CANON_INK_CMYK))
      valptrs[c++]= c_strdup("Black/Color");
    if ((caps.inks & CANON_INK_CcMmYK))
      valptrs[c++]= c_strdup("Photo/Color");
    if ((caps.inks & CANON_INK_CcMmYy))
      valptrs[c++]= c_strdup("Photo");
    if ((caps.inks & CANON_INK_CcMmYyK))
      valptrs[c++]= c_strdup("Black/Photo Color");
    *count = c;
    p = valptrs;
  }
  else if (strcmp(name, "MediaType") == 0)
  {
    *count = 11;
    p = media_types;
  }
  else if (strcmp(name, "InputSlot") == 0)
  {
    *count = 3;
    p = media_sources;
  }
  else
    return (NULL);

  valptrs = malloc(*count * sizeof(char *));
  for (i = 0; i < *count; i ++)
    valptrs[i] = c_strdup(p[i]);

  return (valptrs);
}


/*
 * 'canon_imageable_area()' - Return the imageable area of the page.
 */

void
canon_imageable_area(const printer_t *printer,	/* I - Printer model */
		     const vars_t *v,   /* I */
                     int  *left,	/* O - Left position in points */
                     int  *right,	/* O - Right position in points */
                     int  *bottom,	/* O - Bottom position in points */
                     int  *top)		/* O - Top position in points */
{
  int	width, length;			/* Size of page */

  canon_cap_t caps= canon_get_model_capabilities(printer->model);

  default_media_size(printer, v, &width, &length);

  *left   = caps.border_left;
  *right  = width - caps.border_right;
  *top    = length - caps.border_top;
  *bottom = caps.border_bottom;
}

void
canon_limit(const printer_t *printer,	/* I - Printer model */
	    const vars_t *v,  		/* I */
	    int  *width,		/* O - Left position in points */
	    int  *length)		/* O - Top position in points */
{
  canon_cap_t caps= canon_get_model_capabilities(printer->model);
  *width =	caps.max_width;
  *length =	caps.max_height;
}

/*
 * 'canon_cmd()' - Sends a command with variable args
 */
static void
canon_cmd(FILE *prn, /* I - the printer         */
	  char *ini, /* I - 2 bytes start code  */
	  char cmd,  /* I - command code        */
	  int  num,  /* I - number of arguments */
	  ...        /* I - the args themselves */
	  )
{
  static int bufsize= 0;
  static unsigned char *buffer;
  int i;
  va_list ap;

  if (!buffer || (num > bufsize)) {
    if (buffer)
      free(buffer);
    buffer = malloc(num);
    bufsize= num;
    if (!buffer) {
#ifdef DEBUG
      fprintf(stderr,"\ncanon: *** buffer allocation failed...\n");
      fprintf(stderr,"canon: *** command 0x%02x with %d args dropped\n\n",
	      cmd,num);
#endif
      return;
    }
  }
  if (num) {
    va_start(ap, num);
    for (i=0; i<num; i++)
      buffer[i]= (unsigned char) va_arg(ap, int);
    va_end(ap);
  }

  fwrite(ini,2,1,prn);
  if (cmd) {
    fputc(cmd,prn);
    fputc((num & 255),prn);
    fputc((num >> 8 ),prn);
    fwrite(buffer,num,1,prn);
  }
}

#ifdef DEBUG
#define PUT(WHAT,VAL,RES) fprintf(stderr,"canon: "WHAT" is %04x =% 5d = %f\" = %f mm\n",(VAL),(VAL),(VAL)/(1.*RES),(VAL)/(RES/25.4))
#else
#define PUT(WHAT,VAL,RES) do {} while (0)
#endif

static void
canon_init_printer(FILE *prn, canon_cap_t caps,
		   int output_type, const char *media_str,
		   const vars_t *v, int print_head,
		   const char *source_str,
		   int xdpi, int ydpi,
		   int page_width, int page_height,
		   int top, int left,
		   int use_dmt)
{
#define MEDIACODES 11
  static unsigned char mediacode_63[] = {
    0x00,0x00,0x02,0x03,0x04,0x08,0x07,0x03,0x06,0x05,0x05,0x09
  };
  static unsigned char mediacode_6c[] = {
    0x00,0x00,0x02,0x03,0x04,0x08,0x07,0x03,0x06,0x05,0x0a,0x09
  };

  #define ESC28 "\x1b\x28"
  #define ESC5b "\x1b\x5b"
  #define ESC40 "\x1b\x40"

  unsigned char
    arg_63_1 = 0x00,
    arg_63_2 = 0x00, /* plain paper */
    arg_63_3 = caps.max_quality, /* output quality  */
    arg_6c_1 = 0x00,
    arg_6c_2 = 0x01, /* plain paper */
    arg_6d_1 = 0x03, /* color printhead? */
    arg_6d_2 = 0x00, /* 00=color  02=b/w */
    arg_6d_3 = 0x00, /* only 01 for bjc8200 */
    arg_6d_a = 0x03, /* A4 paper */
    arg_6d_b = 0x00,
    arg_70_1 = 0x02, /* A4 printable area */
    arg_70_2 = 0xa6,
    arg_70_3 = 0x01,
    arg_70_4 = 0xe0,
    arg_74_1 = 0x01, /* 1 bit per pixel */
    arg_74_2 = 0x00, /*  */
    arg_74_3 = 0x01; /* 01 <= 360 dpi    09 >= 720 dpi */

  int media= canon_media_type(media_str,caps);
  int source= canon_source_type(source_str,caps);

  int printable_width=  page_width*10/12;
  int printable_height= page_height*10/12;

  arg_6d_a= canon_size_type(v,caps);
  if (!arg_6d_a) arg_6d_b= 1;

  if (caps.model<3000)
    arg_63_1= arg_6c_1= 0x10;
  else
    arg_63_1= arg_6c_1= 0x30;

  if (output_type==OUTPUT_GRAY) arg_63_1|= 0x01;
  arg_6c_1|= (source & 0x0f);

  if (print_head==0) arg_6d_1= 0x03;
  else if (print_head<=2) arg_6d_1= 0x02;
  else if (print_head<=4) arg_6d_1= 0x04;
  if (output_type==OUTPUT_GRAY) arg_6d_2= 0x02;

  if (caps.model==8200) arg_6d_3= 0x01;

  arg_70_1= (printable_height >> 8) & 0xff;
  arg_70_2= (printable_height) & 0xff;
  arg_70_3= (printable_width >> 8) & 0xff;
  arg_70_4= (printable_width) & 0xff;

  if (xdpi==1440) arg_74_2= 0x04;
  if (ydpi>=720)  arg_74_3= 0x09;

  if (media<MEDIACODES) {
    arg_63_2= mediacode_63[media];
    arg_6c_2= mediacode_6c[media];
  }

  if (use_dmt) {
    arg_74_1= 0x02;
    arg_74_2= 0x80;
    arg_74_3= 0x09;
  }

  /* workaround for the bjc8200 - not really understood */
  if (caps.model==8200 && use_dmt) {
    arg_74_1= 0xff;
    arg_74_2= 0x90;
    arg_74_3= 0x04;
  }

  /*
#ifdef DEBUG
  fprintf(stderr,"canon: printable size = %dx%d (%dx%d) %02x%02x %02x%02x\n",
	  page_width,page_height,printable_width,printable_height,
	  arg_70_1,arg_70_2,arg_70_3,arg_70_4);
#endif
  */

  /* init printer */

  canon_cmd(prn,ESC5b,0x4b, 2, 0x00,0x0f);
  if (caps.features & CANON_CAP_CMD61)
    canon_cmd(prn,ESC28,0x61, 1, 0x01);
  canon_cmd(prn,ESC28,0x62, 1, 0x01);
  canon_cmd(prn,ESC28,0x71, 1, 0x01);

  if (caps.features & CANON_CAP_CMD6d)
    canon_cmd(prn,ESC28,0x6d,12, arg_6d_1,
	      0xff,0xff,0x00,0x00,0x07,0x00,
	      arg_6d_a,arg_6d_b,arg_6d_2,0x00,arg_6d_3);

  /* set resolution */

  canon_cmd(prn,ESC28,0x64, 4, (ydpi >> 8 ), (ydpi & 255),
	                        (xdpi >> 8 ), (xdpi & 255));

  canon_cmd(prn,ESC28,0x74, 3, arg_74_1, arg_74_2, arg_74_3);

  canon_cmd(prn,ESC28,0x63, 3, arg_63_1, arg_63_2, arg_63_3);

  if (caps.features & CANON_CAP_CMD70)
    canon_cmd(prn,ESC28,0x70, 8, arg_70_1, arg_70_2, 0x00, 0x00,
	                         arg_70_3, arg_70_4, 0x00, 0x00);

  canon_cmd(prn,ESC28,0x6c, 2, arg_6c_1, arg_6c_2);

  if (caps.features & CANON_CAP_CMD72)
    canon_cmd(prn,ESC28,0x72, 1, 0x61); /* whatever for - 8200 might need it */

  /* some linefeeds */

  top= (top*ydpi)/72;
  PUT("topskip ",top,ydpi);
  canon_cmd(prn,ESC28,0x65, 2, (top >> 8 ),(top & 255));
}

void canon_deinit_printer(FILE *prn, canon_cap_t caps)
{
  /* eject page */
  fputc(0x0c,prn);

  /* say goodbye */
  canon_cmd(prn,ESC28,0x62,1,0);
  if (caps.features & CANON_CAP_CMD61)
    canon_cmd(prn,ESC28,0x61, 1, 0x00);
  canon_cmd(prn,ESC40,0,0);
  fflush(prn);
}


/*
 *  'alloc_buffer()' allocates buffer and fills it with 0
 */
static unsigned char *canon_alloc_buffer(int size)
{
  unsigned char *buf= malloc(size);
  if (buf) memset(buf,0,size);
  return buf;
}

/*
 * 'advance_buffer()' - Move (num) lines of length (len) down one line
 *                      and sets first line to 0s
 *                      accepts NULL pointers as buf
 *                  !!! buf must contain more than (num) lines !!!
 *                      also sets first line to 0s if num<1
 */
static void
canon_advance_buffer(unsigned char *buf, int len, int num)
{
  if (!buf || !len) return;
  if (num>0) memmove(buf+len,buf,len*num);
  memset(buf,0,len);
}

/*
 * 'canon_print()' - Print an image to a CANON printer.
 */
void
canon_print(const printer_t *printer,		/* I - Model */
            int       copies,		/* I - Number of copies */
            FILE      *prn,		/* I - File to print to */
	    Image     image,		/* I - Image to print */
	    const vars_t    *v)
{
  unsigned char *cmap = v->cmap;
  int		model = printer->model;
  const char	*resolution = v->resolution;
  const char	*media_type = v->media_type;
  const char	*media_source = v->media_source;
  int 		output_type = v->output_type;
  int		orientation = v->orientation;
  const char	*ink_type = v->ink_type;
  double 	scaling = v->scaling;
  int		top = v->top;
  int		left = v->left;
  int		y;		/* Looping vars */
  int		xdpi, ydpi;	/* Resolution */
  int		n;		/* Output number */
  unsigned short *out;	/* Output pixels (16-bit) */
  unsigned char	*in,		/* Input pixels */
		*black,		/* Black bitmap data */
		*cyan,		/* Cyan bitmap data */
		*magenta,	/* Magenta bitmap data */
		*yellow,	/* Yellow bitmap data */
		*lcyan,		/* Light cyan bitmap data */
		*lmagenta,	/* Light magenta bitmap data */
		*lyellow;	/* Light yellow bitmap data */
  int           delay_k,
                delay_c,
                delay_m,
                delay_y,
                delay_lc,
                delay_lm,
                delay_ly,
                delay_max;
  int		page_left,	/* Left margin of page */
		page_right,	/* Right margin of page */
		page_top,	/* Top of page */
		page_bottom,	/* Bottom of page */
		page_width,	/* Width of page */
		page_height,	/* Height of page */
		page_length,	/* True length of page */
		out_width,	/* Width of image on page */
		out_height,	/* Height of image on page */
		out_bpp,	/* Output bytes per pixel */
		length,		/* Length of raster data */
                buf_length,     /* Length of raster data buffer (dmt) */
		errdiv,		/* Error dividend */
		errmod,		/* Error modulus */
		errval,		/* Current error value */
		errline,	/* Current raster line */
		errlast;	/* Last raster line loaded */
  convert_t	colorfunc = 0;	/* Color conversion function... */
  int           image_height,
                image_width,
                image_bpp;
  int           use_dmt = 0;
  void *	dither;
  double        the_levels[] = { 0.5, 0.75, 1.0 };
  vars_t	nv;

  canon_cap_t caps= canon_get_model_capabilities(model);
  int printhead= canon_printhead_type(ink_type,caps);

  memcpy(&nv, v, sizeof(vars_t));
  /*
  PUT("top        ",top,72);
  PUT("left       ",left,72);
  */

  /*
  * Setup a read-only pixel region for the entire image...
  */

  Image_init(image);
  image_height = Image_height(image);
  image_width = Image_width(image);
  image_bpp = Image_bpp(image);

  /* force grayscale if image is grayscale
   *                 or single black cartridge installed
   */

  if (nv.image_type == IMAGE_MONOCHROME)
    {
      output_type = OUTPUT_GRAY;
    }

  if (printhead == 0 || caps.inks == CANON_INK_K)
    output_type = OUTPUT_GRAY;

  /*
   * Choose the correct color conversion function...
   */

  colorfunc = choose_colorfunc(output_type, image_bpp, cmap, &out_bpp, &nv);

 /*
  * Figure out the output resolution...
  */

  sscanf(resolution,"%dx%d",&xdpi,&ydpi);
#ifdef DEBUG
  fprintf(stderr,"canon: resolution=%dx%d\n",xdpi,ydpi);
#endif

  if (!strcmp(resolution+(strlen(resolution)-3),"DMT") &&
      (caps.features & CANON_CAP_DMT) &&
      nv.image_type != IMAGE_MONOCHROME) {
    use_dmt= 1;
#ifdef DEBUG
    fprintf(stderr,"canon: using drop modulation technology\n");
#endif
  }

 /*
  * Compute the output size...
  */

  canon_imageable_area(printer, &nv, &page_left, &page_right,
                       &page_bottom, &page_top);

  compute_page_parameters(page_right, page_left, page_top, page_bottom,
			  scaling, image_width, image_height, image,
			  &orientation, &page_width, &page_height,
			  &out_width, &out_height, &left, &top);

  /*
   * Recompute the image height and width.  If the image has been
   * rotated, these will change from previously.
   */
  image_height = Image_height(image);
  image_width = Image_width(image);

  default_media_size(printer, &nv, &n, &page_length);

  /*
  PUT("top        ",top,72);
  PUT("left       ",left,72);
  PUT("page_top   ",page_top,72);
  PUT("page_bottom",page_bottom,72);
  PUT("page_left  ",page_left,72);
  PUT("page_right ",page_right,72);
  PUT("page_width ",page_width,72);
  PUT("page_height",page_height,72);
  PUT("page_length",page_length,72);
  PUT("out_width ", out_width,xdpi);
  PUT("out_height", out_height,ydpi);
  */

  Image_progress_init(image);

  PUT("top     ",top,72);
  PUT("left    ",left,72);

  canon_init_printer(prn, caps, output_type, media_type,
		     &nv, printhead, media_source,
		     xdpi, ydpi, page_width, page_height,
		     top,left,use_dmt);

 /*
  * Convert image size to printer resolution...
  */

  out_width  = xdpi * out_width / 72;
  out_height = ydpi * out_height / 72;

  /*
  PUT("out_width ", out_width,xdpi);
  PUT("out_height", out_height,ydpi);
  */

  left = xdpi * left / 72;

  PUT("leftskip",left,xdpi);

  if(xdpi==1440){
    delay_k= 0;
    delay_c= 112;
    delay_m= 224;
    delay_y= 336;
    delay_lc= 112;
    delay_lm= 224;
    delay_ly= 0;
    delay_max= 336;
#ifdef DEBUG
    fprintf(stderr,"canon: delay on!\n");
#endif
  } else {
    delay_k= delay_c= delay_m= delay_y= delay_lc= delay_lm= delay_ly=0;
    delay_max=0;
#ifdef DEBUG
    fprintf(stderr,"canon: delay off!\n");
#endif
  }

 /*
  * Allocate memory for the raster data...
  */

  length = (out_width + 7) / 8;

  if (use_dmt) {
    buf_length= length*2;
  } else {
    buf_length= length;
  }

#ifdef DEBUG
  fprintf(stderr,"canon: buflength is %d!\n",buf_length);
#endif

  if (output_type == OUTPUT_GRAY) {
    black   = canon_alloc_buffer(buf_length*(delay_k+1));
    cyan    = NULL;
    magenta = NULL;
    lcyan   = NULL;
    lmagenta= NULL;
    yellow  = NULL;
    lyellow = NULL;
  } else {
    cyan    = canon_alloc_buffer(buf_length*(delay_c+1));
    magenta = canon_alloc_buffer(buf_length*(delay_m+1));
    yellow  = canon_alloc_buffer(buf_length*(delay_y+1));

    if ((caps.inks & CANON_INK_BLACK_MASK))
      black = canon_alloc_buffer(buf_length*(delay_k+1));
    else
      black = NULL;

    if (printhead==3 && (caps.inks & (CANON_INK_PHOTO_MASK))) {
      lcyan = canon_alloc_buffer(buf_length*(delay_lc+1));
      lmagenta = canon_alloc_buffer(buf_length*(delay_lm+1));
      if ((caps.inks & CANON_INK_CcMmYy) || 
	  (caps.inks & CANON_INK_CcMmYyK))
	lyellow = canon_alloc_buffer(buf_length*(delay_lc+1));
      else
	lyellow = NULL;
    } else {
      lcyan = NULL;
      lmagenta = NULL;
      lyellow = NULL;
    }
  }

#ifdef DEBUG
  fprintf(stderr,"canon: driver will use colors ");
  if (cyan)     fputc('C',stderr);
  if (lcyan)    fputc('c',stderr);
  if (magenta)  fputc('M',stderr);
  if (lmagenta) fputc('m',stderr);
  if (yellow)   fputc('Y',stderr);
  if (lyellow)  fputc('y',stderr);
  if (black)    fputc('K',stderr);
  fprintf(stderr,"\n");
#endif

#ifdef DEBUG
  fprintf(stderr,"density is %f\n",nv.density);
#endif

  nv.density = (nv.density * ydpi) / (xdpi * 1.0);
  if (nv.density > 1.0)
    nv.density = 1.0;
  compute_lut(256, &nv);

#ifdef DEBUG
  fprintf(stderr,"density is %f\n",nv.density);
#endif

  if (xdpi > ydpi)
    dither = init_dither(image_width, out_width, 1, xdpi / ydpi, &nv);
  else
    dither = init_dither(image_width, out_width, ydpi / xdpi, 1, &nv);

  dither_set_black_levels(dither, 1.0, 1.0, 1.0);
  dither_set_black_lower(dither, .8 / ((1 << (use_dmt+1)) - 1));
  /*
  if (use_glossy_film)
  */
  dither_set_black_upper(dither, .999);
  /*
  else
    dither_set_black_upper(dither, .999);
  */

  if (!use_dmt) {
    dither_set_light_inks(dither,
			  (lcyan)   ? (0.3333) : (0.0),
			  (lmagenta)? (0.3333) : (0.0),
			  (lyellow) ? (0.3333) : (0.0), nv.density);
  }

  switch (nv.image_type)
    {
    case IMAGE_LINE_ART:
      dither_set_ink_spread(dither, 19);
      break;
    case IMAGE_SOLID_TONE:
      dither_set_ink_spread(dither, 15);
      break;
    case IMAGE_CONTINUOUS:
      dither_set_ink_spread(dither, 14);
      break;
    }
  dither_set_density(dither, nv.density);

  if (use_dmt)
    {
      dither_set_c_ranges_simple(dither, 3, the_levels, nv.density);
      dither_set_m_ranges_simple(dither, 3, the_levels, nv.density);
      dither_set_y_ranges_simple(dither, 3, the_levels, nv.density);
      dither_set_k_ranges_simple(dither, 3, the_levels, nv.density);
    }
 /*
  * Output the page...
  */

  in  = malloc(image_width * image_bpp);
  out = malloc(image_width * out_bpp * 2);

  errdiv  = image_height / out_height;
  errmod  = image_height % out_height;
  errval  = 0;
  errlast = -1;
  errline  = 0;

  for (y = 0; y < out_height; y ++)
  {
    int duplicate_line = 1;
    if ((y & 63) == 0)
      Image_note_progress(image, y, out_height);

    if (errline != errlast)
    {
      errlast = errline;
      duplicate_line = 0;
      Image_get_row(image, in, errline);
      (*colorfunc)(in, out, image_width, image_bpp, cmap, &nv);
    }

    if (nv.image_type == IMAGE_MONOCHROME)
      dither_monochrome(out, y, dither, black, duplicate_line);
    else if (output_type == OUTPUT_GRAY)
      dither_black(out, y, dither, black, duplicate_line);
    else
      dither_cmyk(out, y, dither, cyan, lcyan, magenta, lmagenta,
		  yellow, 0, black, duplicate_line);

#ifdef DEBUG
    /* fprintf(stderr,","); */
#endif

    canon_write_line(prn, caps, ydpi,
		     black,    delay_k,
		     cyan,     delay_c,
		     magenta,  delay_m,
		     yellow,   delay_y,
		     lcyan,    delay_lc,
		     lmagenta, delay_lm,
		     lyellow,  delay_ly,
		     length, out_width, left, use_dmt);

#ifdef DEBUG
    /* fprintf(stderr,"!"); */
#endif

    canon_advance_buffer(black,   buf_length,delay_k);
    canon_advance_buffer(cyan,    buf_length,delay_c);
    canon_advance_buffer(magenta, buf_length,delay_m);
    canon_advance_buffer(yellow,  buf_length,delay_y);
    canon_advance_buffer(lcyan,   buf_length,delay_lc);
    canon_advance_buffer(lmagenta,buf_length,delay_lm);
    canon_advance_buffer(lyellow, buf_length,delay_ly);

    errval += errmod;
    errline += errdiv;
    if (errval >= out_height)
    {
      errval -= out_height;
      errline ++;
    }
  }
  Image_progress_conclude(image);

  free_dither(dither);

  /*
   * Flush delayed buffers...
   */

  if (delay_max) {
#ifdef DEBUG
    fprintf(stderr,"\ncanon: flushing %d possibly delayed buffers\n",
	    delay_max);
#endif
    for (y= 0; y<delay_max; y++) {

      canon_write_line(prn, caps, ydpi,
		       black,    delay_k,
		       cyan,     delay_c,
		       magenta,  delay_m,
		       yellow,   delay_y,
		       lcyan,    delay_lc,
		       lmagenta, delay_lm,
		       lyellow,  delay_ly,
		       length, out_width, left, use_dmt);

#ifdef DEBUG
      /* fprintf(stderr,"-"); */
#endif

      canon_advance_buffer(black,   buf_length,delay_k);
      canon_advance_buffer(cyan,    buf_length,delay_c);
      canon_advance_buffer(magenta, buf_length,delay_m);
      canon_advance_buffer(yellow,  buf_length,delay_y);
      canon_advance_buffer(lcyan,   buf_length,delay_lc);
      canon_advance_buffer(lmagenta,buf_length,delay_lm);
      canon_advance_buffer(lyellow, buf_length,delay_ly);
    }
  }

 /*
  * Cleanup...
  */

  free_lut(&nv);
  free(in);
  free(out);

  if (black != NULL)    free(black);
  if (cyan != NULL)     free(cyan);
  if (magenta != NULL)  free(magenta);
  if (yellow != NULL)   free(yellow);
  if (lcyan != NULL)    free(lcyan);
  if (lmagenta != NULL) free(lmagenta);
  if (lyellow != NULL)  free(lyellow);

  canon_deinit_printer(prn, caps);
}

/*
 * 'canon_fold_lsb_msb()' fold 2 lines in order lsb/msb
 */

static void
canon_fold_lsb_msb(const unsigned char *line,
		   int single_length,
		   unsigned char *outbuf)
{
  int i;
  for (i = 0; i < single_length; i++) {
    outbuf[0] =
      ((line[0] & (1 << 7)) >> 1) |
      ((line[0] & (1 << 6)) >> 2) |
      ((line[0] & (1 << 5)) >> 3) |
      ((line[0] & (1 << 4)) >> 4) |
      ((line[single_length] & (1 << 7)) >> 0) |
      ((line[single_length] & (1 << 6)) >> 1) |
      ((line[single_length] & (1 << 5)) >> 2) |
      ((line[single_length] & (1 << 4)) >> 3);
    outbuf[1] =
      ((line[0] & (1 << 3)) << 3) |
      ((line[0] & (1 << 2)) << 2) |
      ((line[0] & (1 << 1)) << 1) |
      ((line[0] & (1 << 0)) << 0) |
      ((line[single_length] & (1 << 3)) << 4) |
      ((line[single_length] & (1 << 2)) << 3) |
      ((line[single_length] & (1 << 1)) << 2) |
      ((line[single_length] & (1 << 0)) << 1);
    line++;
    outbuf += 2;
  }
}

/*
 * 'canon_fold_msb_lsb()' fold 2 lines in order msb/lsb
 */

static void
canon_fold_msb_lsb(const unsigned char *line,
		   int single_length,
		   unsigned char *outbuf)
{
  int i;
  for (i = 0; i < single_length; i++) {
    outbuf[0] =
      ((line[0] & (1 << 7)) >> 0) |
      ((line[0] & (1 << 6)) >> 1) |
      ((line[0] & (1 << 5)) >> 2) |
      ((line[0] & (1 << 4)) >> 3) |
      ((line[single_length] & (1 << 7)) >> 1) |
      ((line[single_length] & (1 << 6)) >> 2) |
      ((line[single_length] & (1 << 5)) >> 3) |
      ((line[single_length] & (1 << 4)) >> 4);
    outbuf[1] =
      ((line[0] & (1 << 3)) << 4) |
      ((line[0] & (1 << 2)) << 3) |
      ((line[0] & (1 << 1)) << 2) |
      ((line[0] & (1 << 0)) << 1) |
      ((line[single_length] & (1 << 3)) << 3) |
      ((line[single_length] & (1 << 2)) << 2) |
      ((line[single_length] & (1 << 1)) << 1) |
      ((line[single_length] & (1 << 0)) << 0);
    line++;
    outbuf += 2;
  }
}

static void
canon_pack(unsigned char *line,
	   int length,
	   unsigned char *comp_buf,
	   unsigned char **comp_ptr)
{
  unsigned char *start;			/* Start of compressed data */
  unsigned char repeat;			/* Repeating char */
  int count;			/* Count of compressed bytes */
  int tcount;			/* Temporary count < 128 */

  /*
   * Compress using TIFF "packbits" run-length encoding...
   */

  (*comp_ptr) = comp_buf;

  while (length > 0)
    {
      /*
       * Get a run of non-repeated chars...
       */

      start  = line;
      line   += 2;
      length -= 2;

      while (length > 0 && (line[-2] != line[-1] || line[-1] != line[0]))
	{
	  line ++;
	  length --;
	}

      line   -= 2;
      length += 2;

      /*
       * Output the non-repeated sequences (max 128 at a time).
       */

      count = line - start;
      while (count > 0)
	{
	  tcount = count > 128 ? 128 : count;

	  (*comp_ptr)[0] = tcount - 1;
	  memcpy((*comp_ptr) + 1, start, tcount);

	  (*comp_ptr) += tcount + 1;
	  start    += tcount;
	  count    -= tcount;
	}

      if (length <= 0)
	break;

      /*
       * Find the repeated sequences...
       */

      start  = line;
      repeat = line[0];

      line ++;
      length --;

      while (length > 0 && *line == repeat)
	{
	  line ++;
	  length --;
	}

      /*
       * Output the repeated sequences (max 128 at a time).
       */

      count = line - start;
      while (count > 0)
	{
	  tcount = count > 128 ? 128 : count;

	  (*comp_ptr)[0] = 1 - tcount;
	  (*comp_ptr)[1] = repeat;

	  (*comp_ptr) += 2;
	  count    -= tcount;
	}
    }
}


/*
 * 'canon_write()' - Send graphics using TIFF packbits compression.
 */

static int
canon_write(FILE          *prn,		/* I - Print file or command */
	    canon_cap_t   caps,	        /* I - Printer model */
	    unsigned char *line,	/* I - Output bitmap data */
	    int           length,	/* I - Length of bitmap data */
	    int           coloridx,	/* I - Which color */
	    int           ydpi,		/* I - Vertical resolution */
	    int           *empty,       /* IO- Preceeding empty lines */
	    int           width,	/* I - Printed width */
	    int           offset, 	/* I - Offset from left side */
	    int           dmt)
{
  unsigned char
    comp_buf[COMPBUFWIDTH],		/* Compression buffer */
    in_fold[COMPBUFWIDTH],
    *in_ptr= line,
    *comp_ptr, *comp_data;
  int newlength;
  unsigned char color;

 /* Don't send blank lines... */

  if (line[0] == 0 && memcmp(line, line + 1, length - 1) == 0)
    return 0;

  /* fold lsb/msb pairs if drop modulation is active */

  if (dmt) {
    if (1) {
      if (caps.features & CANON_CAP_MSB_FIRST)
	canon_fold_msb_lsb(line,length,in_fold);
      else
	canon_fold_lsb_msb(line,length,in_fold);
      in_ptr= in_fold;
    }
    length*= 2;
    offset*= 2;
  }

  /* pack left border rounded to multiples of 8 dots */

  comp_data= comp_buf;
  if (offset) {
    int offset2= (offset+4)/8;
    while (offset2>0) {
      unsigned char toffset = offset2 > 128 ? 128 : offset2;
      comp_data[0] = 1 - toffset;
      comp_data[1] = 0;
      comp_data += 2;
      offset2-= toffset;
    }
  }

  canon_pack(in_ptr, length, comp_data, &comp_ptr);
  newlength= comp_ptr - comp_buf;

  /* send packed empty lines if any */

  if (*empty) {
#ifdef DEBUG
    /* fprintf(stderr,"<%d%c>",*empty,("CMYKcmy"[coloridx])); */
#endif
    fwrite("\x1b\x28\x65\x02\x00", 5, 1, prn);
    fputc((*empty) >> 8 , prn);
    fputc((*empty) & 255, prn);
    *empty= 0;
  }

 /* Send a line of raster graphics... */

  fwrite("\x1b\x28\x41", 3, 1, prn);
  putc((newlength+1) & 255, prn);
  putc((newlength+1) >> 8, prn);
  color= "CMYKcmy"[coloridx];
  if (!color) color= 'K';
  putc(color,prn);
  fwrite(comp_buf, newlength, 1, prn);
  putc('\x0d', prn);
  return 1;
}


static void
canon_write_line(FILE          *prn,	/* I - Print file or command */
		 canon_cap_t   caps,	/* I - Printer model */
		 int           ydpi,	/* I - Vertical resolution */
		 unsigned char *k,	/* I - Output bitmap data */
		 int           dk,	/* I -  */
		 unsigned char *c,	/* I - Output bitmap data */
		 int           dc,	/* I -  */
		 unsigned char *m,	/* I - Output bitmap data */
		 int           dm,	/* I -  */
		 unsigned char *y,	/* I - Output bitmap data */
		 int           dy,	/* I -  */
		 unsigned char *lc,	/* I - Output bitmap data */
		 int           dlc,	/* I -  */
		 unsigned char *lm,	/* I - Output bitmap data */
		 int           dlm,	/* I -  */
		 unsigned char *ly,	/* I - Output bitmap data */
		 int           dly,	/* I -  */
		 int           l,	/* I - Length of bitmap data */
		 int           width,	/* I - Printed width */
		 int           offset,  /* I - horizontal offset */
		 int           dmt)
{
  static int empty= 0;
  int written= 0;

  if (k) written+=
    canon_write(prn, caps, k+ dk*l,  l, 3, ydpi, &empty, width, offset, dmt);
  if (y) written+=
    canon_write(prn, caps, y+ dy*l,  l, 2, ydpi, &empty, width, offset, dmt);
  if (m) written+=
    canon_write(prn, caps, m+ dm*l,  l, 1, ydpi, &empty, width, offset, dmt);
  if (c) written+=
    canon_write(prn, caps, c+ dc*l,  l, 0, ydpi, &empty, width, offset, dmt);
  if (ly) written+=
    canon_write(prn, caps, ly+dly*l, l, 6, ydpi, &empty, width, offset, dmt);
  if (lm) written+=
    canon_write(prn, caps, lm+dlm*l, l, 5, ydpi, &empty, width, offset, dmt);
  if (lc) written+=
    canon_write(prn, caps, lc+dlc*l, l, 4, ydpi, &empty, width, offset, dmt);

  if (written)
    fwrite("\x1b\x28\x65\x02\x00\x00\x01", 7, 1, prn);
  else
    empty++;
}
