/*
 $Log: print.C,v $
 * Revision 1.25  1994/03/12  16:41:27  root
 * function skip_to_col(...) encapsulates DJ500 tabulating commands
 * option -il ignores Ctrl-L
 * option -sp separates logical pages by horizontal and
 * vertical lines
 *
 * Revision 1.24  1994/03/08  22:58:37  root
 * option `spacing factor'
 * option `character set'
 *
 * Revision 1.23  1994/03/08  20:54:44  root
 * Structure flag_t for further option extensions
 *
 * Revision 1.22  1994/01/20  21:53:59  root
 * option -q
 *
 * Revision 1.21  1994/01/14  17:11:37  root
 * Printing to pipe.
 * Cleaned up print_file()
 *
 * Revision 1.20  1994/01/12  16:51:27  root
 * replaced some exit_500()'s by exit()'s.
 * now really: package 1.11
 *
 * Revision 1.19  1994/01/12  16:39:36  root
 * package 1.11
 *
 * Revision 1.18  1994/01/12  16:06:15  root
 * In the `horizontal cursor positioning' escape sequence of the
 * DeskJet, the column argument is counted from the `physical'
 * left margin. NOT from the margin set by the `set left margin'
 * escape sequence.
 *
 * Revision 1.17  1994/01/10  19:51:50  root
 * Error when printing stdin: the last physical page
 * was NOT printed under certain circumstances. Fixed that.
 *
 * Revision 1.16  1994/01/03  10:07:55  root
 * package 1.1
 *
 * Revision 1.15  1994/01/03  00:51:42  root
 * fixed error in put_page(): switch to actual line when previous line
 * is full and has no CR.
 *
 * Revision 1.14  1994/01/02  18:52:55  root
 * margins: top, bottom, left, right
 * fixed the pagelength for US Legal paper
 * function escape(...) to issue printer escape sequences more easily.
 *
 * Revision 1.13  1993/11/21  15:19:59  root
 * som minor changes. Option -s now only shows the geometry
 * and does not produce output.
 *
 * Revision 1.12  1993/11/21  13:33:27  root
 * *** empty log message ***
 *
 * Revision 1.11  1993/11/21  02:14:49  root
 * Option -wc; Option -79x66
 *
 * Revision 1.10  1993/11/21  00:59:49  root
 * US Letter and US Legal paper sizes.
 * Copyright notice.
 * Printing to stdout.
 *
 * Revision 1.9  1993/08/29  19:52:27  root
 * reset printer before and after use
 * fixed getpage (suppressing partially filled logical pages)
 *
 * Revision 1.7  1993/04/05  21:43:36  root
 * changed default page size from 80x66 to 79x66 so that
 * two logical pages fit on one physical page when printing small
 * fixed a bug in print_file()
 *
 * Revision 1.6  1993/04/05  19:10:48  root
 * logical pages
 * correct handling of backspaces: we now can print manpages
 *
 * Revision 1.5  1993/04/05  13:01:35  root
 * rework of squeeze_line(), getpage()
 * changed page_t
 *
 * Revision 1.4  1993/04/03  18:29:04  root
 * changed from German to English
 * improved the white-space-compression
 * more comments
 *
 * Revision 1.3  1993/02/02  02:00:55  root
 * Zeilen- und Spaltenberechnung neu.
 * Verarbeitung von Dateinamen, die mit dem '-' anfangen.
 *
 * Revision 1.2  1993/02/01  02:54:24  root
 * Mehrspaltendruck.
 *
 * Revision 1.1  1993/01/29  00:07:28  root
 * Initial revision
 *
 */

char rcsid[] = "$Id: print.C,v 1.25 1994/03/12 16:41:27 root Exp root $";

#ifndef VERSION
#define VERSION "unknown"
#endif

char prog_version[] = VERSION;

#include <stdio.h>
#include <stdlib.h>
#include <paper.h>
#include <string.h>

char *font_names[]    = {"courier", "cgtimes", "lettgoth", 0};
char *size_names[]    = {"a4", "letter", "legal", 0};
char *charset_names[] = {"ascii", "ansi", "ecma", "pc8", "pc850",
			 "hpr8", "hpl", 0};

enum {
  opt_lndscp	= 1,
  opt_lq	= 2,
  opt_italic	= 4,
  opt_narrow	= 8,
  opt_lowhgt	= 16,
  opt_phypage	= 32,
  opt_showsize  = 64,
  opt_ign_ctrll = 128,
  opt_sep_logp  = 256,

  mode_pipethrough = 1 << 31,

  pitch_wide    = 1,
  pitch_normal  = 2,
  pitch_medium  = 3,
  pitch_small   = 4,
  
  font_courier	= 0,
  font_cgtimes	= 1,
  font_lettgoth	= 2,

  size_dina4    = 0,
  size_usletter = 1,
  size_uslegal  = 2,

  char_ascii    = 0,
  char_ansi     = 1,
  char_ecma     = 2,
  char_pc8      = 3,
  char_pc850    = 4,
  char_hpr8     = 5,
  char_hpl      = 6,
  
  system_tab	= 8,
  printer_tab	= 8,
  backspc	= 8
};

typedef struct flag_t_struct {
  int
    opt,
    font,
    charset,
    pagesize,
    pitch;
  float		
    hor_spacing,
    vert_spacing;
} flag_t;

typedef char * char_p_t;
// This typedef is needed, because `xxx = new (char *)[no_of_lines]' results
// in a warning, and we don't like these.

