// ------------------------------- //
// -------- Start of File -------- //
// ------------------------------- //
// ----------------------------------------------------------- // 
// C++ Source Code File Name: pscript.cpp
// Compiler Used: MSVC40, DJGPP 2.7.2.1, GCC 2.7.2.1, HP CPP 10.24
// Produced By: Doug Gaer 
// File Creation Date: 12/17/1997  
// Date Last Modified: 11/03/1998 
// Copyright (c) 1997 Douglas M. Gaer
// ----------------------------------------------------------- // 
// ------------- Program Description aond Details ------------- // 
// ----------------------------------------------------------- // 
/*
The VBD C++ classes are copyright (c) 1997, by Douglas M. Gaer.
All those who put this code or its derivatives in a commercial
product MUST mention this copyright in their documentation for
users of the products in which this code or its derivative
classes are used. Otherwise, you have the freedom to redistribute
verbatim copies of this source code, adapt it to your specific
needs, or improve the code and release your improvements to the
public provided that the modified files carry prominent notices
stating that you changed the files and the date of any change.

THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
THE ENTIRE RISK OF THE QUALITY AND PERFORMANCE OF THIS SOFTWARE
IS WITH YOU. SHOULD ANY ELEMENT OF THIS SOFTWARE PROVE DEFECTIVE,
YOU WILL ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR
CORRECTION.

The PostScriptDrv class is used to create postscript documents.
This version prints postscript documents to a file.
*/
// ----------------------------------------------------------- // 
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <math.h> // For ceil() function
#include "pscript.hpp"

// PostScript fonts
const char *PostScriptFonts[NumberOfFonts] = {
  // Courier fonts
  "Courier",             // COURIER
  "Courier-Bold",        // COURIER_BOLD
  "Courier-Oblique",     // COURIER_OBLIQUE
  "Courier-BoldOblique", // COURIER_BOLD_OBLIQUE

  // Times-Roman fonts
  "Times-Roman",      // TIMES
  "Times-Bold",       // TIMES_BOLD
  "Times-Italic",     // TIMES_ITALIC
  "Times-BoldItalic", // TIMES_BOLD_ITALIC

  // Helvetica fonts
  "Helvetica",             // HELV
  "Helvetica-Bold",        // HELV_BOLD
  "Helvetica-Oblique",     // HELV_OBLIUQE
  "Helvetica-BoldOblique", // HELV_BOLD_OBLIQUE

  // Symbol font
  "Symbol" // SYMBOL                     
};

// PostScript command strings defined in the postscript prologue
const char *BACKSPACE  = "B";
const char *ENDPAGE    = "EP";
const char *LINETO     = "L";
const char *MOVETO     = "M";
const char *NEWPATH    = "NP";
const char *SHOW       = "S";
const char *STARTPAGE  = "SP";
const char *STROKE     = "ST";
const char *TAB        = "T";

void GetSystemTime(char *s, int full_month_name)
// Stand alone function use to create a custom time string for
// PostScript headers.
{
  time_t STime;
  struct tm *TimeBuffer;
  char month[25]; char day[25]; char year[25];
  char hour[25]; char minutes[25]; char seconds[25];
  time(&STime);
  TimeBuffer = localtime(&STime);
  if(full_month_name)
    strftime(month, 25, "%B", TimeBuffer);
  else
    strftime(month, 25, "%b", TimeBuffer);
  strftime(day, 25, "%d", TimeBuffer);
  strftime(year, 25, "%Y", TimeBuffer);
  strftime(hour, 25, "%H", TimeBuffer);
  strftime(minutes, 25, "%M", TimeBuffer);
  strftime(seconds, 25, "%S", TimeBuffer);

  // Weekday Name Month Day, Year HH:MM:SS
  sprintf(s, "%s %s, %s %s:%s:%s", month, day, year, hour, minutes, seconds);
}

