September 93 - GETTING STARTED WITH QUICKDRAW GX
GETTING STARTED WITH QUICKDRAW GX
PETE ("LUKE") ALEXANDER
A beta version of QuickDraw GX comes to you on this issue's CD. As you
contemplate the vast scope of it all, you may wonder how you're ever going to get
your arms around this new imaging technology. Not to worry -- this article will get
you started. It walks you through the steps to getting QuickDraw GX up and
drawing and presents a simple "GX-hip" application shell that incorporates the basics
for you to experiment with.
QuickDraw GX offers developers a totally new and markedly improved way of imaging on the
Macintosh. Yes, you'll have to learn the new system, but look at what you get: The API is simpler
and the human interface is better. The amount of control your application can exercise over text and
graphics has been greatly increased. Your application will be able to produce consistent output no
matter what the output device. And extensive support for color is built in throughout the system.
With the beta version of QuickDraw GX in hand, you're no doubt eager to create a QuickDraw GX
application and start drawing. This article covers just about everything you need to know to get
started: initializing QuickDraw GX, using windows, creating and manipulating QuickDraw GX
shapes, printing, and debugging. By way of illustration, we discuss the QuickDraw GX shell that
you'll find on this issue's CD. But before we do that, let's take a quick look under the hood at the
major features of QuickDraw GX and how it fits into the Macintosh architecture.
QUICKDRAW GX: A QUICK LOOK UNDER THE HOOD
QuickDraw GX coexists happily with QuickDraw, thank goodness. It doesn't replace QuickDraw,
but instead "moves in next door," so you still live with a Macintosh Toolbox based on QuickDraw.
You can run QuickDraw-based applications on a QuickDraw GX system. These applications won't
even notice that QuickDraw GX is installed, but they'll be able to take advantage of some of the
QuickDraw GX printing features, including improved background printing to all devices, desktop
printers, print job queuing, and better type management.
QuickDraw GX has three major pieces: graphics, typography, and printing. You can visualize the
relationship of these three different pieces to each other and to QuickDraw as shown in Figure 1.
Figure 1 QuickDraw and the Pieces of QuickDraw GX
GRAPHICSThe basic building block of QuickDraw GX graphics is theshape. A shape is an object that contains,
among other things, a geometry of some type and a fill property that specifies how the geometry
should be interpreted when drawn (such as framed or filled).
There are four basic types of shapes, classified by the nature of the geometry they contain: geometric
shapes, typographic shapes, bitmap shapes, and pictures.
- A geometric shape contains a primitive geometry: a point, a line, a curve, a rectangle,
a polygon (a series of points connected by straight lines), or a path (a series of
points connected by straight or curved lines). In addition, there are two other
special geometric shape types: empty (no geometry at all) and full (covers the
entire coordinate system).
- A typographic shape contains text, glyphs (renditions of individual characters or
character combinations over which your application has direct control), or layouts
(pieces of text for which QuickDraw GX automatically chooses and positions
glyphs, given certain information by the application).
- A bitmap shape contains a reference to a block of memory containing a bit image,
as well as information on how to interpret the bits: the pixel size, color space,
color set, and color profile.
- A picture contains a list of other shapes. The shapes in the list can be other
pictures, so that a picture is actually a hierarchical database of shapes.
Besides containing a geometry, a shape contains references to three other objects that describe how it
should be rendered. These objects are the style, the transform, and the ink.
- The styledefines the pen thickness, the place where the pen draws (inside, outside,
or on the geometry), the kind of start and end cap (such as round, pointy, or
square), and ways to dash, join, and pattern shapes. For a text shape, the style also
defines the font, size, variation, and text face.
- The transformcontrols the skew, scale, perspective, and clipping of the geometry.
It also specifies where to draw it and how to hit-test it.
- The inktells the system which color to draw the geometry in. Ink also includes
information about the color matching and transfer mode.
Some of these objects in turn contain references to other objects. For example, a transform points to
a list of view port objects that describe where to draw the geometry. A view port is like a QuickDraw
grafPort in that it defines an area of local space as a drawing environment. Unlike a grafPort, though,
a view port doesn't contain state information about the drawing environment (pen, color, transfermode, and so on).
A view port contains the mapping used to convert from the view port's local space
to a global space described by aview group. A view port object points in turn to a list
of view device objects, which describe the clip shape, mapping, and bitmap associated with a physical device such as
a monitor.
A shape can also have one or moreattributes, which modify the shape's behavior. These attributes
enable your application to specify how a shape is edited or how QuickDraw GX stores the shape. For
example, if you set the shape attribute gxMapTransformShape, this tells QuickDraw GX that you
want it to manipulate the transform referenced by the shape, instead of directly manipulating the data
contained within the geometry of the shape.
Figure 2 depicts the shape object and what it references. The owner count is the number of other
objects within the application that reference that object. The tag list is a list of tag objects, which are
simply containers for any data the application associates with the owning object.
Figure 2 The Shape Object and What It References
TYPOGRAPHY
QuickDraw GX has a sophisticated typographic model that's fully integrated with graphics. The
ability to do kerning, tracking, and justification, as well as ligatures and ornamental forms of various
characters, is provided by the line layout routines, supported by the QuickDraw GX smart font
format. The line layout routines work with the typographic information contained in the TrueType
GX and Type 1 GX fonts to give you a ton of control over how text is placed on a page.
Because QuickDraw GX typography is fully integrated with graphics, you can rotate, skew, and
change the perspective of typographic shapes the same way you can geometric shapes. You can use
the text shape to draw a line of text with one style. The glyph shape enables you to draw text in
several styles and graphically manipulate each glyph. The layout shape uses the information
contained in a TrueType GX or Type 1 GX font to automatically kern, justify, and track, and to
support ligatures, final forms (special forms found at the ends of words), and ornamental forms of the
various glyphs contained within the layout shape.
Note that although QuickDraw GX supports all existing Macintosh font formats (Type 1, bitmap,
and TrueType), to take full advantage of its extensive line layout capabilities you must use TrueType
GX or Type 1 GX fonts.
PRINTING
QuickDraw GX improves printing for both users and developers. Users get an improved human
interface, and developers get much more control and functionality. From the application's point of
view, QuickDraw GX offers true device independence: you can send the same data to all supported
devices and the output will be rendered appropriately on each device.
QuickDraw GX introduces three new printing concepts: desktop printers, portable digital
documents, and printing extensions.
Users can createdesktop printerswith the Chooser. These are represented as icons on the desktop and
are full Finder citizens; users can drag and drop print files and documents to them. Users can also
manage the print queue and redirect print files and documents by dragging them to and from
desktop printers, and can share desktop printers with other users via PrinterShare GX.
A portable digital document(PDD) file contains all the objects required to render a document on a
screen or printer, so you can open, review, and print the file on any system running QuickDraw GX
without the application or fonts used to create the document. When a PDD file is created, only the
glyphs used in the document are saved along with it; since the document can't very well be edited,
the PDD is secure for transporting fonts. When you print, you can save the print job as a PDD with
or without the fonts required. If you know that the person you're sending the PDD file to has the
fonts you used, you can choose not to save the fonts with the PDD.
Printing extensionsare small standalone pieces of code that modify the behavior of printing and give
users vastly increased control, at a system rather than a program level, over how a printed page looks.
For example, through a printing extension the user can direct a printer to print "Confidential"
diagonally across each page, no matter what program is doing the printing. The user selects a
printing extension from a list displayed in the expanded Print, By Page Setup, and Document Setup
dialogs (which appear when More Choices is clicked in the regular dialogs).
The API for QuickDraw GX printing gives you easy access to information about the page size and
orientation of a print job and enables you to keep the user from changing these settings.
QuickDraw GX supports raster, vector, and PostScriptTM devices. The bad news is
that if your system is running QuickDraw GX, you won't be able to use any non-QuickDraw GX
printer drivers. The good news is that because QuickDraw GX provides system-level support for
developing printer drivers, it's a whole lot easier to develop printer drivers for QuickDraw GX than
it is for the old QuickDraw-based printing architecture -- you can plan on months of development
time as opposed to years. And in many cases you may find that a printing extension, which is easier
yet to develop, will suffice to implement the desired functionality; for more information, see the
article "Developing QuickDraw GX Printing Extensions" later in this issue.
PROGRAMMING AMENITIES
QuickDraw GX offers you some truly useful programming goodies: libraries of handy high-level
routines, extensive error-handling capabilities, and a powerful new debugging tool called
GraphicsBug.
THE QUICKDRAW GX LIBRARIES
As you cruise around the QuickDraw GX folder on this issue's CD, you'll notice a folder named
Libraries. Open it and you'll find libraries of code for many common graphics, line layout, and
printing tasks. These provide sample code that most applications will need in order to create a
QuickDraw GX application. But unlike Macintosh Toolbox code, this library code can be modified
or extended by you to meet your own particular needs. All the library code is based on core
QuickDraw GX calls.
ERROR HANDLING IN QUICKDRAW GX
The goal of QuickDraw GX's error-handling capabilities is to never allow QuickDraw GX to crash
your Macintosh, and to inform you anytime QuickDraw GX can't complete an operation.
QuickDraw GX uses two different models for handling errors: one for graphics and layout errors and
another for printing errors. We'll discuss graphics and layout errors here. Printing errors are
described later in this article, under "Basic Printing in QuickDraw GX."
There's both a debugging and a nondebugging version of the combined graphics and layout portions
of QuickDraw GX. The debugging version provides extensive error-handling capabilities to help you
debug your applications under development. The nondebugging version is lean and mean; it has
fewer error-handling capabilities and is faster than the debugging version. You can differentiate
between the two versions by their sizes and version strings: the nondebugging version is smaller, and
the version string for the debugging version has the word "debug" in it. When you're developing
your QuickDraw GX application, you should be using the debugging version.
In the debugging version, information about internal data and drawing problems comes in three
flavors: notices, warnings, and errors. Only a few selected errors and warnings are issued in the
nondebugging version.
Notices. A notice informs you that the operation you're performing isn't really needed. Notices aren't
necessarily bad things; they're just information to help you improve the efficiency of your application.
For example, if you've already colored a shape and you try to color it again, you'll receive the
following notice in the installed debugger:
GRAPHICS NOTICE: color already set
Warnings. A warning informs you that QuickDraw GX doesn't allow the operation you're trying to
perform. While this might not cause any problems, you also might not get the result you expected.
For example, if you try to use a font that isn't available, QuickDraw GX will substitute the default
font and give you the following warning:
GRAPHICS WARNING: font substitution took place
Errors. An error means that QuickDraw GX couldn't draw your shape or complete a routine. For
example, if you try to draw an empty shape or one that hasn't been defined, you'll receive the
following error:
GRAPHICS ERROR: shape is nil
Checking for drawing errors. Once you've finished developing your application, you'll still want to be
able to check for drawing errors. The QuickDraw GX routine GXGetShapeDrawError lets you do
this and, in case of an error, fail in a graceful manner. For example, this code fragment checks that
drawing was successful and fails if it wasn't:
GXDrawShape(gthePage);
if (drawingError = GXGetShapeDrawError(gthePage) != noDrawError)
// Your error-handling code here!
Ignoring notices and warnings. Sometimes you might want to ignore a particular notice or warning
because you know what you're doing. Use these routines to ignore a notice or warning:
void GXIgnoreGraphicsNotice(gxGraphicsNotice notice);
void GXIgnoreGraphicsWarning(gxGraphicsWarning warning);
For example, if you wanted to ignore a notice about recoloring a shape, you would make this call:
void GXIgnoreGraphicsNotice(color_already_set);
When you call GXIgnoreGraphicsNotice or GXIgnoreGraphicsWarning, the notice or warning is
added to the top of the notice stack or warning stack, respectively. (It's added to the stack even when
not ignored, but the stack handling is taken care of behind the scenes for you in that case.) So you
must balance this with a call to one of the following routines to ensure that you don't overflow the
notice or warning stack:
void GXPopGraphicsNotice(void);
void GXPopGraphicsWarning(void);
In the nondebugging version, where notices and most warnings aren't available, calling the
GXIgnoreGraphicsXXX and GXPopGraphicsXXX routines still results in a trap call and dispatch
even though they just return immediately. There may be a small performance penalty for this, so you
should remember to remove the unnecessary calls for a shipping application.
Grabbing errors, notices, and warnings. In the nondebugging version, you receive only a few selected
errors and warnings. If you've tested your application thoroughly, these should be the only errors you
see:
out_of_memory
not_enough_memory_for_graphics_client_heap
graphics_client_memory_too_small
could_not_create_backing_store
These should be the only warnings you see:
<<font or character>>_substitution_occurred
<<map, move, scale, rotate, or skew>>_shape_out_of_range
<<map, move, scale, rotate, or skew>>_transform_out_of_range
You probably don't want your user to end up in a debugger or with a system bomb, so you should
catch errors by calling the GXGetGraphicsError routine; you can then handle the error
appropriately within your application or present it to the user.
gxGraphicsError GXGetGraphicsError(gxGraphicsError *stickyError);
You can also grab notices (in the debugging version only) and warnings with these calls:
gxGraphicsNotice GXGetGraphicsNotice(gxGraphicsNotice *stickyNotice);
gxGraphicsWarning GXGetGraphicsWarning(gxGraphicsWarning *stickyWarning);
GRAPHICSBUG: A POWERFUL NEW DEBUGGING TOOL
The only way to create and modify shapes in QuickDraw GX is through the public API; you can't
operate on any data directly. This is a very good thing because it lets Apple expand the system in the
future with minimal compatibility risk. But if you can't see the data you're working with, won't
debugging be a nightmare? Here's where GraphicsBug comes to the rescue. GraphicsBug is an
application that enables you to inspect the contents of any QuickDraw GX graphics or layout object
to make sure it contains the correct information. The command set is very similar to that of
MacsBug; just type "?" to get a list of the commands available. GraphicsBug works only in the
debugging version of QuickDraw GX 1.0b1 but in both versions of later QuickDraw GX releases.
INITIALIZING QUICKDRAW GX
Now that we've checked out the horsepower under the hood and the amenities built in for
programmers, we're ready to get QuickDraw GX up and drawing. The first step is to initialize
QuickDraw GX, but before you do, you need to make sure the user has installed it. Use the Gestalt
selector 'grfx' to determine whether the graphics and typography portions of QuickDraw GX have
been installed, and the Gestalt selector 'pmgr' to determine whether QuickDraw GX printing has
been installed.
In the case of our QuickDraw GX shell, the following routine finds out which parts of QuickDraw
GX have been installed:
Boolean QuickDrawGXAvailable()
{
long theFeatureInQuestion;
if (Gestalt('grfx', &theFeatureInQuestion) == noErr)
{
if (Gestalt('pmgr', &theFeatureInQuestion) == noErr)
gQDGXPrintingInstalled = true;
return (true);
}
return (false);
}
The QuickDraw GX shell uses the global variable gQDGXPrintingInstalled to determine if
QuickDraw GX printing has been installed. If it has, the printing menu items in the File menu are
enabled. Otherwise, an alert tells the user that QuickDraw GX printing hasn't been installed, and the
application works without printing.
Once you know that the user has QuickDraw GX, you're ready to initialize it. After the generic
Macintosh Toolbox initialization, you create a new graphics client to allocate memory. Then you can
set up error handling and validation as an aid to tracking down problems, although if you're eager to
get on with drawing, you don't have to do this right now. Finally, you can initialize the common
color library to get quick and easy color. In the QuickDraw GX shell, the routine QuickDrawGXInit
does all of this initialization.
CREATING A NEW GRAPHICS CLIENT
A graphics client is a reference to the block of MultiFinder memory used by QuickDraw GX
graphics and layout called theQuickDraw GX heap. When your application creates a new graphics
client, QuickDraw GX usually allocates this block of memory. The QuickDraw GX heap contains all
the graphics and layout objects your application creates while running QuickDraw GX, as well as a
few objects QuickDraw GX uses to manage the heap. (See "Managing Memory in the QuickDraw
GX Heap" for more details.)
The simplest way to create a graphics client is to call EnterGraphics, which defines a client for you
based on some fundamental assumptions. If you want to have more control over the graphics client
you create, call GXNewGraphicsClient:
gGraphicsClient = GXNewGraphicsClient(nil,
gGraphicsHeapSize * 1024, 0L);
The variable gGraphicsClient holds the new graphics client. You can use this variable anytime you
need to access this graphics client. In our shell, we only need this variable when the application shuts
down. The first parameter tells the Memory Manager where we want to create the QuickDraw GXheap.
A nil value tells the Memory Manager to create the heap within MultiFinder memory. That's
usually where you want it, but you can also specify a pointer to a block of memory in your
application heap or even the system heap. The second parameter defines the size of the heap in bytes.
Our shell uses a 115K heap (gGraphicsHeapSize = 115); if you pass 0, you get the default heap size of
600K. The last parameter, named separateStack, tells QuickDraw GX to allocate a stack for the
graphics client, which is necessary if that client is going to run at interrupt time. To get a separate
stack, just pass any nonzero value that defines the stack size.
SETTING UP ERROR HANDLING AND VALIDATION
After setting up the graphics client, you should enable the error, warning, and notice capabilities if
you want to make debugging easier for yourself down the line. In our QuickDraw GX shell we
enable all three with the routines SetGraphicsLibraryErrors (which enables errors and warnings) and
SetGraphicsLibraryNotices (which enables notices).
The debugging version of QuickDraw GX graphics and layout provides extensive validation facilities
that let you determine whether you're passing valid data to the QuickDraw GX API and whether
anything's gone awry internally. (See "Tracking Down a Memory Trashing Problem" for details on
how to use validation to find out how damage is being caused in the QuickDraw GX heap.) In
general, you should always run with gxPublicValidation + gxTypeValidation while you're developing
a QuickDraw GX application. To set this level of validation, make this call:
GXSetValidation(gxPublicValidation + gxTypeValidation);
This setting ensures that for all calls, QuickDraw GX checks the routines on entry and makes sure
that the types are correct. For example, if you call GXDrawShape with this validation setting,
QuickDraw GX makes sure that the shape being passed in is a valid shape and that it's an object of
the correct type. This will result in a slight decrease in performance but will help you catch bad data
earlier.
Your application can validate various QuickDraw GX graphics and layout objects -- shape, style,
transform, ink, view port, view device, view group, color profile, color set, tag, or graphics client, or
any combination -- with a GXValidate call before they're passed to a routine. For example, if you
wanted to validate the inks in the heap, you would make the following call:
GXValidateInk(gxInk);
If you're interested in using only these validation calls, set the validation level to gxPublicValidation.
SETTING UP THE COMMON COLOR LIBRARY
The common color library provided by QuickDraw GX gives you a quick way to color the shapes
you create. This library contains 103 common colors, including maroon, teal, fluorescent blue, apple
green, Mars orange, and Venetian red. To initialize this library, make this call:
InitCommonColors();
With the color library set up, you can call SetShapeCommonColor to color a shape. We'll do this
later with the shapes we draw in the QuickDraw GX shell window.
USING WINDOWS WITH QUICKDRAW GX
Now that you've initialized QuickDraw GX, you need to set it up to work within a window. The first
step is to create a view port and attach it to a window. Then you need to provide routines to zoom
and resize the view port and to scroll its contents.
Recall that QuickDraw GX draws all shapes in a view port, which contains the mapping used to
convert from the view port's local space to the global space (described by a view group). The view
group determines whether the contents of the view port are drawn on the screen in a window or to
an off-screen area. A view port also contains its clip shape, describing the area in which drawing can
take place.
ATTACHING A VIEW PORT TO A WINDOW
By attaching a view port to a window, you guarantee that all the shapes you draw to the view port
will be drawn in the correct location even after the user moves the window. You attach a view port to
your window with this call:
gxViewPort windowParentViewPort;
windowParentViewPort = GXNewWindowViewPort(theWindow);
If all goes well, windowParentViewPort contains a reference to the view port attached to the window
(the parent view port). You can't change the clip shape or mapping of this particular view port,
because QuickDraw GX automatically maintains all the characteristics of the parent view port. This
is a problem if you don't want your scroll bars to be overwritten. However, you can attach a view
port to the parent view port (see Figure 3) and then adjust this newest view port (the child view port)
to reflect changes due to scrolling or resizing the window.
Figure 3 The View Port Hierarchy
Defining the new view port's bounds. Before we create a new view port we need to determine its
bounds. We can find the bounds of the window by converting the window's QuickDraw portRect
into a fixed-point QuickDraw GX rectangle. The following utility routine does this.
void GetFixedWindowBounds(WindowPtr myWindow,
gxRectangle *boundingBoxPtr)
{
GrafPtr oldPort;
Rect qdBounds;
gxRectangle gxBounds;
GetPort(&oldPort);
SetPort(myWindow);
qdBounds = myWindow->portRect;
// Convert the QuickDraw rectangle into a GX fixed-point
// rectangle.
GXQDGlobalToFixedLocal((Point *) &qdBounds.top,
(gxPoint *) &gxBounds.left);
GXQDGlobalToFixedLocal((Point *) &qdBounds.bottom,
(gxPoint *) &gxBounds.right);
*boundingBoxPtr = gxBounds;
SetPort(oldPort);
}
Now we can create a QuickDraw GX rectangle that represents the portRect of the window. The
rectangle will reside in the variable viewRect.
GetFixedWindowBounds(theWindow, &viewRect);
We need to adjust viewRect for the scroll bars attached to the window.
viewRect.right -= ff(kScrollBarWidth - 1);
viewRect.bottom -= ff(kScrollBarWidth - 1);
Creating and activating the child view port. Now we're ready to create a child view port that's attached
to the parent view port. We want to create a new view port within the same view group (that is,
sharing the same global space) as the window's view port:
gxViewPort gcontentViewPort;
gcontentViewPort =
GXNewViewPort(GXGetViewPortViewGroup(windowParentViewPort));
The gcontentViewPort variable now contains a valid view port that we can work with. Now we're
ready to set the clip shape of this view port. The clip shape can be any geometry-based shape like a
rectangle, a polygon, or a path. The clip shape for gcontentViewPort will simply be defined by the
rectangle contained in viewRect, which is the portRect of the window, minus the scroll bar areas.
After we set the clip shape, we dispose of the shape because it's no longer needed, thereby freeing up
space within the QuickDraw GX heap:
gxRectangle contentViewPortShape;
contentViewPortShape = GXNewRectangle(&viewRect);
GXSetViewPortClip(gcontentViewPort, contentViewPortShape);
GXDisposeShape(contentViewPortShape);
Next, we need to set the mapping of gcontentViewPort to be the default mapping and attach
gcontentViewPort to the parent view port:
GXSetViewPortMapping(gcontentViewPort, nil);
GXSetViewPortParent(gcontentViewPort, windowParentViewPort);
Now we need to tell QuickDraw GX which view port we want the shapes to be drawn in. We could
have all shapes drawn to both view ports, but that wouldn't be especially efficient. So we make the
following call, which tells QuickDraw GX to draw all the shapes we make from now on in
gcontentViewPort:
SetDefaultViewPort(gcontentViewPort);
ZOOMING AND RESIZING THE CHILD VIEW PORT
Anytime the user zooms or resizes the window, we must update the clip shape of the child view port
we attached to the window's parent view port. To do this, we get the portRect of the window in a
fixed-point rectangle, adjust it for the scroll bars, create a new clip shape from this rectangle, and
reset the clip shape of the view port to this new clip shape. The following routine does the work:
void ResetContentViewPortClip(WindowPtr theWindow)
{
gxRectangle viewRect;
gxShape contentViewPortClipShape;
// Get the window's portRect into the fixed-point viewRect.
GetFixedWindowBounds(theWindow, &viewRect);
// Adjust the viewRect to accommodate the scroll bars.
viewRect.right -= ff(kScrollBarWidth - 1);
viewRect.bottom -= ff(kScrollBarWidth - 1);
// Create and set the new clip shape.
contentViewPortClipShape = GXNewRectangle(&viewRect);
GXSetViewPortClip(gcontentViewPort, contentViewPortClipShape);
GXDisposeShape(contentViewPortClipShape);
}
SCROLLING THE CHILD VIEW PORT'S CONTENTS
When the user scrolls in the window, we need to reset the mapping of the child view port before we
call ScrollRect to scroll the bits in the window. That will ensure that when we redraw the contents of
the window on the next update event, all the shapes will be located in the correct window-relative
position. Otherwise they would be redrawn in their old position, because the geometry of the shapes
-- which includes their position in the view port -- doesn't change.
This remapping approach gives us an advantage at print time. If we didn't adjust the mapping of the
child view port, we would need to adjust the mapping of each shape. While it's possible to do that,
we would have to do it again at print time to ensure that the shapes printed on the right page. If the
page contained a lot of shapes, that could be a very time-consuming operation.
To update the mapping of gcontentViewPort (the child view port) to reflect the scrolling of the
window, we get its current mapping, adjust it to translate the view port by the scroll amount, and set
the mapping to the changed one.
gxMapping viewPortMapping;
GXGetViewPortMapping(gcontentViewPort, &viewPortMapping);
GXMoveMapping(&viewPortMapping, ff(hScroll), ff(vScroll));
GXSetViewPortMapping(gcontentViewPort, &viewPortMapping);
CREATING, MANIPULATING, AND DRAWING QUICKDRAW GX SHAPES
At this point, you've learned how to initialize QuickDraw GX and deal with view ports. It's finally
time to talk about creating, manipulating, and drawing shapes.
A shape contains all the information required to draw it. To create a shape with QuickDraw GX, you
simply define its geometry. Then you can draw it by calling GXDrawShape(myShape). If you haven't
specified otherwise, your shape will use the default style, transform, and ink supplied by QuickDraw
GX for the particular type of shape. When you change a shape's style, transform, or ink, QuickDraw
GX copies a reference to the new style, transform, or ink into your shape.
To illustrate the process of creating, manipulating, and drawing shapes, in our QuickDraw GX shell
we'll create a typographic shape containing text. We'll outline the text in some color and fill the
inside of each letter with a pattern composed of stars. Then we'll create a typographic shape
containing a line layout of some text, which we'll render in a combination of different fonts and
scripts.
In the QuickDraw GX shell we'll use a picture, which we'll store in the global variable gthePage, to
collect all the shapes we draw to the window. Using a picture enables us to make just one call to
GXDrawShape to draw the contents of the window. We also need to set the gxUniqueItemsShape
shape attribute so that each time we add a shape to the picture, QuickDraw GX will make a copy of
the shape and add the copy, rather than just adding a reference to the shape. These calls create our
picture and set the shape attribute:
gxShape gthePage
gthePage = GXNewShape(gxPictureType);
GXSetShapeAttributes(gthePage, gxUniqueItemsShape);
The variable gthePage now holds an empty picture, ready to have shapes added to it.
EXAMPLE 1: A SHAPE CONTAINING TEXT
First we'll create a shape containing text, which we'll store in the variable tempTextShape. We want
the text to read "GX." We set the text shape's position, create the new shape, set the text size, and set
the font to New York:
gxPoint textPosition = {ff(10), ff(205)};
gxShape tempTextShape;
tempTextShape = GXNewText (2, (unsigned char *) "GX", &textPosition);
GXSetShapeTextSize(tempTextShape, ff(250));
// This next call comes from the Font Library.
SetShapeCommonFont(tempTextShape, newyorkFont);
The variable tempTextShape now holds the text shape.
Outlining the text. We said that we want to outline the text with some color and fill the text shape with
stars. The approach we'll take to outlining our text shape is to first convert it into a path shape,
which requires only the GXSetShapeType call.
After the conversion to a path shape, each character in the text shape becomes a path. Thus, the
converted shape will contain two different paths, one for each character. To draw the outline of each
path, we set the fill type to gxClosedFrameFill. Then we set the pen to draw on the outside of each
contour, set the pen thickness, set the color of our path to a color from the common color library,
and scale it 125% on the x-axis and 65% on the y-axis so that it will come out looking short and fat.
GXSetShapeType(tempTextShape, gxPathType);
GXSetShapeFill(tempTextShape, gxClosedFrameFill);
GXSetShapeStyleAttributes(tempTextShape, gxOutsideFrameStyle);
GXSetShapePen(tempTextShape, ff(3));
SetShapeCommonColor(tempTextShape, blue);
GXScaleShape(tempTextShape, fl(1.25), fl(0.65), 0, 0);
Now we add our path shape to the picture we've stored in gthePage:
GXSetPictureParts(gthePage, 0, 0, 1, &tempTextShape, nil, nil, nil);
From now on, whenever we draw gthePage, our path shape stored in tempTextShape will be drawn
as well. (See Figure 4.)
Figure 4 The Path Shape Outlining Our Text
Filling with stars. To fill our shape with stars, we start by changing the fill type and color of our path.
Then we define the star shape and the pattern record that will replace the style of our shape, add our
new patterned path to our picture, and dispose of all the unneeded shapes.
We need to change the fill type of tempTextShape to solid fill because at this point we want to fill
the contents rather than draw the outline of each path. We also want our stars to be gray, so we need
to reset the color.
GXSetShapeFill(tempTextShape, gxSolidFill);
SetShapeCommonColor(tempTextShape, cold_grey);
We define the star as a polygon shape containing one contour and five points, with the default fill of
evenOdd (which results in a star with a hollow pentagon inside), and we scale it by 15% to make it
tiny:
gxShape starShape;
long starGeometry[] = {1, // One contour.
5, // Five points defining the polygon.
ff(60), 0, ff(90), ff(90), 0, ff(30),
ff(120), ff(30), 0, ff(90)};
starShape = GXNewPolygons((gxPolygons *) starGeometry);
GXScaleShape(starShape, fl(0.15), fl(0.15), 0, 0);
The pattern record contains the shape to be used as the pattern, two vectors (u and v) that describe
how to tile the pattern, and the pattern attribute. (See Figure 5.) We'll attach this record to the style
of our path shape, replacing the default style.
Figure 5 Making the Star Pattern
The bounding box of our star shape will be a fixed-point rectangle contained in the variable
starShapeBounds. This information is used to define the u and v vectors of our pattern record.
gxRectangle starShapeBounds;
GXGetShapeBounds(starShape, 0L, &starShapeBounds);
We define u and v to place the stars side by side without overlapping:
gxPatternRecord starPattern;
starPattern.u.x = 0;
starPattern.u.y = starShapeBounds.bottom;
starPattern.v.x = starShapeBounds.right + fix1;
starPattern.v.y = 0;
We set the attributes of the pattern record to gxNoAttributes and then add our star polygon shape to
the pattern record:
starPattern.attributes = gxNoAttributes;
starPattern.pattern = starShape;
Finally, we add the starPattern to tempTextShape. QuickDraw GX copies a new style to the
converted tempTextShape with the pattern record, replacing the default style currently referenced by
tempTextShape. Anytime this shape is drawn, it will be drawn patterned with stars. We add our
updated tempTextShape to the picture stored in the variable gthePage:
GXSetShapePattern(tempTextShape, &starPattern);
GXSetPictureParts(gthePage, 0, 0, 1, &tempTextShape, nil, nil, nil);
Now we can dispose of our star polygon shape because it's contained in our pattern record, which has
been encapsulated into the style of our shape. We also dispose of tempTextShape because there's
now a unique reference to it in our picture.
GXDisposeShape(starShape);
GXDisposeShape(tempTextShape);
The results. Our picture, gthePage, now contains two shapes. The first shape is a colored path that
outlines the text "GX." The second shape is the same path filled with gray stars. When we call
GXDrawShape(gthePage), all the shapes contained in our picture are drawn. The results are shown
in Figure 6.
Figure 6 Our Text Outlined With Color and Filled With Stars
EXAMPLE 2: A SHAPE CONTAINING A LINE LAYOUT
In a text shape, all the text can be rendered in only one font and size. In a layout shape, on the other
hand, the text can be rendered in a combination of different fonts and sizes by applying multiple style
runs. A layout shape can contain as many style runs as you like. A style controls the font, the size, the
script (such as Roman or Arabic), run features (which pertain to swashes, ligatures, and final forms
available with the specified font), justification overrides, and kerning adjustments.
To illustrate QuickDraw GX's line layout features, we'll create a layout shape containing the text
"Catch the Nasty WAVE, Dude." We'll start by creating the styles to be applied in three different
style runs, and then we'll define the layout shape. Finally, we'll make the whole line of text slant
backward, to demonstrate that you can perform any graphical operation on typographic shapes in
QuickDraw GX.
Creating the styles. We're going to apply styles to our layout shape in three different runs. The first
run of text, "Catch the Nasty," will use the Hoefler Italic font; the second run, "WAVE," will use the
Times Roman font; and the third run, "Dude," will use Hoefler Italic again. In the first and third
runs, we'll enable a total of three of the run features available with Hoefler Italic. But before we
create the styles, we need to initialize the run controls used to regulate the run features. We specify
the number of run features and styles we'll use.
gxRunControls runControls;
gxRunFeature runFeatures[3];
gxStyle styles[3];
InitializeRunControls(&runControls);
Here's how we specify the style used for our first text run:
styles[0] = NewLayoutStyle((char *)"\pHoefler Italic", ff(36), 0,
&runControls, nil, 0, nil);
We want to enable two run features in our first text run: an "as" ligature in "Nasty" and swashes on
the "C" and "N."
runFeatures[0].featureType = gxLigatureType;
runFeatures[0].featureSelector = gxLigatureRareOnSelector;
runFeatures[1].featureType = gxAlternateDesignsType;
runFeatures[1].featureSelector =
gxAlternateDesignsChanceryOnSelector;
Now we add the two run features to the style used by this run of text:
GXSetStyleRunFeatures(styles[0], 2, runFeatures);
We create the styles for the second and third text runs:
styles[1] = NewLayoutStyle((char *)"\pTimes Roman", ff(38), 0,
&runControls, nil, 0, nil);
styles[2] = NewLayoutStyle((char *)"\pHoefler Italic", ff(40), 0,
&runControls, nil, 0, nil);
The run feature used by the last run of text will enable the final form of the "e" in "Dude" and will
update the style:
runFeatures[2].featureType = gxSmartSwashType;
runFeatures[2].featureSelector = gxSmartSwashLineFinalsOnSelector;
GXSetStyleRunFeatures(styles[2], 3, runFeatures);
We'll also get the swash on "D" in "Dude" because we enabled the swash capabilities in the second
run feature. When we call GXSetStyleRunFeatures, we tell QuickDraw GX line layout to use all
three entries in the array.
Defining the layout shape. We need to define the length of our layout (in bytes) and initialize the
lengths array used to tell QuickDraw GX line layout the length of each run of text:
short totalLengthOfLayout, lengthsArray[3];
lengthsArray[0] = 15; lengthsArray[1] = 6; lengthsArray[2] = 5;
totalLengthOfLayout = 26;
We now have all the information required to define our layout shape and add it to our picture stored
in the variable gthePage. Our layout shape will contain three runs of text using three different styles.
Each style will use a different text size. Two different fonts will be used, and three run features will
be enabled. The definition looks like this:
char *sampleText = "Catch the Nasty WAVE, Dude";
gxPoint layoutPosition = {ff(10), ff(65)};
tempLayoutShape = GXNewLayout(1, &totalLengthOfLayout,
(void *) &sampleText, 3, lengthsArray, styles, 0, nil, nil,
nil, &layoutPosition);
GXSetPictureParts(gthePage, 0, 0, 1, &tempLayoutShape, nil,
nil, nil);
We draw tempLayoutShape by calling GXDrawShape(tempLayoutShape). The result is shown in
Figure 7. Note that "WAVE" has been kerned automatically.
Figure 7 Our Line Layout Shape
Skewing the shape and cleaning up. Because QuickDraw GX treats all typographic shapes as graphics,
we can perform any graphical operation on our layout shape, now stored in tempLayoutShape. We'll
skew it by 125% in the x direction about its origin, move the shape 15 pixels in the x direction to put
it back into the window after the skew, and move it 75 pixels in the y direction to move it away from
the beginning position. Finally, we'll add the shape to our picture stored in the variable gthePage.
When drawn, it will look like Figure 8.
GXSkewShape(tempLayoutShape, fl(1.25), 0, shapePosition.x,
shapePosition.y);
GXMoveShape(tempLayoutShape, ff(15), ff(75));
GXSetPictureParts(gthePage, 0, 0, 1, &tempLayoutShape, nil, nil,
nil);
Figure 8 Our Line Layout Shape, Skewed
We no longer need tempLayoutShape because it's now part of our picture, so we can dispose of
tempLayoutShape and our array of styles:
short loop;
GXDisposeShape(tempLayoutShape);
for (loop = 0; loop <= 2; loop++)
GXDisposeStyle(styles[loop]);
Now gthePage contains three shapes. The first shape is a colored path that outlines the text "GX,"
the second shape is the same path filled with stars, and the third shape is a typographically cool and
skewed rendition of "Catch the Nasty WAVE, Dude." When we call GXDrawShape(gthePage), all
these shapes are drawn. In the QuickDraw GX shell, gthePage is set up in the
CreateThePageOfGXShapes function.
BASIC PRINTING IN QUICKDRAW GX
QuickDraw GX adds quite a few new printing features, as mentioned earlier in this article. We'll
briefly explore printing here by discussing methods for printing shapes, how to initialize QuickDraw
GX printing, how to handle printing errors, how to implement three new print items added to the
File menu, and how to finish up printing.
METHODS FOR PRINTING SHAPES
Three approaches to printing shapes are available to your application: you can send shapes to
QuickDraw GX printing one by one, you can send a picture that contains all the shapes used to
represent a page, or you can send a picture that contains other pictures.
Depending on your application, sending an entire picture full of information may be simplest, but if
the picture contains quite a few shapes, say 20,000 to 25,000, you might suffer a performance penalty.
On the other hand, sending shapes one by one is more complicated for some applications, but it may
be the most efficient in terms of performance when you have lots of shapes -- more than 20,000. If
you have lots of shapes in one big picture but you don't want to send them one by one, you should
consider breaking the big picture into smaller pictures. (Recall that a QuickDraw GX picture is a
shape that can contain other pictures.) This approach would enable you to group similar items in the
same way as in MacDraw®.
You need to decide which method works best for your application. In the case of our shell, we'll send
a picture for each page of our document (which conveniently consists of only one page).
INITIALIZING QUICKDRAW GX PRINTING
To initialize QuickDraw GX printing, we call InitPrinting after we create the graphics client. We
then create a job, which is an extensible data structure containing all the information required to
print a document. For example, a job specifies the formatting printer driver, the printer for which the
job has been targeted, when the document should be printed, and the number of pages in the
document.
OSErr myQDGXPrintError;
gxJob gDocumentJob;
myQDGXPrintError = GXInitPrinting();
if (myQDGXPrintError == noErr)
myQDGXPrintError = GXNewJob(&gDocumentJob);
else
// Your error-handling code here!
HANDLING PRINTING ERRORS
QuickDraw GX printing handles printing errors differently from the Printing Manager. The
Printing Manager requires you to check for an error after each call. If you receive an error, you must
match all your open calls with the appropriate close calls, close up the Printing Manager, and report
the error to the user.
In QuickDraw GX printing, printing errors are local to the job. This gives you the ability to poll for
errors after a group of routines, making your code smaller and cleaner. If you do receive an error
within a group of routines, the routine that caused the error will prevent the remaining calls from
executing until the error is cleared by calling GXGetJobError. You can then alert the user to the
problem. All the QuickDraw GX printing errors are listed in PrintingErrors.h.
IMPLEMENTING THE NEW MENU ITEMS
Your application should implement three new items in the File menu: Print One Copy, Document
Setup, and By Page Setup.
- Print One Copy should print one copy of the document without presenting the
user with any dialog other than a status dialog.
- Document Setup should present a dialog that lets the user format the entire
document, similar to the old Page Setup dialog.
- By Page Setup should present a dialog that enables page-by-page formatting of a
document (for instance, the user should be able to choose a different page size for
each page in the document).
We won't take the space to reprint the code to implement Document Setup or By Page Setup here;
check it out in our QuickDraw GX shell.
When the user chooses Print One Copy from the File menu, we want to print the application's
document. In our QuickDraw GX shell, that means printing the contents of gthePage:
OSErr DoPrintOneCopy(WindowPtr window)
{
Str255 windowTitle;
OSErr printError;
if (window)
{
GetWTitle(window, windowTitle);
// Start sending the job. The job has the same name as our
// window, and it contains one page. The name appears in the
// status dialog.
GXStartJob(gDocumentJob, windowTitle, 1);
// Send the entire page down to the printer. (All the shapes
// that are being printed have been collected into gthePage.)
GXPrintPage(gDocumentJob, 1, GXGetJobFormat(gDocumentJob, 1),
gthePage);
// This call tells QuickDraw GX printing we've finished
// sending the job, so terminate the transmission (that is,
// the connection to the printer).
GXFinishJob(gDocumentJob);
if (GXGetJobError(gDocumentJob) != noErr)
// Your error-handling code here!
}
}
FINISHING UP
Once we've finished printing, we leave the printing system open. This isn't a problem, because the
QuickDraw GX printing system has a very small memory requirement when not in use --
approximately 35K. The main reason we leave it open is that we want to keep the job around, ready
to be used the next time the user prints. If we were to close the printing system, we would need to
recreate the job.
CLOSING UP THE QUICKDRAW GX WORLD
Closing up the QuickDraw GX world is as straightforward as initializing it. You should dispose of all
the QuickDraw GX objects you created while your application was running. For example, if you
called the GXNewShape routine to create a shape, you should call the GXDisposeShape routine to
dispose of it.
In our QuickDraw GX shell's shutdown, we need to dispose of the QuickDraw GX picture we
created to contain all the shapes drawn to our window, the common color library, our window, and
the graphics client. To dispose of the picture we created,
we first test to make sure it contains something. If it does, we call GXDisposeShape on it:
if (gthePage != nil) GXDisposeShape(gthePage);
The QuickDraw GX shell has also been using the common color library, which we dispose of by
calling
DisposeCommonColors();
Now we can dispose of the window; this also disposes of the view ports. In our shell, we created our
own pointer to the window record, so we need to dispose of it with the appropriate call.
To close up the QuickDraw GX printing system, we dispose of the job and then call GXExitPrinting
before we dispose of the graphics client. If we were going to save our picture to disk, we would want
to flatten the job to disk as well.
if (GXDisposeJob(gDocumentJob))
// Your error-handling code here!
if (GXExitPrinting())
// Your error-handling code here!
Finally, we need to call GXDisposeGraphicsClient to dispose of the graphics client we created
earlier:
GXDisposeGraphicsClient(gGraphicsClient);
If for some reason you haven't yet disposed of all the QuickDraw GX objects you've created while
your application has been running, GXDisposeGraphicsClient disposes of them. If you enabled
graphics notices earlier in your application, you'll receive a graphics notice for the first QuickDraw
GX object you didn't dispose of. For example, if you didn't dispose of a shape, you'll receive this
notice:
GRAPHICS NOTICE: shape not disposed
This isn't a problem when you're shutting down your application, because the QuickDraw GX heap
is cleaned up when GXDisposeGraphicsClient is called. But getting a notice like this when your
application ends means that you've been leaving unused items in the QuickDraw GX heap. That
could mean reduced performance, since the QuickDraw GX Memory Manager has to page these
unused objects into and out of the heap in a low-memory situation.
If you do receive a graphics notice about something you didn't dispose of, you should track down the
object to improve your runtime memory management. Prevent your application from calling
GXDisposeGraphicsClient so that the QuickDraw GX heap doesn't disappear before you can analyze
it. In GraphicsBug, use the Heap menu to choose your application's heap; then type "hd" to get a
dump of your QuickDraw GX heap, or "hd shape" to get a list of all the shapes you forgot to throw
away.
In our QuickDraw GX shell, the routine ShutDownProgram closes up the QuickDraw GX world.
WHERE TO GO FROM HERE
We've taken a look at what you need to do to begin making use of the new QuickDraw GX
technology. Now you're ready to tackle that huge pile of QuickDraw GX material.
I suggest starting with the sample code that comes with QuickDraw GX. There are sample
applications ranging from very simple to relatively complex, and some tools to play with. Try out the
samples that interest you. For an illustration of the minimum number of lines of code needed to draw
GX shapes, take a look at the "One Rectangle" sample. It initializes the QuickDraw GX world,
attaches a view port to a window, creates a rectangle, and draws the shape to the window. Then take a look at the QuickDraw GX documentation. You should start with the "QuickDraw GX
Objects" chapter, which describes all the objects illustrated in Figure 2. From there, proceed to the
parts of the documentation that make the most sense for your application.
I hope you've enjoyed this overview of QuickDraw GX. We haven't covered all the exciting parts of
the technology, by any means. This is only the beginning!
MANAGING MEMORY IN THE QUICKDRAW GX HEAP
The graphics and layout portions of QuickDraw GX use their own heap format and their own relocating
Memory Manager to improve efficiency. Why is this necessary? The GX system is object based, so it needs
the ability to quickly move graphics and layout objects from the heap onto disk when they aren't needed and
additional memory is required, and to move these objects back into memory when they're needed. This isn't
possible with the current Memory Manager.
Your application has quite a bit of control over the objects it creates in the QuickDraw GX heap. The
QuickDraw GX API provides calls to unload objects from the heap, and shape attributes that indicate when
a shape should be paged out of the heap to the backing store on disk. If you don't unload the objects or set
any of the shape attributes yourself, QuickDraw GX unloads the oldest objects first.
You can use a GXUnload call to tell QuickDraw GX to page a graphics or layout object -- specifically, a
shape, style, transform, ink, color profile, color set, or tag -- from the heap to the backing store on disk
when the next memory management call occurs. For example, to unload a particular shape from the heap,
you would use the following call:
GXUnloadShape(gxShape target);
QuickDraw GX maintains a reference to the objects, so it can read them from disk back into the heap if your
application needs them. It does this automatically, but if you want to improve the performance of your
application, you can explicitly load graphics and layout objects with a GXLoad call. For example, to load a
particular shape into the heap, you would use the following call:
GXLoadShape(gxShape target);
There are two shape attributes that you can set to indicate when to page a shape out to disk when memory
is needed:
- When gxDiskShape is set, this shape will be the first shape to be paged out of memory to disk when
memory is needed.
- When gxMemoryShape is set, this shape will be the last to be paged out to disk.
It's generally a good idea to dispose of an object as soon as you've finished with it. If you leave a bunch of
unused objects lying around in the heap, QuickDraw GX will maintain them and page them out to disk when
memory becomes tight. This wasted operation will cause your application to take a performance hit.
If you have a collection of shapes, say a picture, that you won't need for a while, you should consider
flattening them to disk and then disposing of them. When you flatten a shape to disk, each object is
compressed and sent to the file as a stream of data. When you dispose of the objects you just flattened, you
free up some heap space.
TRACKING DOWN A MEMORY TRASHING PROBLEM
Validation is a good way to track down a memory trashing problem. If the QuickDraw GX heap gets
trashed, you need to know if it's your application that's doing the damage or if it's QuickDraw GX. These
validation levels can help you find this out:
gxNoMemoryManagerValidation = 0x0000
gxApBlockValidation = 0x0100
gxFontBlockValidation = 0x0200
gxApHeapValidation = 0x0400
gxFontHeapValidation = 0x0800
gxCheckApHeapValidation = 0x1000
gxCheckFontHeapValidation = 0x2000
To see if the problem is caused by your application, add gxApHeapValidation or gxFontHeapValidation to
the types of validation you set. The one you choose depends on whether you believe the problem lies in a
general API call or within the font heap (the heap separate from the QuickDraw GX heap where all the font
information is cached). This type of validation checks the public API calls when they make a memory request.
If your application doesn't trash memory after adding this validation, it means that the problem is probably
caused by an internal call within QuickDraw GX. In that case, add the validation level
gxCheckApHeapValidation or gxCheckFontHeapValidation. This additional validation checks the memory
allocation for the internal core calls. If you die at this point, you know that the internal call is causing the
problem.
PETE ("LUKE") ALEXANDER started out as a meteorology major in college and to this day is great at forecasting the
weather. He'd be happy to see nothing but blue skies, and in all kinds of weather he strives to incorporate something blue
in his attire. He loves raw carrots, despises cooked ones. He hates waiting in lines and wearing a wristwatch. Sometimes
when he's driving down the street and spots someone sitting at a bus stop, he'll honk his horn to wake them from their
dazed stupor. And when he sees bicyclists sitting in their bike seats while riding up hills, he's been known to yell out the
window, "Stand up, you weenie!"*
If you're a PostScript language whiz making the transition to QuickDraw GX, you'll find the article "QuickDraw GX for
PostScript Programmers" in this issue helpful.*
Shapes are completely resolution independent, which enables accurate representation at any size to the screen or printer. *
The By Page Setup and Document Setup dialogs are new in QuickDraw GX. They're described later in this article under
"Basic Printing in QuickDraw GX."*
For a complete list of all the errors, warnings, and notices provided by the graphics and layout portions of QuickDraw GX,
take a look at the graphics errors.h header file. *
Routine naming in QuickDraw GX is very predictable and logical. All calls preceded by "GX" are from the core API, while
ones without "GX" are from the library or application code. In addition, all calls pertaining to the same object are very
similar, and once you grasp how to operate on one object, you pretty much know how to operate on all objects. *
If you're looking for a shortcut, you can skip the InitCommonColors call, because your first call to SetShapeCommonColor
will initialize the library. *
The number of view ports that can be attached to another view port is limited only by the amount of memory you make
available in the QuickDraw GX heap. *
QuickDraw GX uses fixed-point numbers, offering the advantage of speed, instead of the floating-point numbers used by
QuickDraw. The ff macro uses IntToFixed to convert an integer into a fixed-point number; another handy macro, fl,
converts a floating-point number into a fixed-point number.*
Another approach to outlining text, described in the article "QuickDraw GX for PostScript Programmers" in this issue,
involves using text faces. *
THANKS TO OUR TECHNICAL REVIEWERS Hugo Ayala, Cary Clark, Tom Dowdy, Dave Hersey, Daniel Lipton, Dave
Opstad*