For version 0.7.0.
To top.

The short Shapes tutorial

Welcome to this tutorial on Shapes!
This document is meant to serve as a short introduction to the Shapes language and related tools. It assumes you have already got a reason for being curious about Shapes. Likewise, being a short tutorial it will not waste your time telling about the history and background of Shapes; interested readers will find such information elsewhere.
Sections:    Hello, Shaper!    Basics    Programming    Where to go from here

Hello, Shaper!

Let's begin by working through a very small example. We'll just make a diagonal line — that's pretty much the simplest thing I can think of. (Making a very small spot would actually be simpler, but creating lines is such an important topic for Shapes that it ought to be part of the first example.)

The really short source

A Shapes program may consist of just one expression. For this example,
[stroke (0cm,0cm)--(2cm,1cm)]
would be a valid program. However, to display one of the fundamental language constructs of Shapes, we shall look at a slightly more clumsy way of achieving the same result.
Please refer to structure.html for an explanation of the various ways a Shapes program may define its output.

The source

The following, slightly longer, program does the same job as the program in the previous section. It's still just one line of code, and it will be explained in just a moment:
•page << [stroke (0cm,0cm)--(2cm,1cm)]
If you don't like the bullet character (•), you may prefix page with a hash sign (#) instead, like so:
#page << [stroke (0cm,0cm)--(2cm,1cm)]
In either case, make sure your file is encoded using utf-8 (if you avoid the bullet character, most eight bit encodings should be fine by intended coincidence). Call the file hello.shape.
So what does the code mean? As you might have guessed, ..Shapes..IO..•page, has something to do with the “page” we are drawing on, and the following << is an insertion operator (borrowed from c++) that takes the following argument and tacks it onto the page. The expression delimited by square brackets is a function call; here ..Shapes..Graphics..stroke is the callee, and there is one argument. The argument is a straight path between two points, simply constructed by combining the two coordinate pairs using the connection operator --.
The example displays the following traits of the Shapes language
  1. Function calls have the callee inside the bracketing syntax, like in Scheme.
  2. Statements are not terminated using a designated token; statements end by themselves, like in Haskell.
  3. States (here, ..Shapes..IO..•page) and variables (here, ..Shapes..Graphics..stroke) are distinguished by belonging to different name spaces, like variables are distinguished from types in Haskell. This is called Hungarian notation, and is used also for other distinctions in Shapes.
  4. Coordinates are specified as pairs of lengths, as opposed to pairs of scalars, unlike many other graphics languages where there is no distinction between lengths and scalars.
  5. The syntax makes use of characters outside the ASCII range, but there is always ASCII fall-backs.

Compiling and viewing

To obtain the pretty piece of graphics so carefully designed above, we just need to pass the file to the Shapes compiler:
shapes hello.shape
This will produce the file hello.pdf, and if your favorite pdf viewer happens to be xpdf, you could open the file by typing
xpdf hello.pdf &
However, when your file changes, you will want to update the xpdf window, and then Shapes comes to the rescue; try using the compiler with an extra option:
shapes --xpdf hello.shape
If you're on a Mac, you may replace --xpdf by --open.

Final comments

If you reached this point, this shows you're up and running! You're ready to go on with more exciting examples! By the way, can you figure out how you would go about to add another stroke to your page, or a longer stroke connecting several points?
There are many things the user can control using command line options to the compiler, but these won't be covered in this tutorial. Please refer to the man page for further details on this.


In this chapter, we'll quickly go through a couple of simple but important things about the Shapes language.

Producing nice labels with LaTeX