PostScriptDrv::PostScriptDrv()
{
  ncopies = 1;          // Print at least one copy of this document
  landscape = 0;        // Use portrait mode by default
  use_header = 0;       // Do not use headers and trailers by default
  use_lr_margin = 0;    // Do not use left and right margins by default  
  use_tb_margin = 0;    // Do not use top and bottom margins by default
  document_name = 0;    // Name of this document
  date_string = 0;      // Time and date this document was printed
  draw_header_line = 0; // Do not draw line between header and document 

  tabstop = DEFAULT_TAB_SIZE;
  page_count = 0;
  SetFont(COURIER, 10);
  DocumentSetup();
  row = start_y;
  line_count = lines_per_page;
  SetHeaderFont(COURIER_BOLD, 15);
}

PostScriptDrv::PostScriptDrv(const PostScriptDrv &ob)
{
  ncopies = ob.ncopies;        
  landscape = ob.landscape;      
  lines_per_page = ob.lines_per_page;
  columns = ob.columns;
  tabstop = ob.tabstop;
  row = ob.row;
  start_x = ob.start_x;
  start_y = ob.start_y;
  page_count = ob.page_count;
  font_size = ob.font_size;
  page_width = ob.page_width; 
  page_height = ob.page_height;
  chars_per_inch = ob.chars_per_inch;
  char_width = ob.char_width;
  text_font = ob.text_font;
  use_lr_margin = ob.use_lr_margin;
  use_tb_margin = ob.use_tb_margin;
  use_header = ob.use_header;
  header_font = ob.header_font;
  header_font_size = ob.header_font_size;
  document_name = ob.document_name;
  date_string = ob.date_string;
  draw_header_line = ob.draw_header_line;
  line_count = ob.line_count;
}
  
PostScriptDrv &PostScriptDrv::operator=(const PostScriptDrv &ob)
{
  ncopies = ob.ncopies;        
  landscape = ob.landscape;      
  lines_per_page = ob.lines_per_page;
  columns = ob.columns;
  tabstop = ob.tabstop;
  row = ob.row;
  start_x = ob.start_x;
  start_y = ob.start_y;
  page_count = ob.page_count;
  font_size = ob.font_size;
  page_width = ob.page_width; 
  page_height = ob.page_height;
  text_font = ob.text_font;
  chars_per_inch = ob.chars_per_inch;
  char_width = ob.char_width;
  use_lr_margin = ob.use_lr_margin;
  use_tb_margin = ob.use_tb_margin;
  use_header = ob.use_header;
  header_font = ob.header_font;
  header_font_size = ob.header_font_size;
  document_name = ob.document_name;
  date_string = ob.date_string;
  draw_header_line = ob.draw_header_line;
  line_count = ob.line_count;
  
  return *this;
}

void PostScriptDrv::SetFont(PSFonts font, double size)
{
  switch(font){
    case COURIER:              
      font_size = size;
      char_width = 0.6 * font_size; // Width of each character
      break;
      
    case COURIER_BOLD:         
      font_size = size;
      char_width = 0.6 * font_size; // Width of each character
      break;
      
    case COURIER_OBLIQUE:      
      font_size = size;
      char_width = 0.6 * font_size; // Width of each character
      break;
      
    case COURIER_BOLD_OBLIQUE: 
      font_size = size;
      char_width = 0.6 * font_size; // Width of each character
      break;
      
    case TIMES:             
      font_size = size;
      char_width = 0.57 * font_size; // Width of each character
      break;
      
    case TIMES_BOLD:        
      font_size = size;
      char_width = 0.6 * font_size; // Width of each character
      break;
      
    case TIMES_ITALIC:      
      font_size = size;
      char_width = 0.55 * font_size; // Width of each character
      break;
      
    case TIMES_BOLD_ITALIC: 
      font_size = size;
      char_width = 0.58 * font_size; // Width of each character
      break;
      
    case HELV:             
      font_size = size;
      char_width = 0.6 * font_size; // Width of each character
      break;
      
    case HELV_BOLD:        
      font_size = size;
      char_width = 0.6 * font_size; // Width of each character
      break;
      
    case HELV_OBLIQUE:     
      font_size = size;
      char_width = 0.6 * font_size; // Width of each character
      break;
      
    case HELV_BOLD_OBLIQUE:
      font_size = size;
      char_width = 0.6 * font_size; // Width of each character
      break;
      
    case SYMBOL:
      font_size = size;
      char_width = 0.57 * font_size; // Width of each character
      break;

    default: // COURIER              
      font_size = DEFAULT_POINT_SIZE;
      char_width = 0.6 * font_size; // Width of each character
      break;
  }

  // Set font name
  text_font = (char *)PostScriptFonts[font];

  // Calculate the number of characters per inch
  chars_per_inch = PIXELS_PER_INCH / char_width; 
}

void PostScriptDrv::SetHeaderFont(PSFonts font, double size)
{
  header_font_size = size;
  header_font = (char *)PostScriptFonts[font];
}

void PostScriptDrv::SetPaperSize(PSPaperSizes size)
// Calculates the x and y positions for landscape and portrait modes
// for different paper sizes. There are 72 points per inch, with the
// origin (0,0) in the lower left corner of the page. By default,
// distances are specified in PostScript points (1/72 of an inch). 
 
{
  switch(size) {
    case LETTER_SIZE: // 8.5 X 11 inches 
      page_width = int(8.5 * PIXELS_PER_INCH); // 612 points per inch 
      page_height = int(11 * PIXELS_PER_INCH); // 729 points per inch 
      break;

    case LEGAL_SIZE: // 8.5 x 14 inches 
      page_width = int(8.5 * PIXELS_PER_INCH); // 612 points per inch 
      page_height = int(14 * PIXELS_PER_INCH); // 1008 points per inch 
      break;
      
    case TABLOID_SIZE: // 11 x 17 inches  
      page_width = int(11 * PIXELS_PER_INCH);  // 792 points per inch 
      page_height = int(17 * PIXELS_PER_INCH); // 1224 points per inch 
      break;

    case A3_SIZE: // 297mm x 420mm (11.70" X 16.55")
      page_width = int(11.70 * PIXELS_PER_INCH);  // 842.4 points per inch 
      page_height = int(16.55 * PIXELS_PER_INCH); // 1191.6 points per inch 
      break;

    case A4_SIZE: // 210mm x 297mm (8.27" X 11.70") 
      page_width = int(8.27 * PIXELS_PER_INCH);   // 595.44 points per inch 
      page_height = int(11.70 * PIXELS_PER_INCH); // 842.4 points per inch 
      break;
      
    default: // Default value is letter size: 8.5 X 11 inches 
      page_width = int(8.5 * PIXELS_PER_INCH); // 612 points per inch 
      page_height = int(11 * PIXELS_PER_INCH); // 729 points per inch 
      break;
  }
}

