/************************************************************************/
/*									*/
/* Description: Ascii to PostScript printer program.			*/
/* File: imag:/users/local/a2ps/a2ps.c					*/
/* Created: Mon Nov 28 15:22:15 1988 by miguel@imag (Miguel Santana)	*/
/* Version: 2.0								*/
/*									*/
/* Edit history:							*/
/* 1) Derived of shell program written by evan@csli (Evan Kirshenbaum).	*/
/*    Written in C for improve speed execution and portability. Many	*/
/*    improvements have been added.					*/
/* Fixes by Oscar Nierstrasz @ cui.uucp:				*/
/* 2) Fixed incorrect handling of stdin (removed error if no file names)*/
/* 3) Added start_page variable to eliminate blank pages printed for	*/
/*	files that are exactly multiples of 132 lines (e.g., man pages)	*/
/* Modified by miguel@imag:						*/
/* 4) Added new options at installation : sheet format (height/width in	*/
/*    inches), page format (number of columns per line and of lines per	*/
/*    page).								*/
/* Modified by miguel@imag:						*/
/* 5) Added new option to print n copies of a same document.		*/
/* 6) Cut long filenames if don't fit in the page header.		*/
/* Modified by Tim Clark (T.Clark@warwick.ac.uk):			*/
/* 7) Two additional modes of printing (portrait and wide format modes)	*/
/* 8) Fixed to cope with filenames which contain a character which must	*/
/*    be escaped in a PostScript string.				*/
/* Modified by miguel@imag.fr to					*/
/* 9) Add new option to suppress heading printing.			*/
/* 10) Add new option to suppress page surrounding border printing.	*/
/* 11) Add new option to change font size. Number of lines and columns	*/
/*     are now automatically adjusted, depending on font size and	*/
/*     printing mode used.						*/
/* 12) Minor changes (best layout, usage message, etc).			*/
/*									*/
/************************************************************************/

/*
 * Copyright (c) 1988, Miguel Santana, miguel@imag.imag.fr
 *
 * Permission is granted to copy and distribute this file in modified
 * or unmodified form, for noncommercial use, provided (a) this copyright
 * notice is preserved, (b) no attempt is made to restrict redistribution
 * of this file, and (c) this file is not distributed as part of any
 * collection whose redistribution is restricted by a compilation copyright.
*/


#include <stdio.h>
#include <ctype.h>
#include <stdarg.h>
#ifdef ANSIC
#include <sys/types.h>
#include <time.h>
#else
#ifdef BSD
#include <sys/time.h>
#else
#ifdef SYSV
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
#else
#ifdef AMIGA
#include <exec/types.h>
#include <time.h>
#endif
#endif
#endif
#endif

#ifndef	HEADER_PS
#define	HEADER_PS	"./header.ps"
#endif

#ifndef WIDTH
#define	WIDTH	8.27
#endif

#ifndef HEIGHT
#define	HEIGHT	11.64
#endif

#ifndef MARGIN
#define	MARGIN	1.2
#endif

#ifndef DIR_SEP
#define	DIR_SEP	'/'
#endif

#define	PORTRAIT_HEADER		0.29
#define	LANDSCAPE_HEADER	0.22
#define	PIXELS_INCH		72
#define MAXFILENAME		20
#define	FALSE			0
#define	TRUE			1

int fold_line();
void print_file();
char cut_line();
int printchar();
void skip_page();


int column = 0;			/* Column number (in current line) */
int line = 0;			/* Line number (in current page) */
int line_number = 0;		/* Source line number */
int first_page;			/* First page for a file */
int nonprinting_chars, chars;	/* Number of nonprinting and total chars */
int prefix_width;		/* Width in characters for line prefix */
int pages = 0;			/* Number of logical pages printed */
int sheets = 0;			/* Number of physical pages printed */
int linesperpage;		/* Lines per page */
int columnsperline;		/* Characters per output line */

