Subscribe to this Thread
(Receive email notifications)
(Receive email notifications)
Pin To Profile
PicoFont
PicoFont aims to solve a single problem I have experienced with Picotron. That being a singular, small font scale. As far as I know, no way to increase the font size exists. This is okay for most things, but for some use cases this limits functionality. That is why I am introducing PicoFont into the PicoUI family (kind of). PicoFont is a simple and declarative font renderer.
Font definitions are comprised of a single table:
include "font.lua" local simple = { meta = { name = "Simple Font", author = "BlueFalconHD" }, -- To get the pod, hold down shift and select all of the text sprites for your -- font. Then paste it here as a pod, add a comma at the end, and define your -- character properties. pod = --[[pod_type="gfx",region_w=8]]unpod("b64:bHo0AA0EAADSFgAA8xx7e2JtcD1weHUAQyAFBQQAJwAHIBcgFxAXABcAByxmbGFncz0wLHBhbl94CADIeT0wLHpvb209OH0sPQBACAQHMAIAETdCABEnQgAPQQAaBH4AfzAHIAcAJwB9ABwBfAA-BwBHvgAkA30AP2dANzcAGcAECAQQBxAHAAcABxD4AAECAB8QugAc7wAXABcQJyAHADcwBzAX-AAfEgd5AQFHABAX-wAfIPsAGl8BBgQHAPYAHTIgB2DuAD8XEAfoARwxBAcEOwBjEBcABwAXPQEPsgAaMQIHBDYAHwA8ACBDBQUEFzoAAYAADy4BHT8FBQRqASYF4gIvIBfiAiEEeABCRwAHMAIAD2kCKQECAA-6ACMPfAAfjwUEAEdAJ0BH8wAaMAMHBN4BADMBAGUDD6gBHy8gFw0EJgSlAS8HANEDHiUFBF0CAwQAAgIAD4IAHgN_AAQIAA8uAh0EwAARED4AEjAEAA_FABwxRyAHAgAfR30AHAPnAhFnRgEfF7wAHgJDBhFHBgAACAAPfgAjAagCD2sDIwKBAAQCAA_BAB4SVwcHAaYDH0c8ACAiJxA8AC8HMPoAJi8QJ-oAIwi3AQ_5ARwzAwgEqwMCAgAvACd8ABwkRyD3AAG3Aj8QFxC9ACAAUgYiJxD2Ag-DAB4FxQgvBzC_ASCABwgEB0AnICd2AwDaBjIQF0ACAA_EAB4BPwkRB64FHhfGAA8JAhgDAgAPAwMmL0cAigIsAgIAAAwBLwAHggAlDwQBKj9AJ0ACASQVR4YDAQIAD40CIg_CASkGQQAHyAUfEJECHgKDAgGDAQMEAACWBh8XjgAhAYgAIyAHCAAOlAEPRgAaHzBYASQQR8sGAgIAD6EDHTMDCASaAw_jBCUiACe7AABIAQ6AAA9BABg-ACdA7QsiIAgEtwAEBAAFMgUPogYiEEA7AA9eByQD5w0PWQMjFDCPCA8BAiICgQABeQAPgwAmBUQADwsLIwNBAAimBQ_FAB0mAwPFDA9MCx0ANQAXRwYAD_8NGk8IBFcA7gAiIwcwdQM-gAcQ6gAeD4ULHj8BAwSdAxpPAgMEAIsOHT8BAQSUABoQAnwBHyBmAB1PBAEEN9EPHCEQB-gGAKwNH0f0BRwBnQ83IAcQBAAfIEAAHScXEEAAPxAXIC4CHSMXEKEEEBCwDgCiAg8CCCARArEOFAcCAB8QPQAdAYMAAwIADxcCGzAFBQT7Bz8QRxCeBB1PBgEEV2oAGk8DBEdAwwYeIRcA3AABnQE-BxAXPwAcA90BAEQAD7wRHhEC4wsFAgAfFzwAHAfPAS8AJ7UEHB93xQIcA7AEBAQAD0sETTIDAgTZAA8REiJPADdANx0FHA9rABxfEBAE8PAxAM1QbT04fX0="), -- This just maps a specific sprite in the pod above to character. -- Determines the sprite to show for a string. Any spaces are ignored sprites chars = { "abcdefgh", "ijklmnop", "qrstuvwx", "yzABCDEF", "GHIJKLMN", "OPQRSTUV", "WXYZ1234", "567890*#", "!?\" '.;-", "$/%&()+_", "={}[]|\\,", "^@: ", }, -- Y-offset of the sprite when displayed. Useful for characters that -- don't match the height of the rest or that have descenders. ascent = { -3,0,-3,0,-3,0,-3,-1, -2,-2,-1,-1,-3,-3,-3,-3, -3,-3,-3,-1,-3,-3,-3,-3, -3,-3,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,-3,-2, 0,0,0,0,0,-7,-2,-4, -1,0,0,0,0,0,-2,-7, -3,0,0,0,0,0,0,-7, 0,-3,-4,0,0,0,0,0, }, -- x-offset of sprites -- somewhat finnicky as of right now -- a negative value results in the character to the right of the one specified -- being further away. offset = { 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,-1,0,0, -- make period stand out more by adding spacing 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,-1,0,0,0,0,0, }, -- Define special characters -- space maps to " " -- Undefined is what is shown when no sprite is mapped to a character. -- Undefined is the only required property for fonts. special = { space = --[[pod_type="gfx"]]unpod("b64:bHo0AAwAAAALAAAAsHB4dQBDIAUJBPAd"), undefined = --[[pod_type="gfx"]]unpod("b64:bHo0ABwAAAAaAAAA8AtweHUAQyAFCAQHIAcAJwA3ACcAJwA3AIcAFw==") } } simple_font = Font:new(simple) |
Then text can be drawn using fonts in the draw loop like:
-- font:draw(text, x, y, scale, color, { -- kerning = int?, -- line_spacing = int?, -- wrap = { -- enabled = bool?, -- wrap_bounds = {x = int, y = int, w = int, h = int}?, -- wrap_offscreen = bool?, -- }? -- }) simple_font:draw("Hello world!", 2, 2, 1, 7) |
Full code
-- CONSTANTS { sw = 480 sh = 270 -- } Font = {} Font.__index = Font -- Constructor method to initialize the font object function Font:new(f) local obj = {} setmetatable(obj, Font) if not f.special.undefined then error("Fonts must specify at least an undefined character.") end obj.font_data = self:parseFont(f) return obj end -- Method to parse the font structure function Font:parseFont(f) local cc = "" -- Turn a char array (2D) into a single long string. for i = 1, #f.chars do if type(f.chars[i]) ~= "string" then error(string.format( "2D string array has non-string type (expected: 'string', got '%s') at c[%s]", type(f.chars[i]), i )) end cc = cc .. f.chars[i] end local font_chars = cc local char_data = { undefined = { bmp = f.special.undefined, width = f.special.undefined:width(), height = f.special.undefined:height(), ascent = 0, offset = 0, }, space = { bmp = f.special.space or f.special.undefined, width = (f.special.space or f.special.undefined):width(), height = (f.special.space or f.special.undefined):height(), ascent = 0, offset = 0, }, } -- Loop through every font_char character for i = 1, #font_chars do local c = font_chars:sub(i, i) -- Lua indexing with sub for characters if c ~= " " then -- Save character info char_data[c] = { bmp = f.pod[i].bmp, width = f.pod[i].bmp:width(), height = f.pod[i].bmp:height(), ascent = f.ascent[i], offset = f.offset[i], } end end return { meta = { name = f.meta.name or "No name", author = f.meta.author or "No author" }, chars = char_data } end function Font:character(c,col) col = col or 7 if c == " " then return self.font_data.chars.space elseif c == "\n" then return { special = "newline", width = 0, height = 0 } elseif not self.font_data.chars[c] then return self.font_data.chars.undefined else return self.font_data.chars[c] end end function Font:chars(s) --col) local c = {} -- s: string for i=1,#s do table.insert(c, self:character(s[i])) end return c end function Font:recolored(col) local function recolor_chars(nc) local c = self.font_data.chars for k,v in pairs(c) do -- CHANGE THIS 7 TO THE COLOR THE FONT IS IN IN THE SPRITE EDITOR c[k] = v c[k].bmp = replaceAll(c[k].bmp, 7, nc) end return c end -- Cache the recolered versions of the font. self.cache = self.cache or {} self.cache.colored = self.cache.colored or {} self.cache.colored[col] = self.cache.colored[col] or recolor_chars(col) return self.cache.colored[col] end local function ud_replaceAll(ud, original, new) local w = ud:width() local h = ud:height() local n = ud:copy() for y=0,h-1 do for x=0,w-1 do if (n:get(x,y) == original) n:set(x,y,new) end end return n end local function renderChars(x, y, chars, opts) -- Set default values for opts if not provided opts = opts or {} opts.size = opts.size or 1 opts.color = opts.color or 7 opts.kerning = opts.kerning or 1 opts.line_spacing = opts.line_spacing or 2 opts.wrap = opts.wrap or { enabled = false, wrap_bounds = {x = 0, y = 0, w = 100, h = 100}, wrap_offscreen = true } local xo = 0 local yo = 0 local running_max_height = 0 local size = opts.size local wrap_enabled = opts.wrap.enabled local wrap_bounds = opts.wrap.wrap_bounds local wrap_offscreen = opts.wrap.wrap_offscreen local kerning = opts.kerning local line_spacing = opts.line_spacing -- Enable clipping if wrap is enabled if wrap_enabled then clip(wrap_bounds.x, wrap_bounds.y, wrap_bounds.w, wrap_bounds.h) end for i, v in ipairs(chars) do if v.special == "newline" then yo = yo + running_max_height + line_spacing xo = 0 -- Reset the running height for the next line running_max_height = 0 else -- Calculate scaled width, height, ascent, and offset local scaled_width = v.width * size local scaled_height = v.height * size local scaled_offset = v.offset * size local scaled_ascent = v.ascent * size local scaled_kerning = kerning * size if scaled_height > running_max_height then running_max_height = scaled_height end local recolored = ud_replaceAll(v.bmp, 7, opts.color) -- Check for normal wrapping if enabled if (x + xo >= wrap_bounds.w - scaled_width) and wrap_enabled then yo = yo + running_max_height + line_spacing xo = 0 -- Reset the running height for the next line running_max_height = 0 end -- Check for offscreen wrapping if enabled if wrap_offscreen and (x + xo + scaled_width >= sw) then yo = yo + running_max_height + line_spacing xo = 0 running_max_height = 0 end -- Draw the sprite with consistent scaling sspr(recolored, 0, 0, v.width, v.height, x + xo, y + (yo - scaled_ascent), scaled_width, scaled_height) -- Update x-offset by width, scaled kerning, and offset xo = xo + (scaled_width - scaled_offset + scaled_kerning) end end -- Reset the clipping bounds clip() end function Font:draw(text, x, y, size, col, opts) -- Calculate the recolored font. --self:recolored(col) opts = opts or {} opts.size = size opts.color = col or 7 -- Resolve the characters for text local chars = self:chars(text,col) -- Render the text renderChars(x,y,chars,opts) end |
Coming soon
- PicoUI integration with adaptive, sizable view
1
[Please log in to post a comment]