typedef struct page_t_struct {
  //=============================
  // For each line:
  char_p_t
    *data;	// Space for the text.
  int
    *startcol,	// Physical character column no., the line
		// starts at on the page.
    *len,	// Length of text line with EXPANDED TAB characters.
    		//                          ~~~~~~~~~~~~ see squeeze_line().
  //=============================
  // For the whole page:
    cpi,	// Characters per inch.
    lpi,	// Lines per inch.
    pt_size,    // Size of characters in points
    // How many lines and columns fit onto a sheet of paper?
    phy_pgwdt,	// Page width (no. of characters).
    phy_pghgt,	// Page height (no. of lines).
    // Let's divide the page into logical blocks:
    log_lines,	// Logical amount of lines that fit on a page:
	      	// = log_x_pgno * phy_pghgt
    log_pghgt,	// Logical page height. A logical page is guaranteed not to
    		// be broken on text column or physical page boundaries.
    log_y_pgno,	// How many logical pages fit onto a sheet of paper in height?
    log_x_pgno,	// ... and in width?
    log_pgwdt,	// Width of a logical page.
    usedlin,	// No. of lines that contain valid data in actual page.
    linefull,	// Used character columns of printer page. This can be less
	      	// than the whole character width, when using multiple text
	      	// column output.
    left_margin,
    right_margin,
    top_margin,
    bottom_margin, 
    datasize;	// The extent of the page buffer.
  char
    *database;	// Start address of character data space to store the
	      	// text lines in.
  //=============================
  // Count all pages:
  int
    log_rd_pg,	// No. of pages read in (logical pages)
    phy_rd_pg,	//			(physical pages)
    log_wr_pg,	// No. of printed pages (logical pages)
    phy_wr_pg;	//			(physical pages)

  //=============================
} page_t;

typedef struct pagelist_t_struct {
  int
    startpage,
    endpage;
  struct pagelist_t_struct
    *next;
} pagelist_t;

// Initialize page structure to be used by get_page() and put_page().
void setup_page(page_t *pg, flag_t flags);

// Delete dynamically allocated memory of page structure.
void delete_page(page_t *);

// Clear the page without printing it.
void clear_page(page_t *pg);

void print_file(page_t *page, pagelist_t *pgl,
		char *filename, FILE *printer,
		flag_t flags);

int page_in_pagelist(int, pagelist_t *);

void putpage(page_t *pg, pagelist_t *pgl, FILE *stream, flag_t flags);

void setup_pagelist(char *, pagelist_t **);

void delete_pagelist(pagelist_t *);

void issue_ff(FILE *printer);

void skip_to_col(page_t *pg, int previouspos, int thispos,
		 char actline[], FILE *stream);

void setup_dj500(flag_t flags, page_t page, FILE *printer);

void reset_dj500(FILE *);

#ifndef DEFAULT_PRT_NAME
#define DEFAULT_PRT_NAME "/dev/lp1"
#endif

#ifndef DEFAULT_FONT
#define DEFAULT_FONT font_courier
#endif

#ifndef DEFAULT_SIZE
#define DEFAULT_SIZE size_dina4
#endif

#ifndef DEFAULT_CHARSET
#define DEFAULT_CHARSET char_ascii
#endif

#ifndef DEFAULT_PAPER
#define DEFAULT_PAPER "a4"
#endif

#ifndef DEFAULT_MARGIN
#define DEFAULT_MARGIN 1.4
#endif

#ifndef DEFAULT_DIR_SEP
#define DEFAULT_DIR_SEP "/"
#endif

char default_prt_name[] = DEFAULT_PRT_NAME;
int  default_font       = DEFAULT_FONT;
int  default_size       = DEFAULT_SIZE;
int  default_charset    = DEFAULT_CHARSET;
char default_paper[]    = DEFAULT_PAPER;
float default_margin    = DEFAULT_MARGIN;
char default_dir_sep[]  = DEFAULT_DIR_SEP;

char
  *progname;

int
  verbose        = 1,
  file_ok	 = 0,
  file_erroneous = 0;

FILE
  *printer = NULL;

void exit_500(int i)
{
  reset_dj500(printer);
  exit(i);
}

