Compare commits

...

3 Commits

Author SHA1 Message Date
Dustin L. Howett
b488e8c500 HAX: load fnt files (???) 2025-11-10 11:53:56 -06:00
Dustin L. Howett
a1ba9f7607 Merge remote-tracking branch 'origin/main' into dev/duhowett/fhl-2025/bitmap-fonts 2025-09-18 17:14:09 -05:00
Dustin L. Howett
b3ded9b0cf bitmap fonts...? 2025-03-07 15:42:14 -06:00
5 changed files with 312 additions and 3 deletions

View File

@@ -526,9 +526,180 @@ void AtlasEngine::_resolveTransparencySettings() noexcept
}
}
namespace
{
// For now we're only using fixed pitch single color fonts, but the rest
// of the flags are included here for completeness.
static constexpr DWORD DFF_FIXED = 0x0001;
static constexpr DWORD DFF_PROPORTIONAL = 0x0002;
static constexpr DWORD DFF_1COLOR = 0x0010;
static constexpr DWORD DFF_16COLOR = 0x0020;
static constexpr DWORD DFF_256COLOR = 0x0040;
static constexpr DWORD DFF_RGBCOLOR = 0x0080;
#pragma pack(push, 1)
struct GLYPHENTRY
{
WORD geWidth;
DWORD geOffset;
};
struct FONTINFO
{
WORD dfVersion;
DWORD dfSize;
CHAR dfCopyright[60];
WORD dfType;
WORD dfPoints;
WORD dfVertRes;
WORD dfHorizRes;
WORD dfAscent;
WORD dfInternalLeading;
WORD dfExternalLeading;
BYTE dfItalic;
BYTE dfUnderline;
BYTE dfStrikeOut;
WORD dfWeight;
BYTE dfCharSet;
WORD dfPixWidth;
WORD dfPixHeight;
BYTE dfPitchAndFamily;
WORD dfAvgWidth;
WORD dfMaxWidth;
BYTE dfFirstChar;
BYTE dfLastChar;
BYTE dfDefaultChar;
BYTE dfBreakChar;
WORD dfWidthBytes;
DWORD dfDevice;
DWORD dfFace;
DWORD dfBitsPointer;
DWORD dfBitsOffset;
BYTE dfReserved;
DWORD dfFlags;
WORD dfAspace;
WORD dfBspace;
WORD dfCspace;
DWORD dfColorPointer;
DWORD dfReserved1[4];
// Character table follows
// Face name follows
};
}
BitmapFontInfo loadBitmapFont(wil::zwstring_view path)
{
wil::unique_hfile fontFile{ CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr) };
THROW_LAST_ERROR_IF(!fontFile);
BY_HANDLE_FILE_INFORMATION fi;
GetFileInformationByHandle(fontFile.get(), &fi);
auto fileData{ std::make_unique_for_overwrite<std::byte[]>(fi.nFileSizeLow) };
DWORD dwRead{ 0 };
THROW_IF_WIN32_BOOL_FALSE(ReadFile(fontFile.get(), fileData.get(), fi.nFileSizeLow, &dwRead, nullptr));
const auto header = reinterpret_cast<const FONTINFO*>(fileData.get());
auto numGlyphs{ header->dfLastChar - header->dfFirstChar + 1 }; // there is always a sentinel entry
std::span<const GLYPHENTRY> glyphEntries{ reinterpret_cast<const GLYPHENTRY*>(fileData.get() + sizeof(FONTINFO)), gsl::narrow_cast<size_t>(numGlyphs) };
WORD maxPixWidth = 0;
std::for_each(std::begin(glyphEntries), std::end(glyphEntries), [&](auto&& ge) {
maxPixWidth = std::max(maxPixWidth, ge.geWidth);
});
auto rgbaBitmapSize{ (16 * maxPixWidth) * (16 * header->dfPixHeight) };
std::vector<uint8_t> rgbaBitmap;
rgbaBitmap.resize(rgbaBitmapSize * sizeof(uint32_t));
auto exploded = reinterpret_cast<uint32_t*>(rgbaBitmap.data());
auto didx = header->dfFirstChar;
const auto stride = 16 * maxPixWidth;
for (auto&& ge : glyphEntries)
{
// characters are stored end to end, scanline 0 ... N 0 ... N
// we need to transform that into 0 0 0 0 1 1 1 1 2 2 2 2 ...
// bitmap offset plus currently glyph number-rows
auto sgstart = reinterpret_cast<uint8_t*>(fileData.get()) + ge.geOffset;
auto dgstart = exploded + ((didx % 16) * maxPixWidth) + (((didx / 16) * header->dfPixHeight) * stride);
for (int y = 0; y < header->dfPixHeight; ++y)
{
auto scancol = 0;
auto width = ge.geWidth;
while (width)
{
auto row = sgstart[y + (scancol * header->dfPixHeight)];
auto drstart = dgstart + (scancol * 8);
auto cw = std::min<WORD>(width, 8);
for (int i = 0; i < cw; ++i)
{
*drstart++ = 0xFFFFFFFF * ((row >> (7 - i)) & 1);
}
scancol++;
width -= cw;
}
dgstart += stride;
}
++didx;
}
#if 0
auto w = CreateFile(L"c:\\src\\terminal\\dev\\image.bin", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, CREATE_ALWAYS, 0, nullptr);
WriteFile(w, rgbaBitmap.data(), (DWORD)rgbaBitmap.size(), nullptr, nullptr);
CloseHandle(w);
#endif
return BitmapFontInfo{
.bitmap = std::move(rgbaBitmap),
.glyphSize = { maxPixWidth, header->dfPixHeight },
.bitmapSizeInCharacters = { 16, 16 },
.glyphResidence = {
0xFFFFFFFFFFFFFFFFULL,
0xFFFFFFFFFFFFFFFFULL,
0xFFFFFFFFFFFFFFFFULL,
0xFFFFFFFFFFFFFFFFULL,
},
.invalidGlyphBitmapIndex = header->dfDefaultChar,
};
}
[[nodiscard]] HRESULT AtlasEngine::_updateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, float>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept
try
{
if (fontInfoDesired.GetFaceName().find_last_of(L'.') != std::wstring::npos)
{
const auto font = _api.s.write()->font.write();
float scale = (float)font->dpi / 96.f;
font->bitmapFontInfo = loadBitmapFont(fontInfoDesired.GetFaceName());
scale = 1.0f;
font->cellSize = {
gsl::narrow<u16>(lrintf(font->bitmapFontInfo.glyphSize.x * scale)),
gsl::narrow<u16>(lrintf(font->bitmapFontInfo.glyphSize.y * scale)),
};
const auto& cs = font->cellSize;
auto lineWidth = gsl::narrow<u16>(lrintf(1.f * scale));
font->fontName = L"VGA 8x16";
font->fontSize = 20;
font->advanceWidth = cs.x;
font->baseline = cs.y;
font->gridTop = { 0, 1 };
font->gridBottom = { gsl::narrow<u16>(cs.y - lineWidth), lineWidth };
font->gridLeft = { 0, lineWidth };
font->gridRight = { gsl::narrow<u16>(cs.x - lineWidth), lineWidth };
font->underline = { gsl::narrow<u16>(cs.y - lineWidth), lineWidth };
font->strikethrough = { gsl::narrow<u16>(cs.x - lineWidth), lineWidth };
font->doubleUnderline[0] = { gsl::narrow<u16>(cs.y - lineWidth), lineWidth };
font->doubleUnderline[1] = { gsl::narrow<u16>(cs.y - lineWidth * 3), lineWidth };
font->overline = { 0, lineWidth };
font->antialiasingMode = AntialiasingMode::Aliased;
font->builtinGlyphs = true;
font->colorGlyphs = false;
font->thinLineWidth = lineWidth;
fontInfo.SetFromEngine(L"VGA 8x16", FF_MODERN, 400, false, { font->cellSize.x, font->cellSize.y }, { 10, 20 });
return S_OK;
}
std::vector<DWRITE_FONT_FEATURE> fontFeatures;
if (!features.empty())
{
@@ -900,3 +1071,62 @@ void AtlasEngine::_resolveFontMetrics(const FontInfoDesired& fontInfoDesired, Fo
_api.s.write()->font.write()->fontCollection = std::move(collection);
return true;
}
#if 0
struct bdflex
{
enum tok
{
LIT,
NUM,
START,
FONT,
SIZE,
FONTBBOX,
STARTPROP,
FONTASC,
FONTDSC,
ENDPROP,
CHARS,
STARTCHAR,
ENCODING,
SWIDTH,
DWIDTH,
BBX,
BITMAP,
ENDCHAR,
ENDFONT,
END,
};
size_t off; // position of ch
std::tuple<size_t, std::string_view> item;
std::string_view input;
public:
std::string_view next()
{
if (off >= input.size())
{
return {};
}
const auto st = off;
for (; off < input.size() && !isspace(til::at(input, off)); ++off)
;
auto p = input.substr(st, off - st);
for (; off < input.size() && isspace(til::at(input, off)); ++off)
;
return p;
}
int e()
{
bdflex::* (bdflex::*)(int);
}
};
#endif

View File

@@ -838,7 +838,7 @@ void AtlasEngine::_flushBufferLine()
codepoint = til::combine_surrogates(codepoint, beg[i++]);
}
const auto c = (builtinGlyphs && BuiltinGlyphs::IsBuiltinGlyph(codepoint)) || BuiltinGlyphs::IsSoftFontChar(codepoint);
const auto c = (builtinGlyphs && BuiltinGlyphs::IsBuiltinGlyph(codepoint)) || BuiltinGlyphs::IsSoftFontChar(codepoint) || (codepoint >= 0 && codepoint <= 255);
if (custom != c)
{
break;
@@ -855,7 +855,27 @@ void AtlasEngine::_flushBufferLine()
}
else
{
#if 0
_mapRegularText(segmentBeg, segmentEnd);
#endif
auto& row = *_p.rows[_api.lastPaintBufferLineCoord.y];
auto initialIndicesCount = row.glyphIndices.size();
const auto shift = gsl::narrow_cast<u8>(row.lineRendition != LineRendition::SingleWidth);
const auto colors = _p.foregroundBitmap.begin() + _p.colorBitmapRowStride * _api.lastPaintBufferLineCoord.y;
const auto l = segmentEnd - segmentBeg;
row.glyphIndices.insert(row.glyphIndices.end(), l, 0);
row.glyphAdvances.insert(row.glyphAdvances.end(), l, static_cast<f32>(_p.s->font->cellSize.x));
row.glyphOffsets.insert(row.glyphOffsets.end(), l, {});
for (size_t i = segmentBeg; i < segmentEnd; ++i)
{
const auto col = _api.bufferLineColumn[i];
row.colors.emplace_back(colors[static_cast<size_t>(col) << shift]);
}
row.mappings.emplace_back(nullptr, gsl::narrow_cast<u32>(initialIndicesCount), gsl::narrow_cast<u32>(row.glyphIndices.size()));
}
}

