program rsgra(rsdata, picture, rsgrap, wave, marks, output);
(* rsequence graph

  Thomas D. Schneider, Ph.D.
  toms@alum.mit.edu
  https://alum.mit.edu/www/toms 

module libraries required: delman, prgmods, dops *)

label 1; (* end of program *) 
  
const 
(* begin module version *)
   version = 5.13; (* of rsgra.p 2021 Aug 27
2021 Aug 27, 5.13: fix horizontal x axis 'Position L (in bases)'
2021 Aug 27, 5.12: remove rotation solution, now solved using new latex with
                   ps2pdf -dALLOWPSTRANSPARENCY
2021 Aug 16, 5.11: fix y axis label
2021 Aug 13, 5.10: rotation problem fixed!
2021 Aug 03, 5.09: handle 90 degree rotations to stop powerdot rotation
2016 Jul 22, 5.08: finish moving 'Position L' and book title
2015 sep 27, 5.07: debug
2007 Feb  9, 5.06: prgmod upgrade
2007 Feb  9, 5.05: avoid rotation by 90 degrees
                   crash because eof fails on empty wave file.
2003 Nov 18, 5.04: adjust location of figure on page.
2003 Nov 18, 5.01: label position axis
1998 Jun 19: point to rseq in see also
1993 Aug 19: minor changes
1991 May 31: rsgrap file becomes wave file and rsgrap takes
range of the graph to show.
origin 1986 feb 19 *)
(* end module version *)
  
(* begin module describe.rsgra *)
(*
name
   rsgra: rsequence graph
  
synopsis
   rsgra(rsdata: in, picture: out, rsgrap, wave: in, marks: in,
         output: out) 

files 
   rsdata: data file from rseq program

   picture: graph of rsequence in PostScript

   rsgrap: parameters to control the program.
      first line: two integers that define the from-to range to display

   wave: parameters to control the program.  empty file means no cosine
      wave, otherwise the parameters of the wave are given one per line:
      extreme: char;  h or l, the extreme high or low point on the curve
         defined by the wavelocation and wavebit
      wavelocation: real; the location in bases of the extreme
      wavebit: real; the location in bits of the extreme
      waveamplitude: real; the amplitude of the wave in bits
      wavelength: real; the wave length of the wave in bases

   marks: an empty file or a set of integers, one per line that are the
      locations of bases that should be specially marked on the graph.
      If the first line of the file begins with the letter 'b' and is
      followed by a real number, then this number defines the location of
      a bar to be placed on the graph immediately after the position given.

   output: messages to the user

description    Rsgra generates a graph of Rsequence versus position l.
The user may overlay a cosine wave on the graph, to signify    the
orientation of the major grove, for example.

   See the discussion about the REMOVE feature in makelogo.p.

see also
   rseq.p, makelogo.p
  
author
   Thomas D. Schneider 
  
bugs

   2007 Feb 9: Adobe Readers look at PostScript files and if the file
   contains a 90 rotate, the reader TURNS THE WHOLE PAGE.  Stupid.  To
   avoid this, break 90 degree rotations into two 45 degree rotations.
   Amazingly simple way to fool an idiotic program.

   But it doesn't work.  So the rotation is not done now.

   For the same reason, the marks file cannot be empty.
   Use a large negative value to avoid having marks.

*)
(* end module describe.rsgra *)

(* begin module rsgra.const *)
      nfield = 4; (* size of field for printing n, the number of sites *)
(* end module rsgra.const *)
  
(* begin module pic.const *)
   pi = 3.14159265354; (* circumference divided by diameter of circle *)
   picfield = 8; (* width of numbers printed to the file *)
   picwidth = 5; (* number of decimal places for numbers *)
   charwidth = 0.0625; (* the width of characters in inches (ie, inches/char)
                        this allows centering of strings. *)
   (* note: for the Times-Roman  font, 0.0625 is a good value.
            for the Courier-Bold font, 0.08   is a good value. *)
   dotfactor = 0.00625; (* the size of dots *)
   defscale = 81; (* default scale factor.  coordinate units per inch *)
