Static utility that renders a built-in 5x7 LCD-style font to small textures for a cold-chip wallet UI, formats balance and rate lines, caches generated line textures and exposes cache clearing.
namespace NoChillquarium;
/// <summary>
/// GBA-style LCD text for the cold-chip wallet.
/// Renders full lines to textures from a built-in 5×7 pixel font (no atlas crop — readable in S&box UI).
/// </summary>
public static class WalletFont
{
// Output glyph cell size in the baked texture
public const int CellW = 12;
public const int CellH = 16;
const int Dot = 2; // each font bit → 2×2 px block
// Fixed LCD viewport (px). Must match .cold-chip-lcd-glass content area.
public const float LcdGlassInnerW = 164f;
public const float LcdBalanceMaxH = 28f;
/// <summary>Rate line stays tiny under the balance (much smaller than primary digits).</summary>
public const float LcdRateMaxH = 12f;
public const int MaxBalanceChars = 8; // e.g. Ð999.9K
public const int MaxRateChars = 10; // e.g. +12.5K/S
static readonly Dictionary<string, Texture> _lineCache = new();
// 5×7 bitmaps (rows of 5 chars '0'/'1')
static readonly Dictionary<char, string[]> Patterns = BuildPatterns();
static Dictionary<char, string[]> BuildPatterns()
{
var d = new Dictionary<char, string[]>
{
['0'] = new[] { "01110", "10001", "10011", "10101", "11001", "10001", "01110" },
['1'] = new[] { "00100", "01100", "00100", "00100", "00100", "00100", "01110" },
['2'] = new[] { "01110", "10001", "00001", "00010", "00100", "01000", "11111" },
['3'] = new[] { "01110", "10001", "00001", "00110", "00001", "10001", "01110" },
['4'] = new[] { "00010", "00110", "01010", "10010", "11111", "00010", "00010" },
['5'] = new[] { "11111", "10000", "11110", "00001", "00001", "10001", "01110" },
['6'] = new[] { "00110", "01000", "10000", "11110", "10001", "10001", "01110" },
['7'] = new[] { "11111", "00001", "00010", "00100", "01000", "01000", "01000" },
['8'] = new[] { "01110", "10001", "10001", "01110", "10001", "10001", "01110" },
['9'] = new[] { "01110", "10001", "10001", "01111", "00001", "00010", "01100" },
['.'] = new[] { "00000", "00000", "00000", "00000", "00000", "01100", "01100" },
['+'] = new[] { "00000", "00100", "00100", "11111", "00100", "00100", "00000" },
['-'] = new[] { "00000", "00000", "00000", "11111", "00000", "00000", "00000" },
['/'] = new[] { "00001", "00010", "00100", "01000", "10000", "00000", "00000" },
[':'] = new[] { "00000", "01100", "01100", "00000", "01100", "01100", "00000" },
[' '] = new[] { "00000", "00000", "00000", "00000", "00000", "00000", "00000" },
['A'] = new[] { "01110", "10001", "10001", "11111", "10001", "10001", "10001" },
// B also used for Billions suffix on LCD
['B'] = new[] { "11110", "10001", "10001", "11110", "10001", "10001", "11110" },
['C'] = new[] { "01110", "10001", "10000", "10000", "10000", "10001", "01110" },
['D'] = new[] { "11110", "10001", "10001", "10001", "10001", "10001", "11110" },
['E'] = new[] { "11111", "10000", "10000", "11110", "10000", "10000", "11111" },
['F'] = new[] { "11111", "10000", "10000", "11110", "10000", "10000", "10000" },
['H'] = new[] { "10001", "10001", "10001", "11111", "10001", "10001", "10001" },
['K'] = new[] { "10001", "10010", "10100", "11000", "10100", "10010", "10001" },
['L'] = new[] { "10000", "10000", "10000", "10000", "10000", "10000", "11111" },
['M'] = new[] { "10001", "11011", "10101", "10001", "10001", "10001", "10001" },
['N'] = new[] { "10001", "11001", "10101", "10011", "10001", "10001", "10001" },
['S'] = new[] { "01111", "10000", "10000", "01110", "00001", "00001", "11110" },
['T'] = new[] { "11111", "00100", "00100", "00100", "00100", "00100", "00100" },
['U'] = new[] { "10001", "10001", "10001", "10001", "10001", "10001", "01110" },
['W'] = new[] { "10001", "10001", "10001", "10001", "10101", "11011", "10001" },
['Y'] = new[] { "10001", "10001", "01010", "00100", "00100", "00100", "00100" },
// Ð — D with bar
['\u00d0'] = new[] { "01111", "01001", "01001", "11101", "01001", "01001", "01111" },
['Ð'] = new[] { "01111", "01001", "01001", "11101", "01001", "01001", "01111" },
};
return d;
}
/// <summary>
/// Balance for LCD — compact abbreviations so digit count stays small and glyphs stay large.
/// Shell size is fixed; we never grow the device for bigger wallets.
/// </summary>
public static string BalanceLine
{
get
{
// Use shown balance (calm ticker), compact LCD formatting
var body = Economy.FormatDogeLcd( Economy.ShownBalance );
var line = "\u00d0" + body;
if ( line.Length > MaxBalanceChars )
line = line[..MaxBalanceChars];
return line;
}
}
/// <summary>
/// Rate for LCD — always short. Boost = trailing F (not the long word FED).
/// </summary>
public static string RateLine
{
get
{
var rate = Economy.IncomePerSecond;
var s = "+" + Economy.FormatDogeLcd( rate ) + "/S";
if ( FeedSystem.IsBoosted )
s += " F";
if ( s.Length > MaxRateChars )
s = s[..MaxRateChars];
return s.ToUpperInvariant();
}
}
public static Texture GetLineTexture( string text, int maxChars = 14 )
{
text ??= "";
if ( text.Length > maxChars )
text = text[..maxChars];
if ( text.Length == 0 )
text = " ";
if ( _lineCache.TryGetValue( text, out var cached ) && cached is not null )
return cached;
var n = text.Length;
var w = Math.Max( 1, n * CellW );
var h = CellH;
var pixels = new Color32[w * h];
var bg = new Color32( 10, 40, 10, 255 );
var ink = new Color32( 200, 236, 48, 255 );
var inkDim = new Color32( 80, 130, 30, 255 );
for ( var i = 0; i < pixels.Length; i++ )
pixels[i] = bg;
for ( var gi = 0; gi < n; gi++ )
{
var ch = text[gi];
if ( !Patterns.TryGetValue( ch, out var pat ) )
{
var u = char.ToUpperInvariant( ch );
if ( !Patterns.TryGetValue( u, out pat ) )
pat = Patterns[' '];
}
var baseX = gi * CellW + 1;
var baseY = 1;
for ( var ry = 0; ry < pat.Length; ry++ )
{
var row = pat[ry];
for ( var rx = 0; rx < row.Length; rx++ )
{
if ( row[rx] != '1' )
continue;
var px0 = baseX + rx * Dot;
var py0 = baseY + ry * Dot;
for ( var dy = 0; dy < Dot; dy++ )
for ( var dx = 0; dx < Dot; dx++ )
{
var x = px0 + dx;
var y = py0 + dy;
if ( (uint)x >= (uint)w || (uint)y >= (uint)h )
continue;
// slight edge darkening for contrast
var c = (dx == 0 || dy == 0) ? inkDim : ink;
pixels[y * w + x] = c;
if ( dx > 0 && dy > 0 )
pixels[y * w + x] = ink;
}
}
}
}
try
{
var tex = Texture.Create( w, h )
.WithName( $"nochill_lcd_{text.GetHashCode():x8}" )
.Finish();
if ( tex is null )
return null;
tex.Update( pixels );
_lineCache[text] = tex;
if ( _lineCache.Count > 64 )
{
var drop = _lineCache.Keys.FirstOrDefault();
if ( drop is not null )
_lineCache.Remove( drop );
}
return tex;
}
catch
{
try
{
var tex = Texture.Create( w, h ).Finish();
if ( tex is null )
return null;
tex.Update( pixels, 0, 0, w, h );
_lineCache[text] = tex;
return tex;
}
catch ( Exception e )
{
Log.Warning( $"[NO-CHILLquarium] LCD bake failed: {e.Message}" );
return null;
}
}
}
public static void ClearLineCache() => _lineCache.Clear();
}