int main(int argno, char *arglist[])
{
    char paper[256];
    char *paper_opt = 0;
  int
    margin,
    i, j;
  flag_t
    flags = {0, };
  char
    null_string = 0,
    *pagelist_string,
    *printer_name;
  float
    spacing_factor;
  pagelist_t
    *pagelist;
  page_t
    page = {0, };
  
  progname = arglist[0];

  pagelist_string    = &null_string;
  printer_name	     = default_prt_name;
  flags.font   	     = default_font;
  flags.pagesize     = default_size;
  flags.charset      = default_charset;
  flags.opt 	     = 0;
  flags.hor_spacing  = 1.0;
  flags.vert_spacing = 1.0;
  
  for(i = 1; i < argno; i += 1) {
    if(strcmp(arglist[i], "-wc") == 0) {
      printf("\n\
    djscript - A text formatter for the HP DeskJet500 (tm) printer.\n\
\n\
    Version %s; Copyright (C) 1993, 1994 Joerg Bullmann\n\
\n\
    This program is free software; you can redistribute it and/or modify\n\
    it under the terms of the GNU General Public License as published by\n\
    the Free Software Foundation; either version 2 of the License, or\n\
    (at your option) any later version.\n\
\n\
    This program is distributed in the hope that it will be useful,\n\
    but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n\
    GNU General Public License for more details.\n\
\n\
    You should have received a copy of the GNU General Public License\n\
    along with this program; if not, write to the Free Software\n\
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n\n", prog_version);
      exit(0);
    }
    else if(strncmp(arglist[i], "-P", 2) == 0)
      printer_name = arglist[i] + 2;
    else if(strncmp(arglist[i], "-F", 2) == 0) {
      for(j = 0; font_names[j] && strcmp(arglist[i] + 2, font_names[j]); j++);
      if(font_names[j])
	flags.font = j;
      else {
	fprintf(stderr, "%s: unknown font `%s'\n",
		progname, arglist[i] + 2);
	exit(1);
      }
    }
    else if(strncmp(arglist[i], "-S", 2) == 0) {
        paper_opt = arglist[i] + 2;
    }
    else if(strncmp(arglist[i], "-C", 2) == 0) {
      for(j = 0; charset_names[j] && strcmp(arglist[i] + 2, charset_names[j]); j++);
      if(charset_names[j])
	flags.charset = j;
      else {
	fprintf(stderr, "%s: unknown font `%s'\n",
		progname, arglist[i] + 2);
	exit(1);
      }
    }
    else if(strncmp(arglist[i], "-cw", 3) == 0) {
      if(sscanf(arglist[i] + 3, "%d", &(page.log_pgwdt)) != 1 ||
	 page.log_pgwdt < 0) {
	fprintf(stderr, "%s: bad column width `%s'.\n",
		progname, arglist[i] + 3);
	exit(1);
      }
    }
    else if(strncmp(arglist[i], "-cn", 3) == 0) {
      if(sscanf(arglist[i] + 3, "%d", &(page.log_x_pgno)) != 1 ||
	 page.log_x_pgno < 0) {
	fprintf(stderr, "%s: bad column number `%s'.\n",
		progname, arglist[i] + 3);
	exit(1);
      }
    }
    else if(strncmp(arglist[i], "-m", 2) == 0) {
      if(sscanf(arglist[i] + 3, "%d", &margin) != 1) {
	fprintf(stderr, "%s: bad margin size `%s'.\n",
		progname, arglist[i] + 3);
	exit(1);
      }
      switch(arglist[i][2]) {
      case 'l':
	page.left_margin = margin; break;
      case 'r':
	page.right_margin = margin; break;
      case 't':
	page.top_margin = margin; break;
      case 'b':
	page.bottom_margin = margin; break;
      default:
	fprintf(stderr, "%s: bad margin type `%c'.\n",
		progname, arglist[i][2]);
	exit(1);	
      }
    }
    else if(strncmp(arglist[i], "-sf", 3) == 0) {
      if(sscanf(arglist[i] + 4, "%f", &spacing_factor) != 1 ||
	 spacing_factor <= 0) {
	fprintf(stderr, "%s: bad spacing factor `%s'.\n",
		progname, arglist[i] + 4);
	exit(1);
      }
      switch(arglist[i][3]) {
      case 'h':
	flags.hor_spacing = spacing_factor; break;
      case 'v':
	flags.vert_spacing = spacing_factor; break;
      default:
	fprintf(stderr, "%s: bad spacing factor type `%c'.\n",
		progname, arglist[i][3]);
	exit(1);	
      }
    }
    else if(strncmp(arglist[i], "-lph", 4) == 0) {
      if(sscanf(arglist[i] + 4, "%d", &(page.log_pghgt)) != 1 ||
	 page.log_pghgt < 0) {
	fprintf(stderr, "%s: bad logical page height `%s'.\n",
		progname, arglist[i] + 3);
	exit(1);
      }
    }
    else if(strcmp(arglist[i], "-7966") == 0) {
      page.log_x_pgno = 0;
      page.log_pgwdt  = 79;
      page.log_pghgt  = 66;
    }
    else if(strncmp(arglist[i], "-pr", 3) == 0)
      pagelist_string = arglist[i] + 3;
    else if(strcmp(arglist[i], "-sg") == 0)
      flags.opt |= opt_showsize;
    else if(strcmp(arglist[i], "-pp") == 0)
      flags.opt |= opt_phypage;
    else if(strcmp(arglist[i], "-ls") == 0)
      flags.opt |= opt_lndscp;
    else if(strcmp(arglist[i], "-itl") == 0)
      flags.opt |= opt_italic;
    else if(strcmp(arglist[i], "-lq") == 0)
      flags.opt |= opt_lq;
    else if(strcmp(arglist[i], "-low") == 0)
      flags.opt |= opt_lowhgt;
    else if(strcmp(arglist[i], "-nrw") == 0)
      flags.opt |= opt_narrow;
    else if(strcmp(arglist[i], "-il") == 0)
      flags.opt |= opt_ign_ctrll;
    else if(strcmp(arglist[i], "-sp") == 0)
      flags.opt |= opt_sep_logp;
    else if(strcmp(arglist[i], "-sml") == 0)
      flags.opt |= (opt_narrow | opt_lowhgt);
    else if(strcmp(arglist[i], "-q") == 0)
      verbose = 0;
    else if(strcmp(arglist[i], "-h") == 0) {
      printf("Usage:\n");
      printf("  %s -h\n", progname);
      printf("  %s [Options] [<fil> ... ]\n", progname);
      printf("Options:\n");
      printf("  -F<font>    : Select font: `courier', `cgtimes', `lettgoth'\n"
	     "                (default: `%s')\n", font_names[default_font]);
      printf("  -P<prfil>   : Print to file <prfil> (default: `%s'). If <prfil>\n"
	     "                equals `-', standard output is taken. If <prfil>\n"
             "                equals `:<cmd>', output is piped into <cmd>.\n",
	     default_prt_name);
      printf("  -S<size>    : Select paper size: `a4', `legal', `letter'\n"
	     "                (default: `%s')\n",
	     size_names[default_size]);
      printf("  -C<charset> : Select character set: `ascii', `ansi', `ecma'\n"
	     "                `pc8', `pc850', `hpr8', `hpl' (default: `%s')\n",
	     charset_names[default_charset]);
      printf("  -lq         : Letter Quality instead of Draft.\n");
      printf("  -itl        : Italics font.\n");
      printf("  -nrw        : Narrow printing. `horizontal double density'\n"
	     "                (ignored with font `cgtimes')\n");
      printf("  -low        : Low font. `vertical double density'\n");
      printf("  -sml        : Small font: narrow and low.\n");
      printf("  -ls         : Landscape orientation instead of portrait.\n"
	     "                (works only with font `courier normal')\n");
      printf("  -lph<n>     : Set the logical page height to <n> lines.\n");
      printf("  -c[nw]<n>   : Produce multi column output of <n> text columns (n)\n");
      printf("                or columns of <n> characters width each (w).\n");
      printf("  -7966       : Logical pages of 66 lines and 79 columns each.\n");
      printf("  -m[lrtb]<n> : set left/right/top/bottom margin to column/row <n>.\n");
      printf("  -sf[hv]<x>  : Horizontal and vertical spacing factors.\n");
      printf("  -pr<pgs>    : Print pages in page range specification <pgs> only.\n");
      printf("                <pgs> is a comma separated list of expressions of the\n");
      printf("                form i, -i, i-j, or i- where i and j denote logical\n");
      printf("                page numbers.\n");
      printf("  -il         : Ignore Ctrl-L characters.\n");
      printf("  -pp         : Start each file on a new physical page.\n");
      printf("  -sp         : Separate logical pages by horizontal and vertical bars.\n");
      printf("  -sg         : Show the page geometry; do not print.\n");
      printf("  -q          : Be quiet.\n");
      printf("  -wc         : Show nonwarranty and copying info.\n");
      printf("  -h          : Show this help info.\n");
      printf("\n");
      exit(0);
    }
    else if(strcmp(arglist[i], "-") == 0)
      i += 1;
    else if(arglist[i][0] == '-')
      fprintf(stderr, "%s: option `%s' ignored.\n",
	      progname, arglist[i]);
  }

  if(flags.font == font_cgtimes)
    flags.opt |= mode_pipethrough;

  if((flags.opt & opt_lndscp) &&
     ((flags.opt & opt_italic) || flags.font != font_courier)) {
    fprintf(stderr,
	    "%s: in landscape orientation, font `courier normal' only.\n",
	    progname);
    exit(1);
  }

  if(!(flags.opt & opt_showsize)) {
    if(strcmp(printer_name, "-") == 0) {
      printer = stdout;
      if(verbose)
	fprintf(stderr, "%s: printing to standard output.", progname);    
    }
    else if(printer_name[0] == ':') {
      if((printer = popen(printer_name + 1, "w")) == NULL) {
	fprintf(stderr, "%s: couldn't open pipe to `%s'.\n",
		progname, printer_name + 1);
	exit(1);
      }
    if(verbose)
      fprintf(stderr, "%s: printing into pipe `%s'.",
	      progname, printer_name + 1);
    }
    else {
      if((printer = fopen(printer_name, "w")) == NULL) {
	fprintf(stderr, "%s: couldn't open file `%s'.\n",
	      progname, printer_name);
	exit(1);
      }
      if(verbose)
	fprintf(stderr, "%s: printing to file `%s'.",
		progname, printer_name);
    }
  }
  /* Handle paper preference */
  if (paper_opt) {
      strncpy(paper,paper_opt,256);
  } else {
      if ( systempapername() != NULL)
      {
          strncpy(paper,systempapername(), 256);
      } else {
          strncpy(paper,defaultpapername(),256);
      }
  }
  for(j = 0; size_names[j] && strcmp(paper, size_names[j]); j++);
  if(size_names[j])
    flags.pagesize = j;
  else {
    fprintf(stderr, "%s: unknown paper name `%s'\n",
      progname, paper);
      exit(1);
  }


  setup_page(&page, flags);

  setup_pagelist(pagelist_string, &pagelist);

  setup_dj500(flags, page, printer);

  for(i = 1; i < argno; i += 1)
    if(arglist[i][0] != '-')
      print_file(&page, pagelist, arglist[i], printer, flags);
    else if(strcmp(arglist[i], "-") == 0 && ++i < argno)
      print_file(&page, pagelist, arglist[i], printer, flags);

  if(file_ok + file_erroneous == 0)
    print_file(&page, pagelist, "", printer, flags);

  if(flags.opt & mode_pipethrough)
    issue_ff(printer);
  else
    putpage(&page, pagelist, printer, flags);

  if(verbose) {
    if(file_ok > 0)
      fprintf(stderr,
	      "\n%s: %d File(s) printed.",
	      progname, file_ok);
    if(file_erroneous > 0)
      fprintf(stderr,
	      "\n%s: %d File(s) NOT printed.",
	      progname, file_erroneous);
    
    fprintf(stderr, "\n");
  }

  delete_page(&page);
  delete_pagelist(pagelist);

  reset_dj500(printer);

  if(printer_name[0] == ':')
    pclose(printer);
  else
    fclose(printer);

  exit(0);
}

