Software Tools for Line Drawings

by Donald Daniel, 2018

Contents

up one level

Introduction

Line drawings today are usually done on a computer using a software package that already exists. This article is for people who would prefer to write a new program to draw a particular thing. If the program reads an input data file with dimensions and proportions of what they wish to draw, it will be easy to re-draw it with different dimensions and proportions.

The following set of line drawings was produced by a single program that used the software tools presented in this article.

file1.png

file2.png

file3.png

file4.png

file5.png

file6.png

Source code for a drawing program is provided at the end of this article. You will have to add source code to the program to draw whatever you want to draw. You will be able to see your drawing on the computer screen, to print your drawing at small scale on your printer, or to scale up your drawing to large scale, put it on a USB stick, and have a shop with a very large printer print it at large size. Such a shop might be called a blueprint shop, a reproduction shop or a graphics shop. You will be able to put your drawing in a web site.

Before the age of computers line drawings of machine parts, clothing patterns, etc. were drawn by hand by draftsmen using a straight edge to draw straight lines and a compass to draw arcs. The draftsmen knew tricks to enable them to draw smooth curves that were far from simple arcs by carefully joining lines and arcs. The software tools presented here will enable you to draw such curves without learning the draftsman's tricks. The example below shows a smooth curve from point 1 to point 2 made of two arcs and a straight line.

sline.png

The drawings will start by positioning coordinates that establish the dimensions and proportions of what is to be drawn. Then the coordinates will be connected by drawing lines and smooth curves to draw the desired object.

The source code of a basic drawing program including the software tools is presented. It is written in the Oberon-2 language and runs on the Debian linux operating system. If you wish to use a different programming language and operating system you will have to do the work of translating it from Oberon-2 to the language of your choice. Whether you wish to use this program as it is or translate it to another language you will need to refer to the "how to program a computer" article found elsewhere at this website. The article explains Oberon-2.

Computers can draw exotic mathematical curves. But practical drawing of objects long ago and in the present does not need such curves. Lines and arcs are sufficient.

Most computers now have available software called Postscript or a Postscript clone called Ghostscript. This software allows the drawing of lines and arcs and alphanumeric characters. It does not include the advanced tricks of the draftsman. The drawing program presented here draws lines, arcs and characters using Ghostscript but adds tools to make the job easier.

Tools for Coordinates

From now on the source code of the program will be discussed. You will have to look at the source code. Portions of the source code enclosed between (* and *) are comments for you to read, and do not affect the execution of the program. Also, parts of the program that are not being used in the present configuration of the program are commented out. This is so that when the program is compiled the compiler will not warn that these parts are not being used. If these parts of the program are later to be used, they will have to be uncommented. Commenting and uncommenting to get clean compilations will be a significant part of the effort required to use this program.

In the beginning of the program are global declarations. In the TYPE section of the declarations you can see the form of the coordinates, abreviated as "coord". In the VAR section you can see that "cd" is an array of "coord". Below the declarations find "PROCEDURE setcoord" which serves to place a coordinate where you want it on the drawing. Immediately below that is the procedure "moveto". The first parameter "crd" is the number of the coordinate you will move to. The second parameter "md" chooses whether you will draw a straight line from your present position to the coordinate or move without drawing a line. Its possible values are "move" or "draw".

The next procedure is "angle", which measures the angle of c2 from c1. Only positive angles are reported. In most subsequent procedures that use angles only positive angles are acceptable. The angles must be in radians, not degrees. If you want to convert degrees to radians multiply the degrees by the constant "degtorad". If you want to convert radians to degrees multiply the radians by "radtodeg". If the procedure "angle" seems a bit complicated, it is because I wanted to avoid possible numerical difficulties by not using the funcion "arctan" beyond 45 degrees.

The next procedure "distance" uses the Pythagorean theorem to calculate the distance between two coordinates.

The procedure "newcoord" allows you to position a second coordinate at an angle and distance from the first coordinate.

The procedure "midpoint" allows you to position a third coordinate a specified fraction of the way along a straight line between two coordinates.

We will ignore the procedure "mirror" because it is used by other procedures, and we will not normally use it ourselves.

The procedure "rotate" finds a new coordinate "nc" that is a result of rotating "oc" about center point "ctr".

The procedure "intersect" finds the intersection of two lines defined by four coordinates. Rather than using the usual method that requires a determinant, I used a trigonometric method. Note that if p12 is expected to be beyond the intersection the fraction "f" needs to be chosen enough less than 1 to provide a subtitute for p12 that will not be beyond the intersection.

The procedure "tangentarc" requires some explanation. We start with coordinates p1, p2 and "ofst". If an arc starts at ofst at angle ang, it will be tangent to a line p1-p2 only if the radius is chosen properly. Tangentarc finds the point on line p1-p2 where an arc with the right radius would be tangent to p1-p2.

The procedure "circle" is primarily to draw small circles at coordinate locations to make the locations visible in the drawing. But circle could be used to draw a large circle if you need one.

Now skip down to procedure "ntrsctarc". It finds the intersection of a line with an arc. "ntrsct2arc" finds the intersection of two overlapping circles.

Procedure "reflect" uses line o1,o2 like a door hinge to fold line c1,c2 over o1,o2 to create line r1,r2.

Procedure "shiftright" shifts the coordinates of a line to form coordinates of a new line.

Procedure "tangentline" finds a point on a circle where a line from coordinate "ofst" outside of the circle would be tangent to the circle.

This is the end of the list of procedures that place or find coordinates.

Tools for Curve Drawing

Below will be different kinds of curves drawn in the procedure "test". The first part of test is the same for all examples. Only the code that computed each example will be reproduced here just before the curve is shown. In each example the procedure "label" is called. It prints the number of the coordinate. The second parameter of label indicates where the number will be printed in relation to the location of the coordinate. The choices are 0 through 7. O prints the number to the right of the coordinate. Each higher number represents locations in 45 degree increments ccw around the coordinate.

The procedure "arc" draws an arc through two or three of the points c1,c2,c3. Read the comment paragraph in the procedure for more details. Below is an example.

