/* Copyright (C) 1989, 1995, 1996 Aladdin Enterprises.  All rights reserved.
  
  This file is part of Aladdin Ghostscript.
  
  Aladdin Ghostscript is distributed with NO WARRANTY OF ANY KIND.  No author
  or distributor accepts any responsibility for the consequences of using it,
  or for whether it serves any particular purpose or works at all, unless he
  or she says so in writing.  Refer to the Aladdin Ghostscript Free Public
  License (the "License") for full details.
  
  Every copy of Aladdin Ghostscript must include a copy of the License,
  normally in a plain ASCII text file named PUBLIC.  The License grants you
  the right to copy, modify and redistribute Aladdin Ghostscript, but only
  under certain conditions described in the License.  Among other things, the
  License requires that the copyright notice and this notice be preserved on
  all copies.
*/

/* $Id: gdevepag.c,v 1.9 1999/02/23 11:50:22 nari Exp $ */
/* Many parts of this file are copied from gdevdjet.c */
/* Epson ESC/Page driver for Ghostscript */

#include "gdevprn.h"

#define ESC 0x1b    /* \033 */
#define GS  0x1d    /* \035 */
#ifndef assert
# define assert(x)
#endif

#define X_DPI 300
#define Y_DPI 300

/*
 *  struct epag_cont: gsの-dオプションで指定可能なパラメータ
 */
static struct _epag_cont{
  bool tumble;
  bool noPaperSelect; /* 用紙サイズ選択コードを出力しない */
  float offX;         /* X方向オフセット、 右側が正 単位 mm */
  float offY;         /* Y方向オフセット、 上側が正 単位 mm */
  int cRowBuf;

  bool bSkipBlank;    /* skip blankを行う           */
  bool bShowBubble;   /* debug用に bubbleを印刷する */
  int nBw;            /* block width (byte,8dot)    */
  int nBh;            /* block height(line)         */
  
  bool bEpsonRemote;  /* Epson/Remoteの制御コードを出力 */
} epag_cont =
{
  0,          /* tumble */
  0,          /* noPaperSelect */
  0.0,        /* offX          */
  0.0,        /* offY          */
  256,        /* cRowBuf       */
  1,          /* bSkipBlank    */
  0,          /* bShowBubble   */ 
  4,          /* block width(byte)  */  
  32,         /* block height(line) */
  0,          /* bEpsonRemote */ 
};

/*
 * EpagPageCont:    印刷中の各種情報を保持する構造体
 */
typedef struct {
  gx_device_printer *pdev;
  FILE      *fp;

  int x0,y0;           /* pixel座標原点のプリンタ座標での位置 */
  
  int bw;
  int bh;
  int maxBx;
  int maxBy;

  /* イメージバッファ */
  
  int  bpl;
  int  maxY;   /* バッファの row数, bhの倍数 */
  int  r;      /* バッファに中のデータの開始row番号 */
  int  h;      /* 有効なデータのrowの数   [r:r+h-1]のrowがバッファにある*/
  byte *bp;    /* イメージバッファ */
  byte *bp2;   /* 圧縮用バッファ */
  int  ibp2;   /* bp2のポインタ */
    
  /* Bubble */

  struct _epagBubble **bubbleTbl;
  struct _epagBubble *freeBubbleList;
  byte *bubbleBuffer; 
  
} EpagPageCont;

/*
 *    EpagBubble:  印字データのある矩形を記憶する構造体
 */
typedef struct _epagBubble{
  struct _epagBubble *next;
  int x0,y0,x1,y1;             /* 位置、byte単位 */
} EpagBubble;

/* The device descriptors */
private dev_proc_open_device(epag_open);
private dev_proc_close_device(epag_close);
private dev_proc_print_page(epag_print_page);
private void epag_printer_initialize(gx_device_printer *pdev, FILE *fp,int);
private dev_proc_print_page(epag_print_page);
private dev_proc_get_params(epag_get_params);
private dev_proc_put_params(epag_put_params);
private void epag_paper_set(gx_device_printer *pdev, FILE *fp);
private void epag_bubble_flush_all(EpagPageCont *cont);
private void epag_page_cont_init(gx_device_printer *pdev,FILE *fp,EpagPageCont *cont);
private void epag_page_close(EpagPageCont *cont);
private int epag_read_image(EpagPageCont *cont);
private void epag_process_line(EpagPageCont *cont);
private int epag_is_black(EpagPageCont *cont, int bx);
private void epag_rect_add(EpagPageCont *cont,int start,int end);
private void  epag_bubble_gen(EpagPageCont *cont,
			      int x0, int x1, int y0, int y1);
private void epag_bubble_flush(EpagPageCont *cont,EpagBubble *bbl);
private void epag_bubble_image_out(EpagPageCont *cont,EpagBubble *bbl);

/*
 *   デバイスの宣言
 */
private gx_device_procs prn_epag_procs =
prn_params_procs(epag_open, gdev_prn_output_page, epag_close,
		 epag_get_params, epag_put_params);
gx_device_printer far_data gs_epag_device =
prn_device(prn_epag_procs, "epag", 
	   DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
	   X_DPI, Y_DPI,
	   0.0, 0.0, 0.0, 0.0,	/* margins */
	   1, epag_print_page);

#define ppdev ((gx_device_printer *)pdev)

static char *epson_remote_start = "\033\001@EJL \r\n";

/* Open the printer, adjusting the margins if necessary. */
private int
epag_open(gx_device *pdev)
{
  return gdev_prn_open(pdev);
}

private int
epag_close(gx_device *pdev)
{
  gdev_prn_open_printer(pdev, 1);
  if(ppdev->Duplex && (pdev->PageCount & 1)){
    fprintf(ppdev->file ,"%c%dY%c%dX", GS, 0, GS, 0);
    fprintf(ppdev->file,"Blank Page\r\n");
    fprintf(ppdev->file,"%c0dpsE",GS);
  }

  if(epag_cont.bEpsonRemote){
    fputs(epson_remote_start, ppdev->file);
    fputs(epson_remote_start, ppdev->file);
  }
  return gdev_prn_close(pdev);
}

private int 
epag_print_page(gx_device_printer *pdev, FILE *fp)
{
  EpagPageCont cont;
  int y;
  int code = 0;
  
  /* printer initialize */
  
  if(pdev->PageCount == 0) 
    epag_printer_initialize(pdev, fp, 1);

  epag_page_cont_init(pdev,fp,&cont);   /* contの初期化 */

  for(y = 0; y < cont.maxBy; y++){
    code = epag_read_image(&cont);
    if(code < 0) return code;
    epag_process_line(&cont);
  }
  epag_bubble_flush_all(&cont);         /* bubbleのflush */
  epag_page_close(&cont);               /* 後処理:メモリの解放等 */
  
  if(pdev->Duplex)
    fprintf(fp,"%c0dpsE",GS);
  else
    fprintf(fp,"\014"); /* eject page */
  return code;
}

/* output printer initialize code */

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

static char can_inits[] ={
  ESC, 'S', ESC, ESC, 'S', 034,
  ESC, 'z', 0, 0,                  /* ESC Page モードへ  */
  GS, 'r', 'h', 'E',               /* hard reset         */
  GS, '1', 'm', 'm', 'E',          /* ページメモリモード */
  GS, '3', 'b', 'c', 'I',          /* 圧縮形式           */
  GS, '2', 'a', 'b', 'P',          /* イメージ描画後下へ */
};

