Transformations

The PostScript interpreter keeps track of a matrix called the current transformation matrix. When constructing an image, the interpreter uses this matrix to convert the world coordinates used by the program into device coordinates used by the printer itself. Generally, the actual contents of the matrix are of little interest to a well-written PostScript program; the reason for this is that the specific contents are device-dependent. A program that uses them might not work properly. PostScript does provide a number of operators, however, that transform the matrix in a device-independent way. These operators allow you to transform the way user space maps onto device space, and they modify the current transformation matrix with a simple matrix transformation. The basic transformation operators are:

It is useful to realize that the current transformation matrix (and, hence the effect of all these operators) is part of the current graphics state and can be saved and restored using the gsave, and grestore operators. In addition, the transformations on the matrix affect path components constructed after the transformation. Even if a path is only partially constructed when a transformation is invoked, the parts of the path that were in place before the transformation will be unaffected.

Rotate

The rotate operator takes a single, numerical operand. This operand specifies how many degrees to rotate the user space around its origin (positive values specify counter clockwise rotations). This transform allows you to do some pretty neat tricks. For example, let’s say you have written a routine to draw some complex shape; and you have found that you need to draw it several times at different angles. In a more primitive graphics system, you might need to re-write to routine to take an angle as an argument, but in PostScript you only need to rotate the coordinates with the rotate operator.

As a concrete example, let’s say you want to draw lines in a circular pattern so that each line is ten degrees from its neighbors. Rather than figure out the coordinates for each of the 36 lines, we can just draw a horizontal line and rotate it repeatedly to different angles. To do the repeated looping, we can use the for operator. The for operator takes four arguments: an initial index value, a step size, a final index value, and a procedure. The operator increments an index from the initial value to the final value, incrementing it by the step size. For each index value, for will push the index on the stack and execute the procedure. This gives you a simple means of looping.

We start by setting up the for loop. At the beginning of the loop’s procedure, we start a new path and save the graphics state.

        0 10 360 {              % Go from 0 to 360 degrees in 10 degree steps
          newpath               % Start a new path
          gsave                 % Keep rotations temporary

We next set the start of the line to (144, 144) and rotate the coordinates, we do not rotate before moving because (144, 144) would then be in a different location.

            144 144 moveto
            rotate              % Rotate by degrees on stack from 'for'

We next draw just a horizontal line:

            72 0 rlineto
            stroke

Finally, we restore the old graphics state and end the loop.

          grestore              % Get back the unrotated state
        } for                   % Iterate over angles

Translate

The translate operator takes two operands: an x-coordinate, and a y-coordinate. The translate operator sets the origin of user space to the point that was at the given coordinates in user space. The main use of the translate is to draw copies of a shape in different locations. Typically, a shape will be constructed at the origin, and the shape will be translated to the correct location before it is to be drawn. A simple example translates a box constructed at the origin to the point (72, 72) in the original user space.

Scale

The scale operator takes two arguments: an x scale factor, and a y scale factor. The operator scales each coordinate by its associated scale factor. That is, if you have an x scale factor of 0.5 and a y scale factor of 3, the x coordinate will be reduced by a factor of two while the y coordinate will be magnified by a factor of 3. This operator allows you to change the size and dimensions of objects quite easily.

A simple example can just scale text in a couple of ways: We can make things narrow:

        gsave
          72 72 moveto
          0.5 1 scale           % Make the text narrow
          (Narrow Text) show    % Draw it
        grestore

We can make things tall:

        gsave
          72 144 moveto
          1 2 scale             % Make the text tall
          (Tall Text) show      % Draw it
        grestore

We can distort the text completely:

        gsave
          72 216 moveto
          2 0.5 scale           % Make the text wide and short
          (Squeezed Text) show  % Draw it
        grestore

Combining Transformations

Each of these transformations merely modifies the current transformation matrix. This means that these operators can be combined for some interesting effects. For example, you can take a normal document and print two of its pages on a single page (reduced and placed side-by-side) simply by translating the first page to one side, rotating the page by ninety degrees and then reducing the page so that it fits. The second page is handled in the same manner, but is translated to the other side of the page. This can be easily done by PostScript post-processors so long as they know where one page ends and the next begins (this is often accomplished using special comments). A somewhat simpler example is to draw a simple box and some text translated, rotated, and scaled in various ways. An important thing to remember when viewing this example is that translations are always relative to the current user space. This means that

        0.5 0.5 scale
        72 72 translate

will have a different effect on the image than does

        72 72 translate
        0.5 0.5 scale

In the first case, the origin will be half an inch in from the bottom and left margins. In the second case, the origin will be an inch in from the two margins.