If the lines of text are too long you can fix the problem with these instructions.

- introduction
- tools for coordinates
- tools for curve drawing
- scale and origin of the plot
- compiling and running the program
- drawing your application
- the program

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.

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.

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.

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 straight lines defined by four coordinates. The intersection is found even if the lines are too short to reach 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.

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);

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);

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);

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);

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);

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.

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.

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

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.

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*) (*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); (*i is the coordinate that is the intersection of two straight lines, p11 to p12, and p21 to p22. The interesection point will be calculated even if the lines are too short to reach the intersection*) VAR x1,y1,x2,y2,x3,y3,x4,y4,n1,d1,n2,d2:REAL; BEGIN x1:=cd[p11].x;y1:=cd[p11].y;x2:=cd[p12].x;y2:=cd[p12].y; x3:=cd[p21].x;y3:=cd[p21].y;x4:=cd[p22].x;y4:=cd[p22].y; n1:=((x1*y2-y1*x2)*(x3-x4))-((x1-x2)*(x3*y4-y3*x4)); d1:=((x1-x2)*(y3-y4))-((y1-y2)*(x3-x4)); n2:=((x1*y2-y1*x2)*(y3-y4))-((y1-y2)*(x3*y4-y3*x4)); d2:=((x1-x2)*(y3-y4))-((y1-y2)*(x3-x4)); cd[i].x:=n1/d1;cd[i].y:=n2/d2; 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); 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); 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. we want the acute angle between two points on a circle. thus the average of 0 and 1.5*pi is 1.75*pi, not 0.75*pi. this proceedure assumes both angles are positive, and neither is greater than 2*pi*) 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); 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 (*font height is in units of 1/72 inch*) outfile.WriteString('/Latin-Modern-Roman 14 selectfont '); outfile.WriteLn; IF small THEN xorg:=xcorg*scale;yorg:=ycorg*scale;ELSE xorg:=xcorgl*scale;yorg:=ycorgl*scale;END; setcoord(1,10,0);setcoord(2,15,-3); setcoord(3,10,-10);setcoord(4,15,-7); moveto(1,move);moveto(2,draw); moveto(3,move);moveto(4,draw); intersect(1,2,3,4,5);circle(5,0.3); 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('/Latin-Modern-Roman 14 selectfont'); 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.