(* end module pic.const version = 2.59; (@ of dops.p 1990 December 5 *)
  
(* begin module interact.const *) 
(* begin module string.const *)
      maxstring = 2000; (* the maximum string *)
(* end module string.const version = 5.31; (@ of prgmod.p 2006 Oct 10 *)
(* end module interact.const version = 5.31; (@ of prgmod.p 2006 Oct 10 *)
  
(* begin module rsgra.filler.const *) 
      fillermax = 21; (* the size of the filler array for a string *) 
(* end module rsgra.filler.const *)

type  
(* begin module rsgra.type *)
(* begin module wave.type *)
      waveptr = ^waveparam; (* to link several wave definitions together *)
      waveparam = record (* parameters to define a cosine wave *)
         (* define a cosine wave: *)
         extreme: char; (* h or l, the high or low extreme to be defined *)
         wavelocation: real; (* the location in bases of the extreme *)
         wavebit: real; (* the location in bits of the extreme *)
         waveamplitude: real; (* the amplitude of the wave in bits *)
         wavelength: real; (* the wave length of the wave in bases *)
{ 2000 Nov 1.  This is difficult because the length of a curve is not
simply multiplied by a scale factor.
         BASEdashon: real; (* dashon interval in bases *)
         BASEdashoff: real; (* dashon interval in bases *)
         BASEdashoffset: real; (* dashon interval in bases *)
}
         dashon: real; (* the size of on dashes in cm.
                          dashon <= 0 means no dashes *)
         dashoff: real; (* the size of on dashes in cm *)
         dashoffset: real; (* the size of off dashes in cm *)
         thickness: real; (* thickness of the wave in cm.  <= 0 means default *)
         next: waveptr; (* the next wave *)
      end;
(* end module wave.type version = 5.31; (@ of prgmod.p 2006 Oct 10 *)
(* end module rsgra.type version = 5.31; (@ of prgmod.p 2006 Oct 10 *)

(* begin module rs.type *)
   rstype = record (* types of data in the data table from rseq *)
      rstart, rstop: integer; (* range of the data *)
      l, (* position *)
      nal,ncl,ngl,ntl: integer; (* numbers of each base *)
      rsl, (* rsequence(l) *)
      rs,  (* running sum of rs *)
      varhnb, (* variance estimate for small sample correction *)
      sumvar: (* sum of varhnb *)
         real;
      nl: integer; (* = a+c+g+t *)
      ehnb: real; (* hg-e(n) *)
      flag: char; (* e = exact, a = approximate sampling statistics *)
   end;
(* end module rs.type *)

(* begin module interact.type *)
(* begin module string.type *)
   stringptr = ^string; (* pointer to a string *)
   string = record (* a string of characters *)
      letters: array[1..maxstring] of char; (* the letters in the string *)
      length: integer; (* the number of characters in the string *)
      current: integer; (* the letter we are working on *)
      next: stringptr; (* the next string in a series *)
   end;
(* end module string.type version = 5.31; (@ of prgmod.p 2006 Oct 10 *)
(* end module interact.type version = 5.31; (@ of prgmod.p 2006 Oct 10 *)

(* begin module trigger.type *) 
      trigger = record (* an object to be searched for *)
         seek: string; (* the characters looked for *)
         state: integer; (* how close to triggering we are *)
         skip: boolean; (* trigger not found- skip the line *)
         found: boolean (* the trigger was found *)
      end;
(* end module trigger.type version = 5.31; (@ of prgmod.p 2006 Oct 10 *)

(* begin module filler.type *)
      (* the following is an array used to fill a string.
      it is convenient to have it much shorter than the maxstring, so that
      it is easy to fill the string using procedure fillstring.
      the user must declare the value of constant fillermax. *)
      filler = packed array[1..fillermax] of char;
(* end module filler.type version = 5.31; (@ of prgmod.p 2006 Oct 10 *)
  
var 
(* begin module rsgra.var *)
   rsdata, (* output of rseq program *)
   picture, (* output of this program, graph in PostScript *)
   rsgrap, (* parameters to control the program *)
   wave, (* parameters to control the drawing of the cosine wave *)
   marks: (* locations of marks *)
      text;
(* end module rsgra.var *)

(* begin module pic.var *)
   inpicture: boolean; (* true if we are drawing the picture,
                          ie, startpic has been called *)
   picxglobal, picyglobal: real; (* absolute location in the graph *)
   pictolerance: real; (* 10 raised to the picwidth,
      to detect values close to zero *)

   scale: real; (* scale factor.  graphic coordinate units per inch *)

(* NONSTANDARD for efficient use of postscript, keep track of
whether there is a current path *)
   inpath: boolean;

(* NONSTANDARD keep track of number of segments drawn so that
they can be stroked.  This (probably) solves the problem of the
Apple printer dying because it can't handle the data. *)
   segments: integer;

   xsideold, ysideold: real; (* current size of a rectangle.  see rectsize *)
(* end module pic.var version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module halt *) 
procedure halt;
(* stop the program.  the procedure performs a goto to the end of the
   program.  you must have a label:
      label 1;
   declared, and also the end of the program must have this label:
      1: end.
   examples are in the module libraries.
   this is the only goto in the delila system. *)
begin
      writeln(output,' program halt.');
      goto 1
end;
(* end module halt version = 5.31; (@ of prgmod.p 2006 Oct 10 *)
  
(* begin module interact.clearstring *) 
(* begin module clearstring *)
(* These modules clear strings in various ways *)
(* ---- *)
procedure emptystring(var ribbon: string);
(* empty the contents of the string but do NOT
remove the pointer.  This is useful for clearing
one string within a linked list of them. *)
var   index: integer; (* to the ribbon *)
begin (* clearstring *)
   with ribbon do begin
      for index := 1 to maxstring do letters[index] := ' ';
      length := 0;
      current := 0;
   end
end; (* emptystring *)
(* ---- *)
procedure clearstring(var ribbon: string);
(* empty the string and remove the pointer *)
begin (* clearstring *)
   with ribbon do begin
      emptystring(ribbon);
      next := nil;
   end
end; (* clearstring *)
(* ---- *)
procedure initializestring(var ribbon: string);
(* start the string with a nil pointer.   This routine
should be called before doing linked list work.  This allows
the standard string routines to clear the string without
killing the pointer.  This is now deprecated, do not use it
since clearstring still clears the next pointer. *)
begin (* initializestring *)
   writeln(output,'remove initializestring routine!');
   writeln(output,'replace it with clearstring routine!');
   halt; (* to force deprecation *)
   clearstring(ribbon);
   ribbon.next := nil;
end; (* initializestring *)
(* end module clearstring version = 5.31; (@ of prgmod.p 2006 Oct 10 *)
(* end module interact.clearstring version = 5.31; (@ of prgmod.p 2006 Oct 10 *)

(* begin module interact.writestring *) 
(* begin module writestring *)
procedure writestring(var tofile: text; var s: string);
(* write the string s to file tofile, no writeln *)
var   i: integer; (* index to s *)
begin (* writestring *)
      with s do for i := 1 to length do write(tofile, letters[i])
end; (* writestring *)
(* end module writestring version = 5.31; (@ of prgmod.p 2006 Oct 10 *)
(* end module interact.writestring version = 5.31; (@ of prgmod.p 2006 Oct 10 *)
  
(* begin module trigger.proc *) 
(* this module allows one to scan a series of characters, as from
an array or a file, and to "trigger" or detect a simple string
in the series.  the advantage of the trigger is that several triggers
can "observe" a stream of characters at once, each looking for a
different thing.
some other modules required: interact.const, interact.type *)

procedure resettrigger(var t: trigger);
(* reset the trigger to ground state *)
begin (* resettrigger *)
      with t do begin
         state := 0;
         skip := false;
         found := false
      end
end; (* resettrigger *)

procedure testfortrigger(ch: char; var t: trigger);
(* look at the character ch.
   if it is part of the trigger (at the current trigger state),
       then the trigger state goes higher.
   if it is not part of the trigger then the trigger state is reset,
      skip is true and one should skip onward to find the trigger.
   if the trigger is found, found is true.
1996 Sep 12: Bug found!  In the case of a trigger "ab", the program
used to miss it for situations like "aab".  This was because at the
first a it would step up.  Then it would see the second a and recognize
that was not part of ab.  It would fail to realize that it could be
the start of a new one.  The code now accounts for that possibility. *)
begin (* testfortrigger *)
      with t do begin
         state := succ(state);
{
writestring(list,seek);
writeln(list,'testfortrigger seek.letters[',state:1,']:',
seek.letters[state],' ch:',ch);
}
         if seek.letters[state] = ch
         then begin
            skip := false;
            if state = seek.length then found := true
                                   else found := false
         end
         else begin
            (* it failed.  But wait!  It could be the beginning
            of a NEW trigger string! *)
            if seek.letters[1] = ch then begin
               state := 1;
               skip := false;
               found := false
            end
            else begin (* reset trigger *)
               state := 0;
               skip := true;
               found := false
            end
         end
      end
end; (* testfortrigger *)
(* end module trigger.proc version = 5.31; (@ of prgmod.p 2006 Oct 10 *)

(* begin module filler.fillstring *)
procedure fillstring(var s: string; a: filler);
(* this procedure makes it reasonably easy to fill the string s with
characters.  one calls the procedure as: *)
(*                           1         2         3         4         5 *)
(*                  12345678901234567890123456789012345678901234567890 *)
(*   fillstring(s, 'this-is-the-string                                ');
the two comments make it easy to line the characters up. also, for this
example, it was assumed that the length of filler as defined by the
constant fillermax was 50. *)
var
      length: integer; (* of the string without trailing blanks *)
      index: integer; (* of s *)
begin (* fillstring *)
      clearstring(s);
      length := fillermax;
      while (length > 1) and (a[length] = ' ') do length := pred(length);
      if (length = 1) and (a[length] = ' ') then begin
         writeln(output, 'fillstring: the string is empty');
         halt
      end;

      for index := 1 to length do s.letters[index] := a[index];
      s.length := length;
      s.current := 1
end; (* fillstring *)
(* end module filler.fillstring version = 5.31; (@ of prgmod.p 2006 Oct 10 *)

(* begin module filler.filltrigger *) 
procedure filltrigger(var t: trigger;
                      a: filler);
(* fill the trigger t *)
begin (* filltrigger *)
         fillstring(t.seek,a)
end; (* fillstring *)
(* end module filler.filltrigger version = 5.31; (@ of prgmod.p 2006 Oct 10 *)

(* begin module copyaline *)
procedure copyaline(var fin, fout: text);
(* copy a line from file fin to file fout *)
begin (* copyaline *)
      while not eoln(fin) do begin
         fout^ := fin^;
         put(fout);
         get(fin)
      end;
      readln(fin);
      writeln(fout);
end; (* copyaline *)
(* end module copyaline version = 5.31; (@ of prgmod.p 2006 Oct 10 *)

(* begin module pic.functions *)
(* ********************************************************************** *)
(* begin module pic.await *)
procedure await;
(* Wait for user to type a carriage return.  the routine assumes that
there is a global file called input. *)
begin
(* the infinite way: 
    writeln(output);
    writeln(output,'*********************************');
    writeln(output,'* Use control-c to kill program *');
    writeln(output,'*********************************');
    while true do begin end;*)
end;
(* end module pic.await version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.startpic *)
procedure startpic(var afile:text; setscale,x,y: real; thefont: char);
(* open the graphics field, with the given scale, and at (x,y)
in that scale.  scale is in device coordinates per inch.
The font is chosen with thefont; t = Times-Roman, c = Courier-Bold *)
(* start pic output to file afile, set the globals *)
(* NONSTANDARD *)
(* this is the actual "world" coordinates used: *)
(*                        xmin,  xmax,  ymin, ymax *)
(*    ns; if (setwindow(-5.0/scale, +5.0/scale, -5.0/scale, +5.0/scale)*)
begin
   writeln(afile,'gsave'); (* save the current graphics state *)

{
Only use initgraphics if really necessary.  Since it restarts
the graphics, one can't put the figure inside another
PostScript figure.
   writeln(afile,'initgraphics'); (* make sure the printer
           is ready to print, without this, sometimes an Apple laserwriter
           will print the graph upside down, tiny and backwards! *)
}
   scale := setscale; (* set the global scale *)
   (* clean residue from before *)
   writeln(afile,'clear erasepage % REMOVE FOR PACKAGING INTO ANOTHER FIGURE');

   case thefont of
      'c': begin
           writeln(afile,'/Courier-Bold findfont'); (* locate the font *)
           writeln(afile,10:1,' scalefont'); (* set the font size in points*)
      end;
      't': begin
           writeln(afile,'/Times-Roman findfont'); (* locate the font *)
           writeln(afile,12:1,' scalefont'); (* set the font size in points*)
      end;
   end;

   writeln(afile,'setfont'); (* put the font into the current font *)

(* If the following statement is done then it will work on the sun, but will
kill the applewriter!!!! 

Sun's non-standard PostScript extension, setlinewidth has default 0,
as stated in the Read This First and the NeWS Manual.
This draws very quickly with 1 bit wide lines.
If '1 setlinequality' is not done, then one cannot set the width of lines.
So to use PostScript on the screen, I must first do
'1 setlinequality'.  However, if I send this code to the Applewriter,
it kills PostScript on the Applewriter and I get no output whatsoever!
(It took me several hours to figure this out, since once PostScript is killed
on the Applewriter, the NEXT output is also smashed and I had to figure that
out also...)

So a standard PostScript program will not work correctly with the
default.  "Correcting" the PostScript program so that it works on the
Sun means that it BOMBS on the Applewriter.

The default for setlinequality should be '1 setlinequality' so that
the same PostScript code can be used both on the Sun and with other devices.
If you want speed, use the nonstandard form.
An alternative is to redefine setlinequality so that '0 setlinequality'
does give correct results with standard PostScript.

Please review this, Randy.  I think that Sun should fix it.
   writeln(afile,'1 setlinequality');  makes lines at least 1 bit wide *)

   (* set the scale to inches
   writeln(afile,
                 scale:picfield:picwidth,' ',
                 scale:picfield:picwidth,' scale'); *)

   (* define some things in postscript *)
   (* doline allows less stuff to be put in the output file.
      it takes two numbers off the stack, copies them, draws a line
      to them as coordinates. *)
(* replaced by 'currentpoint translate'
   writeln(afile,'/doline { 2 copy lineto } def');
*)
   (* define a function that makes inches out of a number *)
(* do this all internally here, it's faster
   writeln(afile,'/i { ',scale:picfield:picwidth,' mul} def');
*)

   (* move to the start point on the page *)
   writeln(afile,
                     (x*scale):picfield:picwidth,
                 ' ',(y*scale):picfield:picwidth,
                 ' translate');

    writeln(afile);
    writeln(afile,'% Define functions so the text produced is smaller');
    writeln(afile,'/a {stroke newpath 0 0} def     % special for arc');
    writeln(afile,'/c {stroke 0 0 moveto} def      % current point');
    writeln(afile,'/f {findfont 10 scalefont setfont} def');
    writeln(afile,'   % to set fonts simply use the f function.  Example:');
    writeln(afile,'   %/Symbol f (\142) /Courier-Bold f (-galactosidase');
    writeln(afile,'/l {lineto} def');
    writeln(afile,'/m {moveto} def');
    writeln(afile,'/n {stroke newpath 0 0 moveto} def'); (* new segment *)
    writeln(afile,'/rl {rlineto} def');
    writeln(afile,'/rm {rmoveto} def');
    writeln(afile,'/s {newpath 0 0 moveto} def     % Start path '); 
    writeln(afile,'/t {currentpoint translate} def % translate ');
    writeln(afile,'/x {show} def                   % show teXt ');
    writeln(afile);

   (* start out the pathway *)
   inpath := false;
   (* start the number of segments written: *)
   segments := 0;

   (* now for the normal pic stuff: *)
   inpicture := true;
   picxglobal := 0.0;
   picyglobal := 0.0;
   pictolerance := trunc(exp(picwidth*ln(10))+0.5)
(*;writeln(output,'pictolerance = ',pictolerance:picfield:picwidth);*)
end;
(* end module pic.startpic version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.stoppic *)
procedure stoppic(var afile:text);
(* stop pic output to file afile *)
(* NONSTANDARD *)
begin
    if inpath then begin
       writeln(afile,'stroke');
       inpath := false
    end;

    writeln(afile,'showpage % REMOVE FOR PACKAGING INTO ANOTHER FIGURE');
    writeln(afile,'grestore'); (* restore the current graphics state
                                  to what it was before the startpic *)

    await;

    inpicture := false;
end;
(* end module pic.stoppic version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.drawr *)
procedure drawr(var afile: text; dx,dy: real; visibility: char;
               spacing: real);
(* make a line to file afile by relative draw of dx,dy with visibility
  i invisible
  - dashed
  . dotted
  l line
with the dashes or dots separated by the spacing given
(this has no effect with invisible and line). *)
(* NONSTANDARD *)
var
   ddx,ddy: real; (* changes in dx and dy for dots and dashes *)
   dr: real; (* the hypotenuse, the distance actually drawn *)
   on: boolean; (* draw linesegment if true *)
   y: real; (* the variable for tracking dots and dashes *)
   r: integer; (* number of times to cycle for dots and dashes *)
   ss: real; (* precalculated value to make things a bit faster *)
   theta: real; (* angle of the line *)
procedure checkseg(var afile: text);
(* NONSTANDARD checks how many segments have been written, if
more than 'buffer', stroke them to the postscript page *)
const buffer = 10;
begin
   if segments >= buffer
   then begin
   (* New segment: writeln(afile,'stroke newpath 0 0 moveto'); *)
      writeln(afile,'n');
      segments := 0
   end
   else segments := segments + 1;
end;
begin (* drawr *)
   if not inpath then begin
      (* starts from current coordinates *)
      (* Start path: writeln(afile,'newpath 0 0 moveto'); *)
      writeln(afile,'s');
      inpath := true
   end
   else checkseg(afile);
(* checks
if not (visibility in ['l','i','.','-'])
then writeln(afile,'%YELLLLLL!!!',visibility,'!');
writeln(afile,'% ',visibility,' line');*)

   (* put these on the stack, they will always be used *)
   write(afile,    (dx*scale):picwidth:picfield,
               ' ',(dy*scale):picwidth:picfield);
   case visibility of
  'l','i': begin
              case visibility of
                 'i': write(afile,' m');
                 'l': write(afile,' l');
              end
           end;
  '.','-': begin (* make up our own dots and dashes *)
              writeln(afile); (* move away from the (dx,dy) on the stack *)
              if spacing <= 0.0 then begin
                 writeln(output,'drawr: spacing zero with . or - line');
                 halt
              end;
              if dx = 0.0
              then begin
                 ddx := 0.0; (* avoid division by zero *)
                 ddy := scale*spacing;
                 if dy < 0 then ddy := - ddy; (* this makes sure that
                    we draw lines straight down if that was the request *)
              end
              else begin
                 (* find out the angle of the slope, intentionally
                    lose the sign *)
                 theta := arctan(abs(dy/dx));

                 ddx := scale*spacing*cos(theta);
                 ddy := scale*spacing*sin(theta);

                 (* return the sign to the little buggers *)
                 if dx < 0 then ddx := -ddx;
                 if dy < 0 then ddy := -ddy;
              end;
              y := 0;
              case visibility of
                 '.': ss := scale*dotfactor;
                 '-': on := true;
              end;

              dr := sqrt(dx*dx+dy*dy);
              for r := 1 to round(dr/spacing) do begin
                 case visibility of
                 '-': begin
                         write(afile,    (ddx):picwidth:picfield,
                                     ' ',(ddy):picwidth:picfield);
                         if on
                         then writeln(afile,' rl')
                         else writeln(afile,' rm');
                         on := not on
                      end;
                 '.': begin
                         (* put out a dot like in dotr *)
                         write(afile,
                                    +ss:picwidth:picfield,' 0 rl');
                         write(afile,' ',
                                    -ss:picwidth:picfield,' 0 rl');
                         write(afile,' ',(ddx):picwidth:picfield,
                                     ' ',(ddy):picwidth:picfield);
                         writeln(afile,' rm');
                      end;
                 end
              end;
              (* let's make really sure we got there!! *)
              writeln(afile,' m'); (* pulled from the stack *)
           end;
   end;

   (* an elegant way to make postscript keep a global record is
   to translate the coordinates! *)
   (* writeln(afile,' currentpoint translate'); *)
   writeln(afile,' t');

   picxglobal := picxglobal + dx;
   picyglobal := picyglobal + dy;
end;
(* end module pic.drawr version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.mover *)
procedure mover(var afile: text; dx,dy: real);
(* move relative the amount (dx, dy). *)
begin
   drawr(afile,dx,dy,'i',0.0);
end;
(* end module pic.mover version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.liner *)
procedure liner(var afile: text; dx,dy: real);
(* draw a line the relative amount (dx, dy). *)
begin
   drawr(afile,dx,dy,'l',0.0);
end;
(* end module pic.liner version = 2.59; (@ of dops.p 1990 December 5 *)


(* begin module pic.drawa *)
procedure drawa(var afile: text; x,y: real; visibility: char;
               spacing: real);
(* make a line to file afile to absolute coordinate x,y with visibility
  i invisible
  - dashed
  . dotted
  l line
with the dashes or dots separated by the spacing given
(this has no effect with invisible and line). *)
var
   dx, dy: real; (* differences between current and desired
                    locations *)
begin
   dx := x - picxglobal;
   dy := y - picyglobal;

   drawr(afile,dx,dy,visibility,spacing)
end;
(* end module pic.drawa version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.movea *)
procedure movea(var afile: text; x,y: real);
(* move to absolute x and y *)
begin
   drawa(afile,x,y,'i',0.0);
end;
(* end module pic.movea version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.linea *)
procedure linea(var afile: text; x,y: real);
(* draw a line from current position to absolute x and y *)
begin
   drawa(afile,x,y,'l',0.0);
end;
(* end module pic.linea version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.graphstring *)
procedure graphstring(var tofile: text; var s: string; centered: boolean); 
(* graph the string s.  If it is recognized as a quoted string (surrounded
by double quotes), graph it without the quotes and center it.
Always center if centered is true.
Otherwise simply graph it.  if not in picture, just write it to output *)
(* NONSTANDARD *)
var i: integer; (* index to s, and temporary storage *)
    mv: real; (* holds amount to move, in plotting coordinates *)
    quoted: boolean; (* true if the string is quoted *)
begin
   if (inpicture and (s.length > 0))
   then with s do begin
      if length > 2
      then if (letters[1]='"') and (letters[length]='"')
           then quoted := true
           else quoted := false
      else quoted := false;

      (* override so quoted strings are always centered *)
      if quoted then centered := true;

      if centered then begin
         (* generate the calls to center the string.  Note: this must not
            be done for the pic program, which already centers *)
         if quoted then i := length-2
                   else i := length;
         mv := i*charwidth/2.0;
         mover(tofile,-mv,0.0);
      end;

      (* do the non-standard postscript: *)
      (* do postscript to complete pervious path *)
      (* set current point: writeln(tofile,'stroke 0 0 moveto'); *)
      writeln(tofile,'c');

      write(tofile,'('); (* begin postscript literal *)
      if quoted
      then for i := 2 to length-1 do write(tofile,letters[i])
      else for i := 1 to length   do write(tofile,letters[i]);
      write(tofile,')'); (* end postscript literal *)
      writeln(tofile,' x'); (* show the literal *)
      inpath := false; (* force new path from here *)

      if centered then begin
         (* restore to previous location *)
         mover(tofile,mv,0.0);
      end;
   end
   else begin
(* no output if not in picture 
      writestring(tofile,s);
      writeln(tofile) *)
   end
end;
(* end module pic.graphstring version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.stringinteger *)
procedure stringinteger(number: integer; var name: string;
                        width: integer; leadingzeros: boolean);
(* make the string from the number, start putting characters in
after the current length point. use width characters.
if leadingzeros is true, trail zeros before the number. *)
var
   bigdigit: integer; (* the location of the biggest digit *)
   dig: integer; (* number of digits in the number *)
   place: integer; (* place to write the next digit of the number *)
   sign: integer; (* the sign of the number *)
begin
   with name do begin
      if number < 0
      then begin
         sign := -1;
         length := length + 1; (* provide room for the sign!! *)
         number := -number;
         if leadingzeros then begin
            writeln(output,'WARNING: stringinteger: the sign of a negative',
                           ' number with leading zeros is lost');
         end
      end
      else sign := +1;

      (* log 10 of the number plus 1 is the number of digits in the number.
      On this sun computer ln(1000)/ln(10) is 2.9999, which when
      truncated gives 2, rather than the desired 3.  To avoid this
      kind of problem, 0.1 is added. *)
      if number > 9
      then dig := trunc(ln(number+0.1)/ln(10))+1
      else dig := 1;

      if dig > width then begin
         writeln(output,'stringinteger: number width too small');
         writeln(output,dig:1,' digit number (',number:1,')');
         writeln(output,'does not fit in ',width:1,' characters');
         halt
      end;
      if leadingzeros
      then bigdigit := length + 1 (* no sign if leading zeros *)
      else begin
         bigdigit := length + width - dig + 1;

         if (bigdigit <= length) and (sign < 0) then begin
            writeln(output,'stringinteger: no room for sign');
            halt
         end;
      end;
      if sign < 0 then letters[bigdigit-1] := '-';

      for place := length + width downto bigdigit do begin
         case (number mod 10) of
            0: letters[place] := '0';
            1: letters[place] := '1';
            2: letters[place] := '2';
            3: letters[place] := '3';
            4: letters[place] := '4';
            5: letters[place] := '5';
            6: letters[place] := '6';
            7: letters[place] := '7';
            8: letters[place] := '8';
            9: letters[place] := '9';
         end;
         number := number div 10;
      end;
      length := length + width;
   end
end;
(* end module pic.stringinteger version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.stringreal *)
procedure stringreal(number: real; var name: string;
                     width, decimal: integer);
(* make the string from the real number, start putting characters in
at the start point. use width characters and decimal characters
after the decimal place *)
   (* note that the rounding operation to get the digits below zero
      must be done first.  then the digits above zero can be lopped off.
      this makes 99.99 come out correctly to 100.0 (to 1 decimal place)
      otherwise, 99.99 -> 0.99 -> 1.0 (rounded) -> 10 (print with 1 decimal
      place), and stringinteger won't be happy about that. *)
var
   abovezero: integer; (* the number shifted above the decimal place, to 
      'decimal' positions (and rounded) *)
   shift: integer; (* power of ten used to shift a number around
      relative to the decimal point *)
   sign: integer; (* the sign of the number *)
   thedecimal: integer; (* integer version of the decimal part of the number *)
   theupper: integer; (* integer version of the upper part of the number *)
begin
   if number < 0 then sign := -1
                 else sign := +1;

   number := abs(number); (* make positive *)

   (* the amount to shift the number above zero *)
   shift := round(exp(decimal*ln(10))); (* amount to move above zero *)
   abovezero := round(number*shift); (* move above zero, round off *)
   theupper := trunc(abovezero/shift);
   thedecimal := abovezero - shift*theupper;

   (* create the actual real number *)
   (* before decimal point *)
   stringinteger(sign*theupper,name,width-decimal-1,false);
   with name do begin (* put in the decimal point *)
      length := length + 1;
      letters[length] := '.';
   end;
   stringinteger(thedecimal,name,decimal,true); (* after decimal point *)
end;
(* end module pic.stringreal version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.picnumber *)
procedure picnumber(var afile: text;
                    dx, dy, number: real; width, decimal: integer;
                    centered: boolean);
(* Supply graphic commands for a 'number' whose center is at the relative point
(dx, dy) from the current point, 'width' characters wide and 'decimal'
characters beyond the decimal point.
If the width is zero, no number is produced.
procedure stringnumber(number: integer; start: integer; var name: string);
the location after the call is the same as before the call.
The string is optionally centered *)
var
   name: string; (* the string to pack the number into for shipping out *)
begin
   if width > 0 then begin
      mover(afile,dx,dy);

      clearstring(name);

      if decimal>0
      then stringreal(number,name,width,decimal)
      else stringinteger(round(number),name,width,false);

      graphstring(afile, name, centered);
      mover(afile,-dx,-dy);
   end
end;
(* end module pic.picnumber version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.xtic *)
procedure xtic(var afile: text; length, dx, dy, number: real;
               width, decimal: integer);
(* produce a tic mark for the x axis of "length" long.
Supply a number whose center is at the relative point (dx, dy)
from the end to the tick, 'width' characters wide and 'decimal'
characters beyond the decimal point.
If the width is zero, no number is produced.
the location after the call is the same as before the call. *)
begin
   liner(afile,0.0,-length);
   picnumber(afile,dx,dy,number,width,decimal,true);
   mover(afile,0.0,length);
end;
(* end module pic.xtic version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.ytic *)
procedure ytic(var afile: text; length, dx, dy, number: real;
               width, decimal: integer);
(* produce a tic mark for the y axis of "length" long.
Supply a number whose center is at the relative point (dx, dy)
from the end to the tick, 'width' characters wide and 'decimal'
characters beyond the decimal point.
If the width is zero, no number is produced.
the location after the call is the same as before the call. *)
begin
   liner(afile,-length,0.0);
   picnumber(afile,dx,dy,number,width,decimal,true);
   mover(afile,length,0.0);
end;
(* end module pic.ytic version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.xaxis *)
procedure xaxis(var afile: text;
         axlength,fromtic,interval,totic: real;
         length, dx, dy: real;
                width, decimal: integer);
(* draw an x axis starting from the current position.
the length of the xaxis is axlength.
the axis is labeled with numbers starting with fromtic
at intervals given up to totic.
the remaining variables describe the form of the tic marks as in xtic.
If the width is zero, no number is produced.
the location after the call is the same as before the call. *)
var
   jump: real; (* the space to move on the graph between tic marks *)
   jumpdistance: real; (* the total jumps made.  this may not be
      a simple function of the input variables since they may
      not work out to an exact number of jumps *)
   tic: real; (* the numerical value of the tic label *)
   totici2: real; (* an extra half interval guarantees the last tic is placed *)
begin
   liner(afile,axlength,0.0);
   mover(afile,-axlength,0.0);
   if totic = fromtic then begin
      writeln(output,'xaxis: fromtic and totic cannot be equal');
      halt;
   end;
   if (axlength = 0.0) or (interval = 0.0) then begin
      writeln(output,'xaxis: neither axlength nor interval can be zero');
      halt;
   end;
   jump := axlength * interval / (totic - fromtic);
   jumpdistance := 0;

   (* note!  the extra amount of interval/2 makes sure that the last tic
      is placed, if the computer does not add quite right and overshoots
      by a tich on the value of tic.  This much makes it surely work! *)
   tic := fromtic;
   if interval > 0.0 then begin
      totici2 := totic + interval/2.0;
      while tic <= totici2 do begin
         xtic(afile,length,dx,dy,tic,width,decimal);
         tic := tic + interval;
         if tic <= totici2 then begin
            mover(afile,jump,0.0);
            jumpdistance := jumpdistance + jump;
         end
      end
   end
   else if interval < 0.0 then begin
      totici2 := totic - interval/2.0;
      while tic >= totici2 do begin
         xtic(afile,length,dx,dy,tic,width,decimal);
         tic := tic + interval;
         if tic >= totici2 then begin
            mover(afile,jump,0.0);
            jumpdistance := jumpdistance + jump
         end
      end
   end;

   mover(afile,-jumpdistance,0.0)
end;
(* end module pic.xaxis version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.yaxis *)
procedure yaxis(var afile: text;
         aylength,fromtic,interval,totic: real;
         length, dx, dy: real;
                width, decimal: integer);
(* draw a y axis starting from the current position.
the length of the yaxis is aylength.
the axis is labeled with numbers starting with fromtic
at intervals given up to totic.
the remaining variables describe the form of the tic marks as in ytic.
If the width is zero, no number is produced.
the location after the call is the same as before the call. *)
var
   half: real; (* half the jump interval.  By adding this to the while loops,
                  we assure that the very last tic gets done, and isn't lost
                  due to roundoff *)
   jump: real; (* the space to move on the graph between tic marks *)
   jumpdistance: real; (* the total jumps made.  this may not be
      a simple function of the input variables since they may
      not work out to an exact number of jumps *)
   tic: real; (* the numerical value of the tic label *)
begin
   liner(afile,0.0,aylength);
   mover(afile,0.0,-aylength);
   if totic = fromtic then begin
      writeln(output,'yaxis: fromtic and totic cannot be equal');
      halt;
   end;
   if (aylength = 0.0) or (interval = 0.0) then begin
      writeln(output,'yaxis: neither aylength nor interval can be zero');
      halt;
   end;
   jump := aylength * interval / (totic - fromtic);
   jumpdistance := 0;
   half := interval / 2.0;

   tic := fromtic;
   if interval > 0.0 then while tic <= totic+half do begin
      ytic(afile,length,dx,dy,tic,width,decimal);
      tic := tic + interval;
      if tic <= totic+half then begin
         mover(afile,0.0,jump);
         jumpdistance := jumpdistance + jump
      end
   end
   else if interval < 0.0 then while tic >= totic-half do begin
      ytic(afile,length,dx,dy,tic,width,decimal);
      tic := tic + interval;
      if tic >= totic-half then begin
         mover(afile,0.0,jump);
         jumpdistance := jumpdistance + jump
      end
   end;

   mover(afile,0.0,-jumpdistance)
end;
(* end module pic.yaxis version = 2.59; (@ of dops.p 1990 December 5 *)
(* ********************************************************************** *)
(* end module pic.functions version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.degtorad *)
function degtorad(angle: real):real;
(* convert angle in degrees to radians *)
begin
   degtorad := (angle / 360) * 2 * pi 
end;
(* end module pic.degtorad version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.polrec *)
procedure polrec(r,theta: real; var x,y: real);
(* convert polar to rectangular coordinates,
theta is in radians *)
begin
   x := r*cos(theta);
   y := r*sin(theta)
end;
(* end module pic.polrec version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.arc *)
procedure arc(var thefile: text; angle1, angle2, radius: real;
              steps: integer);
(* create an arc in thefile going from angle1 to angle2 (degrees) in the
positive direction of angle, with the given radius.
use the given number of steps to make it.  return to the same position
as before the arc was drawn. *)
var
   dtheta: real; (* change in theta *)
(* s: integer; (@ index to the steps *)
   theta: real; (* current angle *)
   x,y: real; (* coordinates around starting point *)
(* zerox,zeroy: real; (@ starting location, center of curve *)
begin
(*
   zerox := picxglobal;
   zeroy := picyglobal;
*)
   theta := degtorad(angle1);
   dtheta := degtorad( abs(angle2-angle1)/steps );
   polrec(radius,theta,x,y);
(* can't do this for postscript arc:
   movea(thefile,zerox+x,zeroy+y);
      ' ',scale*zerox:  picfield:picwidth,
      ' ',scale*zeroy:  picfield:picwidth,
*)

   (* NONSTANDARD postscript, much faster *)
   writeln(thefile,
   (* 'stroke newpath', *)(* force there to be no current point *)
   (* ' 0 0', *)
      'a',
      ' ',scale*radius: picfield:picwidth,
      ' ',angle1:       picfield:picwidth,
      ' ',angle2:       picfield:picwidth);
   write(thefile, 'arc');
   if angle2 < angle1 then write(thefile,'n'); (* for negative draws *)
   (* origin move: writeln(thefile,' stroke newpath 0 0 moveto'); *)
   writeln(thefile,' n');
   (* the moveto puts us back to the origin *)

(*
   for s := 1 to steps do begin
      theta := theta + dtheta;
      polrec(radius,theta, x,y);
      linea(thefile,zerox+x,zeroy+y);
   end;

   movea(thefile,zerox,zeroy)
*)
end;
(* end module pic.arc version = 2.59; (@ of dops.p 1990 December 5 *)

(* begin module pic.circler *)
procedure circler(var afile: text; radius: real);
(* make a circle at the current position of some radius. *)
var
   steps: integer; (* number of steps to make the circle *)
begin
   (* number of segments increases with diameter, but the constant
      still should be a function of how good it looks on a particular
      graphic system, I'm afraid.  However, there should be a lower
      bound on the number of steps, so even small circles look good *)
   if radius < 1.0 then steps := 25
                   else steps := round(radius*25);

   arc(afile,0.0,360.0,radius,steps);
end;
(* end module pic.circler version = 2.59; (@ of dops.p 1990 December 5 *)

(* ********************************************************************** *)
  
(* begin module skipblanks *) 

(* 2003 July 31: tab is considered a blank character *)

function isblank(c: char): boolean;
(* is the character c blank or tab? *)
const
   tab = 9; (* tab character *)
begin
   isblank := (c = ' ') or (ord(c) = tab)
end;

procedure skipblanks(var thefile: text);
(* skip over blanks until a non-blank, or end of line, is found *)
begin
   while isblank(thefile^) and not eoln(thefile) do get(thefile);
end;

procedure skipnonblanks(var thefile: text);
(* skip over nonblanks until a blank, or end of line, is found *)
begin
   while (not isblank(thefile^)) and not eoln(thefile) do get(thefile);
end;

procedure skipcolumn(var thefile: text);
(* skip over a data column *)
begin
   skipblanks(thefile); skipnonblanks(thefile)
end;
(* end module skipblanks version = 5.31; (@ of prgmod.p 2006 Oct 10 *)
(* ********************************************************************** *)

{ 2007 Feb 9 Block original routine which fails because the eof test
fails (an empty afile is not detected as empty, then the read fails
because it is empty).
(* begin module rsgra.readwaveparameters *)
procedure readwaveparameters(var afile: text; var p: waveparam);
(* read from afile the parameters p, as defined by type param *)
begin
reset(afile);
      with p do begin
         if eof(afile)
         then wave := false
         else begin
            wave := true;
            readln(afile,extreme);
            readln(afile,wavelocation);
            readln(afile,wavebit);
            readln(afile,waveamplitude);
            readln(afile,wavelength);
         end
      end
end;
(* end module rsgra.readwaveparameters *)
}

(* begin module readwaveparameters *)
procedure readawaveparameter(var afile: text; var wp: waveparam);
{
                                 cmperbase, cmperbit: real);
}
(* Read one wave parameter from a file. *)
{
2000 Nov 1:  The following experimental code is not implemented:
See the coscurve program
(https://alum.mit.edu/www/toms/delila/coscurve.html) for details
about the conversion from bases to cm.  The code now requires
cm per base and cmperbit.
const
   lover2pi = 1.2160067233; (* L/2pi for L being the length of
       the cosine function from 0 to 2pi *)
var
   multiplier: real; (* factor to multiply by to convert from bases to cm. *)
}
begin
   readln(afile,wp.extreme);
   readln(afile,wp.wavelocation);
   readln(afile,wp.wavebit);
   readln(afile,wp.waveamplitude);
   readln(afile,wp.wavelength);
   if wp.wavelength <= 0.0 then begin
      writeln(output,'wave parameters: wavelength must be positive');
      halt
   end;
   (* one may skip the dash and thickness parameters
      of the last wave definition.  This allows
      backwards compatability, but should not be
      used in general. *)
   if eof(afile)
   then begin
      wp.dashon := 0;
      wp.dashoff := 0;
      wp.dashoffset := 0;
      wp.thickness := 0
   end
   else begin
      (* implement new method to read dash information
         without breaking previous wave parameter files *)
      if afile^ <> 'd'
      then begin (* default to old method *)
         readln(afile,wp.dashon);
         wp.dashoff := wp.dashon;
         wp.dashoffset := 0;
      end
      else begin
         get(afile); (* skip the 'd' *)
         (* reading the parameters directly in cm would require
            that the user compute or guess the values.
            So read in as bases and compute cm.
            Unfortunately the length of a curve is not the simple
            scale factor.  Make the user guess for now.
         *)
         readln(afile,wp.dashon, wp.dashoff, wp.dashoffset)

{
experimental code to account for actual wave length:
         readln(afile, wp.BASEdashon, wp.BASEdashoff, wp.BASEdashoffset);
         multiplier    := 1;
         multiplier    := lover2pi
 * wp.waveamplitude (* in bits *)
 * cmperbit (* converts to cm *)
 / wp.wavelength (* 10.6 bp/turn *)
* cmperbase
;
multiplier    := lover2pi;
multiplier    := 1.0;
multiplier    := 1.0/lover2pi;
         multiplier := lover2pi
                       * wp.waveamplitude (* in bits *)
                       * cmperbit (* converts to cm *)
                       * wp.wavelength (* 10.6 bp/turn *)
                       * cmperbase;
         multiplier := 1.0
         wp.dashon     := multiplier * wp.BASEdashon;
         wp.dashoff    := multiplier * wp.BASEdashoff;
         wp.dashoffset := multiplier * wp.BASEdashoffset;
with wp do begin
   writeln(output, 'cmperbase  = ',cmperbase:5:2);
   writeln(output, 'cmperbit   = ',cmperbit:5:2);
   writeln(output, 'dashon     = ',dashon:5:2);
   writeln(output, 'dashoff    = ',dashoff:5:2);
   writeln(output, 'dashoffset = ',dashoffset:5:2);
   writeln(output, 'waveamplitude   = ',wp.waveamplitude:5:2);
   writeln(output, 'wavelength      = ',wp.wavelength:5:2);
end;
}

      end;
      if eof(afile)
      then wp.thickness := 0
      else readln(afile,wp.thickness);
   end;
end;

procedure readwaveparameters(var afile: text; var w: waveptr);
{
                                 cmperbase, cmperbit: real);
}
(* read from afile the wave parameters w.  The routine is done either at end
of file, when a period ('.') appears or at a blank line.  This makes it
compatable with different reading mechanisms. *)
var
   done: boolean; (* done reading *)
   morethanone: boolean; (* more than one wave? *)
   p: waveptr; (* one of the definitions of a cosine wave *)
procedure waystoend;
(* if end of file, a blank line or a period, consider the job done *)
var
   clear: boolean; (* not eof, so clear to test for comment *)
begin
   if eof(afile) then done := true;
   clear := false;
   if not done (* skip any number of comments *)
   then while (not clear) and (not done) do begin
      if eof(afile) then begin
         done := true;
         clear := true;
      end
      else if afile^ = '*'
           then readln(afile)
           else clear := true
   end;
   if not done then if eoln(afile) then done := true;
   if not done then if afile^='.' then begin
      readln(afile);
      done := true;
   end;
end;
begin
   done := false;
   morethanone := false;
   w := nil;
   waystoend;
   if not done then begin
      new(w);
      p := w;
      while not done do begin
         waystoend;
         if done
         then p^.next := nil
         else begin
            if afile^='*'
            then readln(afile) (* skip any number of comments *)
            else begin
               if morethanone then begin
                  new(p^.next);
                  p := p^.next
               end;
{
               readawaveparameter(afile, p^, cmperbase, cmperbit);
}
               readawaveparameter(afile, p^);
               (* if we ever come back again there will be more than one: *)
               morethanone := true;
            end
         end
      end
   end
   else w := nil;
end;
(* end module readwaveparameters version = 5.31; (@ of prgmod.p 2006 Oct 10 *)

(* begin module rsgra.header *)
procedure header(var infile, marks, outfile: text);
(* do the header of the plot *)
var
      index: integer; (* to lines in the infile *) 
begin
      reset(infile);
      reset(marks);
      rewrite(outfile);

(*
      writeln(outfile,'.nf');
      writeln(outfile,'% rsgra ',version:4:2);
*)

      writeln(outfile, '%!PS-Adobe-2.0 EPSF-2.0');

{
writeln(outfile, '%%Orientation: Portrait'); (* 2021 aug 03 zzzqqq *)
writeln(outfile, '%%Orientation: Landscape'); (* 2021 aug 03 zzzqqq no help *)
}

      writeln(outfile, '%%Title: rsgra ',version:4:2);
      writeln(outfile, '%%Creator: Tom Schneider');
      writeln(outfile, '%%BoundingBox:    0   0   630 900');
      writeln(outfile, '%%DocumentFonts:');
      writeln(outfile, '%%EndComments');
      writeln(outfile, '%%EndProlog');

{zzzqqq
https://community.adobe.com/t5/postscript/how-do-i-set-page-orientation-with-postscript-when-the-resulting-pdf-is-viewed-in-adobe-acrobat/m-p/3519467
http://pages.cs.wisc.edu/~rjl/landscape/
This does nothing:
      writeln(outfile, '<</Orientation 3>> setpagedevice');
}

      (* copy header lines to outfile *)
      for index := 1 to 3 do begin
         write(outfile,'% ');
         copyaline(infile,outfile);
      end;

      writeln(outfile);

      (* identify the marks *)
      writeln(outfile,'% MARKS: ');
      while not eof(marks) do begin
         write(outfile,'% ');
         copyaline(marks,outfile);
      end;

      reset(marks);
end;
(* end module rsgra.header *)

(* begin module rsgra.cosine *)
procedure cosine(var afile: text;
                 amplitude, phase, wavelength, base,
                 xmin,ymin,xmax,ymax: real);
(* draw a cosine function down the page:
         x := amplitude*cos( ((-y-phase)/wavelength)*2*pi) + base,
where the given parameters are in inches.
parts outside the box defined by xmin,ymin,xmax,ymax are not drawn.
pi is defined in pic.const *)
const
      step = 0.01; (* step size for the lines drawn *)
var
      inside1: boolean; (* point 1 was inside the bounds *)
      inside2: boolean; (* point 2 was inside the bounds *)
      x1,y1,x2,y2: real; (* internal coordinates *)
function c(y: real): real;
(* do the actual calculation of the cosine given y *)
begin
      c := amplitude*cos( ((-y-phase)/wavelength)*2*pi) + base
end;
function between(a,b,c: real):boolean;
(* is b between a and c? *)
begin between:=(a<=b) and (b<=c) end;
begin
      (* start graphing at the top of the plot *)
      y1 := ymax;
      x1 := c(y1);
      inside1 := between(ymin,y1,ymax) and between(xmin,x1,xmax);

      while y1 >= ymin do begin
         x2 := x1;
         y2 := y1;
         inside2 := inside1;

         y1 := y1 - step;
         x1 := c(y1);
         inside1 := between(ymin,y1,ymax) and between(xmin,x1,xmax);

         if inside1 and inside2 then begin
               movea(afile,x1,y1);
               linea(afile,x2,y2);
         end
      end
end;
(* end module rsgra.cosine *)

(* ********************************************************************** *)
(* ********************************************************************** *)

(* begin module rs.readrsrange *)
procedure readrsrange(var rsdata: text; var r: rstype);
(* read the range data from rsdata to r.  data is assumed to
be the rsdata file from program rseq. *)
var
   index: integer; (* for counting lines of rsdata *)
   skip: char; (* a character to skip the '*' on the line *)
begin
      for index := 1 to 11 do readln(rsdata);
      readln(rsdata, skip, r.rstart, r.rstop);
    (*  writeln(output, 'range: ',r.rstart:1,' ',r.rstop:1);*)
end;
(* end module rs.readrsrange *)
  
(* begin module rs.getrsbegin *)
procedure getrsbegin(var infile: text);
(* skip to the beginning of the data in a data file from
the rseq program *)
var
      ch: char; (* a character read from infile *)  
      begindata: trigger; (* a trigger to locate the beginning of the data *)
begin
(*                                       1         2  *) 
(*                              123456789012345678901 *)
filltrigger(begindata         ,'l    a    c    g    t');   
      resettrigger(begindata);  

      reset(infile);
      while not begindata.found do begin
         if eoln(infile) then readln(infile);
         if eof(infile) then begin
            writeln(output,'beginning of data not found');
            halt;
         end;
         read(infile,ch);
         testfortrigger(ch,begindata); 
      end;
      readln(infile);
end;
(* end module rs.getrsbegin *)

(* begin module rs.readrsdata *)
procedure readrsdata(var rsdata: text; var rdata: rstype);
(* read data from the data file of program rseq into the datatype *)
begin
   with rdata do begin
      read(rsdata,l,nal,ncl,ngl,ntl,rsl,rs,varhnb,sumvar,nl,ehnb);
      (* skip spaces to find the flag: *)
      while rsdata^=' ' do get(rsdata);
      readln(rsdata,flag);
(* writeln(output,'readrsdata: l a c g t flag = ',
l:1,' ',nal:1,' ',ncl:1,' ',ngl:1,' ',ntl:1,' ',flag); *)
   end
end;
(* end module rs.readrsdata *)
  
(* ********************************************************************** *)
(* ********************************************************************** *)

(* begin module themain *)
procedure themain(var infile, rsgrap, wave, marks, outfile: text); 
(* the main procedure of the program *) 
const
   duplicates = 2;  (* number of times to draw the main line *)
   halfdatawidth = 2.4; (* inches to shift the data to the left
      from the left edge of the graph.
      the lowpoint is added to this to find the dataposition *)
   leftbound = 3.0; (* distance of the left bound of the graph
      from the left side of the paper, (not including the data) *)
   spacing = 0.01;  (* spacing between the multiple drawings of
                       the main line *)
   xscale = 2.00;   (* inches per bit across the plot *)
   yscale = 0.1595;   (* vertical spacing between graph lines,
      ie, inches per base *)
      (* 0.15 inches worked nicely for a while, until I wanted the graph
         to match the standard printer output (unix pr).
         There were 39 bases in the range, and they should have been
         2.3 bases longer, so the new yscale is 0.15 * (39+2.3)/39. *)
var 
   bar: real; (* the size of an error bar *)
   barposition: integer; (* position after which to write a bar *)
   basetomark: integer; (* the base to be marked according to the marks file *)
   dataposition: real; (* amount to shift the data to the left from the
      zero of the graph *)
   dx,dy: real; (* changes of x and y to make multiple lines *)
   frombase, tobase: integer; (* range of the graph to display *)
   line: integer; (* an index to the multiple lines drawn *)
   linedx,linedy: real; (* line * dx or dy, the actual jump made for 
      multiple lines (done for speed) *)
   lowpoint: real; (* the lowest point in the entire plot *)
   notfirstpoint: boolean; (* true for all points except
      the first point plotted *)
{
   params: waveparam; (* parameters to control the program *)
}
   params: waveptr; (* wave parameters to control the program *)
   point: real; (* a low point in the graph *)
   position: integer; (* a location in the aligned sequence *)
   rdata: rstype; (* data from rseq *)
   xmin,ymin,xmax,ymax: real; (* the bounds of the graph, in inches *)
   x1,y1: real; (* the current end of the curve *)
   x2,y2: real; (* the previous end of the curve *)
   ybar: real; (* location of the bar in graphics coordinates *)

(* zzzqqq *)
{
duplines: integer; (* rotation problem: index for invisible lines *)
}

begin
      writeln(output,'rsgra ',version:4:2);

      (* copy header stuff *)
      header(infile,marks,outfile);

      reset(wave);
      readwaveparameters(wave,params);

      reset(rsgrap);
      if eof(rsgrap) then begin
         frombase := -maxint;
         tobase := maxint;
      end
      else readln(rsgrap,frombase,tobase);

      (* now read the rest of the parameters *)
      if eof(marks)
      then begin
         barposition := -maxint (* don't do the bar *)
      end
      else begin
         if marks^ = 'b' then begin
            get(marks); (* skip the 'b' *)
            readln(marks,barposition);
            writeln(output,'bar placed at position ',barposition:1);
         end;
      end;

      (* get the first base to mark *)
      if not eof(marks) then readln(marks,basetomark)
                        else basetomark := -maxint;

      (* find the range of the graph in bases *)
      reset(infile);
      readrsrange(infile,rdata);
{
      if params.wave
}
      if params <> nil
      then writeln(outfile,'% cosine wavelength is ',
                            params^.wavelength:4:2, ' bases');
      with rdata do begin
         if frombase < rstart then frombase := rstart;
         if tobase > rstop then tobase := rstop;
      end;

      (* find lowest bound of the graph, in bits *)
      getrsbegin(infile);
      lowpoint := maxint;
      with rdata do for position := rstart to rstop do begin
         (* skip lines with an '*' *)
         if infile^ <> '*' then begin
            readrsdata(infile,rdata);
            point := rsl - sqrt(varhnb);
            if point < lowpoint then lowpoint := point
         end
         else readln(infile)
      end;
      (* make lowpoint be just below zero, or the next tenth of
         a bit lower: *)
      if lowpoint >= 0.0 then lowpoint := -0.1
                         else lowpoint := -trunc( (-10*lowpoint) + 1)/10;
      dataposition := -lowpoint*xscale + halfdatawidth;
      (* dataposition: vertical position of the table *)

      (* define the bounds of the graph region, in inches *)
      with rdata do begin
         xmin := lowpoint * xscale;
         xmax := 2.0 * xscale;
         ymin := -(tobase-frombase+2)*yscale;
         ymax := 0.0;
      end;

{
      startpic(outfile,defscale,0.7,5.0,'c'); (* up to 5.08 2016 jul 22 *)
}
      (*                          x   y    *)
      startpic(outfile,defscale,1.0,5.0,'c'); (* 5.10 2021 aug 13 *)

(* 2021 Aug 13 rotation problem
This chunk of text forces ghostscript to respect
the demanded orientation of text and NOT to switch it
from landscape to portrait in powerdot.
It does so by creating MANY lines in the orientation I desire.
Right, a crappy solution to a braindead ghostscript "feature".
Yes, ghostscript fails to respect gsave and grestore!!!  Idiots. *)
(* zzzqqq *)
(* 2021 Aug 27: code removed because new latex and this solved it:
ps2pdf -dALLOWPSTRANSPARENCY rot.ps
writeln(outfile,'gsave');
writeln(outfile,'newpath 0 0 moveto % Start path');
writeln(outfile,'32 0 moveto currentpoint translate');
writeln(outfile,'2 2 scale');
writeln(outfile,'% create an invisible horizontal text that forces ghostscript');
writeln(outfile,'% to NOT ROTATE the FUBAR page!');
writeln(outfile,'%1 0 0 setrgbcolor % red for testing'); 
writeln(outfile,' 1 1 1 setrgbcolor % white => invisible!!!!!!');
writeln(outfile,'(This line will be invisible.) show');
{for duplines := 1 to 100 do begin}
{for duplines := -frombase to tobase do begin}
for duplines := 1 to 1000 do begin
   writeln(outfile,'gsave (',duplines:1,
                   ' 100 This line will be invisible.',
                   ' It should be as wide as the table.',
                   ') show grestore');
end;
writeln(outfile,'90 rotate');
writeln(outfile,'0 0 1 setrgbcolor % blue');
{
writeln(outfile,'(This line should run vertically.) show ');
}
writeln(outfile,'grestore');
*)

writeln(outfile,'90 rotate'); (* rotate the table below the graph *)

      (* the coordinate (0,0) is the upper left hand corner of the graph,
         not including the data.  First create the location of the data: *)
      movea(outfile,-leftbound,0.0);
      movea(outfile,-dataposition,0.0);

      writeln(outfile,'gsave 1 2 scale');
      writeln(outfile,'(   l    a    c    g    t  Rs(l)) x');
      writeln(outfile,'grestore');

      (* placement of the label in bits along the Y axis: *)
      movea(outfile,0.25*xscale,3*yscale);
      writeln(outfile,'gsave 1 2 scale');
      writeln(outfile,'(Rs(l) = Rsequence(l), Information in bits) x');
      writeln(outfile,'grestore');

      (* placement of the label in bits along the X axis: *)
      movea(outfile,
{
            -1.60*xscale,
2021 Aug 27: adjust location of the 'Position L' label.
            -2.20*xscale,
this failed - way too low!  Undo and adjust.
            -1.35*xscale,
}
            -1.40*xscale,
            (
            (frombase-tobase)/2 (* the center *)
            + 8.5 (* adjust for size of the words, in cm *)
            )*yscale
            );
      writeln(outfile,'gsave 2 2 scale');
      writeln(outfile,'-90 rotate');
      writeln(outfile,'(Position L (in bases)) x');
      writeln(outfile,'grestore');

      (* now put the book id onto the graph *)
      (* x coordinate was xscale-dataposition,
         but this way more title will fit on: *)

      (* 2016 Jul 22 -0.2 moves the book title to the right *)
      movea(outfile,(-2*dataposition)/2,4*yscale-0.2);
      write(outfile,'(');

      write(outfile,'rsgra ',version:4:2,' ');
      reset(infile); readln(infile); readln(infile);
      while not eoln(infile) do begin (* like copyaline but no writeln *)
         outfile^ := infile^;   put(outfile);   get(infile)
      end;
      writeln(outfile,') x % NOHEADER FOR PACKAGING INTO ANOTHER FIGURE');

      (* create upper x axis *)
      movea(outfile,0.0,0.0);
      xaxis(outfile, xmax, 0.0,1.0,2.0, -0.1,0.0,+0.1, 3,1);
      (* more ticks: *)
      mover(outfile,lowpoint*xscale,0.0);
      xaxis(outfile, (2.0-lowpoint)*xscale,
            lowpoint,0.1,2.0, -0.1,0.0,+0.0, 0,1);

      (* dashed line down the plot: *)
      movea(outfile,0.0,0.0);
      drawa(outfile,0.0,ymin,'-',0.03);

      (* create lower x axis *)
      xaxis(outfile, xmax, 0.0,1.0,2.0, +0.1,0.0,-0.1, 0,1);
      (* more ticks: *)
      mover(outfile,xmin,0.0);
      xaxis(outfile, (2.0-lowpoint)*xscale,
            lowpoint,0.1,2.0, +0.1,0.0,-0.0, 0,1);
      (* xaxis(outfile, axlength,fromtic,interval,totic: real;
              length, dx, dy: real;   width, decimal: integer); *)

      notfirstpoint := false; (* about to draw the first point, so there is
         no previous point *)

      (* prepare for reading the data for graphing *)
      getrsbegin(infile);

      (* create the curve of rsequence(l) *)
      for position := rdata.rstart to rdata.rstop do begin
         (* skip lines with an '*' *)
         if infile^ <> '*' then begin
            readrsdata(infile,rdata);

            if (position >= frombase) and (position <= tobase)
            then with rdata do begin

               (* move to the next data line and draw a bar *)
               if position = barposition then begin
                  (* the factor multiplying dataposition determines the
                     left end of the bar.  It is made to fit nicely. *)
                  (* the extra 0.5 makes sure that the position is between
                     bases, so it won't overwrite the marks *)
                  ybar := (frombase-position-1.5)*yscale;
(*
                  movea(outfile,-1.60*dataposition,ybar);
*)
                  movea(outfile,-dataposition,ybar);
                  linea(outfile, 2*xscale, ybar)
               end;

               (* move to the next data line and give the data *)
	       (* the extra amount added to the position places the numbers in
	          a better position *)
               movea(outfile,-dataposition,-(position-frombase+1.2)*yscale);
               writeln(outfile,'(',l:nfield,
                                   ' ',nal:nfield,' ',ncl:nfield,
                                   ' ',ngl:nfield,' ',ntl:nfield,
                                   rsl:7:2,') x');

               if notfirstpoint then begin
                  (* multiple draw line from previous point *)
                  x2 := x1;                (* set (x2,y2) to previous point *)
                  y2 := y1;
                  x1 := rsl*xscale;        (* update (x1,y1) *)
                  y1 := (frombase-position-1)*yscale;
   
                  (* the slope of the line from (x2,y2) to (x1,y1) is
                     m := (y1-y2)/(x1-x2);
                     the multiple lines must be drawn perpendicular to this,
                     m' := -1/m;
                     the arctan of this gives the angle to move the lines in.
                     conversion from polar to rectangular coordinates
                     completes the job.  note that y2-y1 will never be zero. *)
                  polrec(spacing,arctan((x1-x2)/(y2-y1)),dx,dy);
   
                  for line := -duplicates to duplicates do begin
                     linedx := line*dx;
                     linedy := line*dy;
                     movea(outfile, x2+linedx, y2+linedy);
                     linea(outfile, x1+linedx, y1+linedy)
                  end
               end
               else begin (* the first point does not have a line drawn to it *)
                  x1 := rsl*xscale;
                  y1 := (frombase-position-1)*yscale;
                  notfirstpoint := true;
               end;
   
               (* create error bar and circle *)
               (* move back to the location of the new data point: *)
               movea(outfile, x1, y1);
               bar := sqrt(varhnb)*xscale;
               mover(outfile,+bar,0.0);
               liner(outfile,-bar,0.0);
               circler(outfile,yscale/3);
               liner(outfile,-bar,0.0);

               (* create specially marked bases *)
               if basetomark = l then begin
                  (*movea(outfile,lowpoint+xscale*0.025,*)
                  movea(outfile,lowpoint,
                                -(position-frombase+1)*yscale);
                  circler(outfile,yscale/3);
                  writeln(output,'marking base ',l:1);
                  if not eof(marks) then readln(marks,basetomark)
                                    else basetomark := -maxint;
               end
            end
         end
         else readln(infile)
      end;
   
      (* overlay a cosine wave as specified by the parameters *)
      with rdata do begin
{
         if params.wave then with params do begin
}
         if params <> nil then with params^ do begin
            if extreme = 'h'
            then cosine(outfile,           (* high center point cosine wave *)
                    +xscale*waveamplitude,
                    yscale*(wavelocation-frombase+1), yscale*wavelength,
                    xscale*(wavebit-waveamplitude),
                    xmin,ymin,xmax,ymax)
            else cosine(outfile,           (* low center point cosine wave *)
                    -xscale*waveamplitude,
                    yscale*(wavelocation-frombase+1), yscale*wavelength,
                    xscale*(wavebit+waveamplitude),
                    xmin,ymin,xmax,ymax);
         end;
      end;
      stoppic(outfile);
end;
(* end module themain *)
 
begin
      themain(rsdata, rsgrap, wave, marks, picture);
1: end.