void PostScriptDrv::DocumentSetup(double LR_margin, double TB_margin, int man)
// Calculates the x and y positions, lines per page and columns for
// landscape and portrait modes. The left/right and top/bottom margin
// offsets will not be used if left/right or top/bottom margins are
// not enabled. If man if true the line per page will be set to max
// lines per page for UNIX man pages produced by nroff. nroff is used
// to format man pages under most variants of UNIX.
{
  start_x = (int)PRINTABLE_OFFSET_X;
  
   // If using page headers set the Top, Bottom, Left and Right margins
  if(use_header) {
    if(landscape && use_lr_margin == 0)
      use_tb_margin = 1;
    else
      use_lr_margin = use_tb_margin = 1;
    
    LR_margin = TB_margin = HEADER_OFFSET;
  }
  
  if(landscape) {
    start_y = int(page_width - (PRINTABLE_OFFSET_Y + font_size));
    
    // Account for the printable areas at the top and bottom of the page
    lines_per_page = (int)ceil((start_y - PRINTABLE_OFFSET_Y) / font_size);

    if(man == 1 && lines_per_page > UNIX_MAN_PAGE_LINES) 
      lines_per_page = UNIX_MAN_PAGE_LINES;
    
    if(use_lr_margin) { // Calculate the left and right margins offsets
      double right_margin = LR_margin * 2;
      double new_page_width = page_height - (right_margin * PIXELS_PER_INCH);
      columns = (int)ceil(new_page_width / char_width);
      start_x = int(LR_margin * PIXELS_PER_INCH);
    }
    else {
      double width = page_height - (PRINTABLE_OFFSET_Y * 2);
      columns = (int)ceil((width / char_width) - 1);
    }

    if(use_tb_margin) { // Calculate the top and bottom margins
      int nlines = (int)ceil((TB_margin * PIXELS_PER_INCH) / font_size);
      start_y = int(page_width - (nlines * font_size));
      double bottom_margin = page_width - ((TB_margin *2 ) * PIXELS_PER_INCH);
      lines_per_page = (int)ceil(bottom_margin / font_size);
      if(man == 1 && lines_per_page > UNIX_MAN_PAGE_LINES)
	lines_per_page = UNIX_MAN_PAGE_LINES;
    }
  }
  else { 
    start_y = int(page_height - (PRINTABLE_OFFSET_Y + font_size));
    lines_per_page = (int)ceil(start_y / font_size) - 1;

    if(man == 1 && lines_per_page > UNIX_MAN_PAGE_LINES)
      lines_per_page = UNIX_MAN_PAGE_LINES;

    if(use_lr_margin) { // Calculate the left and right margins offsets
      double right_margin = LR_margin * 2;
      double new_page_width = page_width - (right_margin * PIXELS_PER_INCH);
      columns = (int)ceil(new_page_width / char_width);
      start_x = int(LR_margin * PIXELS_PER_INCH);
    }
    else {
      double width = page_width - (PRINTABLE_OFFSET_X * 2);
      columns = (int)ceil((width / char_width) - 1);
    }
    
    if(use_tb_margin) { // Calculate the top and bottom margins
      int nlines = (int)ceil((TB_margin * PIXELS_PER_INCH) / font_size);
      start_y = int(page_height - (nlines * font_size));
      double bottom_margin = page_height - ((TB_margin * 2) * PIXELS_PER_INCH);
      lines_per_page = (int)ceil(bottom_margin / font_size);
      if(man == 1 && lines_per_page > UNIX_MAN_PAGE_LINES)
	lines_per_page = UNIX_MAN_PAGE_LINES;
    }
  }
}

void PostScriptDrv::Prologue(ostream &stream, char *pn, char *hn,
			     char *cd, char *un)
// Generates the PostScript header and includes the prologue.
// Arguments are: pn - Program Name, hn - Hostname,
// cd - Creation Date, un - Username.
{
  stream << "%!PS-Adobe-2.0" << endl;
  if(pn) {
    stream << "%%Produced By: " <<  pn;
    if(hn) stream << " on " << hn << endl; else  stream << endl;
  }

  if(cd) stream << "%%Creation Date: " << cd << endl;
  if(un) stream << "%%For: " << un << endl;
  if(text_font) stream << "%%DocumentFont: " << text_font << endl;
  stream << "%%Pages: (atend)" << endl;
  stream << "%%EndComments" << endl;

  stream << "% PostScript Prologue for ASCII to PostScript converter" << endl;
  stream << "%%BeginProlog" << endl;
  
  // PostScript command strings 
  stream << "/B {NW 0 rmoveto}bind def" << endl;
  stream << "/EP {SV restore /#copies exch def showpage}bind def" << endl;
  stream << "/L /lineto load def" << endl;
  stream << "/M /moveto load def" << endl;
  stream << "/NP /newpath load def" << endl;
  stream << "/S /show load def" << endl;
  stream << "/SP {/SV save def findfont exch scalefont setfont ( )" << endl;
  stream << "  stringwidth pop dup /W exch def neg /NW exch def}bind def"
	 << endl;
  stream << "/ST /stroke load def" << endl;
  stream << "/T {W mul 0 rmoveto}bind def" << endl;

  // PostScript procedures 
  stream << "/drawLine { " << endl;
  stream << "gsave " << LINE_WIDTH
	 << " setlinewidth 0 setgray moveto 0 rlineto stroke grestore"
	 << endl;
  stream << "} bind def" << endl;
  
  stream << "/drawThickLine { " << endl;
  stream << "gsave " << THICK_LINE_WIDTH
	 << " setlinewidth 0 setgray moveto 0 rlineto stroke grestore" 
	 << endl;
  stream << "} bind def" << endl;

  stream << "%%EndProlog" << endl;
}

