Log In  


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
2


It seams great but how can I use it ?
myFont = Font:new(f) -> what is "f" ?


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)

This is an example of a font definition. You make the sprites in the standard editor and copy the pod. Then you write the character array which maps the individual sprites in the sprite editor to characters. Hopefully this helps



[Please log in to post a comment]