September 93 - QUICKDRAW GX FOR POSTSCRIPT PROGRAMMERS
QUICKDRAW GX FOR POSTSCRIPT PROGRAMMERS
DANIEL LIPTON
With QuickDraw GX, the Macintosh gets a brand new, powerful, and totally different
model for text and graphics. Programmers of graphics and page layout applications
accustomed to using custom PostScript code during the printing process will have to
learn new techniques for imaging on the Macintosh, but the reward is a robust feature
set, an easier API, and consistent output whether to a screen (of any resolution or
color depth) or a printer (PostScript, raster, or vector). This article should help
those programmers make the transition.
QuickDraw, while a powerful imaging model for its time and well suited for
interactive graphics on the screen, lacks many features demanded by today's users. To
provide features such as transformation (rotation, skewing, and so on) and Bézier
curves (ubiquitous in most modern graphics applications), applications in a
QuickDraw world must do much of the work of drawing to the screen themselves.
However, when printing to PostScript devices such as the Apple LaserWriter, these
applications can offload much of this work to the printer by simply using the
PostScript language to draw most, if not all, of their graphics and text. For this
reason, many Macintosh application programmers have also become PostScript
programmers and know how to get things done with the PostScript language.
WHAT IS POSTSCRIPT?
Before getting into the details of how to make the transition from the PostScript
language to QuickDraw GX, you need to understand the two models. The article "Getting
Started With QuickDraw GX" in this issue of develop provides an introduction to
QuickDraw GX. For an overview of the features of the PostScript language, read on.
The PostScript language is probably best known as a robust graphics model with many
capabilities. These capabilities include the ability to fill or frame paths made up of
line and cubic Bézier segments, render continuous tone images in both color and
grayscale, transform graphics with a matrix, and clip to a path made up of line and
cubic Bézier segments. PostScript code can also draw text in a variety of different
typefaces and manipulate this text as a graphic.
To a limited extent the PostScript language is also a printing model. Certain operators
in the PostScript language are related to printing. These include operators for page
control (showpage and copypage), for controlling paper selection, and for
controlling device-specific features (setpagedevice in PostScript Level 2).
In addition, the PostScript language serves as a document interchange format. Since
it's so widely available on so many different platforms and printers, a PostScript file
can be treated as a device-independent document interchange. (However, it's not
easily edited except by an expert.) Similarly, it's also used to export clip art.
Encapsulated PostScript (EPS) files are widely used for exporting and importing
artwork into documents.
But the most important attribute of the PostScript language is that, more than a
graphics model, it's a programming language with most of the constructs of modern
high-level languages. The PostScript language is really a wrapper for the PostScript
graphics model. The graphics are invoked by operators in the language. This full
programmability makes it easy for programmers to extend the PostScript model to
meet their needs. If a desired feature isn't in the PostScript graphics model, it can
frequently be programmed in the PostScript language. For example, PostScript Level
1 doesn't contain patterns, but a PostScript procedure can be written to fill a
PostScript path with a pattern.
Due to this programmability, it's possible to emulate directly on PostScript printers
many of the QuickDraw GX features that aren't present in the PostScript graphics
model. When QuickDraw GX generates a PostScript stream, it includes a complex set of
PostScript procedures to do so.
COMPARING QUICKDRAW GX AND POSTSCRIPT
This section compares QuickDraw GX and the PostScript language in terms of their
graphics, text- drawing, printing, and programming models.
THE GRAPHICS MODEL
Some differences between the QuickDraw GX and PostScript graphics models include
math types, Bézier curves, matrix transformation, and orientation of the y-axis.
Math types. Before entering the world of QuickDraw GX programming, a PostScript
programmer must understand the basic differences in how numbers are represented
by QuickDraw GX and the PostScript language.
The PostScript language uses floating-point numbers and QuickDraw GX uses
fixed-point numbers. The advantage to floating-point numbers is numeric range; the
advantage to fixed-point numbers is speed. With fixed-point numbers, addition and
subtraction are no slower than with regular integers. QuickDraw GX uses 16.16
fixed-point numbers (32-bit numbers with the high 16 bits representing the integer
portion and the low 16 bits representing the fractional portion).
In the PostScript language, color component values are represented by floating-point
numbers between 0.0 and 1.0. In QuickDraw GX, color component values are
represented by a type called colorValue, which is really a short such that 0x0000 is
0.0 and 0xFFFF is 1.0. QuickDraw GX also uses a type called fract. The fract type is
like the fixed type except that only the high two bits are the integer portion and the
low 30 bits are the fractional portion. This is generally used for numbers between -1
and 1 where fractional precision is important.
Curves. Both QuickDraw GX and the PostScript language support Bézier curves.
However, each supports a different kind (see Figure 1). While the PostScript language
uses cubics, GX uses quadratics. A cubic Bézier curve segment is defined by four
control points: a starting point on the curve, two points off the curve, and an ending
point on the curve. A quadratic Bézier curve is defined by three control points: a
starting point on the curve, a control point off the curve, and an ending point on the
curve.
Figure 1 Comparing Control Points on Bézier Curves
Figure 2 illustrates two similarly shaped paths. The one on the left is defined by two
quadratic segments, requiring five control points. The one on the right is defined by a
single cubic segment, requiring four control points. This seems to imply that in
drawing similar shapes, more points are required using quadratics than using cubics
and that, therefore, quadratics are at a disadvantage. However, to reduce data size, the
data structure for a QuickDraw GX path allows implied points. Each point in the
QuickDraw GX path has a control bit, indicating whether the point is on or off the
curve. If two consecutive points in the path are off the curve, there's an implied point
halfway between the two explicitly specified points. So, as shown in Figure 2, it's only
necessary to supply four points for the quadratic path, as the point between point 2
and point 3 is implicit.
Figure 2 Control Points for Paths
Matrix transformations. Both QuickDraw GX and the PostScript language allow
anything to be transformed through a matrix before being drawn. Both use a 3 x 3
transformation matrix. However, in the PostScript language the matrix has implicit
constant values in the last column, so there are only six degrees of freedom rather
than nine. QuickDraw GX allows you to specify all nine elements of the matrix.
To modify the current transformation matrix (CTM) by a given transformation, an
application may use the following PostScript code:
[ 4.17 0.0 0.0 -4.17 -1280.0 1650.5 ] concat
This code concatenates the following matrix with the CTM in the graphics state:
4.17 0.0 0.0
0.0 -4.17 0.0
-1280.0 1650.5 1.0
QuickDraw GX has a data structure called gxMapping, which is a structure containing
one field. The field is a 3 x 3 array. The first two columns contain fixed-point
numbers and specify the skewing, scaling, rotation, and translation of the
transformation. The third column is made up of fractional numbers (numbers of type
fract) and specifies the perspective portion of the transformation. The following code
generates a mapping that's equivalent to the matrix in the PostScript code:
/* Declare a mapping structure (fract1 is a constant for 1.0 in
mathtypes.h). */
gxMapping aMapping =
{ { fl(4.17), fl(0.0), fl(0.0) }
{ fl(0.0), -fl(4.17), fl(0.0) }
{ -fl(1280.0), fl(1650.5), fract1 } };
Figure 3 Coordinate Systems
The y-axis. The y-axis orientation differs in the PostScript graphics model and
QuickDraw GX. In the PostScript model, the y-axis increases from the bottom of the
page or window to the top and in QuickDraw GX, as in QuickDraw, it increases from top
to bottom (see Figure 3).
OBJECT-BASED MODEL VERSUS STREAM-BASED PROTOCOL
A fundamental difference between graphics code for QuickDraw GX and PostScript code
is that QuickDraw GX is object-based and PostScript code is essentially a
stream-based protocol. Although the PostScript language is a programming language,
documents usually consist of a set of PostScript procedures followed by a stream that
invokes those procedures. Each model has advantages: With a stream-based protocol
the graphic content of any given page is virtually infinite. As long as PostScript code is
continuously streamed to the printer, it renders into the frame buffer untilshowpage
is issued -- which essentially says, "This page is done; start the next one." With an
object model it's relatively easy to share data between objects. A quick summary of
objects in QuickDraw GX illustrates this advantage.
QuickDraw GX objects. The shape object is the basic element of the QuickDraw GX
graphics model. A shape contains a geometry of any primitive type and points to three
other objects that describe how to render that geometry: the ink object, which
describes how to apply color to the geometry (as well as transfer mode); the style
object, which describes how to affect the geometry before rendering (pattern, dash,
and so on); and the transform object, which describes how to map and clip the
geometry before rendering. These objects can, in turn, point to other objects. For
example, a transform object points to a list of view port objects that describe where to
draw the geometry (such as in which window). An ink object points to a color profile
object that describes the colors in the ink in a device-independent manner. All the
previously described objects could also have lists of tag objects. A tag object is simply
a container for any data the application associates with the owning object.
Data sharing is extremely easy in this object model. If I have a picture made up of 100
different shapes and 30 of them have the same color, these 30 shapes can all point to
the same ink object. The color for these 30 shapes is stored only once. In a
stream-based protocol, it's only convenient to share data between consecutive items in
the stream. (You can write PostScript code that shares data between nonconsecutive
objects, but it's not easy.)
PostScript procedures and dictionaries versus QuickDraw GX objects.
Emulating the object model in PostScript code is possible because it's a programming
language. You could use PostScript dictionaries as containers for shapes and then have
a PostScript procedure that draws one of these dictionaries. The following is a simple
example of how this could work. (Warning: Serious PostScript code ahead.)
/aShape 7 dict def % Make a dictionary for the shape.
aShape begin % Put it on the dictionary stack.
/geometryProcedure { % Define a procedure for the geometry
newpath % to draw a rectangle.
100 100 moveto
0 100 rlineto
100 0 rlineto
0 -100 rlineto
closepath
} bind def
% Dictionary entries for transform.
/Transform [ 10 0 0 10 0 0 ] def
% Dictionary entries for the color.
/redComponent 1.0 def
/greenComponent 0.0 def
/blueComponent 1.0 def
/penWidth 5.0 def
/fillType (framed) def
end % Dictionary definition.
% The following procedure takes a shape dictionary and draws it.
/DrawShapeDict {
begin % Put the shape dictionary on the stack.
gsave % Shape shouldn't affect graphics state.
Transform concat % Apply transform.
redComponent greenComponent blueComponent setrgbcolor
% Set the color.
geometryProcedure % Execute the geometry procedure.
fillType (framed) eq { % If the shape is framed,
penWidth setlinewidth % set the pen width and
stroke % stroke the path.
} { % Else, fill the shape.
eofill
} ifelse
grestore
end
} bind def