diff --git a/CLAUDE.md b/CLAUDE.md index 3bdb543..4c11d11 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/Makefile b/Makefile index 17b09e8..c6f1b28 100644 --- a/Makefile +++ b/Makefile @@ -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 $@ $< diff --git a/demo.c b/demo.c index 74ac6a1..1fb14fb 100644 --- a/demo.c +++ b/demo.c @@ -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"); + } } diff --git a/fon/CGA40WOA.FON b/fon/CGA40WOA.FON new file mode 100644 index 0000000..559a519 --- /dev/null +++ b/fon/CGA40WOA.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32482989159a4d10af71599300b99e7c618d768e97832bf7013b78e52fcea4e9 +size 6336 diff --git a/fon/CGA80WOA.FON b/fon/CGA80WOA.FON new file mode 100644 index 0000000..dd176d5 --- /dev/null +++ b/fon/CGA80WOA.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93e7c6c123d9b53a2d933f63093b4b85302023517f56abf057f9ef8a94d83b8b +size 4304 diff --git a/fon/COURE.FON b/fon/COURE.FON new file mode 100644 index 0000000..8cb63ed --- /dev/null +++ b/fon/COURE.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e81d15ef094f997d48cb144375866fb2f5d988b0b93b43c38cf58985e27acf45 +size 23408 diff --git a/fon/DOSAPP.FON b/fon/DOSAPP.FON new file mode 100644 index 0000000..70aa2b0 --- /dev/null +++ b/fon/DOSAPP.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0da0d4ed89fd1e8810c7f2cdb5372abfb02cb3d031acacc1a5bbc853f879c2bd +size 36656 diff --git a/fon/EGA40WOA.FON b/fon/EGA40WOA.FON new file mode 100644 index 0000000..50578d7 --- /dev/null +++ b/fon/EGA40WOA.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0e5dbe96d1cae29501b481cd98a1eac5f0f662aa367aa9712a419c3c32f4284 +size 8368 diff --git a/fon/EGA80WOA.FON b/fon/EGA80WOA.FON new file mode 100644 index 0000000..deb56c3 --- /dev/null +++ b/fon/EGA80WOA.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e7a86167874f989433a264345e5ea6c0e000861cbca8153858b23d7d35d5ecc +size 5312 diff --git a/fon/MODERN.FON b/fon/MODERN.FON new file mode 100644 index 0000000..83ba26d --- /dev/null +++ b/fon/MODERN.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b532bc33388b2aa350f1a497485fb63385adfc9d25094725e4c00bb0bf77628c +size 8704 diff --git a/fon/ROMAN.FON b/fon/ROMAN.FON new file mode 100644 index 0000000..997ac59 --- /dev/null +++ b/fon/ROMAN.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25733f3e5dddebea00c32382d6e40438282ea740f23955010cb38c4b1e312297 +size 13312 diff --git a/fon/SCRIPT.FON b/fon/SCRIPT.FON new file mode 100644 index 0000000..0b0a3c6 --- /dev/null +++ b/fon/SCRIPT.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eacbfca9a5ef05a108ef5337c773d82a43398bb8ea177e5ebeef62934dd75811 +size 12288 diff --git a/fon/SERIFE.FON b/fon/SERIFE.FON new file mode 100644 index 0000000..12dc886 --- /dev/null +++ b/fon/SERIFE.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19bfe5865332ca1f1392bf77950f79dbb7d9e5600f79bc546397567310364fe2 +size 57936 diff --git a/fon/SMALLE.FON b/fon/SMALLE.FON new file mode 100644 index 0000000..1eb27d0 --- /dev/null +++ b/fon/SMALLE.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de416395301da32c96bf34625483ea6ecdcdaaeb9ff72cdca00b5af4d7092da9 +size 26112 diff --git a/fon/SSERIFE.FON b/fon/SSERIFE.FON new file mode 100644 index 0000000..0620036 --- /dev/null +++ b/fon/SSERIFE.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b2658673e3c017847f7f6b272d2ec57dff7c1137567a0643e2057450859a5ce +size 64544 diff --git a/fon/SYMBOLE.FON b/fon/SYMBOLE.FON new file mode 100644 index 0000000..cc7e8b9 --- /dev/null +++ b/fon/SYMBOLE.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85ea10f38c3d4c7035d958af6fef01d9ceac1faad107cbcacdc4b3aaa24afe81 +size 56336 diff --git a/fon/VGAFIX.FON b/fon/VGAFIX.FON new file mode 100644 index 0000000..ecce04b --- /dev/null +++ b/fon/VGAFIX.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57402d1e89dc0d8368cde612d720ab4a6a813bfe5e243e9da2775013e06f2b0f +size 5360 diff --git a/fon/VGAOEM.FON b/fon/VGAOEM.FON new file mode 100644 index 0000000..c9fd38d --- /dev/null +++ b/fon/VGAOEM.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:011326f65e151d83c157ef6fdd23a2d851e8d0f2662d8409ea0b4a142e343a13 +size 5168 diff --git a/fon/VGASYS.FON b/fon/VGASYS.FON new file mode 100644 index 0000000..da09b4a --- /dev/null +++ b/fon/VGASYS.FON @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7baefa14ef8ef2355374d0b5061043dd5f4f8c33ff57680366e4a725f91646f8 +size 7280 diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..4c55210 --- /dev/null +++ b/run.sh @@ -0,0 +1,2 @@ +#!/bin/bash +flatpak run com.dosbox_x.DOSBox-X -conf dosbox-x.conf diff --git a/win31drv/neformat.h b/win31drv/neformat.h index eb441cd..5f66fe1 100644 --- a/win31drv/neformat.h +++ b/win31drv/neformat.h @@ -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) // ============================================================================ diff --git a/win31drv/windrv.c b/win31drv/windrv.c index c85b8bf..08fcafc 100644 --- a/win31drv/windrv.c +++ b/win31drv/windrv.c @@ -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,16 +126,32 @@ 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 // ============================================================================ -static ThunkContextT gThunkCtx; -static StubContextT gStubCtx; -static bool gInitialized = false; -static int32_t gLastError = WDRV_OK; -static bool gDebug = false; -static bool gIsS3 = false; +static ThunkContextT gThunkCtx; +static StubContextT gStubCtx; +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); diff --git a/win31drv/windrv.h b/win31drv/windrv.h index 8bd685f..1b94143 100644 --- a/win31drv/windrv.h +++ b/win31drv/windrv.h @@ -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)