WinComm/delphi/KPANSI.md
Scott Duensing ec6eebb2a5 Add TrueType font support with 4-tier font selection and documentation
Replace the inherited Font property with a FontSize property that drives
a multi-tier font selection strategy for pixel-perfect OEM rendering:
Terminal raster (exact size) > Perfect DOS VGA 437 (multiples of 8px) >
Terminal raster (nearest) > stock OEM fallback.  Add DxBuf uniform
character spacing to ExtTextOut for correct TrueType monospace rendering.
Bundle the Perfect DOS VGA 437 font (cmap converted from format 6 to
format 0 for Win 3.1 compatibility).  Size the test form dynamically
from terminal dimensions.  Add component documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:15:11 -06:00

333 lines
11 KiB
Markdown

# TKPAnsi - ANSI BBS Terminal Emulation Component
TKPAnsi is a visual Delphi 1.0 component providing ANSI/VT100 terminal
emulation with scrollback, cursor blinking, and ANSI music support. It is a
TCustomControl descendant that renders incoming data using standard ANSI
escape sequences for cursor positioning, color attributes, and screen
manipulation.
Installs to the **KP** component palette tab.
## Properties
### Published (Design-Time and Runtime)
| Property | Type | Default | Description |
|---|---|---|---|
| FontSize | Integer | 12 | Font point size (4-72). Controls the OEM terminal font size. The control auto-sizes Width and Height from FontSize, Cols, and Rows. |
| Cols | Integer | 80 | Terminal columns (1-256). |
| Rows | Integer | 25 | Terminal rows (1-255). |
| ScrollbackSize | Integer | 500 | Maximum scrollback history lines. |
| CursorVisible | Boolean | True | Show or hide the cursor (also controlled by DEC mode ?25h/l). |
| Color | TColor | clBlack | Control background color. |
| OnKeyData | TKeyDataEvent | nil | Keyboard data callback. |
| TabStop | Boolean | True | Accept keyboard focus. |
### Public (Read-Only)
| Property | Type | Description |
|---|---|---|
| CursorCol | Integer | Current 0-based cursor column. |
| CursorRow | Integer | Current 0-based cursor row. |
## Methods
### Public
| Method | Description |
|---|---|
| Create(AOwner) | Constructor. Allocates screen lines, initializes defaults. |
| Destroy | Destructor. Frees paint font, screen, and scrollback. |
| Clear | Moves the current screen to scrollback and allocates fresh blank lines. Resets the cursor to 0,0. |
| Reset | Resets all attributes and parse state, then calls Clear. |
| Write(S) | Parses the string and renders immediately. Convenience wrapper around WriteDeferredBuf + FlipToScreen. |
| WriteDeferredBuf(Buf, Len) | Parses a PChar buffer into the cell buffer, then renders dirty rows. Used for bulk serial data. |
| FlipToScreen | Renders all dirty rows to the screen DC. Handles coalesced scrolls, cursor ghost cleanup, and scrollbar updates. |
| TickBlink | Toggles cursor and text blink state every 500 ms using GetTickCount. Call from the application's main loop. |
## Events
### OnKeyData
```pascal
TKeyDataEvent = procedure(Sender: TObject; const Data: string) of object;
```
Fires when keyboard input produces data to send to the serial port.
| Key | Data sent |
|---|---|
| Printable characters | The character itself |
| CR, LF, BS, TAB, ESC | The control character |
| Arrow Up/Down/Right/Left | ESC[A / ESC[B / ESC[C / ESC[D |
| Home / End | ESC[H / ESC[K |
| Page Up / Page Down | ESC[V / ESC[U |
| Insert | ESC[@ |
| Delete | #127 |
| F1-F10 | ESC OP through ESC OY |
| ENQ (#5) | ESC[?1;0c (Device Attributes response) |
| DSR query (ESC[6n) | ESC[row;colR (cursor position report) |
## Types
### TParseState
```
psNormal Normal character input
psEscape Received ESC, awaiting next byte
psCSI In CSI sequence (ESC[...), parsing parameters
psCSIQuestion DEC private mode (ESC[?...)
psMusic Accumulating ANSI music string (ESC[M...^N)
```
### TTermCell
```pascal
TTermCell = record
Ch: Char; { Display character }
FG: Byte; { Foreground palette index 0-15 }
BG: Byte; { Background palette index 0-15 }
Bold: Boolean; { Bold attribute (FG +8 at render) }
Blink: Boolean; { Blink attribute (toggles on TickBlink) }
end;
```
### TTermLineRec
```pascal
TTermLineRec = record
Cells: array[0..255] of TTermCell;
end;
PTermLine = ^TTermLineRec;
```
### ANSI Color Palette
Standard 16-color ANSI palette in BGR TColor order:
```
0 Black 8 Dark Gray
1 Red 9 Bright Red
2 Green 10 Bright Green
3 Brown/Yellow 11 Bright Yellow
4 Blue 12 Bright Blue
5 Magenta 13 Bright Magenta
6 Cyan 14 Bright Cyan
7 Light Gray 15 Bright White
```
## ANSI Escape Sequences
### CSI Sequences (ESC[ params final-byte)
| Final | Name | Description |
|---|---|---|
| A | CUU | Cursor up P1 rows (default 1) |
| B | CUD | Cursor down P1 rows (default 1) |
| C | CUF | Cursor forward P1 columns (default 1) |
| D | CUB | Cursor back P1 columns (default 1) |
| H, f | CUP/HVP | Cursor position to row P1, column P2 (1-based) |
| J | ED | Erase display: 0=below, 1=above, 2=all |
| K | EL | Erase line: 0=to end, 1=from start, 2=entire |
| L | IL | Insert P1 blank lines at cursor |
| M | DL | Delete P1 lines at cursor |
| P | DCH | Delete P1 characters at cursor |
| S | SU | Scroll up P1 lines |
| T | SD | Scroll down P1 lines |
| @ | ICH | Insert P1 blank characters at cursor |
| m | SGR | Set graphic rendition (see below) |
| s | SCP | Save cursor position |
| u | RCP | Restore cursor position |
| c | DA | Device attributes (P1=0: respond ESC[?1;0c) |
| n | DSR | Device status report (P1=5: OK, P1=6: cursor pos) |
### SGR Codes (ESC[ code m)
| Code | Effect |
|---|---|
| 0 | Reset all attributes |
| 1 | Bold (maps FG index +8 for bright colors) |
| 5 | Blink (cell visibility toggles every 500 ms) |
| 7 | Reverse video (swap FG and BG at render) |
| 22 | Normal intensity (cancel bold) |
| 25 | Blink off |
| 27 | Reverse off |
| 30-37 | Foreground color (black, red, green, yellow, blue, magenta, cyan, white) |
| 40-47 | Background color (same order) |
### DEC Private Modes (ESC[? code h/l)
| Mode | h (set) | l (reset) |
|---|---|---|
| ?7 | Enable auto-wrap at right margin | Disable auto-wrap |
| ?25 | Show cursor | Hide cursor |
### Control Characters
| Char | Action |
|---|---|
| BEL (#7) | MessageBeep |
| BS (#8) | Cursor left one column |
| TAB (#9) | Advance to next tab stop (multiple of 8) |
| LF (#10) | Cursor down; scroll at bottom |
| CR (#13) | Cursor to column 0 |
| ENQ (#5) | Respond with device attributes |
## ANSI Music
ANSI music is triggered by `ESC[M` and terminated by `^N` (Ctrl-N, #14).
The string between these delimiters contains musical notation:
| Command | Description |
|---|---|
| T*n* | Set tempo in BPM (32-255) |
| L*n* | Set default note length (1=whole, 2=half, 4=quarter, 8=eighth) |
| O*n* | Set octave (0-7) |
| < / > | Shift octave down / up |
| A-G | Play note (optional # or + for sharp, - for flat) |
| P*n* | Pause for duration *n* |
Notes accept an optional duration digit after the letter (e.g., C4 = quarter
note C). A trailing dot adds 50% to the duration.
Uses the Windows 3.1 sound API: OpenSound, SetVoiceAccent, SetVoiceNote,
StartSound, CloseSound.
## Font Selection
CreatePaintFont selects the best available font for the requested FontSize.
The selection order ensures pixel-perfect rendering when possible:
1. **Terminal raster at the exact size.** If the Terminal font has a raster
at the requested cell height, use it. Terminal is pixel-perfect but only
available at specific heights (typically 12 and 24 pixels on VGA).
2. **Perfect DOS VGA 437 at multiples of 8.** If the requested cell height
is a multiple of 8 (8, 16, 24, 32, ...) and the font is installed, use
it. This TrueType CP437 font renders pixel-perfectly at these sizes.
Install `DOSVGA.TTF` via Control Panel > Fonts.
3. **Terminal raster at the nearest available size.** Search outward from
the requested height (alternating up and down) to find the closest
Terminal raster variant.
4. **Stock OEM_FIXED_FONT.** Last resort. Always available.
### Supported Point Sizes (96 DPI)
| FontSize | Cell Height | Font Used |
|---|---|---|
| 6 | 8 | Perfect DOS VGA 437 |
| 9 | 12 | Terminal raster |
| 12 | 16 | Perfect DOS VGA 437 |
| 18 | 24 | Terminal raster |
| 24 | 32 | Perfect DOS VGA 437 |
| 30 | 40 | Perfect DOS VGA 437 |
| 36 | 48 | Perfect DOS VGA 437 |
Other sizes fall back to the nearest Terminal raster height.
### Installing the Perfect DOS VGA 437 Font
1. Open Control Panel and double-click Fonts.
2. Click Add.
3. Browse to the directory containing `DOSVGA.TTF` and select it.
4. Click OK to install.
The font is free for personal and commercial use. See `fonts/DOSVGA.TXT`
for license details.
## Rendering Architecture
### Split-Phase Design
Rendering is split into parsing and drawing to avoid interleaving CPU work
with GDI kernel transitions:
1. **Parse phase** (ParseDataBuf): Pure CPU loop that fills the FScreen cell
buffer with characters and attributes. No GDI calls. Three inline fast
paths eliminate function-call overhead for printable runs, CSI parameter
accumulation, and common control characters (ESC, CR, LF).
2. **Render phase** (FlipToScreen or WriteDeferredBuf): Flushes coalesced
scrolls via a single ScrollDC call, then redraws only dirty rows via
RenderRow.
### Dirty Row Tracking
Each row has a boolean dirty flag (FDirtyRow). FAllDirty forces a full
redraw. DirtyBlinkRows marks only the cursor row and rows containing blink
cells, reducing blink overhead from ~63 ms to ~3 ms on a 486.
### RenderRow
Scans cells for color runs (consecutive cells with the same effective FG and
BG). Each run is drawn with a single ExtTextOut call using ETO_OPAQUE
(fills background and renders text in one operation). A DxBuf array forces
uniform character spacing for TrueType fonts.
### Scroll Coalescing
Multiple scroll-up operations during a parse batch are accumulated in
FPendingScrolls. The render phase issues one ScrollDC to shift on-screen
pixels, then only redraws the newly exposed bottom rows.
### Blink
Cursor and text blink are driven by TickBlink, which uses GetTickCount to
toggle FBlinkOn every 500 ms. No WM_TIMER is used. The application calls
TickBlink from its main loop.
## Scrollback Buffer
- **FScreen**: TList of PTermLine holding the active terminal display.
- **FScrollback**: TList of PTermLine holding history (up to ScrollbackSize).
- **FScrollPos**: 0 = live view, >0 = viewing history (lines scrolled back).
- Vertical scrollbar (WS_VSCROLL) allows the user to scroll through history.
- TrimScrollback batch-frees excess lines in a single pass.
## Usage Example
```pascal
FAnsi := TKPAnsi.Create(Self);
FAnsi.FontSize := 12;
FAnsi.Parent := Self;
FAnsi.Left := 0;
FAnsi.Top := 38;
FAnsi.OnKeyData := AnsiKeyData;
{ In the main loop: }
Len := FComm.ReadInputBuf(@Buf, BufSize);
if Len > 0 then
FAnsi.WriteDeferredBuf(@Buf, Len);
FAnsi.TickBlink;
FAnsi.FlipToScreen;
{ Size the form to fit: }
ClientWidth := FAnsi.Width;
ClientHeight := FAnsi.Top + FAnsi.Height;
```
## Platform Notes
- Targets Windows 3.1 / Delphi 1.0.
- Uses CS_OWNDC for a persistent device context (font and background mode
survive across GetDC/ReleaseDC cycles).
- Uses `{ }` comments (Delphi 1.0 does not support `//` comments).
- The Perfect DOS VGA 437 TrueType font requires Windows 3.1's TrueType
rasterizer. The font's cmap was converted from format 6 to format 0 and
its OS/2 table downgraded from version 3 to version 1 for Win 3.1
compatibility.
- Requested with DEFAULT_CHARSET (not OEM_CHARSET) to prevent Win 3.1's
font mapper from rejecting TrueType fonts in favor of raster substitutes.