#define PROGNAME "g_dot"
#include <stdio.h>

short gap=0;
#define COUNTFILENAME "pub:count"
char outfilename[31];

char dflag=0;  /* display dither matrix option */
char lflag=0;  /* direct output to laserprinter dir */
char screen_flag = 0; /* output meant for doc:lg1 program display */
short header=1;/* set if pic file contains a header char */
short xsize;   /* pic file size                          */
FILE *fd = NULL; /* pointer to description file */
FILE *fp = NULL; /* pointer to pic file */
FILE *fq = NULL; /* pointer to output file */
char iflag=0;  /* set if units are specified in inches */
char cflag=0;  /* set if units are specified in cms    */


#define EOS '\0'
#define  TABLEWIDTH  256  /* conversion map */
short conv[TABLEWIDTH];
char mapfilename[31];
char new_mfn[31];
#define  MAPFILE  "map.dat"
char mflag=0; /* set if mapfile is to be consulted */

short psize;
char centred=0;

short cur_x=0,cur_y=0;

#define samestr(S,T) (!(strcmp(S,T)))

#define send_header(A,B) (fprintf(fq,"\033[ 0;%d;0;%d;0G\n",A,B))
#define move_right(x)  (fprintf(fq,"\033[ %dC",x))
#define move_left(x)   (fprintf(fq,"\033[ %dD",x))
#define move_up(x)     (fprintf(fq,"\033[ %dA",x))
#define move_down(x)   (fprintf(fq,"\033[ %dB",x))

#define place_cursor(X,Y) (move_cursor(X-cur_x,Y-cur_y))

move_cursor(x,y)
short x,y;
{
  if(0<x) move_right(x);
  else    move_left(-x);
  if(0<y) move_down(y);
  else    move_up(-y);
  cur_x+=x;cur_y+=y;
}


readmap()
{
  FILE *fp=NULL;
  register short *p,i;

  if(!mflag)  {
    linmap();
    if(!screen_flag) invertmap();    if(dflag)  showmap();
    return(0);
  }
  if(samestr(mapfilename,new_mfn)) return(0);
  strcpy(mapfilename,new_mfn);
  if((fp = fopen(mapfilename,"r")) == NULL) {
    fprintf(stderr,"file : %s ",mapfilename); error("can't open map file");
  }

  fprintf(stderr,"reading map file :%s",mapfilename);

  for (i=0,p=conv; i<TABLEWIDTH; i++)
    *p++ = getc(fp)&0377;
  fclose(fp);

  fprintf(stderr,"\n");
  if(!screen_flag) invertmap();  if(dflag)  showmap();

}

linmap()
{
  register short i;
  register short *convp;
  
  for (i=0,convp=conv; i<TABLEWIDTH; i++)
    *convp++ = i;
}

showmap()
{
  register short i;
  register short *convp;

  for (i=0,convp=conv; i<TABLEWIDTH; i++)
    fprintf(stderr,"conv[%3d] = %3d\n",i,*convp++);
}

invertmap()
{
  register short i;
  register short *convp;

  for (i=0,convp=conv; i<TABLEWIDTH; i++,convp++)
      *convp = 256 - *convp;
}

#define MAXBUF1 2000  
#define MAXBUF  2000  /*should be less*/
unsigned char buf[MAXBUF];
short bufsize,buf1size,bsize,b1size;
unsigned char buf1[MAXBUF];
short scale=1;       /* blow up scale */


#define MAXD 16
#define MAXDSQR 256
unsigned char dither[MAXDSQR];
static char hex[16+1] = "0123456789ABCDEF";


/* The two char arrays below are combined to form the dither matrix.  */
/* 16 submatrices similar to "dithfont" are combined as a 4 by 4 set */
/* To produce a 16 by 16 matrix. Each submatrix in the matrix has a */
/* positional correspondence with an element in the matrix "random". */
/* For each submatrix every element it contains is incremented by */
/* the value held in the "corresponding" element of the matrix "random" */