double font_size = 0.0;		/* Size of a char for body font */
int numbering = TRUE;		/* Line numbering option */
int folding = TRUE;		/* Line folding option */
int restart = TRUE;		/* Restart page number at each file option */
int only_printable = FALSE;	/* Replace non printable char by space option */
int interpret = TRUE;		/* Interpret TAB, FF and BS chars option */
int print_binaries = FALSE;	/* Force printing for binary files */ 
int copies_number = 1;		/* Number of copies to print */
int landscape = TRUE;		/* Otherwise portrait format sheets */
int wide_pages = FALSE;         /* TRUE implies landscape, not twinpage */
int twinpage = TRUE;		/* 2 pages per sheet if true, 1 otherwise */
int no_border = FALSE;		/* TRUE if user don't want the border */
int no_header = FALSE;		/* TRUE if user don't want the header */
int column_width = 8;	        /* default column tab width (8) */

main(argc, argv)
int argc;
char *argv[];
{
   register int narg;
   register char *arg;
   double char_width, header_size;
   double page_height, page_width;
   int i;
   extern double atof();

   /* Option processing */
   arg = argv[narg = 1];
   while (narg < argc && arg[0] == '-')
   {
      switch (arg[1])
      {
      case '?':					/* help */
	 goto usage;
      case '#':					/* n copies */
	 copies_number = 0;
	 arg += 2;
	 while (*arg != 0 && isdigit(*arg))
	    copies_number = copies_number*10 + (*arg++ - '0');
	 if (*arg != 0 || copies_number <= 0)
	    goto usage;
	 break;
      case 'b':					/* print binary files */
	 if (arg[2] != 0)
	    goto usage;
	 print_binaries = TRUE;
	 break;
      case 'f':					/* change font size */
	 if (arg[2] == 0) {
	    folding = TRUE;
	    break;
	 }
	 if ((font_size = atof(&arg[2])) == 0.0) {
	    fprintf(stderr, "Wrong value for option -s\n");
	    exit(1);
	 }
	 break;
      case 'h':					/* help */
	 goto usage;
      case 'i':					/* interpret control chars */
	 if (arg[2] != 0)
	    goto usage;
	 interpret = TRUE;
	 break;
      case 'n':					/* number file lines */
	 if (arg[2] == 0)
	 {
	    numbering = TRUE;
	    break;
	 }
	 if (arg[3] != 0)
	    goto usage;
	 switch (arg[2])
	 {
	 case 'b':				/* don't print binaries */
	    print_binaries = FALSE;
	    break;
	 case 'f':				/* cut lines too long */
	    folding = FALSE;
	    break;
	 case 'h':				/* don't print header */
	    no_header = TRUE;
	    break;
	 case 'i':				/* don't interpret ctrl chars */
	    interpret = FALSE;
	    break;
	 case 'n':				/* don't number lines */
	    numbering = FALSE;
	    break;
	 case 'p':				/* landscape format */
	    landscape = TRUE;
	    break;
	 case 'r':				/* don't restart sheet number */
	    restart = FALSE;
	    break;
	 case 's':				/* no surrounding border */
	    no_border = TRUE;
	    break;
	 case 'v':				/* only printable chars */
	    only_printable = TRUE;
	    break;
	 case 'w':				/* twin pages */
	    wide_pages = FALSE;
	    break;
	 default:
	    goto usage;
	 }
	 break;
      case 'p':					/* portrait format */
	 if (arg[2] != 0)
	    goto usage;
	 if (wide_pages) {
	    fprintf(stderr, "a2ps: options -p and -w are incompatible\n");
	    exit(1);
	 }
	 landscape = FALSE;
	 break;
      case 'r':					/* restart sheet number */
	 if (arg[2] != 0)
	    goto usage;
	 restart = TRUE;
	 break;
      case 't':					/* set tab size */
	 if (arg[2] == 0 || (column_width = atoi(arg+2)) <= 0)
	    goto usage;
	 break;
      case 'v':					/* print control chars */
	 if (arg[2] != 0)
	    goto usage;
	 only_printable = FALSE;
	 break;
      case 'w':					/* wide format */
	 if (arg[2] != 0)
	    goto usage;
	 if (!landscape) {
	    fprintf(stderr, "a2ps: options -p and -w are incompatible\n");
	    exit(1);
	 }
	 wide_pages = TRUE;
	 break;
      default:
      usage:
	 fprintf(stderr,"Usage: %s [options] [f1 f2 ... fn]\n", argv[0]);
	 fprintf(stderr,"options = -#num\t(number of copies to print)\n");
	 fprintf(stderr,"          -?\t(print this information)\n");
	 fprintf(stderr,"          -f\t(fold lines too large)\n");
	 fprintf(stderr,"          -fnum\t(font size, num is a float number)\n");
	 fprintf(stderr,"          -h\t(print this information)\n");
	 fprintf(stderr,"          -i\t(interpret tab, bs and ff chars)\n");
	 fprintf(stderr,"          -n\t(number line files)\n");
	 fprintf(stderr,"          -p\t(print in portrait mode)\n");
	 fprintf(stderr,"          -r\t(restart page number after each file)\n");
	 fprintf(stderr,"          -tn\t(set tab size to n)\n");
	 fprintf(stderr,"          -v\t(show non-printing chars in a clear form)\n");
	 fprintf(stderr,"          -w\t(print in wide format)\n");
	 fprintf(stderr,"          -nb\t(don't force printing binary files)\n");
	 fprintf(stderr,"          -nf\t(don't fold lines)\n");
	 fprintf(stderr,"          -nh\t(don't print the header)\n");
	 fprintf(stderr,"          -ni\t(don't interpret special chars)\n");
	 fprintf(stderr,"          -nn\t(don't number output lines)\n");
	 fprintf(stderr,"          -np\t(don't print in portrait format)\n");
	 fprintf(stderr,"          -nr\t(don't restart page number)\n");
	 fprintf(stderr,"          -ns\t(don't print surrounding borders)\n");
	 fprintf(stderr,"          -nv\t(replace non-printing chars by space)\n");
	 fprintf(stderr,"          -nw\t(don't print in wide format)\n");
	 exit(1);
      }
      arg = argv[++narg];
   }
   if (arg != 0 && strcmp(arg, "?") == 0)
      goto usage;

   twinpage = landscape && !wide_pages;
   if (font_size == 0.0)
      font_size = landscape ? 6.8 : 9.0;
   page_height = (HEIGHT - MARGIN) * PIXELS_INCH;
   page_width = (WIDTH - MARGIN) * PIXELS_INCH;
   char_width = 0.6 * font_size;
   if (landscape) {
      header_size = no_header ? 0.0 : LANDSCAPE_HEADER * PIXELS_INCH;
      linesperpage = ((page_width - header_size) / font_size) - 1;
      if (wide_pages)
	 columnsperline = (page_height / char_width) - 1;
      else
	 columnsperline = ((page_height / 2) / char_width) - 1;
   }
   else {
      header_size = no_header ? 0.0 : PORTRAIT_HEADER * PIXELS_INCH;
      linesperpage = ((page_height - header_size) / font_size) - 1;
      columnsperline = (page_width / char_width) - 1;
   }
   if (linesperpage <= 0 || columnsperline <= 0) {
      fprintf(stderr, "Font %g too big !!\n", font_size);
      exit(1);
   }

   /* Header printing (postcript prolog) */
   print_header();

   /* Print files designated or standard input */
   prefix_width = numbering ? 6 : 1;
   if (narg >= argc)
      print_file("stdin");
   else
   {
      while (narg < argc)
      {
	 if (freopen(arg, "r", stdin) == 0)
	 {
	    fprintf(stderr, "Error opening %s\n", arg);
	    printf("\n%%%%Trailer\ncleanup\ndocsave restore end\n");
	    exit(1);
	 }
	 print_file(arg);
	 arg = argv[++narg];
      }
   }

   printf("\n%%%%Trailer\ncleanup\ndocsave restore end\n");
}

