Copyright (c) Hyperion Entertainment and contributors.

Graphics Library and Text

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
WIP.png This page is currently being updated to AmigaOS 4.x. Some of the information contained here may not yet be applicable in part or totally.

Graphics Library and Text

On the Amiga, rendering text is similar to rendering lines and shapes. The Amiga graphics library provides text functions based around the RastPort structure, which makes it easy to intermix graphics and text.

About Amiga Fonts

In order to render text, the Amiga needs to have a graphical representation for each symbol or text character. These individual images are known as glyphs. The Amiga gets each glyph from a font in the system font list. At present, the fonts in the system list contain a bitmap of a specific point size for all the characters and symbols of the font.

Fonts are broken up into different font families. For example, the Amiga's Topaz is a font family. Each font family shares a basic look but can have a variety of styles and point sizes.

The style of a font refers to a minor alteration in the way the plain version of the font's characters are rendered. Currently, the Amiga supports three font styles: bold, italics and underline (the font's style may also be considered plain when it does not have any of these styles). Although these styles can be inherent to a font, they are normally added algorithmically as text is rendered.

On the Amiga, the point size of a font normally refers to the height of the font in pixels. For example, Topaz-8 is 8 pixels high. Because the size of Amiga pixels varies between display modes, the appearance of a font will also vary between display modes. Future versions of the Amiga OS may measure font size in other units. For example, the standard point in the PostScript page description language normally refers to a point as being a square dot that is 1/72 of an inch on a side. Using a standard measuring unit such as the PostScript point makes it possible to create a WYSIWYG (What You See Is What You Get) display that exactly matches printer or other device output.

When the Amiga first starts up, the only fonts in the system font list are Topaz-8 and Topaz-9, both of which are in ROM. Any other fonts must be loaded from disk or generated somehow.

System Fonts

If an application asks the diskfont.library to load a font of a size that has no corresponding bitmap on disk, the library can generate that size font. If diskfont.library can find a smaller or larger version of the font requested, it can scale that font's bitmap to the size needed. Of course, because the library is scaling a bitmap, the quality of the bitmap can degenerate in the scaling process.

The diskfont.library can utilize AGFA Compugraphic font outlines. The Compugraphic fonts (CG fonts) are mathematical outlines that describe what the font's characters look like. The advantage of the outline fonts is that they can be scaled to any point size without the loss of resolution inherent in bitmap scaling. From the programmers point of view, no extra information is necessary for using the CG fonts, the diskfont.library takes care of all the scaling. Future releases of the OS may bring finer control over the font scaling engine which will allow an application to rotate and shear glyphs.

The Text Function

Amiga text rendering is centered around the graphics.library function Text(), which renders text into a rastport:

VOID Text( struct RastPort *myrp, CONST_STRPTR mystring, uint32 count );

where myrp is a pointer to the target rastport, mystring is the string to render, and count is the number of characters of mystring to render. Text() renders at the current rastport position and it takes care of moving the rastport's current X position as it renders each letter. Text() only renders text horizontally, so repositioning the rastport's Y position (for example, for a new line) is the responsibility of the application. This is covered in more detail later in this article.

Like the other rastport based graphics primitives, most of the text rendering attributes are specified within the RastPort structure itself. The current position, the color of the text, and even the font itself are all specified in the RastPort structure.

Choosing the Font

The graphics.library function SetFont() changes the rastport's current font:

VOID SetFont( struct RastPort *myrp, struct TextFont *mytf );

The parameter mytf is a pointer to an open, valid TextFont structure. The system uses the TextFont structure to keep track of fonts (The TextFont structure is discussed in detail later in this article). The OpenFont() (from graphics.library) and OpenDiskFont() (from diskfont.library) functions both return a pointer to a valid TextFont structure. The OpenFont() function will only open fonts that have already been loaded and are currently in the system list. Normally applications use the OpenDiskFont() call instead of OpenFont() because OpenDiskFont() can load and open fonts from disk as well as open those that are already in the system list.

Here are prototypes for these functions:

struct TextFont *OpenDiskFont( struct TextAttr *mytextAttr );
struct TextFont *OpenFont( struct TextAttr *mytextAttr );

The mytextAttr argument points to a TextAttr structure that describes the requested font. The TextAttr structure (from <graphics/text.h>) looks like this:

struct TextAttr {
    STRPTR  ta_Name;            /* name of the font */
    UWORD   ta_YSize;           /* height of the font */
    UBYTE   ta_Style;           /* intrinsic font style */
    UBYTE   ta_Flags;           /* font preferences and flags */
};

where ta_Name is a string naming the font to open, ta_YSize is the point size of the font (normally in pixels), ta_Style is a bitfield describing the font style, and ta_Flags is a bitfield that further describes characteristics of the font. Note that the name of the font can either be the font name alone (<font name>.font) or it can be prepended with a full path. Without a path to the font, if the font is not already loaded into the system list, OpenDiskFont() will look in the FONTS: directory for the font file. If there is a path, OpenDiskFont() will look in that directory for the font files, allowing the user to put fonts in any directory (although this is discouraged). OpenFont() and OpenDiskFont() try to find a font that matches your TextAttr description. An important thing to remember about OpenDiskFont() is that only a process can call it (as opposed to a task). This is primarily because the function has to use dos.library to scan disks for font files.

The font styles for ta_Style (from <graphics/text.h>) are:

FSF_UNDERLINED The font is underlined
FSF_BOLD The font is bolded
FSF_ITALIC The font is italicized
FSF_EXTENDED The font is extra wide

The flags for ta_Flags (from <graphics/text.h>) are:

FPF_ROMFONT
This font is built into the ROM (currently, only Topaz-8 and Topaz-9 are ROM fonts).
FPF_DISKFONT
This font was loaded from disk (with diskfont.library)
FPF_REVPATH
This font is designed to be printed from from right to left (Hebrew is written from right to left)
FPF_TALLDOT
This font was designed for a Hires screen (640x200 NTSC, non-interlaced)
FPF_WIDEDOT
This font was designed for a Lores Interlaced screen (320x400 NTSC)
FPF_PROPORTIONAL
The character widths of this font are not constant
FPF_DESIGNED
This font size was explicitly designed at this size rather than constructed. If you do not set this bit in your TextAttr, then the system may generate a font for you by scaling an existing ROM or disk font.

For example to open an 11 point bold, italic Topaz font, the code would look something like this:

/* pseudotext.c */
 
int main()
{
    struct TextAttr myta = {
        "topaz.font"
        11,
        FSF_ITALIC | FSF_BOLD,
        NULL
    };
 
    struct TextFont *myfont, *oldfont;
    struct RastPort *myrp;
    struct Window   *mywin;
 
    . . .
 
    /* open the graphics and diskfont libraries and whatever else you may need */
    . . .
 
    if (myfont = IDiskfont->OpenDiskFont(&myta))
    {
        /* you would probably set the font of the rastport you are going to use */
        myrp    = mywin->RPort
        oldfont = myrp->Font;
        IGraphics->SetFont(myrp, myfont);
 
        . . .
 
        /* perform whatever drawing you need to do */
 
        . . .
 
        /* time to clean up.  If the rastport is not exclusively yours,
           you may need to restore the original font or other Rasport values */
        IGraphics->SetFont(myrp, oldfont);
        IGraphics->CloseFont(myfont);
    }
 
    /* close whatever libraries and other resources you allocated */
    return 0;
}

The example above uses the graphics.library's SetFont() function to change the rastport's current font. Notice that this example restores the rastport's original font (myrp->Font) before exiting. This isn't normally necessary unless some other process assumes the rastport's font (or other drawing attributes) will not change. Intuition does not rely on the window's RPort.Font field for rendering or closing the default window font, so applications can change that font without having to restore it.

The user can easily change the default system fonts with the Font Preferences editor. Program designers should not make assumptions about the system font, and wherever possible, honor the user font preferences. See the Preferences section for more information on how to find user preferences.

Setting the Text Drawing Attributes

In addition to SetFont(), there are rastport control functions that set attributes for text rendering:

VOID SetAPen( struct RastPort *rp, ULONG pen );
VOID SetBPen( struct RastPort *rp, ULONG pen );
VOID SetDrMd( struct RastPort *rp, ULONG drawMode );
VOID SetABPenDrMd( struct RastPort *rp, ULONG apen, ULONG bpen, ULONG drawMode );

The color of the text depends upon the rastport's current drawing mode and pen colors. You set the draw mode with the SetDrMd() function passing it a pointer to a rastport and a drawing mode: JAM1, JAM2, COMPLEMENT or INVERSEID.

If the drawing mode is JAM1, the text will be rendered in the RastPort.FgPen color. Wherever there is a set bit in the character's bitmap image, Text() will set the corresponding bit in the rastport to the FgPen color. This is known as overstrike mode. You set the FgPen color with the SetAPen() function by passing it a pointer to the rastport and a color number.

If the drawing mode is set to JAM2, Text() will place the FgPen color as in the JAM1 mode, but it will also set the bits in the rastport to the RastPort.BgPen color wherever there is a corresponding cleared bit in the character's bitmap image. Basically, this prints the character themselves in the FgPen color and fills in the surrounding parts of the character image with the BgPen color. You set the BgPen color with the SetBPen() function by passing it a pointer to the rastport and a color number.

If the drawing mode is COMPLEMENT, for every bit set in the character's bitmap image, the corresponding bits in the rastport (in all of the rastport's bitplanes) will have their state reversed. cleared bits in the character's bitmap image have no effect on the destination rastport. As with the other drawing modes, the write mask can be used to protect certain bitplanes from being modified (see Graphics Primitives for more details).

The JAM1, JAM2, and COMPLEMENT drawing modes are mutually exclusive of each other but each can be modified by the INVERSVID drawing mode. If you combine any of the drawing modes with INVERSVID, the Amiga will reverse the state of all the bits in the source drawing area before writing anything into the rastport.

The idea of using a RastPort structure to hold all the rendering attributes is convenient if the rastport's drawing attributes aren't going to change much. This is not the case where several processes need to render into a rastport using very different drawing attributes. An easy way around this problem is to clone the RastPort. By making an exact duplicate of a RastPort, you can change the various rendering parameters of your RastPort without effecting other programs that render into the RastPort you cloned. Because a RastPort only contains a pointer to the rendering area (the bitmap), the original RastPort and the cloned RastPort both render into the bitmap, but they can use different drawing parameters (font, drawing mode, colors, etc.).

Rendering the Text

When the Text() routine renders text, it renders at the current rastport position along the text's baseline. The baseline is an imaginary line on top of which the text is rendered. Each font has a baseline that is a constant number of pixels from the top of the font's bitmap. For most fonts, parts of some characters are rendered both above and below the baseline (for example, y, g, and j usually have parts above and below the baseline). The part below the baseline is called the descender.

Descenders and Baseline of Amiga Fonts

Because Text() only increments the rastport's current X position as it renders text horizontally, programs that need to print several lines of text have to take care of moving the current pointer to the beginning of the next line, usually with the graphics.library's Move() function:

VOID Move( struct RastPort *rp, LONG x, long y );

When moving the current position to the beginning of the next line, an application must make sure it leaves enough space above and below the baseline to prevent characters on different lines from overlapping each other. There are a few fields in the TextFont structure returned by OpenFont() and OpenDiskFont() that are useful for spacing and rendering text:

struct TextFont {
    struct Message tf_Message;  /* reply message for font removal */
                                /* font name in LN        |    used in this */
    UWORD   tf_YSize;           /* font height            |    order to best */
    UBYTE   tf_Style;           /* font style             |    match a font */
    UBYTE   tf_Flags;           /* preferences and flags  /    request. */
    UWORD   tf_XSize;           /* nominal font width */
    UWORD   tf_Baseline;        /* distance from the top of char to baseline */
    UWORD   tf_BoldSmear;       /* smear to affect a bold enhancement */
 
    UWORD   tf_Accessors;       /* access count */
 
    UBYTE   tf_LoChar;          /* the first character described here */
    UBYTE   tf_HiChar;          /* the last character described here */
    APTR    tf_CharData;        /* the bit character data */
 
    UWORD   tf_Modulo;          /* the row modulo for the strike font data */
    APTR    tf_CharLoc;         /* ptr to location data for the strike font */
                                /*   2 words: bit offset then size */
    APTR    tf_CharSpace;       /* ptr to words of proportional spacing data */
    APTR    tf_CharKern;        /* ptr to words of kerning data */
};

The fields of interest to applications are as follows.

tf_YSize
The "height", in pixels, of this font. None of the characters in this font will be taller than this value.
tf_XSize
This is the character width for monospaced (non-proportional) fonts. The width includes the extra space needed to the left and right of the character to keep the characters from running together.
tf_Baseline
The distance in pixels from the top line of the font to the baseline.
tf_LoChar
This is the first character glyph (the graphical symbol associated with this font) defined in this font. All characters that have ASCII values below this value do not have an associated glyph.
tf_HiChar
This is the last character glyph defined in this font. All characters that have ASCII values above this value do not have an associated glyph. An application can use these values to avoid rendering characters which have no associated glyphs. Any characters without an associated glyph will have the default glyph associated to them. Normally, the default glyph is a rectangle.

To erase text, the graphics.library provides two functions that were specifically designed to clear parts of a rastport based on the dimensions of the current font:

VOID ClearEOL( struct RastPort *rp );
VOID ClearScreen( struct RastPort *rp );

Using the current font, ClearEOL() will clear the rest of the current text line from the rastport's current position to the edge of the rastport. ClearScreen() will clear the rest of the line as ClearEOL() does, but it will also clear the rastport below the current line of text.

Setting the Font Style

The OpenFont() and OpenDiskFont() functions both search through the fonts available to them, looking for the font that most closely matches the TextAttr structure. If these functions can't find a font that matches exactly, they will open the one with the same name that most closely matches the TextAttr structure's ta_YSize, ta_Style, and ta_Flags fields (in that order of preference).

If the font doesn't match your style choice exactly, it is possible to ask the system to alter how it renders the font so it matches the style you need. The rastport contains some flags that tell the system's text rendering functions to algorithmically add styles to characters as they are rendered. Currently, the system can add up to three styles to a font: italics, bold, and underline. The system cannot alter the style of a font if the style is already intrinsic to the font. For example, it is not possible to add (or remove) the bold styling to a font if the font was designed to be bolded. There are two graphics.library functions that deal with software font style setting:

ULONG AskSoftStyle( struct RastPort *rp );
ULONG SetSoftStyle( struct RastPort *rp, ULONG newstyle, ULONG enable );

The AskSoftStyle() function returns a bitmask of the style bits available to the rastport's current font. The style bits are the same ones used by the TextAttr's ta_Style field (from <graphics/text.h>). SetSoftStyle() changes the rastport's current software style setting according to the style bits set in the newstyle field (from the function prototype above).

SetSoftStyle() pays attention only to the bits of newstyle that have the corresponding bit in the enable field set as well. This function returns the style, which is the combined result of previous soft style selection, the effect of this function, and the style inherent in the set font. The following code fragment turns on the algorithmic font attributes for the rastport (myrastport) based on those style attributes that were requested in the OpenDiskFont() call (mytextattr.ta_Style) and not inherent in the font.

/* Set the font and add software styling to the text if I asked for a
   style in OpenFont() and didn't get it. Because most Amiga fonts do
   not have styling built into them, if the user selected some kind of
   styling for the text, it will have to be added algorithmically by
   calling SetSoftStyle().
*/
if (myfont = IDiskFont->OpenDiskFont(mytextattr))
{
        IGraphics->SetFont(myrastport, myfont);
        IGraphics->SetSoftStyle(myrastport,
                     mytextattr.ta_Style ^ myfont->tf_Style,
                     (FSF_BOLD | FSF_UNDERLINED | FSF_ITALIC));
...
...
        IGraphics->CloseFont(myfont);
}

Does the Text Fit?

The Text() function renders its text on a single horizontal line without considering whether or not the text it renders will actually fit in the visible portion of the display area. Although for some applications this behavior is acceptable, other applications, for example a word processor, need to render all of their text where the user can see it. These applications need to measure the display area to determine how much text can fit along a given baseline. The graphics.library contains several functions that perform some of the necessary measurements:

WORD TextLength( struct RastPort *my_rp, STRPTR mystring, ULONG mycount );
 
VOID TextExtent( struct RastPort *my_rp, STRPTR mystring, LONG mycount,
                 struct TextExtent *textExtent );
 
VOID FontExtent( struct TextFont *font, struct TextExtent *fontExtent );
 
ULONG TextFit  ( struct RastPort *rp, STRPTR mystring, ULONG strLen,
                 struct TextExtent *textExtent, struct TextExtent *constrainingExtent,
                 LONG strDirection, ULONG constrainingBitWidth,
                 ULONG constrainingBitHeight );

The TextLength() function is intended to mimic the Text() function without rendering the text. Using the exact same parameters as the Text() function, TextLength() returns the change in my_rp's current X position (my_rp.cp_x) that would result if the text had been rendered using the Text() function. As in Text(), the mycount parameter tells how many characters of mystring to measure.

Some fonts have characters that intrinsically render outside of the normal rectangular bounds. This can result for example, from the Amiga's version of kerning (which is discussed later in this article) or from algorithmic italicizing. In such cases, TextLength() is insufficient for determining whether a text string can fit within a given rectangular bounds.

The TextExtent() function offers a more complete measurement of a string than the TextLength() function. TextExtent() fills in the TextExtent structure passed to it based on the current rendering settings in my_rp.

The TextExtent structure <graphics/text.h>) supplies the dimensions of mystring's bounding box:

struct TextExtent {
    UWORD   te_Width;           /* same as TextLength */
    UWORD   te_Height;          /* same as tf_YSize   */
    struct Rectangle te_Extent; /* relative to CP     */
};

The Rectangle structure (from <graphics/gfx.h>):

struct Rectangle
{
    WORD   MinX,MinY;
    WORD   MaxX,MaxY;
};

TextExtent() fills in the TextExtent structure as follows:

te_Width
the same value returned by TextLength().
te_Height
the font's Y size.
te_Extent.MinX
the pixel offset from the rastport's current X position to the left side of the bounding box defined by the rectangle structure. Normally, this is zero.
te_Extent.MinY
the distance in pixels from the baseline to the top of the bounding box.
te_Extent.MaxX
the pixel offset from the rastport's current X position to the right side of the bounding box. Normally, this is te_Width - 1.
te_Extent.MaxY
the distance from the baseline to the bottom of the bounding box.

The FontExtent() function is similar to the TextExtent() function. It fills in a TextExtent() structure that describes the bounding box of the largest possible single character in a particular open font, including the effects of kerning. Because the TextExtent() function looks at an open DiskFont structure rather than a rastport to figure out values of the TextExtent structure, it cannot consider the effects of algorithmic styling.

The TextFit() function looks at a string and returns the number of characters of the string that will fit into a given rectangular bounds. TextFit() takes the current rastport rendering settings into consideration when measuring the text. Its parameters (from the prototype above) are:

my_rp
tells which rastport to get the rendering attributes from
mystring
the string to "fit"
strLen
number of characters of mystring to "fit"
constrainingExtent
a TextExtent structure describing the bounding box in which to "fit" mystring
strDirection
the offset to add to the string pointer to get to the next character in mystring (can be negative)
constrainingBitWidth
an alternative way to specify the width of the bounding box in which to "fit" mystring
constrainingBitHeight
an alternative way to specify the height of the bounding box in which to "fit" mystring

TextFit() will only pay attention to the constrainingBitWidth and constrainingBitHeight fields if constrainingExtent is NULL.

Text Measuring Example

The following example, measuretext.c, opens a window on the default public screen and renders the contents of an ASCII file into the window. It uses TextFit() to measure how much of a line of text will fit across the window. If the entire line doesn't fit, measuretext will wrap the remainder of the line into the rows that follow. This example makes use of an ASL font requester, letting the user choose the font, style, size, drawing mode, and color.

/*
** MeasureText.c
*/
 
#define INTUITION_IOBSOLETE_H
 
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <graphics/text.h>
#include <graphics/rastport.h>
#include <intuition/intuition.h>
#include <exec/libraries.h>
 
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <proto/diskfont.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/asl.h>
 
#define BUFSIZE 32768
 
UBYTE buffer[BUFSIZE];
 
void MainLoop(void);
void EOP(void);
 
struct IntuitionIFace *IIntuition;
struct GraphicsIFace *IGraphics;
struct DiskfontIFace *IDiskfont;
struct AslIFace *IAsl;
 
BPTR myfile;
UWORD wtbarheight;
struct FontRequester *fr;
struct TextFont *myfont;
struct Window *w;
struct RastPort *myrp;
struct Task *mytask;
 