View File

@@ -351,6 +351,7 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p)
}
_softFontBitmap.reset();
_vgaBitmap.reset();
}
void BackendD3D::_d2dRenderTargetUpdateFontSettings(const RenderingPayload& p) const noexcept
@@ -678,12 +679,12 @@ void BackendD3D::_debugUpdateShaders(const RenderingPayload& p) noexcept
struct FileVS
{
std::wstring_view filename;
wil::com_ptr<ID3D11VertexShader> BackendD3D::*target;
wil::com_ptr<ID3D11VertexShader> BackendD3D::* target;
};
struct FilePS
{
std::wstring_view filename;
wil::com_ptr<ID3D11PixelShader> BackendD3D::*target;
wil::com_ptr<ID3D11PixelShader> BackendD3D::* target;
};
static constexpr std::array filesVS{
@@ -1602,6 +1603,10 @@ BackendD3D::AtlasGlyphEntry* BackendD3D::_drawBuiltinGlyph(const RenderingPayloa
{
shadingType = _drawSoftFontGlyph(p, r, glyphIndex);
}
else if (glyphIndex <= 255)
{
shadingType = _drawVGA816Glyph(p, r, glyphIndex);
}
else
{
// This code works in tandem with SHADING_TYPE_TEXT_BUILTIN_GLYPH in our pixel shader.
@@ -1695,6 +1700,47 @@ BackendD3D::ShadingType BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p
return ShadingType::TextGrayscale;
}
BackendD3D::ShadingType BackendD3D::_drawVGA816Glyph(const RenderingPayload& p, const D2D1_RECT_F& rect, u32 glyphIndex)
{
const auto font = p.s->font;
const BitmapFontInfo& bfi = p.s->font->bitmapFontInfo;
const auto width = static_cast<size_t>(bfi.glyphSize.x);
const auto height = static_cast<size_t>(bfi.glyphSize.y);
if (!_vgaBitmap)
{
const D2D1_SIZE_U size{
static_cast<UINT32>(bfi.glyphSize.x * bfi.bitmapSizeInCharacters.x),
static_cast<UINT32>(bfi.glyphSize.y * bfi.bitmapSizeInCharacters.y),
};
const D2D1_BITMAP_PROPERTIES1 bitmapProperties{
.pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED },
.dpiX = static_cast<f32>(96),
.dpiY = static_cast<f32>(96),
};
THROW_IF_FAILED(_d2dRenderTarget->CreateBitmap(size, nullptr, 0, &bitmapProperties, _vgaBitmap.addressof()));
const D2D1_RECT_U fillr{
0, 0, size.width, size.height
};
/* we have the entire bitmap right now, why not just blast the entire thing into D2D bitmap? */
_vgaBitmap->CopyFromMemory(&fillr, bfi.bitmap.data(), size.width * sizeof(u32));
}
_d2dRenderTarget->PushAxisAlignedClip(&rect, D2D1_ANTIALIAS_MODE_ALIASED);
const auto restoreD2D = wil::scope_exit([&]() {
_d2dRenderTarget->PopAxisAlignedClip();
});
D2D1_RECT_F srcRect{
static_cast<float>((glyphIndex % bfi.bitmapSizeInCharacters.x) * width),
static_cast<float>((glyphIndex / bfi.bitmapSizeInCharacters.x) * height),
static_cast<float>((glyphIndex % bfi.bitmapSizeInCharacters.x) * width + width),
static_cast<float>((glyphIndex / bfi.bitmapSizeInCharacters.x) * height + height),
};
_d2dRenderTarget->DrawBitmap(_vgaBitmap.get(), &rect, 1, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, &srcRect, nullptr);
return ShadingType::TextGrayscale;
}
void BackendD3D::_drawGlyphAtlasAllocate(const RenderingPayload& p, stbrp_rect& rect)
{
if (stbrp_pack_rects(&_rectPacker, &rect, 1))

View File

@@ -252,6 +252,7 @@ namespace Microsoft::Console::Render::Atlas
[[nodiscard]] ATLAS_ATTR_COLD AtlasGlyphEntry* _drawGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex);
AtlasGlyphEntry* _drawBuiltinGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex);
ShadingType _drawSoftFontGlyph(const RenderingPayload& p, const D2D1_RECT_F& rect, u32 glyphIndex);
ShadingType _drawVGA816Glyph(const RenderingPayload& p, const D2D1_RECT_F& rect, u32 glyphIndex);
void _drawGlyphAtlasAllocate(const RenderingPayload& p, stbrp_rect& rect);
static AtlasGlyphEntry* _drawGlyphAllocateEntry(const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex);
static void _splitDoubleHeightGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, AtlasGlyphEntry* glyphEntry);
@@ -308,6 +309,7 @@ namespace Microsoft::Console::Render::Atlas
wil::com_ptr<ID2D1SolidColorBrush> _emojiBrush;
wil::com_ptr<ID2D1SolidColorBrush> _brush;
wil::com_ptr<ID2D1Bitmap1> _softFontBitmap;
wil::com_ptr<ID2D1Bitmap1> _vgaBitmap;
bool _d2dBeganDrawing = false;
bool _fontChangedResetGlyphAtlas = false;

View File

@@ -346,6 +346,15 @@ namespace Microsoft::Console::Render::Atlas
u16 height = 0;
};
struct BitmapFontInfo
{
std::vector<uint8_t> bitmap;
u16x2 glyphSize;
u16x2 bitmapSizeInCharacters;
std::array<uint64_t, 256 / 64> glyphResidence;
u16 invalidGlyphBitmapIndex;
};
struct FontSettings
{
wil::com_ptr<IDWriteFontCollection> fontCollection;
@@ -379,6 +388,8 @@ namespace Microsoft::Console::Render::Atlas
std::vector<uint16_t> softFontPattern;
til::size softFontCellSize;
BitmapFontInfo bitmapFontInfo;
};
struct CursorSettings