Allow graphics.print() to use a larger fonts

Currently only uses a single, tiny font.

  • Add a way to choose the font from the existing fonts the E1 supports today.
  • Add at least one larger font (or allow users to add their own)

Not sure if this is more important on the E1 Mini due to the smaller display size.

As a hack, I’ve created a lua method to draw my own font. It’s not the prettiest, but neither is the tiny graphics.print(). It seems to work and the E1 Mini has no problem keeping up when using it.

3 Likes

can you share it?

PM’d you. Will post here with an improved version in a few days.

1 Like

Also interested in this.. :face_in_clouds:

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
1 Like

@Quelectra , wow! ))

cool… can you do a screen capture of the electra one so we can see what it looks like? :folded_hands:

Is there a method to take a screen shot on the E1 via lua or midi?

The big numbers on the right (25, 2) and the title at the top “MB - 62 D’Lux 6” are in block font.

Legible from table height. The other fonts are too small for my eyes at any distance. The size of the square used as a virtual pixel controls the size. In theory you could have a font that is the full height. The pixelation is less noticeable at smaller sizes and larger viewing distances. In theory you could anti-alias; but the goal was to minimize CPU and draw the least number of rectangles. Assume a C++ implementation of a scalable, smooth font would be more performant than Lua. IMHO we just need a few more fixed font sizes built in.

5 Likes

DAMN that looks great… plus all those cool af graphics!
If you don’t mind can I mess with it a bit and see how I can mod the text?

Sure. Have at the code above.

1 Like

Super-cool!!!

We need those multi-fader controls (and dials!) on E1 MkII!!! )))

2 Likes


roundfont.lua.zip (3.1 KB)

4 Likes

Nice. RoundFont looks a lot better. A few notes on limitations to anyone who wants to use this generally (per the comments in the file):

  • It only handles 5 characters (Whamy). You will need to add the other characters for general purpose.
  • It’s ok for slow changing text, but probably too slow to change quickly in response to a user action (e.g., rotate knob). It uses a lot of separate curve drawing calls … and it does not merge the calls like BlockFont does.

IMHO, these solutions help, but we still want a few more native fonts (or a scalable font) in the OS and the ability to choose between them in different situations.

1 Like

100%

The Whammy was only for illistrative purposes… cellSize = 2.0 on the 32x48 grid → 96px cap height (120px including “y”'s descender), 370px measured string width, dot radius clamped to 1px 16ms would be a HUGE drag to watch on your E1screen LOL

This resolution is for labels only, no where near as effective and efficient as the blockfont!
block beats dots :slight_smile:

but yeah… a scallable font would be ideal


I am getting back up to speed with firmware development. A development release will be going out next week. It will include scalable fonts without anti-aliasing, along with a host of other improvements. The glyphs will be loaded to the font registers of E1’s video chip and thus it will be must faster to render.

Big thanks to @Quelectra and @stevenclements for the inspiration.

6 Likes

WOW! That’s some exciting news!!!

2 Likes

@martin Just making sure I haven’t missed anything: The new, scalable fonts haven’t happened yet, correct?

Hi @eusti, I know I said it would be ready last week, but I still need a few more days. I will announce it and ping you as soon as it is ready.

Please note that it will be a beta release. The number of changes in the project is quite high, and I wouldd like tech-oriented users to test and review it first.

5 Likes