CHAPTER 5: INTRODUCING THE VDI ------------------------------- [NOTE: Words enclosed in asterisks (i.e. *word*) should be read as italicized text. This text file is copyright 1993 by Clayton Walnum. All rights reserved.] GEM (Graphics Environment Manager) is made up of many libraries of functions, each of which handles certain portions of the system's activities. These libraries are grouped into two major units, called the AES (Applications Environment Services) and the VDI (Virtual Device Interface). The AES, which we've been using since the beginning of the book, contains the functions we need to handle windows, dialog boxes, menu bars, and event processing. The VDI controls most of the ST's graphic capabilities and provides some mouse and cursor control functions, as well. The VDI plays an important role in making your graphics programs operate on many different devices. Unfortunately, one of the crucial elements in the graphics interface, GDOS (Graphics Device Operating System), is not built into the current operating system. GDOS is the portion of the VDI that links the graphics functions to the drivers needed to assure that the graphics operate properly on all graphics devices. GDOS also makes it possible to load different fonts into your ST, using the standard VDI functions. At this time, however, we're concerned only with one device: the screen. --The VDI functions-- The VDI provides a series of functions that let us quickly draw many graphic shapes. This simplifies the development of programs that rely heavily on graphics. If you programmed an 8-bit Atari (or still do), think of all the work involved in drawing a circle. The VDI provides a function that will draw any size circle we want--with a single call. There are also functions for drawing ellipses, lines, rectangles, rounded rectangles, arcs, pie slices and a number of other useful graphics. And it doesn't stop there. Each graphic function has a group of related attributes that may be set before the graphic is drawn, allowing various types of lines, fills and colors. --The Program-- This chapter's sample program shows how to set up your program to use the VDI. It also includes a demonstration of some of the VDI's graphics capabilities. Specifically, when the program is run, you will see the different types of line styles available through the VDI. To exit the program, press your Return key. --The VDI Arrays-- Calling VDI functions is a lot like calling AES functions. In both cases, you must place appropriate values into control and input arrays, and then call the function by using a *trap #2* instruction. Look at the data section of this chapter's sample program. There, you'll find the usual AES parameter block (*apb*). Right below that, you'll see something called *vpb*. Yep. That's a VDI parameter block. Although these parameter blocks work the same way--that is, they both tell the OS where to find the arrays it needs-- they contain different values. The VDI, for example, doesn't use the *addr_in* or *addr_out* arrays. Let's look at the *vpb* in detail. The first value is the address of a control array. In our *apb* we have a 0 in this location because we found it easier to have different control arrays for each function, and then plug the address of the appropriate array into the *apb* when we needed it. But because the values in the VDI control array vary from one function call to another, it's clumsy to try to set them up ahead of time. The *int_in* and *int_out* arrays are the same as those in the *apb*. We can use the same arrays for either an AES or VDI call. But the VDI *apb* has something a little different, arrays called *pts_in* and *pts_out*. These arrays usually contain coordinates for the VDI's drawing functions. For example, the starting and ending screen coordinates for a line would go into the *pts_in* array before calling the function. --Initializing the VDI-- When we use the VDI in our programs, we must set up something called a virtual workstation. To do this, we do the following: 1) Get a handle to the physical screen. 2) Open a virtual workstation. 3) Clear the virtual workstation. Look at this chapter's program listing. After the usual program initialization, which concludes with the call to *appl_init*, we begin the VDI initialization. The first step is performed by calling the AES function #77, *graf_handle*, like this: move.l #graf_handle,apb jsr aes move int_out,gr_handle This returns the handle for the currently open device (the screen) or workstation, as well as the size of the system font. Because GEM is capable of having many programs in memory at once, each requires some identification, to keep commands for one program from corrupting another. This is accomplished by assigning each program a handle. The variable *gr_handle* in the above call is an integer value that identifies the current workstation. After the call, this value is found in the first element of the *int_out* array. The *graf_handle* call also returns some information about the system font. The width of a character cell will be in *int_out+2*, the cell height will be in *int_out+4*, the width of a box that can enclose a character will be in *int_out+6*, and the height of the box will be in *int_out+8*. We won't be using any of this character information now, but you should know where to find it when you need it. --The Virtual Workstation-- The *graf_handle* call returns the handle to the physical workstation. What we really need for our program is a handle to a virtual workstation. It's kind of tough to explain the difference, but I'll give it a shot. A particular device may have many virtual workstations, but only one physical workstation. The physical workstation is directly associated with the device itself, usually the screen. You can think of a virtual workstation as a "pretend" device. It has its own section of memory and keeps its data and status completely separate from all other virtual workstations. When you activate an application (such as clicking on a desk accessory), it becomes bound to the physical workstation. In a sense, it becomes the physical workstation. We get the handle for our virtual workstation with a call to the VDI function #100, *v_opnvwk*, like this: move #100,contrl clr contrl+2 move #11,contrl+6 move gr_handle,contrl+12 movea.l #int_in,a5 move #9,d5 loop: move #1,(a5)+ dbra d5,loop move #2,int_in+20 jsr vdi move contrl+12,vws_handle This function expects the system attributes to be in the *int_in* array. (In this example, we're loading the values into *int_in* using a loop.) These attributes determine default values for system attributes such as default colors, fill patterns, and line styles. (For those of you familiar with C, the *int_in* array in this call serves the same function as C's *work_in* array.) In the above example, we place a 1 in elements 1 through 10 of the *int_in* array, and a 2 in the eleventh element of the *int_in* array. This is the standard way to initialize these attributes. You should note the manner in which the control array is used, since all VDI calls require similar parameters in this array. The first element of the control array must contain the opcode for the VDI function we wish to call. *Contrl+2* must contain the number of point pairs that will be found in the *pts_in* array, in this case, 0. *Contrl+6* must contain the number of integer values that are in the *int_in* array. In this function call, *int_in* will contain 11 values, so we place an 11 in *contrl+6*. Finally, *contrl+12* must contain the device handle. In most VDI calls, this value will be the handle to our virtual workstation. However, because we don't yet have that handle, we use the graphics handle that was returned by the call to *graf_handle*. After the call to *v_opnvwk*, our *int_out* and *pts_out* arrays will contain much information about the virtual device's current attributes, as follows: control+4 -- 6 = # of point pairs in pts_out control+8 -- 45 = # of integers in int_out control+12 -- Device handle (0 if no device opened) int_out, work_out[0] -- Maximum horizontal screen coord. int_out+2, work_out[1] -- Maximum vertical screen coord. int_out+4, work_out[2] -- Device coordinate units flag: 0 = Device can display a precisely scaled image. 1 = Device cannot display a precisely scaled image. int_out+6, work_out[3] -- Width of a pixel in microns. int_out+8, work_out[4] -- Height of a pixel in microns. int_out+10, work_out[5] -- Number of font heights: 0 = continuous scaling. int_out+12, work_out[6] -- Number of line types. int_out+14, work_out[7] -- Number of line widths: 0 = Continuous scaling. int_out+16, work_out[8] -- Number of marker styles. int_out+18, work_out[9] -- Number of marker sizes: 0 = Continuous scaling. int_out+20, work_out[10] -- Number of supported text fonts. int_out+22, work_out[11] -- Number of pattern styles. int_out+24, work_out[12] -- Number of hatch styles. int_out+26, work_out[13] -- Number of available colors. int_out+28, work_out[14] -- Number of generalized drawing primitives. int_out+30 to int_out+48 work_out[15] to work_out[24] -- Code numbers for available generalized drawing primitives: 1 = filled bar. 2 = arc. 3 = pie slice. 4 = filled circle. 5 = filled ellipse. 6 = elliptical arc. 7 = elliptical pie slice. 8 = rounded rectangle. 9 = filled rounded rectangle. 10 = justified graphics text. -1 = end of GDP list. int_out+50 to int_out+68 work_out[25] to work_out[34] -- Code numbers for graphics operations available to the GDPs: 0 = lines. 1 = markers. 2 = graphics text. 3 = fills. 4 = none. int_out+70, work_out[35] -- Color flag: 0 = no colors available on device. 1 = colors available on device. int_out+72, work_out[36] -- Text rotation flag: 0 = device cannot rotate text. 1 = device can rotate text. int_out+74, work_out[37] -- Fill flag: 0 = device cannot fill areas. 1 = device can fill areas. int_out+76, work_out[38] -- Cell array flag: 0 = device not capable of cell array operation. 1 = device capable of cell array operation. int_out+78, work_out[39] -- Number of available colors: 0 = more than 32,767. 1 = Black and white 2 or more = Number of colors available. int_out+80, work_out[40] -- Locator input devices: 1 = keyboard. 2 = keyboard and mouse (or other device) int_out+82, work_out[41] -- Valuator input devices: 1 = keyboard. 2 = other device. int_out+84, work_out[42] -- Choice devices: 1 = function keys. 2 = other keypad. int_out+86, work_out[43] -- Text input devices: 1 = keyboard. int_out+88, work_out[44] -- Type of workstation: 0 = output. 1 = input. 2 = input and output. 3 = reserved 4 = metafile output. pts_out, work_out[45] -- Minimum character width. pts_out+2, work_out[46] -- Minimum character height. pts_out+4, work_out[47] -- Maximum character width. pts_out+6, work_out[48] -- Maximum character height. pts_out+8, work_out[49] -- Minimum line width. pts_out+10, work_out[50] -- 0. pts_out+12, work_out[51] -- Maximum line width. pts_out+14, work_out[52] -- 0. pts_out+16, work_out[53] -- Minimum marker width. pts_out+18, work_out[54] -- Minimum marker height. pts_out+20, work_out[55] -- Maximum marker width. pts_out+22, work_out[56] -- Maximum marker height. Don't let all this information intimidate you. Right now, we're only concerned with the three values returned in *contrl+4*, *contrl+8*, and *contrl+12*. *Contrl+12* will contain the handle to our virtual workstation, *contrl+4* will contain the number of points (two values each) returned in *pts_out*, and *contrl+8* will contain the number of integers returned in *int_out*. For this function call, *contrl+4* should contain a 6 after the call, which mean six pairs of integers have been returned in *pts_out*, and *contrl+8* should contain a 45, the number of integers returned in *int_out*. For every VDI call, *contrl+4* and *contrl+8* are used the same way. You can always check them to see how many values have been returned by a VDI function. Finally, in the above example, notice that we are calling a subroutine called *vdi* to perform a VDI function. The *vdi* subroutine works similarly to the *aes* subroutine, setting up the VDI function and then doing a *trap #2*. --Clearing the Workstation-- The last step in getting the VDI initialized is to clear the workstation. Clearing a workstation ensures that all the devices have been flushed and the screen has been erased. In other words, it presents us with a blank slate upon which our program can begin to operate. To clear a workstation, we call VDI function #3, *v_clrwk*, like this: move #3,contrl clr contrl+2 clr contrl+6 move vws_handle,contrl+12 jsr vdi As you can see, this function has no special parameters, nor does it return any values. It simply does its business and returns control to the program. Notice again, how we're using the control array. Because we have no values in the *pts_in* and *int_in* arrays, we set both *contrl+2* and *contrl+6* to 0. When calling a VDI function, you also must be sure that the virtual workstation handle is in *contrl+12*. In the above example, we show the workstation handle being placed in *contrl+12*. However, in the sample program, this instruction is missing. Why? Because the handle is already in *contrl+12*, where it was placed by the call to *v_opnvwk*. The handle will stay in *contrl+12*, right where we want it, until we overwrite it. --Polylines-- Now that we've got our workstation set up, we can get down to business. The first graphic we'll experiment with is the polyline. Those of you who are up on your linguistics know that the prefix "poly" means "many." A polyline is one or more lines connected from point to point, which allows the programmer to draw complex shapes with a single function call. But before we draw our polylines, we need to set some line attributes, like color, width, and end style. Any lines drawn after we've set the attributes will use those attributes. Also, the line attributes stay in effect until we change them again. To set a line's color, we use a call to VDI function #17, *vsl_color*, like this: move #17,contrl clr contrl+2 move #1,contrl+6 move vws_handle,contrl+12 move color,int_in jsr vdi Here, *color* is an integer from 0 to the device maximum (low resolution=15, medium resolution=3, and high resolution=1). If we use a number higher than the maximum, the function will default to color 1. On the ST, the default color palette, starting with 0 and ending with 15, is white, black, red, green, blue, cyan, yellow, magenta, white, black, light red, light green, light blue, light cyan, light yellow and light magenta. The actual color value set is returned in *int_out*. When we're drawing lines, we can also choose an end style with a call to VDI function #108, *vsl_ends*: move #108,contrl clr contrl+2 move #2,contrl+6 move vws_handle,contrl+12 move ends,int_in move ends,int_in+2 jsr vdi In this case, *ends* is an integer value from 0 to 2. A value of 0 will yield a square end, 1 will get us an arrow, and 2 will result in a rounded end. Here we're using *ends* as both the beginning and ending line style, but each end of the line can have a different end style if we like. To do this, we'd put the first end style in *int_in* and the second end style in *int_in+2*. This function returns no values. Finally, we can set the thickness of our lines with a call to VDI function #16, *vsl_width*: move #16,contrl move #1,contrl+2 clr contrl+6 move vws_handle,contrl+12 move width,pts_in clr pts_in+2 jsr vdi The variable *width*, which is placed into the first element of *pts_in*, must be an odd positive integer. The line will be set to the closest width less than or equal to the value of *width*. Notice that, for some strange reason, *pts_in+2* must contain a 0. The actual width set is returned in the first element of *pts_out*. Once we have the attributes set, we can draw our first line. We do this with a call to VDI function #6, *v_pline*: move #6,contrl move #2,contrl+2 clr contrl+6 move vws_handle,contrl+12 move x1,pts_in move y1,pts_in+2 move x2,pts_in+4 move y2,pts_in+6 jsr vdi In this call, *x1* and *y1* are the X and Y coordinates of one end of the line. The integers *x2* and *y2* are, of course, the X and Y coordinates of the other end of the line. In the example program, we're using *v_pline* to draw only single lines. However, as we said before, this function can be used to draw many connected lines. For example, to extend the above example so that it draws two connected lines, we could do something like this: move #6,contrl move #3,contrl+2 clr contrl+6 move vws_handle,contrl+12 move x1,pts_in move y1,pts_in+2 move x2,pts_in+4 move y2,pts_in+6 move x3,pts_in+8 move y3,pts_in+10 jsr vdi Notice that we are now loading a 3 into *contrl+2*, because the *pts_in* array now contains three points. (Remember, a point is really two values.) We've also loaded the values *x3* and *y3* (the new point) into the next two elements of the *pts_in* array. These values are the next point to which the *v_pline* function should draw. We could continue drawing lines by adding more and more points to the *pts_in* array. We just must be sure that the value in *contrl+2* is the total number of points in the array, otherwise the function will ignore some of the points. If we're drawing a polyline at the smallest width, we can choose between six system line types with a call to VDI function #15, *vsl_type*: move #15,contrl clr contrl+2 move #1,contrl+6 move vws_handle,contrl+12 move type,int_in jsr vdi Here, *type* is an integer value from 1 to 7 as follows: 1 solid ________________ 2 long dash ___ ___ ___ ___ 3 dots ............... 4 dash dot __.__.__.__.__. 5 dash __ __ __ __ __ __ 6 dash dot dot __..__..__..__..__ 7 user defined Type 7 lets you set up your own line types, but we're not going to get into that now. This function returns, in *int_out*, the actual line type set. Now that we've covered the basics of setting up the VDI and calling graphics functions, let's take a quick look at some of the other graphics you can draw with the VDI. --Rectangles-- We can use *v_pline* to draw a standard square-cornered box, but the VDI also supplies a function that will let us draw rectangles with rounded corners. To do this, we call VDI function #11, sub-function #8, *v_rbox*: move #11,contrl move #2,contrl+2 clr contrl+6 move #8,contrl+10 move vws_handle,contrl+12 move x1,pts_in move y1,pts_in+2 move x2,pts_in+4 move y2,pts_in+6 jsr vdi Here, we must supply the pixel coordinates of the upper-left and lower-right corners. We place these values into the *pts_in* array. Also note that this function call requires that you place a sub-function number (8) into *contrl+10*. The line attributes--color, style and width-- are used with *v_rbox*, allowing a wide variety of rectangles. This function returns no values. You can also draw a filled rounded rectangle, using VDI function #11, sub-function #9, *v_rfbox*: move #11,contrl move #2,contrl+2 clr contrl+6 move #9,contrl+10 move vws_handle,contrl+12 move x1,pts_in move y1,pts_in+2 move x2,pts_in+4 move y2,pts_in+6 jsr vdi This function uses the currently active fill pattern, which we'll learn to set later on. To draw a filled rectangle with square corners, use VDI function #11, sub-function #1, *v_bar*: move #11,contrl move #2,contrl+2 move #0,contrl+6 move #1,contrl+10 move vws_handle,contrl+12 move x1,pts_in move y1,pts_in+2 move x2,pts_in+4 move y2,pts_in+6 jsr vdi As you may suspect, this function, too, uses the current fill pattern. --Circles, Ellipses, and Arcs-- GEM's VDI provides several functions for drawing various types of curved lines and shapes. If you'd like to draw a filled circle, just use VDI Function #11, sub- function #4, *v_circle*: move #11,contrl move #3,contrl+2 move #0,contrl+6 move #4,contrl+10 move vws_handle,contrl+12 move centerx,pts_in move centery,pts_in+2 move #0,pts_in+4 move #0,pts_in+6 move radius,pts_in+8 move #0,pts_in+10 jsr vdi Here, *centerx* and *centery* are the X and Y coordinates of the circle's center, and *radius* is the circle's radius. Note that *pts_in+4*, *pts_in+6*, and *pts_in+10* all contain dummy arguments of 0. The *v_circle* function, like *v_rfbox*, uses the current fill attributes. You can draw ellipses, too. An ellipse looks something like a squashed circle or a solid oval and is drawn by a call to VDI function #11, sub-function 5, *v_ellipse*: move #11,contrl move #2,contrl+2 move #0,contrl+6 move #5,contrl+10 move vws_handle,contrl+12 move x,pts_in move y,pts_in+2 move hrad,pts_in+4 move vrad,pts_in+6 jsr vdi Here, the parameters *x* and *y* denote the ellipse's center point, and *hrad* and *vrad* are the X and Y radiuses in pixels. Once again, the active fill attributes are used. No values are returned from the function. Arcs are curved lines and are easy to draw with VDI function #11, sub-function #2, *v_arc*: move #11,contrl move #4,contrl+2 move #2,contrl+6 move #2,contrl+10 move vws_handle,contrl+12 move angle1,int_in move angle2,int_in+2 move x,pts_in move y,pts_in+2 move #0,pts_in+4 move #0,pts_in+6 move #0,pts_in+8 move #0,pts_in+10 move rad,pts_in+12 move #0,pts_in+14 jsr vdi The parameters *x*, *y*, and *rad* are the X,Y- coordinates of the center and the radius, respectively. The integers *angle1* and *angle2* are the beginning and ending angles of the arc, in tenths of degrees (that is, values from 1 to 3600). Note that *pts_in+4*, *pts_in+6*, *pts_in+8*, *pts_in+10*, and *pts_in+14* all contain dummy arguments that must contain zeroes. No value is returned from the function. The final call in this family of functions allows you to draw pie slices, which help you to draw pie charts. To draw a pie slice, use a call to VDI function #11, sub- function #3, *v_pieslice*: move #11,contrl move #4,contrl+2 move #2,contrl+6 move #3,contrl+10 move vws_handle,contrl+12 move angle1,int_in move angle2,int_in+2 move x,pts_in move y,pts_in+2 move #0,pts_in+4 move #0,pts_in+6 move #0,pts_in+8 move #0,pts_in+10 move rad,pts_in+12 move #0,pts_in+14 jsr vdi The parameters here are the same as those for the arc function. However, the body of the pie slice will be colored by whatever fill pattern is active. This function returns no values. --Polymarkers-- Polymarkers are a number of predefined shapes you can use in your graphics. To draw them, you call VDI function #7, *v_pmarker*: move #7,contrl move #3,contrl+2 move #0,contrl+6 move vws_handle,contrl+12 move x1,pts_in move y1,pts_in+2 move x2,pts_in+4 move y2,pts_in+6 move x3,pts_in+8 move y3,pts_in+10 jsr vdi The parameter placed in *contrl+2* is the number of markers you want to draw. The X,Y coordinates for the markers are stored in the *pts_in* array. The above example shows three markers being drawn, but you can draw as many as you need. This function returns no values. What do these markers look like? You have a choice of six predefined shapes which (from 1 to 6, respectively) are dot, plus sign, asterisk, square, diagonal cross, and diamond. To set the polymarker type, call VDI function #18, *vsm_type*: move #18,contrl move #0,contrl+2 move #1,contrl+6 move vws_handle,contrl+12 move type,int_in jsr vdi Here, *type* is a value from 1 to 6. If you should choose a value out of this range, the function will select the asterisk as a default. The value chosen will be returned in *int_out*. There are two other attributes that affect polymarkers: color and height. Color is set with a call to VDI function #20, *vsm_color*: move #20,contrl move #0,contrl+2 move #1,contrl+6 move vws_handle,contrl+12 move color,int_in jsr vdi Here, *color* is a value from 0 to the device maximum. All the rules of the *vsl_color* call apply in this case. The actual color set is returned in *int_out*. You can change the size of all polymarkers, except the dot (which always appears in the smallest size), with a call to VDI function #19, *vsm_height*: move #19,contrl move #1,contrl+2 move #0,contrl+6 move vws_handle,contrl+12 move #0,pts_in move height,pts_in+2 jsr vdi The parameter *height* is the polymarker's size on the Y-axis. The actual height will be the greatest height available on the device, less than or equal to the height parameter. This will be returned in *pts_out+2*. Note that, in the above function call, *pts_in* is always 0, with the requested height being placed in *pts_in+2*. --Fill Patterns-- GEM supplies many patterns we can use to fill our figures. There's a series of functions to let us set these patterns up the way we want them. The first step is a call to VDI function #23, *vsf_interior*: move #23,contrl move #0,contrl+2 move #1,contrl+6 move vws_handle,contrl+12 move pattern,int_in jsr vdi Here, *pattern* is a value from 0 to 4. The values are interpreted as follows: 0 Hollow (background color) 1 Solid 2 Pattern 3 Hatch 4 User-defined The actual pattern set is returned in *int_out*. If you choose style 0 or 1, you need go no further, but style 2 allows you to choose between 24 different patterns, and style 3 provides 12 hatch styles. You choose the pattern you wish to use, with a call to VDI function #24, *vsf_style*: move #24,contrl move #0,contrl+2 move #1,contrl+6 move vws_handle,contrl+12 move fill,int_in jsr vdi Here, *fill* is a value from 0 to 23. Consult your reference manual to see what these styles look like. The fill that was actually set is returned in *int_out*. The color of the fill is selected with a call to VDI function #25, *vsf_color*: move #25,contrl move #0,contrl+2 move #1,contrl+6 move vws_handle,contrl+12 move color,int_in jsr vdi All the rules for the *vsl_color* function apply here, also, with the actual color that was set returned in *int_out*. Finally, you can choose between a visible or invisible border for your fill with a call to VDI function #104, *vsf_perimeter*: move #104,contrl move #0,contrl+2 move #1,contrl+6 move vws_handle,contrl+12 move flag,int_in jsr vdi Here, a *flag* value of 0 will give you an invisible border. Any other value will cause the border to be drawn in the current fill color. --Conclusion-- Now that you've been introduced to many of the graphics functions available to you through the VDI, study the sample program to see them in action, then take some time and experiment with the VDI on your own. See if you can write a program to draw a simple picture, or maybe a graph. --Summary-- * The VDI (Virtual Device Interface) controls most of the ST's graphic capabilities and provides some mouse and cursor control functions, as well. * You call VDI functions much like you call AES functions, by placing appropriate values into control and input arrays, and then calling the function by using a *trap #2* instruction. * To initialize the VDI, you must get a handle to the physical screen, open a virtual workstation, and finally clear the workstation. * The AES function #77, *graf_handle*, obtains a handle to the physical workstation and returns information about the system font. * The VDI function #100, *v_opnvwk*, obtains a handle to a virtual workstation. * The VDI function #3, *v_clrwk*, clears a workstation, flushing all devices and clearing the screen. * The VDI functions *vsl_color* (#17), *vsl_ends* (#108), *vsl_width* (#16), and *vsl_type* (#15) set various line attributes. * The VDI function #6, *v_pline*, draws polylines. * Other shapes can be drawn using the VDI functions *v_rbox* (#11,8), *v_rfbox* (#11,9), *v_bar* (#11,1), *v_circle* (#11,4), *v_ellipse* (#11,5), *v_arc* (#11,2), and *v_pieslice* (#11,3). * Polymarkers are drawn by VDI function #7, *v_pmarker*. * The polymarker attributes can be set by the VDI functions *vsm_type* (#18), *vsm_color* (#20), and *vsm_height* (#19). * The current fill pattern's attributes can be set by the VDI functions *vsf_interior* (#23), *vsf_style* (#24), *vsf_color* (#25), and *vsf_perimeter* (#104).