mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-13 00:26:28 +00:00
docs: expand 7 Tier 3 articles to Deep quality
- gcc_amiga.md: 101→606 lines — pipeline, Docker, platform builds, flags, antipatterns, FAQ - trackdisk.md: 178→428 lines — MFM encoding, 16-command reference, antipatterns, FPGA impact - console.md: 244→470 lines — decision guide, TUI/progress cookbooks, antipatterns, pitfalls - layers.md: 224→739 lines — ClipRect engine, LVO API, backfill hooks, 4 antipatterns, optimization - text_fonts.md: 215→708 lines — ColorFont, Compugraphic outlines, 3 cookbooks, 4 antipatterns - windows.md: 370→778 lines — 5 antipatterns, decision guide, 3 cookbooks, modern analogies - menus.md: 378→695 lines — render chain diagram, 5 antipatterns, lifecycle cookbook, 6 FAQ
This commit is contained in:
parent
ab88118dc1
commit
9f5d9de1ed
8 changed files with 3129 additions and 209 deletions
|
|
@ -4,22 +4,32 @@
|
|||
|
||||
## Overview
|
||||
|
||||
AmigaOS uses **bitmap fonts** rendered through `graphics.library`. Each font character is stored as a strip of pixels in a single bitmap. Fonts can be ROM-resident (topaz — always available), loaded from disk via `diskfont.library`, or generated algorithmically (bold, italic, underline).
|
||||
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 LR
|
||||
flowchart TD
|
||||
subgraph "Font Sources"
|
||||
ROM["ROM Fonts<br/>(topaz 8, topaz 9)"]
|
||||
DISK["Disk Fonts<br/>(FONTS: directory)"]
|
||||
OUTLINE["Compugraphic Outlines<br/>(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<br/>(rendered text)"]
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -34,7 +44,7 @@ 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, etc. */
|
||||
UBYTE ta_Flags; /* FPF_ROMFONT, FPF_DISKFONT, FPF_PROPORTIONAL, etc. */
|
||||
};
|
||||
|
||||
/* Loaded font instance: */
|
||||
|
|
@ -42,7 +52,7 @@ 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;
|
||||
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 */
|
||||
|
|
@ -57,6 +67,19 @@ struct TextFont {
|
|||
};
|
||||
```
|
||||
|
||||
### 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:
|
||||
|
|
@ -77,7 +100,28 @@ tf_CharSpace[ch - tf_LoChar]:
|
|||
|
||||
---
|
||||
|
||||
## Opening 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): */
|
||||
|
|
@ -93,17 +137,36 @@ struct TextFont *font2 = OpenDiskFont(&ta2);
|
|||
struct TextAttr ta3 = {"topaz.font", 8, FSF_BOLD, FPF_ROMFONT};
|
||||
struct TextFont *bold = OpenFont(&ta3);
|
||||
|
||||
/* Assign to RastPort: */
|
||||
SetFont(rp, font);
|
||||
|
||||
/* Close when done: */
|
||||
CloseFont(font);
|
||||
/* 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! */
|
||||
|
|
@ -126,12 +189,28 @@ TextExtent(rp, "Hello", 5, &te);
|
|||
```
|
||||
|
||||
> [!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.
|
||||
> `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
|
||||
|
|
@ -150,12 +229,123 @@ Text(rp, "Bold Italic Text", 16);
|
|||
SetSoftStyle(rp, 0, supported);
|
||||
```
|
||||
|
||||
| Style | Method | Notes |
|
||||
|---|---|---|
|
||||
| Bold | Smear right by `tf_BoldSmear` | Characters become slightly wider |
|
||||
| Italic | Shear top scanlines right | Fixed-angle slant |
|
||||
| Underline | Draw line at descender level | 1-pixel line below baseline |
|
||||
| Extended | Widen each character | Rarely used |
|
||||
| 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.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -189,27 +379,330 @@ FreeMem(afh, bufSize);
|
|||
|
||||
---
|
||||
|
||||
## Font Preferences
|
||||
## Practical Cookbooks
|
||||
|
||||
The system font can be changed via Preferences. Applications should respect the user's choice:
|
||||
### Cookbook: Centered Title Bar
|
||||
|
||||
```c
|
||||
/* Get the current screen font (user's preference): */
|
||||
struct TextAttr *screenFont;
|
||||
struct Preferences prefs;
|
||||
GetPrefs(&prefs, sizeof(prefs));
|
||||
/* prefs contains font info */
|
||||
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;
|
||||
|
||||
/* Better: use the screen's font directly: */
|
||||
struct TextFont *scrFont = screen->RastPort.Font;
|
||||
SetFont(myRastPort, scrFont);
|
||||
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
|
||||
|
||||
- NDK39: `graphics/text.h`, `graphics/rastport.h`
|
||||
- ADCD 2.1: `OpenFont`, `OpenDiskFont`, `SetFont`, `Text`, `TextLength`
|
||||
- See also: [diskfont.md](../11_libraries/diskfont.md) — disk font loading
|
||||
- See also: [rastport.md](rastport.md) — RastPort text rendering context
|
||||
### 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.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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue