* Move font-rendering properties to hexfont class

This commit is contained in:
Nils Dagsson Moskopp 2023-03-18 22:33:18 +01:00
parent 25ea8cb779
commit 3d59899f78
3 changed files with 129 additions and 136 deletions

View File

@ -18,6 +18,78 @@ dofile("combining.lua")
dofile("pixelops.lua") dofile("pixelops.lua")
dofile("utf8.lua") dofile("utf8.lua")
hexfont = setmetatable(
{},
{
__call = function(self, ...)
local new_hexfont = setmetatable(
{},
{
__index = self
}
)
new_hexfont:constructor(...)
return new_hexfont
end
}
)
local iter = function(table_)
local index = 0
local total = #table_
return function()
index = index + 1
if index <= total
then
return table_[index]
end
end
end
hexfont.constructor = function(self, properties)
properties = properties or {}
assert(
"table" == type(properties)
)
-- Defaults
self.background_color = properties.background_color or { 0x00 }
self.foreground_color = properties.foreground_color or { 0xFF }
self.scanline_order = properties.scanline_order or "bottom-top"
self.tabulator_size = properties.tabulator_size or 8 * 8
self.kerning = properties.kerning or false
local minimal_hexfont = {
-- U+FFFD REPLACEMENT CHARACTER
"FFFD:0000018003C006600C301998399C7F3E7E7E3E7C1FF80E70066003C001800000"
}
self:load_glyphs(
iter(minimal_hexfont)
)
end
-- Usage:
-- hexfont.load_glyphs(io.lines("unifont.hex"))
-- hexfont.load_glyphs(io.lines("unifont_upper.hex"))
hexfont.load_glyphs = function(self, iterator)
assert( "function" == type(iterator) )
for line in iterator do
assert("string" == type(line))
local codepoint_hex, bitmap_hex = line:match(
"([0123456789ABCDEF]+):([01234567890ABCDEF]+)"
)
codepoint = tonumber(codepoint_hex, 16)
self[codepoint] = bitmap_hex
end
end
-- Test: Glyphs are correctly loaded
local font = hexfont({})
assert(
font[0xFFFD] == "0000018003C006600C301998399C7F3E7E7E3E7C1FF80E70066003C001800000"
)
font = nil
-- a lookup table was chosen for readability -- a lookup table was chosen for readability
-- DO NOT EVER REFUCKTOR IT INTO A FUNCTION! -- DO NOT EVER REFUCKTOR IT INTO A FUNCTION!
local hex_to_bin = { local hex_to_bin = {
@ -39,39 +111,26 @@ local hex_to_bin = {
["F"] = "1111", ["F"] = "1111",
} }
-- convert a binary bitmap to pixels accepted by tga_encoder hexfont.bitmap_to_pixels = function(self, bitmap_hex)
--
-- properties.background_color and properties.foreground_color must
-- have the same amount of entries. Use one entry for grayscale or
-- colormapped (palette) output, use three entries for RGB and four
-- entries for RGBA.
--
local bitmap_to_pixels = function(bitmap_hex, properties)
-- bitmap_hex must be a string of uppercase hexadecimal digits -- bitmap_hex must be a string of uppercase hexadecimal digits
assert( assert(
"string" == type(bitmap_hex) and "string" == type(bitmap_hex) and
bitmap_hex:match("[0123456789ABCDEF]+") == bitmap_hex bitmap_hex:match("[0123456789ABCDEF]+") == bitmap_hex
) )
local properties = properties or {}
assert(
"table" == type(properties)
)
local background_color = properties.background_color or { 0 }
local foreground_color = properties.foreground_color or { 255 }
-- background and foreground color must have equal color depth -- background and foreground color must have equal color depth
assert( assert(
#background_color == #foreground_color "table" == type(self.background_color) and
"table" == type(self.foreground_color) and
#self.background_color == #self.foreground_color
) )
local colormap = { local colormap = {
background_color, self.background_color,
foreground_color, self.foreground_color,
} }
local kerning = properties.kerning or false
assert( assert(
"boolean" == type(kerning) "boolean" == type(self.kerning)
) )
-- scanline order “bottom-top” was chosen as the default to match -- scanline order “bottom-top” was chosen as the default to match
@ -79,10 +138,9 @@ local bitmap_to_pixels = function(bitmap_hex, properties)
-- using another file format encoder to care about scanline order -- using another file format encoder to care about scanline order
-- (users who “do not care about scanline order” might find their -- (users who “do not care about scanline order” might find their
-- glyphs upside down … the fault, naturally, lies with the user) -- glyphs upside down … the fault, naturally, lies with the user)
local scanline_order = properties.scanline_order or "bottom-top"
assert( assert(
"bottom-top" == scanline_order or "bottom-top" == self.scanline_order or
"top-bottom" == scanline_order "top-bottom" == self.scanline_order
) )
local height = 16 local height = 16
@ -115,15 +173,15 @@ local bitmap_to_pixels = function(bitmap_hex, properties)
-- flip image upside down for ”bottom-top” scanline order -- flip image upside down for ”bottom-top” scanline order
-- (i.e. the first encoded pixel is the bottom left pixel) -- (i.e. the first encoded pixel is the bottom left pixel)
if "bottom-top" == scanline_order then if "bottom-top" == self.scanline_order then
pixels = pixelops.flip_vertically(pixels) pixels = pixelops.flip_vertically(pixels)
end end
if kerning then if self.kerning then
-- remove rightmost column if it is empty -- remove rightmost column if it is empty
local remove_rightmost_column = true local remove_rightmost_column = true
for h = 1, height do for h = 1, height do
if foreground_color == pixels[h][width] then if self.foreground_color == pixels[h][width] then
remove_rightmost_column = false remove_rightmost_column = false
end end
end end
@ -137,8 +195,8 @@ local bitmap_to_pixels = function(bitmap_hex, properties)
local remove_leftmost_column = true local remove_leftmost_column = true
for h = 1, height do for h = 1, height do
if ( if (
foreground_color == pixels[h][1] or self.foreground_color == pixels[h][1] or
foreground_color == pixels[h][2] self.foreground_color == pixels[h][2]
) then ) then
remove_leftmost_column = false remove_leftmost_column = false
end end
@ -155,87 +213,20 @@ local bitmap_to_pixels = function(bitmap_hex, properties)
return pixels return pixels
end end
hexfont = setmetatable( hexfont.render_line = function(self, text)
{},
{
__call = function(self, ...)
local new_hexfont = setmetatable(
{},
{
__index = self
}
)
new_hexfont:constructor(...)
return new_hexfont
end
}
)
local iter = function(table_)
local index = 0
local total = #table_
return function()
index = index + 1
if index <= total
then
return table_[index]
end
end
end
hexfont.constructor = function(self)
local minimal_hexfont = {
-- U+FFFD REPLACEMENT CHARACTER
"FFFD:0000018003C006600C301998399C7F3E7E7E3E7C1FF80E70066003C001800000"
}
self:load_glyphs(
iter(minimal_hexfont)
)
end
-- Usage:
-- hexfont.load_glyphs(io.lines("unifont.hex"))
-- hexfont.load_glyphs(io.lines("unifont_upper.hex"))
hexfont.load_glyphs = function(self, iterator)
assert( "function" == type(iterator) )
for line in iterator do
assert("string" == type(line))
local codepoint_hex, bitmap_hex = line:match(
"([0123456789ABCDEF]+):([01234567890ABCDEF]+)"
)
codepoint = tonumber(codepoint_hex, 16)
self[codepoint] = bitmap_hex
end
end
-- Test: Glyphs are correctly loaded
local font = hexfont()
assert(
font[0xFFFD] == "0000018003C006600C301998399C7F3E7E7E3E7C1FF80E70066003C001800000"
)
font = nil
hexfont.render_line = function(self, text, properties)
assert( assert(
"string" == type(text) "string" == type(text)
) )
properties = properties or {}
assert(
"table" == type(properties)
)
-- default colors are black (0) & white (255) in 1 bit color depth
local background_color = properties.background_color or { 0 }
local foreground_color = properties.foreground_color or { 255 }
-- background and foreground color must have equal color depth -- background and foreground color must have equal color depth
assert( assert(
#background_color == #foreground_color "table" == type(self.background_color) and
"table" == type(self.foreground_color) and
#self.background_color == #self.foreground_color
) )
local tabulator_size = properties.tabulator_size or 8 * 8
assert( assert(
"number" == type(tabulator_size) "number" == type(self.tabulator_size)
) )
local result = {} local result = {}
@ -251,19 +242,16 @@ hexfont.render_line = function(self, text, properties)
if nil == bitmap_hex then if nil == bitmap_hex then
bitmap_hex = self[0xFFFD] bitmap_hex = self[0xFFFD]
end end
local bitmap = bitmap_to_pixels( local bitmap = self:bitmap_to_pixels(bitmap_hex)
bitmap_hex,
properties
)
if 0x0009 == codepoint then -- HT (horizontal tab) if 0x0009 == codepoint then -- HT (horizontal tab)
local result_width = #result[1] local result_width = #result[1]
local tab_stop = math.floor( local tab_stop = math.floor(
result_width / tabulator_size + 1 result_width / self.tabulator_size + 1
) * tabulator_size ) * self.tabulator_size
result = pixelops.pad_right( result = pixelops.pad_right(
result, result,
tab_stop - result_width, tab_stop - result_width,
background_color self.background_color
) )
else else
local result_width = #result[1] local result_width = #result[1]
@ -273,7 +261,7 @@ hexfont.render_line = function(self, text, properties)
-- FIXME: this is horrible, but seems to work -- FIXME: this is horrible, but seems to work
for j = 1, 16 do for j = 1, 16 do
for k = 1, bitmap_width do for k = 1, bitmap_width do
if foreground_color == bitmap[j][k] then if self.foreground_color == bitmap[j][k] then
result[j][result_width - bitmap_width + k] = bitmap[j][k] result[j][result_width - bitmap_width + k] = bitmap[j][k]
end end
end end
@ -291,32 +279,23 @@ hexfont.render_line = function(self, text, properties)
return result return result
end end
hexfont.render_text = function(self, text, properties) hexfont.render_text = function(self, text)
local properties = properties or {}
assert(
"table" == type(properties)
)
local background_color = properties.background_color or { 0 }
local foreground_color = properties.foreground_color or { 255 }
-- background and foreground color must have equal color depth -- background and foreground color must have equal color depth
assert( assert(
#background_color == #foreground_color "table" == type(self.background_color) and
"table" == type(self.foreground_color) and
#self.background_color == #self.foreground_color
) )
local scanline_order = properties.scanline_order or "bottom-top"
assert( assert(
"bottom-top" == scanline_order or "bottom-top" == self.scanline_order or
"top-bottom" == scanline_order "top-bottom" == self.scanline_order
) )
local result local result
-- TODO: implement UAX #14 -- TODO: implement UAX #14
for utf8_line in string.gmatch(text .. "\n", "([^\n]*)\n") do for utf8_line in string.gmatch(text .. "\n", "([^\n]*)\n") do
local pixels = self:render_line( local pixels = self:render_line(utf8_line)
utf8_line,
properties
)
assert( nil ~= pixels ) assert( nil ~= pixels )
if nil == result then if nil == result then
result = pixels result = pixels
@ -327,24 +306,24 @@ hexfont.render_text = function(self, text, properties)
pixels = pixelops.pad_right( pixels = pixelops.pad_right(
pixels, pixels,
result_width - pixels_width, result_width - pixels_width,
background_color self.background_color
) )
elseif result_width < pixels_width then elseif result_width < pixels_width then
result = pixelops.pad_right( result = pixelops.pad_right(
result, result,
pixels_width - result_width, pixels_width - result_width,
background_color self.background_color
) )
end end
assert( assert(
#result[1] == #pixels[1] #result[1] == #pixels[1]
) )
if "bottom-top" == scanline_order then if "bottom-top" == self.scanline_order then
for i = #pixels, 1, -1 do for i = #pixels, 1, -1 do
table.insert(result, 1, pixels[i]) table.insert(result, 1, pixels[i])
end end
end end
if "top-bottom" == scanline_order then if "top-bottom" == self.scanline_order then
for i = 1, #pixels do for i = 1, #pixels do
result[#result+1] = pixels[i] result[#result+1] = pixels[i]
end end

View File

@ -16,5 +16,6 @@ Codezeilen und schreibe das auch nicht dran.
unicode_text = {} unicode_text = {}
-- unicode_text only supports GNU Unifont .hex file format for now
dofile("hexfont.lua") dofile("hexfont.lua")
unicode_text.hexfont = hexfont unicode_text.hexfont = hexfont

View File

@ -2,21 +2,34 @@
-- -*- coding: utf-8 -*- -- -*- coding: utf-8 -*-
dofile("init.lua") dofile("init.lua")
font = unicode_text.hexfont()
font:load_glyphs( io.lines("/usr/share/unifont/unifont.hex") )
font:load_glyphs( io.lines("unifont_upper.hex") )
dofile("tga_encoder.lua") dofile("tga_encoder.lua")
local pixels = font:render_text("ABC 123 😀\
font_1 = unicode_text.hexfont()
font_1:load_glyphs(
io.lines("/usr/share/unifont/unifont.hex")
)
local text = "ABC 123 😀\
\ \
𐍈") 𐍈"
local pixels = font_1:render_text(text)
local image = tga_encoder.image(pixels) local image = tga_encoder.image(pixels)
image:save("test.tga") image:save("test.tga")
font_2 = unicode_text.hexfont(
{
background_color = { 0x33, 0x66, 0x99 },
foreground_color = { 0xDD, 0x00, 0x00 },
}
)
font_2:load_glyphs(
io.lines("/usr/share/unifont/unifont.hex")
)
font_2:load_glyphs(
io.lines("unifont_upper.hex")
)
local file = io.open("UTF-8-demo.txt") local file = io.open("UTF-8-demo.txt")
tga_encoder.image( tga_encoder.image(
font:render_text( font_2:render_text(
file:read("*all") file:read("*all")
) )
):save("UTF-8-demo.tga") ):save("UTF-8-demo.tga")