void PostScriptDrv::drawLine(ostream &stream, int points,
				       int xpos, int ypos)
{
  stream << points << " " << xpos << " " << ypos << " drawLine"
	 << endl;
}

void PostScriptDrv::drawThickLine(ostream &stream, int points,
				  int xpos, int ypos)
{
  stream << points << " " << xpos << " " << ypos << " drawThickLine"
	 << endl;
}

int PostScriptDrv::ProcessText(char *in, ostream &stream, int cut, int filter)
// Process a line of text before printing it. If cut is true the line
// will be truncated to match the number of columns for this line. If
// filter is true each line will be filtered for escape seqences and
// non-printable characters before the line is truncated. Retuns true 
// if successful.
{
  int len = strlen(in);

  // Truncate the line if it exceeds the maximum number of columns
  if(len >= columns && filter == 1) { 
    int word_count = 0;

    // Filter escape seqences and non-printable chars before truncating line
    for(int pos = 0; pos < len; pos++) { 
      if(in[pos] == ' ') word_count = 0; else word_count++;

      // Filter bold sequences produced by nroff
      // nroff is used to format man pages under most variants of UNIX
      char c = in[pos++];
      if(c == in[pos+1] && in[pos] == in[pos+2] &&
	 c == in[pos+3] &&  in[pos] == in[pos+4] &&
	 c == in[pos+5]) {
	int bold_offset = (word_count * 7) - word_count;
	len -= bold_offset; 
	word_count = 0;
      }
      
      // Filter underline sequences produced by nroff 
      // nroff is used to format man pages under most variants of UNIX
      if(in[pos] == '\b') len--;
    } 
  }

  // Truncate if necessary after filtering line 
  if(len >= columns && cut == 1) { 
    char *buf = new char[columns];
    buf[columns] = '\0';
    memmove(buf, in, columns);
    stream << start_x << " "  << row << " " << MOVETO << endl;
    int rv = PrintLine(buf, stream);
    delete [] buf;
    return rv;
  }
  else {
    stream << start_x << " "  << row << " " << MOVETO << endl;
    return PrintLine(in, stream);
  }
}