void escape(const char string[], FILE *printer)
{
  fprintf(printer, "%c%s", 0x1b, string);
}

void escape_sis(char string1[], int val, char string2[], FILE *printer)
{
  fprintf(printer, "%c%s%d%s", 0x1b, string1, val, string2);
}

void escape_sfs(char string1[], float val, char string2[], FILE *printer)
{
  fprintf(printer, "%c%s%4.2f%s", 0x1b, string1, val, string2);
}

void issue_ff(FILE *printer)
{
  escape("&l0H", printer); // page out!
}

void reset_dj500(FILE *printer)
{
  escape("E", printer); // Reset the printer!
}

//================================================================
// Initialize the Deskjet 500 and put it into the desired mode.
// The mode is determined by the flags, font, and page variables.
//================================================================
void setup_dj500(flag_t flags, page_t page, FILE *printer)
{
  // HP Deskjet 500 PCL commands

  reset_dj500(printer);

  escape("&k2G", printer); // CR => CR + LF

  escape("&s0C", printer); // Wrap long lines.

  //=============================================================
  // The commands no. 1 to 9 must be issued in this order.
  //=============================================================

  // 1. Page orientation
  escape((flags.opt & opt_lndscp) ? "&l1O" : "&l0O", printer);

  // 2. Character set
  switch(flags.charset) {
  case char_ascii:
  case char_ansi:
    escape("(0U", printer); break;
  case char_ecma:
    escape("(0N", printer); break;
  case char_pc8:
    escape("(10U", printer); break;
  case char_pc850:
    escape("(12U", printer); break;
  case char_hpr8:
    escape("(8U", printer); break;
  case char_hpl:
    escape("(1U", printer); break;
  default:
    fprintf(stderr, "%s: bad character set.\n", progname);
    exit_500(1);
  }

  // 3. Proportional spacing
  escape((flags.font == font_cgtimes) ? "(s1P" : "(s0P", printer);

  // 4. Horizontal density (cpi)
  if(flags.font != font_cgtimes)
    escape_sis("(s", page.cpi, "H", printer);

  // 5. Height of characters
  escape_sis("(s", 18 - page.lpi, "V", printer);

  // 6. Typeface
  escape((flags.opt & opt_italic) ? "(s1S" : "(s0S", printer); // italic : normal

  // 7. Line thickness normal
  escape("(s0B", printer);
  
  // 8. Font
  switch (flags.font) {
    case font_lettgoth:         escape("(s6T",   printer); break;
    case font_cgtimes:          escape("(s4101T", printer); break;
    default: /* font_courier */ escape("(s3T", printer);
  }

  // 9. Quality
  escape((flags.opt & opt_lq) ? "(s2Q" : "(s1Q", printer); // nice : fast

  // Paper size
  switch(flags.pagesize) {
  case size_dina4:     escape("&l26A", printer); break;
  case size_uslegal:   escape("&l3A",  printer); break;
  case size_usletter:  escape("&l2A",  printer); break;
  default:
    fprintf(stderr, "%s: bad paper size.\n", progname);
    exit_500(1);
  }

  // Perforation skip
  escape("&l0L", printer);

  // Vertical spacing (lpi)
  escape_sis("&l", page.lpi, "D", printer);

  escape_sfs("&l", (float) 48  / (page.lpi / flags.vert_spacing), "C", printer);
  escape_sfs("&k", (float) 120 / (page.cpi / flags.hor_spacing), "H", printer);
  
  // Text Scale Mode
  escape("&k6W", printer);

  // Margins have to be set after the font, because they are
  // measured in character columns/rows.
  escape_sis("&a", page.left_margin,  "L", printer);
  escape_sis("&a", page.right_margin, "M", printer);
  escape_sis("&l", page.top_margin,   "E", printer);
  escape_sis("&l", page.phy_pghgt,    "F", printer);
  
}

