Add loadable font support: .FON/.FNT loading with v2→v3 conversion

Load Windows 3.x .FON (NE resource containers) and raw .FNT font files
for use with ExtTextOut. Multiple fonts can be active simultaneously.
All available .FON files contain v2 fonts but VBESVGA.DRV requires v3
in 386 protected mode, so the loader converts on load.

- Add NE resource table structures (NeResourceTypeT, NeResourceEntryT)
- Add WdrvFontT opaque type with load/unload API
- Implement buildFontFromFnt() v2→v3 converter
- Implement wdrvLoadFontFon() NE resource parser
- Move font from per-driver to global singleton (wdrvLoadFontBuiltin)
- Add WdrvFontT parameter to wdrvExtTextOut (NULL = built-in)
- Add Demo 6: Courier, Sans Serif, System fonts side by side
- Copy fon/*.FON to bin/ during build

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Duensing 2026-02-21 23:04:30 -06:00
parent 946719052f
commit e8a7812233
23 changed files with 553 additions and 62 deletions

View file

@ -110,6 +110,17 @@ windriver/
- ET4000 is 640x480 8bpp, software-rendered (no accelerator engine in DOSBox-X)
- CR30=0x00 on ET4000 → isS3=false → no S3 engine wait, no display start shift
## Font Loading Notes
- .FON files are NE containers with RT_FONT (type 8) resources; each resource is raw .FNT data
- All Win 3.x .FON files contain v2 fonts (0x0200); VBESVGA.DRV requires v3 (0x0300)
- v2→v3 conversion: insert 30-byte extension at 0x76, expand 4-byte char table to 6-byte entries
- **v2 char table offsets are absolute from segment base**, not relative to fsBitsOffset.
Correct v3 offset = v2offset + shift (where shift = newBitmapOff - origBitsOff)
- `wdrvLoadFontFon(path, index)` loads from .FON; `wdrvLoadFontFnt(path)` loads raw .FNT
- `wdrvLoadFontBuiltin()` returns the VGA ROM 8x16 singleton; must NOT be passed to wdrvUnloadFont
- `wdrvExtTextOut` takes a `WdrvFontT font` parameter (NULL = built-in)
- Available test fonts in `fon/`: COURE.FON (8x13, 9x16, 12x20), SSERIFE.FON, SERIFE.FON, VGASYS.FON, etc.
## Current Demo Status
- S3TRIO.DRV, VBESVGA.DRV, VGA.DRV, ET4000.DRV all work: Load → Enable → Draw → Disable → Unload
- Demo 1: Fill rectangles (BitBlt) — works

View file

@ -47,6 +47,7 @@ $(DEMO_EXE): $(DEMO_OBJ) lib | $(BINDIR)
unzip -oj tools/cwsdpmi.zip bin/CWSDPMI.EXE -d $(BINDIR) 2>/dev/null; true
cp tools/TEST.BAT $(BINDIR)/
-cp -n drivers/*.DRV $(BINDIR)/ 2>/dev/null; true
-cp -n fon/*.FON $(BINDIR)/ 2>/dev/null; true
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<

64
demo.c
View file

@ -314,33 +314,87 @@ static void demoDrawing(WdrvHandleT drv)
logMsg("Demo 5: ExtTextOut text rendering\n");
int32_t ret;
// Opaque text: white on blue
// Opaque text: white on blue (built-in font via NULL)
const char *msg1 = "Hello from ExtTextOut!";
ret = wdrvExtTextOut(drv, 10, 10, msg1, (int16_t)strlen(msg1),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 170), true);
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 170), true, NULL);
logMsg(" msg1 ret=%" PRId32 "\n", ret);
// Opaque text: yellow on dark red
const char *msg2 = "Win3.x DDI Text Output";
ret = wdrvExtTextOut(drv, 10, 30, msg2, (int16_t)strlen(msg2),
MAKE_RGB(255, 255, 0), MAKE_RGB(170, 0, 0), true);
MAKE_RGB(255, 255, 0), MAKE_RGB(170, 0, 0), true, NULL);
logMsg(" msg2 ret=%" PRId32 "\n", ret);
// Transparent text: green on existing background
const char *msg3 = "Transparent mode";
ret = wdrvExtTextOut(drv, 10, 50, msg3, (int16_t)strlen(msg3),
MAKE_RGB(0, 255, 0), MAKE_RGB(0, 0, 0), false);
MAKE_RGB(0, 255, 0), MAKE_RGB(0, 0, 0), false, NULL);
logMsg(" msg3 ret=%" PRId32 "\n", ret);
// Show resolution info
char buf[64];
int len = sprintf(buf, "Mode: %dx%d", screenW, screenH);
ret = wdrvExtTextOut(drv, 10, 70, buf, (int16_t)len,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), true);
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), true, NULL);
logMsg(" msg4 ret=%" PRId32 "\n", ret);
logMsg(" Text output done\n");
}
// Demo 6: Multiple loaded fonts
if (info.hasExtTextOut) {
logMsg("Demo 6: Loaded fonts from .FON files\n");
int32_t ret;
// Load fonts from .FON files
WdrvFontT courFont = wdrvLoadFontFon("COURE.FON", 1); // 9x16 Courier
WdrvFontT sansFont = wdrvLoadFontFon("SSERIFE.FON", 0); // MS Sans Serif
WdrvFontT sysFont = wdrvLoadFontFon("VGASYS.FON", 0); // System
int16_t textY = 100;
if (courFont) {
const char *msg = "Courier: The quick brown fox jumps over the lazy dog";
ret = wdrvExtTextOut(drv, 10, textY, msg, (int16_t)strlen(msg),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 128), true, courFont);
logMsg(" Courier ret=%" PRId32 "\n", ret);
textY += 20;
} else {
logMsg(" COURE.FON not loaded\n");
}
if (sansFont) {
const char *msg = "Sans Serif: The quick brown fox jumps over the lazy dog";
ret = wdrvExtTextOut(drv, 10, textY, msg, (int16_t)strlen(msg),
MAKE_RGB(255, 255, 0), MAKE_RGB(128, 0, 0), true, sansFont);
logMsg(" Sans ret=%" PRId32 "\n", ret);
textY += 20;
} else {
logMsg(" SSERIFE.FON not loaded\n");
}
if (sysFont) {
const char *msg = "System: The quick brown fox jumps over the lazy dog";
ret = wdrvExtTextOut(drv, 10, textY, msg, (int16_t)strlen(msg),
MAKE_RGB(0, 255, 255), MAKE_RGB(0, 0, 0), true, sysFont);
logMsg(" System ret=%" PRId32 "\n", ret);
textY += 20;
} else {
logMsg(" VGASYS.FON not loaded\n");
}
// Built-in font for comparison
const char *msg = "Built-in: The quick brown fox jumps over the lazy dog";
ret = wdrvExtTextOut(drv, 10, textY, msg, (int16_t)strlen(msg),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 128, 0), true, NULL);
logMsg(" Built-in ret=%" PRId32 "\n", ret);
wdrvUnloadFont(courFont);
wdrvUnloadFont(sansFont);
wdrvUnloadFont(sysFont);
logMsg(" Font demo done\n");
}
}

BIN
fon/CGA40WOA.FON (Stored with Git LFS) Normal file

Binary file not shown.

BIN
fon/CGA80WOA.FON (Stored with Git LFS) Normal file

Binary file not shown.

BIN
fon/COURE.FON (Stored with Git LFS) Normal file

Binary file not shown.

BIN
fon/DOSAPP.FON (Stored with Git LFS) Normal file

Binary file not shown.

BIN
fon/EGA40WOA.FON (Stored with Git LFS) Normal file

Binary file not shown.

BIN
fon/EGA80WOA.FON (Stored with Git LFS) Normal file

Binary file not shown.

BIN
fon/MODERN.FON (Stored with Git LFS) Normal file

Binary file not shown.

BIN
fon/ROMAN.FON (Stored with Git LFS) Normal file

Binary file not shown.

BIN
fon/SCRIPT.FON (Stored with Git LFS) Normal file

Binary file not shown.

BIN
fon/SERIFE.FON (Stored with Git LFS) Normal file

Binary file not shown.

BIN
fon/SMALLE.FON (Stored with Git LFS) Normal file

Binary file not shown.

BIN
fon/SSERIFE.FON (Stored with Git LFS) Normal file

Binary file not shown.

BIN
fon/SYMBOLE.FON (Stored with Git LFS) Normal file

Binary file not shown.

BIN
fon/VGAFIX.FON (Stored with Git LFS) Normal file

Binary file not shown.

BIN
fon/VGAOEM.FON (Stored with Git LFS) Normal file

Binary file not shown.

BIN
fon/VGASYS.FON (Stored with Git LFS) Normal file

Binary file not shown.

2
run.sh Executable file
View file

@ -0,0 +1,2 @@
#!/bin/bash
flatpak run com.dosbox_x.DOSBox-X -conf dosbox-x.conf

View file

@ -188,6 +188,26 @@ typedef struct __attribute__((packed)) {
#define NE_ENTRY_EXPORTED 0x01 // Entry is exported
#define NE_ENTRY_SHDATA 0x02 // Entry uses shared data segment
// ============================================================================
// NE resource table structures
// ============================================================================
#define RT_FONT 8
typedef struct __attribute__((packed)) {
uint16_t fileOffset; // shifted by rscAlignShift
uint16_t length; // shifted by rscAlignShift
uint16_t flags;
uint16_t resourceId; // high bit set = integer ID
uint32_t reserved;
} NeResourceEntryT; // 12 bytes
typedef struct __attribute__((packed)) {
uint16_t typeId; // 0x8000|type = integer type, 0 = end
uint16_t count;
uint32_t reserved;
} NeResourceTypeT; // 8 bytes
// ============================================================================
// Display driver ordinal numbers (standard DDI exports)
// ============================================================================

View file

@ -107,12 +107,6 @@ struct WdrvDriverS {
uint16_t drawModeOff;
uint32_t drawModeLinear;
// Font (.FNT v3 format, separate 16-bit block)
uint16_t fontSel;
uint32_t fontLinear;
uint32_t fontSize;
bool fontInitialized;
// Current state
bool enabled;
uint32_t currentColor;
@ -132,6 +126,20 @@ struct WdrvDriverS {
bool isS3;
};
// ============================================================================
// Font instance structure
// ============================================================================
struct WdrvFontS {
uint16_t fontSel;
uint32_t fontLinear;
uint32_t fontSize;
char faceName[64];
uint16_t pixHeight;
uint16_t pixWidth; // 0 = proportional
uint16_t fsVersion; // original version before conversion
};
// ============================================================================
// Global state
// ============================================================================
@ -142,6 +150,8 @@ static bool gInitialized = false;
static int32_t gLastError = WDRV_OK;
static bool gDebug = false;
static bool gIsS3 = false;
static struct WdrvFontS gBuiltinFont;
static bool gBuiltinFontInit = false;
// Forward declarations
@ -158,7 +168,8 @@ static uint32_t colorToPhys(struct WdrvDriverS *drv, uint32_t colorRef);
static void setDisplayStart(struct WdrvDriverS *drv, uint32_t byteOffset);
static void freeDrawObjects(struct WdrvDriverS *drv);
static bool initFont(struct WdrvDriverS *drv);
static bool initBuiltinFont(void);
static WdrvFontT buildFontFromFnt(const uint8_t *fntData, uint32_t fntSize);
static uint16_t alloc16BitBlock(uint32_t size, uint32_t *linearOut);
static void free16BitBlock(uint16_t sel, uint32_t linear);
static void setError(int32_t err);
@ -1079,6 +1090,13 @@ void wdrvShutdown(void)
return;
}
// Free the built-in font singleton
if (gBuiltinFontInit) {
free16BitBlock(gBuiltinFont.fontSel, gBuiltinFont.fontLinear);
memset(&gBuiltinFont, 0, sizeof(gBuiltinFont));
gBuiltinFontInit = false;
}
removeExceptionCapture();
removeInt2FhHandler();
removeDpmi300Proxy();
@ -2106,7 +2124,7 @@ int32_t wdrvSetPalette(WdrvHandleT handle, int32_t startIndex, int32_t count, co
int32_t wdrvExtTextOut(WdrvHandleT handle, int16_t x, int16_t y,
const char *text, int16_t length,
uint32_t fgColor, uint32_t bgColor,
bool opaque)
bool opaque, WdrvFontT font)
{
if (!handle || !handle->enabled) {
return WDRV_ERR_NOT_ENABLED;
@ -2115,9 +2133,10 @@ int32_t wdrvExtTextOut(WdrvHandleT handle, int16_t x, int16_t y,
return WDRV_ERR_UNSUPPORTED;
}
// Lazy-init the VGA BIOS font
if (!handle->fontInitialized) {
if (!initFont(handle)) {
// Use built-in font if none specified
if (!font) {
font = wdrvLoadFontBuiltin();
if (!font) {
logErr("windrv: ExtTextOut: font init failed\n");
return WDRV_ERR_NO_MEMORY;
}
@ -2190,7 +2209,7 @@ int32_t wdrvExtTextOut(WdrvHandleT handle, int16_t x, int16_t y,
params[i++] = strSel; // lpString seg
params[i++] = 0; // lpString off
params[i++] = (uint16_t)length; // count
params[i++] = handle->fontSel; // lpFont seg
params[i++] = font->fontSel; // lpFont seg
params[i++] = 0x0042; // lpFont off (fsType)
params[i++] = dgSel; // lpDrawMode seg
params[i++] = handle->drawModeOff; // lpDrawMode off
@ -2203,7 +2222,7 @@ int32_t wdrvExtTextOut(WdrvHandleT handle, int16_t x, int16_t y,
params[i++] = 0; // wOptions
dbg("windrv: ExtTextOut(%d,%d) len=%d font=%04X:0042 dm=%04X:%04X fg=0x%08lX bg=0x%08lX %s\n",
x, y, length, handle->fontSel, dgSel, handle->drawModeOff,
x, y, length, font->fontSel, dgSel, handle->drawModeOff,
(unsigned long)physFg, (unsigned long)physBg,
opaque ? "OPAQUE" : "TRANSPARENT");
@ -2719,27 +2738,20 @@ static void freeDrawObjects(struct WdrvDriverS *drv)
// Objects are embedded in DGROUP - freed when module is unloaded
drv->brushRealized = false;
drv->penRealized = false;
// Font is a separate 16-bit block
if (drv->fontInitialized) {
free16BitBlock(drv->fontSel, drv->fontLinear);
drv->fontSel = 0;
drv->fontLinear = 0;
drv->fontInitialized = false;
}
}
// Build a .FNT v3 font structure from the VGA BIOS 8x16 ROM font.
// ============================================================================
// Font loading
//
// v3 layout offsets:
// 0x0000: FntHeader16T (117 bytes)
// 0x0075: pad byte
// 0x0076: v3 extension (fsFlags..fsReserved, 30 bytes)
// 0x0094: v3 character table (257 entries x 6 bytes = 1542)
// 0x069A: bitmap data (256 chars x 16 rows = 4096)
// 0x169A: device name "\0"
// 0x169B: face name "System\0"
// buildFontFromFnt() converts raw .FNT data (v2 or v3) into a 16-bit
// accessible font block in v3 format. v2->v3 conversion expands the
// 4-byte char table entries to 6-byte entries and adjusts all offsets.
//
// initBuiltinFont() builds a v3 font from the VGA BIOS 8x16 ROM font.
// ============================================================================
// Built-in font layout offsets (fixed for the synthesized VGA ROM font)
#define FONT_V3EXT_OFF 0x0076
#define FONT_CHAR_TABLE_OFF 0x0094
#define FONT_BITMAP_OFF 0x069A
@ -2747,7 +2759,145 @@ static void freeDrawObjects(struct WdrvDriverS *drv)
#define FONT_FACENAME_OFF 0x169B
#define FONT_TOTAL_SIZE 0x16A2
static bool initFont(struct WdrvDriverS *drv)
static WdrvFontT buildFontFromFnt(const uint8_t *fntData, uint32_t fntSize)
{
if (fntSize < sizeof(FntHeader16T)) {
logErr("windrv: buildFontFromFnt: data too small (%" PRIu32 " bytes)\n", fntSize);
setError(WDRV_ERR_BAD_FONT);
return NULL;
}
const FntHeader16T *srcHdr = (const FntHeader16T *)fntData;
if (srcHdr->fsVersion != 0x0200 && srcHdr->fsVersion != 0x0300) {
logErr("windrv: buildFontFromFnt: unsupported version 0x%04X\n", srcHdr->fsVersion);
setError(WDRV_ERR_BAD_FONT);
return NULL;
}
if (srcHdr->fsType != 0) {
logErr("windrv: buildFontFromFnt: not a raster font (type=%u)\n", srcHdr->fsType);
setError(WDRV_ERR_BAD_FONT);
return NULL;
}
uint16_t origVersion = srcHdr->fsVersion;
uint16_t nChars = (uint16_t)(srcHdr->fsLastChar - srcHdr->fsFirstChar + 1);
uint32_t newSize;
uint32_t linearOut;
uint16_t sel;
uint8_t *base;
if (origVersion == 0x0300) {
// Already v3 — allocate, copy, patch fsBitsPointer
newSize = fntSize;
sel = alloc16BitBlock(newSize, &linearOut);
if (sel == 0) {
setError(WDRV_ERR_NO_MEMORY);
return NULL;
}
base = (uint8_t *)linearOut;
memcpy(base, fntData, fntSize);
FntHeader16T *hdr = (FntHeader16T *)base;
hdr->fsBitsPointer = ((uint32_t)sel << 16) | hdr->fsBitsOffset;
} else {
// v2 -> v3 conversion
//
// v2 layout:
// 0x0000: Header (117 bytes)
// 0x0075: Pad byte
// 0x0076: v2 char table ((nChars+1) * 4 bytes)
// fsBitsOffset: Bitmap data + strings
//
// v3 layout:
// 0x0000: Header (117 bytes, fsVersion -> 0x0300)
// 0x0075: Pad byte
// 0x0076: v3 extension (30 bytes, zeroed)
// 0x0094: v3 char table ((nChars+1) * 6 bytes)
// newBitmapOff: Bitmap data + strings (copied)
uint32_t v3CharTableOff = 0x0094;
uint32_t newBitmapOff = v3CharTableOff + (uint32_t)(nChars + 1) * 6;
uint32_t origBitsOff = srcHdr->fsBitsOffset;
uint32_t bitmapLen = fntSize - origBitsOff;
newSize = newBitmapOff + bitmapLen;
sel = alloc16BitBlock(newSize, &linearOut);
if (sel == 0) {
setError(WDRV_ERR_NO_MEMORY);
return NULL;
}
base = (uint8_t *)linearOut;
memset(base, 0, newSize);
// Copy the 117-byte header
memcpy(base, fntData, sizeof(FntHeader16T));
// Patch header for v3
FntHeader16T *hdr = (FntHeader16T *)base;
hdr->fsVersion = 0x0300;
hdr->fsSize = newSize;
hdr->fsBitsOffset = newBitmapOff;
hdr->fsBitsPointer = ((uint32_t)sel << 16) | newBitmapOff;
// Adjust string offsets that point into the bitmap area
int32_t shift = (int32_t)newBitmapOff - (int32_t)origBitsOff;
if (hdr->fsDevice >= origBitsOff) {
hdr->fsDevice += shift;
}
if (hdr->fsFace >= origBitsOff) {
hdr->fsFace += shift;
}
// v3 extension at 0x76 is already zeroed (30 bytes)
// Convert v2 char table to v3 char table
const FntCharEntry16T *v2Table = (const FntCharEntry16T *)(fntData + 0x0076);
FntCharEntry30T *v3Table = (FntCharEntry30T *)(base + v3CharTableOff);
for (uint16_t c = 0; c <= nChars; c++) {
v3Table[c].width = v2Table[c].width;
v3Table[c].offset = (uint32_t)((int32_t)v2Table[c].offset + shift);
}
// Copy bitmap data + string data
memcpy(base + newBitmapOff, fntData + origBitsOff, bitmapLen);
}
// Allocate the font handle
struct WdrvFontS *font = (struct WdrvFontS *)calloc(1, sizeof(struct WdrvFontS));
if (!font) {
free16BitBlock(sel, linearOut);
setError(WDRV_ERR_NO_MEMORY);
return NULL;
}
FntHeader16T *hdr = (FntHeader16T *)base;
font->fontSel = sel;
font->fontLinear = linearOut;
font->fontSize = newSize;
font->pixHeight = hdr->fsPixHeight;
font->pixWidth = hdr->fsPixWidth;
font->fsVersion = origVersion;
// Read face name string from the font block
if (hdr->fsFace < newSize) {
strncpy(font->faceName, (const char *)(base + hdr->fsFace), sizeof(font->faceName) - 1);
font->faceName[sizeof(font->faceName) - 1] = '\0';
} else {
strcpy(font->faceName, "Unknown");
}
dbg("windrv: buildFontFromFnt: \"%s\" %ux%u v%c->v3 sel=%04X size=%" PRIu32 "\n",
font->faceName, font->pixWidth, font->pixHeight,
origVersion == 0x0200 ? '2' : '3', sel, newSize);
return font;
}
static bool initBuiltinFont(void)
{
// Get VGA BIOS 8x16 font pointer via INT 10h AH=11h AL=30h BH=06
__dpmi_regs regs;
@ -2762,21 +2912,21 @@ static bool initFont(struct WdrvDriverS *drv)
uint8_t vgaFont[4096];
dosmemget(fontRmAddr, 4096, vgaFont);
dbg("windrv: initFont: VGA 8x16 font at real %04X:%04X (linear 0x%08lX)\n",
dbg("windrv: initBuiltinFont: VGA 8x16 font at real %04X:%04X (linear 0x%08lX)\n",
regs.x.es, regs.x.bp, (unsigned long)fontRmAddr);
// Allocate a 16-bit accessible block for the .FNT v3 structure
uint32_t linearOut;
uint16_t sel = alloc16BitBlock(FONT_TOTAL_SIZE, &linearOut);
if (sel == 0) {
logErr("windrv: initFont: failed to allocate font block\n");
logErr("windrv: initBuiltinFont: failed to allocate font block\n");
return false;
}
uint8_t *base = (uint8_t *)linearOut;
memset(base, 0, FONT_TOTAL_SIZE);
// Fill the .FNT v3 header (same base structure as v2)
// Fill the .FNT v3 header
FntHeader16T *hdr = (FntHeader16T *)base;
hdr->fsVersion = 0x0300;
hdr->fsSize = FONT_TOTAL_SIZE;
@ -2807,21 +2957,16 @@ static bool initFont(struct WdrvDriverS *drv)
hdr->fsBitsPointer = ((uint32_t)sel << 16) | FONT_BITMAP_OFF;
hdr->fsBitsOffset = FONT_BITMAP_OFF;
// v3 extension fields at 0x76 are already zeroed (fsFlags, fsAspace,
// fsBspace, fsCspace, fsColorPointer, fsReserved)
// v3 extension fields at 0x76 are already zeroed
// Build v3 character table (257 entries x 6 bytes: 256 chars + sentinel)
// v3 entries have a DWORD absolute offset from segment base.
// Glyph data is per-character contiguous: char C's 16 rows at
// FONT_BITMAP_OFF + C*16.
// Build v3 character table (257 entries: 256 chars + sentinel)
FntCharEntry30T *charTable = (FntCharEntry30T *)(base + FONT_CHAR_TABLE_OFF);
for (int c = 0; c <= 256; c++) {
charTable[c].width = 8;
charTable[c].offset = FONT_BITMAP_OFF + (uint32_t)(c < 256 ? c : 0) * 16;
}
// Copy VGA ROM glyphs directly — already in per-character contiguous
// format (16 consecutive bytes per character, stride=1 between rows)
// Copy VGA ROM glyphs directly
memcpy(base + FONT_BITMAP_OFF, vgaFont, 4096);
// Device name (empty string)
@ -2830,20 +2975,207 @@ static bool initFont(struct WdrvDriverS *drv)
// Face name
memcpy(base + FONT_FACENAME_OFF, "System", 7);
drv->fontSel = sel;
drv->fontLinear = linearOut;
drv->fontSize = FONT_TOTAL_SIZE;
drv->fontInitialized = true;
gBuiltinFont.fontSel = sel;
gBuiltinFont.fontLinear = linearOut;
gBuiltinFont.fontSize = FONT_TOTAL_SIZE;
gBuiltinFont.pixHeight = 16;
gBuiltinFont.pixWidth = 8;
gBuiltinFont.fsVersion = 0x0300;
strcpy(gBuiltinFont.faceName, "System");
gBuiltinFontInit = true;
dbg("windrv: initFont: v3 font block sel=%04X linear=0x%08lX size=%u\n",
dbg("windrv: initBuiltinFont: v3 font block sel=%04X linear=0x%08lX size=%u\n",
sel, (unsigned long)linearOut, FONT_TOTAL_SIZE);
dbg("windrv: initFont: fsBitsPointer=%08lX charTable@0x%04X bitmap@0x%04X\n",
(unsigned long)hdr->fsBitsPointer, FONT_CHAR_TABLE_OFF, FONT_BITMAP_OFF);
return true;
}
WdrvFontT wdrvLoadFontBuiltin(void)
{
if (!gBuiltinFontInit) {
if (!initBuiltinFont()) {
return NULL;
}
}
return &gBuiltinFont;
}
WdrvFontT wdrvLoadFontFnt(const char *fntPath)
{
FILE *f = fopen(fntPath, "rb");
if (!f) {
logErr("windrv: wdrvLoadFontFnt: cannot open '%s'\n", fntPath);
setError(WDRV_ERR_FILE_NOT_FOUND);
return NULL;
}
fseek(f, 0, SEEK_END);
long fileSize = ftell(f);
fseek(f, 0, SEEK_SET);
if (fileSize < (long)sizeof(FntHeader16T) || fileSize > 0x100000) {
logErr("windrv: wdrvLoadFontFnt: bad file size %ld\n", fileSize);
fclose(f);
setError(WDRV_ERR_BAD_FONT);
return NULL;
}
uint8_t *buf = (uint8_t *)malloc((uint32_t)fileSize);
if (!buf) {
fclose(f);
setError(WDRV_ERR_NO_MEMORY);
return NULL;
}
if (fread(buf, 1, (uint32_t)fileSize, f) != (size_t)fileSize) {
logErr("windrv: wdrvLoadFontFnt: read error\n");
free(buf);
fclose(f);
setError(WDRV_ERR_BAD_FONT);
return NULL;
}
fclose(f);
WdrvFontT font = buildFontFromFnt(buf, (uint32_t)fileSize);
free(buf);
return font;
}
WdrvFontT wdrvLoadFontFon(const char *fonPath, int32_t fontIndex)
{
FILE *f = fopen(fonPath, "rb");
if (!f) {
logErr("windrv: wdrvLoadFontFon: cannot open '%s'\n", fonPath);
setError(WDRV_ERR_FILE_NOT_FOUND);
return NULL;
}
// Read MZ header
MzHeaderT mz;
if (fread(&mz, sizeof(mz), 1, f) != 1 || mz.signature != MZ_SIGNATURE) {
logErr("windrv: wdrvLoadFontFon: not a valid MZ executable\n");
fclose(f);
setError(WDRV_ERR_BAD_FORMAT);
return NULL;
}
// Read NE header
uint32_t neOff = mz.neHeaderOffset;
fseek(f, (long)neOff, SEEK_SET);
NeHeaderT ne;
if (fread(&ne, sizeof(ne), 1, f) != 1 || ne.signature != NE_SIGNATURE) {
logErr("windrv: wdrvLoadFontFon: not a valid NE executable\n");
fclose(f);
setError(WDRV_ERR_BAD_FORMAT);
return NULL;
}
// Seek to resource table
uint32_t resTableOff = neOff + ne.resourceTableOffset;
fseek(f, (long)resTableOff, SEEK_SET);
// Read resource alignment shift
uint16_t rscAlignShift;
if (fread(&rscAlignShift, 2, 1, f) != 1) {
logErr("windrv: wdrvLoadFontFon: cannot read resource table\n");
fclose(f);
setError(WDRV_ERR_BAD_FORMAT);
return NULL;
}
// Walk type groups looking for RT_FONT
WdrvFontT result = NULL;
for (;;) {
NeResourceTypeT typeHdr;
if (fread(&typeHdr, sizeof(typeHdr), 1, f) != 1) {
break;
}
if (typeHdr.typeId == 0) {
break; // end of resource table
}
uint16_t typeNum = typeHdr.typeId & 0x7FFF;
if (typeNum == RT_FONT) {
// Found font resources — read all entries
if (fontIndex < 0 || fontIndex >= (int32_t)typeHdr.count) {
logErr("windrv: wdrvLoadFontFon: fontIndex %" PRId32 " out of range (0..%u)\n",
fontIndex, typeHdr.count - 1);
fclose(f);
setError(WDRV_ERR_BAD_FONT);
return NULL;
}
// Read all entries to get to the requested one
NeResourceEntryT *entries = (NeResourceEntryT *)malloc(
(uint32_t)typeHdr.count * sizeof(NeResourceEntryT));
if (!entries) {
fclose(f);
setError(WDRV_ERR_NO_MEMORY);
return NULL;
}
if (fread(entries, sizeof(NeResourceEntryT), typeHdr.count, f) != typeHdr.count) {
logErr("windrv: wdrvLoadFontFon: cannot read resource entries\n");
free(entries);
fclose(f);
setError(WDRV_ERR_BAD_FORMAT);
return NULL;
}
uint32_t fntOffset = (uint32_t)entries[fontIndex].fileOffset << rscAlignShift;
uint32_t fntLength = (uint32_t)entries[fontIndex].length << rscAlignShift;
free(entries);
dbg("windrv: wdrvLoadFontFon: font[%" PRId32 "] offset=0x%08" PRIX32 " length=%" PRIu32 "\n",
fontIndex, fntOffset, fntLength);
// Read the .FNT data
uint8_t *fntBuf = (uint8_t *)malloc(fntLength);
if (!fntBuf) {
fclose(f);
setError(WDRV_ERR_NO_MEMORY);
return NULL;
}
fseek(f, (long)fntOffset, SEEK_SET);
if (fread(fntBuf, 1, fntLength, f) != fntLength) {
logErr("windrv: wdrvLoadFontFon: cannot read font data\n");
free(fntBuf);
fclose(f);
setError(WDRV_ERR_BAD_FONT);
return NULL;
}
fclose(f);
result = buildFontFromFnt(fntBuf, fntLength);
free(fntBuf);
return result;
}
// Skip past this type's resource entries
fseek(f, (long)typeHdr.count * sizeof(NeResourceEntryT), SEEK_CUR);
}
logErr("windrv: wdrvLoadFontFon: no FONT resources found in '%s'\n", fonPath);
fclose(f);
setError(WDRV_ERR_BAD_FONT);
return NULL;
}
void wdrvUnloadFont(WdrvFontT font)
{
if (!font || font == &gBuiltinFont) {
return;
}
free16BitBlock(font->fontSel, font->fontLinear);
free(font);
}
static uint16_t alloc16BitBlock(uint32_t size, uint32_t *linearOut)
{
uint8_t *mem = (uint8_t *)calloc(1, size);

View file

@ -45,12 +45,14 @@
#define WDRV_ERR_NOT_LOADED -11 // No driver loaded
#define WDRV_ERR_NOT_ENABLED -12 // Driver not enabled
#define WDRV_ERR_UNSUPPORTED -13 // Operation not supported by driver
#define WDRV_ERR_BAD_FONT -14 // Invalid font file or data
// ============================================================================
// Opaque driver handle
// ============================================================================
typedef struct WdrvDriverS *WdrvHandleT;
typedef struct WdrvFontS *WdrvFontT;
// ============================================================================
// Driver information (returned by wdrvGetInfo)
@ -149,11 +151,32 @@ int32_t wdrvPolyline(WdrvHandleT handle, Point16T *points, int16_t count, uint32
int32_t wdrvRectangle(WdrvHandleT handle, int16_t x, int16_t y, int16_t w, int16_t h, uint32_t color);
// Draw text using the ExtTextOut DDI function.
// Uses a built-in 8x16 VGA bitmap font.
// Pass NULL for font to use the built-in 8x16 VGA bitmap font.
int32_t wdrvExtTextOut(WdrvHandleT handle, int16_t x, int16_t y,
const char *text, int16_t length,
uint32_t fgColor, uint32_t bgColor,
bool opaque);
bool opaque, WdrvFontT font);
// ============================================================================
// Font loading
// ============================================================================
// Load a font from a .FON file (NE container with FONT resources).
// fontIndex selects which font resource (0-based).
// Returns NULL on failure (call wdrvGetLastError for details).
WdrvFontT wdrvLoadFontFon(const char *fonPath, int32_t fontIndex);
// Load a font from a raw .FNT file.
// Returns NULL on failure.
WdrvFontT wdrvLoadFontFnt(const char *fntPath);
// Get a handle to the built-in VGA 8x16 font (lazy-initialized).
// The returned handle must NOT be passed to wdrvUnloadFont.
WdrvFontT wdrvLoadFontBuiltin(void);
// Unload a font previously loaded with wdrvLoadFontFon or wdrvLoadFontFnt.
// Silently ignores NULL and the built-in font.
void wdrvUnloadFont(WdrvFontT font);
// ============================================================================
// Palette operations (for 8bpp modes)