The following example shows how to produce a label typeset using LaTeX:
•page << (TeX `$\Xi^{2}$´)
Things worth noting:
  1. There is no function call here. The expression delimited by parentheses is a special form indicated by the TeX keyword.
  2. The string delimiters is a pair of one grave and once acute accent. If you find these difficult to produce using your keyboard layout, there is an alternative syntax; the poor man's string combines straight double quote marks with a pair of parentheses, in this case ("$\Xi^{2}$").
  3. The backslash character (\) is not an escape character (there is no escape character at all in normal strings).
  4. LaTeX is also useful for including pictures via the includegraphics macro.
The string syntax is chosen carefully not to interfere with the use of LaTeX — that's why there is no escape character.
There is also a function that does the same job as the TeX special form, but there are good reasons for avoiding it.
Quotation marks in labels
Since Shapes allows pairwise `…´ inside normal strings, the ` cannot be used to create the left double quote in LaTeX, since there will be no matching ´ at the other end of the quote. This example presents ways to deal with this.

Source: show/hide visit

Moving things around

When putting strokes on the page, there is a natural way of controlling where the strokes appear by simply specifying the desired coordinates. When using the TeX special form, however, the produced graphics always ends up at the origin. We clearly need to be able to move things around… well, strictly speaking, we don't move things in Shapes, since the language is functional. What we really do is create a new object which only differs from the original by a transform, see §Transform.
In Shapes each transform is represented as an object in itself, which can then be applied to objects of geometric nature. For example, to create the transform that moves (shifts, see ..Shapes..Geometry..shift) things 2 cm to the right and 3 mm down, we write (we also give a name to the transform, so that it can be referred to later):
T1: [shift (2cm,~3mm)]
  1. The sign of a negative number or length is written using the tilde (~) character. Negative values can also be written using the usual dash (-) if enclosed withing parentheses, for instance, (2cm,(-3mm)). The latter alternative is convenient when the source is produced by other programs.
Another very common transformation is rotation (see ..Shapes..Geometry..rotate):
T2: [rotate 25°]
  1. The angle argument is basically interpreted as radians, but angles may be entered in degrees if immediately followed by the degree sign (°). In fact, 25° is just a way of entering the number 25⋅π/180. For the full syntax of floats and a note on how to type the degree sign, see float.
The following program illustrates how the transforms can be applied:
lbl: (TeX `$x^{2} + y^{2}$´)
•page << [T1 lbl] << [T2 lbl] << lbl
The complete example with output is given below.
A label painted once in its original location, once after a rotation, and once after a shift.

Source: show/hide visit
Transforms can be combined using the multiplication operator (*). Hence, the following two lines do the same thing:
•page << [T2 [T1 lbl]]
•page << [T2*T1 lbl]
It is almost allways the case that combining the operators is (sometimes much) more efficient than applying one transform after the other.
Well, that's about all we are going to say regarding transforms in this tutorial. Before ending, we'll just mention that there are other constructors beside the ones seen here, that (almost all) transforms can be inverted (see ..Shapes..Geometry..inverse), and that transforms also exist in 3d (see §Transform3D).

Constructing paths

Path construction is at the heart of the Shapes language, and we shall cover only a small part of the powerful ways in which paths can be specified. The main documentation on path construction is in the syntax chapter.
Basically, path construction is about entering coordinates in a convenient way. The cubic splines (often referred to as Bezier splines) that Shapes uses for paths (which is the obvious choice since this is was is supported by pdf) consist both of points on the path and interpolation points that control the shape of the path between the points that are on the path. Basically each path segment between two points on the path have one interpolation point associated with the starting point, and one associated with the final point. Each of the interpolation points may be omitted, which corresponds to the limiting case when the interpolation point approaches the associated point on the path. The following example tries to illustrate the roles of points on the path and of the interpolation points.
Single Bezier spline
Bezier splines. Points on the path are marked with circles, and interpolation points are marked with a cross and a dashed line showing which terminal point it belongs to. Absence of an interpolation point — that is, an interpolation point which is logically coinciding with the terminal point it is associated with — is simply represented by not marking the interpolation point at all. Note that the absence of an interpolation point is the limiting case as the interpolation point approaches the terminal point.

Source: show/hide visit
In the next example we will see how to connect spline segments, and how offsets are interpreted.
Connected Bezier splines
Bezier splines. Points on the path are marked with circles, and interpolation points are marked with a cross and a dashed line showing which terminal point it belongs to. Solid lines with arrowhead indicates how offset coordinates are interpreted during path construction.