setcoord(1,11,1); setcoord(2,5,3); setcoord(3,1,1);
label(1,6);label(2,6);label(3,6);
arc(1,2,3,13,draw);

arc.png

The procedure "arcang" needs only two coordinates to draw an arc. The arc leaves c1 at angle ang1 and ends at c2:

setcoord(1,11,1); setcoord(2,1,1); 
label(1,6);label(2,6);
arcang(140*degtorad,1,2,draw);

arcang.png

The procedure "arcline" draws an arc starting at c1 at an angle ang1. At a fraction "frac" of the distance from c1 to c2 the arc ends and a line goes the rest of the way to c2. Since both ang1 and frac can be changed, this procedure can produce a wide variety of curves. In the example below frac is 0.5. If frac were 0.01 the arc would disappear and the result would be a line from c1 to c2. If frac were 0.99 the result would be like the arcang example above.

setcoord(1,11,1); setcoord(2,1,1);
label(1,6);label(2,6);
arcline(140*degtorad,0.5,1,2,draw);

arcline.png

The procedure "midarc" draws a smooth curve through three points. The curve consists of two lines and an arc. The fraction "f" determines the size of the arc. If "f" is 0.01 the arc has almost zero radius and dissapears. If "f" is 0.99 the arc is as large as it can be and almost reaches the outer point closest to the center point. One of the lines dissapears. In the example f is 0.7.

setcoord(1,11,1); setcoord(2,5,3);setcoord(3,1,1);
label(1,6);label(2,6);label(3,6);
midarc(1,2,3,draw,0.7);

midarc.png

The procedure "sline" makes "S" shaped curves. It connects two points with a smooth curve consisting of two arcs and a line. The angle that each arc leaves its point and the radius of each arc is adjustable, giving lots of freedom in shaping the curve.

setcoord(1,0,7);setcoord(2,10,10);
label(1,4);label(2,0);
sline(1,2,FALSE,3,330*degtorad,5,180*degtorad);

sline.png

Since all of the above curve procedures leave you with knowledge of the angle of the ends of the curve, you can smoothly extend the curve.

Postscript provides another curve besides arcs, namely Bezier curves. In my experience you can waist a lot of time getting a Bezier curve to do what you want. I strongly recommend not using Bezier curves. With the curves provided here you should get a good enough curve with less waisted time. Bezier curves will change in a less predictable manner if you change the proportions of your drawing by changing the input data file.

Scale and Origin of the Plot

Any x,y coordinate graphic system must have an origin and scale. The origin of the postscript graphics is the lower left corner of the page. The scale is such that 72 postscript units represents one inch on the page. But that scale and origin is not likely to represent the requirements of your project. The scale and origin used in this program is what I needed for my last project. You can change the scale and origin to match the requirements of your project. In my project I was using an example in a book. In the example the origin was the upper left of the figure. The units were inches. The figure would be much larger than printed in the book. It would have to be printed on a large printer at a graphics shop. But I also needed to print the figure at small size so I could print it on my computer printer.

In the global declarations at the beginning of the program you can find xcorg and ycorg representing the origins for small scale printing, and xcorgl and ycorgl representing the origins for large scale printing. At the beginning of the procedure "getdata" the user chooses between large scale and small scale. At that point the value of the variable "scale" is set according to the choice made.

It is possible to use the original Postscript scale and origin. For this example we will use the full scale choice, but we could also have used the small scale choice. Set xcorgl=0; ycorgl=0;. Then in getdata set scale for the large choice to 1.0. Then in procedure test put this example:

setcoord(1,72,144);setcoord(2,306,648);
setcoord(3,540,144);
moveto(1,move);moveto(2,draw);moveto(3,draw);
moveto(1,draw);

Then use the program and choose the large scale. Draw the test example. You will get a large triangle that can be printed on 8.5 by 11 paper using the original Postscript origin and scale. You can use the vi editor to examine the file "temp1" and see the Postscript file that is plotted.

When through with this example be sure to set origin and scale values back to what they were because the examples depend on it.

Your printer will probably not allow you to print closer to the edge of the paper than a quarter inch, even though the Postscript coordinate system goes all the way to the edge of the paper.

Compiling and Running the Program

It is assumed that you have read the article "how to program a computer" at this website at least up to the part about how to compile and run a program. Also you should have read the articles at this website "how to use the linux terminal" and "how to use the vi editor in linux".

You could use the vi editor to write the program in this html article as a text file. But text is different from html. Some of the lines in the text file will have the & character. These lines will have to be changed. When viewing these lines in the text file with the vi editor the lines should look the same as when viewing the html version in your browser. Once these lines have been fixed, if the program is moved to the "src" directory, the program can be compiled with the commmand "oocm curve". This command will be executed from the directory "prog" above the directory "src".

The input data file required to run the program is called "lidfile". It must be in the directory "prog". It is listed below:

"john smith"
16 wide 
14 high

In the trivial example we are using only two numbers are needed in the data file. If we were drawing something more complicated several numbers might be required.

The name "john smith" may not be needed for your application. Its purpose is if you wish to use the same program to send different drawings to different people where each person sends you a different data file to use in the program.

An example of running the program is given below:


--->bin/curve
enter 1 for small scale, 2 for full scale
--->1
enter 1 for plain, 2 for inlays and turnings
--->2
enter filename
--->lidfile
 16.00 wide
 14.00 high
draw, prtcrd, dist, ang, print, batch, q
enter command
--->draw
lid
 border1, test, q
enter command
--->lid
enter command
--->border1
enter command
--->q
press enter to view plot, then 
enter quit to finish viewing plot

quit
draw, prtcrd, dist, ang, print, batch, q
enter command
--->q

In the example the lines that start with ---> were entered by the user. The rest were responses by the program. The plot would be displayed on the screen during this process. The plot is a trivial example of a plot, the lid for a box. The dashed lines at the edge of the lid are the parts of the lid that are to be folded down over the edge of the box. The folded parts are referred to as inlays in the program. A large rectangular border is drawn around the lid. The purpose of this border is for use when the picture is drawn at large scale to be plotted on a large plotter at a graphics shop. The large plotter requires a rectangular border so it can automatically cut the paper about a half inch beyond the border. Large plotters are good, but not perfect. If your border is 50 inches long, the error may be a half inch.