void print_file(name)
char *name;
{
   register int c;
   int start_line, continue_exit;
   int char_width;
   int start_page;
   char new_name[MAXFILENAME+1];
   char *p;

   /*
    * Boolean to indicates that previous char is \n (or interpreted \f)
    * and a new page would be started, if more text follows
    */
   start_page = FALSE;

   /*
    * Printing binary files is not very useful. We stop printing
    * if we detect one of these files. Our heuristic to detect them:
    * if 50% characters of first page are non-printing characters,
    * the file is a binary file.
    * Option -b force binary files impression.
    */
   first_page = TRUE;
   nonprinting_chars = chars = 0;

   /*
    * Preprocessing (before printing):
    * - TABs expansion (see interpret option)
    * - FF and BS interpretation
    * - replace non printable characters by a space or a char sequence
    *   like:
    *     ^X for ascii codes < 0x20 (X = [@, A, B, ...])
    *     ^? for del char
    *     M-c for ascii codes > 0x3f
    * - prefix parents and backslash ['(', ')', '\'] by backslash
    *   (escape character in postcript)
    */
   column = 0;
   line = line_number = 0;
   start_line = TRUE;

   if (strlen(name) > MAXFILENAME) {
      cut_filename(name, new_name);
      name = new_name;
   }
   putchar('(');
   for (p = name; *p != 0;)
      printchar(*p++);
   printf(") newfile\n");

   if (restart)
      printf("/sheet 1 def\n");

   pages = 0;
   skip_page();

   c = getchar();
   while (c != EOF)
   {
      /* Form feed */
      if (c == '\f' && interpret)
      {
	 /* Close current line */
	 if (!start_line)
	 {
	    printf(") s\n");
	    start_line = TRUE;
	 }
	 /* start a new page ? */
	 if (start_page)
	    skip_page();
	 /* Close current page and begin another */
	 printf("endpage\n") ;
	 start_page = TRUE;
	 /* Verification for binary files */
	 if (first_page && is_binaryfile(name))
	    return;
	 line = 0;
	 if ((c = getchar()) == EOF)
	    break;
      }

      /* Start a new line? */
      if (start_line)
      {
	 if (start_page)
	 {	 /* only if there is something to print! */
	    skip_page();
	    start_page = FALSE ;
	 }
	 if (numbering)
	    printf("(%-5d ", ++line_number);
	 else
	    printf("( ");
	 start_line = FALSE;
      }

      /* Interpret each character */
      switch (c)
      {
      case '\b':
	 if (!interpret)
	    goto print;
	 if (column)
	    column--;
	 putchar(c);
	 break;
      case '\n':
	 column = 0;
	 start_line = TRUE;
	 printf(") s\n");
	 if (++line >= linesperpage)
	 {
	    printf("endpage\n");
	    start_page = TRUE ;
	    if (first_page && is_binaryfile(name))
	       return;
	    line = 0;
	 }
	 break;
      case '\t':
	 if (interpret)
	 {
	    continue_exit = FALSE;
	    do
	    {
	       if (++column + prefix_width > columnsperline)
		  if (folding)
		  {
		     if (fold_line(name) == FALSE)
			return;
		  }
		  else
		  {
		     c = cut_line();
		     continue_exit = TRUE;
		     break;
		  }
	       putchar(' ');
	    } while (column % column_width);
	    if (continue_exit)
	       continue;
	    break;
        }
      default:
      print:
	 if (only_printable)
	    char_width = 1;
	 else
	 {
	    char_width = c > 0177 ? 2 : 0;
	    char_width += c < ' ' || c == 0177 ? 2 : 1;
	 }
	 if (prefix_width + (column += char_width) > columnsperline)
	    if (folding)
	    {
	       if (fold_line(name) == FALSE)
		  return;
	    }
	    else
	    {
	       c = cut_line();
	       continue;
	    }
	 nonprinting_chars += printchar(c);
	 chars++;
	 break;
      }
      c = getchar();
   }

   if (!start_line)
      printf(") s\n");
   if (!start_page)
      printf("endpage\n");
}