Source: show/hide visit
Given the examples above, the following observations and claims regarding path construction should seem at least plausible:
  1. When the path leaves a through-point, it does so in direction towards the next interpolation point, or the next through-point if there are no interpolation points.
  2. Hence, to avoid a corner at a path point, its handles mush point in opposite directions.
  3. To avoid discontinuity in the second order derivatives, the handles shall additionally be of equal lengths.
  4. Since angles to interpolation points are so important, it is desirable to be able to enter interpolation points in polar coordinates, relative to the through-point.
  5. Specifying the distance to the interpolation points is not as intuitive as specifying the angles.
  6. Hence, some mechanism for automatic determination of such distances should be welcome.
  7. A mechanism for automatic determination of angles should be welcome too.
  8. Automatic choices and values set directly by the user should mix in a way that allows complete freedom to mix the two, while also being easy to learn and remember.
  9. While it is possible to use polar coordinates to specify interpolation points relative to the through-point by using the ..Shapes..Geometry..dir function, for example (+ r*[dir a]), this solution has no room for automatic choices.
Of course, all of these features are available in Shapes. The syntax may feel a bit clumsy initially, but that's the small price we pay for the powerful and easy to remember syntax, see polar-handle-2D and corner-point-2D.
While automatic angles are determined using a rather simple scheme, selecting the distances is much more complicated. In Shapes, angles are always determined before determining any distances. This means that a scheme for automatic determination of distances has to determine these based on the location of the through-points and the angles from the through-points to the intermediate interpolation points. (When the two distances at a path point shall be equal, and both are to be determined automatically, then both are determined individually first, and then the smaller is used on both sides of the path point.) It is easy to see that the task is over-parameterized as stated here, and that the coordinates of the through-points can be reduced to just the distance between. A special unit is a mapping from the three resulting parameters to a length. The most frequently used special unit is %C, which helps to produce circular arcs.
The use of polar handle coordinates and automatic determination of angles and distances will not be discussed further in this tutorial. The reader is encouraged to follow the links in the text to the reference documentation in order to learn more about path construction.

Painting paths

You are already familiar with one of the path-painting functions, namely ..Shapes..Graphics..stroke. A similar function, ..Shapes..Graphics..fill, will fill the path with paint instead of stroking it, and ..Shapes..Graphics..fillstroke will do both. The following example also shows how to put an arrowhead on the path while stroking it. Besides, it also shows how is used to make a round spot at a given point.
Filling, stroking, filling-and-stroking, and stroking a path with an arrow head. Spotting a point can be seen as a special case of stroking a singleton path.

Source: show/hide visit
Things to note:
  1. The arrowheads are defined in a Shapes extension file. If your Shapes compiler has the path /usr/local/bin/shapes, then the extension files are typically located in /usr/local/share/shapes/extenions/.
  2. Functions can take named arguments, and here the argument called head is some kind of object which defines what the arrowhead shall look like.
  3. As you might have guessed, ..Shapes..Graphics..stroke also takes an argument called tail.

Changing graphics parameters, such as color

Until now, all our drawings have been black on white, and all strokes of the default width 1 bp. In pdf such properties are part of the graphics state, and Shapes gives you control over these parameters via dynamic variables (see Functions, and the lexical and dynamic environments below); just like ..Shapes..Geometry..@defaultunit has an impact on how smooth paths come out, ..Shapes..Traits..@width controls how thick the lines coming from ..Shapes..Graphics..stroke are.
As usual, we use an example to show what the most important parameters are called, and how to set them.
Changing parameters in the pdf graphics state, such as stroking and non-stroking (that is, fill) color, dashpattern, &c.

Source: show/hide visit
Things to note:
  1. The various ways several dynamic bindings can be combined to one set of bindings, including using the function ..Shapes..bindings and inserting the empty set of bindings ..Shapes..nullbind explicitly to make it one operator & per binding.

Final comments

By now you know how to construct the most important basic elements that constitute the visual contents of a page. Thus you are ready to use Shapes as a meta-language for pdf — indeed, the first name of the language was MetaPDF — but that is not what Shapes is meant for. Shapes is all about how you put these elements on the page, and to this end you need to learn more about what programming in Shapes is like.


Under this overly generic title, we'll discuss basic programming concepts, such as states, functions, and structures. As usual, we'll often do this by giving relevant pointers to the reference documentation.

The code bracket

We have already seen how variable bindings can be introduced, like this:
a: 8 + 7
but we have not yet discussed where such code may appear. The proper place to introduce a variable binding is a code bracket. A complete Shapes program is implicitly enclosed in a code bracket, and the user may introduce new code brackets by using the delimiters { and }.
Here, the sum of 7 and 8 is squared by using a variable binding for the sum, and then multiplying that variable by itself:
  a: 8 + 7
  a * a
