% output=pdftex engine=pdftex language=uk
%
% copyright=pragma-ade readme=readme.pdf licence=cc-by-nc-sa
\startcomponent mfun-004
\environment mfun-000
\chapter [sec:welcome] {Welcome to \METAPOST}
\startintro
In this chapter, we will introduce the most important
\METAPOST\ concepts as well as demonstrate some drawing
primitives and operators. This chapter does not replace the
\METAFONT\ book or \METAPOST\ manual, both of which provide
a lot of explanations, examples, and (dirty) tricks.
As its title says, the \METAFONT\ book by Donald.\ E.\ Knuth
is about fonts. Nevertheless, buying a copy is worth the
money, because as a \METAPOST\ user you can benefit from the
excellent chapters about curves, algebraic expressions, and
(linear) equations. The following sections are incomplete in
many aspects. More details on how to define your own macros
can be found in both the \METAFONT\ book and \METAPOST\
manual, but you will probably only appreciate the nasty
details if you have written a few simple figures yourself.
This chapter will give you a start.
A whole section is dedicated to the basic extensions to
\METAPOST\ as provided by \METAFUN. Most of them are meant to
make defining graphics like those shown in this document more
convenient.
Many of the concepts introduced here will be discussed in
more detail in later chapters. So, you may consider this
chapter to be an appetizer for the following chapters. If
you want to get started quickly, you can safely skip this
chapter now.
\stopintro
\section {Paths}
Paths are the building blocks of \METAPOST\ graphics. In its
simplest form, a path is a single point.
\startuseMPgraphic{axis}
tickstep := 1cm ; ticklength := 2mm ;
drawticks unitsquare xscaled 4cm yscaled 3cm shifted (-1cm,-1cm) ;
tickstep := tickstep/2 ; ticklength := ticklength/2 ;
drawticks unitsquare xscaled 4cm yscaled 3cm shifted (-1cm,-1cm) ;
\stopuseMPgraphic
\startlinecorrection[blank]
\startMPcode
\includeMPgraphic{axis}
drawpoint "1cm,1.5cm" ;
\stopMPcode
\stoplinecorrection
Such a point is identified by two numbers, which represent
the horizontal and vertical position, often referred to as
$x$ and $y$, or $(x,y)$. Because there are two numbers
involved, in \METAPOST\ this point is called a pair. Its
related datatype is therefore \type {pair}. The following
statements assigns the point we showed previously to a pair
variable.
\starttyping
pair somepoint ; somepoint := (1cm,1.5cm) ;
\stoptyping
A pair can be used to identify a point in the two
dimensional coordinate space, but it can also be used to
denote a vector (being a direction or displacement). For
instance, \type {(0,1)} means \quote {go up}. Looking
through math glasses, you may consider them vectors, and if
you know how to deal with them, \METAPOST\ may be your
friend, since it knows how to manipulate them.
You can connect points and the result is called a path. A
path is a straight or bent line, and is not necessarily a
smooth curve. An example of a simple rectangular path is:
\footnote {In the next examples we use the debugging
features discussed in \in {chapter} [sec:debugging] to
visualize the points, paths and bounding boxes.}
\startuseMPgraphic{path}
path p ;
p := unitsquare xyscaled (2cm,1cm) shifted (.5cm,.5cm) ;
\stopuseMPgraphic
\startlinecorrection[blank]
\startMPcode
\includeMPgraphic{axis}
\includeMPgraphic{path}
drawpath p ;
\stopMPcode
\stoplinecorrection
This path is constructed out of four points:
\startlinecorrection[blank]
\startMPcode
\includeMPgraphic{axis}
\includeMPgraphic{path}
swappointlabels := true ; drawpath p ; drawpoints p ;
\stopMPcode
\stoplinecorrection
Such a path has both a beginning and end and runs in a certain
direction:
\startlinecorrection[blank]
\startMPcode
\includeMPgraphic{axis}
\includeMPgraphic{path}
autoarrows := true ;
swappointlabels := true ; drawarrowpath p ; drawpoints p ;
\stopMPcode
\stoplinecorrection
A path can be open or closed. The previous path is an example
of a closed path. An open path looks like this:
\startuseMPgraphic{path}
path p ; p := (1cm,1cm)..(1.5cm,1.5cm)..(2cm,0cm) ;
\stopuseMPgraphic
\startlinecorrection[blank]
\startMPcode
\includeMPgraphic{axis}
\includeMPgraphic{path}
swappointlabels := true ; drawpath p ; drawpoints p ;
\stopMPcode
\stoplinecorrection
When we close this path |<|and in a moment we will see how to
do this|>| the path looks like:
\startbuffer
\startlinecorrection[blank]
\startMPcode
\includeMPgraphic{axis}
\includeMPgraphic{path}
p := p .. cycle ;
swappointlabels := true ; drawpath p ; drawpoints p ;
\stopMPcode
\stoplinecorrection
\stopbuffer
\getbuffer
The open path is defined as:
\starttyping
(1cm,1cm)..(1.5cm,1.5cm)..(2cm,0cm)
\stoptyping
The \quote {double period} connector \type {..} tells
\METAPOST\ that we want to connect the lines by a smooth
curve. If you want to connect points with straight line
segments, you should use \type {--}.
Closing the path is done by connecting the first and last
point, using the \type {cycle} command.
\starttyping
(1cm,1cm)..(1.5cm,1.5cm)..(2cm,0cm)..cycle
\stoptyping
Feel free to use \type {..} or \type {--} at any point in
your path.
\starttyping
(1cm,1cm)--(1.5cm,1.5cm)..(2cm,0cm)..cycle
\stoptyping
\startuseMPgraphic{path}
path p ; p := (1cm,1cm)--(1.5cm,1.5cm)..(2cm,0cm)..cycle ;
\stopuseMPgraphic
This path, when drawn, looks like this:
\getbuffer
As you can see in some of the previous examples, \METAPOST\
is capable of drawing a smooth curve through the three
points that make up the path. We will now examine how this
is done.
\startlinecorrection[blank]
\startMPcode
\includeMPgraphic{axis}
\includeMPgraphic{path}
p := p .. cycle ; swappointlabels := true ;
drawpath p ; drawcontrollines p ; drawpoints p ; drawcontrolpoints p ;
\stopMPcode
\stoplinecorrection
The six small points are the so called control points. These
points pull their parent point in a certain direction. The
further away such a point is, the stronger the pull.
Each point has at most two control points. As you can see in
the following graphic, the endpoints of a non closed curve
have only one control point.
\startuseMPgraphic{path}
path p ; p := (1.5cm,1.5cm)..(2cm,0cm)..(1cm,1cm) ;
\stopuseMPgraphic
\startbuffer[path]
\startlinecorrection[blank]
\startMPcode
\includeMPgraphic{axis}
\includeMPgraphic{path}
swappointlabels := true ;
drawpath p ; drawcontrollines p ; drawpoints p ; drawcontrolpoints p ;
\stopMPcode
\stoplinecorrection
\stopbuffer
\getbuffer[path]
This time we used the path:
\starttyping
(1.5cm,1.5cm)..(2cm,0cm)..(1cm,1cm)
\stoptyping
When you connect points by a smooth curve, \METAPOST\ will
calculate the control points itself, unless you specify one
or more of them.
\startuseMPgraphic{path}
path p ; p := (1cm,1cm)..(1.5cm,1.5cm)..controls (3cm,2cm)..(2cm,0cm) ;
\stopuseMPgraphic
\getbuffer[path]
This path is specified as:
\starttyping
(1cm,1cm)..(1.5cm,1.5cm)..controls (3cm,2cm)..(2cm,0cm)
\stoptyping
In this path, the second and third point share a
control point. Watch how the curve is pulled in that
direction. It is possible to pull a bit less by choosing
a different control point:
\starttyping
(1cm,1cm)..(1.5cm,1.5cm)..controls (2.75cm,1.25cm)..(2cm,0cm)
\stoptyping
Now we get:
\startuseMPgraphic{path}
path p ; p := (1cm,1cm)..(1.5cm,1.5cm)..controls (2.75cm,1.25cm)..(2cm,0cm) ;
\stopuseMPgraphic
\getbuffer[path]
We can also specify a different control point for each
connecting segment.
\startuseMPgraphic{path}
path p ; p := (1cm,1cm)..controls (.5cm,2cm) and (2.5cm,2cm)..(2cm,.5cm) ;
\stopuseMPgraphic
\getbuffer[path]
This path is defined as:
\starttyping
(1cm,1cm)..controls (.5cm,2cm) and (2.5cm,2cm)..(2cm,.5cm)
\stoptyping
\section {Transformations}
We can store a path in a path variable. Before we can use
such a variable, we have to allocate its memory slot with
\type {path}.
\starttyping
path p ; p := (1cm,1cm)..(1.5cm,2cm)..(2cm,0cm) ;
\stoptyping
Although we can manipulate any path in the same way, using a
variable saves us the effort to key in a path more than once.
\startuseMPgraphic{axis}
tickstep := 1cm ; ticklength := 2mm ;
drawticks unitsquare xscaled 8cm yscaled 4cm ;
tickstep := tickstep/2 ; ticklength := ticklength/2 ;
drawticks unitsquare xscaled 8cm yscaled 4cm ;
\stopuseMPgraphic
\startuseMPgraphic{path}
path p ; p := (1cm,1cm)..(1.5cm,2cm)..(2cm,0cm)..cycle ;
path q ; q := p shifted (4cm,2cm) ;
\stopuseMPgraphic
\startbuffer[path]
\startlinecorrection[blank]
\startMPcode
\includeMPgraphic{axis}
\includeMPgraphic{path}
swappointlabels := true ;
drawpath p ; drawcontrollines p ; drawpoints p ; drawcontrolpoints p ;
drawpath q ; drawcontrollines q ; drawpoints q ; drawcontrolpoints q ;
\stopMPcode
\stoplinecorrection
\stopbuffer
\getbuffer[path]
In this graphic, the path stored in \type {p} is drawn twice,
once in its displaced form. The displacement is defined as:
\starttyping
p shifted (4cm,2cm)
\stoptyping
In a similar fashion you can rotate a path. You can even
combine shifts and rotations. First we rotate the path 15
degrees counter||clockwise around the origin.
\starttyping
p rotated 15
\stoptyping
\startuseMPgraphic{path}
path p ; p := (1cm,1cm)..(1.5cm,2cm)..(2cm,0cm)..cycle ;
path q ; q := p rotated 15 ;
\stopuseMPgraphic
\getbuffer[path]
This rotation becomes more visible when we also shift the
path to the right by saying:
\starttyping
rotated 15 shifted (4cm,0cm)
\stoptyping
Now we get:
\startuseMPgraphic{path}
path p ; p := (1cm,1cm)..(1.5cm,2cm)..(2cm,0cm)..cycle ;
path q ; q := p rotated 15 shifted (4cm,0cm) ;
\stopuseMPgraphic
\getbuffer[path]
Note that \type {rotated 15} is equivalent to \typ {p
rotatedaround (origin, 15)}.
It may make more sense to rotate the shape around its
center. This can easily be achieved with the \type
{rotatedaround} command. Again, we move the path to the
right afterwards.
\starttyping
p rotatedaround(center p, 15) shifted (4cm,0cm)
\stoptyping
\startuseMPgraphic{axis}
tickstep := 1cm ; ticklength := 2mm ;
drawticks unitsquare xscaled 10cm yscaled 3cm ;
tickstep := tickstep/2 ; ticklength := ticklength/2 ;
drawticks unitsquare xscaled 10cm yscaled 3cm ;
\stopuseMPgraphic
\startuseMPgraphic{path}
path p ; p := (1cm,1cm)..(1.5cm,2cm)..(2cm,0cm)..cycle ;
path q ; q := p rotatedaround(center p, 15) shifted (4cm,0cm) ;
\stopuseMPgraphic
\getbuffer[path]
Yet another transformation is slanting. Just like characters
can be upright or slanted, a graphic can be:
\starttyping
p slanted 1.5 shifted (4cm,0cm)
\stoptyping
\startuseMPgraphic{path}
path p ; p := (1cm,1cm)..(1.5cm,2cm)..(2cm,0cm)..cycle ;
path q ; q := p slanted 1.5 shifted (4cm,0cm) ;
\stopuseMPgraphic
\getbuffer[path]
The slant operation's main application is in tilting fonts.
The $x$||coodinates are increased by a percentage of their
$y$||coordinate, so here every $x$ becomes $x+1.5y$. The
$y$||coordinate is left untouched. The following table
summarizes the most important primitive transformations that
\METAPOST\ supports.
\starttabulate[|lT|l|]
\HL
\NC \METAPOST\ code \NC mathematical equivalent \NC \NR
\HL
\NC (x,y) shifted (a,b) \NC $(x+a,y+b)$ \NC \NR
\NC (x,y) scaled s \NC $(sx,sy)$ \NC \NR
\NC (x,y) xscaled s \NC $(sx,y)$ \NC \NR
\NC (x,y) yscaled s \NC $(x,sy)$ \NC \NR
\NC (x,y) zscaled (u,v) \NC $(xu-yv,xv+yu)$ \NC \NR
\NC (x,y) slanted s \NC $(x+sy,y)$ \NC \NR
\NC (x,y) rotated r \NC $(x\cos(r)-y\sin(r),x\sin(r)+y\cos(r))$ \NC \NR
\HL
\stoptabulate
The previously mentioned \type {rotatedaround} is not a
primitive but a macro, defined in terms of shifts and
rotations. Another transformation macro is mirroring, or in
\METAPOST\ terminology, \type {reflectedabout}.
\startbuffer[path]
\startlinecorrection[blank]
\startMPcode
\includeMPgraphic{axis}
\includeMPgraphic{path}
swappointlabels := true ;
drawpath p ; drawpoints p ;
drawpath q ; drawpoints q ;
\stopMPcode
\stoplinecorrection
\stopbuffer
\startuseMPgraphic{path}
path p ; p := unitsquare scaled 2cm shifted (2cm,.5cm) ;
path q ; q := unitsquare scaled 2cm shifted (2cm,.5cm) reflectedabout((2.4cm,-.5),(2.4cm,3cm)) ;
draw (2.4cm,-.5cm)--(2.4cm,3cm) ;
\stopuseMPgraphic
\getbuffer[path]
The reflection axis is specified by a pair of points. For
example, in the graphic above, we used the following command
to reflect the square about a line through the given points.
\starttyping
p reflectedabout((2.4cm,-.5),(2.4cm,3cm))
\stoptyping
The line about which the path is mirrored. Mirroring does
not have to be parallel to an axis.
\starttyping
p reflectedabout((2.4cm,-.5),(2.6cm,3cm))
\stoptyping
The rectangle now becomes:
\startuseMPgraphic{path}
path p ; p := unitsquare scaled 2cm shifted (2cm,.5cm) ;
path q ; q := unitsquare scaled 2cm shifted (2cm,.5cm) reflectedabout((2.4cm,-.5),(2.6cm,3cm)) ;
draw (2.4cm,-.5cm)--(2.6cm,3cm) ;
\stopuseMPgraphic
\getbuffer[path]
\pagereference [zscaled]The table also mentions \type {zscaled}.
\startuseMPgraphic{path}
path p ; p := unitsquare scaled (1cm) shifted (1cm,.5cm) ;
path q ; q := unitsquare scaled (1cm) zscaled (2,.5) shifted (1cm,.5cm) ;
\stopuseMPgraphic
\getbuffer[path]
A \type {zscaled} specification takes a vector as argument:
\starttyping
p zscaled (2,.5)
\stoptyping
The result looks like a combination of scaling and rotation,
and conforms to the formula in the previous table.
Transformations can be defined in terms of a transform
matrix. Such a matrix is stored in a transform variable. For
example:
\starttyping
transform t ; t := identity scaled 2cm shifted (4cm,1cm) ;
\stoptyping
We use the associated keyword \type {transformed} to apply
this matrix to a path or picture.
\starttyping
p transformed t
\stoptyping
In this example we've taken the \type {identity} matrix as
starting point but you can use any predefined
transformation. The identity matrix is defined in such a way
that it scales by a factor of one in both directions and
shifts over the zero||vector.
Transform variables can save quite some typing and may help
you to force consistency when many similar transformations
are to be done. Instead of changing the scaling, shifting
and other transformations you can then stick to just
changing the one transform variable.
\section {Constructing paths}
In most cases, a path will have more points than the few
shown here. Take for instance a so called {\em super ellipse}.
\startlinecorrection[blank]
\startMPcode
path p ; p := fullsquare xyscaled (5cm,3cm) superellipsed .85 ;
drawpath p ; drawpoints p ;
visualizepaths ; draw p shifted (6cm,0cm) withcolor .625yellow ;
\stopMPcode
\stoplinecorrection
These graphics provide a lot of information. In this picture
the crosshair in the center is the {\em origin} and the
dashed rectangle is the {\em bounding box} of the super
ellipse. The bounding box specifies the position of the
graphic in relation to the origin as well as its width and
height.
In the graphic on the right, you can see the points that
make up the closed path as well as the control points. Each
point has a number with the first point numbered zero.
Because the path is closed, the first and last point coincide.
\startuseMPgraphic{axis}
tickstep := 1cm ; ticklength := 2mm ;
drawticks unitsquare xscaled 8cm yscaled 3cm ;
tickstep := tickstep/2 ; ticklength := ticklength/2 ;
drawticks unitsquare xscaled 8cm yscaled 3cm ;
\stopuseMPgraphic
\startbuffer
\startlinecorrection[blank]
\startMPcode
string str ; defaultfont := "\truefontname{Mono}" ;
\includeMPgraphic{axis}
\includeMPgraphic{points}
\includeMPgraphic{path}
label.lft(str,(14.5cm,2.5cm)) ;
drawwholepath scantokens(str) ;
\stopMPcode
\stoplinecorrection
\stopbuffer
We've used the commands \type {..} and \type {--} as path
connecting directives. In the next series of examples, we
will demonstrate a few more. However, before doing that, we
define a few points, using the predefined \type {z}
variables.
\startuseMPgraphic{points}
z0 = (0.5cm,1.5cm) ; z1 = (2.5cm,2.5cm) ;
z2 = (6.5cm,0.5cm) ; z3 = (2.5cm,1.5cm) ;
\stopuseMPgraphic
\starttyping
z0 = (0.5cm,1.5cm) ; z1 = (2.5cm,2.5cm) ;
z2 = (6.5cm,0.5cm) ; z3 = (2.5cm,1.5cm) ;
\stoptyping
Here \type {z1} is a short way of saying \type {(x1,y1)}.
When a \type {z} variable is called, the corresponding \type
{x} and \type {y} variables are available too. Later we will
discuss \METAPOST\ capability to deal with expressions, which
are expressed using an \type {=} instead of \type {:=}. In
this case the expression related to \type {z0} is expanded
into:
\starttyping
z0 = (x0,y0) = (0.5cm,1.5cm) ;
\stoptyping
But let's for this moment forget about their expressive
nature and simply see them as points which we will now
connect by straight line segments.
\startuseMPgraphic{path}
str := "z0--z1--z2--z3--cycle" ;
\stopuseMPgraphic
\getbuffer
The smooth curved connection, using \type {..} looks like:
\startuseMPgraphic{path}
str := "z0..z1..z2..z3..cycle" ;
\stopuseMPgraphic
\getbuffer
If we replace the \type {..} by \type {...}, we get a
tighter path.
\startuseMPgraphic{path}
str := "z0...z1...z2...z3...cycle" ;
\stopuseMPgraphic
\getbuffer
Since there are \type {..}, \type {--}, and \type {...}, it
will be no surprise that there is also \type {---}.
\startuseMPgraphic{path}
str := "z0---z1---z2---z3---cycle" ;
\stopuseMPgraphic
\getbuffer
If you compare this graphic with the one using \type {--}
the result is the same, but there is a clear difference in
control points. As a result, combining \type {..} with \type
{--} or \type {---} makes a big difference. Here we get a
non||smooth connection between the curves and the straight
line.
\startuseMPgraphic{path}
str := "z0..z1..z2--z3..cycle" ;
\stopuseMPgraphic
\getbuffer
As you can see in the next graphic, when we use \type {---},
we get a smooth connection between the straight line and the
rest of the curve.
\startuseMPgraphic{path}
str := "z0..z1..z2---z3..cycle" ;
\stopuseMPgraphic
\getbuffer
So far, we have joined the four points as one path.
Alternatively, we can constrict subpaths and connect them
using the ampersand symbol, \type {&}.
\startuseMPgraphic{path}
str := "z0..z1..z2 & z2..z3..z0 & cycle" ;
\stopuseMPgraphic
\getbuffer
So far we have created a closed path. Closing is done by \type
{cycle}. The following path may look closed but is in fact
open.
\startuseMPgraphic{path}
str := "z0..z1..z2..z3..z0" ;
\stopuseMPgraphic
\getbuffer
Only a closed path can be filled. The closed alternative
looks as follows. We will see many examples of filled closed
paths later on.
\startuseMPgraphic{path}
str := "z0..z1..z2..z3..z0..cycle" ;
\stopuseMPgraphic
\getbuffer
Here the final \type {..} will try to make a smooth
connection, but because we already are at the starting
point, this is not possible. However, the \type {cycle}
command can automatically connects to the first point.
Watch the difference between the previous and the next
path.
\startuseMPgraphic{path}
str := "z0..z1..z2..z3..cycle" ;
\stopuseMPgraphic
\getbuffer
It is also possible to combine two paths into one that don't
have common head and tails. First we define an open path:
\startuseMPgraphic{path}
str := "z0..z1..z2" ;
\stopuseMPgraphic
\getbuffer
The following path is a closed one, and crosses the
previously shown path.
\startuseMPgraphic{path}
str := "z0..z3..z1..cycle" ;
\stopuseMPgraphic
\getbuffer
With \type {buildcycle} we can combine two paths into one.
\startuseMPgraphic{path}
str := "buildcycle(z0..z1..z2 , z0..z3..z1..cycle)" ;
\stopuseMPgraphic
\getbuffer
We would refer readers to the \METAFONT\ book and the
\METAPOST\ manual for an explanation of the intricacies of
the \type {buildcycle} command. It is an extremely
complicated command, and there is just not enough room here
to do it justice. We suffice with saying that the paths
should cross at least once before the \type {buildcycle}
command can craft a combined path from two given paths. We
encourage readers to experiment with this command.
In order to demonstrate another technique of joining paths,
we first draw a few strange paths. The last of these three
graphics demonstrates the use of \type {softjoin}.
\startuseMPgraphic{path}
str := "z0--z1..z2--z3" ;
\stopuseMPgraphic
\getbuffer
\startuseMPgraphic{path}
str := "z0..z1..z2--z3" ;
\stopuseMPgraphic
\getbuffer
Watch how \type {softjoin} removes a point in the process of
smoothing a connection. The smoothness is accomplished by
adapting the control points of the neighbouring points in the
appropriate way.
\startuseMPgraphic{path}
str := "z0--z1 softjoin z2--z3" ;
\stopuseMPgraphic
\getbuffer
Once a path is known, you can cut off a slice of it. We will
demonstrate a few alternative ways of doing so, but first we
show one more time the path that we take as starting point.
\startuseMPgraphic{path}
str := "z0..z1..z2..z3..cycle" ;
\stopuseMPgraphic
\getbuffer
This path is made up out of five points, where the cycle
duplicates the first point and connects the loose ends. The
first point has number zero.
We can use these points in the \type {subpath} command,
which takes two arguments, specifying the range of points to
cut of the path specified after the keyword \type {of}.
\startuseMPgraphic{path}
str := "subpath(2,4) of (z0..z1..z2..z3..cycle)" ;
\stopuseMPgraphic
\getbuffer
The new (sub|)|path is a new path with its own points that
start numbering at zero. The next graphic shows both the
original and the subpath from point 1 upto~3.
\startuseMPgraphic{path}
str := "(z0..z1..z2..z3..cycle)" ;
sub := "subpath(1,3)" ;
\stopuseMPgraphic
\startbuffer[sub]
\startlinecorrection[blank]
\startMPcode
string str, sub ; defaultfont := "\truefontname{Mono}" ;
\includeMPgraphic{axis}
\includeMPgraphic{points}
\includeMPgraphic{path}
label.lft(str,(14.5cm,2.5cm)) ;
label.lft(sub,(14.5cm,2.0cm)) ;
sub := sub & " of " & str ;
path p ; p := scantokens(str) ;
path q ; q := scantokens(sub) ;
drawwholepath p ; swappointlabels := true ;
drawpath q withcolor .625yellow ;
drawpoints q withcolor .625red ;
drawpointlabels q ;
\stopMPcode
\stoplinecorrection
\stopbuffer[sub]
\getbuffer[sub]
In spite of what you may think, a point is not fixed. This is
why in \METAPOST\ a point along a path is officially called a
time. The next example demonstrates that we can specify any
time on the path.
\startuseMPgraphic{path}
str := "(z0..z1..z2..z3..cycle)" ;
sub := "subpath(2.45,3.85)" ;
\stopuseMPgraphic
\getbuffer[sub]
Often we want to take a slice starting at a specific point.
This is provided by \type {cutafter} and its companion \type
{cutbefore}. Watch out, this time we use a non||cyclic path.
\startuseMPgraphic{path}
str := "(z0..z1..z2..z3)" ;
\stopuseMPgraphic
\getbuffer
When you use \type {cutafter} and \type {cutbefore} it
really helps if you know in what direction the path runs.
\startuseMPgraphic{path}
str := "(z0..z1..z2..z3) cutafter z2" ;
\stopuseMPgraphic
\getbuffer
\startuseMPgraphic{path}
str := "(z0..z1..z2..z3) cutbefore z1" ;
\stopuseMPgraphic
\getbuffer
Here is a somewhat silly way of accomplishing the same
thing, but it is a nice introduction to \METAPOST's \type
{point} operation. In order to use this command
effectively, you need to know how many points make up the
path.
\startuseMPgraphic{path}
str := "(z0..z1..z2..z3) cutbefore point 2 of (z0..z1..z2..z3)" ;
\stopuseMPgraphic
\getbuffer
As with \type {subpath}, you can use fractions to specify
the time on the path, although the resulting point is not
necessarily positioned linearly along the curve.
\startuseMPgraphic{path}
str := "(z0..z1..z2..z3) cutbefore point 2.5 of (z0..z1..z2..z3)" ;
\stopuseMPgraphic
\getbuffer
If you really want to know the details of where fraction
points are positioned, you should read the \METAFONT\ book
and study the source of \METAFONT\ and \METAPOST, where you
will find the complicated formulas that are used to
calculate smooth curves.
\startuseMPgraphic{path}
str := "z0..z1..cycle" ;
\stopuseMPgraphic
\getbuffer
Like any closed path, this path has points where the
tangent is horizontal or vertical. Early in this chapter we
mentioned that a pair (or point) can specify a direction or
vector. Although any angle is possible, we often use one
of four predefined directions:
\starttabulate[|Tl|Tl|]
\HL
\NC right \NC ( 1, 0) \NC \NR
\NC up \NC ( 0, 1) \NC \NR
\NC left \NC (-1, 0) \NC \NR
\NC down \NC ( 0,-1) \NC \NR
\HL
\stoptabulate
We can use these predefined directions in combination with
\type {directionpoint} and \type {cutafter}. The following
command locates the first point on the path that has a
tangent that points vertically upward, and then feeds this
point to the \type {cutafter} command.
\startuseMPgraphic{path}
str := "(z0..z1..cycle) cutafter directionpoint up of (z0..z1..cycle)" ;
\stopuseMPgraphic
\getbuffer
You are not limited to predefined direction vectors. You
can provide a pair denoting a direction. In the next
example we use the following cyclic path:
\startuseMPgraphic{path}
str := "z0..z1..cycle" ;
\stopuseMPgraphic
\getbuffer
Using \type {( )} is not mandatory but makes the expression look
less complicated.
\startuseMPgraphic{path}
str := "(z0..z1..cycle) cutafter directionpoint (1,1) of (z0..z1..cycle)" ;
\stopuseMPgraphic
\getbuffer
We will apply these commands in the next chapters, but first
we will finish our introduction in \METAPOST. We have seen
how a path is constructed and what can be done with it. Now
it is time to demonstrate how such a path is turned into a
graphic.
\section{Angles}
You can go from angles to vectors and vice versa using the
\type {angle} and \type {dir} functions. The next example
show both in action.
\startbuffer
pickup pencircle scaled 2mm ;
draw (origin -- dir(45) -- dir(0) -- cycle)
scaled 3cm withcolor .625red ;
draw (origin -- dir(angle(1,1)) -- dir(angle(1,0)) -- cycle)
scaled 3cm shifted (3.5cm,0) withcolor .625yellow ;
draw (origin -- (1,1) -- (1,0) -- cycle)
scaled 3cm shifted (7cm,0) withcolor .625white ;
\stopbuffer
\typebuffer
\startlinecorrection[blank]
\processMPbuffer
\stoplinecorrection
The \type {dir} command returns an unit vector, which is
why the first two shapes look different and are smaller
than the third one. We can compensate for that by an
additional scaling:
\startbuffer
pickup pencircle scaled 2mm ;
draw (origin -- dir(45) -- dir(0) -- cycle)
scaled sqrt(2) scaled 3cm withcolor .625red ;
draw (origin -- dir(angle(1,1)) -- dir(angle(1,0)) -- cycle)
scaled sqrt(2) scaled 3cm shifted (4.5cm,0) withcolor .625yellow ;
draw (origin -- (1,1) -- (1,0) -- cycle)
scaled 3cm shifted (9cm,0) withcolor .625white ;
\stopbuffer
\typebuffer
\startlinecorrection[blank]
\processMPbuffer
\stoplinecorrection
\section {Drawing pictures}
Once a path is defined, either directly or as a variable, you
can turn it into a picture. You can draw a path, like we did
in the previous examples, or you can fill it, but only if it
is closed.
\startlinecorrection[blank]
\startMPcode
visualizepaths ;
path p ; p := (0cm,1cm)..(2cm,2cm)..(4cm,0cm)..cycle ;
draw p withcolor .625red ;
fill p shifted (7cm,0) withcolor .625white ;
\stopMPcode
\stoplinecorrection
Drawing is done by applying the \type {draw} command to a
path, as in:
\starttyping
draw (0cm,1cm)..(2cm,2cm)..(4cm,0cm)..cycle ;
\stoptyping
The rightmost graphic was made with \type {fill}:
\starttyping
fill (0cm,1cm)..(2cm,2cm)..(4cm,0cm)..cycle ;
\stoptyping
If you try to duplicate this drawing, you will notice that
you will get black lines instead of red and a black fill
instead of a gray one. When drawing or filling a path, you
can give it a color, use all kinds of pens, and achieve
special effects like dashes or arrows.
\startlinecorrection[blank]
\startMPcode
visualizepaths ;
path p ; p := (0cm,1cm)..(2cm,2cm)..(4cm,0cm)..(2cm,1cm)..cycle ;
drawarrow p withcolor .625red ;
draw p shifted (7cm,0) dashed withdots withcolor .625yellow ;
\stopMPcode
\stoplinecorrection
These two graphics were defined and drawn using the following
commands. Later we will explain how you can set the line
width (or penshape in terms of \METAPOST).
\starttyping
path p ; p := (0cm,1cm)..(2cm,2cm)..(4cm,0cm)..(2cm,1cm)..cycle ;
drawarrow p withcolor .625red ;
draw p shifted (7cm,0) dashed withdots withcolor .625yellow ;
\stoptyping
Once we have drawn one or more paths, we can store them in a
picture variable. The straightforward way to store a picture
is to copy it from the current picture:
\starttyping
picture pic ; pic := currentpicture ;
\stoptyping
The following command effectively clears the picture memory
and allows us to start anew.
\starttyping
currentpicture := nullpicture ;
\stoptyping
We can shift, rotate and slant the picture stored in \type
{pic} as we did with paths. We can say:
\starttyping
draw pic rotated 45 withcolor red ;
\stoptyping
A picture can hold multiple paths. You may compare a picture
to grouping as provided by drawing applications.
\starttyping
draw (0cm,0cm)--(1cm,1cm) ; draw (1cm,0cm)--(0cm,1cm) ;
picture pic ; pic := currentpicture ;
draw pic shifted (3cm,0cm) ; draw pic shifted (6cm,0cm) ;
pic := currentpicture ; draw pic shifted (0cm,2cm) ;
\stoptyping
We first draw two paths and store the resulting \quote
{cross} in a picture variable. Then we draw this picture
two times, so that we now have three copies of the cross.
We store the accumulated drawing again, so that after
duplication, we finally get six crosses.
\startlinecorrection[blank]
\startMPcode
path p ; p := (0cm,0cm)--(1cm,1cm) ;
path q ; q := (1cm,0cm)--(0cm,1cm) ;
for i=p,q :
drawpath i ; drawcontrollines i ; drawpoints i ; drawcontrolpoints i ;
endfor ;
picture pic ; pic := currentpicture ;
draw pic shifted (3cm,0cm) ;
draw pic shifted (6cm,0cm) ;
pic := currentpicture ;
draw pic shifted (0cm,2cm) ;
\stopMPcode
\stoplinecorrection
You can often follow several routes to reach the same
solution. Consider for instance the following graphic.
\startbuffer[points]
w := 4cm ; h := 2cm ; ww := 1cm ; hh := 1.5cm ;
\stopbuffer
\startbuffer[common]
drawoptions(withcolor .625white) ;
\stopbuffer
\startbuffer[background]
fill (unitsquare xscaled w yscaled h) enlarged 2mm withcolor .625yellow ;
\stopbuffer
\startbuffer[shape]
fill (0,0)--(ww,0)--(ww,hh)--(w,hh)--(w,h)--(0,h)--cycle ;
fill (ww,0)--(w,0)--(w,hh)--cycle ;
\stopbuffer
\typebuffer[shape]
\startlinecorrection[blank]
\processMPbuffer[common,points,shape]
\stoplinecorrection
The points that are used to construct the paths are defined
using the constants \type {w}, \type {h}, \type {ww} and
\type {hh}. These are defined as follows:
\typebuffer[points]
In this case we draw two shapes that leave part of the
rectangle uncovered. If you have a background, this
technique allows the background to \quote {show through}
the graphic.
\startlinecorrection[blank]
\processMPbuffer[common,points,background,shape]
\stoplinecorrection
A not uncommon practice when making complicated graphics is
to use unfill operations. Since \METAPOST\ provides one,
let us see what happens if we apply this command.
\startbuffer[shape]
fill (0,0)--(w,0)--(w,h)--(0,h)--cycle ;
unfill (ww,0)--(w,hh)--(ww,hh)--cycle ;
\stopbuffer
\typebuffer[shape]
\startlinecorrection[blank]
\processMPbuffer[common,points,background,shape]
\stoplinecorrection
This does not always give the desired effect, because
\METAPOST's \type {unfill} is not really an unfill, but a
\type {fill} with color \type {background}. Since this
color is white by default, we get what we just showed. So,
if we set \type {background} to \type {black}, using \typ
{background := black}, we get:
\startbuffer[back]
background := black ;
\stopbuffer
\startlinecorrection[blank]
\processMPbuffer[back,common,points,background,shape]
\stoplinecorrection
Of course, you can set the variable \type {background} to a
different color, but this does not hide the fact that
\METAPOST\ lacks a real unfill operation.
\startbuffer[shape]
fill (0,0)--(0,h)--(w,h)--(w,0)--(ww,0)--(w,hh)--(ww,hh)--
(ww,0)--cycle ;
\stopbuffer
\startbuffer[path]
autoarrows := true ;
path p ; p := (0,0)--(0,h)--(w,h)--(w,0)--(ww,0)--(w,hh)--(ww,hh)--
(ww,0)--cycle ;
draw p withpen pencircle scaled 1mm withcolor .625red;
numeric l ; l := length(p)-1 ;
for i=0 upto l :
drawarrow subpath(i,i+1) of p
withpen pencircle scaled 1mm
withcolor (.5+.5(i/l))*red ;
endfor ;
\stopbuffer
\startlinecorrection[blank]
\processMPbuffer[common,points,background,shape]
\stoplinecorrection
Since we don't consider this \type {unfill} a suitable
operator, you may wonder how we achieved the above result.
\typebuffer[shape]
\startlinecorrection[blank]
\processMPbuffer[common,points,background,shape,path]
\stoplinecorrection
This feature depends on the \POSTSCRIPT\ way of filling
closed paths, which comes down to filling either the left
or the right hand side of a curve. The following
alternative works too.
\startbuffer[shape]
fill (0,0)--(0,h)--(w,h)--(w,hh)--(ww,hh)--(ww,0)--(w,hh)--
(w,0)--cycle ;
\stopbuffer
\typebuffer[shape]
\startlinecorrection[blank]
\processMPbuffer[common,points,background,shape]
\stoplinecorrection
The next alternative will fail. This has to do with the
change in direction at point \type {(0,0)} halfway through
the path. Sometimes changing direction can give curious but
desirable effects, but here it brings no good.
\startbuffer[shape]
fill (0,0)--(0,h)--(w,h)--(w,0)--(0,0)--(ww,0)--(ww,hh)--
(w,hh)--(ww,0)--cycle ;
\stopbuffer
\typebuffer[shape]
This path fails because of the way \POSTSCRIPT\ implements
its fill operator. More details on how \POSTSCRIPT\ defines
fills can be found in the reference manuals.
\startlinecorrection[blank]
\processMPbuffer[common,points,background,shape]
\stoplinecorrection
Some of the operations we have seen are hard coded into
\METAPOST\ and are called primitives. Others are defined as
macros, that is, a sequence of \METAPOST\ commands. Since
they are used often, you may expect \type {draw} and \type
{fill} to be primitives, but they are not. They are macros
defined in terms of primitives.
Given a path \type {pat}, you can consider a draw to be defined
in terms of:
\starttyping
addto currentpicture doublepath pat
\stoptyping
The \type {fill} command on the other hand is defined as:
\starttyping
addto currentpicture contour pat
\stoptyping
Both macros are actually a bit more complicated but this is
mainly due to the fact that they also have to deal with
attributes like the pen and color they draw with.
You can use \type {doublepath} and \type {contour}
directly, but we will use \type {draw} and \type {fill}
whenever possible.
Given a picture \type {pic}, the following code is valid:
\starttyping
addto currentpicture also pic
\stoptyping
You can add pictures to existing picture variables, where
\type {currentpicture} is the picture that is flushed to the
output file. Watch the subtle difference between adding a
\type {doublepath}, \type {contour} or picture.
\section {Variables}
At this point you may have noted that \METAPOST\ is a
programming language. Contrary to some of today's
languages, \METAPOST\ is a simple and clean language.
Actually, it is a macro language. Although \METAPOST\ and
\TEX\ are a couple, the languages differ in many aspects.
If you are using both, you will sometimes wish that
features present in one would be available in the other.
When using both languages, in the end you will understand
why the conceptual differences make sense.
Being written in \PASCAL, it will be no surprise that
\METAPOST\ has some \PASCAL||like features, although some
may also recognize features from \ALGOL68\ in it.
First there is the concept of variables and assignments.
There are several data types, some of which we already have
seen.
\starttabulate
\HL
\NC numeric \NC real number in the range $-4096 \ldots +4096$ \NC \NR
\NC boolean \NC a variable that takes one of two states: true or false \NC \NR
\NC pair \NC point or vector in 2||dimensional space \NC \NR
\NC path \NC a piecewise collection of curves and line segments \NC \NR
\NC picture \NC collection of stroked or filled paths \NC \NR
\NC string \NC sequence of characters, like \type {"metapost"} \NC \NR
\HL
\stoptabulate
There are too additional types, \type {transform} and \type
{pen}, but we will not discuss these in depth.
\starttabulate
\HL
\NC transform \NC transformation vector with six elements \NC \NR
\NC pen \NC pen specification \NC \NR
\HL
\stoptabulate
You can achieve interesting effects by using pens with
certain shapes. For the moment you may consider a pen to be
a path itself that is applied to the path that is drawn.
The \type {numeric} data type is used so often that it is
the default type of any non declared variable. This means
that
\starttyping
n := 10 ;
\stoptyping
is the same as
\starttyping
numeric n ; n := 10 ;
\stoptyping
When writing collections of macros, it makes sense to use
the second method, because you can never be sure if \type {n}
isn't already declared as a picture variable, and assigning
a numeric to a picture variable is not permitted.
Because we often deal with collections of objects, such as
a series of points, all variables can be organized in
arrays. For instance:
\starttyping
numeric n[] ; n[3] := 10 ; n[5] := 13 ;
\stoptyping
An array is a collection of variables of the same type that
are assigned and accessed by indexing the variable name, as
in \type {n[3] := 5}. Multi||dimensional arrays are also
supported. Since you need a bit of imagination to find an
application for 5||dimensional arrays, we restrict
ourselves to a two||dimensional example.
\starttyping
numeric n[][] ; n[2][3] := 10 ;
\stoptyping
A nice feature is that the bounds of such an array needs not
to be set beforehand. This also means that each cell that
you access is reported as {\em unknown} unless you have
assigned it a value.
\section {Conditions}
The existence of boolean variables indicates the presence
of conditionals. Indeed, the general form of \METAPOST's
if, then, else conditional follows.
\starttyping
if n=10 : draw p ; else : draw q ; fi ;
\stoptyping
Watch the colons after the if and else clause. They may not
be omitted. The semi||colons on the other hand, are optional
and depend on the context. You may say things like:
\starttyping
draw if n=10 : p ; else : q ; fi ;
\stoptyping
Here we can omit a few semi||colons:
\starttyping
draw if n=10 : p else : q fi withcolor red ;
\stoptyping
Adding semi||colons after \type {p} and \type {q} will
definitely result in an error message, since the semi||colon
ends the draw operation and \typ {withcolor red} becomes an
isolated piece of nonsense.
There is no case statement available, but for most purposes,
the following extension is adequate:
\starttyping
draw p withcolor if n<10 : red elseif n=10 : green else : blue fi ;
\stoptyping
There is a wide repertoire of boolean tests available.
\starttyping
if picture p :
if known n :
if odd i :
if cycle q :
\stoptyping
Of course, you can use \type {and}, \type {or}, \type
{not}, and \type {( )} to construct very advanced boolean
expressions. If you have a bit of programming experience,
you will appreciate the extensive support of conditionals
in \METAPOST.
\section {Loops}
Yet another programming concept present in \METAPOST\ is the
loop statement, the familiar \quote {for loop} of all
programming languages.
\starttyping
for i=0 step 2 until 20 :
draw (0,i) ;
endfor ;
\stoptyping
As explained convincingly in Niklaus Wirth's book on
algorithms and datastructures, the for loop is the natural
companion to an array. Given an array of length $n$, you can
construct a path out of the points that make up the array.
\starttyping
draw for i=0 step 1 until n-1 : p[i] .. endfor p[n] ;
\stoptyping
If the step increment is not explicitly stated, it has an
assumed value of 1. We can shorten the previous loop
construct as follows:
\starttyping
draw for i=0 upto n-1 : p[i] .. endfor p[n] ;
\stoptyping
After seeing \type {if} in action, the following \type {for} loop
will be no surprise:
\startbuffer
draw origin for i=0 step 10 until 100 : ..{down}(i,0) endfor ;
\stopbuffer
\typebuffer
This gives the zig||zag curve:
\startlinecorrection[blank]
\processMPbuffer
\stoplinecorrection
You can use a loop to iterate over a list of objects. A simple
3||step iteration is:
\starttyping
for i=p,q,r :
fill i withcolor .8white ;
draw i withcolor red ;
endfor ;
\stoptyping
Using \type {for} in this manner can sometimes save a bit of
typing. The list can contain any expression, and may be of
different types.
In the previous example the \type {i} is an independent
variable, local to the for loop. If you want to change the
loop variable itself, you need to use \type {forsuffixes}. In
the next loop the paths \type {p}, \type {q} and~\type {r}
are all shifted.
\starttyping
forsuffixes i = p, q, r :
i := i shifted (3cm,2cm) ;
endfor ;
\stoptyping
Sometimes you may want to loop forever until a specific
condition occurs. For this, \METAPOST\ provides a special
looping mechanism:
\startbuffer[demo]
numeric done[][], i, j, n ; n := 0 ;
forever :
i := round(uniformdeviate(10)) ; j := round(uniformdeviate(2)) ;
if unknown done[i][j] :
drawdot (i*cm,j*cm) ; n := n + 1 ; done[i][j] := n ;
fi ;
exitif n = 10 ;
endfor ;
\stopbuffer
\typebuffer[demo]
Here we remain in the loop until we have 10 points placed.
We use an array to keep track of placed points. The
\METAPOST\ macro \type {uniformdeviate(n)} returns a random
number between 0 and~n and the \type {round} command is
used to move the result toward the nearest integer. The
\type {unknown} primitive allows us to test if the array
element already exists, otherwise we exit the conditional.
This saves a bit of computational time as each point is
drawn and indexed only once.
\startbuffer[pen]
pickup pencircle scaled 2mm ;
\stopbuffer
\startlinecorrection[blank]
\processMPbuffer[pen,demo]
\stoplinecorrection
The loop terminator \type {exitif} and its companion \type
{exitunless} can be used in \type {for}, \type {forsuffixes}
and \type {forever}.
\section {Macros}
In the previous section we introduced \type {upto}. Actually
this is not part of the built in syntax, but a sort of
shortcut, defined by:
\starttyping
def upto = step 1 until enddef ;
\stoptyping
You just saw a macro definition where \type {upto} is the
name of the macro. The counterpart of \type {upto} is \type
{downto}. Whenever you use \type {upto}, it is replaced by
\typ {step 1 until}. This replacement is called expansion.
There are several types of macros. A primary macro is used
to define your own operators. For example:
\starttyping
primarydef p doublescaled s =
p xscaled (s/2) yscaled (s*2)
enddef ;
\stoptyping
Once defined, the \type {doublescaled} macro is implemented
as in the following example:
\starttyping
draw somepath doublescaled 2cm withcolor red ;
\stoptyping
When this command is executed, the macro is expanded. Thus,
the actual content of this command becomes:
\starttyping
draw somepath xscaled 1cm yscaled 4cm withcolor red ;
\stoptyping
If in the definition of \type {doublescaled} we had added a
semi||colon after \type {(s*2)}, we could not have set the
color, because the semicolon ends the statement. The \type
{draw} expects a path, so the macro can best return one.
A macro can take one or more arguments, as in:
\starttyping
def drawrandomscaledpath (expr p, s) =
draw p xscaled (s/2) yscaled (s*2) ;
enddef ;
\stoptyping
When using this macro, it is expected that you will pass it
two parameters, the first being a path, the second a numeric
scale factor.
\starttyping
drawrandomscaledpath(fullsquare, 3cm) ;
\stoptyping
Sometimes we want to return a value from a macro. In that
case we must make sure that any calculations don't
interfere with the expectations. Consider:
\starttyping
vardef randomscaledpath(expr p, s) =
numeric r ; r := round(1 + uniformdeviate(4)) ;
p xscaled (s/r) yscaled (s*r)
enddef ;
\stoptyping
Because we want to use the same value of \type {r} twice,
we have to use an intermediate variable. By using a \type
{vardef} we hide everything but the last statement. It is
important to distinguish \type {def} macros from those
defined with \type {vardef}. In the latter case, \type
{vardef} macros are not a simple expansion and replacement.
Rather, \type {vardef} macros return the value of their
last statement. In the case of the \type {randomscaledpath}
macro, a path is returned. This macro is used in the
following manner:
\starttyping
path mypath ; mypath := randomscaledpath(unitsquare,4cm) ;
\stoptyping
Note that we send \type {randomscaledpath} a path (\type
{unitsquare}) and a scaling factor (\type {4cm}). The macro
returns a scaled path which is then stored in the path
variable \type {mypath}.
The following argument types are accepted:
\starttabulate
\HL
\NC expr \NC something that can be assigned to a variable \NC \NR
\NC text \NC arbitrary \METAPOST\ code ending with a \type {;} \NC \NR
\NC suffix \NC a variable bound to another variable \NC \NR
\HL
\stoptabulate
An expression is passed by value. This means that in the
body of the macro, a copy is used and the original is left
untouched. On the other hand, any change to a variable passed
as suffix is also applied to the original.
Local variables must be handled in a special manner, since
they may conflict with variables used elsewhere. This is
because all variables are global by default. The way
out of this problem is using grouping in combination with
saving variables. The use of grouping is not restricted to
macros and may be used anywhere in your code. Variables
saved and declared in a group are local to that group. Once
the group is exited the variables cease to exist. Grouping
is not bound to macros and may be used anywhere in your
code.
\starttyping
vardef randomscaledpath(expr p, s) =
begingroup ; save r ; numeric r ;
r := round(1 + uniformdeviate(4)) ;
p xscaled (s/r) yscaled (s*r)
endgroup
enddef ;
\stoptyping
In this particular case, we could have omitted the grouping,
since \type {vardef} macros are always grouped automatically.
Therefore, we could have defined the macro as:
\starttyping
vardef randomscaledpath(expr p, s) =
save r ; numeric r ; r := round(1 + uniformdeviate(4)) ;
p xscaled (s/r) yscaled (s*r)
enddef ;
\stoptyping
The command \type {save r} declares that the variable \type
{r} is local to the macro. Thus, any changes to the (new)
numeric variable \type {r} are local and will not interfere
with a variable \type {r} defined outside the macro. This
is important to understand, as variables outside the macro
are global and accessible to the code within the body of
the macro.
Macro definitions may be nested, but since most \METAPOST\
code is relatively simple, it is seldom needed. Nesting is
discouraged as it makes your code less readable.
Besides \type {def} and \type {vardef}, \METAPOST\ also
provides the classifiers \type {primarydef}, \type
{secondarydef} and \type {tertiarydef}. You can use these
classifiers to define macros like those provided by
\METAPOST\ itself:
\starttyping
primarydef x mod y = ... enddef ;
secondarydef p intersectionpoint q = ... enddef ;
tertiarydef p softjoin q = ... enddef ;
\stoptyping
A primary macro acts like the binary operators \type {*} or
\type {scaled} and \type {shifted}. Secondary macros are
like \type {+}, \type {-} and logical \type {or}, and take
less precedence. The tertiary operators like \type {<} or
the path and string concatenation operator \type {&} have
tertiary macros as companions. More details can be found in
the \METAFONT\ book. When it comes to taking precedence,
\METAPOST\ tries to be as natural as possible, in the sense
that you need to provide as few \type {( )}'s as possible.
When in doubt, or when surprised by unexpected results, use
parentheses.
\section{Arguments}
The \METAPOST\ macro language is rather flexible in how you
feed arguments to macros. If you have only one argument, the
following definitions and calls are valid.
\starttyping
def test expr a = enddef ; test (a) ; test a ;
def test (expr a) = enddef ; test (a) ; test a ;
\stoptyping
A more complex definition is the following. As you can see,
you can call the \type {test} macro in your favorite way.
\starttyping
def test (expr a,b) (expr c,d) = enddef ;
test (a) (b) (c) (d) ;
test (a,b) (c,d) ;
test (a,b,c) (d) ;
test (a,b,c,d) ;
\stoptyping
The type of the arguments is one of \type {expr}, \type
{primary} or \type {suffix}. When fetching arguments,
\METAPOST\ uses the type to determine how and what to grab.
A fourth type is \type {text}. When no parenthesis are
used, a \type {text} argument grabs everything upto the
next semicolon.
\starttyping
def test (expr a) text b = enddef ;
test (a) ; test (a) b ;
\stoptyping
You can use a \type {text} to grab arguments like \typ
{withpen pencircle scaled 10 withcolor red}. Because \type
{text} is so hungry, you may occasionally need a two
stage definition:
\starttyping
def test expr a = dotext(a) enddef ;
def dotest (expr a) text b = ... enddef ;
test a ; test a b ;
\stoptyping
This definition permits arguments without parenthesis,
which is something you want with commands like \type
{draw}.
The \type {vardef} alternative behaves in a similar way. It
always provides grouping. You need to generate a return
value and as a result may not end with a semicolon.
You may consider the whole \type {vardef} to be encapsulated
into parenthesis and thereby to be a (self contained)
variable. Adding additional parenthesis often does more harm
than good:
\starttyping
vardef test (expr a) =
( do tricky things with a ; manipulated_a )
enddef ;
\stoptyping
Here the tricky things become part of the return value, which
quite certainly is something that you don't want.
The three operator look||alike macro definitions are less
flexible and have the definition scheme:
\starttyping
primarydef x test y = enddef ;
secondarydef x test y = enddef ;
tertiarydef x test y = enddef ;
\stoptyping
When defining macros using this threesome you need to be
aware of the associated priorities. When using these
definitions, you also have to provide your own grouping.
In the plain \METAPOST\ macro collection (\type {plain.mp})
you can find many examples of clever definitions. The
following (simplified) version of \type {min} demonstrates
how we use the argument handler to isolate the first
argument from the provided list, simply by using two
arguments.
\starttyping
vardef min (expr u) (text t) =
save min_u ; min_u := u ;
for uu = t : if uu__len : s := len fi ;
r := pat cutbefore t ;
r := (r cutafter point (arctime s of r) of r) ;
s := len/2 + off ; if s<=0 : s := 0 elseif s>len : s := len fi ;
l := reverse (pat cutafter t) ;
l := (reverse (l cutafter point (arctime s of l) of l)) ;
(l..r)
enddef ;
\stopbuffer
\typebuffer
This code fragment also demonstrates how we can treat the
\type {loc} argument as pair (coordinates) or fraction of
the path. We calculate the piece of path before and after
the given point separately and paste them afterwards as
\type {(l..r)}. By adding braces we can manipulate the path
in expressions without the danger of handling \type {r}
alone.
We can now implement left, center and right arrows by
providing this macro the right parameters. The offset (the
fourth parameter), is responsible for a backward
displacement. This may seem strange, but negative values
would be even more confusing.
\startbuffer
def rightarrow (expr p,t,l) = pointarrow(p,t,l,-l) enddef ;
def leftarrow (expr p,t,l) = pointarrow(p,t,l,+l) enddef ;
def centerarrow(expr p,t,l) = pointarrow(p,t,l, 0) enddef ;
\stopbuffer
\typebuffer
We can now apply this macro as follows:
\startbuffer[a]
path p ; p := fullcircle scaled 3cm ;
pickup pencircle scaled 2mm ;
draw p withcolor .625white ;
drawarrow leftarrow (p, .4 ,2cm) withcolor .625red ;
drawarrow centerarrow(p,point 5 of p,2cm) withcolor .625yellow ;
draw point .4 along p withcolor .625yellow ;
draw point 5 of p withcolor .625red ;
\stopbuffer
\typebuffer[a]
\startlinecorrection[blank]
\processMPbuffer[a]
\stoplinecorrection
Watch how we can pass a point (\typ {point 5 of p}) as well
as a fraction (\type {.4}). The following graphic
demonstrates a few more alternatives.
\startbuffer[a]
pickup pencircle scaled 2mm; autoarrows := true ;
path p ; p := fullcircle yscaled 3cm xscaled \the\hsize ;
draw p withcolor .5white;
for i=1, 2, 3 :
drawdot point i of p withpen pencircle scaled 5mm withcolor .625white ;
endfor ;
for i=.60, .75, .90 :
drawdot point i along p withpen pencircle scaled 5mm withcolor .625white ;
endfor ;
\stopbuffer
\startbuffer[b]
drawarrow leftarrow (p,point 1 of p,2cm) withcolor red ;
drawarrow centerarrow (p,point 2 of p,2cm) withcolor blue ;
drawarrow rightarrow (p,point 3 of p,2cm) withcolor green ;
drawarrow pointarrow (p,.60,4cm,+.5cm) withcolor yellow ;
drawarrow pointarrow (p,.75,3cm,-.5cm) withcolor cyan ;
drawarrow centerarrow (p,.90,3cm) withcolor magenta ;
\stopbuffer
\startlinecorrection[blank]
\processMPbuffer[a,b]
\stoplinecorrection
The arrows are drawn using the previously defined macros.
Watch the positive and negative offsets in call to \type
{pointarrow}.
\typebuffer[b]
\stopcomponent
__