private void
epag_printer_initialize(gx_device_printer *pdev, FILE *fp, int copies)
{
  double xDpi,yDpi;
  char *pse = NULL;
  int paper_size;
  
  xDpi = pdev->x_pixels_per_inch;
  yDpi = pdev->y_pixels_per_inch;

  /* Epson/Remoteの制御コード出力 */
  
  if(epag_cont.bEpsonRemote){
    fputs(epson_remote_start, fp);
    fprintf(fp,"@EJL SELECT LANGUAGE=ESC/PAGE\r\n");
    fprintf(fp,"@EJL SET RS=%s \r\n", xDpi > 300 ? "FN" : "QK");
    fprintf(fp,"@EJL ENTER LANGUAGE=ESC/PAGE\r\n");
  }
  
  /* 初期化コード */
  
  fwrite(can_inits, sizeof(can_inits), 1, fp);
  
  /* 両面印刷の設定 */
  if(pdev->Duplex_set > 0){
    if(pdev->Duplex){
      fprintf(fp,"%c1sdE",GS);
      if(epag_cont.tumble==0)
	fprintf(fp,"%c0bdE",GS);
      else 
	fprintf(fp,"%c1bdE",GS);
    }
    else
      fprintf(fp,"%c0sdE",GS);
  }
    
  /* 最小単位設定  */
  
  fprintf(fp,"%c0;%4.2fmuE",GS, 72.0 / xDpi );

  /* 解像度設定     */
  
  fprintf(fp,"%c0;%d;%ddrE",GS, (int)(xDpi+0.5),(int)(yDpi+0.5));

  /* 用紙サイズ設定 */
  
  if(!epag_cont.noPaperSelect)
    epag_paper_set(pdev,fp);
  
  /* コピー枚数設定 */

  if(copies > 1)
    fprintf(fp,"%c%dcoO",GS, copies < 256 ? copies : 255);
}

private int
epag_get_params(gx_device *pdev, gs_param_list *plist)
{
  int code;

  if(ppdev->Duplex_set < 0)
    ppdev->Duplex_set = 0;  /* 両面印刷を可能にする */

  if((code = gdev_prn_get_params(pdev, plist)) < 0 ||
     (code = param_write_int(plist, "cRowBuf", &epag_cont.cRowBuf)) < 0 ||
     (code = param_write_bool(plist, "Tumble", &epag_cont.tumble)) < 0 || 
     (code = param_write_bool(plist, "EpagNoPaperSelect",
			      &epag_cont.noPaperSelect)) < 0 || 
     (code = param_write_float(plist, "EpagOffX", &epag_cont.offX)) < 0 ||
     (code = param_write_float(plist, "EpagOffY", &epag_cont.offY)) < 0 ||
     (code=param_write_bool(plist,"EpagSkipBlank",&epag_cont.bSkipBlank))<0 ||
     (code=param_write_bool(plist,"EpagShowBubble",&epag_cont.bShowBubble))<0 ||
     (code=param_write_int(plist,"EpagBlockWidth",&epag_cont.nBw))<0 ||
     (code=param_write_int(plist,"EpagBlockHeight",&epag_cont.nBh))<0 ||
     (code=param_write_bool(plist,"EpagEpsonRemote",&epag_cont.bEpsonRemote))<0
     )
    return code;
  return code;
}

/* Put properties. */
private int
epag_put_params(gx_device *pdev, gs_param_list *plist)
{
  param_read_int(plist, "cRowBuf", &epag_cont.cRowBuf);
  param_read_bool(plist, "Tumble", &epag_cont.tumble);
  param_read_bool(plist, "EpagNoPaperSelect", &epag_cont.noPaperSelect);
  param_read_float(plist, "EpagOffX", &epag_cont.offX);
  param_read_float(plist, "EpagOffY", &epag_cont.offY);
  param_read_bool(plist, "EpagSkipBlank", &epag_cont.bSkipBlank);
  param_read_bool(plist, "EpagShowBubble",&epag_cont.bShowBubble);
  param_read_int(plist,  "EpagBlockWidth", &epag_cont.nBw);
  param_read_int(plist,  "EpagBlockHeight",&epag_cont.nBh);
  param_read_bool(plist, "EpagEpsonRemote",&epag_cont.bEpsonRemote);
  gdev_prn_put_params(pdev, plist);
  return 0;
}

/*
 * EpagPaperTable: 用紙のサイズの情報を保持する構造体
 */
typedef struct {
  int width;           /* 用紙幅  単位 point */
  int height;          /* 用紙高  単位 point */
  int escpage;         /* ESC/Pageの用紙設定パラメータ */
} EpagPaperTable;