/*
 * Cut long filenames.
 */
int cut_filename(old_name, new_name)
char *old_name, *new_name;
{
   register char *p;
   register int i;

   p = old_name + (strlen(old_name)-1);
   while (p >= old_name && *p != DIR_SEP) p--;

   for (i = 0, p++; *p != 0 && i < MAXFILENAME; i++)
      *new_name++ = *p++;
   *new_name = 0;
}

/*
 * Fold a line too long.
 */
int fold_line(name)
char *name;
{
   column = 0;
   printf(") s\n");
   if (++line >= linesperpage)
   {
      printf("endpage\n");
      skip_page();
      if (first_page && is_binaryfile(name))
	 return FALSE;
      line = 0;
   }
   if (numbering)
      printf("(      ");
   else
      printf("( ");

   return TRUE;
}

/*
 * Cut a textline too long to the size of a page line.
 */
char cut_line()
{
   char c;

   while ((c = getchar()) != EOF && c != '\n' && c != '\f');
   return c;
}

/*
 * Print a char in a form accept by postscript printers.
 */
int printchar(c)
unsigned char c;
{
   if (c >= ' ' && c < 0177)
   {
      if (c == '(' || c == ')' || c == '\\')
         putchar('\\');
      putchar(c);
      return 0;
   }

   if (only_printable)
   {
      putchar(' ');
      return 1;
   }

   if (c > 0177)
   {
      printf("M-");
      c &= 0177;
   }
   if (c < ' ')
   {
      putchar('^');
      if ((c = c + '@') == '(' || c == ')' || c == '\\')
	 putchar('\\');
      putchar(c);
   }
   else if (c == 0177)
      printf("^?");
   else
      putchar(c);

   return 1;
}

