diff --git a/11_libraries/README.md b/11_libraries/README.md index b013dd5..6606032 100644 --- a/11_libraries/README.md +++ b/11_libraries/README.md @@ -18,7 +18,7 @@ Shared libraries beyond the core exec/dos/graphics/intuition subsystems. These p | [rexxsyslib.md](rexxsyslib.md) | ARexx scripting: hosting ARexx ports, command parsing, sending commands, return codes | | [mathffp.md](mathffp.md) | Motorola FFP and IEEE 754 floating point | | [layers.md](layers.md) | Window clipping: ClipRect engine, Simple/Smart/Super refresh, damage repair, backfill hooks, layer locking | -| [diskfont.md](diskfont.md) | Disk-based fonts: FONTS: directory structure, AvailFonts enumeration, color fonts (OS 3.0+) | +| [diskfont.md](diskfont.md) | **Bitmap fonts deep dive: .font file format (FontContentsHeader), font descriptor files (DiskFontHeader), glyph bitmap layout, FONTS: assign, adding/installing fonts, bitmap vs TrueType/OpenType comparison, color fonts (OS 3.0+), Compugraphic outline fonts, AvailFonts enumeration, font loading pipeline** | | [datatypes.md](datatypes.md) | DataTypes system: object-oriented file loading for images, sound, text, animation via BOOPSI classes | | [amigaguide.md](amigaguide.md) | AmigaGuide hypertext help system: database format, @commands, API, ARexx integration, cross-database linking | | [translator.md](translator.md) | translator.library: English-to-phonetic translation for speech synthesis, narrator.device integration, ARPABET phonemes | diff --git a/11_libraries/diskfont.md b/11_libraries/diskfont.md index 2d303df..867b892 100644 --- a/11_libraries/diskfont.md +++ b/11_libraries/diskfont.md @@ -1,156 +1,619 @@ [← Home](../README.md) · [Libraries](README.md) -# diskfont.library — Disk-Based Font Loading +# diskfont.library — Amiga Bitmap Fonts: Concept, File Format, and Disk Loading ## Overview -`diskfont.library` loads bitmap fonts from disk (the `FONTS:` assign). Only two fonts are built into ROM — **topaz 8** and **topaz 9**. All other fonts (helvetica, times, courier, etc.) must be loaded from disk via this library. +**`diskfont.library`** loads bitmap fonts from disk into the Amiga graphics system. Unlike modern TrueType/OpenType fonts that store mathematical curve descriptions, Amiga fonts are **bitmap fonts** — each glyph is a hand-drawn grid of pixels at a fixed size. A 14-pixel "Helvetica" and a 24-pixel "Helvetica" are entirely separate files, each hand-designed (or auto-scaled) for that specific pixel height. -```mermaid -flowchart LR - APP["Application"] -->|"OpenDiskFont(&ta)"| DFL["diskfont.library"] - DFL -->|"Scans FONTS: directory"| FONTS["FONTS:
helvetica.font
helvetica/24"] - DFL -->|"Returns loaded font"| TF["struct TextFont"] - TF -->|"SetFont(rp, font)"| RP["RastPort"] +This was the dominant font technology of the late 1980s and early 1990s. Before CPUs were fast enough to rasterize Bézier curves on the fly, storing pre-rendered pixel data was the only practical option. The Amiga's bitmap font system was sophisticated for its era — proportional spacing, kerning, algorithmic bold/italic/underline, and color fonts (OS 3.0+) — but it was fundamentally pixel-bound. - ROM["ROM"] -->|"OpenFont(&ta)"| TOPAZ["topaz 8/9
(always available)"] +Two fonts are built into every Amiga ROM: **topaz 8** and **topaz 9**. Every other font — helvetica, times, courier, garnet, sapphire, and any third-party font — lives on disk under the `FONTS:` assign and must be loaded via `diskfont.library`. - style DFL fill:#e8f4fd,stroke:#2196f3,color:#333 - style FONTS fill:#c8e6c9,stroke:#2e7d32,color:#333 -``` +> [!NOTE] +> This article focuses on the **disk side** — font file formats, directory structure, loading pipeline, and the conceptual model. For the in-memory `TextFont` structure, rendering with `Text()`/`SetFont()`, and algorithmic styles, see [text_fonts.md](../08_graphics/text_fonts.md). --- -## Font Directory Structure +## The Bitmap Font Concept — Why Pixels Instead of Curves -Amiga bitmap fonts are stored as a descriptor file plus per-size data files: +### How Bitmap Fonts Work + +A bitmap font stores each character as a flat pixel grid. The letter "A" at 14 pixels tall might be a 9×14 pixel array: + +``` +......... +..#####.. +.##...##. +.##...##. +.#######. +.##...##. +.##...##. +......... +``` + +Every glyph at every size is pre-drawn. The OS simply copies the glyph's pixel strip to the screen — no math, no rasterization, no hinting engine. This is why Amiga text rendering is so fast: it's just `Blitter` and `BlitMaskBitMapRastPort` operations under the hood. + +### Comparison with Modern TrueType / OpenType + +| Aspect | Amiga Bitmap Fonts | TrueType / OpenType | +|---|---|---| +| **Storage** | Pixel grid per glyph per size | Mathematical curves (Bézier splines) | +| **Scaling** | Each size is a separate file; scaling creates distortion | Arbitrary size rendered from same outlines | +| **File size** | Small per size, but multiplies by size count | One file covers all sizes | +| **Quality at scale** | Perfect at designed size; jagged/blurry when scaled | Smooth at any size (with hinting) | +| **Rendering cost** | Near-zero — just copy pixels | Requires curve rasterization + hinting | +| **Rotation** | Impossible (pixel grid is axis-aligned) | Built-in (curves are coordinate-independent) | +| **Memory** | Full glyph bitmap in Chip RAM during use | Glyph cache; outlines are small | +| **Typographic features** | Basic: kerning, proportional spacing | Rich: ligatures, alternates, feature tables | + +### Why the Amiga Used Bitmap Fonts + +1. **CPU limitations**: A 7 MHz 68000 cannot rasterize TrueType curves at interactive speeds. Even on 1990s PCs, TrueType rasterization was a noticeable cost. The Amiga solved this by pre-rendering everything. +2. **Memory constraints**: A 14-pixel bitmap font for ASCII 32–127 fits in roughly 2–4 KB. Loading and blitting pixels uses zero CPU beyond the initial `LoadSeg()`. +3. **Hardware acceleration**: The Blitter can copy glyph pixels directly to the screen's planar bitmap. This hardware-accelerated text path depends on glyphs already being in planar pixel format. +4. **Era conventions**: In 1985, bitmap fonts were the industry standard. Apple's Macintosh (1984) used bitmap "suitcase" fonts. Windows 3.1 (1992) defaulted to bitmap system fonts. TrueType didn't become universal until the mid-1990s. + +--- + +## Font File Format — The Complete On-Disk Structure + +### The Two-File Architecture + +Every Amiga bitmap font consists of two kinds of files: ``` FONTS: - helvetica.font ← font descriptor (FontContents header) - helvetica/ - 9 ← bitmap data for 9-pixel height - 11 ← bitmap data for 11-pixel height - 13 ← bitmap data for 13-pixel height - 18 ← bitmap data for 18-pixel height - 24 ← bitmap data for 24-pixel height - times.font - times/ - 11 - 13 - 18 - 24 +├── helvetica.font ← Font Contents File (descriptor) +└── helvetica/ ← Font Data Directory + ├── 9 ← Font Descriptor File (size 9) + ├── 11 ← Font Descriptor File (size 11) + ├── 14 ← Font Descriptor File (size 14) + ├── 19 ← Font Descriptor File (size 19) + └── 24 ← Font Descriptor File (size 24) ``` -The `.font` descriptor file contains a `FontContentsHeader` listing all available sizes, styles, and flags. +```mermaid +graph TB + subgraph "Font Contents File
(helvetica.font)" + HDR["FontContentsHeader"] + FC1["FontContents[0]: size 9"] + FC2["FontContents[1]: size 11"] + FC3["FontContents[2]: size 14"] + FC4["..."] + end + + subgraph "Font Descriptor Files
(helvetica/9, 11, 14…)" + DFH9["DiskFontHeader
+ TextFont
+ glyph bitmaps
(size 9)"] + DFH11["DiskFontHeader
+ TextFont
+ glyph bitmaps
(size 11)"] + DFH14["DiskFontHeader
+ TextFont
+ glyph bitmaps
(size 14)"] + end + + FC1 -.->|"points to"| DFH9 + FC2 -.->|"points to"| DFH11 + FC3 -.->|"points to"| DFH14 + + style HDR fill:#e8f4fd,stroke:#2196f3,color:#333 + style DFH9 fill:#fff9c4,stroke:#f9a825,color:#333 +``` + +### 1. The Font Contents File (`.font`) + +This is the **index file** — it lists every available size for a given typeface. Its C structure: + +```c +/* libraries/diskfont.h — NDK39 */ +#define MAXFONTPATH 256 + +struct FontContentsHeader { + UWORD fch_FileID; /* $0F00 = FCH_ID, $0F02 = TFCH_ID, $0F03 = scalable */ + UWORD fch_NumEntries; /* number of FontContents entries following */ + struct FontContents fch_FC[]; /* variable-length array */ +}; + +struct FontContents { + char fc_FileName[MAXFONTPATH]; /* path to size directory, e.g. "helvetica/14" */ + UWORD fc_YSize; /* pixel height */ + UBYTE fc_Style; /* FSF_BOLD, FSF_ITALIC, etc. */ + UBYTE fc_Flags; /* FPF_ROMFONT, FPF_DISKFONT, etc. */ +}; +``` + +#### File ID Values + +| ID | Name | Meaning | +|---|---|---| +| `$0F00` | `FCH_ID` | Standard bitmap font (uses `FontContents` entries) | +| `$0F02` | `TFCH_ID` | Tagged bitmap font (uses `TFontContents` — supports `TA_DeviceDPI` tags) | +| `$0F03` | — | Scalable outline font (Compugraphic / IntelliFont, not bitmap) | + +#### File on Disk (Binary Layout) + +``` +Offset Size Field +────── ──── ────────────────────────────── +$00 2 fch_FileID ($0F00 for bitmap) +$02 2 fch_NumEntries (e.g. 5 sizes) +$04 260 FontContents[0] +$108 260 FontContents[1] +$20C 260 FontContents[2] +... ... (NumEntries × 260 bytes each) +``` + +Each `FontContents` entry is exactly `MAXFONTPATH + 4` = 260 bytes, regardless of actual string length. The `fc_FileName` field is null-padded. + +#### Per-Entry Flags + +| Flag | Bit | Meaning | +|---|---|---| +| `FPF_ROMFONT` | 0 | Font is built into ROM (topaz only) | +| `FPF_DISKFONT` | 1 | Font is loaded from disk | +| `FPF_REVPATH` | 2 | Designed for right-to-left text | +| `FPF_TALLDOT` | 3 | Designed for Hires (640-pixel) screen | +| `FPF_WIDEDOT` | 4 | Designed for Lores Interlaced screen | +| `FPF_PROPORTIONAL` | 5 | Character widths are not constant | +| `FPF_DESIGNED` | 6 | Hand-designed at this size (not scaled) | +| `FPF_REMOVED` | 7 | Font has been removed from system list | + +### 2. The Font Descriptor File (numeric filename, e.g. `14`) + +Each numeric file in the font directory is a **loadable DOS hunk** — it's literally wrapped as a `HUNK_CODE` so `LoadSeg()` can load it. The first two longwords contain a `MOVEQ #0,D0 : RTS` instruction pair to safely exit if the file is accidentally executed. + +```c +/* libraries/diskfont.h — NDK39 */ +#define MAXFONTNAME 32 + +struct DiskFontHeader { + /* The 8 bytes BEFORE this struct (not part of it!) are: */ + /* ULONG dfh_NextSegment; // BPTR — filled by LoadSeg */ + /* ULONG dfh_ReturnCode; // MOVEQ #0,D0 : RTS */ + + struct Node dfh_DF; /* Exec Node — links loaded fonts together */ + UWORD dfh_FileID; /* DFH_ID ($0F80) */ + UWORD dfh_Revision; /* font revision number */ + LONG dfh_Segment; /* segment address after LoadSeg */ + char dfh_Name[MAXFONTNAME]; /* font name, e.g. "helvetica" */ + struct TextFont dfh_TF; /* The actual TextFont structure */ + /* Immediately after dfh_TF: glyph bitmap data, char location tables */ +}; +``` + +The `dfh_TF` is the same `struct TextFont` described in [text_fonts.md](../08_graphics/text_fonts.md). After it in memory come: + +| Field | Description | +|---|---| +| `tf_CharData` | Bitmap strip containing all glyphs (one row per scanline, `tf_Modulo` bytes wide) | +| `tf_CharLoc` | Per-character location table: 2 WORDs per glyph (bit offset, width in pixels) | +| `tf_CharSpace` | Proportional spacing: 1 WORD per glyph (advance width) | +| `tf_CharKern` | Kerning table: 1 WORD per glyph (extra space after this character) | --- -## Loading a Disk Font +## The Font Loading Pipeline + +```mermaid +sequenceDiagram + participant APP as Application + participant DF as diskfont.library + participant DOS as dos.library + participant DISK as FONTS: (disk) + participant GFX as graphics.library + + APP->>DF: OpenDiskFont(&ta) + DF->>DISK: Open("helvetica.font", MODE_OLDFILE) + DISK-->>DF: FileHandle + DF->>DF: Read FontContentsHeader + DF->>DF: Scan entries for matching YSize & Style + DF->>DISK: Close(.font file) + + alt Size found + DF->>DOS: LoadSeg("helvetica/14") + DOS-->>DF: Segment (DiskFontHeader loaded) + DF->>DF: Verify dfh_FileID == DFH_ID + DF->>DF: Build TextFont from DiskFontHeader + DF->>GFX: Link font into system font list + DF-->>APP: struct TextFont * + else Size not found + DF-->>APP: NULL + end + + APP->>GFX: SetFont(rp, font) + APP->>GFX: Text(rp, "Hello", 5) + APP->>GFX: CloseFont(font) + GFX->>DF: Unlink & free font memory +``` + +### Font Request Matching + +When `OpenDiskFont()` receives a `struct TextAttr`, the library searches the `.font` file's entries: + +1. **Exact YSize match**: If the requested size exists as a `fc_YSize`, that entry is selected. +2. **No match**: Returns `NULL`. Unlike modern font systems, the Amiga does **not** automatically scale to the nearest size. +3. **Style matching**: If the requested style (`FSF_BOLD`, `FSF_ITALIC`) doesn't exist at that size, `graphics.library` can algorithmically generate it (`SetSoftStyle`). +4. **Scaled fonts (`AFF_SCALED`)**: OS 2.0+ can auto-generate intermediate sizes by scaling the nearest designed size. These appear in `AvailFonts()` with the `AFF_SCALED` flag. Quality is significantly worse than hand-designed sizes. + +--- + +## How Software Uses Different Fonts + +### The Standard Pattern ```c -struct Library *DiskfontBase = OpenLibrary("diskfont.library", 0); +struct Library *DiskfontBase = OpenLibrary("diskfont.library", 0L); -/* Request a specific font and size: */ -struct TextAttr ta = {"helvetica.font", 24, 0, 0}; +/* 1. Request a specific disk font: */ +struct TextAttr ta = {"helvetica.font", 14, 0, 0}; struct TextFont *font = OpenDiskFont(&ta); if (font) { + /* 2. Assign it to the RastPort: */ SetFont(rp, font); - Move(rp, 10, 30); - Text(rp, "Disk Font Text", 14); - /* When done with the font: */ + /* 3. Position cursor at baseline: */ + Move(rp, 10, 20 + font->tf_Baseline); + + /* 4. Render text: */ + Text(rp, "Disk-loaded font", 16); + + /* 5. Measure text for alignment: */ + UWORD w = TextLength(rp, "Centered", 8); + Move(rp, (screenWidth - w) / 2, 50 + font->tf_Baseline); + Text(rp, "Centered", 8); + + /* 6. Release when done: */ CloseFont(font); } -else -{ - /* Font not found — fall back to ROM font: */ - struct TextAttr fallback = {"topaz.font", 8, 0, FPF_ROMFONT}; - struct TextFont *topaz = OpenFont(&fallback); - SetFont(rp, topaz); -} CloseLibrary(DiskfontBase); ``` -### Size Matching +### ROM Font vs Disk Font — When to Use Each -If the exact requested size is not available, `OpenDiskFont` returns NULL. To find the nearest available size, use `AvailFonts` to enumerate, then request the closest match. +| Scenario | Use | Why | +|---|---|---| +| System UI, debug output, quick text | `OpenFont("topaz.font")` — ROM font | Always available, zero disk access, zero load time | +| Application body text, custom UI | `OpenDiskFont("helvetica.font")` — disk font | Professional appearance; user expects non-topaz fonts | +| Word processor, DTP, final output | `OpenDiskFont()` with `FPF_DESIGNED` | Hand-tuned glyphs look best; avoid scaled variants | +| Memory-critical (games, demos) | `OpenFont("topaz.font")` or embed custom font | Disk fonts consume Chip RAM for glyph data | +| Font enumeration / chooser | `AvailFonts()` + iterate | Build a font picker dialog | ---- +### Getting the User's Preferred Font -## Enumerating Available Fonts +Applications should respect the system font preference set in Workbench Preferences: ```c -/* AvailFonts returns all fonts in ROM and on disk: */ -LONG bufSize = 4096; -APTR buf = AllocMem(bufSize, MEMF_ANY | MEMF_CLEAR); +/* Read the user's font preference: */ +struct Preferences *prefs = AllocMem(sizeof(struct Preferences), MEMF_ANY); +GetPrefs(prefs, sizeof(struct Preferences)); -LONG shortfall = AvailFonts(buf, bufSize, AFF_DISK | AFF_MEMORY); -if (shortfall > 0) +struct TextAttr userFont = { + prefs->FontName, /* e.g. "helvetica.font" */ + prefs->FontSize, /* e.g. 14 or YSIZE_DEFAULT */ + FSF_NORMAL, + FPF_DISKFONT +}; + +struct TextFont *font = OpenDiskFont(&userFont); +if (!font) { - /* Buffer too small — reallocate and retry: */ - FreeMem(buf, bufSize); - bufSize += shortfall; - buf = AllocMem(bufSize, MEMF_ANY | MEMF_CLEAR); - AvailFonts(buf, bufSize, AFF_DISK | AFF_MEMORY); + /* Fall back to topaz if user's font is unavailable */ + struct TextAttr fallback = {"topaz.font", 8, 0, FPF_ROMFONT}; + font = OpenFont(&fallback); } +SetFont(rp, font); -struct AvailFontsHeader *afh = (struct AvailFontsHeader *)buf; -struct AvailFonts *af = (struct AvailFonts *)&afh[1]; - -for (int i = 0; i < afh->afh_NumEntries; i++) -{ - Printf("%s size %-3ld %s\n", - af[i].af_Attr.ta_Name, - af[i].af_Attr.ta_YSize, - (af[i].af_Type & AFF_DISK) ? "disk" : - (af[i].af_Type & AFF_MEMORY) ? "ROM" : "scaled"); -} - -FreeMem(buf, bufSize); +FreeMem(prefs, sizeof(struct Preferences)); ``` --- -## Font Types +## Adding New Fonts to Amiga -| Flag | Source | Notes | -|---|---|---| -| `AFF_MEMORY` | ROM or already loaded in memory | topaz, or previously opened disk fonts | -| `AFF_DISK` | Available on `FONTS:` | Requires disk access to load | -| `AFF_SCALED` | Algorithmically scaled from another size | Lower quality; avoid when native size exists | -| `AFF_BITMAP` | Bitmap (pixel) font | Standard Amiga font format | -| `AFF_TAGGED` | Tagged (OS 3.0+ extended) font | Supports color fonts, outlined fonts | +### Manual Installation + +1. **Copy the `.font` file** into `FONTS:`: + ```bash + Copy MyFont.font FONTS: + ``` + +2. **Copy the font directory** into `FONTS:`: + ```bash + Copy MyFont FONTS: ALL + ``` + +3. **Verify the structure:** + ``` + FONTS: + ├── MyFont.font ← font contents (index) + └── MyFont/ + ├── 11 ← size 11 pixels + ├── 14 ← size 14 pixels + └── 18 ← size 18 pixels + ``` + +4. **Re-scan available fonts** (or reboot). The `AvailFonts()` function automatically picks up new fonts on the next call — no reboot needed. + +### Extending the FONTS: Assign + +The `FONTS:` assign can span multiple directories and volumes: + +```bash +; Add a floppy disk of fonts to the search path: +Assign FONTS: FontDisk: ADD + +; Add fonts from a hard drive subdirectory: +Assign FONTS: Work:MyFonts ADD + +; Verify current path: +Assign FONTS: +; Output: FONTS: SYS:Fonts Work:MyFonts FontDisk: +``` + +Fonts on any path in the `FONTS:` assign cascade — if `helvetica.font` exists in both `SYS:Fonts` and `Work:MyFonts`, the first one found is used. + +### Creating Your Own Fonts — Font Editors + +| Tool | Source | Era | Notes | +|---|---|---|---| +| **FED** (Font EDitor) | Commodore (shipped with Workbench) | 1985–1990 | Basic but functional; shipped with 1.x | +| **Personal Fonts Maker** | Cloanto | 1990–1994 | Professional drawing tools; the standard for custom bitmap fonts | +| **TypeFace** | — | 1992+ | Supported both bitmap and Compugraphic outline editing | +| **Calligrapher** | — | 1991+ | Calligraphic stroke-based font design | +| **FontMachine** | — | 1993+ | AmigaGuide-based font designer | + +### Outline Fonts — The Evolution Beyond Bitmaps + +OS 2.0 introduced support for **Compugraphic (CG) outline fonts**, and OS 3.0 added the **Agfa IntelliFont** engine (via `bullet.library`). These are `.font` files with `fch_FileID = $0F03` that point to scalable outline data instead of fixed-size bitmaps. + +```mermaid +graph LR + subgraph "Bitmap (1.x+)" + B_FONT[".font file
fch_FileID = $0F00"] + B_DIR["size/14
(DiskFontHeader + glyph pixels)"] + B_FONT --> B_DIR + end + + subgraph "Outline (2.0+)" + O_FONT[".font file
fch_FileID = $0F03"] + O_ENGINE["bullet.library
(IntelliFont engine)"] + O_DATA["Outline data
(.otag / .ofont / .type)"] + O_FONT --> O_ENGINE + O_ENGINE --> O_DATA + end + + style B_FONT fill:#fff9c4,stroke:#f9a825 + style O_FONT fill:#e8f5e9,stroke:#4caf50 +``` + +Outline fonts can scale to any size from a single set of curves, eliminating the need for separate per-size files. However, they require CPU rasterization and were considered slow on 68000–68020 systems — bitmap fonts remained the default for interactive applications. + +--- + +## Enumerating All Available Fonts + +```c +struct AvailFontsHeader *afh = NULL; +LONG bufSize = 4096; + +/* Retry loop — buffer may be too small: */ +do { + if (afh) { FreeMem(afh, bufSize); bufSize += 1024; } + afh = AllocMem(bufSize, MEMF_ANY | MEMF_CLEAR); +} while (AvailFonts((STRPTR)afh, bufSize, AFF_DISK | AFF_MEMORY | AFF_SCALED) > 0); + +struct AvailFonts *af = &afh->afh_AF; + +for (LONG i = 0; i < afh->afh_NumEntries; i++) +{ + UWORD type = af[i].af_Type; + const char *source = + (type & AFF_MEMORY) ? "ROM" : + (type & AFF_DISK) ? "disk" : + (type & AFF_SCALED) ? "scaled" : "other"; + + Printf("%-20s y=%2ld %-6s %s%s%s\n", + af[i].af_Attr.ta_Name, + af[i].af_Attr.ta_YSize, + source, + (af[i].af_Attr.ta_Style & FSF_BOLD) ? "B" : " ", + (af[i].af_Attr.ta_Style & FSF_ITALIC) ? "I" : " ", + (af[i].af_Attr.ta_Style & FSF_UNDERLINED) ? "U" : " "); +} + +FreeMem(afh, bufSize); +``` + +### Memory Considerations for `AvailFonts()` + +The buffer must be in **any memory** (not Chip RAM). `AvailFonts()` writes `AvailFontsHeader` + N × `AvailFonts` entries. If the buffer is too small, it returns the number of **additional bytes** needed. The retry loop above handles this correctly. --- ## Color Fonts (OS 3.0+) -OS 3.0 introduced **color bitmap fonts** — each glyph can have multiple bitplanes: +OS 3.0 extended bitmap fonts with **color support** — glyphs with multiple bitplanes: + +``` +Traditional font: 1 bitplane → black or background color +Color font: up to 8 bitplanes → 256 colors per glyph +``` ```c -/* Color fonts use ColorTextFont — an extension of TextFont: */ +/* graphics/text.h — NDK39 */ struct ColorTextFont { struct TextFont ctf_TF; /* standard TextFont */ - UWORD ctf_Flags; /* CT_COLORFONT etc. */ - UBYTE ctf_Depth; /* number of bitplanes */ - UBYTE ctf_FgColor; /* default foreground pen */ - UBYTE ctf_Low; /* lowest color used */ - UBYTE ctf_High; /* highest color used */ - APTR ctf_PlanePick; /* plane selection */ - APTR ctf_PlaneOnOff; /* plane on/off defaults */ + 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 */ + APTR ctf_CharData[8]; /* per-plane glyph data pointers */ }; ``` +Color fonts store each bitplane as a separate glyph bitmap. The `ctf_ColorTable` maps pen numbers to the screen's actual palette — a font's "red" might be pen 17 on one screen and pen 5 on another. + +> [!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 at the same size. On a 512 KB Chip RAM system, this can be prohibitive for multi-font applications. + +--- + +## Historical Context + +### Competitive Landscape (1985–1993) + +| Platform | Font System | Scalable? | Notes | +|---|---|---|---| +| **AmigaOS 1.x–3.x** | Bitmap fonts (diskfont.library) | OS 2.0+ (Compugraphic) | Proportional, kerning, algorithmic styles; color fonts in 3.0 | +| **Macintosh System 1–6** | Bitmap "suitcase" fonts + FOND resources | No (until TrueType in System 7, 1991) | Multiple sizes per family; 72 DPI screen assumption | +| **Windows 1.x–3.0** | Bitmap `.FON` files | No (until TrueType in 3.1, 1992) | Monospaced system font; proportional in 3.0 | +| **Atari ST TOS** | GDOS bitmap fonts (optional) | No (until SpeedoGDOS, 1991) | 8×8 default system font; GDOS added proportional fonts | +| **X11 (Unix)** | BDF (Bitmap Distribution Format) | No (PostScript via display PostScript) | Server-side fonts; XFT + FreeType came much later | + +The Amiga was **ahead of its contemporaries** in font quality: proportional spacing, kerning, and algorithmic style generation were available from day one (1985). The Mac didn't get TrueType until 1991; Windows until 1992. But by 1993, the industry had shifted to outline fonts, and AmigaOS's bitmap model was showing its age. + +### The Compugraphic / IntelliFont Era + +Commodore licensed **Agfa Compugraphic (CG) outline font technology** for AmigaOS 2.0, and later **Agfa IntelliFont** for OS 3.0. These were real outline fonts (cubic Bézier curves), rendered by `bullet.library`. The Amiga could do what macOS and Windows did with TrueType — but on slower hardware and with a smaller font library. By the time outline fonts arrived, the Amiga market was already in decline. + +--- + +## Modern Analogies + +| Amiga Concept | Modern Equivalent | Where It Matches / Differs | +|---|---|---| +| Bitmap `.font` file + per-size directory | Sprite sheets in game engines | Both are pre-rendered pixel grids; neither scales well | +| `OpenDiskFont()` → `SetFont()` → `Text()` | `CTFontCreateWithName()` → `CGContextSetFont()` → `CTLineDraw()` | Same three-step pattern; modern APIs handle scaling transparently | +| `AvailFonts()` enumeration | `NSFontManager.availableFonts` / DirectWrite `IDWriteFontCollection` | Same concept — enumerate what's installed | +| Algorithmic bold/italic | `NSFontManager.convertWeight(_:of:)` | Amiga does pixel smearing/shearing; modern does weighted stroke | +| `FONTS:` assign | `$XDG_DATA_DIRS/fonts` / Windows `C:\Windows\Fonts` | Same multi-path search concept | +| Compugraphic outline fonts | TrueType (Apple/Microsoft, 1991) | Both are Bézier outline formats; CG predates TrueType | + +--- + +## Decision Guide — Bitmap vs Outline Fonts on Amiga + +```mermaid +flowchart TD + A["Need a font for
your application?"] --> B{"Target OS version?"} + B -->|"1.x"| C["✅ Bitmap only
OpenDiskFont()"] + B -->|"2.x+"| D{"Font quality
critical?"} + B -->|"3.x+"| D + + D -->|"Yes — DTP, word processor"| E{"CPU?"} + D -->|"No — UI, debug, utility"| F["✅ Bitmap font
fast, cheap, reliable"] + + E -->|"68020+"| G["Outline font viable
(bullet.library)"] + E -->|"68000"| H["⚠️ Outline too slow
Use designed bitmap sizes"] + + style C fill:#e8f5e9,stroke:#4caf50 + style F fill:#e8f5e9,stroke:#4caf50 + style G fill:#fff9c4,stroke:#f9a825 +``` + +--- + +## Best Practices + +1. **Always check `OpenDiskFont()` return value** — the font might not be installed on the user's system; fall back to topaz +2. **Use `FPF_DESIGNED` sizes** — auto-scaled (`AFF_SCALED`) fonts look jagged; prefer hand-designed bitmap sizes +3. **Close fonts when done** — `CloseFont()` decrements the accessor count; leaking fonts wastes Chip RAM +4. **Cache opened fonts** — opening the same font repeatedly reads from disk each time; keep a pointer if you'll use it again +5. **Respect the user's font preference** — read `GetPrefs()` and use the system font for UI elements +6. **Use topaz for debug/development output** — no disk dependency, always 8 or 9 pixels, predictable width +7. **Don't mix bitmap and outline font APIs** — `OpenFont()` and `OpenDiskFont()` return the same `TextFont*` type, but outline fonts interact with `bullet.library` internally + +### Antipatterns + +| Antipattern | Why It's Wrong | Correct Approach | +|---|---|---| +| **The Hardcoded Helvetica** | Assuming "helvetica.font/14" exists → NULL on minimal Workbench installs | Always fall back to topaz if `OpenDiskFont()` returns NULL | +| **The Leaked Accessor** | Calling `OpenDiskFont()` in a loop without `CloseFont()` → font never freed | Pair every `OpenDiskFont()` with a `CloseFont()` | +| **The AFF_SCALED Surprise** | Using `AvailFonts()` without checking `AFF_SCALED` flag → user picks a blurry scaled size | Filter out or visually flag `AFF_SCALED` entries in font pickers | +| **The Baseline Mishandling** | `Move(rp, x, y)` without adding `font->tf_Baseline` → text renders at the wrong vertical position | Always: `Move(rp, x, y + font->tf_Baseline)` | +| **The Missing FONTS: Assign** | Relying on FONTS: being available on a minimal boot → `OpenDiskFont()` fails silently | Verify `FONTS:` assign exists, or open from an explicit path | + +--- + +## Pitfalls + +### 1. `AvailFonts()` Buffer Management + +**Bad** — single-shot allocation, might overflow: +```c +APTR buf = AllocMem(4096, MEMF_ANY); +AvailFonts(buf, 4096, AFF_DISK); /* may return shortfall > 0 — data truncated! */ +``` + +**Good** — retry loop until buffer is large enough: +```c +LONG size = 4096; +struct AvailFontsHeader *afh = NULL; +LONG shortfall; +do { + if (afh) { FreeMem(afh, size); size += shortfall; } + afh = AllocMem(size, MEMF_ANY | MEMF_CLEAR); +} while ((shortfall = AvailFonts((STRPTR)afh, size, AFF_DISK | AFF_MEMORY)) > 0); +``` + +### 2. Font Name Includes `.font` Suffix + +`OpenDiskFont()` expects the full filename including the `.font` extension: + +```c +/* WRONG: */ +struct TextAttr ta = {"helvetica", 14, 0, 0}; +OpenDiskFont(&ta); /* → NULL */ + +/* CORRECT: */ +struct TextAttr ta = {"helvetica.font", 14, 0, 0}; +OpenDiskFont(&ta); /* → works */ +``` + +### 3. DiskFont Must Come from `OpenDiskFont()`, Not `OpenFont()` + +`OpenFont()` searches only already-loaded (ROM/memory) fonts. `OpenDiskFont()` triggers the disk search pipeline: + +```c +/* WRONG for disk fonts: */ +struct TextFont *f = OpenFont(&ta); /* only finds ROM fonts! */ + +/* CORRECT: */ +struct TextFont *f = OpenDiskFont(&ta); /* searches FONTS: on disk */ +``` + +### 4. Font Not Found After Installation Without Restart + +Adding fonts via `Assign FONTS: NewPath: ADD` takes effect immediately. But fonts are cached after first load — if a font was previously opened and is still in memory, the new version won't be seen until the old one is `CloseFont()`'d by all users. + --- ## References -- NDK39: `diskfont/diskfont.h`, `graphics/text.h` -- ADCD 2.1: diskfont.library autodocs -- See also: [text_fonts.md](../08_graphics/text_fonts.md) — TextFont structure and rendering +### NDK Headers +- `libraries/diskfont.h` — `FontContentsHeader`, `FontContents`, `DiskFontHeader`, `AvailFontsHeader` +- `graphics/text.h` — `TextFont`, `TextAttr`, `ColorTextFont`, style/flags constants +- `preferences.h` — `struct Preferences` (font preference fields) + +### ADCD 2.1 / ROM Kernel Manual +- *Libraries Manual* — Chapter 29: "Graphics Library and Text" — section "Composition of a Bitmap Font on Disk" +- `diskfont.library` Autodocs — `OpenDiskFont()`, `AvailFonts()` + +### External References +- **Andrew Graham's Amiga Bitmap Fonts series** — Deep dive into .font and descriptor file binary format: + - [Part 1: Introduction](https://andrewgraham.dev/blog/amiga-bitmap-fonts-part-1-introduction/) + - [Part 2: The Font Contents File](https://andrewgraham.dev/blog/amiga-bitmap-fonts-part-2-the-font-contents-file/) + - [Part 3: The Font Descriptor File](https://andrewgraham.dev/blog/amiga-bitmap-fonts-part-3-the-font-descriptor-file/) +- **smugpie/amiga-bitmap-font-tools** — Node.js tools for reading/extracting Amiga bitmap fonts: https://github.com/smugpie/amiga-bitmap-font-tools +- **Cloanto Personal Fonts Maker** — The definitive Amiga bitmap font editor +- **AmigaOS Manual: Workbench Fonts** — Official font installation guide: https://wiki.amigaos.net/wiki/AmigaOS_Manual:_Workbench_Fonts + +### Cross-References in This Knowledge Base +- [text_fonts.md](../08_graphics/text_fonts.md) — `TextFont` structure, rendering with `Text()`/`SetFont()`, algorithmic styles, font preferences +- [rastport.md](../08_graphics/rastport.md) — RastPort text rendering context, pen position, drawing mode +- [intuition_base.md](../09_intuition/intuition_base.md) — Screen font handling in Intuition +- [preferences / GetPrefs()](../07_dos/environment.md) — Reading user font preferences diff --git a/README.md b/README.md index f725200..0752ba0 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,7 @@ The Amiga's documentation was scattered across out-of-print manuals, Usenet post | [rexxsyslib.md](11_libraries/rexxsyslib.md) | ARexx interface | | [mathffp.md](11_libraries/mathffp.md) | Floating point libraries, FFP, IEEE | | [layers.md](11_libraries/layers.md) | Window clipping layers | -| [diskfont.md](11_libraries/diskfont.md) | Disk-based font loading | +| [diskfont.md](11_libraries/diskfont.md) | **Bitmap fonts: .font file format, FontContentsHeader, glyph bitmap layout, FONTS: assign, adding fonts, bitmap vs TrueType, Compugraphic outline fonts** | | [datatypes.md](11_libraries/datatypes.md) | DataTypes system: object-oriented file loading for images, sound, text, animation | | [amigaguide.md](11_libraries/amigaguide.md) | AmigaGuide hypertext help system: database format, API, context-sensitive help | | [translator.md](11_libraries/translator.md) | translator.library: English-to-phonetic translation, narrator.device integration, ARPABET phonemes |