int main(int argc, char **argv)
{
  struct TextAttr myta;
 
  if (argc != 2)
  {
    IDOS->Printf("template: MeasureText <file name>\n");
    return -1;
  }
 
  /* Open the file to print out. */
  if ((myfile = IDOS->Open(argv[1], MODE_OLDFILE)) == ZERO)
  {
    IDOS->Printf("File %s not found\n", argv[1]);
    return -1;
  }
 
  struct Library *IntuitionBase = IExec->OpenLibrary("intuition.library", 50);
  IIntuition = (struct IntuitionIFace*)IExec->GetInterface(IntuitionBase, "main", 1, NULL);
 
  struct Library *GfxBase = IExec->OpenLibrary("graphics.library", 50);
  IGraphics = (struct GraphicsIFace*)IExec->GetInterface(GfxBase, "main", 1, NULL);
 
  struct Library *DiskfontBase = IExec->OpenLibrary("diskfont.library", 50);
  IDiskfont = (struct DiskfontIFace*)IExec->GetInterface(DiskfontBase, "main", 1, NULL);
 
  struct Library *AslBase = IExec->OpenLibrary("asl.library", 50);
  IAsl = (struct AslIFace*)IExec->GetInterface(AslBase, "main", 1, NULL);
 
  if (IIntuition != NULL && IGraphics != NULL && IDiskfont != NULL && IAsl != NULL)
  {
    if (fr = (struct FontRequester *)                /* Open an ASL font requester */
             IAsl->AllocAslRequestTags(ASL_FontRequest,
               /* Supply initial values for requester */
               ASL_FontName, (ULONG)"topaz.font",
               ASL_FontHeight, 11L,
               ASL_FontStyqles, FSF_BOLD | FSF_ITALIC,
               ASL_FrontPen,  0x01L,
               ASL_BackPen,   0x00L,
 
               /* Give us all the gadgetry */
               ASL_FuncFlags, FONF_FRONTCOLOR | FONF_BACKCOLOR |
                              FONF_DRAWMODE | FONF_STYLES,
               TAG_END))
    {
      /* Pop up the requester */
      if (IAsl->AslRequest(fr, 0L))
      {
        myta.ta_Name       = fr->fo_Attr.ta_Name;         /* extract the font and */
        myta.ta_YSize      = fr->fo_Attr.ta_YSize;        /* display attributes   */
        myta.ta_Style      = fr->fo_Attr.ta_Style;        /* from the FontRequest */
        myta.ta_Flags      = fr->fo_Attr.ta_Flags;        /* structure.           */
 
        if (myfont = IDiskfont->OpenDiskFont(&myta))
        {
          if (w = IIntuition->OpenWindowTags(NULL,WA_SizeGadget,  TRUE,
                                             WA_MinWidth,    200,
                                             WA_MinHeight,   200,
                                             WA_DragBar,     TRUE,
                                             WA_DepthGadget, TRUE,
                                             WA_Title,       (ULONG)argv[1],
                                             TAG_END))
          {
            myrp = w->RPort;
 
            /* figure out where the baseline of the uppermost line should be. */
            wtbarheight = w->WScreen->BarHeight + myfont->tf_Baseline + 2;
 
            /* Set the font and add software styling to the text if I asked for it */
            /* in OpenFont() and didn't get it.  Because most Amiga fonts do not   */
            /* have styling built into them (with the exception of the CG outline  */
            /* fonts), if the user selected some kind of styling for the text, it  */
            /* will to be added algorithmically by calling SetSoftStyle().         */
 
            IGraphics->SetFont(myrp, myfont);
            IGraphics->SetSoftStyle(myrp,   myta.ta_Style ^ myfont->tf_Style,
                                    (FSF_BOLD | FSF_UNDERLINED | FSF_ITALIC));
            IGraphics->SetDrMd(myrp, fr->fo_DrawMode);
            IGraphics->SetAPen(myrp, fr->fo_FrontPen);
            IGraphics->SetBPen(myrp, fr->fo_BackPen);
            IGraphics->Move(myrp, w->WScreen->WBorLeft, wtbarheight);
            mytask = IExec->FindTask(NULL);
 
            MainLoop();
 
            IDOS->Delay(25);                    /* short delay to give user a chance to */
            Intuition->CloseWindow(w);               /* see the text before it goes away.    */
          }
 
          IGraphics->CloseFont(myfont);
        }
      }
      else
        IDOS->Printf("Request Cancelled\n");
 
      IAsl->FreeAslRequest(fr);
    }
  }
 
  IExec->DropInterface((struct Interface*)IAsl);
  IExec->CloseLibrary(AslBase);
 
  IExec->DropInterface((struct Interface*)IDiskfont);
  IExec->CloseLibrary(DiskfontBase);
 
  IExec->DropInterface((struct Interface*)IGraphics);
  IExec->CloseLibrary(GfxBase);
 
  IExec->DropInterface((struct Interface*)IIntuition);
  IExec->CloseLibrary(IntuitionBase);
 
  IDOS->Close(myfile);
 
  return 0;
}
 
 
void MainLoop(void)
{
  struct TextExtent resulttextent;
  LONG fit, actual, count, printable, crrts;
  BOOL aok = TRUE;
 
  while (((actual = IDOS->Read(myfile, buffer, BUFSIZE)) > 0) && aok)  /* while there's something to */
  {                                                              /* read, fill the buffer.     */
    count = 0;
 
 
    while(count < actual)
    {
      crrts = 0;
 
      while ( ((buffer[count] < myfont->tf_LoChar) ||    /* skip non-printable characters, but */
               (buffer[count] > myfont->tf_HiChar)) &&   /* account for newline characters.    */
               (count < actual) )
      {
        if (buffer[count] == '\012') crrts++; /* is this character a newline?  if it is, bump */
        count++;                               /* up the newline count.                        */
      }
 
      if (crrts > 0)                  /* if there where any newlines, be sure to display them. */
      {
        IGraphics->Move(myrp, w->BorderLeft, myrp->cp_y + (crrts * (myfont->tf_YSize + 1)));
        EOP();                                          /* did we go past the end of the page? */
      }
 
      printable = count;
      while ( (buffer[printable] >= myfont->tf_LoChar) &&      /* find the next non-printables */
              (buffer[printable] <= myfont->tf_HiChar) &&
              (printable < actual) )
      {
        printable++;
      }                                  /* print the string of printable characters wrapping  */
      while (count < printable)          /* lines to the beginning of the next line as needed. */
      {
        /* how many characters in the current string of printable characters will fit */
        /* between the rastport's current X position and the edge of the window?      */
        fit = IGraphics->TextFit(  myrp,                &(buffer[count]),
                        (printable - count), &resulttextent,
                        NULL,                1,
                        (w->Width  - (myrp->cp_x + w->BorderLeft + w->BorderRight)),
                        myfont->tf_YSize + 1  );
        if ( fit == 0 )
        {
            /* nothing else fits on this line, need to wrap to the next line.         */
            IGraphics->Move(myrp, w->BorderLeft, myrp->cp_y + myfont->tf_YSize + 1);
        }
        else
        {
           IGraphics->Text(myrp, &(buffer[count]), fit);
           count += fit;
        }
        EOP();
      }
 
      if (mytask->tc_SigRecvd & SIGBREAKF_CTRL_C)        /* did the user hit CTRL-C (the shell */
      {                                                  /* window has to receive the CTRL-C)? */
        aok = FALSE;
        IDOS->Printf("Ctrl-C Break\n");
        count = BUFSIZE + 1;
      }
    }
  }
  if (actual < 0)
    IDOS->Printf("Error while reading\n");
}
 
 
void EOP(void)
{
    if (myrp->cp_y > (w->Height - (w->BorderBottom + 2))) /* If we reached page bottom, clear the */
    {                                                     /* rastport and move back to the top.   */
        IDOS->Delay(25);
 
        IGraphics->SetAPen(myrp, 0);
        IGraphics->RectFill(myrp, (LONG)w->BorderLeft, (LONG)w->BorderTop, w->Width - (w->BorderRight + 1),
                 w->Height - (w->BorderBottom + 1) );
        IGraphics->SetAPen(myrp, 1);
        IGraphics->Move(myrp, w->BorderLeft + 1, wtbarheight);
        IGraphics->SetAPen(myrp, fr->fo_FrontPen);
    }
}

Font Scaling and Aspect Ratio

AmigaOS has the ability to scale fonts to new sizes and dimensions. This means, if the diskfont.library can't find the font size an application requests, it can create a new bitmap font by scaling the bitmap of a different size font in the same font family. AmigaOS can utilize AGFA Compugraphic outline fonts, yielding scalable fonts that don't have the exaggerated jagged edges inherent in bitmap scaling.

The best thing about the Amiga's font scaling is that its addition to the system is completely invisible to an application program. Because the diskfont.library takes care of all the font scaling, any program that uses OpenDiskFont() to open a font can have scalable fonts available to it.

When scaling a font (either from an outline or from another bitmap), diskfont.library can adjust the width of a font's glyphs according to an aspect ratio passed to OpenDiskFont(). A font glyph is the graphical representations associated with each symbol or character of a font.

The aspect ratio refers to the shape of the pixels that make up the bitmap that diskfont.library creates when it scales a font. This ratio is the width of a pixel to the height of the pixel (XWidth/YWidth). The diskfont.library uses this ratio to figure out how wide to make the font glyphs so that the look of a font's glyphs will be the same on display modes with very different aspect ratios.

For the diskfont.library to be able to scale a font to a new aspect ratio, it needs to know what the font's current aspect ratio is. The Amiga gets the aspect ratio of a font currently in the system list from an extension to the TextFont structure called (oddly enough) TextFontExtension. When the system opens a new font (and there is sufficient memory) it creates this extension.

A font's TextFont structure contains a pointer to its associated TextFontExtension. While the font is opened, the TextFont's tf_Message.mn_ReplyPort field points to a font's TextFontExtension. The <graphics/text.h> include file #defines tf_Message.mn_ReplyPort as tf_Extension.

The TextFontExtension structure contains only one field of interest: a pointer to a tag list associated with this font:

struct TagItem *tfe_Tags; /* Text Tags for the font */

If a font has an aspect ratio associated with it, the OS stores the aspect ratio as a tag/value pair in the tfe_Tags tag list.

The TA_DeviceDPI tag holds a font's aspect ratio. The data portion of the TA_DeviceDPI tag contains an X DPI (dots per inch) value in its upper word and a Y DPI value in its lower word. These values are unsigned words (UWORD). At present, these values do not necessarily reflect the font's true X and Y DPI, so their specific values are not relevant. At present, only the ratio of the X aspect to the Y aspect is important (more on this later in the article).

Notice that the X and Y DPI values are not aspect values. The X and Y aspect values are the reciprocals of the X and Y DPI values, respectively:

XDPI = 1/XAspect
YDPI = 1/YAspect

so, the aspect ratio is YDPI/XDPI, not XDPI/YDPI.

Before accessing the tag list, an application should make sure that this font has a corresponding TextFontExtension. The ExtendFont() function will return a value of TRUE if this font already has an extension or ExtendFont() was able to create an extension for this font.

The Amiga has a place to store a font's X and Y DPI values once the font is loaded into memory, but where do these X and Y values come from? A font's X and Y DPI values can come from several sources. The X and Y DPI can come from a font's disk-based representation, or it can be set by the programmer.

For the traditional Amiga bitmap fonts, in order to store the X and Y DPI values in a bitmap font's ".font" file, the structures that make up the ".font" file had to be expanded slightly. See the discussion of the FontContentsHeader structure in Composition of a Bitmap Font on Disk for more information. Currently, out of all the system standard bitmap fonts (those loaded from bitmaps on disk or ROM, not scaled from a bitmap or outline), only one has a built in aspect ratio: Topaz-9.

For the Compugraphic outline fonts, the X and Y DPI values are built into the font outline. Because the format of the Compugraphic outline fonts is proprietary, information about their layout is available only from AGFA Compugraphic. For most people, the format of the outline fonts is not important, as the diskfont.library handles converting the fonts to an Amiga-usable form.

The other place where a font can get an aspect ratio is an application. When an application opens a font with OpenDiskFont(), it can supply the TA_DeviceDPI tag that the diskfont.library uses to scale (if necessary) the font according to the aspect ratio. To do so, an application has to pass OpenDiskFont() an extended version of the TextAttr structure called the TTextAttr:

struct TTextAttr {
    STRPTR  tta_Name;           /* name of the font           */
    UWORD   tta_YSize;          /* height of the font         */
    UBYTE   tta_Style;          /* intrinsic font style       */
    UBYTE   tta_Flags;          /* font preferences and flags */
    struct TagItem *tta_Tags;   /* extended attributes        */
};

The TextAttr and the TTextAttr are identical except that the tta_Tags field points to a tag list where you place your TA_DeviceDPI tag. To tell OpenDiskFont() that it has a TTextAttr structure rather than a TextAttr structure, set the FSF_TAGGED bit in the tta_Style field.

For example, to ask for Topaz-9 scaled to an aspect ratio of 75 to 50 the code might look something like this:

#define MYXDPI (75L << 16)
#define MYYDPI (50L)
 
struct TTextAttr mytta = {
    "topaz.font", 9,
    FSF_TAGGED,   0, NULL
};
 
struct TagItem tagitem[2];
struct TextFont *myfont;
uint32 dpivalue;
 
 . . .
 
tagitem[0].ti_tag = TA_DeviceDPI;
tagitem[0].ti_Data = MYXDPI | MYYDPI;
tagitem[1].ti_tag = TAG_END;
mytta.tta_tags = tagitem;
 
 . . .
 
if (myfont = IDiskfont->OpenDiskFont(&mytta))
{
    dpi = IUtility->GetTagData(TA_DeviceDPI,
                     O,
                     ((struct TextFontExtension *)(myfont->tf_Extension))->tfe_Tags);
    if (dpi) IDOS->Printf("XDPI = %ld    YDPI = %ld\n",
                    ((dpi & 0xFFFF0000)>>16),
                    (dpi & 0x0000FFFF));
          . . .                             /* Blah Blah print blah */
 
    IGraphics->CloseFont(myfont);
}

Some Things to Look Out For

One misleading thing about the TA_DeviceDPI tag is that its name implies that the diskfont.library is going to scale the font glyphs according to an actual DPI (dots per inch) value. As far as scaling is concerned, this tag serves only as a way to specify the aspect ratio, so the actual values of the X and Y elements are not important, just the ratio of one to the other. A font glyph will look the same if the ratio is 2:1 or 200:100 as these two ratios are equal.

To makes things a little more complicated, when diskfont.library scales a bitmap font using an aspect ratio, the X and Y DPI values that the OS stores in the font's TextFontExtension are identical to the X and Y DPI values passed in the TA_DeviceDPI tag. This means the system can associate an X and Y DPI value to an open font size that is very different from the font size's actual X and Y DPI. For this reason, applications should not use these values as real DPI values. Instead, only use them to calculate a ratio.

For the Compugraphic outline fonts, things are a little different. The X and Y DPI values are built into the font outline and reflect a true X and Y DPI. When the diskfont.library creates a font from an outline, scaling it according to an application-supplied aspect ratio, diskfont.library does not change the Y DPI setting. Instead, it calculates a new X DPI based on the font’s Y DPI value and the aspect ratio passed in the TA_DeviceDPI tag. It does this because the Amiga thinks of a font size as being a height in pixels. If an application was able to change the true Y DPI of a font, the diskfont.library would end up creating font sizes that were much larger or smaller than the YSize the application asked for. If an application needs to scale a font according to height as well as width, the application can adjust the value of the YSize it asks for in the TTextAttr.