void setup_pagelist(char *str, pagelist_t **lst)
{
int
  i,
  start = 0, end = 0,
  *x;

  if(*str == 0) {
    *lst = 0;
    return;
  }

  x = &start;
  for(i = 0; 1; i++) {
    switch(str[i]) {
      case '-':
        if(start == 0) start = 1;
        x = &end;
        break;
      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':
        *x = *x * 10 + str[i] - '0';
        break;
      case ',':
      case 0:
        if(x == &start) end = start;
        if(start != 0 && (start <= end || end == 0)) {
	  if((*lst = new pagelist_t) == 0) {
	    fprintf(stderr, "%s: not enough memory for pagelist.\n", progname);
	    exit_500(1);
	  }
	  //printf("%d - %d\n", start, end);
	  (*lst)->startpage = start;
	  (*lst)->endpage = end;
	  while(str[i] == ',') i++;
	  setup_pagelist(str + i, &((*lst)->next));
	  return;
	}
	else {
	  fprintf(stderr, "%s: bad pagelist specification `%s'.\n",
		  progname, str);
	  exit_500(1);
	}
        break;
      default:
        fprintf(stderr, "%s: bad pagelist specification `%s'.\n",
		progname, str);
        exit_500(1);
    }
  } 
}

int page_in_pagelist(int pgno, pagelist_t *pgl)
{
  if(pgl == 0) {
    if(verbose)
      fprintf(stderr, "%d ", pgno);
    return 1;
  }

  while(pgl != 0) {
    if(pgno >= pgl->startpage && (pgno <= pgl->endpage || pgl->endpage == 0)) {
      if(verbose)
	fprintf(stderr, "%d ", pgno);
      return 1;
    }
    pgl = pgl->next;
  }

  return 0;
}

void delete_pagelist(pagelist_t *pgl)
{
pagelist_t
  *x;

  while(pgl != 0) {
    x = pgl;
    pgl = pgl->next;
    delete x;
  }
}

