/*----------------------------------------------------------------------*
 | File: PF.c (short for PRINTFILES.c) - V1.15 - MLO 910609             |
 +----------------------------------------------------------------------+
 | This file is an utility for the HP DeskJet 500 printer on the Amiga; |
 | has been written for the SAS C-Compiler v5.10,  and structured to be |
 | easily modified for different computers (e.g. IBM PC's). Its purpose |
 | is to send the correct escape sequence  to direct the printer to use |
 | an internal font,  then to print  one or more files;  the printer is |
 | reset to the default,  UNLESS no file names are given:  in this case |
 | the printer is  left initialised as it was.  I have foreseen  also a |
 | special printing mode: 2 pages (55 lines and 80 columns each) on the |
 | same sheet,  in landscape mode  with a small font and a nice border. |
 +----------------------------------------------------------------------+
 | Usage: PF [switches] [file [file [file ... ] ] ] ; from CLI only.    |
 | Switches:    -l : landscape (default is portrait);                   |
 |              -i : italic (default is roman);                         |
 |             -bN : insert N blanks at the beginning of every line;    |
 |             -aN : tAb stops every N characters (default: 8);         |
 |              -g : Letter-Gothic font (default is Courier);           |
 |              -t : Times font (default is Courier);                   |
 |              -s : small pitch (Courier: 16.67 cpi; Let.Got.: 24cpi); |
 |              -x : extra-small pitch (20 cpi - for Courier only);     |
 |              -8 : 8 lpi (default is 6 lpi);                          |
 |              -6 : 6 points high fonts (default is 12 points fonts);  |
 |              -d : draft quality (default is letter quality);         |
 |              -2 : special mode (2 pages every side).                 |
 | The s/x/l switches are ignored when incompatible with selected font; |
 | the "normal" pitch  is 10 cpi for Courier,  and 12 for Letter-Gothic |
 | (Times is a proportional font). The switches can be specified eg. as |
 | -g -8 -s or as -g8s (for Letter-Gothic font, 8 lpi and 24 cpi); only |
 | the -bN/-aN switches must be alone,  or the last ones in a sequence. |
 | All switches different from -d/-a will be ignored if -2 is selected. |
 | All non-printable characters (00 to 037 and 0137) are deleted.       |
 +----------------------------------------------------------------------+
 | Giving PF with invalid switches (or PF without any argument) a short |
 | help screen will be printed on stdout.                               |
 +---------------------------------------------------------+------------*
 | Author:  Maurizio Loreti, aka MLO or I3NOO.             |
 | Address: University of Padova - Department of Physics   |
 |          Via F. Marzolo, 8 - 35131 PADOVA - Italy       |
 | Phone:   (39)(49) 844-313         FAX: (39)(49) 844-245 |
 | E-Mail:  LORETI at IPDINFN (BITNET); or VAXFPD::LORETI  |
 |         (DECnet) - VAXFPD is node 38.257 i.e. 39169; or |
 |          LORETI@PADOVA.INFN.IT (INTERNET).              |
 | Home: Via G. Donizetti 6 - 35010 CADONEGHE (PD) - Italy |
 *---------------------------------------------------------*/

#include "pf1.h"

/**
 | ANSI prototypes
**/

void    CheckDefaults(void);
void    Cleanup(int code);
void    ClearPageBuffer(void);
void    ClearThisPage(void);
void    Detab(char *buffer, int length);
void    DoubleLine(char *left, char *right);
void    EjectPage(void);
void    ExitProgram(void);
void    FlushBuffers(void);
void    Header(int type);
void    InitPrinter(void);
void    InterLine(void);
void    OutLine(void);
void    ResetPrinter(void);
char  **Setup(int *pArgc, char **argv);
void    Syntax(void);

/**
 | Main program: the approach is strictly top-down, and
 | the meaning should be clear also without comments.
**/