static EpagPaperTable epagPaperTable[] =
{
  { 842, 1190, 13 }, /* A3 */
  { 595,  842, 14 }, /* A4 */
  { 597,  842, 14 }, /* A4     8.3インチ × 11.7インチ */
  { 421,  595, 15 }, /* A5 */
  { 297,  421, 16 }, /* A6 */
  { 709, 1002, 24 }, /* B4 ISO */
  { 501,  709, 25 }, /* B5 ISO */
  { 729, 1032, 24 }, /* B4 JIS */
  { 516,  729, 25 }, /* B5 JIS */
  { 612,  792, 30 }, /* Letter */
  { 396,  612, 31 }, /* Half Letter */
  { 612, 1008, 32 }, /* Legal */
  { 522,  756, 33 }, /* Executive */
  { 612,  936, 34 }, /* Government Legal */
  { 576,  756, 35 }, /* Government Letter */
  { 792, 1224, 36 }, /* Ledger */
  { 593,  935, 37 }, /* F4 */
  { 284,  419, 38 }, /* はがき */
  { 933, 1369, 72 }, /* A3ノビ */
  { 279,  540, 80 }, /* Monarch */
  { 297,  684, 81 }, /* Commercial 10 */
  { 312,  624, 90 }, /* DL */
  { 459,  649, 91 }, /* C5 */
  { 0,0,-1 }, /* 定型外 */
};

void epag_paper_set(gx_device_printer *pdev, FILE *fp)
{
  int i,width,height,w,h,wp,hp,bLandscape;
  EpagPaperTable *pt;

  width  = pdev->MediaSize[0];
  height = pdev->MediaSize[1];

  if(width < height) {
    bLandscape = 0;
    w = width;
    h = height;
    wp = width  / 72.0 * pdev->x_pixels_per_inch;
    hp = height / 72.0 * pdev->y_pixels_per_inch;
  }
  else {
    bLandscape = 1;
    w = height;
    h = width;
    wp = height / 72.0 * pdev->y_pixels_per_inch;
    hp = width  / 72.0 * pdev->x_pixels_per_inch;
  }

  for(pt = epagPaperTable; pt->escpage > 0; pt++)
    if(pt->width == w && pt->height == h)
      break;

  if(pt->escpage > 0){
    fprintf(fp,"%c%d", GS, pt->escpage);
    if(pt->escpage < 0)
      fprintf(fp,";%d;%d",wp, hp);
    fprintf(fp,"psE");
    
    fprintf(fp,"%c%dpoE",GS,bLandscape);
  }
}
 
/*
 * epag_bubble_flush_all: 残っている bubbleを全て出力する。
 */
private void epag_bubble_flush_all(EpagPageCont *cont)
{
  int i;
  
  for(i=0; i<cont->maxBx;i++)
    if(cont->bubbleTbl[i]!=NULL)
      epag_bubble_flush(cont,cont->bubbleTbl[i]);
}

/*
 * epag_page_cont_init: EpagPageContの初期化、バッファ確保等
 */