//===========================================================
// setup_page determines the geometry of the page and the
// text columns. It also allocates memory so that the page
// structure can be used by getpage() and putpage() lateron.
//===========================================================
void setup_page(page_t *pg, flag_t flags)
{
  int sheetheight, sheetwidth;

  // Reset the page counters
  pg->log_rd_pg = 0;
  pg->log_wr_pg = 0;
  pg->phy_rd_pg = 0;
  pg->phy_wr_pg = 0;

  switch(flags.font) {
  case font_cgtimes:  pg->cpi = 0;  break;
  case font_courier:  pg->cpi = 10; break;
  case font_lettgoth: pg->cpi = 12; break;
  default:
    fprintf(stderr, "%s: bad font in setup_page().\n", progname);
    exit_500(1);
  }

  pg->cpi *= ((flags.opt & opt_narrow) != 0) + 1;
  pg->lpi = ((flags.opt & opt_lowhgt) != 0) ? 12 : 6;

  if(flags.opt & opt_lndscp) {
    pg->phy_pghgt = flags.opt & opt_lowhgt ? 96 : 48;
    switch(flags.pagesize) {
    case size_dina4:    pg->phy_pgwdt = 110; break;
    case size_usletter: pg->phy_pgwdt = 103; break;
    case size_uslegal:  pg->phy_pgwdt = 133; break;
    default:
      fprintf(stderr, "%s: bad paper size.\n", progname);
      exit_500(1);
    }
    if(flags.opt & opt_narrow)
      pg->phy_pgwdt *= 2;
  }
  else {
    // For portrait mode I found out these resolutions by trying.
    pg->phy_pgwdt = (flags.font == font_courier ? 10 : 12) *
                    (flags.opt & opt_narrow ? 16 : 8);
    switch(flags.pagesize) {
    case size_dina4:    pg->phy_pghgt = 70; break;
    case size_usletter: pg->phy_pghgt = 66; break;
    case size_uslegal:  pg->phy_pghgt = 84; break;
    default:
      fprintf(stderr, "%s: bad paper size.\n", progname);
      exit_500(1);
    }
    if(flags.opt & opt_lowhgt) {
      pg->phy_pghgt *= 2;
      pg->phy_pghgt -= 1;
    }
  }

  pg->phy_pghgt = (int) (pg->phy_pghgt / flags.vert_spacing);
  pg->phy_pgwdt = (int) (pg->phy_pgwdt / flags.hor_spacing);
  
  sheetheight = pg->phy_pghgt;
  sheetwidth  = pg->phy_pgwdt;

  if(pg->bottom_margin <= 0)
    pg->bottom_margin = pg->phy_pghgt + pg->bottom_margin;
    
  if(pg->top_margin < 0 || pg->top_margin >= pg->bottom_margin) {
    fprintf(stderr, "%s: bad top/bottom margins.\n", progname);
    exit_500(1);
  }

  pg->phy_pghgt = pg->bottom_margin - pg->top_margin;

  if(pg->right_margin <= 0)
    pg->right_margin = pg->phy_pgwdt + pg->right_margin;

  if(pg->left_margin < 0 || pg->left_margin >= pg->right_margin) {
    fprintf(stderr, "%s: bad left/right margins.\n", progname);
    exit_500(1);
  }

  pg->phy_pgwdt = pg->right_margin - pg->left_margin;

  if(pg->log_pghgt > 0) {
    pg->log_y_pgno = (pg->phy_pghgt + 1) / (pg->log_pghgt + 1);
    pg->phy_pghgt  = pg->log_y_pgno * pg->log_pghgt;
  }
  else {
    pg->log_y_pgno = 1;
    pg->log_pghgt  = pg->phy_pghgt;
  }

  if(pg->log_x_pgno > 0)
    pg->log_pgwdt = (pg->phy_pgwdt - pg->log_x_pgno + 1) / pg->log_x_pgno;
  else if(pg->log_pgwdt == 0) {
    pg->log_x_pgno = 1;
    pg->log_pgwdt  = pg->phy_pgwdt;
  }
  else {
    pg->log_x_pgno = (pg->phy_pgwdt + 1) / (pg->log_pgwdt + 1);
    pg->log_pgwdt  = (pg->phy_pgwdt - pg->log_x_pgno + 1) / pg->log_x_pgno;
  }

  if(flags.font == font_cgtimes && pg->log_x_pgno > 1) {
    fprintf(stderr,
	    "%s: multicolumn output with font `cgtimes' not supported.\n",
	    progname);
    exit_500(1);
  }

  pg->log_lines	 = pg->log_x_pgno * pg->phy_pghgt;
  pg->usedlin	 = 0;
  pg->linefull	 = pg->log_pgwdt * pg->log_x_pgno + pg->log_x_pgno - 1;
  // used columns of page

  // Hopefully, this buffersize is big enough:
  pg->datasize = 2 * pg->log_lines * (pg->log_pgwdt + 1);

  if((pg->data     = new char_p_t[pg->log_lines]) == 0 ||
     (pg->database = new char[pg->datasize]) == 0 ||
     (pg->startcol = new int[pg->log_lines]) == 0 ||
     (pg->len	   = new int[pg->log_lines]) == 0) {
    fprintf(stderr,
	    "%s: not enough memory for page buffer.\n",
	    progname);
    exit_500(1);
  }

  pg->data[0] = pg->database;

  if(pg->log_pghgt > pg->phy_pghgt ||
     pg->log_pgwdt > pg->phy_pgwdt) {
    fprintf(stderr,
	    "%s: logical page size exceeds physical page size.\n",
	    progname);
    exit_500(1);
  }

  if(flags.opt & opt_showsize) {
    fprintf(stderr, "Paper size: ");
    switch(flags.pagesize) {
    case size_dina4:    fprintf(stderr, "DIN A4\n"); break;
    case size_uslegal:  fprintf(stderr, "US Legal\n"); break;
    case size_usletter: fprintf(stderr, "US Letter\n"); break;
    }
    fprintf(stderr, "Geometry:            Lines Columns\n");
    fprintf(stderr, "Per inch              %3d    %3d\n", pg->lpi, pg->cpi);
    fprintf(stderr, "On physical page      %3d    %3d\n", sheetheight, sheetwidth);
    fprintf(stderr, "Top margin            %3d\n", pg->top_margin);
    fprintf(stderr, "Bottom margin         %3d\n", sheetheight - pg->bottom_margin);
    fprintf(stderr, "Left margin                  %3d\n", pg->left_margin);
    fprintf(stderr, "Right margin                 %3d\n", sheetwidth - pg->right_margin);
    fprintf(stderr, "On used physical page %3d    %3d\n", pg->phy_pghgt, pg->phy_pgwdt);
    fprintf(stderr, "On logical page       %3d    %3d\n", pg->log_pghgt, pg->log_pgwdt);
    fprintf(stderr, "Logical pages         %3d    %3d\n", pg->log_y_pgno, pg->log_x_pgno);
    exit(0);
  }
}

void delete_page(page_t *pg)
{
  delete pg->database;
  delete pg->data;
  delete pg->startcol;
  delete pg->len;
}

//===================================================================
// squeeze_line preprocesses a text line.
// We want to use as less printer buffer space as possible when
// printing the page lateron.
// We have two main reasons:
// 1) It saves time since we have to wait, if the buffer is full.
// 2) When printing in LANDSCAPE MODE WITH SMALL FONT, a whole
//    page DOESN'T fit into the Deskjet 500's printer buffer (16k).
//    So we have to be accurate here.
// squeeze_line expects the following:
// 1) pg->data[line] is the base address of the string of the current
//    text line. This string needn't be null-terminated, because:
// 2) col contains the length of the text line.
// 3) startcol is the number of leading white spaces. A tab character
//    counts as his adequate space-character-expansion here.
//===================================================================
void squeeze_line(page_t *pg, int line, int col, int startcol)
{
int
  ch,
  poffset,
  readcol;

  // Store the number of leading whitespaces.
  // In variable pg->startcol[line] the offset of character
  // columns of the current text column (logical page) is reckoned in.
  pg->startcol[line] =
    (startcol+=((line/pg->log_pghgt)%pg->log_x_pgno)*(pg->log_pgwdt+1));
  //	       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  // See below in function getpage():
  poffset = 0;
  
  // Cut off trailing white spaces ...
  while(col > 0 && pg->data[line][col - 1] == ' ')
    col--;
  // ... and make the string null-terminated.
  pg->data[line][col] = 0;

  // Replace spaces by appropriate tabs.
  for(col = 0, readcol = 0; pg->data[line][readcol]; readcol += 1, col += 1) {
    ch = pg->data[line][col] = pg->data[line][readcol];
    if(ch == backspc)
      poffset -= 2;
    // The (startcol + readcol + poffset) construct is needed when we are
    // producing multiple column text output.
    if((startcol + readcol + poffset) % printer_tab == printer_tab - 1 &&
       ch == ' ') {
      // We only achieve a saving if we are going backwards here:
      while(col > 0 && pg->data[line][col - 1] == ' ')
	col--;
      pg->data[line][col] = '\t';
    }
  }

  // Determine the length of the line without the leading white spaces.
  pg->len[line]	      = readcol + poffset;
  pg->data[line][col] = 0;

  // The next text line starts in the page buffer at this position:
  if(line + 1 < pg->log_lines)
    pg->data[line + 1] = pg->data[line] + col + 1;

}

