TTEngine explained step by step

1. How to print a text with TTEngine.

1.1. Open TTEngine.

TTEngine is just an AmigaOS shared library so it should be opened the same way every shared library is. You should include TTEngine header file first (I assume you use C or C++ programming language). TTEngine has a few header files for different compilers. Supported compilers are GCC, VBCC, SAS/C, Storm C, Maxon C++ and Dice C. To improve portability and programmer comfort <proto/ttengine.h> file automatically include headers for your compiler. So you can just put

#include <proto/ttengine.h>

into your program includes. Then you should define the library base. Some compilers (GCC for example) do it automatically for you. The library base is named TTEngineBase and is of type struct Library*. There are no user accesible fields in the base, it should be treated as a black-box. If your compiler does not create library bases you can define TTEngine library base as follows:

struct Library *TTEngineBase;

Now you are ready to open TTEngine. As for every shared library you use OpenLibrary() from exec.library. Do not forget to check if TTEngine was opened succesfully. Your code will be similar to this:

TTEngineBase = OpenLibrary("ttengine.library", 5);
if (!TTEngineBase) Printf("Can't open TTEngine!\n");

After this step you are ready to use all the TTEngine functions.

1.2. Load a font.

Of course you have to load TrueType font before printing any text. TT_OpenFont() will do this for you. It is a taglist driven function. Tags and taglists are very common to AmigaOS, so you should be familiar with them. You specify font attributes by tags. Font name is probably the most important. There are two possible ways to specify font name. Firstly you can give it directly as a path to font file using TT_FontFile tag. It is especially useful if your application uses some specific font containing for example musical notes, electronics symbols etc. It is however not recommended for general use, because it forces your application user to keep the font in some fixed directory with fixed name. Better way is specify font file by family and style and allow TTEngine to find best matching font in the database. TT_FamilyTable tag specifies a table containing some font family names. Why table instead of just one name? It is possible user has not a font you are requesting. You can provide a number of fallback families, these will be tried in turn. Assume you set following family table:

STRPTR my_table[] = {"Tahoma", "Arial", "sans-serif", "default", NULL};

TTEngine try to load Tahoma font. If it can't be loaded, Arial will be tried. If Arial fails too, TTEngine will try to load a font marked as "default sans-serif" in the database. The last resort is a font marked as "default". Null pointer at the end of table is compulsory as it marks the table end, so you need not specify how many families are in the table. Family however does not specify the font precisely, as it contains fonts of different weight and style. Additional two tags can be used for this. TT_FontWeight as its name says specifies requested weight and its value can be a number from 1 to 1000. This is a way Cascading Style Sheets use. TT_FontWeight_Normal and TT_FontWeight_Bold are the shortcuts for common weights. Fonts have also a number of styles, typically regular style and italic (sometimes called oblique) one. The style can be selected with TT_FontStyle tag and TT_FontStyle_Regular and TT_FontStyle_Italic values. The last important thing is to specify font size. As you can guess TT_FontSize tag does the thing. Just give it the font height in pixels.

All the described tags have reasonable default values (well, except TT_FontFile, but there is no reasonable default for this...). TT_FamilyTable defaults to {"default", NULL}; TT_FontWeight to TT_FontWeight_Normal, TT_FontStyle to TT_FontStyle_Regular and TT_FontSize to 14 pixels. So you can safely call TT_OpenFont() like this:

font = TT_OpenFont(TAG_END);

It will open default font, regular style, normal weight, 14 pixels size. Let's see some more common example. We try to open Georgia font with some fallbacks, bold italic, 48 pixels.

table = {"Georgia", "Times", "serif", "default", NULL};

font = TT_OpenFont(
  TT_FamilyTable, (ULONG)table,
  TT_FontStyle, TT_FontStyle_Italic,
  TT_FontWeight, TT_FontWeight_Bold,
  TT_FontSize, 48,
TAG_END);

Here is another example where font is opened from file specified directly. Of course TT_FontStyle and TT_FontWeight make no sense here since style and weight are used to select font file, not to transform font glyphs.

font = TT_OpenFont(
  TT_FontFile, (ULONG)"PROGDIR:fonts/SpecialSymbols.ttf",
  TT_FontSize, 37,
TAG_END);

Font handle returned by TT_OpenFont is of type APTR and should be treated as a black-box value containing no public fields. Remember to check if the value returned by TT_OpenFont() is not NULL. The function may fail for a few reasons. It may run out of memory, TTEngine may be unable to find matching font in the database, font file may be missing or corrupted.

1.3. Render to a window.

Now, when you have a font loaded you can try to print some text. The most common case is printing into AmigaOS window. I assume here you know how to open a window and how to handle it. Rendering to other targets such as screen or separate RastPort will be explained later. Most of TTEngine functions use RastPort as their first parameter. As you know window RastPort can be accessed as window->RPort. Our next step will be setting the font for use with window RastPort. It is done by TT_SetFont() call:

TT_SetFont(window->RPort, font);