int PostScriptDrv::PrintLine(char *in, ostream &stream)
// Print a line of PostScript text, escaping characters when
// necessary and handling tabs.
{
  char bufout[BUFOUT];
  char *p, *q, *savep;
  int currentp, instr, tabc, tabto, i, colskip, ncols;
  int seen_sep = 0;
  
  currentp = instr = tabto = 0;
  char *last = bufout + MAX_LINE - 20; // Subtract error factor 

  q = bufout;
  *q = '\0';
  for(p = in; *p != '\0'; p++) {
    switch (*p) {
      case SEP_CHAR: // Column separator character 
	// This assumes that the input buffer contains the entire line,
	// otherwise the column count will be offset.
	if (!seen_sep) { // Discern number of columns 
	  seen_sep = 1;
	  ncols = 2; // There are at least two columns
	  savep = p++;
	  while (*p != '\0') {
	    if (*p++ == SEP_CHAR)
	      ncols++;
	  }
	  p = savep;
	  colskip = columns / ncols;
	}
	if(instr) {
	  sprintf(q, ")%s ", SHOW);
	  q += strlen(q);
	  instr = 0;
	}
	tabto += (colskip - currentp);
	currentp = 0;
	break;

      case '\t': // Tab
	// Count the number of tabs that immediately follow this one
	tabc = 0;
	while (*(p + 1) == '\t') {
	  p++;
	  tabc++;
	}
	if (currentp > 0) { // Not beginning of line 
	  i = tabstop - (currentp % tabstop) + tabc * tabstop;
	  if (instr) {
	    (void) sprintf(q, ")%s ", SHOW);
	    q += strlen(q);
	    instr = 0;
	  }
	}
	else
	  i = (tabc + 1) * tabstop;
	tabto += i;
	currentp += i;
	break;

      case '\b': // Back Space
	*q = '\0';
	stream << bufout << ")" << SHOW << endl;
	// backspacing over tabs doesn't work
	if (tabto != 0) return 0; // Attempt to backspace over a tab
	p++;
	for (i = 1; *p == '\b'; p++)
	  i++;
	if (currentp - i < 0) return 0; // Too many backspaces
	if (!instr) return 0; // Bad Back Spacing

	// Handle consecutive backspace seqences produced by nroff 
	// nroff is used to format man pages under most variants of UNIX
	if (i == 1)  
	  sprintf(bufout, "%s (", BACKSPACE);
	else
	  sprintf(bufout, "-%d %s (", i, TAB);
	currentp -= i;
	q = bufout + strlen(bufout);
	p--;
	break;

      case '\f': // Form Feed
	tabto = 0; // Optimizes 
	*q = '\0';
	if(instr)
	  stream << bufout << ")" << SHOW << endl;
	else
	  stream << bufout << endl;
	EndPage(stream);
	StartPage(page_count + 1, stream);
	row = start_y;
	stream << start_x << " " << row << " " << MOVETO << endl;
	q = bufout;
	currentp = 0;
	instr = 0;
	break;

      case '\r': // Hard Return
	tabto = 0; // optimizes 
	if (instr) {
	  *q = '\0';
	  stream << bufout << ")" << SHOW << endl;
	  instr = 0;
	  q = bufout;
	}
	stream << start_x << " " << row << " " << MOVETO << endl;
	currentp = 0;
	break;

      case '\\': case '(': case ')':
	if (!instr) {
	  if (tabto) {
	    sprintf(q, "%d %s ", tabto, TAB);
	    q += strlen(q);
	    tabto = 0;
	  }
	  *q++ = '(';
	  instr = 1;
	}
	*q++ = '\\';
	*q++ = *p;
	currentp++;
	break;

      default:
	// PostScript files can contain only the printable subset
	// of the ASCII character set (plus the newline marker).
	if (!isascii(*p) || !isprint(*p)) return 0; // Bad character in input
	if (!instr) {
	  if (tabto) {
	    sprintf(q, "%d %s ", tabto, TAB);
	    q += strlen(q);
	    tabto = 0;
	  }
	  *q++ = '(';
	  instr = 1;
	}
	*q++ = *p;
	currentp++;
	break;
    }

    if (q >= last) {
      *q = '\0';
      if (instr)
	stream << bufout << ")" << SHOW << endl;
      else
	stream << bufout << endl;
      q = bufout;
      instr = 0;
    }
  }

  if (instr) {
    sprintf(q, ")%s", SHOW);
    q += strlen(q);
  }
  else
    *q = '\0';

  if(q >= last) return 0; // Output buffer overflow
  if(bufout[0] != '\0') stream << bufout << endl;
  return 1;
}

void PostScriptDrv::EndPage(ostream &stream)
// Terminate the page and indicate the start of the next
{
  if (page_count == MAXPAGES) return; // pagelimit reached;
  stream << ncopies << " " << ENDPAGE << endl;
  page_count++;
}

void PostScriptDrv::ChangeFont(ostream &stream, PSFonts font, double size)
// Change the font before printing
{
  stream << size << " /" <<  PostScriptFonts[font] << " ";
  stream << "findfont " << size << " scalefont setfont" << endl; 
}