unsigned char random[4][4] = {   /* From Foley & Van Dam */
  { 0, 8, 2,10},
  {12, 4,14, 6},
  { 3,11, 1, 9},
  {15, 7,13, 5}
};

unsigned char dithfont[4][4] = {  /* designed to clump dots */
  { 0, 1, 4, 9},
  { 2, 3, 5,10},
  { 6, 7, 8,11},
  {12,13,14,15}
};

/* DITHER MATRIX PRODUCTION */
make_dith()             /* writen for clarity rather than speed */
{
char r,c,row,col;
  for(r=0;r<4;r++)
    for(c=0;c<4;c++)
      for(row=0;row<4;row++)
        for(col=0;col<4;col++)
          dither[4*(c+r*MAXD)+col+row*MAXD] =
                          16*dithfont[row][col]+random[r][c];
}

#define NEWLINE putchar('\n')

show_dither()
{
short i,j;
unsigned char *dp;
  
  for(j=0,dp=dither;j<MAXD;j++){
    for(i=0;i<MAXD;i++,dp++)
        fprintf(stderr,"%3d ",(*dp)&0377);
    NEWLINE ;
  }
}


main(argc,argv)
char **argv;
{

  argv++;

  while((argc>0)&&(*argv[0] == '-'))
  {
    switch((*argv)[1]) {
    case 'd':     /*  d for debug or display */
      dflag=1;
      break;
    case 'r':
      screen_flag=1;
      break;
    case 'l':
      lflag=1;
      break;
    case 'i':  /* inches */
      iflag=1;
      break;
    case 'c':  /* cms */
      cflag=1;
      break;
    default:
      error("bad option");
      break;
    }
    argc--;
    argv++;
  }


  if(iflag&&cflag) error("specifying inches and cms");

  if (!((2<=argc)&&(argc<=3)))
    error("arg count");

  if((fd=fopen(*argv,"r"))==NULL)
    error("can't open description file");

  argc--;
  argv++;
  if(argc==2) {
    if(lflag) error("no output filename needed if sending direct to laser printer");
    strcpy(outfilename,*argv);
  }else
    sprintf(outfilename,"%sF%02dP%02d",
           (lflag?("lp1:"):("")),file_count(),getpid());

  if((fq=fopen(outfilename,"w"))==NULL)
     error("can't open output file");


  if(lflag&&screen_flag)
    fprintf(stderr,
       "warning : you are sending screen inverted output to the printer\n");
  make_dith();
  if(dflag) show_dither();
  display();
  report();
  fclose(fd);
  putc('\n',fq); /* end of last line */
  fclose(fq);
  putchar(7);       /* BEEP */
}
  
getbuf(bufp)
unsigned char *bufp;
{
register short i,s;
unsigned char c;
    for(i=0,bufp=buf;i<xsize;i++) {
      c = conv[getc(fp)&0377];
      for(s=0;s<scale;s++)
        *bufp++ = c;
    }
    /* pad out with whites to make buf length a multiple of DSIZE */
#define WHITE 255
    for(i=bufsize;i<bsize;i++)
        *bufp++ = conv[WHITE];
}

error(s)
char *s;
{
  fprintf(stderr,"%s: %s\n",PROGNAME,s);
  exit(0);
}

prepare_line(dp)
unsigned char *dp;
{
  unsigned char *d1p;
  register short i,g;
  register unsigned char *bufp,*buf1p;
  short bsize_16 = bsize/16;

  d1p=dp;
  for (i=0,bufp=buf,buf1p=buf1; i<bsize_16;i++,dp=d1p) {
      *buf1p++ = ((*bufp++) >= (*dp++));
      *buf1p++ = ((*bufp++) >= (*dp++));
      *buf1p++ = ((*bufp++) >= (*dp++));
      *buf1p++ = ((*bufp++) >= (*dp++));
      for(g=0;g<gap;g++)        *buf1p++ = !screen_flag;  /* 0 for white paper */
      *buf1p++ = ((*bufp++) >= (*dp++));
      *buf1p++ = ((*bufp++) >= (*dp++));
      *buf1p++ = ((*bufp++) >= (*dp++));
      *buf1p++ = ((*bufp++) >= (*dp++));
      for(g=0;g<gap;g++)        *buf1p++ = !screen_flag;  /* 0 for white paper */
      *buf1p++ = ((*bufp++) >= (*dp++));
      *buf1p++ = ((*bufp++) >= (*dp++));
      *buf1p++ = ((*bufp++) >= (*dp++));
      *buf1p++ = ((*bufp++) >= (*dp++));
      for(g=0;g<gap;g++)        *buf1p++ = !screen_flag;  /* 0 for white paper */
      *buf1p++ = ((*bufp++) >= (*dp++));
      *buf1p++ = ((*bufp++) >= (*dp++));
      *buf1p++ = ((*bufp++) >= (*dp++));
      *buf1p++ = ((*bufp++) >= (*dp++));
      for(g=0;g<gap;g++)        *buf1p++ = !screen_flag;  /* 0 for white paper */
    }
    for(i=buf1size;i<b1size;i++)
      *buf1p++ = !screen_flag;
}

#define IN_CONST 299.70731  /* No of pixels per inch   */
#define CMS_CONST 118.15384 /* No of pixels per cm     */

i_convert(cx,cy,fcx,fcy) /* converts inches to pixels */
short *cx,*cy;
float fcx,fcy;
{
  *cx=IN_CONST*fcx;  *cy=IN_CONST*fcy;
}
c_convert(cx,cy,fcx,fcy) /* converts cms to pixels */
short *cx,*cy;
float fcx,fcy;
{
  *cx=CMS_CONST*fcx;  *cy=CMS_CONST*fcy;
}

display()
{
char ch,pic_file[31];
short cx,cy;
char sflag=0;
float fcx,fcy,fsize;

while(1){
  gap=0;cx=0;cy=0;mflag=0;header=1;scale=1;centred=0;

  fscanf(fd,"%1s",&ch);
  if(ch!='-') return(0);

  while(ch=='-')
    {
    switch(getc(fd)) {
    case 'x':
      fscanf(fd,"%hd",&xsize);
      header=(!header);
      break;
    case 'g':
      fscanf(fd,"%hd",&gap);
      break;
    case 'o':
      centred=1;
      break;
    case 'c':
      if(iflag||cflag) {
          fscanf(fd,"%f%f",&fcx,&fcy);
          if(iflag) i_convert(&cx,&cy,fcx,fcy);
          else      c_convert(&cx,&cy,fcx,fcy);
      }else 
        fscanf(fd,"%hd%hd",&cx,&cy);
      break;
    case 's':
      if(iflag||cflag) {
          fscanf(fd,"%f",&fsize);
          if(iflag) psize = IN_CONST*fsize;
          else      psize = CMS_CONST*fsize;
      }else{
        fscanf(fd,"%hd",&scale);
        if (scale<1) error("negative scaling factor");
      }
      break;
    case 'm':
      fscanf(fd,"%s",new_mfn);
      if(new_mfn[0]==EOS) {
         strcpy(new_mfn,MAPFILE);
         fprintf(stderr,"using %s as default\n",MAPFILE);
      }
      mflag=1;
      break;
    default:
      error("bad option in description file");
      break;
    }/*switch*/ 
  fscanf(fd,"%1s",&ch);
  }/*while*/ 


  readmap();   /* placed here to avoid using fp twice */

  ungetc(ch,fd); fscanf(fd,"%s",pic_file);
  if((fp=fopen(pic_file,"r"))==NULL) {
    fprintf(stderr,"file : %s ",pic_file); error("can't open picture file");
  }else
    fprintf(stderr,"reading file : %s\n",pic_file);
  place_cursor(cx,cy);
  display_file();  
  fclose(fp);
}/*while*/
}