//==================================================================
// If the current logical page is empty, do nothing.
// If if is partially filled, fill it up with empty lines.
//==================================================================
void fillpage(page_t *pg)
{
int next_page_start = (pg->usedlin / pg->log_pghgt + 1) * pg->log_pghgt;

  // Is the logical page empty?
  if(next_page_start - pg->log_pghgt == pg->usedlin)
    return;

  // Fill the unused lines of the logical page with `empty lines'.
  while(pg->usedlin < next_page_start)
    squeeze_line(pg, pg->usedlin++, 0, 0);

}

//==================================================================
// Read a page into the page structure *pg.
// If in one getpage() the page isn't filled completely (because of
// an EOF encounter), it can be filled up by immediately following
// getpage()'s.
// If a page is full, additional getpage()'s have NO effect on both
// input stream and page structure.
// getpage() returns the no. of lines in the current page.
// The page buffer is emptied by putpage().
//==================================================================
int getpage(page_t *pg, pagelist_t *pgl, flag_t flags, FILE *stream)
{
int
  poffset,
  leadwhite,
  startcol,
  ch = 0,
  line,
  log_page_startline,
  col;

  // Attach the new data to the end of the stuff that's still
  // in the page buffer. Thus we are able to perform multiple
  // getpage commands with no putpage command between them.
  // If the page is already full, nothing is appended to it.
  if((line = pg->usedlin) == pg->log_lines)
    return line;

  // Start at the beginning of the line.
  col = 0;

  // We don't store the leading white spaces of each line, but
  // their number. So at the start of each line, we're in
  // leading-whitespace-count-mode:
  leadwhite = 1;

  // In variable `startcol' is assumed that the leftmost character column
  // has the number 0. startcol stores the number of leading blanks.
  startcol = 0;

  // If we run into backspace characters, normal column numbers don't
  // correspond to their printing positions on the page anymore.
  // Therefore we introduce the printing offset `poffset', that indicates
  // the difference between the character string column and the print
  // column.
  poffset = 0;

  // This outer loop goes through the logical pages on a physical page.
  do {
    // Determine the first line of the actual logical page.
    log_page_startline = (line / pg->log_pghgt) * pg->log_pghgt;
    // Transfer the logical page from the input stream to the page buffer.
    while(line - log_page_startline < pg->log_pghgt &&
	  (ch = fgetc(stream)) != EOF) {
      if(pg->data[line] + col >= pg->database + pg->datasize) {
	fprintf(stderr, "%s: page buffer overflow.\n", progname);
	exit_500(1);
      }
      else if(startcol + col + poffset >= pg->log_pgwdt ||
              ch == '\f' || ch == '\n') {
	// We interpret a CR character as well as a CTRL-L character
	// to be a line terminating symbol.
	// One line is full.
	squeeze_line(pg, line++, col, startcol);
	// If we have to break the line, we mustn't loose
	// the last character read in.
	if(ch != '\n' && ch != '\f')
	  ungetc(ch, stream);
	col = startcol = poffset = 0;
	leadwhite = 1;
	if(ch == '\f') {
	  if(!(flags.opt & opt_ign_ctrll)) {
	    // If we've found a formfeed character (CTRL-L)
	    // we skip to the next logical page.
	    while(line - log_page_startline < pg->log_pghgt)
	      squeeze_line(pg, line++, col, startcol);
	    // Ignore a CR immediately after a CTRL-L.
	  }
	  if((ch = fgetc(stream)) != '\n' && ch != EOF)
	    ungetc(ch, stream);
        }
      }
      else if(leadwhite) {
	if(ch == ' ')
	  startcol++;
	else if(ch == '\t')
	  startcol = (startcol / system_tab + 1) * system_tab;
	else {
	  ungetc(ch, stream);
	  leadwhite = 0;
	}
      }
      else if(ch == '\t') {
	// Expand tabs here. They are `imploded' in squeeze_line().
	// That's necessary because if a line has to be printed in a
	// text column other than the first one (a logical page that
	// is not printed at the left margin of the physical page),
	// a TAB character may have a different meaning 
	// (i.e. space expansion) than expected.
	do {
	  pg->data[line][col++] = ' ';
	} while((startcol + col + poffset) < pg->log_pgwdt &&
		(startcol + col + poffset) % system_tab != 0);
      }
      else if(ch == backspc) {
	if(startcol + col + poffset > 0) {
	  poffset -= 2;
	  pg->data[line][col++] = ch;
	}
	else
	  fprintf(stderr,
		  "%s: warning: trying to backspace over left margin.\n",
		  progname);
      }
      else
	pg->data[line][col++] = ch;

    }

    // If this log. page read finishes because of an EOF encounter, and the last
    // text line read in is neither full nor terminated by a newline character,
    // we have to process it here. If it IS terminated by a newline character,
    // we are dealing with a valid empty line here.
    if(ch == EOF) {
      // If Ctrl-L, we read in all the lines, and this could be a case. 
      // Is this correct?
      if (line - log_page_startline < pg->log_pghgt)
        squeeze_line(pg, line++, col, startcol);
      // Cut off trailing empty lines at the end of the file.
      while(line > 0 && pg->data[line - 1][0] == 0)
	line--;
    } 

    if(line > 0) {
      pg->log_rd_pg += 1;
      // Do we have to print it, or is it to be suppressed?
      if(!page_in_pagelist(pg->log_rd_pg, pgl))
        line = ((line - 1) / pg->log_pghgt) * pg->log_pghgt;
    }
  } while(line < pg->log_lines && ch != EOF);

  pg->usedlin = line;
  return line;
}