/*
 * Begins a new physical page.
 */
void skip_page()
{
   pages++;
   if (twinpage == FALSE || (pages & 0x1))
   {
      sheets++;
      printf("%%%%Page: %d %d\n", sheets, sheets);
   }
   printf("startpage\n");
}

/*
 * Test if we have a binary file.
 */
is_binaryfile(name)
char *name;
{
   first_page = FALSE;
   if (!print_binaries && (nonprinting_chars*100 / chars) >= 75)
   {
      fprintf(stderr, "%s is a binary file: printing aborted\n", name);
      return TRUE;
   }
   return FALSE;
}

print_header()
{
   register int c;
   FILE *f;
   char *string;
#ifdef ANSIC
   time_t date;
#else
#ifdef AMIGA
   time_t date;
#else
#ifdef BSD
   struct timeval date;
   struct tm *p;
#else
#ifdef SYSV
    struct timeb date;
#endif
#endif
#endif
#endif

   if ((f = fopen(HEADER_PS, "r")) == NULL)
   {
      fprintf(stderr, "Postcript header missing\n");
      exit(1);
   }

   /* Initialize some postcript variables */
   printf("%%! a2ps 3.0\n\n");
   printf("/$a2psdict 100 dict def\n");
   printf("$a2psdict begin\n");
   printf("%% Initialize page description variables.\n");
   printf("/inch {72 mul} bind def\n");
   printf("/landscape %s def\n", landscape ? "true" : "false");
   printf("/twinpage %s def\n", twinpage ? "true" : "false");
   printf("/sheetheight %g inch def\n", HEIGHT);
   printf("/sheetwidth %g inch def\n", WIDTH);
   printf("/margin %g inch def\n", MARGIN);
   printf("/noborder %s def\n", no_border ? "true" : "false");
   if (no_header) {
      printf("/noheader true def\n");
      printf("/headersize 0.0 def\n");
   }
   else {
      printf("/noheader false def\n");
      printf("/headersize %g inch def\n",
	     landscape ? LANDSCAPE_HEADER : PORTRAIT_HEADER);
   }
   printf("/bodyfontsize %g def\n", font_size);
   printf("/lines %d def\n", linesperpage);
   printf("/columns %d def\n", columnsperline);

   /* Retrieve date and hour */
#ifdef ANSIC
   if (time(&date) == -1)
   {
      fprintf(stderr, "Error calculing time\n");
      exit(1);
   }
   string = ctime(&date);

   /* and print them */
   printf("/date (%.6s %.4s %.8s) def\n", string+4, string+20, string+11);
#else
#ifdef AMIGA
   if (time(&date) == -1)
   {
      fprintf(stderr, "Error calculing time\n");
      exit(1);
   }
   string = ctime(&date);

   /* and print them */
   printf("/date (%.6s %.4s %.8s) def\n", string+4, string+20, string+11);
#else
#ifdef BSD
   (void) gettimeofday(&date, (struct timezone *)0);
   p = localtime(&date.tv_sec);
   string = asctime(p);

   /* and print them */
   printf("/date (%.6s %.4s %.8s) def\n", string+4, string+20, string+11);
#else
#ifdef SYSV
   (void)ftime(&date);
   string = ctime(&date.time);
   printf("/date (%.6s %.4s %.8s) def\n", string+4, string+20, string+11);
#endif
#endif
#endif
#endif

   /* Header file printing */
   while ((c = getc(f)) != EOF)
      putchar(c);


   /* Close prolog */
   printf("%%%%EndProlog\n\n");

   /* Ask for printing n copies */
   if (copies_number > 1)
      printf("/#copies %d def\n", copies_number);

   /* Go on */
   printf("/docsave save def\n");
   printf("startdoc\n");
}