Code brackets serve many purposes. To learn more, see code-bracket.

Functions, and the lexical and dynamic environments

In this section, the pure functions will be introduced. These are my favorite function-like abstraction, and hence what you will find in most code examples. Other function-like abstractions will be introduced later, after state have been properly introduced.
A function has no side effects. Given arguments and a dynamic environment, a function returns a value. Given the same arguments and the same dynamic environment, the function will always return the same result. This implies that a function must not be allowed to access anything which can change from time to time. In Shapes these things which can change are the states, so the rule is simply that a pure function is not allowed to access any states. Note that this does not mean a function cannot use states internally to compute its result.
The following example shows how a function is constructed:
textGreater: \ x y .> [if x > y `true´ `false´]
The formal parameters appear between \ and .>, and the expression that follows is the body of the function. The result of calling the function is obtained by evaluating the body, with the formal parameters added to the lexical scope For instance,
•stdout << [textGreater 4 5]
would result in false being written to stdout.
The above example code shows what a typical function call looks like, with the callee and arguments enclosed in square brackets. When there is only one argument, one may reorder things a little:
•stdout << sin [] 30°
•stdout << 30° >> sin
Please read the short discussion about unary calls at this time.
Let us also show how evaluated cuts can be used to bind just some of the arguments of a function:
textGreaterThanEight: [textGreater y:8 ...]
In combination with evaluated cuts of log_after, >> provides a rather clean way to write stuff to the log file at an arbitrary location in the program. Here is an exmaple:
fun: \ a →
    b: 10 * a
    a + b
      >> [debuglog_after ( newString << `The value of a is ´ << a << "{n} ) ...]
      >> [debuglog_after ( newString << `The value of b is ´ << b << "{n} ) ...]
Recall that global states such as ..Shapes..IO..•stderr is not in scope inside the function body, but by making stderr the debug log file (see man page), this is a way to get around the function's scoping barrier.
There is much more to say about evaluated cuts, but we refer to the language reference (including the section linked above, but please note that there are other related sections as well), and just include an example here.
Evaluated cuts
Evaluated cuts may take some time to grok. Use this example as an exercise!

Source: show/hide visit

stdout: show/hide
Function taking an arbitrary number of arguments can be defined by the use of a sink. This is an advanced topic, also discussed in syntax.html.
The lexical scope is the set of bindings which can be “seen” from a particular point in the code. This is a very common concept, explained in more detail in syntax.html (where the reader will also find the example included below), and the reader is referred to [Google `"lexical binding"´] for further in-depth discussions. One thing to note about the lexical scope in Shapes, though, is that when a new binding is introduced, the scope where the binding is introduced is in scope where the right hand side expression is evaluated. That is, defining (mutually) recursive functions is trivial.
Lexical binding
Lexical scoping rules in Shapes make it easy to define (mutually) recursive functions. To access bindings shadowed in the current scope, one can reach out by using the ../ construct.

Source: show/hide visit

stdout: show/hide
Dynamic bindings, recognized by being prefixed with the at sign (@) differ from lexical bindings in that one cannot determine where they were bound by a lexical analysis of the code. Instead, one must (conceptually) unwind the chain of function calls to find the closest point where the dynamic variable was bound. We have already seen dynamic variables in use in Changing graphics parameters, such as color, although the examples seen there were very simple and did not really display the power of dynamic binding. The main documentation is in syntax.html (the example below also appears there), but interested readers should also try [Google `"dynamic binding" lexical´] for further in-depth discussions. The thing to note about dynamic bindings in Shapes is that a dynamic variable can be bound to an expression that is to be evaluated each time the dynamic variable is accessed. This feature i called an dynamic expression (note that these can only be bound to dynamic variables).
Since dynamic bindings and the way it is accomplished in Shapes is a very important topic for this tutorial, study the following examples carefully! The first example just motivates the notation used for one of the operators used to combine dynamic bindings, do not expect to understand this example completely until after reading the second example.
Notation of the asymmetric binding union operator
The story behind the notation of the operator &|, and a typical application of it.

Source: show/hide visit
Dynamic binding
Dynamic scoping in Shapes allows for both dynamically bound variables and dynamically evaluated expressions. The keyword dynamic is used both for introducing new dynamic bindings and to indicate that an expression is to be evaluated dynamically.

Source: show/hide visit

stdout: show/hide


By now, we know that a pure function may not access states, and we have seen the globally defined states ..Shapes..IO..•page and ..Shapes..IO..•stdout in use in the examples. Here, the reader will learn how to set up new states, and what the basic operations on states are. How states are used with non-pure functions is postponed until Non-pure functions.
A state may be introduced in a code bracket just like a variable binding:
•myState: newGroup
The right hand side is a value that is able to spawn new states. Shapes comes with both globally bound values with such capability (for example ..Shapes..Graphics..newGroup) and functions producing such values (for example ..Shapes..Numeric..Random..newRandom).
The states themselves are of state type. For instance, ..Shapes..IO..•page is of type §•Group. The operations on a state which we are about to describe briefly are further described in the documentation for state types.
There are three basic operations, tack on, peek, and freeze. The first of these looks like a c++ insertion operation, and may repeated:
•myState << (TeX `Hello!´)
         << [stroke (1cm,1cm)--(2cm,0cm)]
The state is free to modify itself as it pleases when values are tacked on to it.
The next operation, peek, may also modify the state, but shall also return a value somehow representing the current state of the state. The syntax is simply to enclose the state in parentheses:
picture: (•myState)
Not all state types allows the state to be peeked, usually for efficiency reasons.
The last operation, freeze, returns a value representing the state, and renders the state unusable in the future. This allows for efficient implementation. The state is frozen by writing a semicolon after the state, and this operation is only allowed as the right hand side of a variable binding, or as the last statement in a code bracket. For instance,
finalState: freese •myState
Not all state types allows the state to be frozen.
States can also be used without naming the state. For instance:
picture: ( newGroup << (TeX `Hello!´) << [stroke (1cm,1cm)--(2cm,0cm)] )
gives the same result as
picture: ( (TeX `Hello!´) & [stroke (1cm,1cm)--(2cm,0cm)] )
We end this section with an example.
States allow imperative style programming to some extent in Shapes. This example also illustrates non-pure functions, which have not yet been discussed.

Source: show/hide visit

stdout: show/hide

Non-pure functions

A non-pure function is like a pure function, except that it also takes state arguments by reference. This means that the result of a non-pure function call cannot be cached for reuse, and that the side effects the function may have are limited to the states explicitly passed in the call.
Non-pure functions are defined just like pure functions, and called the same way. The only difference is that states appear among the formal parameters, and that states needs to be passed for these formal parameters. The following example shows how a non-pure function is used to add a stroke to ..Shapes..IO..•page:
mark: \ •dst .> { •dst << [stroke (0cm,0cm)--(1cm,1cm)] }
[mark •page]
A non-pure expression such as [mark •page] may be used as a statement, which is completely unlike a pure expression such as [stroke (0cm,0cm)--(1cm,1cm)]. Statements may appear anywhere in a code bracket, while expressions only make sense at the end of a code bracket.
When a function is called, the formal parameters are bound in a scope where the body of the function is then evaluated. One can think of this scope as a barrier through which states cannot be reached. In early versions of Shapes, there were also procedures, which did not have such a barrier. Their use had to be controlled in certain ways during runtime, were always consiered an ugly feature of the language until the day they were removed. They could be removed without the pain that usually comes with backwards incompatible changes, simply because they had never been used in applications.
See the example under States for more examples.


Structures are the simple means for data abstraction currently supported by Shapes.
A common application of structures that a user may need to know about is arrowheads. An arrowhead function shall, given a path, return a length that the path shall be shortened by, and a picture that is the actual arrowhead. This would be a valid arrowhead function:
\ pth .>
    cut: 2mm
    picture: [fill [circle 1mm]] >> [shift pth.begin.p]
Note that the names of the field in the structure is part of the structure's type, and hence of the function's signature.
Structures have many uses. Please refer to syntax.html for an example and more details.

Where to go from here

This is the end of the tutorial. When you are ready for more, you are recommended to look at the topics which are covered more in-depth in the user's guide.
Get Shapes at Fast, secure and Free Open Source software downloads