void PostScriptDrv::StartPage(int n, ostream &stream)
// Start a new page
{
  stream << "%%Page: ? " << n << endl; 

  if(use_header) // Set the header font
    stream << header_font_size << " /" << header_font << " ";
  else
    stream << font_size << " /" << text_font << " ";
  
  if(landscape) {
    stream << STARTPAGE << " " << page_width << " 0 translate " << endl;
    stream << "90 rotate" << endl;
  }
  else
    stream << STARTPAGE << endl;

  if(use_header) { // Print the header info and reset the font
    int hpoints = int(HEADER_OFFSET * PIXELS_PER_INCH);
    int header_char_width = int(header_font_size * .6);
    int line_width, width, len, line_points, ypos, xpos = start_x; 
    int hoffset = hpoints / 2; // Offsets the header from text
    
    if(landscape) {
      // Leave some room for three hole punch margin at top of page
      ypos = page_width - (hpoints - 20);

      if(use_lr_margin == 0)
	line_points = int(page_height - (PRINTABLE_OFFSET_X * 2));
      else
	line_points = page_height - (hpoints * 2);

      width = page_height;
    }
    else {
      ypos = page_height - hoffset;
      line_points = page_width - (hpoints * 2);
      width = page_width;
    }

    if(draw_header_line) { // Draw the top and bottom lines
      drawThickLine(stream, line_points, xpos, ypos);
      drawThickLine(stream, line_points, xpos, hoffset);
      line_width = THICK_LINE_WIDTH * 2; // Offset the line from header text
    }
    else
      line_width = 0;
    
    ypos += line_width;
    if(date_string != 0 && document_name != 0) {
      // Left justify the document_name
      stream << xpos << " " << ypos << " " << MOVETO << endl;
      stream << "(" << document_name << ")" << " " << SHOW << endl;

      // Right justify the date string
      len = strlen(date_string) * header_char_width;
      if(use_lr_margin == 0)
	xpos = int((width - PRINTABLE_OFFSET_X) - len);
      else
	xpos = (line_points + hpoints) - len;
      stream << xpos << " " << ypos << " " << MOVETO << endl;
      stream << "(" << date_string << ")" << " " << SHOW << endl;
    }
    else { // Center the doucuments name
      if(document_name == 0) document_name = "PostScript Document";
      len = strlen(document_name) * header_char_width;
      xpos = (width / 2) - (len / 2);
      stream << xpos << " " << ypos << " " << MOVETO << endl;
      stream << "(" << document_name << ")" << " " << SHOW << endl;
      xpos = start_x;
    }

    // Draw the page numbers
    char page_number_string[255];
    sprintf(page_number_string, "PAGE %d", n);
    len = strlen(page_number_string) * header_char_width;
    xpos = (width / 2) - (len / 2);
    ypos = int(hoffset - (header_font_size + line_width));
    stream << xpos << " " << ypos << " " << MOVETO << endl;
    stream << "(" << page_number_string << ")" << " " << SHOW << endl;
    
    // Reset the text font
    stream << font_size << " /" << text_font << " " << STARTPAGE << endl;
  }
}

int PostScriptDrv::ConvertTextFile(ifstream &infile, ostream &stream,
				   int wrap, int cut)
