.so ../ADM/mac .XX ideal 79 "Ideal \(em A Picture-specification Language" ...\" Thu May 1 10:28:25 EDT 1986 ...\"refer first ...TR 103 ...RP 80-11272-13 11173 39199-11 ...ND December 17, 1981 .hy 14 \" set hyphenation: 2=not last lines; 4= no -xx; 8=no xx- .ds id \f2ideal\fP .ds [. [ .ds .] ] .nr DV 0 \" for .P1/.P2 .de Ks .SP .KS .. .de Ke .KE .SP .. .de IS \" do not touch these! .br .nf .. .de IE .. .de Ts .nr PS -1 .nr VS -1 .QS .. .de Te .nr PS +1 .nr VS +1 .QE .. .EQ delim $$ .EN ......graphics .TL Ideal \(em A Picture-specification Language .br User and Reference Manual\(dg .AU "MH 2C-518" 5292 Christopher J. Van Wyk .AI .MH .AB This manual describes how to use the existing implementation of \*(id, a programming language to be used for describing pictures. The main use of \*(id is as a preprocessor to .I troff , so that pictures and text may reside in the same file and be typeset together. .I Ideal can also produce device-independent descriptions of pictures which, for example, can be displayed through the .UX system plot filters. .AE .FS \(dg This is a revised version of |reference(van wyk ideal cstr). .FE .NH 1 Introduction .PP .I Ideal is a language for describing pictures. It is intended primarily to operate as a preprocessor to .I troff |reference(troff latest reference), much like .I eqn |reference(latest eqn), .I tbl |reference(latest tbl) and .I pic |reference(latest pic). .PP I have explained the principles that motivate the form of \*(id elsewhere|reference(ideal thesis)|reference(ideal acm). This document describes how to use the existing implementation of \*(id and treats several examples in depth. .Ts Paragraphs like this that appear in smaller type may be skipped on first reading: they present sidelights that may be ignored safely by beginners. .Te .NH 1 Overview of \*(id .PP To take advantage of \*(id's capabilities, you must believe that .ce 1 .I "complex numbers are good" . .IP \(bu Complex numbers have a natural correspondence to points in the Cartesian ($x-y$) coordinate system. .IP \(bu Using complex numbers obviates the need for distinguishing between ``points'' and ``dimensions.'' .IP \(bu Complex numbers capture readily such common operations as translation, rotation, and reflection in the plane. .LP .I Ideal programs define pictures by means of a system of simultaneous equations in the significant points of the picture and a set of drawing instructions to be carried out with respect to those points. .I Ideal solves the system of equations, then draws the picture using the points so determined. .PP All variables in \*(id programs are complex numbers, with the usual operations: .IP \(bu component-wise addition and subtraction: $(a,b) ~ +- ~ (c,d) ~ = ~ (a +- c , b +- d)$ .IP \(bu vector multiplication: $(a,b)*(c,d) ~ = ~ (ac - bd , ad + bc)$ .IP \(bu vector division: $(a,b)/(c,d) ~ = ~ (ac + bd , -ad + bc)/( c sup 2 + d sup 2 )$ .IP \(bu component manipulation: $roman "re" ((a,b)) ~ = ~ a$, $roman "im" ((a,b)) ~ = ~ b$, and $roman "conj" ((a,b)) ~ = ~ (a,-b)$ .IP \(bu vector magnitude calculation: $roman "abs" ((a,b)) ~ = ~ sqrt { { a sup 2 } + { b sup 2 } }$ .IP \(bu unit vector function: $roman "cis" ( theta ) ~ = ~ cos theta + i sin theta$ .LP A non-standard notation that has proved useful is $alpha [ x , y ]$, for complex $x$ and $y$, defined by $x + alpha ( y - x )$, and meaning ``$alpha$ of the way from $x$ to $y$.'' .PP Scalars are treated as vectors with null imaginary component. For example, .CW 1 is treated as .CW (1,0) . .PP The scale of the coordinate system in which \*(id programs are written is entirely a matter of convenience. The \*(id processor proper produces output in the same coordinate system as the input. Postprocessors (``filters'') scale this coordinate system to make sense for the device on which the picture is displayed. .PP Some of the pictures below include captions keyed to the associated programs. Some of the labels are not produced by the program: I added them to help explain the picture. Such labels are parenthesized. For pictures and programs that have not been labeled, you may find that labeling them yourself helps you understand the program. .NH 1 Elements of the \*(id Language .PP This section presents statements that make up the fundamental units of \*(id programs, so the displayed program text represents fragments of complete programs. Text that appears between .CW /* and .CW */ is a comment. .NH 2 Boxes .PP The building blocks of \*(id programs are called .I boxes , which readers familiar with programming may think of as procedures or subroutines. In fact, the picture drawn by an \*(id program must itself be a box, called .I main ; we suppress this extra level of box-nesting for all of Section 3. .NH 3 Defining Boxes .PP Here is a simple box and one instance of it: .Ks .IS ...libfile rect ...width 0.5 ...colwid 8.0 main { put A:rect { ht = 0.5; wd = 1; sw = 0; }; right '(sw) ' at A.sw; right '(nw) ' at A.nw; left ' (se)' at A.se; left ' (ne)' at A.ne; } .IF .P1 rect { var ne, nw, se, sw, wd, ht; nw = sw + (0,1)*ht; ne = nw + wd; se = sw + wd; conn ne to nw to sw to se to ne; } .P2 .Ke .LP It is called .I rect , has six local complex variables: .I ne , .I nw , .I se , .I sw , .I wd , and .I ht , three equations among these variables, and an instruction to draw four lines. .NH 3 Placing Boxes .PP To ask for an instance of .I rect , we use a .I \fIput\fP statement with a .I "parameter section" containing enough additional equations that the local variables of this instance of .I rect are determined uniquely. For example, we might give the dimensions .I ht "" ( and .I wd ) and one of the corners (say .I sw ): .Ks .IS ...width 0.25 main { put rect { sw = 0; ht = 1; wd = 0.5; }; } .IF .P1 put rect { ht = 2; wd = 1; sw = 0; }; .P2 .Ke Any of the following \fIput\fP statements would draw the same rectangle: (C programmers will recognize the .CW /* and .CW */ comment brackets.) .P1 /* giving one corner, one dimension and a relation on the dimensions */ put rect { ht = 2; wd = 0.5*ht; nw = (0,2); }; .P2 .P1 /* giving two adjacent corners and the perpendicular dimension */ put rect { nw = (0,2); sw = 0; wd = 1; }; .P2 .P1 /* giving two diagonal corners and a relation on the dimensions */ put rect { ne = (1,2); sw = 0; wd = 0.5*ht; }; .P2 .P1 /* giving three corners */ put rect { ne = (1,2); nw = (0,2); se = 1; }; .P2 .P1 /* giving the center of a side, a corner, and another dimension */ put rect { (nw+sw)/2 = (0,1); nw = (0,2); wd = 1; }; .P2 .LP The \fIput\fP statement is to \*(id what a procedure call is to a conventional programming language. The difference is that none of the variables of a box must be specified to be a parameter whose value is expected in any call: any of the box's variables may be set by the \fIput\fP statement, and \*(id will determine the rest by means of the equations in the definition of the box. This means we can give whatever information we know about this instance of the box, as long as it is enough to determine everything uniquely. This mechanism is useful because we often want to set down a rectangle by giving one of its corners, but not necessarily the same corner each time; in a conventional programming language, we would need to provide a different procedure for each corner, and the code to solve for the other corners from it. (Almost certainly, not all of the \fIput\fP statements above are equally useful; but it is good to be able to use any of them when the need arises.) .Ts Here is how \*(id solves the system of equations implicit in a program: First, all equations are placed on a queue. Every time a box is called, the solutions to these equations may\(emand probably will\(embe different, so all the equations of a box are enqueued separately for each time that box is put. (Of course, different copies of the equation refer to different copies of the variable, but \*(id keeps that straight.) During processing, \*(id maintains two classes of variables: dependent and independent. Each dependent variable is represented as a linear combination of independent variables plus a constant term. (Variables whose values are known are a trivial case of dependent variables.) All variables start out independent. As long as there are equations on the queue, \*(id examines the head equation: if, after substituting for all dependent variables, the equation is linear, \*(id determines new information from it if possible\(emthat is, \*(id tries to make one variable dependent on the others, thus reducing the number of independent variables\(emor decides whether it is redundant or inconsistent; if the equation remains non-linear after substitution, \*(id adds it to the end of the queue and proceeds. If \*(id ever goes through the whole queue without discovering any new information, the system cannot be solved (by \*(id, anyway) and \*(id complains bitterly. If there are any independent variables left after this processing, \*(id will complain too, because there is no way they can become known. .Te .PP The variables .I wd and .I ht above are complex numbers just like the corners, so we can rotate the rectangle by giving them complex non-real values: .Ks .IS ...width 0.5 main { put rect { sw = 0; wd = (1,1)/abs((1,1)); ht = 2*wd; }; } .IF .P1 put rect { sw = 0; wd = (1,1)/abs((1,1)); ht = 2*wd; }; .P2 .Ke .LP (The double parentheses are needed in .CW abs((1,1)) because .CW abs(1,1) is parsed as a function with two arguments.) One point about this example often confuses new users: the vectors .I wd and .I ht point in the .I same direction. It is in the definition of .I rect that the $ht$ vector is rotated ninety degrees and added to the southern points to arrive at the northern points. Thus, if we give a .I ht that is perpendicular to .I wd , we get a very flat rectangle. .PP On the other hand, the definition of .I rect does not assure that .I ht and .I wd will point in the same direction. This \fIput\fP statement draws a parallelogram: .Ks .IS ...width 0.25 main { put rect { ht = 1; wd = (1,1)/abs((1,1)); sw = 0; }; } .IF .P1 put rect { ht = 1; wd = (1,1)/abs((1,1)); sw = 0; }; .P2 .Ke .LP Some people who feel that a box called .I rect should draw only rectangles are disturbed by this example. One remedy is to add another equation to the definition of .I rect , asserting that two adjacent sides are perpendicular; \*(id will complain if this equation is not satisfied (although it won't stop drawing). One such equation is .P1 wd/abs(wd) = ht/abs(ht); .P2 .PP Here is a definition for box .I arrow that keeps the head of the arrow symmetrical about its shaft: .Ks .IS arrow { var hd, tl, head, wing; head = 0.2; wing = head*(tl-hd)/abs(tl-hd); conn hd to tl; conn hd + cis(25)*wing to hd to hd + cis(-25)*wing; } main { put arrow { hd = 0; tl = (1,1); }; } .IF .P1 arrow { var hd, tl, head, wing; head = 0.1; wing = head*(tl-hd)/abs(tl-hd); conn hd to tl; conn hd + cis(25)*wing to hd to hd + cis(-25)*wing; } .P2 .Ke .LP Note the definition of .I wing in the example above: the second part of the expression is a unit vector that points from .I tl to .I hd ; this is multiplied by .I head , the length of the ``wings'' on the arrowhead. .Ts When a \fIput\fP statement is interpreted, the equations in its parameter section are processed before the equations in its definition. Thus, if there are inconsistent equations between the \fIput\fP statement and the box definition, the equations in the \fIput\fP statement take precedence. This can be useful to provide default values for variables of a box. For instance, if the definition of .I rect gave such default values to .I ht and .I wd , they would take effect unless overridden by equations in the parameter section of the \fIput\fP statement. .Te .Ts .I Ideal ignores inconsistent equations, but it does generate error messages about them. To avoid this error message about a particular equation, use a tilde .CW ~ instead of an equals sign in the equation. The tilde does .I not give the equation any lower ``priority'' than an equation with an equals sign: all it does is shut off the error message. So, the two ordered systems .CW "x = 1" , .CW "x ~ 2" and .CW "x ~ 2" , .CW "x = 1" are .I different . In the former, $x$ receives the value 1, and no error message appears when the second equation is processed; in the latter, $x$ receives the value 2, and an error is generated when the second equation is encountered. .Te .NH 2 Special Boxes\(emCircles and Arcs .PP Boxes to draw circles and circular arcs are defined in special library files. .NH 3 Circles .PP The box named .I circle has five local variables: .I center , .I radius , .I z1 , .I z2 , and .I z3 . The last three may be any points on the circle. As above, we must give enough information to determine a circle; giving two points on it is insufficient, and giving three collinear points is inconsistent. Here are three ways to draw a circle of radius one centered at the origin. .Ks .IS ...width 0.5 ...libfile circle dot { var center; center = ZAP.center; put ZAP:circle { radius = 0.1; }; } main { put A:circle { center = 0; radius = 1; }; left ' (z1)' at cis(0); right '(z2) ' at cis(135); right '(z3) ' at cis(245); put dot {center = cis(0);}; put dot {center = cis(135);}; put dot {center = cis(245);}; } .IF .P1 /* giving center and radius */ put circle { center = 0; radius = 1; }; .P2 .Ke .P1 /* giving center and a point on the circle */ put circle { center = 0; z1 = 1; /* could also have given z2 or z3 */ }; .P2 .P1 /* giving three points on the circle */ put circle { z1 = (1,0); z2 = cis(135); z3 = cis(235); }; .P2 .LP Once the circle has been determined, all five of its internal variables are known. So, if we ask for a circle giving three points, the radius and center will be known afterward. On the other hand, if we ask for a circle by giving the center and the radius, the three points $z1$, $z2$, and $z3$, will be known, and will be on the circle, but there is no guarantee where they will be. .NH 3 Arcs .PP Box .I arc has eight local variables: .I center , .I radius , .I start , .I midway , .I end , .I startang , .I midang , and .I endang . It is an arc centered at point .I center with radius .I radius , starting at point .I start at an angle .I startang , passing through point .I midway at an angle .I midang , and ending at point .I end at an angle .I endang . (All angles are measured with respect to $center$, in degrees, with the positive $x$-axis taken to be zero degrees, and the counterclockwise direction to be positive.) Note that $midway$ is .I not necessarily the midpoint of the arc! If neither .I midway nor .I midang is given, the arc is drawn counterclockwise from .I start to .I end . Once again, a variety of \fIput\fP statements draw the same arc: .Ks .IS ...libfile arc main { put A:arc { start = 1; end = cis(235); center = 0; }; left ' (start)' at A.start; right '(midway) ' at cis(180); left ' (end)' at A.end; put dot {center = A.start;}; put dot {center = cis(180);}; put dot {center = A.end;}; } .IF .P1 /* giving center, radius, and starting and ending angles */ put arc { center = 0; radius = 1; startang = 0; endang = 235; }; .P2 .Ke .P1 /* giving center, starting point, and ending angle */ put arc { center = 0; start = 1; endang = 235; }; .P2 .Ks .IS main { put A:arc { start = 1; end = cis(235); center = 0; }; left ' (end)' at A.start; right '(midway) ' at cis(180); left ' (start)' at A.end; put dot {center = A.start;}; put dot {center = cis(180);}; put dot {center = A.end;}; } .IF .P1 /* giving three points on the arc */ put arc { start = cis(235); midway = -1; end = 0; }; .P2 .Ke .NH 2 Other Elements of \*(id Pictures .PP .I Ideal pictures also may contain text and splines. .NH 3 Text Captions .PP There are three commands to place text with respect to a point. The .I left command left-justifies the text with respect to the specified location: the text will start there. The .I right command right-justifies the text so that it ends at the specified location. The default is to center the text at the point. (The arrows in this picture point to the locations of the named points.) .Ks .P1 left "some text" at x; .P2 .P1 \&"some centered text" at y; .P2 .P1 right "some other text" at z; .P2 .IS ...colwid 6.0 ...width 4.0 arrow { var tl, hd, head, perp, headang; conn tl to hd; perp = head*(tl-hd)/abs(tl-hd); conn hd + cis(headang)*perp to hd to hd + cis(-headang)*perp; head ~ 0.1; headang ~ 25; var start, end; start = tl; end = hd; } main { var x, y, z; var offset; offset = (1,-1); x = 0; left 'some text' at x; put arrow { start = x + offset; end = x; left ' (x)' at start; }; y = 3; 'some centered text' at y; put arrow { start = y + offset; end = y; left ' (y)' at start; }; z = 6; right 'some other text' at z; put arrow { start = z + offset; end = z; left ' (z)' at start; }; } .IE .Ke .LP To include a double quote mark in a string, escape it with a back-slash. If you have a line running through a point at which you place some text, you may want to add a space to one end of the text so that the line doesn't chop through the text. The string may include .I troff special characters (like .CW \e(bu ) and commands to other preprocessors (notably .I eqn ), but \*(id should be run before any other preprocessors. .NH 3 Splines .PP .I Ideal provides quadratic splines that are drawn with a B-spline basis: the user supplies a sequence of guiding points, and \*(id draws a smooth curve that is tangent to the polygonal path they define at the midpoint of each segment; the spline also starts at the first point and ends at the last. .Ks .IS ...colwid 8.0 ...width 1.0 ...maxx 2.0 ...minx -1.0 ...maxy 2.0 ...miny 0.0 main { var x, y, z, w; x = 0; y = 1; z = (-1,1); w = (2,2); spline x to y to z to w; right '(x) ' at x; left ' (y)' at y; right '(z) ' at z; left ' (w)' at w; put dot {center = x;}; put dot {center = y;}; put dot {center = z;}; put dot {center = w;}; } .IF .P1 spline x to y to z to w; .P2 .Ke .NH 1 Putting Boxes Together .PP In this section we present several complete \*(id programs to show how to build up pictures from boxes. .NH 2 Naming Instances of Boxes .PP Most pictures involve more than placements of simple boxes. And when pictures involve several boxes, certain geometrical relationships should exist among them. We can refer to the local variables of a box that has been put\(emall we need to do is name the \fIput\fP statement. Here is a simple example showing a diagram that could be used to illustrate Pythagoras's Theorem for isoceles triangles: .Ks .IS ...libfile rect ...width 1.0 main { put first: rect { sw = 0; ht = wd = 1; }; put next: rect { nw = first.se; ht = wd = first.ht; }; put last: rect { sw = first.ne; se = next.ne; ht = wd; }; } .IF .P1 main { put first: rect { sw = 0; ht = wd = 1; }; put next: rect { nw = first.se; ht = wd = first.ht; }; put last: rect { sw = first.ne; se = next.ne; ht = wd; }; } .P2 .Ke .LP First we place an instance of .I rect called .I first . Then we place another .I rect with its upper left corner ($ne$) at the lower right of .I first . Finally, we draw a third square, two of whose adjacent points are identified with points on the first two squares placed. We could have used any two adjacent points on .I last : if we had placed $last.ne$ at $first.ne$ and $last.nw$ at $next.ne$, \*(id would have figured all the relationships out right, although the program might be quite inscrutable to humans. .PP How can we circumscribe a triangle? If we name the \fIput\fP statement that produces the instance, we can just give its three vertices as values for $z1$, $z2$, and $z3$ in an instance of .I circle . .Ks .IS ...libfile circle triangle { var z1, z2, z3; conn z1 to z2 to z3 to z1; } main { put T: triangle { z1 = 0; z2 = 1; z3 = (2,2); }; put circle { z1 = T.z1; z2 = T.z2; z3 = T.z3; }; } .IF .P1 triangle { var z1, z2, z3; conn z1 to z2 to z3 to z1; } main { put T: triangle { z1 = 0; z2 = 1; z3 = (2,2); }; put circle { z1 = T.z1; z2 = T.z2; z3 = T.z3; }; } .P2 .Ke .NH 2 Parameter Section Commands .PP Any \*(id statement may appear in the parameter section of a \fIput\fP statement. This section illustrates some uses of this feature. .PP Suppose we will need to draw pictures of a linked list before and after insertion of a new node. We might start with this definition of a .I listnode : .Ks .IS ...width 1.0 ...colwid 8.0 listnode { put info: rect { var hook; hook = (nw + sw)/2; ht = lht; wd = lwd/2; }; put next: rect { var c; c = (nw + se)/2; sw = info.se; ht = lht; wd = lwd/2; }; } main { put listnode { info.sw = 0; var lht, lwd; lht = 1; lwd = 2; }; } .IF .P1 listnode { put info: rect { var hook; hook = (nw + sw)/2; ht = lht; wd = lwd/2; }; put next: rect { var c; c = (nw + se)/2; sw = info.se; ht = lht; wd = lwd/2; }; } .P2 .Ke .LP This version of .I listnode depends on .I rect . Notice that it references two variables ($lwd$ and $lht$) that are not local to itself: these variables must be defined in any environment in which $listnode$ is put. (They are global to it.) We have added statements that define new variables ($hook$ in $first$ and $c$ in $next$) that are local to the particular instance of .I rect . .PP Now we draw the list as it is before insertion: .Ks .IS arrow { var hd, tl, head, wing; head = 0.2; wing = head*(tl-hd)/abs(tl-hd); conn hd to tl; conn hd + cis(25)*wing to hd to hd + cis(-25)*wing; } ...width 2.0 main { var lht, lwd; lht = 1; lwd = 2*lht; put first: listnode { info.sw = 0; }; put last: listnode { info.sw = 2[first.info.sw,first.next.se]; conn next.sw to next.ne; }; put new: listnode { info.nw = 2[first.next.ne,first.next.se]; }; put arrow { hd = last.info.hook; tl = first.next.c; }; put arrow { hd = new.info.hook; tl = hd - 1; right 'new ' at tl; }; put arrow { hd = first.info.hook; tl = hd - 1; right 'list ' at tl; }; } .IF .P1 main { var lht, lwd; lht = 1; lwd = 2*lht; put first: listnode { info.sw = 0; }; put last: listnode { info.sw = 2[first.info.sw,first.next.se]; conn next.sw to next.ne; }; put new: listnode { info.nw = 2[first.next.ne,first.next.se]; }; put arrow { hd = last.info.hook; tl = first.next.c; }; put arrow { hd = new.info.hook; tl = hd - 1; right "new " at tl; }; put arrow { hd = first.info.hook; tl = hd - 1; right "list " at tl; }; } .P2 .Ke .LP Here we have added statements directly to the parameter section of two of the calls to .I arrow to avoid either naming each instance of .I arrow or naming the tails of these arrows so that text could be placed there. We have also added a statement to draw the null pointer in box .I last . .PP The program to draw the list after insertion of the new node remains largely unchanged: the nodes haven't moved; only the arrows hooking them together have moved. .Ks .IS main { var lht, lwd; lht = 1; lwd = 2*lht; put first: listnode { info.sw = 0; }; put last: listnode { info.sw = 2[first.info.sw,first.next.se]; conn next.sw to next.ne; }; put new: listnode { info.nw = 2[first.next.ne,first.next.se]; }; put arrow { hd = new.info.nw; tl = first.next.c; }; put arrow { hd = last.info.sw; tl = new.next.c; }; put arrow { hd = new.info.hook; tl = hd - 1; right 'new ' at tl; }; put arrow { hd = first.info.hook; tl = hd - 1; right 'list ' at tl; }; } .IF .P1 main { var lht, lwd; lht = 1; lwd = 2*lht; put first: listnode { info.sw = 0; }; put last: listnode { info.sw = 2[first.info.sw,first.next.se]; conn next.sw to next.ne; }; put new: listnode { info.nw = 2[first.next.ne,first.next.se]; }; /* These two arrows are different: */ put arrow { hd = new.info.nw; tl = first.next.c; }; put arrow { hd = last.info.sw; tl = new.next.c; } /* These are the same as before: */ put arrow { hd = new.info.hook; tl = hd - 1; right "new " at tl; }; put arrow { hd = first.info.hook; tl = hd - 1; right "list " at tl; }; } .P2 .Ke .NH 2 A Shorter Version .PP The previous example is somewhat long-winded because it demonstrates many features of \*(id that really aren't needed. For instance, it would probably have made better sense to define .I listnode as a basic element rather than building it out of .I rect s. This would reduce the need for delving deep into the internals of boxes from outside (e.g. .I first.info.hook ). Here is the same example reworked: .Ks .IS ...width 2 listnode { var n, s, e, w, ne, nw, se, sw, next; n = s + (0,1)*lht; ne = n + 0.5*lwd = nw + lwd; se = s + 0.5*lwd = sw + lwd; e = (ne + se)/2; w = (nw + sw)/2; next = (ne + s)/2; conn nw to ne to se to sw to nw; conn n to s; } main { var lht, lwd; lht = 1; lwd = 2; put first: listnode { sw = 0; }; put last: listnode { sw = 2[first.sw,first.se]; conn s to ne; }; put new: listnode { nw = 2[first.ne,first.se]; }; put arrow { hd = new.nw; tl = first.next; }; put arrow { hd = last.sw; tl = new.next; }; put arrow { hd = new.w; tl = hd - 1; right "new " at tl; }; put arrow { hd = first.w; tl = hd - 1; right "list " at tl; }; } .IE .P1 listnode { var n, s, e, w, ne, nw, se, sw, next; n = s + (0,1)*lht; ne = n + 0.5*lwd = nw + lwd; se = s + 0.5*lwd = sw + lwd; e = (ne + se)/2; w = (nw + sw)/2; next = (ne + s)/2; conn nw to ne to se to sw to nw; conn n to s; } main { var lht, lwd; lht = 1; lwd = 2; put first: listnode { sw = 0; }; put last: listnode { sw = 2[first.sw,first.se]; conn s to ne; }; put new: listnode { nw = 2[first.ne,first.se]; }; put arrow { hd = new.nw; tl = first.next; }; put arrow { hd = last.sw; tl = new.next; }; put arrow { hd = new.w; tl = hd - 1; right "new " at tl; }; put arrow { hd = first.w; tl = hd - 1; right "list " at tl; }; } .P2 .Ke Notice that equations need not be just a left side and a right side: they may include several expressions that should be equal. ........... .NH 1 Iteration of \*(id Constructs .NH 2 Pens .PP In Donald Knuth's \s-2METAFONT\s0|reference(knuth metafont) system, pens are different shapes\(emcircles, ellipses, and polygons\(emthat draw along curves. .I Ideal includes pens as a generalization of this idea: .I any box may be used to draw along a line. For instance, users may define ``dashed'' or ``dotted'' pens. A pen statement looks like this: .SP .P1 conn $x$ to $y$ using $n$ $pen$ { ... } <$a$,$b$>; .P2 (The only keywords in this statement are $conn$, $"to"$, and $using$.) .I Pen may be any box. .I Ideal will place $n$ copies of .I pen in the space from $x$ to $y$. $a$ and $b$ are expressions known to .I pen . The first instance of .I pen will have $a$ at $x$, and the last instance of .I pen will have $b$ at $y$; every instance in between will have its $a$ at the preceding one's $b$, and its $b$ at the succeeding one's $a$, as shown in this picture: .Ks .IS ...colwid 6.0 ...width 4.0 ...libfile rect pen { var e, w, c; var s; e = r.e; s = r.s; w = r.w; c = r.c; put r: rect { ht = wd = 1; }; left ' \f2a\fP' at w; right '\f2b\fP ' at e; } main { put first: pen { w = 0; right '\f2x\fP ' at w; '1' at s+(0,0.1); }; put second: pen { w = first.e; '2' at s+(0,0.1); }; '...' at (second.e + ipth.w)/2; put ipth: pen { w = 2[second.w,second.e]; '\f2i\fP\-1' at s+(0,0.1); }; put ith: pen { w = ipth.e; '\f2i\fP' at s+(0,0.1); }; put inth: pen { w = ith.e; '\f2i\fP+1' at s+(0,0.1); }; '...' at (inth.e + last.w)/2; put last: pen { w = 2[inth.w,inth.e]; '\f2n\fP' at s+(0,0.1); left ' \f2y\fP' at e; }; } .IE .Ke .PP Here is an example box that contains an angular wavy path: .Ks .IS ...colwid 9.0 ...width 1.0 wavy { var start, end, perp, pt1, pt2, ht; perp = (0,1)*(start - end)/abs(start - end); pt1 = 0.25[start,end] + perp*ht; pt2 = 0.75[start,end] - perp*ht; conn start to pt1 to pt2 to end; } main { put wavy { start = 0; end = 1; ht = 0.2; right '(start) ' at start; left ' (end)' at end; }; } .IE .P1 wavy { var start, end, perp, pt1, pt2, ht; perp = (0,1)*(start - end)/abs(start - end); pt1 = 0.25[start,end] + perp*ht; pt2 = 0.75[start,end] - perp*ht; conn start to pt1 to pt2 to end; } .P2 .Ke .LP Here we use .I wavy as a pen to indicate that part of a rectangle is missing. .Ks .IS main { var ne, nw, se, sw; var n1, s1, n2, s2; sw = 0; ne = nw + 2; se = sw + 2; ne = se + (0,1); n2 - 0.4 = n1 = 0.6[nw,ne]; s2 - 0.4 = s1 = 0.4[sw,se]; conn n1 to nw to sw to s1; conn n1 to s1 using int(5*abs(n1-s1)) wavy { ht = -0.1; } <start,end>; conn n2 to ne to se to s2; conn n2 to s2 using int(5*abs(n2-s2)) wavy { ht = -0.1; } <start,end>; } .IF .P1 main { var ne, nw, se, sw; var n1, s1, n2, s2; ne = nw + 2; se = sw + 2; ne = se + (0,1); n2 - 0.4 = n1 = 0.6[nw,ne]; s2 - 0.4 = s1 = 0.4[sw,se]; conn n1 to nw to sw to s1; conn n1 to s1 using int(5*abs(n1-s1)) wavy { ht = -0.1; } <start,end>; conn n2 to ne to se to s2; conn n2 to s2 using int(5*abs(n2-s2)) wavy { ht = -0.1; } <start,end>; } .P2 .Ke .LP We can change .I wavy to contain a smooth wave by changing the word .I conn in the fifth line of its definition to .I spline , and use the same instructions above to draw the picture with the new pen. .Ks .IS wavy { var start, end, perp, pt1, pt2, ht; perp = (0,1)*(start - end)/abs(start - end); pt1 = 0.25[start,end] + perp*ht; pt2 = 0.75[start,end] - perp*ht; spline start to pt1 to pt2 to end; } main { var ne, nw, se, sw; var n1, s1, n2, s2; sw = 0; ne = nw + 2; se = sw + 2; ne = se + (0,1); n2 - 0.4 = n1 = 0.6[nw,ne]; s2 - 0.4 = s1 = 0.4[sw,se]; conn n1 to nw to sw to s1; conn n1 to s1 using int(5*abs(n1-s1)) wavy { ht = -0.1; } <start,end>; conn n2 to ne to se to s2; conn n2 to s2 using int(5*abs(n2-s2)) wavy { ht = -0.1; } <start,end>; } .IE .Ke .NH 2 Pens as For-Statements .PP If \*(id had a for-statement, the pen statement .P1 conn $x$ to $y$ using $n$ $pen$ { ... } <$a$,$b$>; .P2 .LP would be equivalent to .P1 for $i$ = 1 to $n$ { put $pen$ { $a$ = (($i$-1)/$n$)[$x$,$y$]; $b$ = ($i$/$n$)[$x$,$y$]; ... }; } .P2 .Ts This means a pen statement can be used to synthesize a for-statement in an \*(id program. Here is a pen statement to draw a dashed arc: .Ks .IS ...libfile arc main { conn 0 to 180 using 10 arc { center = 0; radius = 1; }<startang, 9+endang>; } .IF .P1 conn 0 to 180 using 10 arc { center = 0; radius = 1; }<startang, 9+endang>; .P2 .Ke .Te .Ts To draw a set of concentric circles we can say: .Ks .IS ...libfile circle main { conn 1 to 6 using 5 circle { center = 0; } <radius, radius+1>; } .IF .P1 main { conn 1 to 6 using 5 circle { center = 0; } <radius,radius+1>; } .P2 .Ke .Te .Ts In the first example, the second expression in angle brackets is important: it means that each ``dash'' covers nine degrees. In the second example, this expression is redundant: its only purpose is to prevent \*(id from generating a stream of error messages about inconsistent equations. This contorted way of simulating for-statements is necessary only because pens are meant to do the right thing at each end of and all along a path that is being drawn with some box, and not as general iteration constructs. .Te .Ts Why doesn't \*(id have for-statements? Notice that every variable in an \*(id program is assigned a value exactly once. Obviously the index of a for-statement must be an exception to that rule. Generating the index internally prevents the need to have two kinds of variables\(emchanging and fixed. The local variables of boxes placed by pen statements cannot be referenced outside the pen statement, because the boxes are not named when they are placed. A general for-statement could lead to the need to generate automatically names for different instances of boxes, a hard problem that I don't understand well enough yet. .Te .......... .NH 2 Filling Regions .PP Because any box can be used as a pen, we can shade regions. (We can have not only dashed and dotted ink, but checkered paint!) Take box .I wavy , for instance. First we construct a box .I brush , which consists of seven copies of .I wavy going horizontally: .Ks .IS wavy { var start, end, perp, pt1, pt2, ht; perp = (0,1)*(start - end)/abs(start - end); pt1 = 0.25[start,end] + perp*ht; pt2 = 0.75[start,end] - perp*ht; conn start to pt1 to pt2 to end; } brush { var top, bot; var bwd, bht; var leftpt, rightpt; leftpt = 0.5*(top+bot) - bwd/2; rightpt = 0.5*(top+bot) +bwd/2; conn leftpt to rightpt using 7 wavy { ht = bht; }<start,end>; } main { put brush { leftpt = 0; rightpt = 7; bht = 0.3; }; } .IF .P1 brush { var top, bot; var bwd, bht; var leftpt, rightpt; leftpt = 0.5*(top+bot) - bwd/2; rightpt = 0.5*(top+bot) + bwd/2; conn leftpt to rightpt using 7 wavy { ht = bht; }<start,end>; } .P2 .Ke Then we use ``brush'' to draw vertically over the region of interest. .Ks .IS background { conn (0,1) to (0,-1) using 6 brush { bwd = 2; bht = 0.1; }<top,bot>; } main { put background { }; } .IF .P1 conn (0,1) to (0,-1) using 6 brush { bwd = 2; bht = 0.1; }<top,bot>; .P2 .Ke .NH 1 Opaque Boxes .PP .I Ideal includes statements to blot out pieces of a picture. In this section we will sometimes place opaque boxes without explicitly drawing any background. In such cases, assume that we have painted over the area with pens as above. .NH 2 Opaque Polygons .PP .I Ideal needs to know the vertices of a polygon in order to opaque the area it covers. The vertices are specified as a list in a .I boundary statement. For instance, to opaque a rectangular region using the .I rect box defined in Section 3, we could use the following statement: .Ks .IS ...libfile rect wavy { var start, end, perp, pt1, pt2, ht; perp = (0,1)*(start - end)/abs(start - end); pt1 = 0.25[start,end] + perp*ht; pt2 = 0.75[start,end] - perp*ht; conn start to pt1 to pt2 to end; } brush { var top, bot; var bwd, bht; var leftpt, rightpt; leftpt = 0.5*(top+bot) - bwd/2; rightpt = 0.5*(top+bot) +bwd/2; conn leftpt to rightpt using 7 wavy { ht = bht; }<start,end>; } background { conn (0,1) to (0,-1) using 7 brush { bwd = 2; bht = 0.1; }<top,bot>; } main { put background { }; put rect { opaque; boundary = sw, se, ne, nw; sw = (-0.4,-0.4); wd = ht = 1; }; } .IF .P1 put rect { opaque; boundary= sw, se, ne, nw; sw = (-0.4,-0.4); wd = ht = 1; }; .P2 .Ke .LP The sides of the rectangle are drawn by .I rect : they are not supplied automatically by the opaquing routine. If we wanted to save only the interior of the rectangle, we could use almost the same statement: .Ks .IS ...minx -1.0 ...maxx 1.0 ...miny -1.0 ...maxy 1.0 main { put background { }; put rect { opaque exterior; boundary = sw, se, ne, nw; sw = (-0.4,-0.4); wd = ht = 1; }; } .IF .P1 put rect { opaque exterior; boundary = sw, se, ne, nw; sw = (-0.4,-0.4); wd = ht = 1; }; .P2 .Ke .PP If we plan to opaque a lot of rectangles, we should include a .I boundary in the definition of .I rect . Such a default .I boundary would be referenced only if the parameter section of the \fIput\fP statement included an opaque statement .I and did not include its own .I boundary . .NH 2 Opaque Circular Arc Polygons .PP The edges of opaque regions can also be circular arcs. .Ts This generalization of the simple boundary statement is the most recent addition to \*(id. It avoids treating circles and their sectors and segments as special cases, and makes opaquing circular arc polygons much easier. .Te .LP To specify a circular arc edge, one gives its endpoints and a point through which it passes; this ``pass-through'' point is marked in the boundary list by the symbol ``\f8^\fP''. For example, the boundary list for the sector shown below is .Ks .IS ...libfile arc main { put background { }; put arc { center = (-1,-1); radius = 2; startang = 30; endang = 60; boundary = center, center+radius*cis(startang), ^center+radius*cis(0.5*(startang+endang)), center+radius*cis(endang); opaque; conn start to center to end; }; } .IF .P1 boundary = center, cis(30), ^ cis(45), cis(60); .P2 .Ke .PP Another common opaque arc is the segment: .Ks .IS main { put background { }; put arc { center = (-1,-1); radius = 2; startang = 0; endang = 90; boundary = start, ^center + radius*cis(0.5*(startang+endang)), end; opaque; conn start to end; }; } .IF .P1 boundary = cis(0), ^ cis(45), cis(90); .P2 .Ke .PP One can construct an opaque circle out of two semicircular edges: .Ks .IS ...libfile circle main { put background { }; put circle { radius = 0.5; center = 0; opaque; }; put circle { radius = 1; center = 0; opaque exterior; }; } .IF .P1 boundary = cis(0), ^ cis(90), cis(180), ^ cis(270); .P2 .Ke .LP Here, the outer circle has an opaque exterior, while the inner circle has an opaque interior. .NH 2 Order is Important .PP Without the ability to opaque, the order in which boxes are put does not matter. But when some boxes are opaque, order obviously .I does matter. Put statements are executed in the order in which they appear in the box definition. When an opaque box is drawn, the opaquing is done .I first , then the lines of the box are drawn; so, for instance, an opaque .I listnode does include the line down its middle separating its .I info field from its .I next field. .NH 2 Some Hard Facts .PP Neither text nor splines can be used to opaque objects, nor will they be clipped properly if they are in a picture and an opaque box is placed over them. .Ts The problem with text is that \*(id operates as a .I troff preprocessor, so it cannot determine anything about the size of the text, and it needs to know that if it is to do anything involving opaquing and text. .Te .Ts The problem with splines is more subtle. When a line or circular arc is chopped, it is easy to specify the lines or circular arcs that remain. But when a spline is chopped, the guiding points of the resulting curve pieces are hard to determine. .Te .NH 1 Paper Commands .PP .I Ideal includes two commands that are analogous to the way people draw on paper. .NH 2 Construct .PP The .I construct statement looks just like a \fIput\fP statement, with the keyword .I put replaced by .I construct . It is best to think of it as laying a sheet of tracing paper over the current drawing. Anything constructed will be drawn on this sheet. When you return to the layer underneath, you may refer to any of the local variables (for example, with a \fIput\fP statement), but the lines and curves in the constructed picture don't show. .Ks .IS ...maxy 2.0 ...minx -1.0 ...maxx 1.0 ...libfile rect arrow main { construct A: rect { sw = 0; wd = ht = 1; }; construct B: rect { n = A.s - (0,1); wd = ht = 1; }; 'top' at A.c; 'bottom' at B.c; put arrow { hd = B.n; tl = A.s; }; } .IF .P1 main { construct A: rect { sw = 0; wd = ht = 1; }; construct B: rect { n = A.s - (0,1); wd = ht = 1; }; 'top' at A.c; 'bottom' at B.c; put arrow { hd = B.n; tl = A.s; }; } .P2 .Ke .LP Here we have used ``invisible boxes'' to place the arrow around the text without drawing the boxes. .NH 2 Draw .PP The .I construct command may also be used to localize the effects of opaque boxes. We saw above how to draw a filled polygon: paint over the area, then opaque the exterior of the polygon. But how can we draw .I two filled polygons in the same picture? .PP One solution is to construct them both, then use the .I draw command to add them to the main picture. The .I draw command transfers the contents of the named sheet of tracing paper to the sheet below. .Ks .IS ...libfile circle ...width 2.0 ...colwid 10.0 null { } pentagon { var center, radius, pt1, pt2, pt3, pt4, pt5; pt1 = center + radius; pt2 = center + cis(72)*radius; pt3 = center + cis(144)*radius; pt4 = center + cis(-144)*radius; pt5 = center + cis(-72)*radius; conn pt1 to pt2 to pt3 to pt4 to pt5 to pt1; boundary = pt1, pt2, pt3, pt4, pt5; } main { construct A: null { conn (0,1) to 0 using 7 brush { bwd = 1; bht = 0.1; }<top,bot>; put pentagon { center = (0,0.5); radius = (0,0.5); opaque exterior; }; }; construct B: null { conn (0.5,0.5) to (1.5,0.5) using 5 brush { bwd = (0,1); bht = 0.1; }<top,bot>; put circle { center = (1,0.5); radius = 0.5; opaque exterior; }; }; draw A; draw B; } .IF .P1 null { } pentagon { var center, radius, pt1, pt2, pt3, pt4, pt5; pt1 = center + radius; pt2 = center + cis(72)*radius; pt3 = center + cis(144)*radius; pt4 = center + cis(-144)*radius; pt5 = center + cis(-72)*radius; conn pt1 to pt2 to pt3 to pt4 to pt5 to pt1; boundary = pt1, pt2, pt3, pt4, pt5; } main { construct A: null { conn (0,1) to 0 using 7 brush { bwd = 1; bht = 0.1; }<top,bot>; put pentagon { center = (0,0.5); radius = (0,0.5); opaque exterior; }; }; construct B: null { conn (0.5,0.5) to (1.5,0.5) using 5 brush { bwd = (0,1); bht = 0.1; }<top,bot>; put circle { center = (1,0.5); radius = 0.5; opaque exterior; }; }; draw A; draw B; } .P2 .Ke .LP Even \fIput\fP statements can be added to the parameter sections of .I construct (and \fIput\fP) statements! Believe it or not, box .I null defined above is one of the most useful: it gives us a way to name a set of commands and variables so that we may reference them later, yet it doesn't require us to define a box that will be used only once. .PP Given .I construct and .I draw , why do we need a special box .I hole to opaque a circular area without drawing anything? Why not just construct an opaque circle, then draw it on the main picture? The problem is that the effect of opaquing is localized just as much as the drawing, so an opaque constructed circle won't opaque anything that lies underneath. ........ .NH 1 Library Files .PP Library files are available to draw common figures and for special figures like circles and arcs. To include a library file as part of an \*(id program, include the line .P1 \&...libfile $name$ .P2 in your \*(id program (between the .CW .IS and .CW .IE lines that mark its start and end.) This section describes available library files that have been alluded to up to now. .NH 2 Rectangle .PP File .I rect contains the definition of box .I rect , which looks like the definition in Section 3, with five more variables: $n$, $s$, $e$, $w$, and $c$, which are the four compass points and the center, respectively. .P1 rect { var ne, nw, sw, se, n, e, w, s, c, ht, wd; ne = se + (0,1)*ht; nw = sw + (0,1)*ht; ne = nw + wd; n = (ne+nw)/2; s = (se+sw)/2; e = (ne+se)/2; w = (nw+sw)/2; c = (ne+sw)/2; ht ~ 1; wd ~ 1.5; boundary = ne, nw, sw, se; conn ne to nw to sw to se to ne; } .P2 .NH 2 Arrow .PP File .I arrow contains the definition of box .I arrow as given in Section 3. .P1 arrow { var tl, hd, head, perp, headang; conn tl to hd; perp = head*(tl-hd)/abs(tl-hd); conn hd + cis(headang)*perp to hd to hd + cis(-headang)*perp; head ~ 0.1; headang ~ 25; } .P2 .NH 2 Wavy .PP Library file .I wavy contains the definition for the familiar .I wavy . .P1 wavy { var start, end, perp, pt1, pt2, ht; perp = (0,1)*(start - end)/abs(start - end); pt1 = 0.25[start,end] + perp*ht; pt2 = 0.75[start,end] - perp*ht; conn start to pt1 to pt2 to end; } .P2 .NH 2 Dash .PP Library file .I dash contains the definition of box .I dash , which may be useful for drawing dashed lines: .P1 dash { var start, end; conn start to 0.25[start,end]; conn 0.75[start,end] to end; } .P2 .......... .NH 2 Circles .PP Box .I circle resides in a library file of the same name, and has the local variables described in Section 3: $center$, $radius$, $z1$, $z2$, and $z3$. .Ts Box .I circle actually consists of variable declarations and a put of box .I CIRCLE . So, if you will have many circles of a particular radius, it might be easier for you to define your own, say, $circle3$: .P1 ...libfile CIRCLE circle { var center, radius, z1, z2, z3; put CIRCLE { radius = 3; } } .P2 .Te .NH 2 Arcs .PP Box .I arc is contained in a library file of the same name, with local variables as described in Section 3: $center$, $radius$, $start$, $midway$, $end$, $startang$, $midang$, and $endang$. .Ts As above, .I arc calls on a box called .I ARC . .Te .NH 1 Examples .NH 2 B-Trees .PP This example depicts a B-tree|reference(btree) whose nodes have many children. .Ks .IS ...colwid 6.0 ...width 3.0 ...libfile rect arrow { var tl, hd, headvec, head; headvec = hd + head*(tl - hd)/abs(tl - hd); conn tl to hd; conn hd + cis(20)*(headvec - hd) to hd to hd + cis(-20)*(headvec - hd); } dot { var s, e; '\(bu' at 0.5[s, e] - (0,0.1); } per { var s, e; '.' at 0.5[s, e]; } main { var rw, rh; rw = 2; rh = 1; var hmv, vmv; hmv = 0.6; vmv = 0.3; var ah; ah = 0.2; put root:rect{ sw = (0,1); wd = rw; ht = 1; }; put next:rect{ sw = youngest.sw + 2*(hmv, vmv); wd = rw; ht = rh; }; put youngest:rect{ sw = bro.sw + (hmv, vmv); wd = rw; ht = rh; opaque; }; put bro:rect{ sw = eldest.sw + (hmv, vmv); wd = rw; ht = rh; opaque; }; put eldest:rect{ im(nw) = im(3[root.nw,root.sw]); re(0.5[nw, next.nw]) = re(0.5[root.nw, root.ne]); wd = rw; ht = rh; opaque; put arrow{ head = ah; tl = 0.0[sw, se] + 0.5 * (nw - sw); hd = tl - (0, 1) * cis(-27); }; put arrow{ head = ah; tl = 0.33[sw, se] + 0.5 * (nw -sw); hd = tl - (0,1) * cis(-9); }; put arrow{ head = ah; tl = 0.67[sw, se] + 0.5 * (nw - sw); hd = tl - (0,1) * cis(9); }; put arrow{ head = ah; tl = 1.0[sw, se] + 0.5 * (nw -sw); hd = tl - (0,1) * cis(27); }; }; put arrow{ head = ah; tl=root.sw + 0.5 * (root.nw - root.sw); hd=eldest.nw; }; put arrow{ head = ah; tl=0.1[root.sw,root.se] + 0.5 * (root.nw - root.sw); hd=bro.nw; }; put a:arrow{ head = ah; tl=0.2[root.sw,root.se] + 0.5 * (root.nw - root.sw); hd=youngest.nw; }; put b:arrow{ head = ah; tl = 0.5[root.ne, root.se]; hd = next.nw; }; put arrow{ head = ah; hd = root.nw; tl = 0.5[root.nw, root.ne] + (0, 1.0); }; conn next.nw to youngest.nw using 3 dot{}<s, e>; conn next.se to youngest.se using 3 dot{}<s, e>; conn b.tl to a.tl using 4 per{}<s, e>; } .IE .Ke .P1 \&...libfile rect arrow { var tl, hd, headvec, head; headvec = hd + head*(tl - hd)/abs(tl - hd); conn tl to hd; conn hd + cis(20)*(headvec - hd) to hd to hd + cis(-20)*(headvec - hd); } dot { var s, e; '\e(bu' at 0.5[s, e] - (0,0.1); } per { var s, e; '.' at 0.5[s, e]; } main { var rw, rh; rw = 2; rh = 1; var hmv, vmv; hmv = 0.6; vmv = 0.3; var ah; ah = 0.2; put root: rect { sw = (0,1); wd = rw; ht = 1; }; put next: rect { sw = youngest.sw + 2*(hmv, vmv); wd = rw; ht = rh; }; put youngest: rect { sw = bro.sw + (hmv, vmv); wd = rw; ht = rh; opaque; }; put bro: rect { sw = eldest.sw + (hmv, vmv); wd = rw; ht = rh; opaque; }; put eldest: rect { im(nw) = im(3[root.nw,root.sw]); re(0.5[nw, next.nw]) = re(0.5[root.nw, root.ne]); wd = rw; ht = rh; opaque; put arrow { head = ah; tl = 0.0[sw, se] + 0.5 * (nw - sw); hd = tl - (0, 1) * cis(-27); }; put arrow { head = ah; tl = 0.33[sw, se] + 0.5 * (nw -sw); hd = tl - (0,1) * cis(-9); }; put arrow { head = ah; tl = 0.67[sw, se] + 0.5 * (nw - sw); hd = tl - (0,1) * cis(9); }; put arrow { head = ah; tl = 1.0[sw, se] + 0.5 * (nw -sw); hd = tl - (0,1) * cis(27); }; }; put arrow { head = ah; tl=root.sw + 0.5 * (root.nw - root.sw); hd=eldest.nw; }; put arrow { head = ah; tl=0.1[root.sw,root.se] + 0.5 * (root.nw - root.sw); hd=bro.nw; }; put a: arrow { head = ah; tl=0.2[root.sw,root.se] + 0.5 * (root.nw - root.sw); hd=youngest.nw; }; put b: arrow { head = ah; tl = 0.5[root.ne, root.se]; hd = next.nw; }; put arrow { head = ah; hd = root.nw; tl = 0.5[root.nw, root.ne] + (0, 1.0); }; conn next.nw to youngest.nw using 3 dot{}<s, e>; conn next.se to youngest.se using 3 dot{}<s, e>; conn b.tl to a.tl using 4 per{}<s, e>; } .P2 .NH 2 A Sector Grid .PP Norm Schryer used this figure to show how numerical integration is used to find the area of planar regions. .Ks .IS ...colwid 8.0 ...width 2.0 ...libfile hole gridline { var a,b; var neg, pos; conn a - neg to a + pos; } main { var n; n = 21; conn (0,0) to (0,1+1/(n-1)) using n gridline {neg = 0; pos = 1;} <a,b>; conn (0,0) to (1+1/(n-1),0) using n gridline {neg = 0; pos = (0,1);} <a,b>; put hole { radius = 1; center = (0,0); opaque exterior; }; } .IF .P1 \&...libfile hole gridline { var a,b; var neg, pos; conn a - neg to a + pos; } main { var n; n = 21; conn (0,0) to (0,1+1/(n-1)) using n gridline { neg = 0; pos = 1; } <a,b>; conn (0,0) to (1+1/(n-1),0) using n gridline { neg = 0; pos = (0,1); } <a,b>; put hole { radius = 1; center = (0,0); opaque exterior; }; } .P2 .Ke .NH 2 Polygon Clipping .PP This example from |reference(pavlidis image processing) illustrates all possible positions of a line segment with respect to a polygon. .Ks .ps 8 .EQ delim $$ gsize 8 define P12 " P sub 1 ( P sub 2 ) " define P21 " P sub 2 ( P sub 1 )" .EN .IS ...width 4.5 ...colwid 6.0 ...libfile circle box spot{ var loc; put circle{ var center, radius; center = loc; radius = 0.02; opaque; }; } box vert{ var midd, llength; conn midd+llength to midd-llength; } box poly{ var c1,p1,p2,p3,p4,p5,r1,tp,bp,ta,tb; p1 = c1+r1; p2 = cis(65)[c1,p1]; p3 = cis(170)[c1,p1]; p4 = cis(190)[c1,p1]; p5 = cis(300)[c1,p1]; tp = c1+(0,ta)*r1; bp = c1+(0,tb)*r1; conn p1 to p2; conn p2 to p3; conn p3 to p4; conn p4 to p5; conn p5 to p1; put vert{ midd=c1; llength=(0,1.6)*r1; }; put spot{ loc=tp; }; put spot{ loc=bp; }; left '$P12$' at tp+0.1; left '$P21$' at bp+0.1; } ...minx -1 ...maxx 6 ...miny -1 ...maxy 12 box main { put poly{ c1 =(1,9); r1 = (1,0); ta=1.1; tb=1.4; left '$++--$' at p2-1.5; right '(a)' at c1-(0.7,1.2); }; put poly{ c1 =(3.5,9); r1 = (1,0); ta=1.1; tb=0.2; left '$+---$' at p2+0.5; left '$(-+--)$' at p2+(0.5,-0.15); right '(b)(b1)' at c1-(0.7,1.2); }; put poly{ c1 =(1,5); r1 = (1,0); ta=1.1; tb=-1; left '$+--+$' at p2-1.5; left '$(-++-)$' at p2-(1.5,0.15); right '(c)(c1)' at c1-(0.7,1.2); }; put poly{ c1 =(3.5,5); r1 = (1,0); ta=0.2; tb=-1; left '$---+$' at p2+(0.5,-0.3); left '$(--+-)$' at p2+(0.5,-0.45); right '(d)(d1)' at c1-(0.7,1.2); }; put poly{ c1 =(1,1); r1 = (1,0); ta=-1; tb=-1.4; left '$--++$' at p4-(0,0.5); right '(e)' at c1-(0.7,1.2); }; put poly{ c1 =(3.5,1); r1 = (1,0); ta=0.2; tb=-0.2; left '$----$' at p1+0.2; right '(f)' at c1-(0.7,1.2); }; } .IE .Ke .ps .P1 \&.ps 8 .EQ delim off .EN \&.EQ gsize 8 define P12 " P sub 1 ( P sub 2 ) " define P21 " P sub 2 ( P sub 1 )" \&.EN \&...libfile circle \&...minx -1 \&...maxx 6 \&...miny -1 \&...maxy 12 box spot{ var loc; put circle{ var center, radius; center = loc; radius = 0.02; opaque; }; } box vert{ var midd, llength; conn midd+llength to midd-llength; } box poly{ var c1,p1,p2,p3,p4,p5,r1,tp,bp,ta,tb; p1 = c1+r1; p2 = cis(65)[c1,p1]; p3 = cis(170)[c1,p1]; p4 = cis(190)[c1,p1]; p5 = cis(300)[c1,p1]; tp = c1+(0,ta)*r1; bp = c1+(0,tb)*r1; conn p1 to p2; conn p2 to p3; conn p3 to p4; conn p4 to p5; conn p5 to p1; put vert{ midd=c1; llength=(0,1.6)*r1; }; put spot{ loc=tp; }; put spot{ loc=bp; }; left '$P12$' at tp+0.1; left '$P21$' at bp+0.1; } box main { put poly{ c1 =(1,9); r1 = (1,0); ta=1.1; tb=1.4; left '$++--$' at p2-1.5; right '(a)' at c1-(0.7,1.2); }; put poly{ c1 =(3.5,9); r1 = (1,0); ta=1.1; tb=0.2; left '$+---$' at p2+0.5; left '$(-+--)$' at p2+(0.5,-0.15); right '(b)(b1)' at c1-(0.7,1.2); }; put poly{ c1 =(1,5); r1 = (1,0); ta=1.1; tb=-1; left '$+--+$' at p2-1.5; left '$(-++-)$' at p2-(1.5,0.15); right '(c)(c1)' at c1-(0.7,1.2); }; put poly{ c1 =(3.5,5); r1 = (1,0); ta=0.2; tb=-1; left '$---+$' at p2+(0.5,-0.3); left '$(--+-)$' at p2+(0.5,-0.45); right '(d)(d1)' at c1-(0.7,1.2); }; put poly{ c1 =(1,1); r1 = (1,0); ta=-1; tb=-1.4; left '$--++$' at p4-(0,0.5); right '(e)' at c1-(0.7,1.2); }; put poly{ c1 =(3.5,1); r1 = (1,0); ta=0.2; tb=-0.2; left '$----$' at p1+0.2; right '(f)' at c1-(0.7,1.2); }; } .P2 .NH 1 Acknowledgements .PP My thanks to all who read drafts of this manual and criticized constructively: Al Aho, Lorinda Cherry, Eric Grosse, Steve Johnson, Brian Kernighan, John Mashey, Doug McIlroy, and Theo Pavlidis; and to early users of \*(id, especially Eric Grosse, Theo Pavlidis, Norm Schryer, and Peter Weinberger. .NH 1 References .LP |reference_placement .FC .BP .2C .EQ delim $$ gsize 10 .EN ......... .NH 1 Ideal Reference Manual .PP .I Ideal programs are usually interpolated into .I troff input files. .Tm .IF S Each program must be preceded by a .CW .IS line, and followed by either a .CW .IE line or a .CW .IF line. The .CW .IE line directs that further text will follow the picture, while the .CW .IF line leaves the current position as it was when the .CW .IS line was encountered. .PP In this description, italicized names refer to nonterminal grammar symbols. A parenthesized construct followed by a star means zero or more repetitions of the construct within. Comments may appear in \*(id programs between .CW /* and .CW */ brackets (which nest), and between .CW # and newline. .NH 2 Elements of the \*(id Language .PP .I Ideal expects to be presented with a collection of boxes. .I Ideal prepares instructions to draw the picture specified by box .I main . If no such box exists, no picture is drawn, but the definitions that did appear are remembered. A box looks like this: .P1 $identifier$ { $(statement)*$ } .P2 .LP The sections below describe allowed statements. An $identifier$ is a sequence of letters and digits that starts with a letter. A $string$ is a sequence of characters between double quotes. .NH 3 Variable Declarations .P1 var $identifier$ $( font CW "," identifier)*$ ; .P2 .PP This declares complex variables for which space will be allocated whenever the box in which this statement appears is instantiated. These declarations should appear before any other statements, though this order is not enforced at the moment. .NH 3 Equations .P1 $expr$ = $expr$ $( ~ font CW "=" ~ expr)*$ ; $expr$ ~ $expr$ $( ~ font CW "~" ~ expr)*$ ; .P2 .PP These statements declare relations that should exist among the named variables. Equations that involve more than two expressions to be equated are processed left to right. The second form (an equation with a .CW ~ instead of an equals sign) requests that no error message be generated if the equation is inconsistent. Built-in operators include .CW + , .CW - (both unary and binary), .CW * , and .CW / , with unary minus binding more tightly than multiplication and division, which in turn bind more tightly than addition or subtraction. Parentheses may be used to enforce a particular order of evaluation. The expression .P1 ( $expr sub 1$ , $expr sub 2$ ) .P2 .LP has as real part the real part of $expr sub 1$ and as imaginary part the real part of $expr sub 2$. (The parentheses in this example are not metacharacters.) The expression .P1 $expr sub 1$ [ $expr sub 2$ , $expr sub 3$ ] .P2 .LP is evaluated as $expr sub 2 + expr sub 1 * ( expr sub 3 - expr sub 2 )$, that is ``$expr sub 1$ of the way from $expr sub 2$ to $expr sub 3$.'' Built-in functions are described below. .NH 4 Manipulating Complex Numbers .P1 re ( $expr$ ) im ( $expr$ ) conj ( $expr$ ) .P2 .PP These functions return, respectively, the real part, the imaginary part, and the complex conjugate of $expr$. .NH 4 Absolute Value .P1 abs ( $expr$ ) .P2 .PP This returns the absolute value of $expr$. .NH 4 Unit Vector Functions .P1 cis ( $expr$ ) E ( $expr$ ) unit ( $expr$ ) .P2 .PP Function $font CW "cis" ()$ returns a unit vector in the direction of its argument, which is interpreted as an angle. For $theta$ in radians, $font CW "cis" ~ theta$ is defined as $e sup { i theta } ~ == ~ cos ~ theta ~ + ~ i sin ~ theta$, and $font CW "E" (x)$ is defined as $font CW "cis" ~ 2 pi x$. If $theta$ is in degrees, which it usually is in .I ideal programs (see below), .I ideal makes the necessary conversion so that $font CW "cis" ()$ and $font CW "E" ()$ work right. .PP Function $font CW "unit" ()$ returns a unit vector in the direction of its argument, which is a vector. For $z$ a complex number, $font CW "unit" (z) ~ == ~ z / font CW "abs" (z)$. .NH 4 Floating Truncation .P1 int ( $expr$ ) .P2 .PP This function returns the integer part of the real part of $expr$. .NH 4 Inverse Trigonometric Function .P1 angle ( $expr$ ) .P2 .PP This function returns the arctangent of $font CW "im" (expr)/ font CW "re" (expr)$. .NH 3 Square Root Function .P1 sqrt ( $expr$ ) .P2 .PP This function returns the square root of its complex argument. .NH 3 Line Drawing .P1 conn $expr$ to $expr$ $($ to $~ expr)*$ ; .P2 .PP This statement directs that lines be drawn between each successive pair of points. .NH 3 Box Placement .P1 $[ident sub 1 font CW ":" ]$ put $ident sub 2$ { $(statement)*$ } ; put $ident sub 1$ : $ident sub 2$ { $(statement)*$ } ; .P2 .PP This statement directs that box $ident sub 2$ be instantiated with the statements in braces prepended to those already present in its definition. If $ident sub 1$ is present in the statement (the optional first form, or the second form), the instantiated box's name is $ident sub 1$, and a local variable $x$ of the box may be referenced as $ident sub 1$.$x$. .NH 3 Pen Drawing .P1 conn $expr sub 1$ to $expr sub 2$ using $expr sub 3$ $identifier$ { $(statement)*$ } < $expr sub 4$ , $expr sub 5$ > ; .P2 .PP This statement is shorthand for the following loop: .P1 0 for i = 1 to $expr sub 3$ by 1 put $identifier$ { $expr sub 4$ = ((i-1)/$expr sub 3$) [$expr sub 1$,$expr sub 2$]; $expr sub 5$ = (i/$expr sub 3$) [$expr sub 1$,$expr sub 2$]; $(statement)*$ }; .P2 .LP Note that it is different from mere text expansion because the upper limit ($expr sub 3$) may not be known explicitly when the program is written. Note too that \*(id does .I not include a for-statement! .NH 3 Drawing Splines .P1 spline $expr$ to $expr$ $( font CW "to" ~ expr)*$ ; .P2 .PP This statement draws a spline guided by the named points in order. .NH 3 Placing Captions .P1 left $string$ at $expr$; $string$ at $expr$; right $string$ at $expr$; .P2 .PP Use these statements to place $string$ at $expr$. The default is to center the string at $expr$. The keyword $"left"$ causes the string to start at $expr$, while the keyword $"right"$ causes the string to end there. .NH 3 Constructing and Drawing Boxes .P1 construct $ident sub 1$ : $ident sub 2$ { $(statement)*$ } ; $ident sub 1$ : construct $ident sub 2$ { $(statement)*$ } ; draw $identifier$ ; .P2 .PP The first two of these statements are like the $put$ statements defined above, but they add to a picture named $ident sub 1$, instead of to the current picture. The third directs that the named picture (created by a $construct$ command) be added to the current picture. .NH 3 Statements Related to Opaquing .P1 boundary = $expr$ , $expr$ $( font CW "," expr)+$ ; opaque [ interior ] ; opaque exterior ; .P2 .PP The $boundary$ statement specifies the vertices of a polygon to be opaqued. The first $opaque$ statement directs that the interior of the polygon be opaque. The second directs that the exterior be made opaque. If no boundary list is given for a circle, it will opaque a circular region. To get an opaque circular region without drawing the circle, use the box $hole$ instead. Two other boxes are available for opaquing: $sector$ opaques a sector of an arc and $segment$ opaques a segment of an arc. The variables in these boxes are the same as in $arc$. .NH 2 Command-Line Options .PP These options may appear on the command line. .NH 3 Selecting the Postprocessor .IP .CW -p .br .CW -4 .br .CW -n .PP .I Ideal usually prepares output for processing by .I troff ; all output dimensions are expressed in inches so that .I troff can do the appropriate thing for any typesetter. The .CW -p option directs that the output be generic .I plot output. The .CW -4 option causes the output to appear on the screen of a 4014. Both options cause a screen erase at each .CW .IS , but only the .CW -4 option causes a pause for input at each .CW .IE . The screen erase at .CW .IS lines may be suppressed by including the line .P1 \&...noerase .P2 and may be restored by the line .P1 \&...yeserase .P2 Option .CW -n directs that the raw .I ideal output remain uninterpreted, so it can be run through .I nroff without harm. ......... .NH 3 Quality Option .IP .CW -q .PP When .I ideal is used to prepare figures for typeset text, one has a choice of how to draw horizontal and vertical lines. Without the .CW -q option, .I ideal uses rule characters, which draw the lines fast, but may not be accurate. Giving the .CW -q option causes .I ideal to draw all lines, including horizontal and vertical lines, using dots; this takes longer, but is more accurate. .NH 3 Angle Processing .IP .CW -r .PP .I Ideal expects angles in degrees. The .CW -r option causes .I ideal to expect them in radians instead. (See also the section on Angle Processing as a command to .I ideal , below.) .NH 3 Including Library Files .IP .CW -l\fIlibfile\fP .PP This causes .I ideal to read the named library file before it begins processing the input files or the standard input. (See also the section on Including Library Files as a command to .I ideal , below.) .NH 2 Commands to Control .I Ideal Processing .PP These commands affect decisions that must be made during the processing of an .I ideal program. They should appear after the .CW .IS delimiter, each on a separate line beginning with three dots. .NH 3 Forgetting a Box .P1 \&...forget $(boxname)*$ .P2 .PP All box definitions except the definition of $main$ survive across .CW .IS\fR/\fP.IE boundaries. If you won't need a box again, and memory space is tight, you can use the $forget$ command to reclaim the space its definition requires. Because of implementation deficiencies, at most five names of boxes may appear on one $forget$ line. There is no way to remember the $main$. .NH 3 Angle Processing .P1 \&...degrees .P2 .P1 \&...radians .P2 .PP .I Ideal expects angles (arguments to $roman "cis"$ and the result of $angle$) to appear in degrees, unless it was invoked with the .CW -r option on the command line, in which case it expects angles in radians. These two commands may be used to switch back and forth between degrees and radians. Only one convention may be used within a single .I ideal program, that is between .CW .IS and .CW .IE . .NH 3 Including Files .P1 \&...libfile $(file)*$ \&...include $(file)*$ .P2 .PP The first command directs that the named library files be interpolated so that their box definitions may be used. The second is a way to include one's own files. The following library files are available: .I arc , .I arrow , .I circle , .I dash , .I rect , and .I wavy. .NH 2 Commands to the Postprocessor .PP Each of these commands must appear between the .CW .IS and .CW .IE delimiters on a separate line that begins with three dots. .NH 3 Defining the Bounding Box .P1 \&...minx $number$ \&...miny $number$ \&...maxx $number$ \&...maxy $number$ .P2 .PP The .I ideal processor computes the minimum and maximum extent of the picture in both $x$- and $y$-directions so that the bounding box (hence, the picture) can be scaled to the desired width. These commands allow you to override .I ideal 's computed bounding box. They are useful when the computed bounding box is too large or too small. .I Ideal cannot compute the bounding box of a string, so it doesn't try, and this may be too liberal. .PP Normally these dimensions are computed anew for each picture produced by .I ideal . Use the line .P1 \&...obbox .P2 between subsequent .CW .IS and .CW .IE\fR/\fP.IF lines to keep the bounding box of the previous picture. .NH 3 Setting the Picture Width .P1 \&...width $number$ .P2 .PP This command causes the bounding box to be scaled to a width of $number$ inches. (If not specified, the default is four inches.) It persists across .CW .IS\fR/\fP.IE boundaries. .NH 3 Setting the Picture Height .P1 \&...height $number$ .P2 .PP This command causes the bounding box to be scaled to a height of $number$ inches. This allows one to impose a different scale on the horizontal and vertical dimensions of a picture. The setting persists across .CW .IS\fR/\fP.IE boundaries. ........ .NH 3 Setting the Column Width .P1 \&...colwid $number$ .P2 .PP This command gives the width of the column in which the picture will appear so that it can be centered in the column. (If not specified, the default is six inches.) The setting persists across .CW .IS\fR/\fP.IE boundaries. .NH 3 Using Postprocessor Commands to Scale Pictures .PP The bounding box and width information can be adjusted to scale the picture or move it on the page. .I Ideal does not check that the entire picture is in fact contained in the bounding box, and it cannot check that the column width is correct: it uses the bounding box information to scale the picture to the width requested, then uses the column width to center the bounding box. .PP For example, to produce figures at the right of the page, .I ideal was told that the column was nine inches wide. Since the pictures are typically no more than two inches wide, they lie within the actual six inch column. .PP Suppose one's figures are drawn in the unit square, though some pieces lie a little outside, and one wants one inch to correspond to one in \*(id's coordinate system. One could set the six parameters as follows: .P1 \&...minx 0 \&...miny 0 \&...maxx 1 \&...maxy 1 \&...width 1 \&...colwid 6 .P2 .LP This gives a way for users to get exact scale representations of their pictures. .NH 2 Obsolete Features .PP These holdovers from earlier implementations are not guaranteed to survive. .NH 3 Options on the .CW .IS Line .PP One could specify the bounding box on the .CW .IS line by .P1 \&.IS minx maxy maxx miny .P2 .LP The bounding box commands to the postprocessor are now preferred. .NH 3 Changes to Keywords .PP The keyword $box$ used to be required before a box definition. .PP The keyword $boundary$ has replaced $bdlist$. .PP The function $angle$ used to be called $atan2$. .NH 3 String Delimiters .PP Strings used to be delimited by single quotes.