private void epag_page_cont_init(gx_device_printer *pdev,FILE *fp,
				 EpagPageCont *cont)
{
  int bpl;
  EpagBubble *bbtbl;
  int i;

  cont->pdev = pdev;
  cont->fp   = fp;

  cont->x0 = (epag_cont.offX / 25.4 - 0.2) * pdev->x_pixels_per_inch; 
  cont->y0 = (epag_cont.offY / 25.4 - 0.2) * pdev->y_pixels_per_inch;
  
  cont->bw = epag_cont.nBw;
  if(cont->bw < 1) cont->bw = 4;
  cont->bh = epag_cont.nBh;
  if(cont->bh < 1) cont->bh = 32;
  
  cont->bpl  = bpl = gdev_mem_bytes_per_scan_line(cont->pdev); 
  cont->maxBx = (bpl + cont->bw - 1)/ cont->bw;
  cont->maxBy = (cont->pdev->height + cont->bh - 1)/cont->bh;

  cont->maxY = epag_cont.cRowBuf / cont->bh * cont->bh ;
  if(cont->maxY < cont->bh) cont->maxY = cont->bh;
  
  cont->bp  = gs_malloc(bpl , cont->maxY, "epag_skip_blank_init(bp)");
  cont->bp2 = gs_malloc(bpl*3/2+1 , cont->maxY, "epag_skip_blank_init(bp2)");

  cont->h = cont->r = 0;

  cont->bubbleTbl=gs_malloc(sizeof(EpagBubble *),cont->maxBx,"bubbleTbl");
  for(i=0;i<cont->maxBx;i++)
    cont->bubbleTbl[i] = NULL;
  cont->bubbleBuffer=gs_malloc(sizeof(EpagBubble),cont->maxBx,"bubbleBuffer");
  bbtbl = (EpagBubble *)cont->bubbleBuffer;
  for(i=0;i<cont->maxBx-1;i++)
    bbtbl[i].next = &bbtbl[i+1];
  bbtbl[i].next = NULL;
  cont->freeBubbleList = &bbtbl[0];
}

private void
epag_page_close(EpagPageCont *cont)
{
  gs_free(cont->bp, bpl, cont->maxY, "epag_skip_blank_init(bp)");
  gs_free(cont->bp2, bpl*3/2+1, cont->maxY, "epag_skip_blank_init(bp2)");
  gs_free(cont->bubbleBuffer, sizeof(EpagBubble), cont->maxBx,"bubbleBuffer");
}

private int
epag_read_image(EpagPageCont *cont)
{
  int bh = cont->bh;
  int bpl = cont->bpl;
  int ri,rmin,i,y;
  int code;
  EpagBubble *bbl;
  
  if(cont->h + cont->bh > cont->maxY){  /* bubbleをflushしてすき間を作る */
    rmin = cont->r + cont->bh; /* これより下の領域を持つ bubbleをflush */
    for(i=0; i< cont->maxBx; i++){
      bbl = cont->bubbleTbl[i];
      if(bbl !=NULL && bbl->y0 < rmin)
	epag_bubble_flush(cont,bbl);
    }
    cont->h -= bh;               /* [r:r+h]のデータが保持されている */
    cont->r += bh;
  }

  ri = cont->r + cont->h;        /* 読み込む row位置 */
  y  = ri % cont->maxY;          /* 読み込み先の位置 */
  code = gdev_prn_copy_scan_lines(cont->pdev, ri, cont->bp + bpl*y, bpl * bh);
  if(code < 0) return code;

  cont->h += bh;
  return 0;
}

/*
 *    bh行分のラスターデータを処理する
 */
private void
epag_process_line(EpagPageCont *cont)
{
  int bh = cont->bh;
  int bx,bInBlack,bBlack,start;

  if(epag_cont.bSkipBlank){
    bInBlack = 0;
    for(bx=0; bx < cont->maxBx; bx++){
      bBlack = epag_is_black(cont, bx);
      if(!bInBlack){
	if(bBlack) {
	  start = bx;
	  bInBlack = 1;
	}
      }
      else {
	if(!bBlack) {
	  bInBlack = 0;
	  epag_rect_add(cont, start, bx);
	}
      }
    }
    if(bInBlack)
      epag_rect_add(cont, start, bx-1);
  }
  else epag_rect_add(cont, 0, cont->maxBx-1);
}

private int
epag_is_black(EpagPageCont *cont, int bx)
{
  int bh  = cont->bh;
  int bpl = cont->bpl;
  int x,y,y0;
  byte *p;
  
  y0 = (cont->r + cont->h - bh) % cont->maxY;
  for(y = 0; y < bh; y++){
    p = &cont->bp[(y0+y)*bpl + bx * cont->bw];
    for(x = 0; x < cont->bw; x++)
      if(p[x]!=0)
	return 1;
  }
  return 0;
}

