10 - VDI Graphics: Text Output
*PROFESSIONAL GEM*
by Tim Oren
Column #10 - VDI Graphics: Text Output
This issue of ST PRO GEM concludes the two column series on VDI with a look at simple VDI text output, and ways to optimize its speed. There is also a Feedback section. You may find the associated download file under the name GMCL10.C in DL3 of the ATARI16 SIG (PCS-58).
To keep the size of this first discussion of text within reason, I am going to restrict it to use of the mono-spaced system font in its default size and orientation. Discussion of alternate and proportionally spaced fonts, baseline rotation, and character scaling will become a later article in this series.
DEFINITIONS
This article makes use of some terminology which may be unfamiliar if you have not used digital typefaces. A mono-spaced font is one in which each character occupies an identically wide space on the screen. A proportional font has characters which occupy different widths. For instance, an 'l' would probably be narrower than a 'w'.
Text may be "justified" right, left, or center. This means that the right character, left character, or center position of the text string is constrained to a given location. In common usage, a page of text is "ragged right" if its lines are left justified only. The text page is "fully justified", "justified" or (ambiguously) "right justified" if BOTH the left and right characters are contrained to fixed columns. Full justification is produced by inserting extra blank characters in the case of a mono-spaced font, or by adding extra pixel columns in the case of proportional output.
A text character (in a monospaced font) is written inside a standard sized cell or box. Vertically, the cell extends from the "top line" down to the "bottom line". If there are one or more blank lines at the top or bottom, they are called "leading" and are used to separate lines of text. The characters themselves always fall between the "ascent line", which is the highest line reached by characters such as 'd' and 'l', and the "descent line", which is the lowest line in characters like 'q' and 'g'. Other locations of interest are the "half line", which is the top of characters like 'a' or 'n', and the "base line", which is the bottom of characters which do not have descenders.
Before plunging into the Attribute Functions for text, you should note that the writing mode (vswr_mode) and clipping rectangle (vs_clip) attributes discussed in the last column (#9) also pertain to text. Since much of the discussion of text optimization will center on these attributes, you may want to review them.
TEXT ATTRIBUTES
The writing color for graphics text is set with the command:
vst_color(vdi_handle, color);
Vdi_handle is always the handle returned from graf_handle() at application startup. Color is a word value between 0 and 15 which designates the output color index. As discussed in previous columns, the actual color which appears is dependent on the current palette settings. In applications such as word and outline processors it is important that characters and their background provide good contrast to avoid eyestrain. In these situations, you may want to use the setPalette and/or setColor XBIOS functions to force the palette to a known state before starting the application.
You can choose a variety of special output effects for your text with the call:
vst_effects(vdi_handle, effects);
Effects is a single flag word, with the bits having the following significance:
- 0 - Thicken
- 1 - Lighten
- 2 - Skew
- 3 - Underline
- 4 - Outline
- 5 - Shadow
In each case, turning the bit on selects the effect. Otherwise, the effect is off. Any number of multiple effects may be selected, but the result may not always be pleasing or legible.
The "thicken" effect widens the character strokes by one pixel, resulting in the appearance of boldface type. The "lighten" effect superimposes a half-tone dither on the character. This mode is useful for indicating non-selectable text items, but is not legible enough for other purposes.
The skew effect shifts the rows of the character the right, with the greatest displacement at the top. This results in the appearance of italic text. You should be aware that the VDI does not compensate for this effect. This means that a skewed italic character which is immediately followed by a normal blank will be overstruck, and part of the top of the character will disappear. Likewise, a skewed character written to the left of an existing normal character will overstrike part of it. There is a related bug in the VDI clipping logic which may cause some parts of a skewed character not to be redrawn if they fall at the edge of a clipping rectangle, even though they should fall within the region.
The outline effect produces output which is a one pixel "halo" around the normal character. The shadow effect attempts to create a "drop shadow" to the side of the character. These effects should be used very sparingly with default sized fonts. They often result in illegible output.
When graphics text is written, a screen coordinate must be specified for the output. The relationship of the text to the screen point is determined by the call:
vst_alignment(vdi_handle, hin, vin, &hout, &vout);
Hin and vin are each words, with values specifying the desired horizontal and vertical alignment, respectively. Hout and vout receive the actual values set by the VDI. If they differ from the requested values, an error has occurred.
Hin may be set to zero for left justification, one for center justification, or two for right justification. The coordinate given when text is written becomes the "anchor point" as described in the definitions above. The default justification is left.
Vin determines what reference line of the text is positioned at the output coordinate. The selection values are:
- 0 - baseline (default)
- 1 - half line
- 2 - ascent line
- 3 - bottom line
- 4 - descent line
- 5 - top line
A common combination of alignments is left (0) and top line (5). This mode guarantees that all text output will lie to the right and below the output coordinate. This corresponds with the AES object and GRECT coordinate systems.
Finally, the call to do the actual output is:
v_gtext(vdi_handle, x, y, string);
X and y define the screen coordinate to be used as the alignment point. String is a pointer to a null terminated string, which must be total eighty characters or less, exclusive of the null. This limit is imposed by the size of the intin[] array in the VDI binding. Be warned that it is NOT checked in the standard binding! Exceeding it may cause memory to be overwritten.
One Inquire Function is useful with text output. The call
vqt_attributes(vdi_handle, attrib);
reads back the current attribute settings into the 10 word array attrib[]. The main items of interest are attrib[6] through attrib[9], which contain the width and height of characters, and the width and height of the character cell in the current font. You should rely on this function to obtain size information, rather than using the output of the graf_handle() function. On the ST, graf_handle() always returns sizes for the monochrome mode system font, which will be incorrect in the color screen modes.
Attrib[1] will contain the current graphics text color as set by vst_color(). Attrib[3] and [4] contain the horizontal and vertical alignment settings, respectively. Attrib[5] contains the current writing mode, as set by vswr_mode().
OPTIMIZATION
The most common complaint about using bit maps for character output is lack of speed. This section suggests ways to speed things up. By adopting all of these methods, you can realize an improvement of two to three times in speed.
BYTE ALIGNMENT
Since writing graphic text is essentially a bit-blit operation, characters which have "fringes", that is, do not align evenly with byte boundaries, will suffer performance penalities. The default system fonts in all resolutions of the ST are a multiple of eight pixels wide, so the problem reduces to assuring that each characters starts at a byte boundary in the screen bit map. This will be true if the horizontal pixel address of the left edge of the character is evenly divisible by eight.
Obviously, byte alignment is easiest to enforce when the horizontal justification is right or left. Doing so with centered text is possible, but requires adding padding blanks to odd length strings.
When writing text within windows, it is helpful to assure that the edges of the window working area are byte aligned. There is a section of code in the download which shows a technique for converting a user requested window position and/or size to its working dimensions, byte-aligning the width and horizontal position, and computing the adjusted external window coordinates.
WRITING MODE
The fastest text output mode is replace. All other modes require reading in the target raster area and merging it with the new information. You may find that you must use transparent or reverse transparent mode, for instance, to use or preserve an underlying background color other than white. In this case, you can still do some optimization by filling in the background color for the entire string with a v_bar() call, rather than doing it one character cell at a time.
CLIPPING
VDI output always proceeds faster when the clipping rectangle is turned off, and text output is no exception. Remember that you may only do this if you are drawing into a dialog box, or into the interior of a window which you know is on top. (You can use the WM_TOPPED and WM_NEWTOP messages for keeping track of the top window, or use the WF_TOP wind_get() call to find the current top.) In both of these cases, you will know the width of the drawing area, and you can truncate the output string to fit exactly, rather than setting the clipping rectangle. For this to work, you must have used the byte alignment technique to assure that the width of the writing area is a multiple of eight.
BINDINGS
The normal binding for v_gtext() is inefficient. It copies the string which you supply character-by-character into intin[] before it calls the VDI itself. In many cases, it will be more efficient for your application to place characters directly into intin[] and make the VDI trap call directly. To give you a start, the code for the standard v_gtext() binding has been included in the download. When setting up intin[], be sure not to load more than 80 characters, or you will probably crash the system!
MOVING TEXT
When performing text editing on the screen, you should avoid rewriting the string under edit whenever possible. It is always more efficient to use the raster operations to move a string to the right or left, assuming that you have obeyed the byte alignment rule. If you are deleting characters, blit the unchanged part of the screen to the left, and overstrike the last character in the string with a blank. If inserting characters, blit the trailing portion of the string to the right before writing in the new character.
THAT'S IT FOR NOW
This concludes the two article series on simple VDI output. Future columns may explore more complex VDI topics such as proportional text. If there is something you would like to see, please use the Online Feedback to let me know! Meanwhile, the next column will give out the locations of some of the "hooks" and "trapdoors" built into the AES object structure, including how to set up user-defined AES drawing objects.
GEMC10.C
>>>>>>>>>>> Demonstration of byte alignment of window interior <<<<<<<<<<<
#define FEATURES 0x0fef /* what border features are used */
WORD msg[8]; /* message from evnt_multi */
GRECT work_area; /* defines working area */
WORD w_hndl; /* handle for window being changed */
wind_calc(1, FEATURES, msg[4], msg[5], msg[6], msg[7],
&work_area.g_x, &work_area.g_y, &work_area.g_w,
&work_area.g_h);
work_area.g_x = align_x(work_area.g_x);
work_area.g_w = align_x(work_area.g_w);
wind_calc(0, FEATURES, work_area.g_x, work_area.g_y,
work_area.g_w, work_area.g_h, &msg[4], &msg[5],
&msg[6], &msg[7]);
wind_set(w_hndl, WF_CXYWH, msg[4], msg[5], msg[6], msg[7]);
>>>>>>>>>>>>>>>>>>>>> Subroutine for above <<<<<<<<<<<<<<<<<<<<<<<<<<<<<
WORD
align_x(x) /* forces word alignment for column position */
WORD x; /* rounding to nearest word */
{
return((x & 0xfff0) + ((x & 0x0008) ? 0x0010 : 0));
}
>>>>>>>>>>>>>>>>>>>>> Standard v_gtext binding <<<<<<<<<<<<<<<<<<<<<<<<<
WORD
v_gtext( handle, x, y, string)
WORD handle, x, y;
BYTE *string;
{
WORD i;
ptsin[0] = x;
ptsin[1] = y;
i = 0;
while (intin[i++] = *string++) /* Copy characters to intin */
; /* There is NO error checking! */
contrl[0] = 8;
contrl[1] = 1;
contrl[3] = --i;
contrl[6] = handle;
vdi();
}