void skip_to_col(page_t *pg,
		 int previouspos,
		 int thispos,
		 char actline[],
		 FILE *stream)
{
  int k;
  
  if(thispos - previouspos > 5) {
    // If the last text line ended in the rightmost text column of
    // its row, NO additional linefeed is given there. The DJ500
    // thinks, that he is still on that previous line. So we have
    // to switch to the actual line by writing a single space
    // character, if we want to issue printer control sequences as
    // e.g. `horizontal cursor positioning'.
    if(!previouspos)
      fputc(' ', stream);
    fprintf(stream, "%c&a%dC%s",
	    0x1b, thispos + pg->left_margin, actline);
  }
  else {
    for(k = previouspos; k < thispos; k++)
      fputc(' ', stream);
    fprintf(stream, "%s", actline);
  }
}

//==================================================================
// We now have to print out the logical lines of our page in the
// right order so that logical pages and text columns make sense.
//==================================================================
void putpage(page_t *pg, pagelist_t *pgl, FILE *stream, flag_t flags)
{
  int
    i, j, k,
    log_page_offset,
    line_index,
    thispos,
    previouspos;
  char
    null_line = 0,
    *actline;

  if(pg->usedlin == 0) {
    clear_page(pg);
    return;
  }

  // Count the pages that are printed.
  pg->phy_wr_pg += 1;
  pg->log_wr_pg += (pg->log_pghgt + pg->usedlin - 1) / pg->log_pghgt;

  // Fill the unused lines of the page with `empty lines'.
  while(pg->usedlin < pg->log_lines)
    pg->data[pg->usedlin++] = &null_line;

  // `log_page_offset' is the number of the logical line that is printed
  // in the upper left corner of the current row of logical pages.

  for(log_page_offset = 0;
      log_page_offset < pg->usedlin;
      log_page_offset += pg->log_pghgt * pg->log_x_pgno) {
    // If this is not the first row of logical pages, separate it
    // from the previous one:
    if(log_page_offset > 0) {
      if(flags.opt & opt_sep_logp) {
	// Draw horizontal line between logical page rows.
	k = (pg->log_pgwdt + 1) * pg->log_x_pgno - 1;
	for(j = 0; j < pg->log_x_pgno; j++) {
	  for(i = 0; i < pg->log_pgwdt; i++)
	    fputc('-', stream);
	  if(j < pg->log_x_pgno - 1) {
	    fputc('|', stream);
	    fputc(backspc, stream);
	    fputc('-', stream);
	  }
	}
      }
      else
	k = 0;
      if(k < pg->phy_pgwdt)
	fputc('\n', stream);
    }
    // Traverse the text lines of one row of logical pages on a physical
    // sheet of paper.
    for(i = 0; i < pg->log_pghgt; i++) {
      previouspos = 0;
      line_index  = log_page_offset + i;
      // Process every text column of the current physical line.
      // In one physical line two adjacent logical lines have the
      // logical distance pg->log_pghgt. You can also call this
      // text block distance.
      for(j = 0; j < pg->log_x_pgno; j++, line_index += pg->log_pghgt) {
	actline = pg->data[line_index];
	if(actline[0] != 0) {
	  // If we have an empty logical line, we skip it.
	  thispos = pg->startcol[line_index];
	  // Fill the physical line from previouspos to thispos with spaces!
	  // Don't waste printer buffer space!!!
	  skip_to_col(pg, previouspos, thispos, actline, stream);
	  previouspos = thispos + pg->len[line_index];
	}
	if(j < pg->log_x_pgno - 1 && (flags.opt & opt_sep_logp)) {
	  // Separate two adjacent logical page columns by vertical bars.
	  thispos = (j + 1) * (pg->log_pgwdt + 1) - 1;
	  skip_to_col(pg, previouspos, thispos, "|", stream);
	  previouspos = thispos + 1;
	}
      }
      // Switch to the next physical line.
      if(previouspos < pg->phy_pgwdt)
	fputc('\n', stream);
    }
  }

  issue_ff(stream);
  
  if(verbose)
    fprintf(stderr, "[%d] ", pg->phy_wr_pg);

  clear_page(pg);
}

void clear_page(page_t *pg)
{
  // Reset the page. Mark it to be empty.
  pg->usedlin = 0;
  pg->data[0] = pg->database;
}

//==================================================================
// Is this page ready to be printed?
// That depends on whether the page ist (at least partially) filled
// and on the flags: flags & opt_phypage means that *pg is to be
// printed even if it is not filled up completely.
// If this flag is not set, the current logical page ist filled
// with blanks.
//==================================================================
int pageready(page_t *pg, flag_t flags)
{
  if(flags.opt & opt_phypage)
    return pg->usedlin > 0;
  else {
    fillpage(pg);
    return pg->usedlin == pg->log_lines;
  }
}

void print_file(page_t *page, pagelist_t *pgl,
		char *filename, FILE *printer,
		flag_t flags)
{
  FILE
    *fil;
  int
    ch;

  if(filename[0] == 0) {
    if(verbose)
      fprintf(stderr, "\nstdin - %s", progname);
    fil = stdin;
  }
  else if((fil = fopen(filename, "r")) == NULL) {
    file_erroneous += 1;
    fprintf(stderr, "\n%s: couldn't open file `%s'.",
	    progname, filename);
    return;
  }
  else {
    file_ok += 1;
    if(verbose)
      fprintf(stderr, "\nFile `%s' - ", filename);
  }

  if(flags.opt & mode_pipethrough)
    while((ch = getc(fil)) != EOF) 
      putc(ch, printer);
  else
    while(getpage(page, pgl, flags, fil) && pageready(page, flags))
      putpage(page, pgl, printer, flags);

  if(fil != stdin)
    fclose(fil);
}
