Here’s the code to draw a pretty crude font. It scales very readably on the E1 Mini, but it’s not going to win any design awards. This will tide us over until a few more fonts/scaling can be supported in the firmware
Pretty effective and efficient. It blows me away to be doing this type of code n 2026.
-- 5x7 dot-matrix font: All printable ASCII (0x20-0x7E) characters in a 5x7 block
-- pattern with optional 2 line descender below the baseline. Most glyphs are
-- exactly 7 rows and sit entirely at/above the baseline; the five lowercase
-- descenders (g j p q y) are 9-row tables with rows 0-6 same as any other glyph,
-- rows 7-8 draw below the baseline.
--
-- Can be scaled to larger size by increasing size of drawn pixel (square).
-- Optimization combines some neighboring pixels into a single call to fillRect()
local BlockFont = {
WIDTH = 5,
HEIGHT = 7,
PIXEL_CHAR = "#"
}
BlockFont.glyphs = {
["0"] = { ".###.", "#...#", "#...#", "#...#", "#...#", "#...#", ".###." },
["1"] = { "..#..", ".##..", "..#..", "..#..", "..#..", "..#..", ".###." },
["2"] = { ".###.", "#...#", "....#", "...#.", "..#..", ".#...", "#####" },
["3"] = { ".###.", "#...#", "....#", "..##.", "....#", "#...#", ".###." },
["4"] = { "...#.", "..##.", ".#.#.", "#..#.", "#####", "...#.", "...#." },
["5"] = { "#####", "#....", "####.", "....#", "....#", "#...#", ".###." },
["6"] = { "..##.", ".#...", "#....", "####.", "#...#", "#...#", ".###." },
["7"] = { "#####", "....#", "...#.", "..#..", ".#...", ".#...", ".#..." },
["8"] = { ".###.", "#...#", "#...#", ".###.", "#...#", "#...#", ".###." },
["9"] = { ".###.", "#...#", "#...#", ".####", "....#", "...#.", ".##.." },
["A"] = { ".###.", "#...#", "#...#", "#####", "#...#", "#...#", "#...#" },
["B"] = { "####.", "#...#", "#...#", "####.", "#...#", "#...#", "####." },
["C"] = { ".###.", "#...#", "#....", "#....", "#....", "#...#", ".###." },
["D"] = { "####.", "#...#", "#...#", "#...#", "#...#", "#...#", "####." },
["E"] = { "#####", "#....", "#....", "####.", "#....", "#....", "#####" },
["F"] = { "#####", "#....", "#....", "####.", "#....", "#....", "#...." },
["G"] = { ".###.", "#...#", "#....", "#.###", "#...#", "#...#", ".####" },
["H"] = { "#...#", "#...#", "#...#", "#####", "#...#", "#...#", "#...#" },
["I"] = { "#####", "..#..", "..#..", "..#..", "..#..", "..#..", "#####" },
["J"] = { "..###", "...#.", "...#.", "...#.", "...#.", "#..#.", ".##.." },
["K"] = { "#...#", "#..#.", "#.#..", "##...", "#.#..", "#..#.", "#...#" },
["L"] = { "#....", "#....", "#....", "#....", "#....", "#....", "#####" },
["M"] = { "#...#", "##.##", "#.#.#", "#...#", "#...#", "#...#", "#...#" },
["N"] = { "#...#", "##..#", "#.#.#", "#..##", "#...#", "#...#", "#...#" },
["O"] = { ".###.", "#...#", "#...#", "#...#", "#...#", "#...#", ".###." },
["P"] = { "####.", "#...#", "#...#", "####.", "#....", "#....", "#...." },
["Q"] = { ".###.", "#...#", "#...#", "#...#", "#.#.#", "#..#.", ".##.#" },
["R"] = { "####.", "#...#", "#...#", "####.", "#.#..", "#..#.", "#...#" },
["S"] = { ".####", "#....", "#....", ".###.", "....#", "....#", "####." },
["T"] = { "#####", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.." },
["U"] = { "#...#", "#...#", "#...#", "#...#", "#...#", "#...#", ".###." },
["V"] = { "#...#", "#...#", "#...#", "#...#", "#...#", ".#.#.", "..#.." },
["W"] = { "#...#", "#...#", "#...#", "#.#.#", "#.#.#", "##.##", "#...#" },
["X"] = { "#...#", "#...#", ".#.#.", "..#..", ".#.#.", "#...#", "#...#" },
["Y"] = { "#...#", "#...#", ".#.#.", "..#..", "..#..", "..#..", "..#.." },
["Z"] = { "#####", "....#", "...#.", "..#..", ".#...", "#....", "#####" },
["#"] = { ".#.#.", ".#.#.", "#####", ".#.#.", "#####", ".#.#.", ".#.#." },
["-"] = { ".....", ".....", ".....", "#####", ".....", ".....", "....." },
["."] = { ".....", ".....", ".....", ".....", ".....", ".##..", ".##.." },
["'"] = { ".##..", ".##..", "..#..", ".....", ".....", ".....", "....." },
["!"] = { "..#..", "..#..", "..#..", "..#..", "..#..", ".....", "..#.." },
["/"] = { "....#", "...#.", "...#.", "..#..", ".#...", ".#...", "#...." },
-- Lowercase. x-height letters (a c e m n o r s u v w x z) leave rows 0-1 blank
-- and sit in rows 2-6; ascenders (d f h i k l t) use the full 7 rows like a
-- capital; descenders (g j p q y) are 9-row tables, rows 7-8 below the baseline.
["a"] = { ".....", ".....", ".###.", "....#", ".####", "#...#", ".####" },
["b"] = { "#....", "#....", "#....", "####.", "#...#", "#...#", "####." },
["c"] = { ".....", ".....", ".###.", "#....", "#....", "#....", ".###." },
["d"] = { "....#", "....#", ".####", "#...#", "#...#", "#...#", ".####" },
["e"] = { ".....", ".....", ".###.", "#...#", "#####", "#....", ".###." },
["f"] = { "..##.", ".#...", "####.", ".#...", ".#...", ".#...", ".#..." },
["g"] = { ".....", ".....", ".####", "#...#", "#...#", ".####", "....#", "#...#", ".###." },
["h"] = { "#....", "#....", "####.", "#...#", "#...#", "#...#", "#...#" },
["i"] = { "..#..", ".....", "..#..", "..#..", "..#..", "..#..", "..#.." },
["j"] = { "...#.", ".....", "...#.", "...#.", "...#.", "...#.", "...#.", "#..#.", ".##.." },
["k"] = { "#....", "#..#.", "#.#..", "##...", "#.#..", "#..#.", "#...#" },
["l"] = { ".##..", "..#..", "..#..", "..#..", "..#..", "..#..", ".###." },
["m"] = { ".....", ".....", "#####", "#.#.#", "#.#.#", "#.#.#", "#.#.#" },
["n"] = { ".....", ".....", "####.", "#...#", "#...#", "#...#", "#...#" },
["o"] = { ".....", ".....", ".###.", "#...#", "#...#", "#...#", ".###." },
["p"] = { ".....", ".....", "####.", "#...#", "#...#", "####.", "#....", "#....", "#...." },
["q"] = { ".....", ".....", ".####", "#...#", "#...#", ".####", "....#", "....#", "....#" },
["r"] = { ".....", ".....", "#.##.", "##...", "#....", "#....", "#...." },
["s"] = { ".....", ".....", ".####", "#....", ".###.", "....#", "####." },
["t"] = { ".#...", ".#...", "####.", ".#...", ".#...", ".#...", "..##." },
["u"] = { ".....", ".....", "#...#", "#...#", "#...#", "#...#", ".####" },
["v"] = { ".....", ".....", "#...#", "#...#", "#...#", ".#.#.", "..#.." },
["w"] = { ".....", ".....", "#...#", "#.#.#", "#.#.#", "##.##", "#...#" },
["x"] = { ".....", ".....", "#...#", ".#.#.", "..#..", ".#.#.", "#...#" },
["y"] = { ".....", ".....", "#...#", "#...#", ".#.#.", "..#..", "..#..", "..#..", ".##.." },
["z"] = { ".....", ".....", "#####", "...#.", "..#..", ".#...", "#####" },
-- No entry for " ". Space character is handled by drawString upstream
['"'] = { ".#.#.", ".#.#.", ".....", ".....", ".....", ".....", "....." },
["$"] = { "..#..", ".####", "#.#..", ".###.", "..#.#", "####.", "..#.." },
["%"] = { "#...#", "....#", "...#.", "..#..", ".#...", "#....", "#...#" },
["&"] = { ".##..", "#..#.", ".##..", "##.#.", "#..#.", "#..#.", ".##.#" },
["("] = { "...#.", "..#..", ".#...", ".#...", ".#...", "..#..", "...#." },
[")"] = { ".#...", "..#..", "...#.", "...#.", "...#.", "..#..", ".#..." },
["*"] = { ".....", "#.#.#", ".###.", "#####", ".###.", "#.#.#", "....." },
["+"] = { ".....", "..#..", "..#..", "#####", "..#..", "..#..", "....." },
[","] = { ".....", ".....", ".....", ".....", ".....", ".##..", "..#.." },
[":"] = { ".....", ".##..", ".##..", ".....", ".##..", ".##..", "....." },
[";"] = { ".....", ".##..", ".##..", ".....", ".....", ".##..", "..#.." },
["<"] = { "...#.", "..#..", ".#...", "#....", ".#...", "..#..", "...#." },
["="] = { ".....", ".....", "#####", ".....", "#####", ".....", "....." },
[">"] = { ".#...", "..#..", "...#.", "....#", "...#.", "..#..", ".#..." },
["?"] = { ".###.", "#...#", "...#.", "..#..", "..#..", ".....", "..#.." },
["@"] = { ".###.", "#...#", "#.###", "#.#.#", "#.###", "#....", ".###." },
["["] = { "..###", "..#..", "..#..", "..#..", "..#..", "..#..", "..###" },
["\\"] = { "#....", ".#...", ".#...", "..#..", "...#.", "...#.", "....#" },
["]"] = { "###..", "..#..", "..#..", "..#..", "..#..", "..#..", "###.." },
["^"] = { "..#..", ".#.#.", "#...#", ".....", ".....", ".....", "....." },
["_"] = { ".....", ".....", ".....", ".....", ".....", ".....", "#####" },
["`"] = { ".#...", "..#..", ".....", ".....", ".....", ".....", "....." },
["{"] = { "...##", "..#..", "..#..", ".#...", "..#..", "..#..", "...##" },
["|"] = { "..#..", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.." },
["}"] = { "##...", "..#..", "..#..", "...#.", "..#..", "..#..", "##..." },
["~"] = { ".....", ".#..#", "#.##.", ".....", ".....", ".....", "....." },
}
-- Draws one glyph. Rows 0..HEIGHT-1 sit at/above baseline y; descenders (g/j/p/q/y)
-- add extra rows below y.
--
-- Runs of contiguous lit cells become a single fillRect, not per-dot.
-- Returns drawn width in px (0 if ch has no glyph).
--
-- Note: x,y arguments are first, before the text to display, in order to match the convention used by graphics.print().
--
-- x: left edge
-- y: baseline
-- ch: character to draw
-- cellSize: px size of one dot-matrix cell (integer >= 1)
-- color: RGB color
function BlockFont.drawChar(x, y, ch, cellSize, color)
local rows = BlockFont.glyphs[ch]
if not rows then
print("[ERROR] - Character '" .. ch .. "' not found in BlockFont")
return 0
end
graphics.setColor(color)
local topY = y - BlockFont.HEIGHT * cellSize
for row = 0, #rows - 1 do
local line = rows[row + 1] or ""
local col = 0
while col < BlockFont.WIDTH do
-- Draw one contiguous rectangle for each run of "pixels" represented by BlockFont.PIXEL_CHAR in the font data.
if line:sub(col + 1, col + 1) == BlockFont.PIXEL_CHAR then
local runStart = col
repeat
col = col + 1
until col >= BlockFont.WIDTH or line:sub(col + 1, col + 1) ~= BlockFont.PIXEL_CHAR
local rx = math.floor(x + runStart * cellSize)
local ry = math.floor(topY + row * cellSize)
local rw = math.floor((col - runStart) * cellSize)
graphics.fillRect(rx, ry, rw, cellSize)
else
col = col + 1
end
end
end
return BlockFont.WIDTH * cellSize
end
-- Draws str left-to-right, spacing px between characters (a space advances without
-- drawing). Returns total px width drawn.
--
-- Use measure() to determine width of a string without drawing.
--
-- Note: x,y arguments are first, before the text to display, in order to match the convention used by graphics.print().
--
-- x: left
-- y: baseline (bottom)
-- str: the text to draw
-- pixelSize: the size of a rectangular 'pixel"
-- color: RGB color
-- extraPadding: extra spacing between characters
--
function BlockFont.drawString(x, y, str, pixelSize, color, extraPadding)
local cx = x
for i = 1, #str do
local ch = str:sub(i, i)
if ch == " " then
cx = cx + BlockFont.WIDTH * pixelSize + extraPadding
else
cx = cx + BlockFont.drawChar(cx, y, ch, pixelSize, color) + extraPadding
end
end
return math.max(0, cx - x - extraPadding)
end
-- Measure the width a string would be when rendered without drawing it.
function BlockFont.measure(str, cellSize, extraPadding)
local n = #str
if n == 0 then return 0 end
return n * BlockFont.WIDTH * cellSize + (n - 1) * extraPadding
end