As mentioned earlier, almost all of the system standard bitmap fonts do not have a built in aspect ratio. This means that if an application loads one of these bitmap fonts without supplying an aspect ratio, the system will not put a TA_DeviceDPI tag in the font’s TextFontExtension: the font will not have an aspect ratio. If a font size that is already in the system font list does not have an associated X and Y DPI, the diskfont.library cannot create a new font of the same size with a different aspect ratio.

The reason for this is the diskfont.library cannot tell the difference between two instances of the same font size where one has an aspect ratio and the other does not. Because diskfont.library cannot see this difference, when an application asks, for example, for Topaz-8 with an aspect ratio of 2:1, OpenDiskFont() first looks through the system list to see if that font is loaded. OpenDiskFont() happens to find the ROM font Topaz-8 in the system font list, which has no X and Y DPI. Because it cannot see the difference, diskfont.library thinks it has found what it was looking for, so it does not create a new Topaz-8 with an aspect ratio of 2:1, and instead opens the Topaz-8 with no aspect ratio.

This also causes problems for programs that do not ask for a specific aspect ratio. When an application asks for a font size without specifying an aspect ratio, OpenDiskFont() will not consider the aspect ratios of fonts in the system font list when it is looking for a matching font. If a font of the same font and style is already in the system font list, even though it may have a wildly distorted aspect ratio, OpenDiskFont() will return the font already in the system rather than creating a new one.

Font Aspect Ratio Example

The following example, "cliptext.c", renders the contents of a text file to a Workbench window. This example gets the new aspect ratio for a font by asking the display database what the aspect ratio of the current display mode is.

/* cliptext.c */
 
#include <exec/types.h>
#include <dos/rdargs.h>
#include <dos/dosextens.h>
#include <intuition/intuition.h>
#include <graphics/text.h>
#include <graphics/displayinfo.h>
#include <graphics/regions.h>
#include <graphics/gfx.h>
#include <libraries/diskfont.h>
#include <libraries/diskfonttag.h>
#include <utility/tagitem.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/layers.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <proto/diskfont.h>
 
static char *vers USED = "$VER: cliptext 50.0";
 
#define BUFSIZE          4096
#define FONT_NAME        0
#define FONT_SIZE        1
#define FILE_NAME        2
#define JAM_MODE         3
#define XASP             4
#define YASP             5
#define NUM_ARGS         6
#define DEFAULTFONTSIZE  11L
#define DEFAULTJAMMODE   0L
#define DEFAULTXASP      0L
#define DEFAULTYASP      0L
 
void MainLoop(void);
 
struct DiskfontIFace *IDiskfont;
struct IntuitionIFace *IIntuition;
struct GraphicsIFace *IGraphics;
struct LayersIFace *ILayers;
 
int32 args[NUM_ARGS];
struct TagItem tagitem[2];
uint8 buffer[BUFSIZE];
BPTR myfile;
struct IntuiMessage *mymsg;
struct DrawInfo *mydrawinfo;
struct Window *mywin;
struct RastPort *myrp;
struct TTextAttr myta;
struct TextFont *myfont;
struct Rectangle myrectangle;
struct Region *new_region;
 