private void
epag_rect_add(EpagPageCont *cont,int start,int end)
{
  int x0 = start * cont->bw;
  int x1 = end   * cont->bw - 1;
  int y0 = cont->r + cont->h - cont->bh;
  int y1 = y0 + cont->bh - 1;
  int i;
  EpagBubble *bbl;

  if((bbl = cont->bubbleTbl[start])!=NULL &&
     bbl->y1 == y0 - 1 &&
     bbl->x0 == x0 &&
     bbl->x1 == x1){
    bbl->y1 = y1;
  }
  else {
    for(i=start; i<=end; i++)
      if(cont->bubbleTbl[i]!=NULL)
	epag_bubble_flush(cont,cont->bubbleTbl[i]);
    epag_bubble_gen(cont, x0, x1, y0, y1);
  }
}

private void
epag_bubble_gen(EpagPageCont *cont, int x0, int x1, int y0, int y1)
{
  EpagBubble *bbl;
  int bx0,bx1,bx;
  
  assert(cont->freeBubbleList != NULL);

  bbl = cont->freeBubbleList;
  cont->freeBubbleList = bbl->next;

  bbl->x0 = x0;
  bbl->x1 = x1;
  bbl->y0 = y0;
  bbl->y1 = y1;
  
  bx0 = x0 / cont->bw;
  bx1 = (x1 + cont->bw - 1)/ cont->bw;

  for(bx = bx0; bx <= bx1; bx++){
    assert(cont->bubbleTbl[bx] == NULL);
    cont->bubbleTbl[bx] = bbl;
  }
}

private void epag_bubble_flush(EpagPageCont *cont,EpagBubble *bbl)
{
  int bx,bx0,bx1;
  
  epag_bubble_image_out(cont, bbl);

  bx0 = bbl->x0 / cont->bw;
  bx1 = (bbl->x1 + cont->bw - 1)/ cont->bw;
  for(bx = bx0; bx <= bx1; bx++){
    assert(cont->bubbleTbl[bx] == bbl);
    cont->bubbleTbl[bx] = NULL;
  }

  bbl->next = cont->freeBubbleList;
  cont->freeBubbleList = bbl;
}

/* イメージを出力 */

private void
epag_bubble_image_out(EpagPageCont *cont,EpagBubble *bbl)
{
  FILE *fp = cont->fp;
  int x,y,i,j,c;
  byte pat,*p,*q;

  x = cont->x0 + bbl->x0 * 8;
  y = cont->y0 + bbl->y0;
  fprintf(fp ,"%c%dY%c%dX", GS, y, GS, x);

  cont->ibp2 = 0;
  c = 0;
  pat = 0;
  q = cont->bp2;
  for(i=bbl->y0; i<= bbl->y1; i++){
    p = cont->bp + (i % cont->maxY) * cont->bpl;
    for(j=bbl->x0; j<= bbl->x1;j++){
      if(pat == p[j] && c < 257) c++;
      else {
	if(c > 0){
	  *q++ = pat;
	  if(c > 1){
	    *q++ = pat;
	    *q++ = c - 2;
	  }
	}
	pat = p[j];
	c = 1;
      }
    }
  }
  
  if(c > 0){
    *q++ = pat;
    if(c > 1){
      *q++ = pat;
      *q++ = c - 2;
    }
  }
  cont->ibp2 = q - cont->bp2;
  
  fprintf(fp,"%c%d;%d;%d;0bi{I",GS,cont->ibp2,
	  (bbl->x1 - bbl->x0 + 1)*8, bbl->y1 - bbl->y0 + 1);
  fwrite(cont->bp2, cont->ibp2, 1, fp);
  if(epag_cont.bShowBubble){
    fprintf(fp,"%c0dmG",GS);
    fprintf(fp,"%c%d;%d;%d;%d;0rG",GS,
	    cont->x0 + bbl->x0 * 8,     cont->y0 + bbl->y0,
	    cont->x0 + bbl->x1 * 8 + 7, cont->y0 + bbl->y1);
  }
}

/*** Local Variables: ***/
/*** compile-command: "make -f unix-gcc.mak zlibc_=-lz PREFIX=/usr/local CFLAGS=\"-g -DA4\"" ***/
/*** End: ***/