The program only draws one item, a lid. Your program may need to draw several items. The drawings are normally written in ps format, or Postscript format. But if you select the command batch, they will be drawn in pdf format. The large plotters in graphics shops require pdf format.

The procedure batchproc in the program can be expanded to draw all the different items you wish to draw.

If you chose to draw "test", then the example in the procedure testproc would be displayed. When the program is finished, the drawing is in ps format in the file "temp1". If you use the command "gv temp1" you can see it. But if you want to put it in a website you will need to convert it to png format. In ps format the figure is drawn on a large sheet of paper even if you have not yet printed it. First you need to reduce it to only the size of the figure by converting it to encapsulated Postscript format. Do this by the command "ps2eps temp1". This will produce "temp1.eps". To convert it to png use the script "xform2ps". The command "./xform2ps temp1.eps" will produce "temp1.eps.png" which is what you want. You can see it with the command "display temp1.eps.png" provided you have the imagemagick package installed. The script xform2ps is listed below:

# convert ps to png 
namechange ()
{
oldname=$1
newname=${oldname%.ps}.png
echo "$newname"
pstopnm -stdout "$oldname" \
| pnmscale 0.3 | pnmflip -cw | pnmtopng > "$newname"
}
for filename in "$@" ; do
namechange "$filename"
done

Drawing your Application

In the example in this program the procedure lidsize computes the main coordinates that define the figure. In a more complicated example such a procedure might compute the coordinates of a complicated structure that would be broken into several figures.

The procedure lid actually draws the figure. In a more complicated example several such procedures might separately draw pieces of a complicated figure. In the procedure lid the first two lines after "IF inlays" change the mode of drawing to dashed lines. The last two lines of that section turn off the dashed line mode.

The Program