int main()
{
  struct Library *DiskfontBase = IExec->OpenLibrary("diskfont.library", 50);
  IDiskfont = (struct DiskfontIFace*)IExec->GetInterface(DiskfontBase, "main", 1, NULL);
 
  struct Library *IntuitionBase = IExec->OpenLibrary("intuition.library", 50);
  IIntuition = (struct IntuitionIFace*)IExec->GetInterface(IntuitionBase, "main", 1, NULL);
 
  struct Library *GfxBase = IExec->OpenLibrary("graphics.library", 50);
  IGraphics = (struct GraphicsIFace*)IExec->GetInterface(GfxBase, "main", 1, NULL);
 
  struct Library *LayersBase = IExec->OpenLibrary("layers.library", 50);
  ILayers = (struct LayersIFace*)IExec->GetInterface(LayersBase, "main", 1, NULL);
 
  if (IDiskfont != NULL && IIntuition != NULL && IGraphics != NULL && ILayers != NULL)
  {
    struct RDArgs *myrda;
    struct DisplayInfo mydi;
    uint32 mymodeid;
 
    int32 mydefaultfontsize = DEFAULTFONTSIZE;
    int32 mydefaultJAMMode = DEFAULTJAMMODE;
    int32 mydefaultXASP = 0;
    int32 mydefaultYASP = 0;
 
    args[FONT_NAME] = (int32)"topaz.font";
    args[FONT_SIZE] = (int32)&mydefaultfontsize;
    args[FILE_NAME] = (int32)"s:startup-sequence";
    args[JAM_MODE]  = (int32)&mydefaultJAMMode;
    args[XASP]      = (int32)&mydefaultXASP;
    args[YASP]      = (int32)&mydefaultYASP;
 
    if (myrda = IDOS->ReadArgs("FontName,FontSize/N,FileName,Jam/N,XASP/N,YASP/N\n", args, NULL))
    {
      if (myfile = IDOS->Open((CONST_STRPTR)args[FILE_NAME], MODE_OLDFILE) )  /* Open the file to display. */
      {
        if (mywin = IIntuition->OpenWindowTags(NULL,                      /* Open that window. */
                     WA_MinWidth,     100,     /* This application wants to hear about three   */
                     WA_MinHeight,    100,     /* things: 1) When the user clicks the window's */
                     WA_SmartRefresh, TRUE,    /* close gadget, 2) when the user starts to     */
                     WA_SizeGadget,   TRUE,    /* resize the window, 3) and when the user has  */
                     WA_CloseGadget,  TRUE,    /* finished resizing the window.                */
                     WA_IDCMP,        IDCMP_CLOSEWINDOW | IDCMP_NEWSIZE | IDCMP_SIZEVERIFY,
                     WA_DragBar,      TRUE,
                     WA_DepthGadget,  TRUE,
                     WA_Title,        args[FILE_NAME],
                     TAG_END))
        {
          tagitem[0].ti_Tag = OT_DeviceDPI;
 
          /* See if there is a non-zero value in the XASP or YASP fields. Diskfont.library */
          /* will get a divide by zero GURU if you give it a zero XDPI or YDPI value.      */
 
          /* if there is a zero value in one of them... */
          if (  ( (*(uint32 *)args[XASP]) == 0) || ( (*(uint32 *)args[YASP]) == 0)  )
          {
            /* ...then use the aspect ratio of the current display as a default... */
            mymodeid = IGraphics->GetVPModeID(&(mywin->WScreen->ViewPort));
            if (IGraphics->GetDisplayInfoData( NULL, (uint8 *)&mydi,
                                          sizeof(struct DisplayInfo), DTAG_DISP, mymodeid))
            {
              mydefaultXASP = mydi.Resolution.x;
              mydefaultYASP = mydi.Resolution.y;
              IDOS->Printf("XAsp = %ld    YAsp = %ld\n", mydefaultXASP, mydefaultYASP);
 
              /* Notice that the X and Y get _swapped_ to keep the look of the    */
              /* font glyphs the same using screens with different aspect ratios. */
              args[YASP]    = (int32)&mydefaultXASP;
              args[XASP]    = (int32)&mydefaultYASP;
            }
            else /* ...unless something is preventing us from getting the screen  */
                 /* screens resolution.  In that case, forget about the DPI tag.  */
              tagitem[0].ti_Tag = TAG_END;
          }
 
          /* Here we have to put the X and Y DPI into the OT_DeviceDPI tags data field.  */
          /* THESE ARE NOT REAL X AND Y DPI VALUES FOR THIS FONT OR DISPLAY. They only   */
          /* serve to supply the diskfont.library with values to calculate the aspect    */
          /* ratio.  The X value gets stored in the upper word of the tag value and the Y*/
          /* DPI gets stored in the lower word.  Because ReadArgs() stores the _address_ */
          /* of integers it gets from the command line, you have to dereference the      */
          /* pointer it puts into the argument array, which results in some ugly casting.*/
 
          tagitem[0].ti_Data = (uint32)( ((uint16) *( (uint32 *)args[XASP] ) << 16) |
                                         ((uint16) *( (uint32 *)args[YASP]) ) );
          tagitem[1].ti_Tag = TAG_END;
 
          myta.tta_Name = (STRPTR)args[FONT_NAME];           /* Set up the TTextAttr     */
          myta.tta_YSize = *((int32 *)args[FONT_SIZE]);       /* structure to match the   */
          myta.tta_Style = FSF_TAGGED;                       /* font the user requested. */
          myta.tta_Flags = 0L;
          myta.tta_Tags = tagitem;
 
          if (myfont = IDiskfont->OpenDiskFont(&myta))       /* open that font */
          {
            /* This is for the layers.library clipping region that gets attached to the   */
            /* window.  This prevents the application from unnecessarily rendering beyond */
            myrectangle.MinX = mywin->BorderLeft; /* the bounds of the inner part of     */
            myrectangle.MinY = mywin->BorderTop;  /* the window.  For now, you can       */
            myrectangle.MaxX = mywin->Width -     /* ignore the layers stuff if you are  */
                (mywin->BorderRight + 1);         /* just interested in learning about   */
            myrectangle.MaxY = mywin->Height -    /* using text.  For more information   */
                (mywin->BorderBottom + 1);        /* on clipping regions and layers, see */
                                                  /* the Layers chapter of this manual.  */
 
            if (new_region = IGraphics->NewRegion())                /* more layers stuff */
            {
              if (IGraphics->OrRectRegion(new_region, &myrectangle));  /* Even more layers stuff */
              {
                ILayers->InstallClipRegion(mywin->WLayer, new_region);
                /* Obtain a pointer to the window's rastport and set up some of the      */
                myrp = mywin->RPort;    /* rastport attributes. This example obtains the */
 
                IGraphics->SetFont(myrp, myfont);  /* text pen for the window's screen using */
                if (mydrawinfo = IIntuition->GetScreenDrawInfo(mywin->WScreen)) /* GetScreenDrawInfo()*/
                {
                  IGraphics->SetAPen(myrp, mydrawinfo->dri_Pens[TEXTPEN]);
                  IIntuition->FreeScreenDrawInfo(mywin->WScreen, mydrawinfo);
                }
 
                IGraphics->SetDrMd(myrp, (int8)(*((int32 *)args[JAM_MODE])));
 
                MainLoop();
              }
              IGraphics->DisposeRegion(new_region);
            }
            IGraphics->CloseFont(myfont);
          }
          IIntuition->CloseWindow(mywin);
        }
      IDOS->Close(myfile);
    }
    IDOS->FreeArgs(myrda);
  }
  else
    IDOS->VPrintf("Error parsing arguments\n", NULL);
 
  IExec->DropInterface((struct Interface*)ILayers);
  IExec->CloseLibrary(LayersBase);
 
  IExec->DropInterface((struct Interface*)IGraphics);
  IExec->CloseLibrary(GfxBase);
 
  IExec->DropInterface((struct Interface*)IIntuition);
  IExec->CloseLibrary(IntuitionBase);
 
  IExec->DropInterface((struct Interface*)IDiskfont);
  IExec->CloseLibrary(DiskfontBase);
 
  return 0;
}
 
 
void MainLoop(void)
{
    int32 count, actual, position;
    BOOL aok = TRUE, waitfornewsize = FALSE;
    struct Task *mytask;
 
    mytask = IExec->FindTask(NULL);
    IGraphics->Move(myrp, mywin->BorderLeft + 1, mywin->BorderTop + myfont->tf_YSize + 1);
 
    while (((actual = IDOS->Read(myfile, buffer, BUFSIZE)) > 0) && aok) /* While there's something   */
    {                                                                   /* to read, fill the buffer. */
        position = 0;
        count = 0;
 
        while (position <= actual)
        {
           if (!(waitfornewsize))
           {
               while ( ((buffer[count] >= myfont->tf_LoChar) &&
                       (buffer[count] <= myfont->tf_HiChar)) && (count <= actual) )
                   count++;
 
               IGraphics->Text(myrp, &(buffer[position]), (count)-position);
 
               while ( ((buffer[count] < myfont->tf_LoChar) ||
                       (buffer[count] > myfont->tf_HiChar)) && (count <= actual) )
               {
                   if (buffer[count] == 0x0A)
                       IGraphics->Move(myrp, mywin->BorderLeft, myrp->cp_y + myfont->tf_YSize + 1);
                   count++;
               }
 
               position = count;
           }
           else IExec->WaitPort(mywin->UserPort);
 
           while (mymsg = (struct IntuiMessage *)IExec->GetMsg(mywin->UserPort))
           {
               if (mymsg->Class == IDCMP_CLOSEWINDOW)     /* The user clicked the close gadget */
               {
                   aok = FALSE;
                   position = actual + 1;
                   IExec->ReplyMsg((struct Message *)mymsg);
               }                                                     /* The user picked up the */
               else if (mymsg->Class == IDCMP_SIZEVERIFY)            /* window's sizing gadget */
               {
                  /* When the user has picked up the window's sizing gadget when the           */
                  /* IDCMP_SIZEVERIFY flag is set, the application has to reply to this message*/
                  /* to tell Intuition to allow the user to move the sizing gadget and resize  */
                  /* the window.  The reason for using this here is because the user can resize*/
                  /* the window while cliptext.c is rendering text to the window. Cliptext.c   */
                  /* has to stop rendering text when it receives an IDCMP_SIZEVERIFY message.  */
                  /*                                                                           */
                  /* if this example had instead asked to hear about IDCMP events that could   */
                  /* take place between SIZEVERIFY and NEWSIZE events (especially INTUITICKS), */
                  /* it should turn off those events here using ModifyIDCMP().                 */
                  /*                                                                           */
                  /* After we allow the user to resize the window, we cannot write into the    */
                  /* window until the user has finished resizing it because we need the        */
                  /* window's new size to adjust the clipping area.  Specifically, we have     */
                  /* to wait for an IDCMP_NEWSIZE message which Intuition will send when the   */
                  /* user lets go of the resize gadget.  For now, we set the waitfornewsize    */
                  /* flag to stop rendering until we get that NEWSIZE message.                 */
 
                   waitfornewsize = TRUE;
                   IGraphics->WaitBlit();
 
                   IExec->ReplyMsg((struct Message *)mymsg);     /* The blitter is done, let the */
               }                                                 /* user resize the window.      */
               else
               {
                   IExec->ReplyMsg((struct Message *)mymsg);
                   waitfornewsize = FALSE;
                          /* The user has resized the window, so get the new window dimensions */
                   myrectangle.MinX = mywin->BorderLeft;      /* and readjust the layers       */
                   myrectangle.MinY = mywin->BorderTop;       /* clipping region accordingly.  */
                   myrectangle.MaxX = mywin->Width - (mywin->BorderRight + 1);
                   myrectangle.MaxY = mywin->Height - (mywin->BorderBottom + 1);
                   ILayers->InstallClipRegion(mywin->WLayer, NULL);
                   IGraphics->ClearRegion(new_region);
                   if (IGraphics->OrRectRegion(new_region, &myrectangle))
                       ILayers->InstallClipRegion(mywin->WLayer, new_region);
                   else
                   {
                       aok = FALSE;
                       position = actual + 1;
                   }
               }
           }
 
           if (mytask->tc_SigRecvd & SIGBREAKF_CTRL_C)              /* Check for user break.   */
           {
               aok = FALSE;
               position = actual + 1;
           }
 
           if (myrp->cp_y > (mywin->Height - (mywin->BorderBottom + 2))) /* if we reached the  */
           {                                               /* bottom of the page, clear the    */
               IDOS->Delay(25);                            /* rastport and move back to the top*/
 
               IGraphics->SetRast(myrp, 0); /* Set the entire rastport to color zero.  This will not */
               IGraphics->Move(myrp,        /* the window borders because of the layers clipping.    */
               mywin->BorderLeft + 1,
               mywin->BorderTop + myfont->tf_YSize + 1);
            }
        }
    }
 
    if (actual < 0)
      IDOS->VPrintf("Error while reading\n", NULL);
}

What Fonts Are Available?

The diskfont.library function AvailFonts() fills in a memory area designated by you with a list of all of the fonts available to the system. AvailFonts() searches the AmigaDOS directory path currently assigned to FONTS: and locates all available fonts. If you haven't issued a DOS Assign command to change the FONTS: directory path, it defaults to the sys:fonts directory.

LONG AvailFonts( struct AvailFontsHeader *mybuffer, LONG bufBytes, LONG flags );

AvailFonts() fills in a memory area, mybuffer, which is bufBytes bytes long, with an AvailFontsHeader structure:

struct AvailFontsHeader {
    UWORD   afh_NumEntries;      /* number of AvailFonts elements */
    /* struct AvailFonts afh_AF[], or struct TAvailFonts afh_TAF[]; */
};

This structure is followed by an array of AvailFonts structures with the number of entries in the array equal to afh_NumEntries:

struct AvailFonts {
    UWORD   af_Type;            /* MEMORY, DISK, or SCALED */
    struct TextAttr af_Attr;    /* text attributes for font */
};

Each AvailFonts structure describes a font available to the OS. The flags field lets AvailFonts() know which fonts you want to hear about. At present, there are four possible flags:

AFF_MEMORY
Create AvailFonts structures for all TextFont's currently in the system list.
AFF_DISK
Create AvailFonts structures for all TextFont's that are currently available from disk.
AFF_SCALED
Create AvailFonts structures for TextFont's that do not have their FPF_DESIGNED flag set. If the AFF_SCALED flag is not present, AvailFonts() will not create AvailFonts structures for fonts that have been scaled, which do not have the FPF_DESIGNED flag set.
AFF_TAGGED
These AvailFonts structures are really TAvailFonts structures. These structures were created to allow AvailFonts() to list tag values:
  struct TAvailFonts {
      UWORD   taf_Type;           /* MEMORY, DISK, or SCALED */
      struct TTextAttr taf_Attr;  /* text attributes for font */
  };

Notice that AFF_MEMORY and AFF_DISK are not mutually exclusive; a font that is currently in memory may also be available for loading from disk. In this case, the font will appear twice in the array of AvailFonts (or TAvailFonts) structures.

If AvailFonts() fails without any major system problems, it will be because the buffer for the AvailFontsHeader structure was not big enough to contain all of the AvailFonts or TAvailFonts structures. In this case, AvailFonts() returns the number of additional bytes that mybuffer needed to contain all of the TAvailFonts or AvailFonts structures. You can then use that return value to figure out how big the buffer needs to be, allocate that memory, and try AvailFonts() again:

int32 afShortage, afSize;
struct AvailFontsHeader *afh;
 . . .
 
    afSize = IDiskfont->AvailFonts(afh, 0, AFF_MEMORY|AFF_DISK|AFF_SCALED|AFF_TAGGED);
    do
    {
        afh = (struct AvailFontsHeader *) IExec->AllocMem(afSize, 0);
        if (afh)
        {
            afShortage = IDiskfont->AvailFonts(afh, afSize, AFF_MEMORY|AFF_DISK|AFF_SCALED|AFF_TAGGED);
            if (afShortage)
            {
                IExec->FreeMem(afh, afSize);
                afSize += afShortage;
            }
        }
        else
        {
            fail("AllocMem of AvailFonts buffer afh failed\n");
            break;
        }
    } while (afShortage); /* if (afh) non-zero here, then:                                 */
                          /* 1. it points to a valid AvailFontsHeader,                     */
                          /* 2. it must have FreeMem(afh, afSize) called for it after use. */

The following code, "AvailFonts.c", uses AvailFonts() to find out what fonts are available to the system. It uses this information to open every available font (one at a time), print some information about the font (including the TA_DeviceDPI tag values if they are present), and renders a sample of the font into a clipping region.

;/* AvailFonts.c - Execute me to compile me with GCC
gcc -Wall -c AvailFonts.c
gcc -Wall -N -o AvailFonts availfonts.o
quit ;
*/
 
#include <classes/window.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/layers.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <proto/diskfont.h>
#include <proto/utility.h>
 
#include <stdio.h>
 
STRPTR vers = "\0$VER: AvailFonts 50.0";
struct LayersIFace *ILayers = NULL;
struct UtilityIFace *IUtility = NULL;
struct IntuitionIFace *IIntuition = NULL;
struct GraphicsIFace *IGraphics 	= NULL;
struct DiskfontIFace *IDiskfont 	= NULL;
struct ClassLibrary *WindowBase = NULL;
Class *WindowClass;
 
void  MainLoop(void);
uint32 StrLen(STRPTR);
 
APTR OpenInterface (CONST_STRPTR lib_name, int32 lib_vers,
                    CONST_STRPTR int_name, int32 int_vers);
void CloseInterface (APTR interface);
 
int32 OpenAll(int argc, char **argv);
int32 openfail(STRPTR reason);
void  CloseAll(void);
 
struct stringstruct {
        STRPTR string;
        int32 charcount;
        int16 stringwidth;
};
 
STRPTR alphabetstring = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
struct stringstruct fname, fheight, XDPI, YDPI, entrynum;
struct Window *mywin;
struct RastPort *mycliprp, myrp;
struct Rectangle myrect;
struct Region *new_region, *old_region;
struct DrawInfo *mydrawinfo;
struct AvailFontsHeader *afh;
int32 fontheight, alphabetcharcount;
int16 stringwidth;
 
int main(int argc, char **argv)
{
  struct TextFont *defaultfont = NULL;
  struct TextAttr defaultfontattr = { "topaz.font", 9, 0, 0 };
 
  int32 afsize, afshortage, cliprectside;
 
  fname.string = "Font Name:  ";
  fheight.string = "Font Height:  ";
  XDPI.string = "X DPI:  ";
  YDPI.string = "Y DPI:  ";
  entrynum.string = "Entry #:  ";
 
  if(OpenAll(argc, argv))
  {
    return(RETURN_FAIL);
  }
 
  if ((mywin = IIntuition->OpenWindowTags(NULL,  /* Open that window. */
    WA_SmartRefresh,TRUE,
    WA_SizeGadget,  FALSE,
    WA_CloseGadget, TRUE,
    WA_IDCMP,       IDCMP_CLOSEWINDOW,
    WA_DragBar,     TRUE,
    WA_DepthGadget, TRUE,
    WA_Title,       (uint32)"AvailFonts() example",
    TAG_END)))
  {
    myrp = *(mywin->RPort);  /* A structure assign: clone my window's Rastport. */
                             /* RastPort.  This RastPort will be used to render */
                             /* the font specs, not the actual font sample.     */
    if ((mydrawinfo = IIntuition->GetScreenDrawInfo(mywin->WScreen)))
    {
      IGraphics->SetFont(&myrp, mydrawinfo->dri_Font);
 
      myrect.MinX = mywin->BorderLeft;  /* LAYOUT THE WINDOW */
      myrect.MinY = mywin->BorderTop;
      myrect.MaxX = mywin->Width - (mywin->BorderRight + 1);
      myrect.MaxY = mywin->Height - (mywin->BorderBottom + 1);
 
      cliprectside = (myrect.MaxX - myrect.MinX) / 20;
 
      fname.charcount    = StrLen(fname.string);
      fheight.charcount  = StrLen(fheight.string);
      XDPI.charcount     = StrLen(XDPI.string);
      YDPI.charcount     = StrLen(YDPI.string);
      entrynum.charcount = StrLen(entrynum.string);
      alphabetcharcount  = StrLen(alphabetstring);
 
      fontheight = (myrp.Font->tf_YSize) + 2;
 
      if (fontheight > ((myrect.MaxY - myrect.MinY) / 6))  /* If the default screen  */
      {                                                    /* font is more than one- */
        defaultfont = IGraphics->OpenFont(&defaultfontattr); /* sixth the size of the  */
        IGraphics->SetFont(&myrp, defaultfont);              /* window, use topaz-9.   */
        fontheight = (myrp.Font->tf_YSize) + 2;
      }
 
      fname.stringwidth   = IGraphics->TextLength(&myrp, (STRPTR)fname.string, fname.charcount);
      fheight.stringwidth = IGraphics->TextLength(&myrp, (STRPTR)fheight.string, fheight.charcount);
      XDPI.stringwidth    = IGraphics->TextLength(&myrp, (STRPTR)XDPI.string, XDPI.charcount);
      YDPI.stringwidth    = IGraphics->TextLength(&myrp, (STRPTR)YDPI.string, YDPI.charcount);
      entrynum.stringwidth =
      IGraphics->TextLength(&myrp, (STRPTR)entrynum.string, entrynum.charcount);
 
      stringwidth = fname.stringwidth;         /* What is the largest string length? */
      stringwidth =
        (fheight.stringwidth > stringwidth) ? fheight.stringwidth : stringwidth;
      stringwidth = (XDPI.stringwidth > stringwidth) ? XDPI.stringwidth : stringwidth;
      stringwidth = (YDPI.stringwidth > stringwidth) ? YDPI.stringwidth : stringwidth;
      stringwidth =
        (entrynum.stringwidth > stringwidth) ? entrynum.stringwidth : stringwidth;
      stringwidth += mywin->BorderLeft;
 
      if (stringwidth < ((myrect.MaxX - myrect.MinX) >> 1)) /* If the stringwidth is */
      {                                                 /* more than half the viewing*/
        IGraphics->SetAPen(&myrp, mydrawinfo->dri_Pens[TEXTPEN]);  /* area, quit because the    */
        IGraphics->SetDrMd(&myrp, JAM2);                           /* font is just too big.     */
 
        IGraphics->Move(&myrp, myrect.MinX + 8 + stringwidth - fname.stringwidth,
          myrect.MinY + 4 + (myrp.Font->tf_Baseline));
        IGraphics->Text(&myrp, fname.string, fname.charcount);
 
        IGraphics->Move(&myrp, myrect.MinX + 8 + stringwidth - fheight.stringwidth,
          myrp.cp_y + fontheight);
        IGraphics->Text(&myrp, fheight.string, fheight.charcount);
 
        IGraphics->Move(&myrp, myrect.MinX + 8 + stringwidth - XDPI.stringwidth,
          myrp.cp_y + fontheight);
        IGraphics->Text(&myrp, XDPI.string, XDPI.charcount);
 
        IGraphics->Move(&myrp, myrect.MinX + 8 + stringwidth - YDPI.stringwidth,
          myrp.cp_y + fontheight);
        IGraphics->Text(&myrp, YDPI.string, YDPI.charcount);
 
        IGraphics->Move(&myrp, myrect.MinX + 8 + stringwidth - entrynum.stringwidth,
          myrp.cp_y + fontheight);
        IGraphics->Text(&myrp, entrynum.string, entrynum.charcount);
 
        myrect.MinX = myrect.MinX + cliprectside;
        myrect.MaxX = myrect.MaxX - cliprectside;
        myrect.MinY = myrect.MinY + (5 * fontheight) + 8;
        myrect.MaxY = myrect.MaxY - 8;
 
        IGraphics->SetAPen(&myrp, mydrawinfo->dri_Pens[SHINEPEN]);         /* Draw a box around */
        IGraphics->Move(&myrp, myrect.MinX - 1, myrect.MaxY + 1);          /* the cliprect.     */
        IGraphics->Draw(&myrp, myrect.MaxX + 1, myrect.MaxY + 1);
        IGraphics->Draw(&myrp, myrect.MaxX + 1, myrect.MinY - 1);
 
        IGraphics->SetAPen(&myrp, mydrawinfo->dri_Pens[SHADOWPEN]);
        IGraphics->Draw(&myrp, myrect.MinX - 1, myrect.MinY - 1);
        IGraphics->Draw(&myrp, myrect.MinX - 1, myrect.MaxY);
 
        IGraphics->SetAPen(&myrp, mydrawinfo->dri_Pens[TEXTPEN]);
        /* Fill up a buffer with a list of the available fonts */
        afsize = IDiskfont->AvailFonts((STRPTR)afh, 0L, AFF_MEMORY|AFF_DISK|AFF_SCALED|AFF_TAGGED);
        do
        {
          afh = (struct AvailFontsHeader *) IExec->AllocVecTags(afsize, TAG_END);
          if (afh)
          {
            afshortage = IDiskfont->AvailFonts((STRPTR)afh, afsize,
              AFF_MEMORY|AFF_DISK|AFF_SCALED|AFF_TAGGED);
            if (afshortage)
            {
              IExec->FreeVec(afh);
              afsize += afshortage;
              afh = (struct AvailFontsHeader *)(-1L);
            }
          }
        } while (afshortage && afh);
 
        if (afh)
        {
          /* This is for the layers.library clipping region that gets attached to */
          /* the window.  This prevents the application from unnecessarily        */
          /* rendering beyond the bounds of the inner part of the window. For     */
          /* more information on clipping, see the Layers chapter of this manual. */
 
          if ((new_region = IGraphics->NewRegion()))                 /* More layers stuff */
          {
            if (IGraphics->OrRectRegion(new_region, &myrect)) /* Even more layers stuff */
            {
              /* Obtain a pointer to the window's rastport and set up some of    */
              /* the rastport attributes.  This example obtains the text pen     */
              /* for the window's screen using the GetScreenDrawInfo() function. */
              mycliprp = mywin->RPort;
              IGraphics->SetAPen(mycliprp, mydrawinfo->dri_Pens[TEXTPEN]);
 
              MainLoop();
 
            }
            IGraphics->DisposeRegion(new_region);
          }
          IExec->FreeVec(afh);
        }
      }
      IIntuition->FreeScreenDrawInfo(mywin->WScreen, mydrawinfo);
    }
    IIntuition->CloseWindow(mywin);
  }
 
  CloseAll();
  return(RETURN_OK);
}
 
void MainLoop(void)
{
  uint16 x;
  struct Task *mytask;
  struct IntuiMessage *mymsg;
  BOOL aok = TRUE;
  struct TAvailFonts *afont;
  struct TextFont *myfont;
  uint8 buf[8];
  uint32 dpi;
 
 
  mytask = IExec->FindTask(NULL);
  afont = (struct TAvailFonts *)&(afh[1]);
 
  for (x = 0; (x < afh->afh_NumEntries); x++)
  {
    if (aok)
    {
      if ((myfont = IDiskfont->OpenDiskFont((struct TextAttr *)&(afont->taf_Attr))))
      {
 
        IGraphics->SetAPen(&myrp, mydrawinfo->dri_Pens[BACKGROUNDPEN]);   /* Print the TextFont attributes. */
        IGraphics->RectFill( &myrp, stringwidth, mywin->BorderTop + 4,
                  mywin->Width - (mywin->BorderRight + 1), myrect.MinY - 2 );
 
        IGraphics->SetAPen(&myrp, mydrawinfo->dri_Pens[TEXTPEN]);
        IGraphics->Move( &myrp, stringwidth + mywin->BorderLeft,
              mywin->BorderTop + 4 + (myrp.Font->tf_Baseline) );
        IGraphics->Text( &myrp, (STRPTR)myfont->tf_Message.mn_Node.ln_Name,
              StrLen((STRPTR)myfont->tf_Message.mn_Node.ln_Name) );
 
        IGraphics->Move(&myrp, stringwidth + mywin->BorderLeft, myrp.cp_y + fontheight); /* Print the      */
        sprintf((STRPTR)buf, "%d", myfont->tf_YSize);                               /* font's Y Size. */
        IGraphics->Text(&myrp, (STRPTR)buf, StrLen((STRPTR)buf));
 
        IGraphics->Move(&myrp, stringwidth + mywin->BorderLeft, myrp.cp_y + fontheight); /* Print the X DPI */
        dpi = IUtility->GetTagData( TA_DeviceDPI, 0L,
                          ((struct TextFontExtension *)(myfont->tf_Extension))->tfe_Tags);
        if (dpi)
        {
            sprintf((STRPTR)buf, "%ld", ((dpi & 0xFFFF0000)>>16));
            IGraphics->Text(&myrp, (STRPTR)buf, StrLen((STRPTR)buf));
        }
        else IGraphics->Text(&myrp, "nil", 3L);
 
        IGraphics->Move(&myrp, stringwidth + mywin->BorderLeft, myrp.cp_y + fontheight); /* Print the Y DPI */
        if (dpi)
        {
            sprintf((STRPTR)buf, "%ld", (dpi & 0x0000FFFF));
            IGraphics->Text(&myrp, (STRPTR)buf, StrLen((STRPTR)buf));
        }
        else IGraphics->Text(&myrp, "nil", 3L);
 
        IGraphics->Move(&myrp, stringwidth + mywin->BorderLeft, myrp.cp_y + fontheight);     /* Print the */
        sprintf((STRPTR)buf, "%d", x);                                                   /* entrynum. */
        IGraphics->Text(&myrp, (STRPTR)buf, StrLen((STRPTR)buf));
 
 
        IGraphics->SetFont(mycliprp, myfont);
        old_region = ILayers->InstallClipRegion(mywin->WLayer, new_region); /* Install clipping rectangle */
 
        IGraphics->SetRast(mycliprp, mydrawinfo->dri_Pens[BACKGROUNDPEN]);
        IGraphics->Move( mycliprp, myrect.MinX, myrect.MaxY - (myfont->tf_YSize - myfont->tf_Baseline) );
        IGraphics->Text(mycliprp, alphabetstring, alphabetcharcount);
 
        IDOS->Delay(100);  /* reduce this delay to go faster */
 
        new_region = ILayers->InstallClipRegion(mywin->WLayer, old_region);  /* Remove clipping rectangle */
 
        while ((mymsg = (struct IntuiMessage *)IExec->GetMsg(mywin->UserPort)))
        {
            aok = FALSE;
            x = afh->afh_NumEntries;
            IExec->ReplyMsg((struct Message *)mymsg);
        }
 
 
        if (mytask->tc_SigRecvd & SIGBREAKF_CTRL_C)        /* Did the user hit CTRL-C (the shell */
        {                                                  /* window has to receive the CTRL-C)? */
            aok = FALSE;
            x = afh->afh_NumEntries;
            IDOS->VPrintf("Ctrl-C Break\n", NULL);
        }
        IGraphics->CloseFont(myfont);
      }
    }
    afont++;
  }
}
 
 
uint32 StrLen(STRPTR string)
{
  uint32 x = 0L;
 
  while (string[x++]);
  return(--x);
}
 
APTR OpenInterface (CONST_STRPTR lib_name, int32 lib_vers,
   CONST_STRPTR int_name, int32 int_vers)
{
  struct Library   *library;
  struct Interface *interface;
  BPTR errout;
 
  library = IExec->OpenLibrary(lib_name, lib_vers);
  if (library)
  {
    interface = IExec->GetInterface(library, int_name, int_vers, NULL);
    if (interface)
    {
      return interface;
    }
 
    IExec->CloseLibrary(library);
  }
 
  if((errout = IDOS->ErrorOutput()))
  {
    IDOS->FPrintf(errout, "FAILURE: Problem opening %s version %ld\n",
      lib_name, lib_vers);
  }
 
  return NULL;
}
 
void CloseInterface (APTR interface)
{
  if (interface)
  {
    struct Library *library = ((struct Interface *)interface)->Data.LibBase;
    IExec->DropInterface(interface);
    IExec->CloseLibrary(library);
  }
}
 
/* return 0 for success, any other value for failure */
int32 OpenAll(int argc, char **argv)
{
 
  if((NULL == (IIntuition =   OpenInterface("intuition.library",	50, "main", 1)))	||
     (NULL == (IUtility     =   OpenInterface("utility.library", 	50, "main", 1)))	||
     (NULL == (ILayers   =   OpenInterface("layers.library", 	50, "main", 1)))	||
     (NULL == (IGraphics = OpenInterface("graphics.library", 	50, "main", 1)))	||
     (NULL == (IDiskfont =  OpenInterface("diskfont.library", 	50, "main", 1)))	)
  {
    return(openfail(NULL));
  }
  WindowBase = IIntuition->OpenClass("window.class", 52, &WindowClass);
  if(NULL == WindowBase)
  {
    return(openfail((char *)"Could not open window class"));
  }
 
  return(0);	// success!
}
 
int32 openfail(STRPTR reason)
{
  if(reason)
  {
    IDOS->Printf("%s\n",reason);
  }
  CloseAll();
  return(RETURN_FAIL);
}
 
void CloseAll(void)
{
  IIntuition->CloseClass(WindowBase);
  CloseInterface(IDiskfont);
  CloseInterface(IGraphics);
  CloseInterface(ILayers);
  CloseInterface(IUtility);
  CloseInterface(IIntuition);
}

How is an Amiga Font Structured in Memory?

So far, this article has concentrated on using library functions to render text, letting the system worry about the layout of the underlying font data. As far as the OS representation of a loaded font is concerned, outline fonts and normal bitmap fonts are structured identically. Color fonts have some extras information associated with them and are discussed a little later. Every loaded font, including color fonts, has a TextFont structure associated with them:

struct TextFont {
    struct Message tf_Message;  /* reply message for font removal */
    UWORD   tf_YSize;
    UBYTE   tf_Style;
    UBYTE   tf_Flags;
    UWORD   tf_XSize;
    UWORD   tf_Baseline;
    UWORD   tf_BoldSmear;       /* smear to affect a bold enhancement */
 
    UWORD   tf_Accessors;
    UBYTE   tf_LoChar;
    UBYTE   tf_HiChar;
    APTR    tf_CharData;        /* the bit character data */
 
    UWORD   tf_Modulo;          /* the row modulo for the strike font data   */
    APTR    tf_CharLoc;         /* ptr to location data for the strike font  */
                                /*   2 words: bit offset then size           */
    APTR    tf_CharSpace;       /* ptr to words of proportional spacing data */
    APTR    tf_CharKern;        /* ptr to words of kerning data              */
};

The first field in this structure is a Message structure. The node in this Message structure is what the OS uses to link together the fonts in the system list. From this node, an application can extract a font's name. The other fields in the TextFont structure are as follows:

tf_YSize
The maximum height of this font in pixels.
tf_Style
The style bits for this particular font, which are defined in <graphics/text.h>. These include the same style bits that were mentioned in the discussion of the TextAttr structure in Choosing the Font. In addition to those bits, there is also the FSF_COLORFONT bit, which identifies this as a special kind of TextFont structure called a ColorTextFont structure. This is discussed later in this article.
tf_Flags
The flags for this font, which were mentioned along with the style bits in the section, "Choosing the Font".
tf_XSize
If this font is monospaced (non-proportional), tf_XSize is the width of each character. The width includes the extra space needed to the left and right of the character to keep the characters from running together.
tf_Baseline
The distance in pixels from the top line of the font to the baseline.
tf_BoldSmear
When algorithmically bolding, the Amiga currently "smears" a glyph by rendering it, moving over tf_BoldSmear number of pixels, and rerendering the glyph.
tf_Accessors
The number of currently open instances of this font (like the open count for libraries).
tf_LoChar
This is the first character glyph (the graphical symbol associated with this font) defined in this font. All characters that have ASCII values below this value do not have an associated glyph.
tf_HiChar
This is the last character glyph defined in this font. All characters that have ASCII values above this value do not have an associated glyph. An application can use these values to avoid rendering characters which have no associated glyphs. Any characters without an associated glyph will have the default glyph associated to them. Normally, the default glyph is a rectangle.
tf_CharData
This is the address of the bitmap from which the OS extracts the font's glyphs. The individual glyphs are bit-packed together. The individual bitmaps of the glyphs are placed in ASCII order side by side, left to right. The last glyph is the default glyph. The following is what the bitmap of the suits-8 font example looks like (suits-8 is the complete, disk-based bitmap font example used later in this article):
 .@@@...@@@......@........@.......@@@....@@@@@@@@@@@@............
 @@@@@.@@@@@...@@@@@.....@@@.....@@@@@...@@........@@............
 .@@@@@@@@@..@@@@@@@@@..@@@@@..@@..@..@@.@@........@@............
 ..@@@@@@@..@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@........@@............
 ...@@@@@....@@@.@.@@@..@@@@@..@@..@..@@.@@........@@............
 ....@@@.........@.......@@@.......@.....@@........@@............
 .....@........@@@@@......@......@@@@@...@@@@@@@@@@@@............
 ................................................................

This font is rather sparse, as it only has five glyphs. Most fonts at least have glyphs for each letter of the alphabet. In this example, each glyph represents a symbol for a suit in a standard deck of cards (from left to right: hearts, spades, diamonds, and clubs). Notice that there is no space between the individual glyphs. The spacing information is kept in separate tables to reduce the amount of memory occupied by the font.

This is number of bytes the pointer must move to go from one line of a glyph to the next. This is the pixel width of the entire font bitmap divided by eight. Notice that the bitmap above does not stop after it gets to the end of the last glyph. It is padded with zero bits to the nearest WORD boundary.

This is a pointer to the CharLoc, the character location table. This table tells the OS how far into the bitmap to look for a character and how many pixels to fetch from each row. The CharLoc table for the suits-8 font looks like this:

$0000000B,$000B000B,$00160007,$001D000B,$0028000C

Each of the five long words in this character location table corresponds to a glyph in Suits-8. Each long word is broken up into two word values. The first word is the offset in pixels from the left edge of the bitmap to the first column containing the corresponding glyph. The second word is the width in pixels of the corresponding glyph image in the bitmap (note, this is not the width of the actual glyph as the actual glyph will have some space on either side of it). For example, the diamond character (the third character) starts at offset $16 (22) and it is 7 pixels wide.

This is a pointer to an array of WORDs containing the width of each glyph in the font. Each entry tells the OS how much to increment the current horizontal position (usually RastPort.cp_X). For reverse path fonts, these values can be negative.

This is a pointer to an array of "kerning" values. As it is used here, the term "kerning" is unorthodox. On the Amiga, kerning refers to the number pixels to leave blank before rendering a glyph. The normal typographical definition of the word refers to the number of pixels to back up before rendering the current glyph and is usually associated with a specific pair of glyphs rather than one particular glyph.

For each glyph the system renders, it has to do several things:

  1. Get the value from the kerning table that corresponds to this glyph and begin the rendering that number of pixels to the right.
  2. Find this glyph's bitmap using the CharLoc table and blit the glyph to the rastport.
  3. If this is a proportional font, look in the spacing table and figure how many pixels to advance the rastport's horizontal position. For a monospaced font, the horizontal position advance comes from the TextFont's tf_XSize field.

When the system opens a new font, it creates an extension to the TextFont structure called the TextFontExtension. This extension is important because it contains a pointer to the font's tag list, which is where the system keeps the font's TA_DeviceDPI values. The TextFont's tf_Message.mn_ReplyPort field contains a pointer to the TextFontExtension structure (the <graphics/text.h> include file #defines tf_Message.mn_ReplyPort as tf_Extension). The only field of interest in the TextFontExtension structure is:

struct TagItem *tfe_Tags; /* Text Tags for the font */

which points to the font's tag list. Before accessing the tag list, an application should make sure that this font has a corresponding TextFontExtension. The ExtendFont() function will return a value of TRUE if this font already has an extension or ExtendFont() was able to create an extension for this font.

But What About Color Fonts?

When the Amiga loads a color font, it has to account for more information than will fit into the TextFont structures. For color fonts, the Amiga uses a superset of the TextFont structure called the ColorTextFont structure (defined in <graphics/text.h>):

struct ColorTextFont {
    struct TextFont ctf_TF;
    UWORD   ctf_Flags;      /* extended flags */
    UBYTE   ctf_Depth;      /* number of bit planes */
    UBYTE   ctf_FgColor;    /* color that is remapped to FgPen */
    UBYTE   ctf_Low;        /* lowest color represented here */
    UBYTE   ctf_High;       /* highest color represented here */
    UBYTE   ctf_PlanePick;      /* PlanePick ala Images */
    UBYTE   ctf_PlaneOnOff;     /* PlaneOnOff ala Images */
    struct ColorFontColors *ctf_ColorFontColors; /* colors for font */
    APTR    ctf_CharData[8];    /*pointers to bit planes ala tf_CharData */
};

The ctf_TF field is the TextFont structure described in the previous section. There are two minor differences between the data stored in a color font's TextFont structure and an ordinary TextFont structure. The first is that the color font's TextFont.tf_Style field has the FSF_COLORFONT bit set. The other difference is that the bitmap that TextFont.tf_CharData points to can be a multi-plane bitmap.

The ctf_Flags field is a bitfield that supports the following flags:

CT_COLORFONT
The color map for this font contains colors specified by the designer.
CT_GREYFONT
The colors for this font describe evenly stepped gray shades from low to high.

The ctf_Depth field contains the bitplane depth of this font's bitmap.

The ctf_FgColor contains the color that will be dynamically remapped during output by changing ctf_FgColor to RastPort.FgPen. This field allows a ColorTextFont to contain color outlines, shadows, etc. while also containing a predominant color that can be changed by the user. If the font does not have a predominant color, ctf_FgColor is 0xFF. For example, given a color font that has a blue and red outline and a white center, the person designing the font can set ctf_FgColor equal to white. Then when the font is used in a paint package that supports color fonts, the white will change to the current foreground color.

The fields ctf_Low and ctf_High contain the lowest and highest color values in the ColorTextFont. For example, a four bitplane color font can have sixteen colors, but the font may use only nine of those colors, thus ctf_Low=0 and ctf_High=8. The most important use of these colors is for defining the boundaries of a gray scale font. If the font uses less than the total number of colors around but needs white as the lowest and black as the highest level of gray, the boundaries would have to be defined in order for the font to be rendered correctly. Defaults for these values should be the lowest and the highest values for the given number of bitplanes.

The ctf_PlanePick and ctf_PlaneOnOff contain information for saving space in memory for some types of ColorTextFont structures. The ctf_PlanePick field contains information about where each plane of data will be rendered in a given bitmap. The ctf_PlaneOnOff field contains information about planes that are not used to render a plane of font data. If ctf_PlaneOnOff contains a zero bit for a given plane, that bitplane is cleared. If ctf_PlaneOnOff contains a set bit for a given plane, that bitplane is filled. For more information on how the ctf_PlaneOnOff and ctf_PlanePick fields work see Specifying the Colors of a Bob.

The ctf_ColorFontColors field contains a pointer to a ColorFontColors structure:

struct ColorFontColors {
    UWORD   cfc_Reserved;       /* *must* be zero */
    UWORD   cfc_Count;          /* number of entries in cfc_ColorTable */
    UWORD  *cfc_ColorTable;     /* 4 bit per component color map packed xRGB */
};

Which specifies the colors used by this font. The ColorFontColors cfc_Count field contains the number of colors defined in this structure. Each color is defined as a single, UWORD entry in the cfc_ColorTable. For each entry in cfc_ColorTable, the lowest four bits make up the blue element, the next four bits the green element, the next four bits the red element, and the upper four bits should be masked out.

The ctf_CharData[] fields is an array of pointers to each of the bitplanes of the color font data.

Composition of a Bitmap Font on Disk

For each Amiga bitmap font stored on disk (normally in the FONTS: assign directory), there is a corresponding ".font" file, a directory, and within that directory, a series of files bearing numeric names. For example, for the font Sapphire, within FONTS:, there is a file called sapphire.font, a directory called Sapphire, and within the directory Sapphire are the files 14 and 19.

For a bitmap font (including color fonts), the ".font" file is a FontContentsHeader structure:

struct FontContentsHeader {
    UWORD   fch_FileID;           /* FCH_ID */
    UWORD   fch_NumEntries;       /* the number of FontContents elements */
    struct FontContents fch_FC[]; /* or struct TFontContents fch_TFC[]; */
};
 
#define MAXFONTPATH 256

Where the fch_FileID field can be be either:

FCH_ID (0x0f00)
This FontContentsHeader uses FontContents structures to describe the available sizes of this font.
TFCH_ID (0x0f02)
This FontContentsHeader uses TFontContents structures to describe the available sizes of this font.

The fch_FileID can also be equal to 0x0F03, but that is only for scalable outline fonts.

The FontContents structure:

struct FontContents {
    char    fc_FileName[MAXFONTPATH];
    UWORD   fc_YSize;
    UBYTE   fc_Style;
    UBYTE   fc_Flags;
};

describes one of the sizes of this font that is available to the system as a designed font size. For each FontContents structure, there should be a corresponding font descriptor file in this font's directory that contains data for this size font. The FontContents fields correspond to the similarly named field in the TextFont structure.

The TFontContents structure is almost the same as the FontContents structure except that it allows the OS to store tag value pairs in the extra space not used by the file name. Currently, this allows the OS to preserve the X and Y DPI (TA_DeviceDPI) values for a font.

struct TFontContents {
    char    tfc_FileName[MAXFONTPATH-2];
    UWORD   tfc_TagCount;       /* including the TAG_END tag */
    /*
     *  if tfc_TagCount is non-zero, tfc_FileName is overlaid with
     *  Text Tags starting at:  (struct TagItem *)
     *      &amp;tfc_FileName[MAXFONTPATH-(tfc_TagCount*sizeof(struct TagItem))]
     */
    UWORD   tfc_YSize;
    UBYTE   tfc_Style;
    UBYTE   tfc_Flags;
};

The fch_NumEntries contains the number of font sizes (and the number of FontContents or TFontContents structures) that this ".font" file describes. The fch_FC[] is the array of FontContents or TFontContents structures that describe this font.

For each font size described in a FontContents (or TFontContents) structure, there is a corresponding file in that font's directory whose name is its size. For example, for the font size Sapphire-19, there is a file in the Sapphire directory called 19. That file is basically a DiskFontHeader disguised as a loadable DOS hunk and is known as a font descriptor file. This allows the diskfont.library to use the dos.library to load the module just like it was a hunk of relocatable 680x0 instructions. It even contains two instructions before the real DiskFontHeader structure that will cause the 680x0 to stop running the DiskFontHeader if it does inadvertently get executed.

#define  MAXFONTNAME    32      /* font name including &quot;.font\0&quot; */
 
struct DiskFontHeader {
    /* the following 8 bytes are not actually considered a part of the  */
    /* DiskFontHeader, but immediately precede it. The NextSegment is   */
    /* supplied by the linker/loader, and the ReturnCode is the code    */
    /* at the beginning of the font in case someone runs it...          */
    /*   ULONG dfh_NextSegment;                    actually a BPTR      */
    /*   ULONG dfh_ReturnCode;                     MOVEQ #0,D0 : RTS    */
    /* here then is the official start of the DiskFontHeader...         */
    struct Node dfh_DF;            /* node to link disk fonts */
    UWORD   dfh_FileID;            /* DFH_ID */
    UWORD   dfh_Revision;          /* the font revision */
    LONG    dfh_Segment;           /* the segment address when loaded */
    char    dfh_Name[MAXFONTNAME]; /* the font name (null terminated) */
    struct TextFont dfh_TF;        /* loaded TextFont structure */
};
 
/* unfortunately, this needs to be explicitly typed */
/* used only if dfh_TF.tf_Style FSB_TAGGED bit is set */
#define dfh_TagList     dfh_Segment     /* destroyed during loading */

The dfh_DF field is an Exec Node structure that the diskfont library uses to link together the fonts it has loaded. The dfh_FileID field contains the file type, which currently is DFH_ID (defined in <libraries/diskfont.h>). The dfh_Revision field contains a revision number for this font. The dfh_Segment field will contain the segment address when the font is loaded. The dfh_FontName field will contain the font's name after the font descriptor is LoadSeg()'ed. The last field, dfh_TextFont is a TextFont structure (or ColorTextFont structure) as described in the previous section. The following is a complete example of a proportional, bitmap font.

* A sparse (but complete) sample font.
*
* 1.  Assemble this file (assumed to have been saved as "suits8.asm"). For example, if you have the
* CAPE 680x0 assembler, and you have assigned "include:" to the directory containing your include
* files, use:
*          CAsm -a "suits8.asm" -o "suits8.o" -i "include:"
*
*     Link "suits8.o".  For example, if you have Lattice, use:
*          BLink from "suits8.o" to "suits8"
*
* 2.  Create the subdirectory "Fonts:suits".  Copy the file "suits8" (created in step 1)
*     to "Fonts:suits/8".
*
* 3.  Create a font contents file for the font.  You can do this by two methods:
*
*   a. Run the program "System/FixFonts" which will create the file "Fonts:suits.font"
*      automatically.
*   b. Use the NewFontContents() call in the diskfont library to create a FontContentsHeader
*      structure, which can be saved in the Fonts: directory as "suits.font".   This is essentially
*      what FixFonts does.
*
* The next word contains the font YSize; in this case, 0x0008.
*
* The next byte contains the font Flags, in this case 0x00.
*
* The last byte contains the font characteristics, in this case 0x60.  This says it is a disk-based
* font (bit 1 set) and the font has been removed (bit 7 set), saying that the font is not currently
* resident.
*
* Summary of suits.font file:
*
* Name: fch_FileID fch_NumEntries fc_FileName     fc_YSize fc_Flags fc_Style
* Size: word       word     MAXFONTPATH bytes word     byte     byte
* Hex:  0f00       0001     73756974732F3800  0008     00       60
* ASCII:            s u i t s / 8 \0
*
* The correct length of a font file may be calculated with this formula:
* length = ((number of font contents entries) * (MAXFONTPATH+4)) + 4. In this case (one entry),
* this becomes (MAXFONTPATH + 8) or 264.
*
* To try out this example font, do the following.  Use the Fonts Preferences editor or a
* program that allows the user to select fonts.  Choose the "suits" font in size 8.
* This example font defines ASCII characters 'a' 'b' 'c' and 'd' only. All other characters
* map to a rectangle, meaning "character unknown".

    INCLUDE  "exec/types.i"
    INCLUDE  "exec/nodes.i"
    INCLUDE  "libraries/diskfont.i"

    MOVEQ     #-1,D0      ; Provide an easy exit in case this file is
    RTS                   ; "Run" instead of merely loaded.

    DC.L      0           ; ln_Succ     * These five entries comprise a Node structure,
    DC.L      0           ; ln_Pred     * used by the system to link disk fonts into a
    DC.B      NT_FONT     ; ln_Type     * list.  See the definition of the "DiskFontHeader"
    DC.B      0           ; ln_Pri      * structure in the "libraries/diskfont.i" include
    DC.L      fontName    ; ln_Name     * file for more information.

    DC.W      DFH_ID      ; FileID
    DC.W      1           ; Revision
    DC.L      0           ; Segment

* The next MAXFONTNAME bytes are a placeholder.  The name of the font contents file (e.g.
* "suits.font") will be copied here after this font descriptor is LoadSeg-ed into memory.  The Name
* field could have been left blank, but inserting the font name and size (or style) allows one to
* tell something about the font by using "Type OPT H" on the file.

fontName:
    DC.B      "suits8"    ; Name

* If your assembler needs an absolute value in place of the "length" variable, simply count the
* number of characters in Name and use that.

length  EQU     *-fontName              ; Assembler calculates Name's length.
        DCB.B   MAXFONTNAME-length,0    ; Padding of null characters.

font:
    DC.L    0               ; ln_Succ       * The rest of the information is a TextFont
    DC.L    0               ; ln_Pred       * structure.  See the "graphics/text.i" include
    DC.B    NT_FONT         ; ln_Type       * file for more information.
    DC.B    0               ; ln_Pri
    DC.L    fontName        ; ln_Name
    DC.L    0               ; mn_ReplyPort
    DC.W    0               ; (Reserved for 1.4 system use.)
    DC.W    8               ; tf_YSize
    DC.B    0               ; tf_Style
    DC.B    FPF_DESIGNED!FPF_PROPORTIONAL!FPF_DISKFONT ; tf_Flags
    DC.W    14              ; tf_XSize
    DC.W    6               ; tf_Baseline  <----* tf_Baseline must be no greater than
    DC.W    1               ; tf_BoldSmear      * tf_YSize-1, otherwise algorithmically-
    DC.W    0               ; tf_Accessors      * generated styles (italic in particular)
    DC.B    97              ; tf_LoChar         * can corrupt system memory.
    DC.B    100             ; tf_HiChar
    DC.L    fontData        ; tf_CharData
    DC.W    8               ; tf_Modulo  <- * add this to the data pointer to go from from
                            ;               * one row of a character to the next row of it.
    DC.L    fontLoc         ; tf_CharLoc <---------------- * bit position in the font data
    DC.L    fontSpace       ; tf_CharSpace                 * at which the character begins.
    DC.L    fontKern        ; tf_CharKern

* The four characters of this font define the four playing-card suit symbols.  The heart, club,
* diamond, and spade map to the lower-case ASCII characters 'a', 'b', 'c', and 'd' respectively The
* fifth entry in the table is the character to be output when there is no entry defined in the
* character set for the requested ASCII value.  Font data is bit-packed edge to edge to save space.

fontData:                             ;   97 (a)       98 (b)       99 (c)   100 (d)      255
    DC.W  $071C0,$08040,$070FF,$0F000 ; <        X        X    X        X        >
    DC.W  $0FBE3,$0E0E0,$0F8C0,$03000 ;  .@@@...@@@.  .....@.....  ...@...  ....@@@....  @@@@@@@@@@@@
    DC.W  $07FCF,$0F9F3,$026C0,$03000 ;  @@@@@.@@@@@  ...@@@@@...  ..@@@..  ...@@@@@...  @@........@@
    DC.W  $03F9F,$0FFFF,$0FFC0,$03000 ;  .@@@@@@@@@.  .@@@@@@@@@.  .@@@@@.  .@@..@..@@.  @@........@@
    DC.W  $01F0E,$0B9F3,$026C0,$03000 ;  ..@@@@@@@..  @@@@@@@@@@@  @@@@@@@  @@@@@@@@@@@  @@........@@
    DC.W  $00E00,$080E0,$020C0,$03000 ;  ...@@@@@...  .@@@.@.@@@.  .@@@@@.  .@@..@..@@.  @@........@@
    DC.W  $00403,$0E040,$0F8FF,$0F000 ;  ....@@@....  .....@.....  ..@@@..  .....@.....  @@........@@
    DC.W  $00000,$00000,$00000,$00000 ;  .....@.....  ...@@@@@...  ...@...  ...@@@@@...  @@@@@@@@@@@@
    DC.W  $00000,$00000,$00000,$00000 ;  ...........  ...........  .......  ...........  ............

fontLoc:                 ; The fontLoc information is used to "unpack" the fontData. Each
    DC.L    $00000000B   ; pair of words specifies how the characters are bit-packed.  For
    DC.L    $0000B000B   ; example, the first character starts at bit position 0x0000, and
    DC.L    $000160007   ; is 0x000B (11) bits wide.  The second character starts at bit
    DC.L    $0001D000B   ; position 0x000B and is 0x000B bits wide, and so on.  This tells
    DC.L    $00028000C   ; the font handler how to unpack the bits from the array.

fontSpace:                        ; fontSpace array:  Use a space this wide to contain this
    DC.W    000012,000012         ; character when it is printed.  For reverse-path fonts
    DC.W    000008,000012,000013  ;  these values would be small or negative.

fontKern:                         ; fontKern array:  Place a space this wide after the
    DC.W    000001,000001         ; corresponding character to separate it from the following
    DC.W    000001,000001,000001  ; character.  For reverse-path fonts these values would be large
                                  ; negative numbers, approximately the width of the characters.
fontEnd:
    END

Function Reference

The following are brief descriptions of the Graphics and Diskfont library functions that deal with text. See the SDK for details on each function call.

Function Description
Text() Render a text string to a RastPort.
SetFont() Set a RastPort's font.
AskFont() Get the TextAttr for a RastPort's font.
OpenFont() Open a font currently in the system font list.
CloseFont() Close a font.
AddFont() Add a font to the system list.
RemFont() Remove a font from the system list.
StripFont() Remove the tf_Extension from a font.
WeighTAMatch() Get a measure of how well two fonts match.
ClearScreen() Clear RastPort from the current position to the end of the RastPort.
ClearEOL() Clear RastPort from the current position to the end of the line.
AskSoftStyle() Get the soft style bits of a RastPort's font.
SetSoftStyle() Set the soft style bits of a RastPort's font.
TextLength() Determine the horizontal raster length of a text string using the current RastPort settings.
TextExtent() Determine the raster extent (along the X and Y axes) of a text string using the current RastPort settings.
FontExtent() Fill in a TextExtent structure with the bounding box for the characters in the specified font.
TextFit() Count the number of characters in a given string that will fit into a given bounds, using the current RastPort settings.