main(
  int argc,
  char **argv
){
  int bufferLength = LINE_LENGTH;

  argv = Setup(&argc, argv);
  InitPrinter();
  bufferLength -= nBlanks;

  for (; argc--; argv++) {
    if ((fp = fopen(*argv, "r")) == NULL) {
      printf("Can't open input file %s ...\n", *argv);
      continue;
    }
    printf("Printing file %s ... ", *argv);
    while (fgets(Buffer, bufferLength, fp) != NULL) {
      Detab(Buffer, bufferLength);
      OutLine();
    }
    printf("done.\n");
    fclose(fp);
    fp = NULL;
    ClearThisPage();
  }
  FlushBuffers();
  ExitProgram();
}

void CheckDefaults(void)
{

/**
 | As the name says, this procedure checks for incompatible options
 | selected: e.g. the only Landscape fonts are Courier, so to give
 | the command PF -TL ... is an error. I suppose that the internal
 | fonts only are present; otherwise this procedure should be modified.
 | Checks:
 | -> Letter-Gothic: 12 or 24 cpi; portrait only.
 | -> Times: proportional; portrait only.
 | -> Courier: 10 or 20 cpi; plus 16.67 cpi for Courier Roman (not
 |    allowed for Courier Italic).
 | -> Landscape/Italic not allowed.
 | -> The number of extra spaces inserted before every line must be
 |    not negative.
**/

  switch (Font) {
    case GOTHIC:
      Orientation = PORTRAIT;
      if (Pitch == P10CPI) {
        Pitch = P12CPI;
      } else {
        Pitch = P24CPI;
      }
      break;
    case TIMES:
      Orientation = PORTRAIT;
      Pitch = PROPORTIONAL;
      break;
    case COURIER:
      if (Pitch == P16_67CPI   &&   Style == ITALIC) {
        Pitch = P20CPI;
      }
      break;
  }

  if (Orientation == LANDSCAPE) {
    Style = ROMAN;
  }

  if (nBlanks < 0) {
    nBlanks = 0;
  } else {
    if (nBlanks) {
      Buffer = inBuffer + nBlanks;
      memset(inBuffer, BLANK, nBlanks);
    }
  }
}

void Cleanup(
  int code
){

/**
 | Releases all system resources (closes opened files
 | and frees heap memory - if any). "code" is the
 | status to be returned to the operating system.
**/

  if (fp  != NULL)                fclose(fp);
  if (prt != NULL)                fclose(prt);
  if (pPB != NULL) {
    if (pPB->line[0] != NULL)     free(pPB->line[0]);
    free(pPB);
  }
  exit(code);
}

void ClearPageBuffer(void)
{

/**
 | This routine resets the content of the page buffer
 | used in the 2-pages-on-a-sheet mode to all-blank
 | lines, and the pointer to the next line to be filled
 | to the first line in the buffer.
**/

  memset(pPB->line[0], BLANK, BUFFER_SIZE);
  ThisLine = 0;
}

void ClearThisPage(void)
{

/**
 | Called every end of file: in the normal mode, ejects a page;
 | in the 2-pages mode, switches from the left to the right page
 | (if there is at least a line on this page), and leaving the
 | remainder of the lines on the current page all blank.
**/

  switch (PageMode) {
    case SINGLE_PAGE:
      EjectPage();
      break;
    case LEFT_PAGE:
      if (ThisLine) {
        Header(UP);
        PageMode = RIGHT_PAGE;
        ThisLine = 0;
      }
      break;
    case RIGHT_PAGE:
      if (ThisLine) {
        FlushBuffers();
        PageMode = LEFT_PAGE;
        ClearPageBuffer();
      }
      break;
  }
}