// Convert the ASCII document to postcript. Returns 1 if successful.
// If wrap is true the line will wrap around the page if the number
// of columns are exceeded. If cut is true the line will be truncated
// to match the number of columns.
{
  if(!infile) return 0; 
  char bufin[BUFIN];

  // Read in text file line by line until EOF
  while(!infile.eof()) {
    StartPage(page_count + 1, stream);
    row = start_y;
    line_count = lines_per_page;
    // Process the next page 
    for (int lineno = 0; lineno < line_count; lineno++) {
      infile.getline(bufin, MAX_LINE);
      if (bufin[0] == '\f') break;
      if (bufin[0] != '\0') {
	int len = strlen(bufin);
	
	if(wrap == 1 && len >= columns) { // Wrap text lines 
	  int i = 0, j = 0, x = 0;
	  char *temp_buf = new char[columns];
	  temp_buf[columns] = '\0';
    
	  while(len >= columns) { // Loop until length is less then columns
	    len = len - columns;
	    for(j = 0; j < columns; j++, i++) temp_buf[j] = bufin[i];
	    stream << start_x << " "  << row << " " << MOVETO << endl;
	    if(PrintLine(temp_buf, stream) == 0) return 0;
	    for(x = 0; x < columns; x++) temp_buf[x] = '\0';
	    row -= (int)font_size; // Update the y position after wrap

	    if(row < font_size) { // Start a new page
	      EndPage(stream);
	      StartPage(page_count + 1, stream);
	      row = start_y;
	      line_count = lines_per_page;
	      lineno = 0;
	    }
	    line_count--;
	    if(len < columns) break;
	  }

	  if(len > 0) { // Print the remaining characters
	    for(j = 0; j < len; j++, i++) temp_buf[j] = bufin[i];
	    stream << start_x << " "  << row << " " << MOVETO << endl;
	    if(PrintLine(temp_buf, stream) == 0) return 0;
	  }
	}
	else 
	  if(ProcessText(bufin, stream, cut) == 0) return 0;
	
	for(int i = 0; i < BUFIN; i++) bufin[i] = 0;  // Clear the buffer
      }
      row -= (int)font_size;
    }
    EndPage(stream);
  } 
  return 1;
}

void PostScriptDrv::Epilogue(ostream &stream, int page_count)
{
  stream << "%%Trailer" << endl;
  stream << "%%Pages: " << page_count << endl;
}


void PostScriptDrv::MediaSetup(ostream &stream, int duplex, int manualfeed,
			       int use_tray, int tray_number)
// Sets the physical sources and physical destinations for the media
// source and destination to be used in the printer. These settings
// are device dependent. 
{
  stream << "%%BeginSetup" << endl;

  if(duplex) {
    // Enable duplex printing if a duplex device is attached to the
    // printer. Each pair of consecutive pages will be printed on
    // opposite sides of a single sheet of paper.
    stream << "%%BeginFeature: *Duplex" << endl;
    stream << "<</Duplex true>>setpagedevice" << endl;
    stream << "%%EndFeature" << endl;
  }

  if(manualfeed) {
    // Feed paper from the manual feed position.
    stream << "%%BeginFeature: *ManualFeed" << endl;
    stream << "<</ManualFeed true>>setpagedevice" << endl;
    stream << "%%EndFeature" << endl;
  }

  if(use_tray) {
    // Select a specific paper tray to print from. The number following
    // "MediaPosition" indicates the actual tray. The correspondence
    // between tray numbers and the actual positions is specific to
    // each printer.

    // Example: The HP LaserJet 5Si/5Si MX printer uses the following
    // numbers to represent the actual paper trays:
    // 0 = Tray 2, 1 = Tray 3, 2 = Envelope Feeder, 3 = Tray 1, 4 = Tray 4
    
    stream << "%%BeginFeature: *MediaPosition" << endl;
    stream << "<</MediaPosition " << tray_number << ">> setpagedevice"
	   << endl;
    stream << "%%EndFeature" << endl;
  }
  
  stream << "%%EndSetup" << endl;
}

void PostScriptDrv::MoveTo(ostream &stream, int x, int y)
{
 stream << x << " "  << y << " " << MOVETO << endl;
}

int PostScriptDrv::StringLen(char *s, int charWidth)
// Convert string lenght to PostScript points
{
  return strlen(s) * charWidth;
}

int PostScriptDrv::StringLen(const char *s, int charWidth)
// Convert string lenght to PostScript points
{
  return strlen(s) * charWidth;
}

int PostScriptDrv::PrintLine(char *s, unsigned max_len, ofstream &stream)
{
  if(s == 0 || max_len <= 0) return 0;
  
  if(strlen(s) > max_len) {
    char *buf = new char[max_len];
    buf[max_len] = '\0';
    memmove(buf, s, max_len);
    int rv = PrintLine(buf, stream);
    delete [] buf;
    return rv;
  }
  else
    return PrintLine(s, stream);
}
// ----------------------------------------------------------- //
// ------------------------------- //
// --------- End of File --------- //
// ------------------------------- //
