module  (output);
  (*------------------------------------------------------------------*)
  (*								      *)
  (*  Written Apr 26,'82   by Jack Gilmer			      *)
  (*								      *)
  (*  This file of absolute graphic routines implements the standard  *)
  (* graphics package for the HP 7225A plotter. All positions are in  *)
  (* mm. on a 250 by 180 mm. page.  All angles are in degrees up from *)
  (* the x-axis.						      *)
  (*								      *)
  (*------------------------------------------------------------------*)


    xfactor = 40.0;	(* # of steps/mm on the HP 7225A *)
    yfactor = 40.0;

    nulcode = 0;	(* ASCII NUL *)
    etxcode = 3;	(* ASCII ETX *)
    enqcode = 5;	(* ASCII ENQ *)
    ackcode = 6;	(* ASCII ACK *)
    esccode = 27;	(* ASCII ESC *)

    maxsent = 136;	(* # of words sent before handshake *)

    minx = 0;		(* screen co-ords in mm. *)
    maxx = 250;
    miny = 0;
    maxy = 180;

    percent = 0.32461;	(* conversion factor from mm. to % of *)
    			(* diagonal of screen co-ordinates *)

    radian = 57.2958;	(* degrees/radian *)

    maxhpstr = 10;	(* short strings for commands, etc. *)

    absp1x = 328;	(* position of lower left corner *)
    absp1y = 279;	(* in abs HP plotter units for window *)


  TYPE
    astringtype = PACKED ARRAY [1..132] OF CHAR;    (* null-terminated *)

    aendtype = (noend, point, square, triangle, cross, ecks, diamond,
                circle);

    alinetype = (noline, solid, dotted, dashed, dotdashed, 
                 dotdotdashed);

    apointtype =  RECORD	(* absolute position in millimeters *)
                    x,
		    y:  REAL;
		  END;

    cmdstrtype = PACKED ARRAY [1..2] OF CHAR;
    hpstrtype = PACKED ARRAY [1..maxhpstr] OF CHAR;

    unittype = (conunit, prunit, admunit, hpunit);


  VAR
    oldend:  aendtype;		(* last end symbol defined *)

    oldline:  alinetype;	(* last set line type *)

    oldpt, 			(* last point plotted *)
    oldleft,			(* existing window corners in mm.*)
    oldright:  apointtype;

    oldpenlifted,		(* last sent pen up/down instruction *)
    penlifted:  BOOLEAN;	(* last pen position *)

    numsent:  INTEGER;		(* co-ord counter for handshake *)

    oldesize,			(* last set end symbol radius *)
    oldrptlength:  REAL;	(* last set line pattern length *)



  FUNCTION serin(unit:  unittype):  CHAR;  EXTERNAL;

  PROCEDURE serout(unit:  unittype;  ch:  CHAR);  EXTERNAL;

  PROCEDURE exit(success:  BOOLEAN);  EXTERNAL;


  FUNCTION gethp:  CHAR;
  (*------------------------------------------------------------------*)
  (*  Gets a character from the HP port using 'serin'.		      *)
  (*------------------------------------------------------------------*)

    BEGIN (* gethp *)
      gethp := serin(hpunit);
    END; (* gethp *)



  PROCEDURE puthp(outchar:  CHAR);
  (*------------------------------------------------------------------*)
  (*  Puts out 'outchar' the port to the HP plotter.  If the	      *)
  (* system call fails, the program aborts with an error message.     *)
  (*------------------------------------------------------------------*)

    BEGIN (* puthp *)
      serout(hpunit, outchar);
    END; (* puthp *)



  PROCEDURE handshake(roomfor:  INTEGER);
  (*------------------------------------------------------------------*)
  (*  Makes sure there is room for 'roomfor' bytes in the HP 7225A    *)
  (* buffer.  Performs the handshake sequence only when necessary.    *)
  (*------------------------------------------------------------------*)

    BEGIN (* handshake *)
      numsent := numsent + roomfor;
      IF numsent > maxsent
	THEN
	  BEGIN
	    numsent := 0;
	    puthp(chr(enqcode));
	    WHILE gethp <> chr(ackcode) DO;
	  END; (* IF numsent ... *)
    END; (* handshake *)



  PROCEDURE puthpstr(str: hpstrtype);
  (*------------------------------------------------------------------*)
  (*  Puts out 'str' using 'puthp' up to but not including the first  *)
  (* blank.							      *)
  (*------------------------------------------------------------------*)

    VAR
      index: 0..maxhpstr;

    BEGIN (* puthpstr *)
      handshake(maxhpstr);
      index := 0;
      REPEAT
	index := index + 1;
	IF str[index] <> ' '
	  THEN
	    puthp(str[index]);
      UNTIL (index = maxhpstr) OR (str[index] = ' ');
    END; (* puthpstr *)



  PROCEDURE putinum(i:  INTEGER);
  (*------------------------------------------------------------------*)
  (*  Sends the integer 'i' to the HP plotter in the form: iiiii or   *)
  (* -iiiii.  Leading zeroes are suppressed.			      *)
  (*------------------------------------------------------------------*)

    VAR
      digit,			(* single digit *)
      factor:  INTEGER;		(* digit selector *)


    BEGIN (* putinum *)
      IF i < 0		(* send '-' if nec. *)
	THEN
	  BEGIN
	    puthp('-');
	    i := 0 - i;
	  END; (* IF i < 0 *)
      factor := 10000;		(* find 1st digit *)
      WHILE ((i DIV factor) = 0) AND (factor > 1) DO
	factor := factor DIV 10;
      WHILE factor > 0 DO	(* send digits *)
	BEGIN
	  digit := i DIV factor;
	  puthp(chr(ord('0') + digit));
	  i := i - (digit * factor);
	  factor := factor DIV 10;
	END; (* WHILE factor ... *)
    END; (* putinum *)



  PROCEDURE putdnum(d:  REAL);
  (*------------------------------------------------------------------*)
  (*  Sends the real number 'd' to the HP plotter in the form:        *)
  (*  ddd.ddd or -ddd.ddd.  Leading zeroes are suppressed.	      *)
  (*------------------------------------------------------------------*)

    VAR
      digit,			(* individual digit *)
      factor,			(* counts order of digit sent *)
      i:  INTEGER;		(* temporary value of d *)


    BEGIN (* putdnum *)
      i := trunc(d);	(* put the integral part, *)
      putinum(i);

      puthp('.');		(* the decimal point, *)

      i := trunc(1000.0 * abs((d - i)));  (* then the fraction *)
      factor := 100;
      WHILE factor > 0 DO
	BEGIN
	  digit := i DIV factor;  (* get digit to send *)
	  puthp(chr(ord('0') + digit));  (* and send it *)
	  i := i - (digit * factor);
	  factor := factor DIV 10;
	END; (* WHILE factor *)
    END; (* putdnum *)



  PROCEDURE putddcmd(cmd:  cmdstrtype;
	    	     d1,
	    	     d2:  REAL);
  (*------------------------------------------------------------------*)
  (*  Sends a command and two decimal parameters to the HP plotter.   *)
  (*------------------------------------------------------------------*)

    BEGIN (* putddcmd *)
      handshake(20);

      puthp(cmd[1]);		(* send command *)
      puthp(cmd[2]);

      putdnum(d1);		(* now the parameters *)
      puthp(',');
      putdnum(d2);
      puthp(';');
    END; (* putddcmd *)



  PROCEDURE putidcmd(cmd:  cmdstrtype;
	    	     i:  INTEGER;
      		     d:  REAL);
  (*------------------------------------------------------------------*)
  (*  Sends a command and an integer and a decimal parameter to the   *)
  (* HP plotter.						      *)
  (*------------------------------------------------------------------*)

    BEGIN (* putidcmd *)
      handshake(18);

      puthp(cmd[1]);		(* send command *)
      puthp(cmd[2]);

      putinum(i);		(* now the parameters *)
      puthp(',');
      putdnum(d);
      puthp(';');
    END; (* putidcmd *)



  PROCEDURE putiicmd(cmd:  cmdstrtype;
	    		 i1,
	    	         i2:  INTEGER);
  (*------------------------------------------------------------------*)
  (*  Sends a command and two integer parameters to the HP plotter.   *)
  (*------------------------------------------------------------------*)

    BEGIN (* putiicmd *)
      handshake(16);

      puthp(cmd[1]);		(* send command *)
      puthp(cmd[2]);

      putinum(i1);		(* now the parameters *)
      puthp(',');
      putinum(i2);
      puthp(';');
    END; (* putiicmd *)



  PROCEDURE putiiiicmd(cmd:  cmdstrtype;
	    	       i1,
	    	       i2,
	    	       i3,
	    	       i4:  INTEGER);
  (*------------------------------------------------------------------*)
  (*  Sends a command and four integer parameters to the HP plotter.  *)
  (*------------------------------------------------------------------*)

    BEGIN (* putiiiicmd *)
      handshake(30);

      puthp(cmd[1]);		(* send command *)
      puthp(cmd[2]);

      putinum(i1);		(* now the parameters *)
      puthp(',');
      putinum(i2);
      puthp(',');
      putinum(i3);
      puthp(',');
      putinum(i4);
      puthp(';');
    END; (* putiiiicmd *)



  PROCEDURE inithp;
  (*------------------------------------------------------------------*)
  (*  Sets up the HP plotter software handshake mode and the scaling. *)
  (*------------------------------------------------------------------*)

    BEGIN (* inithp *)
      puthpstr('IN;       ');	(* init the plotter *)

      puthp(chr(esccode));	(* shut off hardware handshake *)
      puthpstr('.@;0:     ');

      puthp(chr(esccode));	(* software handshake mode 1 *)
      puthpstr('.H        ');
      putinum(maxsent);
      puthpstr(';5;6:     ');	(* decimal equiv of enq and ack *)

      putiiiicmd('SC', 0, 10000, 0, 7200);  (* 40'ths of mm. *)
    END; (* inithp *)



  PROCEDURE penup;
  (*------------------------------------------------------------------*)
  (*  Sets the 'penlifted' indicator. The command is sent to the      *)
  (* plotter only if necessary by 'drawto'.			      *)
  (*------------------------------------------------------------------*)

    BEGIN (* penup *)
      penlifted := TRUE;
    END; (* penup *)



  PROCEDURE pendown;
  (*------------------------------------------------------------------*)
  (*  Sets the 'penlifted' indicator. The command is sent to the      *)
  (* plotter only if necessary by 'drawto'.			      *)
  (*------------------------------------------------------------------*)

    BEGIN (* pendown *)
      penlifted := FALSE;
    END; (* pendown *)



  PROCEDURE sethpplot(l:  alinetype;
      		      rptlen:  REAL;
      		      endsym:  aendtype);
  (*------------------------------------------------------------------*)
  (*  Sends the 'line type' command and sets the penup and pendown.   *)
  (*------------------------------------------------------------------*)

    VAR
      linetynum:  INTEGER;


    BEGIN (* sethpplot *)
      IF ((l = noline) AND (endsym = point)) OR (l <> noline)
	THEN
	  BEGIN
	    IF l = solid
	      THEN
		puthpstr('LT;       ')  (* set solid line or *)
	      ELSE
		BEGIN
		  CASE l OF	(* set pattern number *)
		    noline:        linetynum := 0;
		    dotted:        linetynum := 1;
		    dashed:        linetynum := 2;
		    dotdashed:     linetynum := 4;
		    dotdotdashed:  linetynum := 6;
		  END; (* CASE l *)
		  putidcmd('LT', linetynum, rptlen * percent);
		END; (* ELSE *)
	    pendown;		(* and lower pen *)
	  END (* IF ((l... *)
	ELSE
	  penup;		(* otherwise raise pen *)
    END; (* sethpplot *)



  PROCEDURE sethpstr(height,
      		     width,
      		     angle:  REAL);
  (*------------------------------------------------------------------*)
  (* Uses the 'SI' command to set the character size and the 'DI'     *)
  (* command to set the string angle.				      *)
  (*------------------------------------------------------------------*)

    BEGIN (* sethpstr *)
      putddcmd('SI', width / 10.0,    (* convert to cm. *)
      	             height / 10.0);
      putddcmd('DI', 100.0 * cos(angle / radian),  (* run *)
      		     100.0 * sin(angle / radian)); (* rise *)
    END; (* sethpstr *)



  PROCEDURE sethpwindow(left,
      			right:  apointtype);
  (*------------------------------------------------------------------*)
  (*  Sends the window size to the HP plotter.	This uses absolute    *)
  (* (not scaled) HP plotter units.				      *)
  (*------------------------------------------------------------------*)

    BEGIN (* sethpwindow *)
      putiiiicmd('IW', round((left.x * xfactor) + absp1x),  (* send to *)
      		       round((left.y * yfactor) + absp1y),  (* plotter *)
      		       round((right.x * xfactor) + absp1x),
      		       round((right.y * yfactor) + absp1y));
    END; (* sethpwindow *)



  FUNCTION isinwindow(pt:  apointtype):  BOOLEAN;
  (*------------------------------------------------------------------*)
  (*  Returns TRUE if the 'pt' is within the window defined by	      *)
  (* 'oldleft' and 'oldright'.					      *)
  (*------------------------------------------------------------------*)

    BEGIN (* isinwindow *)
      isinwindow := (pt.x <= oldright.x) AND (pt.y <= oldright.y) AND
      		    (pt.x >= oldleft.x) AND (pt.y >= oldleft.y);
    END; (* isinwindow *)



  PROCEDURE plot(dest:  apointtype);
  (*------------------------------------------------------------------*)
  (*  Plots a line from the present position to 'dest'.		      *)
  (*------------------------------------------------------------------*)



    PROCEDURE drawto(dest:  apointtype);
    (*----------------------------------------------------------------*)
    (*	Moves the pen to 'dest' with the pen specified by 'penlifted'.*)
    (*----------------------------------------------------------------*)

      BEGIN (* drawto *)
	IF penlifted AND (NOT oldpenlifted)
	  THEN
	    puthpstr('PU;       ')
	ELSE IF (NOT penlifted) AND oldpenlifted
	  THEN
	    puthpstr('PD;       ');
	oldpenlifted := penlifted;

	putiicmd('PA', round(dest.x * xfactor),
		       round(dest.y * yfactor));
	oldpt := dest;
      END; (* drawto *)



    PROCEDURE draw(what:  aendtype);
    (*----------------------------------------------------------------*)
    (*	Draws the endpoint symbol 'what' with size 'oldesize'         *)
    (* centered at the present position.   			      *)
    (*----------------------------------------------------------------*)

      VAR
	origin,			(* holds line endpoint *)
	temp:  apointtype;

	oldpenup:  BOOLEAN;	(* holds old penlifted *)



      PROCEDURE closed(initangle:  REAL;
		       npoints:  INTEGER);
      (*--------------------------------------------------------------*)
      (*  Plots a polygon of size 'oldesize' with 'npoints'           *)
      (* corners starting at 'initangle' degrees clockwise from       *)
      (* vertical centered at 'origin'.				      *)
      (*--------------------------------------------------------------*)

        VAR
	  i:  INTEGER;		(* corner counter *)

	  angle:  REAL;		(* angle counter *)


        BEGIN (* closed *)
	  FOR i := 0 to npoints DO
	    BEGIN
	      IF i = 0
	        THEN
		  penup
	      ELSE IF penlifted
	        THEN
		  pendown;
	      angle := (initangle + i * (360.0 / npoints)) / radian;
	      temp.x := origin.x + oldesize * sin(angle);
	      temp.y := origin.y + oldesize * cos(angle);
	      drawto(temp);
	    END; (* FOR i ... *)
        END; (* closed *)



      PROCEDURE open(initangle:  REAL);
      (*--------------------------------------------------------------*)
      (*  Plots a cross of size 'oldesize' with one axis at           *)
      (* 'initangle' clockwise from vertical centered at 'origin'.    *)
      (*--------------------------------------------------------------*)

        VAR
	  i,			(* loop counters *)
	  j:  INTEGER;

	  angle,		(* angle of a line segment *)
	  stangle:  REAL;	(* start angle of a line segment *)


	BEGIN (* open *)
	  FOR j := 0 TO 1 DO
	    BEGIN
	      stangle := initangle + j * 90.0;
	      FOR i := 0 TO 1 DO
		BEGIN
		  IF i = 0
		    THEN
		      penup
		    ELSE
		      pendown;
		  angle := (stangle + i * 180.0) / radian;
		  temp.x := origin.x + oldesize * sin(angle);
		  temp.y := origin.y + oldesize * cos(angle);
		  drawto(temp);
		END; (* FOR i ... *)
	    END; (* FOR j ... *)
	END; (* open *)



      BEGIN (* draw *)
	IF what IN [square, triangle, diamond, circle, cross, ecks]
	  THEN
	    BEGIN
	      origin := oldpt;	(* save endpoint *)
	      oldpenup := penlifted;	(* and pen status *)
	      IF oldline <> solid	(* set it to solid *)
		THEN
		  sethpplot(solid, 0.0, noend);
	      CASE what OF
		square:    closed(45.0, 4);
		triangle:  closed(0.0, 3);
		diamond:   closed(0.0, 4);
		circle:    closed(0.0, 10);
		cross:	 open(0.0);
		ecks:	 open(45.0);
	      END; (* CASE what *)

	      penup;			(* return to endpoint *)
	      drawto(origin);

	      IF oldline <> solid	(* restore line charact's *)
		THEN
		  sethpplot(oldline, oldrptlength, oldend);
	    END; (* IF what... *)
      END; (* draw *)



    BEGIN (* plot *)
      drawto(dest);
      draw(oldend);
    END; (* plot *)



  FUNCTION abadcall:  BOOLEAN;
  (*------------------------------------------------------------------*)
  (*  Returns TRUE if something is wrong with a procedure call.  Not  *)
  (* implemented.						      *)
  (*------------------------------------------------------------------*)

    BEGIN (* abadcall *)
      abadcall := FALSE;
    END; (* abadcall *)



  PROCEDURE adefault;
  (*------------------------------------------------------------------*)
  (*  Sets all globals to their default values.			      *)
  (*------------------------------------------------------------------*)

    BEGIN (* adefault *)
      oldline := noline;		(* simple line *)
      oldrptlength := 5.0;
      oldesize := 3.0;
      oldend := noend;
      sethpplot(oldline, oldrptlength, oldend);

      oldleft.x := minx;		(* window size to full screen *)
      oldleft.y := miny;
      oldright.x := maxx;
      oldright.y := maxy;
      sethpwindow(oldleft, oldright);

      sethpstr(2.70, 1.875, 0.0);	(* default character size *)
    END; (* adefault *)



  PROCEDURE agraph;
  (*------------------------------------------------------------------*)
  (*------------------------------------------------------------------*)

    BEGIN (* agraph *)
    END; (* agraph *)



  PROCEDURE ainit;
  (*------------------------------------------------------------------*)
  (*  Initializes the I/O to the HP plotter, and 		      *)
  (* sets all parameters to their default values, and places the pen  *)
  (* in the lower left-hand corner.				      *)  
  (*------------------------------------------------------------------*)

    BEGIN (* ainit *)
      inithp;
      adefault;
      agraph;
      oldpenlifted := TRUE;
      oldpt.x := minx;
      oldpt.y := miny;
      plot(oldpt);
      numsent := 0;
    END; (* ainit *)



  PROCEDURE amakecopy;
  (*------------------------------------------------------------------*)
  (*	This is not implemented - it could be used to indicate for    *)
  (* the paper to be changed by sending out DP;, then OS;'s till bit  *)
  (* 2 returned was a 1, then a DC;.  This would turn on the 'enter'  *)
  (* light on the plotter, wait until 'enter' was pushed (presumably  *)
  (* after the paper had been changed) by the operator, and then      *)
  (* extinguish the light and return.				      *)
  (*	See the HP plotter manual for more details.		      *)
  (*------------------------------------------------------------------*)

    BEGIN (* amakecopy *)
    END; (* amakecopy *)



  PROCEDURE aplot(endpoint:  apointtype);
  (*------------------------------------------------------------------*)
  (*  Plots to 'endpoint' using line type 'oldline' and end type      *)
  (* 'oldend'.							      *)
  (*------------------------------------------------------------------*)

    BEGIN (* aplot *)
      plot(endpoint);
    END; (* aplot *)



  PROCEDURE asetplot(line:  alinetype;
                     repeatlength,
		     endsize:  REAL;
		     endsymbol:  aendtype);
  (*------------------------------------------------------------------*)
  (*  Sets the characteristics of the line(s) to be plotted next.     *)
  (*------------------------------------------------------------------*)

    BEGIN (* asetplot *)
      sethpplot(line, repeatlength, endsymbol);

      oldline := line;		(* save the values *)
      oldrptlength := repeatlength;
      oldesize := endsize;
      oldend := endsymbol;
    END; (* asetplot *)



  PROCEDURE asetstr(charheight,
                    charwidth,
		    charangle,
		    charspace,
		    strangle:  REAL);
  (*------------------------------------------------------------------*)
  (*  Sets the characteristics of the next string(s) to be plotted.   *)
  (*  Doesn't implement the charspace or charangle settings since it  *)
  (* uses the standard plotter values.				      *)
  (*------------------------------------------------------------------*)

    BEGIN (* asetstr *)
      sethpstr(charheight, charwidth, strangle);
    END; (* asetstr *)



  PROCEDURE asetwindow(lowerleft,
                       upperright:  apointtype);
  (*------------------------------------------------------------------*)
  (*  Sets the window size.  Nothing will appear outside this window. *)
  (*------------------------------------------------------------------*)

    BEGIN (* asetwindow *)
      oldleft := lowerleft;	(* save the window value *)
      oldright := upperright;
      IF lowerleft.x < minx	(* make sure it's valid *)
	THEN
	  oldleft.x := minx;
      IF lowerleft.y < miny
	THEN
	  oldleft.y := miny;
      IF upperright.x > maxx
	THEN
	  upperright.x := maxx;
      IF upperright.y > maxy
	THEN
	  upperright.y := maxy;

      sethpwindow(oldleft, oldright);
    END; (* asetwindow *)



  PROCEDURE asize(VAR size:  apointtype);
  (*------------------------------------------------------------------*)
  (*  Returns the size of the display in mm.			      *)
  (*------------------------------------------------------------------*)

    BEGIN (* asize *)
      size.x := maxx - minx;
      size.y := maxy - miny;
    END; (* asize *)



  PROCEDURE astr(str:  astringtype);
  (*------------------------------------------------------------------*)
  (*  Writes the null-terminated string 'str' on the screen starting  *)
  (* at the current location (taken as the lower left corner of the   *)
  (* first character) and proceeding at 'strangle'.  The	      *)
  (* character angle, height, width and spacing is 'charangle',       *)
  (* 'charheight', 'charwidth' and 'charspace'.			      *)
  (* 								      *)
  (*  In this implementation, the HP 7225A mode of text writing is    *)
  (* used, so the standard spacing and character rotation (same as    *)
  (* line rotation) are used.					      *)
  (*------------------------------------------------------------------*)

    VAR
      index:  INTEGER;


    BEGIN (* astr *)
      handshake(136);	(* make sure there's enough room *)
      puthp('L');	(* send start of LB command *)
      puthp('B');

      index := 1;	(* put out characters *)
      WHILE ord(str[index]) <> nulcode DO
	BEGIN
	  puthp(str[index]);
	  index := index + 1;
	END; (* WHILE str... *)

      puthp(chr(etxcode));	(* and end of string code *)
      puthp(';');
    END; (* astr *)



  PROCEDURE atext;
  (*------------------------------------------------------------------*)
  (*  Sets the display to the text mode.			      *)
  (*------------------------------------------------------------------*)

    BEGIN (* atext *)
    END; (* atext *)



  PROCEDURE awhere(VAR where:  apointtype;
                   VAR inwindow:  BOOLEAN);
  (*------------------------------------------------------------------*)
  (*  Returns the current location and whether it's inside the        *)
  (* window.							      *)
  (*------------------------------------------------------------------*)

    BEGIN (* awhere *)
      where := oldpt;
      inwindow := isinwindow(oldpt);
    END; (* awhere *)
modend.