void Detab(
  char *buffer,
  int length
){

/**
 | Translates TAB stops to blanks: TAB stops are assumed at columns
 | (1 + N * nTabs), with N = 1, 2, ... ; the default value of nTabs
 | is 8, and can be changed using the command switches. In the same
 | time non-printable characters are deleted from the input string:
 | for non-printable I mean characters from 00 to 037 and 0177, assuming
 | that characters over 0177 have special meaning different for every
 | computer and that nothing can be said about them.
 | The buffer can hold "length" characters only.
**/

  char temp[LINE_LENGTH];                 /* Internal buffer */
  char *pC1, *pC2;                        /* Temporary pointers */
  char *pEnd;                             /* Pointer to the buffer end */

  strcpy(temp, buffer);
  pEnd = (buffer + length - 1);
  for (pC1 = temp, pC2 = buffer; *pC1 && pC2 < pEnd; pC1++) {
    if (*pC1 == TAB) {
      do {
        *pC2++ = BLANK;
      } while (((pC2 - buffer) % nTabs) && (pC2 < pEnd));
    } else {
      if (isspace(*pC1)  ||  !iscntrl(*pC1)) {
        *pC2++ = *pC1;
      }
    }
  }
  *pC2 = NIHIL;
}

void DoubleLine(
  char *left,
  char *right
){

/**
 | Prints a line in the 2-pages mode, the two parameters being
 | pointers to the left-page line and the right-page line; a
 | blank line is printed if the corresponding pointer is NULL.
**/

  if (left == NULL) {
    fprintf(prt, "%c%*c%c%*c",
        V_LINE, TOTAL_LENGTH, BLANK, V_LINE, SEP_LENGTH, BLANK);
  } else {
    fprintf(prt, "%c%*c%.*s%*c%c%*c",
        V_LINE, SIDE_LENGTH, BLANK, OUTPUT_LENGTH, left,
        SIDE_LENGTH, BLANK, V_LINE, SEP_LENGTH, BLANK);
  }

  if (right == NULL) {
    fprintf(prt, "%c%*c%c\r",
        V_LINE, TOTAL_LENGTH, BLANK, V_LINE);
  } else {
    fprintf(prt, "%c%*c%.*s%*c%c\r",
        V_LINE, SIDE_LENGTH, BLANK, OUTPUT_LENGTH, right,
        SIDE_LENGTH, BLANK, V_LINE);
  }
}

void EjectPage(void)
{
  fprintf(prt, "%c", FORM_FEED);
}

void ExitProgram(void)
{
  ResetPrinter();
  puts("Good Bye!");
  Cleanup(SYS_NORMAL_CODE);
}

void FlushBuffers(void)
{

/**
 | In the 2-pages mode, prints the current page as
 | it is (non filled lines will be left blank).
**/

  int i;

  switch (PageMode) {
    case LEFT_PAGE:
      if (!ThisLine) return;
      for (i=0; i<ThisLine; i++) {
        DoubleLine(pPB->line[i], NULL);
        InterLine();
      }
      for (i=ThisLine; i<PAGE_LENGTH; i++) {
        DoubleLine(NULL, NULL);
        InterLine();
      }
      Header(DOWN);
      break;
    case RIGHT_PAGE:
      for (i=ThisLine; i<PAGE_LENGTH; i++) {
        DoubleLine(pPB->line[i], NULL);
        InterLine();
      }
      Header(DOWN);
      break;
  }
}

void Header(
  int type
){

/**
 | Prints the top or the down header in the 2-pages mode:
 | a solid line separated from the text by a blank line.
**/

  memset(Buffer, H_LINE, TOTAL_LENGTH);

  if (type == UP) {
    fprintf(prt, "%c%.*s%c%*c%c%.*s%c\r",
         NW, TOTAL_LENGTH, Buffer, NE, SEP_LENGTH,
         BLANK, NW, TOTAL_LENGTH, Buffer, NE);
    InterLine();
  }
  DoubleLine(NULL, NULL);
  InterLine();
  if (type == DOWN) {
    fprintf(prt, "%c%.*s%c%*c%c%.*s%c\r",
         SW, TOTAL_LENGTH, Buffer, SE, SEP_LENGTH,
         BLANK, SW, TOTAL_LENGTH, Buffer, SE);
    EjectPage();
  }
}

void InitPrinter(void)
{

/**
 | Printer initialisation: see the HP DeskJet 500
 | user's manual for explanation...
**/

  char *PitchID[] = {"10", "12", "16.67", "20", "24"};

  if ((prt = fopen(OUTPUT_DEVICE, "w")) == NULL) {
    printf("Can't access the output port ...\n");
    Cleanup(SYS_ABORT_CODE);
  }
  fprintf(prt, "%c&l%do%dD%c(10U%c(s0u", ESC, Orientation, Lpi, ESC, ESC);
  if (Pitch == PROPORTIONAL) {
    fprintf(prt, "1p");
  } else {
    fprintf(prt, "0p%sh", PitchID[Pitch]);
  }
  fprintf(prt, "%dv%ds0b%dt%dQ", Height, Style, Font, Quality);
}

void InterLine(void)
{

/**
 | Writing at 8 lines per inch and 6 points high characters,
 | the double page border is not continuous. This procedure
 | skips half line, draws the border, then skips another half
 | line down to the correct placement for next printing.
**/

  fprintf(prt, "%c=", ESC);
  DoubleLine(NULL, NULL);
  fprintf(prt, "%c=", ESC);
}

void OutLine(void)
{

/**
 | Outputs a line to the printer.
 | In the normal mode, the line is sent to the port after storing a
 | carriage return and a line feed after the text, and leaving to the
 | printer to deal with form feeds and long lines.
 | In the 2-pages mode, the line is truncated to a fixed length: if
 | we are in the left page, the line is stored in the internal buffer;
 | otherwise, it is printed together with the corresponding left line.
**/

  int length, n;
  static char *EndOfLine = "\r\n";

  length = strlen(inBuffer) - 1;

  switch (PageMode) {
    case SINGLE_PAGE:
      memcpy(inBuffer+length, EndOfLine, 3);
      fputs(inBuffer, prt);
      break;
    case LEFT_PAGE:
      if (length > OUTPUT_LENGTH) {
        length = OUTPUT_LENGTH;
      }
      memcpy(pPB->line[ThisLine], Buffer, length);
      if (++ThisLine >= PAGE_LENGTH) {
        Header(UP);
        PageMode = RIGHT_PAGE;
        ThisLine = 0;
      }
      break;
    case RIGHT_PAGE:
      if ((n = OUTPUT_LENGTH - length) > 0) {
        memset(Buffer+length, BLANK, n);
      }
      DoubleLine(pPB->line[ThisLine], Buffer);
      InterLine();
      if (++ThisLine >= PAGE_LENGTH) {
        Header(DOWN);
        PageMode = LEFT_PAGE;
        ClearPageBuffer();
      }
      break;
  }
}

void ResetPrinter(void)
{
  fprintf(prt, "%cE", ESC);
}

char **Setup(
  int *pArgc,
  char **argv
){

/**
 | We look into the command line for switches; the function value is
 | argv when pointing to the first non-switch argument (i.e. the first
 | file name) - if any. If the 2-page mode was requested, the procedure
 | allocates from the heap memory the internal buffer for the left-page
 | lines, and a service buffer filled with the pointers to the first
 | character of every line in the left page; this calling malloc(),
 | to make this program more portable.
**/

  int i;
  char c;

  printf("\n\t\"PF\" - v%.2f - MLO %ld\n\n", VERSION, LAST_CHANGE);

/**
 | Error if called from the Workbench, or if called
 | from the CLI but without any argument.
**/

  if (*pArgc == 0) {
    printf("This program must be called from CLI;\n"
           "type PF for a short help screen.\n\n"
           "Press <CR> to exit ... ");
    while ((i = getchar()) != NEWLINE && i != EOF)  { }
    Cleanup(SYS_ABORT_CODE);
  }
  if (*pArgc < 2) Syntax();

  while (--(*pArgc)) {
    if ((*++argv)[0] == '-') {

/**
 | A switch ...
**/

      for (i=1; (c = (*argv)[i]); i++) {
        switch (c) {
          case 'l': case 'L':
            Orientation = LANDSCAPE;
            break;
          case 'i': case 'I':
            Style = ITALIC;
            break;
          case 'b': case 'B':
            nBlanks = atoi(*argv + ++i);
            goto NextSwitch;
          case 'a': case 'A':
            nTabs = atoi(*argv + ++i);
            goto NextSwitch;
          case 'g': case 'G':
            Font = GOTHIC;
            break;
          case 't': case 'T':
            Font = TIMES;
            break;
          case 's': case 'S':
            Pitch = P16_67CPI;
            break;
          case 'x': case 'X':
            Pitch = P20CPI;
            break;
          case '6':
            Height = 6;
            break;
          case '8':
            Lpi = 8;
            break;
          case 'd': case 'D':
            Quality = DRAFT;
            break;
          case '2':
            PageMode = LEFT_PAGE;
            break;
          default:
            Syntax();
        }
      }
    } else {

/**
 | The first file name; perform some
 | intialisations, then return to the caller.
**/

      if (PageMode != SINGLE_PAGE) {
        if ((pPB = malloc(sizeof(PageBuffer))) == NULL) {
          printf("Can't allocate the Page Buffer ...\n");
          Cleanup(SYS_ABORT_CODE);
        }
        if ((pPB->line[0] = malloc(BUFFER_SIZE)) == NULL) {
          printf("Can't allocate the Line Buffer ...\n");
          Cleanup(SYS_ABORT_CODE);
        }
        for (i=1; i<PAGE_LENGTH; i++) {
          pPB->line[i] = pPB->line[0] + (i * OUTPUT_LENGTH);
        }

        Orientation = LANDSCAPE;
        Font = COURIER;
        Style = ROMAN;
        Pitch = P16_67CPI;
        Height = 6;
        Lpi = 8;
        nBlanks = 0;
        ClearPageBuffer();
      } else {
        CheckDefaults();
      }
      return argv;
    }

NextSwitch: ;
  }

/**
 | Here if no file name given; initialise
 | the printer, then exit without reset.
**/

  CheckDefaults();
  InitPrinter();
  puts("Printer initialised ... Good bye!");
  Cleanup(SYS_NORMAL_CODE);
}

void Syntax(void)
{

/**
 | A syntax error has been detected in the command
 | line; a short help is output to the screen.
**/

  puts("Usage:    PF   [switches]   [file [file [file ... ] ] ]");
  puts("Switches: -l : Landscape (default is Portrait);");
  puts("          -i : Italic (default is Roman);");
  puts("         -bN : insert N Blanks before every output line;");
  puts("         -aN : tAb stops every N characters (default: 8);");
  puts("          -g : Letter-Gothic font (default is Courier);");
  puts("          -t : Times font (default is Courier);");
  puts("          -s : Small pitch (Courier: 16.67 cpi; "
       "Letter-Gothic: 24 cpi);");
  puts("          -x : eXtra-small pitch (20 cpi - for Courier only);");
  puts("          -8 : 8 lines per inch (default: 6 lpi);");
  puts("          -6 : 6 points high font (default: 12 points);");
  puts("          -d : Draft quality (default is Letter quality);");
  puts("          -2 : special mode (2 pages on every sheet of paper).\n");
  puts("The s/x/l switches are ignored when incompatible with selected font.");
  puts("The default pitch is 10 cpi for Courier and 12 cpi for Letter-Gothic");
  puts("(Times is a proportional font);  all switches different  from -d and");
  puts("-a will be ignored,  if -2 is selected.  The switches on the command");
  puts("line can be grouped: e.g. -c -8 -s is the same as -c8s; only -bN and");
  puts("-aN, if present, must be specified alone or the last in a group. The");
  puts("printer is reset to its default,  at the completion  -  UNLESS if no");
  puts("file names are given.\n");

  Cleanup(SYS_NORMAL_CODE);
}
