WinComm/ansibbs/KPANSI.md
Scott Duensing 9a735c6adf Rename delphi/ to ansibbs/ in preparation for another module
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:37:00 -06:00

11 KiB

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

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

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

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
Tn Set tempo in BPM (32-255)
Ln Set default note length (1=whole, 2=half, 4=quarter, 8=eighth)
On Set octave (0-7)
< / > Shift octave down / up
A-G Play note (optional # or + for sharp, - for flat)
Pn 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.

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

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.