[← Home](../README.md) · [Graphics](README.md)
# Text and Fonts — TextFont, TextAttr, Rendering
## Overview
AmigaOS renders all text through **bitmap fonts** — each glyph is a pre-drawn grid of pixels stored in a single monolithic bitmap strip. The `graphics.library` `Text()` function blits these glyph pixels to the screen via the Blitter, making text rendering essentially free in CPU terms. This was a deliberate design: a 7 MHz 68000 could not rasterize TrueType curves at interactive speeds, so the Amiga pre-rendered everything.
Two fonts live in ROM — **topaz 8** and **topaz 9** — available instantly at boot with no disk access. Every other font (helvetica, times, courier, garnet, sapphire, third-party) lives on disk under the `FONTS:` assign and must be loaded via [diskfont.library](../11_libraries/diskfont.md).
```mermaid
flowchart TD
subgraph "Font Sources"
ROM["ROM Fonts
(topaz 8, topaz 9)"]
DISK["Disk Fonts
(FONTS: directory)"]
OUTLINE["Compugraphic Outlines
(OS 2.0+)"]
end
ROM -->|"OpenFont"| TF["struct TextFont"]
DISK -->|"OpenDiskFont"| TF
OUTLINE -->|"OpenDiskFont\n(auto-rasterized)"| TF
TF -->|"SetFont(rp, font)"| RP["RastPort"]
RP -->|"Text(rp, str, len)"| BM["BitMap\n(Blitter blits glyph pixels)"]
STYLE["Algorithmic Styles\n(Bold/Italic/Underline)"] -->|"SetSoftStyle"| RP
COLOR["Color Fonts\n(OS 3.0+, up to 8 bitplanes)"] --> TF
style TF fill:#e8f4fd,stroke:#2196f3,color:#333
style BM fill:#c8e6c9,stroke:#2e7d32,color:#333
style STYLE fill:#fff9c4,stroke:#f9a825,color:#333
style COLOR fill:#fff9c4,stroke:#f9a825,color:#333
```
---
## Key Structures
```c
/* graphics/text.h — NDK39 */
/* Font request — describes what you want: */
struct TextAttr {
STRPTR ta_Name; /* font name, e.g. "topaz.font" */
UWORD ta_YSize; /* desired height in pixels */
UBYTE ta_Style; /* FSF_BOLD, FSF_ITALIC, FSF_UNDERLINED */
UBYTE ta_Flags; /* FPF_ROMFONT, FPF_DISKFONT, FPF_PROPORTIONAL, etc. */
};
/* Loaded font instance: */
struct TextFont {
struct Message tf_Message; /* standard message header */
UWORD tf_YSize; /* actual font height */
UBYTE tf_Style; /* styles this font has built-in */
UBYTE tf_Flags; /* FPF_ROMFONT, FPF_DISKFONT, FPF_PROPORTIONAL, etc. */
UWORD tf_XSize; /* nominal character width */
UWORD tf_Baseline; /* pixels from top to baseline */
UWORD tf_BoldSmear; /* extra pixels for algorithmic bold */
UWORD tf_Accessors; /* current open count */
UBYTE tf_LoChar; /* first character code (usually 32) */
UBYTE tf_HiChar; /* last character code (usually 127 or 255) */
APTR tf_CharData; /* bitmap strip containing all glyphs */
UWORD tf_Modulo; /* bytes per row of font bitmap */
APTR tf_CharLoc; /* location table: offset + width per char */
APTR tf_CharSpace; /* proportional spacing table (NULL = fixed) */
APTR tf_CharKern; /* kerning adjustment table (NULL = none) */
};
```
### Field Reference
| Field | Purpose | Important Notes |
|-------|---------|----------------|
| `tf_YSize` | Font height in pixels | Amiga "point size" = pixel height, not typographic points |
| `tf_Baseline` | Distance from top to baseline | Position text here — not at the top of the cell |
| `tf_BoldSmear` | Pixels to smear for algorithmic bold | Usually 1; affects rendered width |
| `tf_LoChar` / `tf_HiChar` | Character range (typically 32–127 or 32–255) | Characters outside range render as default glyph |
| `tf_CharData` | Pointer to the monolithic glyph bitmap strip | 1 bitplane for monochrome; multiple for ColorFont |
| `tf_CharLoc` | Array of `{offset, width}` pairs, one per character | `ULONG` per char: high word = bit offset, low word = pixel width |
| `tf_CharSpace` | Per-character horizontal advance | `NULL` means fixed-width (`tf_XSize` used for all) |
| `tf_CharKern` | Per-character kerning offset | `NULL` means no kerning |
### Font Bitmap Layout
All characters are stored in a single bitmap strip. The `tf_CharLoc` table tells the renderer where each character starts:
```
tf_CharData bitmap:
┌──┬───┬──┬───┬──┬──┬───────────────────┐
│A │ B │C │ D │E │F │ ... all chars ... │
└──┴───┴──┴───┴──┴──┴───────────────────┘
tf_CharLoc[ch - tf_LoChar]:
bits 31–16 = bit offset into tf_CharData
bits 15–0 = character width in pixels
tf_CharSpace[ch - tf_LoChar]:
spacing advance (proportional fonts)
```
---
## Opening and Closing Fonts
### Font Opening Decision Guide
```mermaid
flowchart TD
NEED{"What font?"} -->|"topaz 8 or 9"| ROM["OpenFont()\nNo disk access needed"]
NEED -->|"Any other font"| DISK["OpenDiskFont()\nLoads from FONTS:"]
NEED -->|"User's screen font"| SCR["Use screen->RastPort.Font\nAlready open, no loading"]
DISK --> FOUND{"Found on disk?"}
FOUND -->|"Yes"| LOAD["Load and return TextFont*"]
FOUND -->|"No exact match"| SCALE{"Can scale existing?"}
SCALE -->|"Yes"| SCALED["Scale bitmap and return\nQuality may be poor"]
SCALE -->|"No"| FAIL["Return NULL"]
style ROM fill:#c8e6c9,stroke:#2e7d32,color:#333
style LOAD fill:#c8e6c9,stroke:#2e7d32,color:#333
style SCALED fill:#fff9c4,stroke:#f9a825,color:#333
style FAIL fill:#ffcdd2,stroke:#c62828,color:#333
```
### Code Examples
```c
/* ROM font (topaz — always available, no disk access): */
struct TextAttr ta = {"topaz.font", 8, 0, FPF_ROMFONT};
struct TextFont *font = OpenFont(&ta);
/* Disk font (requires diskfont.library): */
struct Library *DiskfontBase = OpenLibrary("diskfont.library", 0);
struct TextAttr ta2 = {"helvetica.font", 24, 0, FPF_DISKFONT};
struct TextFont *font2 = OpenDiskFont(&ta2);
/* Request with style (may get algorithmically generated): */
struct TextAttr ta3 = {"topaz.font", 8, FSF_BOLD, FPF_ROMFONT};
struct TextFont *bold = OpenFont(&ta3);
/* Use the screen's current font (respects user preferences): */
struct TextFont *scrFont = screen->RastPort.Font;
SetFont(rp, scrFont);
```
### Font Flags
| Flag | Meaning | Notes |
|------|---------|-------|
| `FPF_ROMFONT` | Font lives in ROM | Only topaz 8 and topaz 9 |
| `FPF_DISKFONT` | Font loaded from disk | Requires diskfont.library |
| `FPF_PROPORTIONAL` | Variable-width characters | `tf_CharSpace != NULL` |
| `FPF_DESIGNED` | Explicitly designed at this size | Set by the font designer; if absent, font was auto-scaled |
| `FPF_TALLDOT` | Designed for HiRes (640×200 NTSC non-interlaced) | Taller pixel aspect |
| `FPF_WIDEDOT` | Designed for LoRes interlaced (320×400 NTSC) | Wider pixel aspect |
| `FPF_REVPATH` | Right-to-left rendering | For Hebrew, Arabic |
---
## Rendering Text
### Draw Mode Effects on Text
| Draw Mode | Effect on Text | FgPen | BgPen |
|-----------|---------------|-------|-------|
| `JAM1` | Draw foreground only — transparent background | Used | Ignored |
| `JAM2` | Draw foreground + solid background | Used | Used |
| `COMPLEMENT` | XOR text with destination | Ignored | Ignored |
| `INVERSVID` | Invert video (swap fg/bg roles) | Modifier | Modifier |
```c
/* Position cursor then render: */
Move(rp, 10, 20 + rp->Font->tf_Baseline); /* baseline-relative! */
Text(rp, "Hello Amiga", 11);
/* Measure width before rendering (for centering/alignment): */
UWORD width = TextLength(rp, "Hello Amiga", 11);
/* Centre text: */
WORD centreX = (screenWidth - width) / 2;
Move(rp, centreX, 100);
Text(rp, "Hello Amiga", 11);
/* Pixel-perfect extent info: */
struct TextExtent te;
TextExtent(rp, "Hello", 5, &te);
/* te.te_Width = total pixel width */
/* te.te_Height = total pixel height */
/* te.te_Extent = bounding rectangle */
```
> [!IMPORTANT]
> `Text()` renders at the **current pen position**, which should be at the font's **baseline** — not the top of the character. The baseline offset is `font->tf_Baseline` pixels below the top. If you position at the top instead, descenders (g, j, p, q, y) will clip.
### Rendering API Reference
| Function | Library | Purpose |
|----------|---------|---------|
| `Text()` | graphics | Render `count` characters at current position |
| `TextLength()` | graphics | Return pixel width of a string (no rendering) |
| `TextExtent()` | graphics | Return width, height, and bounding rect |
| `TextFit()` | graphics | Max characters that fit in a given width |
| `SetFont()` | graphics | Set the RastPort's current font |
| `OpenFont()` | graphics | Open a font already in system memory |
| `CloseFont()` | graphics | Release a font (decrement accessor count) |
| `OpenDiskFont()` | diskfont | Load a font from disk (or system memory) |
| `AvailFonts()` | diskfont | Enumerate all available fonts |
---
## Algorithmic Styles
When a font doesn't have a built-in bold/italic variant, the Amiga can generate these styles **algorithmically** at render time:
```c
/* Style flags: */
#define FSF_UNDERLINED 0x01
#define FSF_BOLD 0x02
#define FSF_ITALIC 0x04
#define FSF_EXTENDED 0x08
/* Ask which styles this font supports algorithmically: */
UWORD supported = AskSoftStyle(rp);
/* Apply bold + italic: */
SetSoftStyle(rp, FSF_BOLD | FSF_ITALIC, supported);
Text(rp, "Bold Italic Text", 16);
/* Reset to normal: */
SetSoftStyle(rp, 0, supported);
```
| Style | Method | Visual Effect | Performance Impact |
|-------|--------|--------------|-------------------|
| Bold | Smear right by `tf_BoldSmear` pixels | Characters become slightly wider | Negligible — extra blit per scanline |
| Italic | Shear top scanlines right | Fixed-angle slant (\~12°) | Negligible — per-scanline offset |
| Underline | Draw 1-pixel line at descender level | Line below baseline | Negligible — one extra line draw |
| Extended | Widen each character by 50% | Extra-wide spacing | Moderate — affects spacing calculation |
> [!NOTE]
> Algorithmic styles work on **any** bitmap font. The `AskSoftStyle()` function returns which styles are supported — always check before applying. All four basic styles are supported by the standard renderer.
---
## Color Fonts (OS 3.0+)
OS 3.0 extended bitmap fonts with **multi-bitplane color support**. Instead of a single bitplane (1-bit: foreground or background), color fonts store up to 8 bitplanes per glyph, enabling 256-color text:
```c
/* graphics/text.h — NDK39 */
struct ColorTextFont {
struct TextFont ctf_TF; /* standard TextFont (must be first!) */
UWORD ctf_Flags; /* CT_COLORFONT, CT_GREYFONT */
UBYTE ctf_Depth; /* number of bitplanes (1–8) */
UBYTE ctf_FgColor; /* default foreground pen */
UBYTE ctf_Low; /* lowest color register used */
UBYTE ctf_High; /* highest color register used */
APTR ctf_PlanePick; /* plane selection for rendering */
APTR ctf_PlaneOnOff; /* plane on/off masks */
struct ColorFontColors *ctf_ColorTable;
APTR ctf_CharData[8]; /* per-plane glyph data pointers */
};
```
```
Traditional font: 1 bitplane → FgPen or BgPen only
Color font: up to 8 bitplanes → 256 colors per glyph pixel
Per-plane glyph storage:
┌─────────────────┐
│ ctf_CharData[0] │ bitplane 0 (LSB)
├─────────────────┤
│ ctf_CharData[1] │ bitplane 1
├─────────────────┤
│ ... │ ...
├─────────────────┤
│ ctf_CharData[N] │ bitplane N
└─────────────────┘
Each plane is a separate bitmap strip, same layout as tf_CharData.
ctf_ColorTable maps pen indices to screen palette entries.
```
> [!WARNING]
> Color fonts require **Chip RAM** for all glyph data planes. A 24-pixel color font at 8 bitplanes uses **8×** the memory of a monochrome bitmap font. On a 512 KB Chip RAM system, loading multiple color fonts can exhaust memory fast.
Color fonts render automatically with `Text()` — the renderer detects `FPF_COLORFONT` in `tf_Flags` and uses the multi-plane blit path. No special code is needed.
---
## Compugraphic Outline Fonts (OS 2.0+)
OS 2.0 added support for **AGFA Compugraphic** outline fonts via `diskfont.library`. These store mathematical curve descriptions instead of pre-drawn pixels. From the programmer's perspective, they work identically to bitmap fonts — `OpenDiskFont()` handles rasterization internally:
```c
/* Same API — diskfont.library auto-detects outline vs bitmap: */
struct TextAttr ta = {"CGTriumvirate.font", 36, 0, 0};
struct TextFont *outlineFont = OpenDiskFont(&ta);
/* If no 36-pixel bitmap exists but a CG outline does,
diskfont.library rasterizes it to a bitmap automatically */
```
| Aspect | Bitmap Font | Compugraphic Outline |
|--------|-------------|---------------------|
| Source | Pre-drawn pixels per size | Mathematical curves |
| Scaling | Distorts at non-designed sizes | Clean at any size |
| File format | `.font` + numbered descriptor files | `.font` with outline data |
| API difference | None — same `OpenDiskFont()` | None — transparent to application |
| Speed | Instant (blit pixels) | Slower (first rasterize, then blit) |
| Availability | OS 1.0+ | OS 2.0+ (requires `diskfont.library` V36+) |
> [!NOTE]
> Compugraphic outlines are **not** TrueType or PostScript. They are AGFA's proprietary curve format. The Amiga never gained native TrueType support; third-party libraries (like TrueDot) filled this gap.
---
## Font Scaling and Aspect Ratio
When `OpenDiskFont()` can't find an exact size match, it scales an existing bitmap. This produces usable but not beautiful results:
```c
/* Request a size that doesn't exist on disk: */
struct TextAttr ta = {"topaz.font", 15, 0, 0};
/* diskfont.library scales topaz 8 or topaz 9 to 15 pixels */
/* Result: usable but slightly blurry or jagged */
struct TextFont *scaled = OpenDiskFont(&ta);
```
The `FPF_DESIGNED` flag in `tf_Flags` indicates whether the font was explicitly designed at this size (clear) or auto-scaled (set). Always check:
```c
if (font->tf_Flags & FPF_DESIGNED) {
/* This size was designed — crisp rendering */
} else {
/* Auto-scaled — may look rough at large sizes */
}
```
### Aspect Ratio Mismatch
Fonts designed for one display mode may look wrong on another:
| Source Mode | Target Mode | Visual Effect |
|-------------|-------------|--------------|
| LoRes 320×256 | HiRes 640×256 | Font appears half-width (compressed) |
| HiRes 640×256 | LoRes 320×256 | Font appears double-width (stretched) |
| Non-interlaced | Interlaced | Font appears half-height (thin) |
| Interlaced | Non-interlaced | Font appears double-height (fat) |
The `FPF_TALLDOT` and `FPF_WIDEDOT` flags indicate which pixel aspect the font was designed for. Check these when rendering across different screen modes.
---
## Available Font Lists
```c
/* List all fonts available on FONTS: */
struct AvailFontsHeader *afh;
LONG bufSize = 4096;
do {
afh = AllocMem(bufSize, MEMF_ANY);
LONG shortBy = AvailFonts((STRPTR)afh, bufSize,
AFF_DISK | AFF_MEMORY | AFF_SCALED);
if (shortBy > 0) {
FreeMem(afh, bufSize);
bufSize += shortBy;
afh = NULL;
}
} while (!afh);
struct AvailFonts *af = &afh->afh_AF;
for (int i = 0; i < afh->afh_NumEntries; i++)
{
Printf("Font: %s, size %ld, type %s\n",
af[i].af_Attr.ta_Name,
af[i].af_Attr.ta_YSize,
(af[i].af_Type & AFF_DISK) ? "disk" : "ROM");
}
FreeMem(afh, bufSize);
```
---
## Practical Cookbooks
### Cookbook: Centered Title Bar
```c
void DrawCenteredTitle(struct RastPort *rp, const char *title,
UWORD width, UWORD y)
{
struct TextFont *font = rp->Font;
UWORD textW = TextLength(rp, title, strlen(title));
WORD x = (width - textW) / 2;
SetDrMd(rp, JAM1);
SetAPen(rp, 1); /* foreground */
Move(rp, x, y + font->tf_Baseline);
Text(rp, title, strlen(title));
}
```
### Cookbook: Multi-Font Label + Value
```c
void DrawLabelValue(struct RastPort *rp,
struct TextFont *labelFont,
struct TextFont *valueFont,
const char *label, const char *value,
UWORD x, UWORD y)
{
/* Draw label in small font */
SetFont(rp, labelFont);
SetAPen(rp, 2);
SetDrMd(rp, JAM1);
Move(rp, x, y + labelFont->tf_Baseline);
Text(rp, label, strlen(label));
/* Draw value in large font, right after label */
UWORD labelW = TextLength(rp, label, strlen(label));
SetFont(rp, valueFont);
SetAPen(rp, 1);
Move(rp, x + labelW + 4, y + valueFont->tf_Baseline);
Text(rp, value, strlen(value));
}
```
### Cookbook: Word-Wrap Text Block
```c
/* Returns the number of lines rendered */
UWORD DrawWrappedText(struct RastPort *rp, const char *text,
UWORD maxWidth, UWORD startX, UWORD startY)
{
struct TextFont *font = rp->Font;
UWORD y = startY;
UWORD lineH = font->tf_YSize + 1; /* +1 for line spacing */
const char *lineStart = text;
const char *wordStart = text;
const char *bestBreak = text;
while (*wordStart)
{
/* Skip to end of word */
const char *wordEnd = wordStart;
while (*wordEnd && *wordEnd != ' ' && *wordEnd != '\n') wordEnd++;
/* Measure from lineStart to wordEnd */
UWORD w = TextLength(rp, lineStart, wordEnd - lineStart);
if (w > maxWidth && bestBreak > lineStart)
{
/* Break at the last good position */
Move(rp, startX, y + font->tf_Baseline);
Text(rp, lineStart, bestBreak - lineStart);
y += lineH;
lineStart = bestBreak + 1; /* skip the space */
bestBreak = lineStart;
}
else
{
bestBreak = wordEnd;
}
if (*wordEnd == '\n')
{
Move(rp, startX, y + font->tf_Baseline);
Text(rp, lineStart, wordEnd - lineStart);
y += lineH;
lineStart = wordEnd + 1;
bestBreak = lineStart;
wordEnd++;
}
wordStart = wordEnd;
while (*wordStart == ' ') wordStart++;
}
/* Final line */
if (lineStart < wordStart)
{
Move(rp, startX, y + font->tf_Baseline);
Text(rp, lineStart, strlen(lineStart));
y += lineH;
}
return (y - startY) / lineH;
}
```
---
## Historical Context & Modern Analogies
### Competitive Landscape
| Platform | Font System | Scalable? | Color Fonts? | Max Glyph Quality |
|----------|-------------|-----------|-------------|-------------------|
| **AmigaOS 1.x** | Bitmap (graphics.library) | No | No | Fixed-size pixel glyphs |
| **AmigaOS 2.0+** | Bitmap + Compugraphic outlines | Yes (outlines) | No | Scalable via curves |
| **AmigaOS 3.0+** | Bitmap + outlines + ColorFont | Yes | Yes (up to 8 planes) | Multi-color pixel glyphs |
| **Mac System 1–6** | Bitmap "suitcase" + FOND resources | No | No | 72 DPI assumption |
| **Mac System 7 (1991)** | TrueType + bitmap fallbacks | Yes | No | Full curve rasterization |
| **Windows 3.0** | Bitmap `.FON` | No | No | System fixed-width |
| **Windows 3.1 (1992)** | TrueType + bitmap | Yes | No | WYSIWYG via TrueType |
| **Atari ST TOS** | 8×8 bitmap, GDOS optional | No (SpeedoGDOS in '91) | No | Minimal system font |
The Amiga was **ahead of Mac and Windows in color fonts** (OS 3.0, 1992) but **behind in scalable font technology** — Compugraphic outlines were less capable than Apple's TrueType.
### Modern Analogies
| Amiga Concept | Modern Equivalent | Notes |
|--------------|-------------------|-------|
| `TextFont` / `TextAttr` | `CTFont` (macOS) / `IDWriteFont` (Windows) / `PangoFontDescription` | Font description + loaded instance |
| `SetFont()` | `CGContextSetFont()` / `SelectObject(hDC, hFont)` | Bind font to drawing context |
| `Text()` | `CTLineDraw()` / `DrawText()` / `pango_layout_show()` | Render string at current position |
| `TextLength()` | `CTLineGetTypographicBounds()` / `GetTextExtentPoint32()` | Measure without rendering |
| `tf_CharData` bitmap strip | Glyph atlas texture (game engines) | Same concept: all glyphs in one bitmap |
| `tf_CharLoc` table | Font atlas lookup table (UV coordinates) | Same concept: offset + width per glyph |
| Algorithmic bold | `NSFontManager.convertWeight()` | Amiga smears pixels; modern re-strokes curves |
| `AvailFonts()` | `NSFontManager.availableFonts` / `IDWriteFontCollection` | Enumerate installed fonts |
| `FONTS:` assign | `/usr/share/fonts` / `C:\Windows\Fonts` | System font directory |
---
## Best Practices
1. **Always use `OpenDiskFont()` over `OpenFont()`** — it loads from disk if needed; `OpenFont()` only finds already-loaded fonts
2. **Check the return value** — `OpenDiskFont()` returns `NULL` if the font doesn't exist; drawing with a NULL font crashes
3. **Restore the original font** if you borrowed a shared RastPort (e.g., window's RPort)
4. **Position at the baseline**, not the top — use `font->tf_Baseline` offset
5. **Use `TextLength()` before `Text()`** for alignment and clipping
6. **Respect user font preferences** — use `screen->RastPort.Font` as the default
7. **Check `FPF_DESIGNED`** before relying on auto-scaled font quality
8. **Open font, set font, draw, close font** — don't hold fonts open longer than needed
9. **Use `JAM2` with a matching `BgPen`** for text that must be readable over any background
10. **Don't assume ASCII 32–127** — check `tf_LoChar`/`tf_HiChar` before indexing
---
## Named Antipatterns
### "The Baseline Blind Spot" — Positioning at the Top Instead of Baseline
```c
/* BAD: Positioning at the top of the character cell —
descenders on g, j, p, q, y clip into the next line */
Move(rp, 10, 50); /* y=50 is the TOP of the cell */
Text(rp, "jumpy", 5);
```
```c
/* CORRECT: Position at the baseline */
Move(rp, 10, 50 + font->tf_Baseline);
Text(rp, "jumpy", 5);
```
### "The Font Leak" — Forgetting CloseFont
```c
/* BAD: OpenDiskFont increments the accessor count.
Forgetting CloseFont leaks the font in memory forever. */
struct TextFont *font = OpenDiskFont(&ta);
SetFont(rp, font);
/* ... draw stuff ... */
/* Missing CloseFont()! Font stays loaded. */
```
```c
/* CORRECT: Always pair OpenDiskFont/OpenFont with CloseFont */
struct TextFont *font = OpenDiskFont(&ta);
if (font) {
struct TextFont *oldFont = rp->Font;
SetFont(rp, font);
/* ... draw stuff ... */
SetFont(rp, oldFont); /* restore */
CloseFont(font);
}
```
### "The Scale Surprise" — Assuming Auto-Scaled Fonts Look Good
```c
/* BAD: Requesting a size that doesn't exist, assuming clean rendering */
struct TextAttr ta = {"topaz.font", 48, 0, 0};
struct TextFont *huge = OpenDiskFont(&ta);
/* topaz has no 48-pixel design — you get a scaled-up 8-pixel bitmap.
It looks like pixelated garbage. */
```
```c
/* CORRECT: Check FPF_DESIGNED and warn/fallback */
struct TextFont *font = OpenDiskFont(&ta);
if (font && !(font->tf_Flags & FPF_DESIGNED)) {
/* Font was auto-scaled — consider using a different size
or an outline font (Compugraphic) for better quality */
}
```
### "The Proportional Trap" — Assuming Fixed Width
```c
/* BAD: Calculating text width by multiplying character count */
UWORD badWidth = strlen(title) * font->tf_XSize;
/* WRONG for proportional fonts (helvetica, times, etc.) */
```
```c
/* CORRECT: Always measure with TextLength */
UWORD goodWidth = TextLength(rp, title, strlen(title));
```
---
## Pitfalls & Common Mistakes
### 1. NULL Font After Failed OpenDiskFont
**Symptom:** System crash or garbage rendering after `SetFont(rp, NULL)`.
**Cause:** `OpenDiskFont()` returns `NULL` when the font doesn't exist. Passing `NULL` to `SetFont()` causes undefined behavior.
**Fix:** Always check the return value:
```c
struct TextFont *font = OpenDiskFont(&ta);
if (!font) {
/* Fallback to a ROM font */
struct TextAttr fallback = {"topaz.font", 8, 0, FPF_ROMFONT};
font = OpenFont(&fallback);
}
if (font) SetFont(rp, font);
```
### 2. Color Font Memory Exhaustion
**Symptom:** System runs out of Chip RAM after loading several large color fonts.
**Cause:** Each color font bitplane is a full copy of the glyph bitmap. An 8-plane color font at 24 pixels with 256 characters uses roughly `24 × 256/8 × 8 = ~49 KB` of Chip RAM per font. Multiple fonts multiply this.
**Fix:** Load color fonts only when needed and close them immediately after use. Prefer monochrome fonts for large text blocks.
### 3. Aspect Ratio Distortion Across Screen Modes
**Symptom:** Text looks squished or stretched when opening a window on a different resolution screen than the font was designed for.
**Cause:** `FPF_TALLDOT`/`FPF_WIDEDOT` flags indicate the font's native pixel aspect. The renderer does not auto-compensate.
**Fix:** Check the screen mode's aspect ratio and choose a font designed for that mode, or accept the distortion.
### 4. Font Name Case Sensitivity
**Symptom:** `OpenDiskFont()` fails even though the font exists on disk.
**Cause:** Font names are case-sensitive on the Amiga filesystem. `"Topaz.font"` ≠ `"topaz.font"`.
**Fix:** Always use lowercase font names (the standard convention):
```c
/* WRONG: */
struct TextAttr ta = {"Topaz.font", 8, 0, 0}; /* uppercase T */
/* CORRECT: */
struct TextAttr ta = {"topaz.font", 8, 0, 0}; /* lowercase */
```
---
## FAQ
**Q: Can I render rotated text?**
A: No — the `Text()` function only renders horizontally. Rotated text requires custom glyph-by-glyph blitting with coordinate transforms. Compugraphic outlines in OS 2.0+ were planned to support rotation but the API was never exposed.
**Q: What is `tf_BoldSmear`?**
A: The number of pixels the renderer shifts and ORs the glyph rightward to produce algorithmic bold. Usually 1. The smear makes characters slightly wider — account for this in width calculations.
**Q: Can I use TrueType fonts?**
A: Not natively. AmigaOS uses bitmap and Compugraphic outlines. Third-party solutions like TrueDot, TypeManager, and AmigaOS 4's bullet.library add TrueType support. For retro development, stick with bitmap fonts.
**Q: What happens if I call `Text()` with characters outside `tf_LoChar`–`tf_HiChar`?**
A: The renderer substitutes a default glyph (usually the character for `tf_LoChar`). It does not crash, but you get wrong characters.
**Q: How do I change the system font for all new windows?**
A: Use the Font Preferences editor (Prefs → Font). The user's selection is stored in `ENV:sys/font.prefs`. Applications should read `screen->RastPort.Font` rather than hardcoding a specific font.
**Q: What is the difference between `OpenFont()` and `OpenDiskFont()`?**
A: `OpenFont()` (graphics.library) only finds fonts already loaded in memory. `OpenDiskFont()` (diskfont.library) can also load from disk and scale. Always prefer `OpenDiskFont()`.
---
## References
### NDK Headers
- `graphics/text.h` — `TextFont`, `TextAttr`, `TextExtent`, style/flag constants
- `graphics/rastport.h` — RastPort font-related fields
- `graphics/gfx.h` — `ColorTextFont`, `ColorFontColors`
- `libraries/diskfont.h` — `DiskFontHeader`, `AvailFonts`
### Autodocs
- ADCD 2.1: graphics.library text functions
- ADCD 2.1: diskfont.library font loading
### Related Knowledge Base Articles
- [diskfont.md](../11_libraries/diskfont.md) — font file format, disk loading pipeline, ColorFont memory layout
- [rastport.md](rastport.md) — RastPort text rendering context and drawing modes
- [bitmap.md](bitmap.md) — BitMap structure (font glyph data is a BitMap)
- [blitter.md](blitter/blitter.md) — hardware that performs the actual glyph blitting
- [console.md](../10_devices/console.md) — console text rendering uses these fonts
- [utility.md](../11_libraries/utility.md) — `Hook` structure (used by font enumeration)