TT_SetFont() returns boolean value of success (TRUE or FALSE). It may fail because it allocates a bit of memory, so for your application reliability you should check returned value.

Now you should specify target window for TTEngine using TT_SetAttrs():

TT_SetAttrs(window->RPort,
  TT_Window, (ULONG)window,
TAG_END);

Where in the window our text will be printed? TTEngine uses RastPort settings whereever possible. Text is always drawn at current RastPort pen position so you should just Move() where you want. Then you can render the text with TT_Text():

Move(window->RPort, 20, 50);
TT_Text(window->RPort, "Hello world!", 12);

You have probably noticed TTEngine functions are very similar to graphics.library text functions. This is intentional, for example TT_Text() has the same prototype as Text(), and takes its parameters in the same processor registers. If you have any expirience with graphics.library and text, you will find TTEngine very easy to use.

1.4. RastPort cleanup.

TTEngine rendering target is always a RastPort. It can be window RastPort, screen RastPort, or a separate one. For every RastPort TTEngine is used with, it creates rendering environment. The environment for a RastPort is created when TT_SetAttrs() or TT_SetFont() is called the first time for this RastPort. All subsequent calls to any TTEngine function taking RastPort as a parameter, trigger a search for an environment of this RastPort (all the environments are linked in a list). Only TT_SetAttrs() and TT_SetFont() are able to create new environment if one does not exist. When you've finished all TTEngine rendering to a RastPort you should dispose the rendering environment for this RastPort. Why? There are two reasons to do this.Rendering environment can't be disposed automatically. Even if I try to patch the system to track RastPort life. There is no function to create or dispose a RastPort, it can be as well created "by hand". That is why TTEngine provides a function for disposing rendering environment called TT_DoneRastPort(). You should call it after the last text rendering operation to the RastPort, but before the RastPort gets disposed. If you are working with window, call TT_DoneRastPort() just before CloseWindow().

TT_DoneRastPort(window->RPort);
CloseWindow(window);

1.5. Closing a font.

When you do not need a font anymore, you should close it (note that you can use one font in many RastPorts at once, there is no need to open a font separately for every RastPort). Use TT_CloseFont(), it is very simple:

TT_CloseFont(font);

1.6. Close TTEngine.

TTEngine should be closed after use, as every shared library does. CloseLibrary() from exec will do this for you. Do not forget to close all opened fonts before.

CloseLibrary(TTEngineBase);

2. I want to do some more.

2.1. Changing text colour.

There are two ways to change text foreground and background colours with TTEngine. The first method is more general and is based on RastPort and its pens. Every RastPort has two pens. APen is the foreground pen and is used for many operations like drawing pixels, lines, rectangles, flood fill etc. BPen is the background pen used for example in JAM2 mode of text output. TTEngine uses RastPort pens as default rendering colours. Using RastPort pens is very simple if you want to output text using system pens. For example let's print some text (without background) using pen 1 (in typical Workbench palette it is black):

SetDrMd(window->RPort, JAM1);
SetAPen(window->RPort, 1);
TT_Text(window->RPort, "[maybe] black text", 18);
Color example 1

In the code above I assumed font is already opened and set for the RastPort. Things get a bit complicated when you want specific RGB colour, not just a system pen. You have to remember TTEngine can work in environment with very few pens available, so your request for red colour can end up in white on traditional, 4 colours Workbench screen. On custom screen you can just set all the palette to the colours you want, and use them, setting pens with SetAPen(). On public screen you can try to obtain best matched pen as shared, or obtain any pen as exclusive and change its colour to the desired one. The second approach can fail if there are no free pens on the screen.

/* obtain pen as shared */

LONG red_pen;

red_pen = ObtainBestPen(window->WScreen->ViewPort.ColorMap,
  0xFFFFFFFF, 0x00000000, 0x00000000, TAG_END);
SetAPen(window->RPort, red_pen);
TT_Text(window->RPort, "more or less red text", 21);
/* ... */
TT_DoneRastPort(window->RastPort);
ReleasePen(window->WScreen->ViewPort.ColorMap, red_pen);
CloseWindow(window);
Color example 2

/* obtain pen as exclusive */

LONG red_pen;

red_pen = ObtainPen(window->WScreen->ViewPort.ColorMap, -1,
  0xFFFFFFFF, 0x00000000, 0x00000000, PEN_EXCLUSIVE);
if (red_pen != -1)
  {
    SetAPen(window->RPort, red_pen);
    TT_Text(window->RPort, "exactly red text", 16);
  }
else PutStr("No free pens.\n");
/* ... */
TT_DoneRastPort(window->RastPort);
ReleasePen(window->WScreen->ViewPort.ColorMap, red_pen);
CloseWindow(window);
Color example 3

As you can see, it is not very comfortable, however it will work on every screen from two colours to 32 bit. But on the other hand you can be reported that there are no free pens while working on 24-bit screen, which is a bit imprecise... That is why TTEngine allows for another way of specifing text colours via TT_SetAttrs() using TT_Foreground and TT_Background tags. Note however this alternative way works only for RGB RastPorts, it means 15 or more bits depth. For palette based RastPorts these tags are ignored. Tags take 32-bit colour value defined as 0RGB, most significant byte has to be zero, next three bytes contain red, green and blue components of the colour. Here is an example:

TT_SetAttrs(window->RastPort,
  TT_Foreground, 0x0060FF80,
  TT_Background, 0x00000000,
TAG_END);
SetDrMd(window->RastPort, JAM2);
TT_Text(window->RastPort, "neon green on black back", 24);
Color example 4

You may want to return TTEngine text colour control back to RastPort and its pens. It can be done with special values for background and foreground tags as shown below:

TT_SetAttrs(window->RastPort,
  TT_Foreground, TT_Foreground_UseRastPort,
  TT_Background, TT_Background_UseRastPort,
TAG_END);

2.2. Changing draw mode

TTEngine supports and uses all RastPort drawing modes and its combinations. These are: Here are examples of JAM1 and JAM2 modes also combined with INVERSVID. The window backgound is a gray checkerboard. Foreground colour is yellow, background colour is dark blue.

JAM1 This is an example of JAM1 draw mode. Yellow glyphs are smoothly laid on the checkerboard. JAM2 This is JAM2. Glyphs are rendered over a rectangle of RastPort background colour.
JAM1 combined with INVERSVID. Foreground and transparent background are reversed. JAM2 combined with INVERSVID. Background and foreground are reversed.

RastPort draw modes are set by SetDrMd() function as usual. TTEngine has however another interesting draw mode not supported by graphics.library. This is transparency. Transparency as well as smoothing works for RGB RastPorts only. For palette based RastPorts transparency setting is ignored. As transparency is TTEngine specific, TT_SetAttrs() should be used to control it with TT_Transparency tag. It takes an unsigned byte value (0 - 255, higher bytes are truncated). Zero means no transparency, glyphs are fully opaque (0 is the default setting). Higher values mean more transparency, with transparency of 255 text is almost invisible (well, you probably need 24-bit screen to see something with TT_Transparency set to 255). Of course transparency can be freely combined with RastPort draw modes. If JAM2 is selected, transparency is applied to both foreground and background colours. Look at examples below.

JAM1JAM2Transparency
JAM1, transparency 51 (20%) JAM2, transparency 51 (20%) 20% (51, 0x33)
JAM1, transparency 128 (50%) JAM2, transparency 128 (50%) 50% (128, 0x80)
JAM1, transparency 205 (80%) JAM2, transparency 205 (80%) 80% (205, 0xCD)

A little example code below shows how one can set JAM2 mode with INVERSVID and 25% transparency:

SetDrMd(window->RastPort, JAM2 | INVERSVID);
TT_SetAttrs(window->RastPort, TT_Transparency, 0x40, TAG_END);

2.3. How long my text is?

You may want to know the length of a text in pixels. It can be useful if doing any text layout, aligning to the right, centering or justifying. TT_TextLength() can be used for obtaining pixel length of a string. It is used the same as graphics.library TextLength(). Returned value is horizontal RastPort pen advance in pixels. Note that for some fonts (especially italic) some pixels may render before text start point and some after text end point. For in-depth discussion of text metrics see 3.2. Text metrics. TT_TextLength() however is precise enough for typical applications. Here is an example of use:

LONG length;

length = TT_TextLength(window->RastPort, "Tell me how long is it.", 25);

If you want to calculate text pixel length before you open your window, you can use screen RastPort for calculation. Do not forget to call TT_DoneRastPort() after calculations. The example below shows how can you measure the text before you open a window on Workbench screen:

struct Screen *wb;
LONG textlen;

wb = LockPubScreen(NULL);
TT_SetFont(&wb->RastPort, font);
textlen = TT_TextLength(&wb->RastPort, "Some text", 9);
TT_DoneRastPort(&wb->RastPort);
UnlockPubScreen(NULL, wb);

You do not need to hold the screen lock until a window is opened on it because text length does not depend on RastPort properties (of course you should use the same font for length calculation and rendering...).

2.4. Controlling antialias.

Antialias is one of TTEngine key features. Note it works on RGB RastPorts only. TTEngine antialias is much better compared to bare FreeType ports because it is brightness based, not just an average between background and foreground. Actual background pixel colour (or background colour in JAM2) and foreground colour are decomposed to a brightness and a hue. Both the components are averaged separately, then translated back to RGB domain. Sometimes however it may be preferrable to switch the smoothing off. TT_Antialias tag of TT_SetAttrs() function can be used for this. The tag has three possible values described below:
Times New Roman 8

Times New Roman 8 pixels not smoothed
Times New Roman 13

Times New Roman 13 pixels not smoothed
Times New Roman 18

Times New Roman 18 pixels not smoothed
Times New Roman 8 smoothed

Times New Roman 8 pixels smoothed
Times New Roman 13 smoothed

Times New Roman 13 pixels smoothed
Times New Roman 18 smoothed

Times New Roman 18 pixels smoothed

Comparision of different sizes of Times New Roman antialiased or not.