MODULE curve;
(*written 2018 donald daniel.  
This program is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software
Foundation, version 3 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General
Public License along with this program.  If not, see
http://www.gnu.org/licenses/.  *)
IMPORT In,Out,Msg,Files,TextRider,
OS:ProcessManagement,rm:=RealMath,IntStr,Strings,Object;
CONST numcoords=320;er=0;q=1;drawv=3;
print=4;lidv=5; 
prtcrd=9;
test=11;
dist=17;batch=18;ang=19;
bdr3=22;
last=26; move=1;draw=2;
pi=3.141592654; degtorad=0.017453; radtodeg=57.2957; 
(*coat origins*)
xcorg=14.49;ycorg=57.96; xcorgl=3;ycorgl=65;
(*coords named tempcoord are for user. temporary coords
for tools must be assigned positions only by each tool.*)
tempcoord1=270;tempcoord2=271; (*tempcoord3=272;
tempcoord4=273; tempcoord5=274;reflectcoord=275;*)
(*281 to 286 used by testproc*)
(*intersect*) intcoord=287; 
(*ntrsct2arc ntrsct1=288;ntrsct2=289; *)
(* arc*) arccenter=290; arccoord1=291;
arccoord2=292;arccoord3=293;arccoord4=294;
(*tangentarc pmcoord1=295;pmcoord2=296;*)
(* midarc mdacoord1=297;mdacoord2=298; mdacoord3=299;
mdacoord4=300;mdacoord5=301;*)
(*shiftright*) shift11=302; shift12=303;shift21=304;shift22=305;
(*arcang *)arcangcd=306;
(*label*) labcoord=307;
(* arcline arcline1=308;arcline2=309;*)
(*sline*) slc1=310;slc2=311;slcc=312;slt1=313;slt2=314;
TYPE cstr=ARRAY 10 OF CHAR; str=ARRAY 40 OF CHAR;
coord = RECORD x,y:REAL END;
VAR user:str;cmdara:ARRAY last+1 OF cstr; filename:str;
command,coordv,choice:LONGINT;
str1:str;invar,outvar:Files.File;resw:Msg.Msg;
infile:TextRider.Reader; outfile:TextRider.Writer;
cd:ARRAY numcoords OF coord; 
(*temp1,temp2,temp3,temp4,*)xorg,yorg,
scale,dashv,widthv,dxorg,dyorg,wide,high,
(*, arc*) th1,th2,arcradius :REAL;
small,inlays:BOOLEAN;

PROCEDURE initvar;
BEGIN
cmdara[er]:='er';
cmdara[q]:='q';
cmdara[drawv]:='draw';
cmdara[print]:='print';
cmdara[prtcrd]:='prtcrd';
cmdara[dist]:='dist';
cmdara[ang]:='ang';
cmdara[test]:='test';
cmdara[batch]:='batch';
cmdara[bdr3]:='border1';
cmdara[lidv]:='lid';
cmdara[last]:='last';
dxorg:=0.0;dyorg:=0.0;
END initvar;

PROCEDURE determine(VAR cmdvar:LONGINT);
CONST bell=7;
VAR i:LONGINT;str1:cstr;
BEGIN
Out.String('enter command');Out.Ln; cmdvar:=er;
In.Line(str1);
FOR i:=q TO last DO
IF (cmdara[i]=str1)THEN cmdvar:=i;END;END;
IF cmdvar=er THEN
(*the line below must be modified to look the way it looks
when seen in your browser*)
Out.Char(CHR(bell));Out.String('<--<< error');
Out.Ln;END; END determine;

PROCEDURE initfile;
BEGIN
outvar:=Files.New('temp1',{Files.write},resw);
outfile:=TextRider.ConnectWriter(outvar);
outfile.WriteString('%!PS');outfile.WriteLn;
END initfile;

PROCEDURE width(w:REAL);
BEGIN
outfile.WriteRealFix(w,8,2);
outfile.WriteString (' setlinewidth'); outfile.WriteLn;
outfile.WriteString('stroke');outfile.WriteLn;
END width;

PROCEDURE dash(w:REAL);
(*it is assumed that width will be called
right after dash, because dash has no 'stroke' *)
BEGIN
outfile.WriteString('[ '); outfile.WriteRealFix(w,8,2);
outfile.WriteRealFix(w,8,2); 
outfile.WriteString(' ] 0 setdash'); outfile.WriteLn;
END dash;

PROCEDURE getdata;
BEGIN
Out.String('enter 1 for small scale, 2 for full scale');
Out.Ln;In.LongInt(choice);
IF(*scale*) choice=2 THEN small:=FALSE;
scale:=72.0; widthv:=2.8;dashv:=3;
ELSE small:=TRUE;scale:=11.18; 
widthv:=1;dashv:=2;END(*scale*);
Out.String('enter 1 for plain, 2 for inlays and turnings');
Out.Ln;In.LongInt(choice);
IF(*inlays*) choice=2 THEN inlays:=TRUE; ELSE inlays:=FALSE; 
END(*inlays*);
In.Line(filename);
Out.String('enter filename');Out.Ln; In.Line(filename);
invar:=Files.Old(filename,{Files.read},resw);
infile:=TextRider.ConnectReader(invar);
infile.ReadString(user);infile.ReadLine(str1);
infile.ReadReal(wide); infile.ReadLine(str1);
Out.RealFix(wide,6,2);Out.String(' wide');Out.Ln;
infile.ReadReal(high); infile.ReadLine(str1);
Out.RealFix(high,6,2);Out.String(' high');Out.Ln;
invar.Close;
END getdata;

PROCEDURE printcoord(i:LONGINT);
BEGIN
Out.String('coord '); Out.LongInt(i,4); 
Out.RealFix(cd[i].x,12,2); 
Out.RealFix(cd[i].y,12,2);Out.Ln;
END printcoord;

 PROCEDURE setcoord(coord:LONGINT;x,y:REAL);
BEGIN cd[coord].x:=x;cd[coord].y:=y;END setcoord; 

 PROCEDURE moveto(crd,md:LONGINT);
VAR x,y:REAL;
BEGIN
x:=cd[crd].x;y:=cd[crd].y; x:=xorg+scale*x;y:=yorg+scale*y;
outfile.WriteRealFix(x,10,2); outfile.WriteRealFix(y,10,2);
IF md=move THEN 
outfile.WriteString(' moveto');
ELSE outfile.WriteString(' lineto');END;
outfile.WriteLn; END moveto;

PROCEDURE angle(c1,c2:LONGINT):REAL;
(*angle of vector from c1 to c2 in radians. The angle
is positive only, thus -10 degrees is reported as
plus 350 degrees, but in radians.*)
CONST pi=3.14159;
VAR x,y,ax,ay,at:REAL;
BEGIN
x:=cd[c2].x-cd[c1].x;y:=cd[c2].y-cd[c1].y;
ax:=ABS(x);ay:=ABS(y);
IF y >= 0 THEN 
  IF x >= 0 THEN 
   IF ay < ax THEN at:=rm.arctan(ay/ax);
   ELSE at:=0.5*pi-rm.arctan(ax/ay);END;
  ELSE 
   IF ay < ax THEN at:=pi-rm.arctan(ay/ax);
   ELSE at:=0.5*pi+rm.arctan(ax/ay);END;
  END;
ELSE
  IF x <= 0 THEN
   IF ay < ax THEN at:=pi+rm.arctan(ay/ax);
   ELSE at:=1.5*pi-rm.arctan(ax/ay);END;
  ELSE
   IF ay < ax THEN at:=2*pi-rm.arctan(ay/ax);
   ELSE at:=1.5*pi+rm.arctan(ax/ay);END;
  END;
END;
RETURN at END angle;

PROCEDURE distance(c1,c2:LONGINT):REAL;
(*straight line distance between two points*)
VAR dx,dy:REAL;
BEGIN
dx:=cd[c1].x-cd[c2].x;dy:=cd[c1].y-cd[c2].y;
RETURN ABS(rm.sqrt(dx*dx+dy*dy)); END distance;

PROCEDURE newcoord(oc,nc:LONGINT;th,ln:REAL);
(*new coord at angle and distance from old coord.
If you want the opposite direction of an angle, 
do not negate it, but subtract pi.*)
BEGIN
cd[nc].x:=cd[oc].x+ln*rm.cos(th); 
cd[nc].y:=cd[oc].y+ln*rm.sin(th); END newcoord;

PROCEDURE midpoint(c1,c2,cm:LONGINT;fract:REAL);
(*new coord along line from c1 to c2 fraction of way*)
VAR cx,cy:REAL;
BEGIN
cx:=(cd[c2].x-cd[c1].x)*fract+cd[c1].x;
cy:=(cd[c2].y-cd[c1].y)*fract+cd[c1].y;
cd[cm].x:=cx; cd[cm].y:=cy; END midpoint;

(*PROCEDURE rotate(ctr,oc,nc:LONGINT;dth:REAL);
(*coord rotated about ctr at angle dth*)
VAR cx,cy,r,th1,th2:REAL;
BEGIN
r:=distance(ctr,oc);th1:=angle(ctr,oc);
th2:=th1+dth;
cx:=cd[ctr].x+r*rm.cos(th2); cy:=cd[ctr].y+r*rm.sin(th2);
cd[nc].x:=cx;cd[nc].y:=cy; END rotate;*)

(*PROCEDURE mirror(other,center,offset,mirpt:LONGINT);
(*mirror offset about center for arc. other,center
on line, offset,mirpt ends of arc tangent to line*)
CONST pi=3.14159;
VAR dist,lineang,offsetang,perpang,mirang,diffang:REAL;
BEGIN
dist:=distance(center,offset);
lineang:=angle(other,center);
offsetang:=angle(center,offset);
perpang:=lineang-pi/2;
diffang:=offsetang-perpang;
IF ABS(diffang)>pi/2 THEN
perpang:=perpang-pi;
diffang:=offsetang-perpang;END;
mirang:=perpang-diffang;
newcoord(center,mirpt,mirang,dist);
END mirror;*)

PROCEDURE intersect(p11,p12,p21,p22,i:LONGINT;f:REAL);
(*intersection of two straight lines.  It is assumed
that p12 and p22 are closest to the intersection for best
results. f is 1.0 unless p12 is beyond the intersection,
in which case f is less than 1 to pretend that p12 is not
beyond the intersection*)
VAR a,b,c,C,h,d,th,frac:REAL;
BEGIN
midpoint(p11,p12,intcoord,f);
c:=distance(p21,p22);a:=distance(intcoord,p21);
b:=distance(intcoord,p22);
C:=rm.arccos((a*a+b*b-c*c)/(2*a*b));
h:=ABS((a*b*rm.sin(C))/c);
th:=ABS(angle(p11,p12)-angle(p21,p22));
d:=ABS(h/rm.sin(th));
frac:=(distance(p11,intcoord)+d)/distance(p11,intcoord);
midpoint(p11,intcoord,i,frac);
END intersect;

(*PROCEDURE tangentarc(ang:REAL;ofst,p1,p2,pt:LONGINT);
(*Find tangent point pt on line p1-p2 that will connect
with arc to offset point. The arc will leave ofst at angle
ang.  The direction from p2 to p1 is the direction of the
end of the arc at the tangent point.  Later mirror can be
called to mirror ofst about pt.  *)
VAR ang2,dist:REAL;
BEGIN
newcoord(ofst,pmcoord1,ang-pi,10);
(*pi is 180 degrees, pmcoord1 and ofst are on same line
with angle ang*)
intersect(pmcoord1,ofst,p1,p2,pmcoord2,1);
ang2:=angle(p2,p1);dist:=distance(ofst,pmcoord2);
newcoord(pmcoord2,pt,ang2,dist);
END tangentarc;*)

PROCEDURE circle(c1:LONGINT;r:REAL);
VAR cx,cy:REAL;
BEGIN
r:=r*scale;
outfile.WriteRealFix(widthv,8,2);
outfile.WriteString (' setlinewidth'); outfile.WriteLn;
outfile.WriteString('stroke');outfile.WriteLn;
cx:=xorg+scale*cd[c1].x;cy:=yorg+scale*cd[c1].y;
outfile.WriteRealFix(cx,14,3); 
outfile.WriteRealFix(cy,14,3);
outfile.WriteRealFix(r,14,3); 
outfile.WriteString('  0 360 arc'); outfile.WriteLn;
outfile.WriteRealFix(widthv,8,2);
outfile.WriteString (' setlinewidth'); outfile.WriteLn;
outfile.WriteString('stroke');outfile.WriteLn;
END circle;

PROCEDURE arc(c1,c2,c3,range,md:LONGINT);
(*c1,c2,c3 coords define arc. They must be in order of
ccw movement around arc. range=13 means arc from c1 to
c3, 12 from c1 to c2, 23 from c2 to c3.  After invoking
this procedure cd[arccenter] will be the center of the
arc. th1 and th2 will be the angles from the arc center
to the beginning and end of the arc. arcradius will be
the radius of the arc. *)
VAR ang12,ang23,dist,cx,cy,r:REAL;
BEGIN
ang12:=angle(c1,c2)-pi/2;
ang23:=angle(c2,c3)-pi/2;
dist:=distance(c1,c2);
IF distance(c2,c3)>dist THEN dist:=distance(c2,c3);END;
midpoint(c1,c2,arccoord1,0.5);
newcoord(arccoord1,arccoord2,ang12,dist);
midpoint(c2,c3,arccoord3,0.5);
newcoord(arccoord3,arccoord4,ang23,dist);
intersect(arccoord2,arccoord1,arccoord4,arccoord3,arccenter,1);
cx:=cd[arccenter].x;cy:=cd[arccenter].y;
r:=distance(arccenter,c1);
arcradius:=r;
r:=scale*r;cx:=xorg+scale*cx;cy:=yorg+scale*cy;
IF (range=12)OR(range=13)THEN moveto(c1,move)
ELSE moveto(c2,move);END;
IF md=draw THEN 
outfile.WriteRealFix(cx,12,2); outfile.WriteRealFix(cy,12,2);
outfile.WriteRealFix(r,12,2);END;
IF range=13 THEN
th1:=angle(arccenter,c1); th2:=angle(arccenter,c3);
ELSIF range=12 THEN th1:=angle(arccenter,c1);
th2:=angle(arccenter,c2);
ELSIF range=23 THEN
th1:=angle(arccenter,c2); th2:=angle(arccenter,c3);
ELSE Out.String('range out of range');Out.Ln;
HALT(1);END;
IF md=draw THEN outfile.WriteRealFix(th1*radtodeg,12,2); 
outfile.WriteRealFix(th2*radtodeg,12,2);
outfile.WriteString(' arc'); outfile.WriteLn;
END(*if md*);
END arc;

PROCEDURE cwccw(t1,t2:REAL):REAL;
(*returns -1 if angle t2 cw from t1, +1 if ccw*)
BEGIN
IF ABS(t2-t1)>pi THEN 
IF t2>t1 THEN t2:=t2-2.0*pi ELSE t1:=t1-2.0*pi END;END;
IF t2-t1>=0.0 THEN RETURN 1 ELSE RETURN -1 END;
END cwccw;

(*PROCEDURE avgang(t1,t2:REAL):REAL;
(*the average of two angles*)
BEGIN
IF ABS(t2-t1)>pi THEN 
IF t2>t1 THEN t2:=t2-2.0*pi ELSE t1:=t1-2.0*pi END;END;
RETURN (t1+t2)*0.5;
END avgang;*)

(*PROCEDURE midarc(p1,p2,p3,md:LONGINT;f:REAL); (*line
from p1, arc with p2 in the middle, line to p3. Order of
points must trace arc ccw.  f is fraction of the largest
radius of arc that can be used.  This invokes arc, so
arccenter, th1, th2 will be useable after invocation.
After finish mdacoor1 and mdacoord2 store position of arc
ends, mdacoord3 stores arc center.*)
VAR r,r1,ang1,ang3,ang14,ang34,ang12,ang32,ang1x,ang3x,
angt,ang21,ang23,ang24,d21,d23,d41,d43,d1x,d3x:REAL;
p4,p1x,p3x:LONGINT;
BEGIN
ang21:=angle(p2,p1);ang23:=angle(p2,p3);
ang24:=avgang(ang21,ang23);
d21:=distance(p2,p1);d23:=distance(p2,p3);
IF d21 < d23 THEN 
angt:=ang21-pi/2;r1:=0.4*d21;
midpoint(p2,p1,mdacoord4,0.5);
newcoord(mdacoord4,mdacoord5,angt,r1);
ELSE angt:=ang23+pi/2; r1:=0.4*d23;
midpoint(p2,p3,mdacoord4,0.5);
newcoord(mdacoord4,mdacoord5,angt,r1);
END;
p4:=mdacoord3;
newcoord(p2,p4,ang24,r1);
intersect(mdacoord4,mdacoord5,p2,p4,p4,1);
midpoint(p2,p4,p4,f);
r:=distance(p2,p4);
d41:=distance(p4,p1);d43:=distance(p4,p3);
d1x:=rm.sqrt(d41*d41-r*r);
d3x:=rm.sqrt(d43*d43-r*r);
ang1:=rm.arcsin(r/d41);
ang3:=rm.arcsin(r/d43);
ang14:=angle(p1,p4);ang12:=angle(p1,p2);
ang34:=angle(p3,p4);ang32:=angle(p3,p2);
ang1x:=ang14+cwccw(ang14,ang12)*ang1;
ang3x:=ang34+cwccw(ang34,ang32)*ang3;
p1x:=mdacoord1;p3x:=mdacoord2;
newcoord(p1,p1x,ang1x,d1x);
newcoord(p3,p3x,ang3x,d3x);
moveto(p1,move);
IF md=draw THEN moveto(p1x,draw);
arc(p1x,p2,p3x,13,draw); moveto(p3,draw); 
ELSE moveto(p1x,move);
arc(p1x,p2,p3x,13,move); moveto(p3,move); END;
END midarc;*)

(*PROCEDURE ntrsctarc(ctr,o1,o2,i1:LONGINT;r:REAL);
(*first intersection point i1 found of line from o1 to
o2 with circle at ctr of radius r. Order of o1 and o2
matters. Triangle formula where one angle and two sides
known.*)
VAR sda,sdb,sdc,ang,angc,a,b,c:REAL;
BEGIN
ang:=angle(o1,o2); sdc:=r;angc:=angle(o1,ctr)-ang;
sda:=distance(o1,ctr); a:=1;b:=-2*sda*rm.cos(angc);
c:=sda*sda-sdc*sdc; sdb:=(-b-rm.sqrt(b*b-4*a*c))/(2*a);
newcoord(o1,i1,ang,sdb); END ntrsctarc;*)

(*PROCEDURE ntrsct2arc(ctr1,ctr2:LONGINT;r1,r2:REAL);
(*two intersection points of two circles found by triangle
formula c^2=a^2+b^2-2ab(cos(c)). r1 goes with ctr1, r2
goes with ctr2, Resulting points are ntrsct1 and ntrsct2.*)
VAR d,ad,ac,a,b,c:REAL;bad:BOOLEAN;
BEGIN
bad:=FALSE;
d:=distance(ctr1,ctr2); IF d>(r1+r2)THEN 
Out.String('ntrsct2arc abort centers too far apart');Out.Ln;
HALT(1);END;
IF r1>(d+r2)THEN
Out.String('ntrsct2arc r1 too great');Out.Ln;HALT(1);END;
IF r2>(d+r1)THEN
Out.String('ntrsct2arc r2 too great');Out.Ln;HALT(1);END;
ad:=angle(ctr1,ctr2);
c:=r2;a:=d;b:=r1;
ac:=rm.arccos((a*a+b*b-c*c)/(2*a*b));
newcoord(ctr1,ntrsct1,ad+ac,r1);
newcoord(ctr1,ntrsct2,ad-ac,r1);
END ntrsct2arc;*)

(*PROCEDURE reflect(c1,c2,o1,o2,r1,r2:LONGINT);
(*reflect line c about line o to produce line r *)
BEGIN
newcoord(o1,reflectcoord,angle(o2,o1)-pi/2,10);
mirror(reflectcoord,o1,c1,r1);
mirror(reflectcoord,o1,c2,r2);
END reflect;*)

PROCEDURE shiftright(c1,c2,i:LONGINT;s:REAL);
(*create temporary coordinates "shiftxx" shifted
to the right a distance s from the coordinates c1,c2.
i is 1 or 2 to designate shift1x or shift2x so two
sets of temporary shift coordinates can exist at the
same time, for calculating the intersection of shifted
lines. WARNING: if you get mixed up about whether the
first or second digit is determined by i, your program
may blow up!*)
BEGIN
IF (i=1) OR (i= 2)THEN ELSE Out.String('shiftright i wrong');
Out.Ln;HALT(1);END;
IF i=1 THEN
newcoord(c1,shift11,angle(c1,c2)-pi/2,s);
newcoord(c2,shift12,angle(c1,c2)-pi/2,s);
ELSE
newcoord(c1,shift21,angle(c1,c2)-pi/2,s);
newcoord(c2,shift22,angle(c1,c2)-pi/2,s);END;
END shiftright;

PROCEDURE tangentline(ctr,ofst,tp:LONGINT;r:REAL;left:BOOLEAN);
(*find tangent point tp on circle with center ctr and
radius r that will connect with straight line to offset
point ofst. As seen from ctr looking at ofst, if tp is on
left side of circle then left is true, otherwize tp is on
right side of circle.*)
VAR ang,dist:REAL;
BEGIN
dist:=distance(ctr,ofst);
ang:=rm.arccos(r/dist);
IF left THEN ang:=-ang;END;
newcoord(ctr,tp,angle(ctr,ofst)-ang,r);
END tangentline;

PROCEDURE arcang(ang1:REAL;c1,c2,md:LONGINT);
(*draw arc that starts at c1 at angle ang1, and ends at
c2. c1 and c2 may be specified in any order. There is
no ccw requirement*)
VAR ang2,ang3,dist,angdiff:REAL;
BEGIN
dist:=distance(c1,c2);
ang2:=angle(c1,c2);
angdiff:=ang1-ang2; 
ang3:=ang1-pi;
newcoord(c1,arcangcd,angdiff+ang3,dist);
IF cwccw(ang1,ang2) < 0 THEN 
moveto(c2,move);arc(c2,c1,arcangcd,12,md);moveto(c2,move);
ELSE moveto(c1,move);arc(arcangcd,c1,c2,23,md) END;
END arcang;

(*PROCEDURE arcline(ang1,frac:REAL;c1,c2,md:LONGINT);
(*draw arc that starts at c1 at angle ang1, at
fraction frac of the way from c1 to c2 it smoothly
transistions to a line to c2. arcline2 is point
where arc meets line*)
VAR diff,dist:REAL;
BEGIN
frac:=0.5*frac;
diff:=ang1-angle(c1,c2);
dist:=frac*distance(c1,c2);
newcoord(c1,arcline1,ang1,dist/rm.cos(diff));
tangentarc(ang1,c1,c2,arcline1,arcline2);
arcang(ang1,c1,arcline2,md);
moveto(arcline2,move);
moveto(c2,md);
END arcline;*)

PROCEDURE sline(c1,c2:LONGINT;left:BOOLEAN;r1,a1,r2,a2:REAL);
(*draw s curve to connect points c1 and c2. Curve will be
two arcs with straight line segment between. Arcs leave
points c1 and c2 at specified angles a1, and a2. Radii
of arcs are specified to be r1 and r2. Centers of arcs
are computed to be slc1 and slc2. Point slcc is on line
between slc1 and slc2 at point where straight line segment
will cross. Ends of straight line segment will join arcs
at tangent points slt1 and slt2. As seen from c1 looking
at c2, if arc leaves c1 to left then left is true.*)
VAR ac1,ac2:REAL;
BEGIN
IF left THEN ac1:=a1-pi/2;ac2:=a2-pi/2;
ELSE ac1:=a1+pi/2;ac2:=a2+pi/2;END;
newcoord(c1,slc1,ac1,r1);newcoord(c2,slc2,ac2,r2);
midpoint(slc1,slc2,slcc,r1/(r1+r2));
tangentline(slc1,slcc,slt1,r1,left);
tangentline(slc2,slcc,slt2,r2,left);
moveto(c1,move); arcang(a1,c1,slt1,draw);
moveto(slt1,move);moveto(slcc,draw);
moveto(slt2,draw);moveto(c2,move);
arcang(a2,c2,slt2,draw);
END sline;

PROCEDURE zerocoords;
VAR i:LONGINT;
BEGIN
FOR i:=0 TO numcoords-1 DO 
cd[i].x:=0.0;cd[i].y:=0.0;END;
END zerocoords;

PROCEDURE outint(i:LONGINT);
BEGIN
outfile.WriteString('(');
outfile.WriteLInt(i,2);
outfile.WriteString(')');
outfile.WriteString(' show');
outfile.WriteLn;
END outint;

PROCEDURE label(coord,location:LONGINT);
VAR ang,ln:REAL;
BEGIN
ang:=location*pi/4;
ln:=0.7;
newcoord(coord,labcoord,1.2*pi,0.6);
newcoord(labcoord,labcoord,ang,ln);
moveto(labcoord,move);
outint(coord);
circle(coord,0.1);
END label;

PROCEDURE border1;
BEGIN
zerocoords;
IF small THEN
xorg:=xcorg*scale;yorg:=ycorg*scale;ELSE
xorg:=xcorgl*scale;yorg:=ycorgl*scale;END;
setcoord(0,-3,6);
setcoord(1,32,6);
setcoord(2,32,-36);
setcoord(3,-3,-36);
moveto(0,move);moveto(1,draw);moveto(2,draw);
moveto(3,draw);moveto(0,draw);
newcoord(3,tempcoord1,0,1);moveto(tempcoord1,move);
newcoord(tempcoord1,tempcoord2,pi/2,1);
moveto(tempcoord2,draw);
newcoord(2,tempcoord1,pi,1);moveto(tempcoord1,move);
newcoord(tempcoord1,tempcoord2,pi/2,1);
moveto(tempcoord2,draw);
newcoord(1,tempcoord1,1.5*pi,2);moveto(tempcoord1,move);
newcoord(tempcoord1,tempcoord2,pi,1);
moveto(tempcoord2,draw);
newcoord(2,tempcoord1,pi/2,2);moveto(tempcoord1,move);
newcoord(tempcoord1,tempcoord2,pi,1);
moveto(tempcoord2,draw);
END border1;

PROCEDURE lidsize;
CONST xctr=14.5;yctr=-15.0;
BEGIN
IF small THEN
xorg:=(xcorg+dxorg)*scale;yorg:=(ycorg+dyorg)*scale;ELSE
xorg:=(xcorgl+dxorg)*scale;yorg:=(ycorgl+dyorg)*scale;END;
setcoord(0,xctr-wide/2,yctr+high/2);
setcoord(1,xctr-wide/2,yctr-high/2);
setcoord(2,xctr+wide/2,yctr-high/2);
setcoord(3,xctr+wide/2,yctr+high/2);
END lidsize;

PROCEDURE lid;
BEGIN
zerocoords;lidsize;
IF small THEN
xorg:=(xcorg+dxorg)*scale;yorg:=(ycorg+dyorg)*scale;ELSE
xorg:=(xcorgl+dxorg)*scale;yorg:=(ycorgl+dyorg)*scale;END;
(*start drawing*)
moveto(0,move);moveto(1,draw);moveto(2,draw);
moveto(3,draw);moveto(0,draw);
IF inlays THEN
outfile.WriteString('gsave');outfile.WriteLn;
outfile.WriteString('newpath');outfile.WriteLn;
shiftright(0,1,1,2.0);
moveto(0,move);
moveto(shift11,draw);moveto(shift12,draw);
moveto(1,draw);
shiftright(1,2,1,2.0);
moveto(shift11,draw);moveto(shift12,draw);
moveto(2,draw);
shiftright(2,3,1,2.0);
moveto(shift11,draw);moveto(shift12,draw);
moveto(3,draw);
shiftright(3,0,1,2.0);
moveto(shift11,draw);moveto(shift12,draw);
moveto(0,draw);
dash(dashv);width(widthv);
outfile.WriteString('grestore');outfile.WriteLn;
END(*inlays*); END lid;


PROCEDURE testproc;
(*small scale is 11.18 units moves one inch.  origin is
two inches to right, two inches down from upper left corner
of screen; positive directions right, up*)
BEGIN
outfile.WriteString('/Palatino-Roman findfont');
outfile.WriteLn;
outfile.WriteString('11 scalefont'); outfile.WriteLn;
outfile.WriteString('setfont');outfile.WriteLn;
(*the following line is coord numbers, not locations,
of the points*)
IF small THEN
xorg:=xcorg*scale;yorg:=ycorg*scale;ELSE
xorg:=xcorgl*scale;yorg:=ycorgl*scale;END;
setcoord(1,0,7);setcoord(2,10,10);
label(1,4);label(2,0);
sline(1,2,FALSE,3,330*degtorad,5,180*degtorad);
END testproc;


PROCEDURE endfile*;
BEGIN
outfile.WriteRealFix(widthv,8,2);
outfile.WriteString (' setlinewidth'); outfile.WriteLn;
outfile.WriteString('stroke');outfile.WriteLn;
outfile.WriteString('showpage'); outfile.WriteLn;
outvar.Close; END endfile;
 
PROCEDURE viewplot*;
VAR i:LONGINT;
BEGIN
Out.String('press enter to view plot, then ');Out.Ln;
Out.String('enter quit to finish viewing plot');Out.Ln;
In.Line(str1);
i:=ProcessManagement.system("gs -sDEVICE=x11 temp1");
END viewplot;

PROCEDURE printplot*;
VAR i:LONGINT;
BEGIN
i:=ProcessManagement.system("lpr temp1");
(*lpr is assumed to send file through 
Ghostscript to printer*)
Out.String('sent to printer');Out.Ln; END printplot;

PROCEDURE crdproc;
BEGIN
Out.String('enter number of coordinate');Out.Ln;
In.LongInt(coordv);printcoord(coordv);In.Line(str1);
END crdproc;

PROCEDURE distproc;
VAR c1,c2:LONGINT;d:REAL;
BEGIN
Out.String('enter numbers of two coordinates');Out.Ln;
In.LongInt(c1); In.LongInt(c2); In.Line(str1);
d:=distance(c1,c2);
Out.RealFix(d,8,2);Out.Ln;
END distproc;

PROCEDURE angproc;
VAR c1,c2:LONGINT;a:REAL;
BEGIN
Out.String('enter numbers of two coordinates');Out.Ln;
In.LongInt(c1); In.LongInt(c2); In.Line(str1);
a:=angle(c1,c2)*radtodeg;
Out.RealFix(a,8,2);Out.Ln;
END angproc;

PROCEDURE botlab(x,y:REAL);

PROCEDURE initfont;
BEGIN
outfile.WriteString('/Palatino-Roman findfont');
outfile.WriteLn;
outfile.WriteString('14 scalefont'); outfile.WriteLn;
outfile.WriteString('setfont');outfile.WriteLn;
END initfont; 

PROCEDURE outreal(r:REAL);
BEGIN
outfile.WriteString('(');
outfile.WriteRealFix(r,8,2);
outfile.WriteString(' ');
END outreal;

PROCEDURE outstring(s:str);
BEGIN
outfile.WriteString(s);
outfile.WriteString(') show');
outfile.WriteLn;
END outstring;

PROCEDURE outstring2(s:str);
BEGIN
outfile.WriteString('(');
outfile.WriteString(s);
outfile.WriteString(') show');
outfile.WriteLn;
END outstring2;

PROCEDURE list;
BEGIN
outstring2(user);
outreal(wide);outstring('wide');
outreal(high);outstring('high');
END list;

BEGIN
IF ~small THEN setcoord(tempcoord1,x,y);
moveto(tempcoord1,move);
initfont; list;END;
END botlab;

PROCEDURE batchproc;
VAR str1,str2:str;i,pat:LONGINT;str3:STRING;
BEGIN
FOR pat:=1 TO 1 DO
initfile;
CASE pat OF
1:lid; border1; dxorg:=0.0;dyorg:=0.0;botlab(5,-35); |
END(*case*);
endfile;
(*to make ps files comment out next 4 lines*)
i:=ProcessManagement.system
("pstoedit  -f fig temp1 temp1.fig");
i:=ProcessManagement.system
("fig2dev -L pdf -b 20 temp1.fig temp1.pdf");
(*to make ps files remove .pdf in next line*)
str1:="mv temp1.pdf file";
IntStr.IntToStr(pat,str2);
Strings.Append(str2,str1);
(*to make ps files change .pdf to .ps next line*) 
str2:=".pdf";Strings.Append(str2,str1);
str3:=Object.NewLatin1(str1);
i:=ProcessManagement.system(str3);
END(*for*);dxorg:=0.0;dyorg:=0.0;END batchproc;

PROCEDURE drawproc;
BEGIN
initfile;
Out.String('lid');
Out.Ln;
Out.String(' border1, test, q');
Out.Ln;
LOOP
determine(command); IF command=q THEN EXIT;END; 
CASE command OF er:|lidv:lid;
|bdr3:border1;
|test:testproc;END(*case*);
END(*loop*);endfile;viewplot;END drawproc;

BEGIN
initvar;getdata;
LOOP Out.String('draw, prtcrd, dist, ang, print, batch, q');
Out.Ln; 
determine(command); IF command=q THEN EXIT;END;
CASE command OF er:|drawv:drawproc;|prtcrd:crdproc;|
dist:distproc;|ang:angproc;|print:printplot;|batch:batchproc;
END(*case*); END(*loop*);END curve.