display_file()
{
register short j,d_count,s;
register unsigned char *dp;
  if (header)
    xsize=(getc(fp))&0377; /*good for xsize upto 256 only*/

  if (xsize<1) error("negative picture size");
  
  if(iflag||cflag)
    if(xsize>psize) error("picture too big for specified size in cms/inches");
    else{
      scale = psize/xsize;
      if(centred) move_cursor( (psize%xsize)/2, (psize%xsize)/2 );
    }
  bufsize=scale*xsize;
  bsize=bufsize;
  /* make buf length a multiple of 16 */
  if(bsize%16) bsize+=(16 - bsize%16);

  if (bsize>MAXBUF)
    error("picture too wide at this scale");
  if (bsize<1)
    error("picture too small at this scale");

  buf1size=bsize + bsize/4*gap;  /* includes space for gaps */

  b1size=buf1size;
  /* make buf1 length a multiple of 16 */
  if(b1size%16) b1size+=(16 - b1size%16);

  if (b1size>MAXBUF1)
    error("gaps make picture too wide");

  send_header(buf1size,b1size);
  for (j=0,dp=dither,d_count=0; j<xsize; j++) {
    getbuf();
    for(s=0;s<scale;s++) {
      prepare_line(dp);
      output_line();
      if(d_count<MAXD-1) {d_count++;dp+=MAXD;}
      else{d_count=0;dp=dither;}
      if(d_count%4==0) blank_lines();
    }
  }
  move_up(buf1size);
}

output_line()
{
register unsigned char c,*buf1p;
register short i;
unsigned char c1;
short b1size_8=b1size/8;

   for(i=0,buf1p=buf1;i<b1size_8;i++) {
     c  = ((*buf1p++)<<1);        /* 76543210 as hex 3210, hex 7654 */
     c  = ((c|(*buf1p++))<<1);
     c  = ((c|(*buf1p++))<<1);
     c1 = (c|(*buf1p++));
     c  = ((*buf1p++)<<1);
     c  = ((c|(*buf1p++))<<1);
     c  = ((c|(*buf1p++))<<1);
     putc(hex[c|(*buf1p++)],fq);
     putc(hex[c1],fq);
   }
   putc(' ',fq);
}

#define WHITE_LINE {for(i=0;i<b1size_4;i++)putc('0',fq);putc(' ',fq);}
#define BLACK_LINE {for(i=0;i<b1size_4;i++)putc('F',fq);putc(' ',fq);}

blank_lines()
{
register short i,g;
short b1size_4=b1size/4;

  if(!screen_flag)
    for(g=0;g<gap;g++)  BLACK_LINE /*;*/
  else
    for(g=0;g<gap;g++)  WHITE_LINE /*;*/
}

file_count()
{
FILE *fq = NULL;
short count=0;
    if((fq=fopen(COUNTFILENAME,"r"))==NULL) {
        fprintf(stderr,"%s created.\n",COUNTFILENAME);
        fprintf(stderr,"Count initialised in %s.\n",COUNTFILENAME);
    }
    else {
        fscanf(fq,"%hd",&count);
        fclose(fq); fq=NULL;
        if (count==0){
          fprintf(stderr,"%s has been corrupted.\n",COUNTFILENAME);
          fprintf(stderr,"Count initialised in %s.\n",COUNTFILENAME);
        }
    }
    if((fq=fopen(COUNTFILENAME,"w"))==NULL)
       error("can't write count file");
    fprintf(fq,"%d",count+1);
    fclose(fq);
  return(count);
}

report()
{
  fprintf(stderr,"Your options were : \n");
  if(screen_flag)    fprintf(stderr,
                 "inversed dot pattern for screen viewing.\n");
  if(lflag) fprintf(stderr,"output sent to laser printer directory\n");
  fprintf(stderr,"Output file is called %s.\n",outfilename);
}
