* 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("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
-- DO NOT EVER REFUCKTOR IT INTO A FUNCTION!
local hex_to_bin = {
@ -39,39 +111,26 @@ local hex_to_bin = {
["F"] = "1111",
}
-- convert a binary bitmap to pixels accepted by tga_encoder
--
-- 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)
hexfont.bitmap_to_pixels = function(self, bitmap_hex)
-- bitmap_hex must be a string of uppercase hexadecimal digits
assert(
"string" == type(bitmap_hex) and
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
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 = {
background_color,
foreground_color,
self.background_color,
self.foreground_color,
}
local kerning = properties.kerning or false
assert(
"boolean" == type(kerning)
"boolean" == type(self.kerning)
)
-- 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
-- (users who “do not care about scanline order” might find their
-- glyphs upside down … the fault, naturally, lies with the user)
local scanline_order = properties.scanline_order or "bottom-top"
assert(
"bottom-top" == scanline_order or
"top-bottom" == scanline_order
"bottom-top" == self.scanline_order or
"top-bottom" == self.scanline_order
)
local height = 16
@ -115,15 +173,15 @@ local bitmap_to_pixels = function(bitmap_hex, properties)
-- flip image upside down for ”bottom-top” scanline order
-- (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)
end
if kerning then
if self.kerning then
-- remove rightmost column if it is empty
local remove_rightmost_column = true
for h = 1, height do
if foreground_color == pixels[h][width] then
if self.foreground_color == pixels[h][width] then
remove_rightmost_column = false
end
end
@ -137,8 +195,8 @@ local bitmap_to_pixels = function(bitmap_hex, properties)
local remove_leftmost_column = true
for h = 1, height do
if (
foreground_color == pixels[h][1] or
foreground_color == pixels[h][2]
self.foreground_color == pixels[h][1] or
self.foreground_color == pixels[h][2]
) then
remove_leftmost_column = false
end
@ -155,87 +213,20 @@ local bitmap_to_pixels = function(bitmap_hex, properties)
return pixels
end
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)
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)
hexfont.render_line = function(self, text)
assert(
"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
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(
"number" == type(tabulator_size)
"number" == type(self.tabulator_size)
)
local result = {}
@ -251,19 +242,16 @@ hexfont.render_line = function(self, text, properties)
if nil == bitmap_hex then
bitmap_hex = self[0xFFFD]
end
local bitmap = bitmap_to_pixels(
bitmap_hex,
properties
)
local bitmap = self:bitmap_to_pixels(bitmap_hex)
if 0x0009 == codepoint then -- HT (horizontal tab)
local result_width = #result[1]
local tab_stop = math.floor(
result_width / tabulator_size + 1
) * tabulator_size
result_width / self.tabulator_size + 1
) * self.tabulator_size
result = pixelops.pad_right(
result,
tab_stop - result_width,
background_color
self.background_color
)
else
local result_width = #result[1]
@ -273,7 +261,7 @@ hexfont.render_line = function(self, text, properties)
-- FIXME: this is horrible, but seems to work
for j = 1, 16 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]
end
end
@ -291,32 +279,23 @@ hexfont.render_line = function(self, text, properties)
return result
end
hexfont.render_text = function(self, text, properties)
local properties = properties or {}
assert(
"table" == type(properties)
)
local background_color = properties.background_color or { 0 }
local foreground_color = properties.foreground_color or { 255 }
hexfont.render_text = function(self, text)
-- background and foreground color must have equal color depth
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(
"bottom-top" == scanline_order or
"top-bottom" == scanline_order
"bottom-top" == self.scanline_order or
"top-bottom" == self.scanline_order
)
local result
-- TODO: implement UAX #14
for utf8_line in string.gmatch(text .. "\n", "([^\n]*)\n") do
local pixels = self:render_line(
utf8_line,
properties
)
local pixels = self:render_line(utf8_line)
assert( nil ~= pixels )
if nil == result then
result = pixels
@ -327,24 +306,24 @@ hexfont.render_text = function(self, text, properties)
pixels = pixelops.pad_right(
pixels,
result_width - pixels_width,
background_color
self.background_color
)
elseif result_width < pixels_width then
result = pixelops.pad_right(
result,
pixels_width - result_width,
background_color
self.background_color
)
end
assert(
#result[1] == #pixels[1]
)
if "bottom-top" == scanline_order then
if "bottom-top" == self.scanline_order then
for i = #pixels, 1, -1 do
table.insert(result, 1, pixels[i])
end
end
if "top-bottom" == scanline_order then
if "top-bottom" == self.scanline_order then
for i = 1, #pixels do
result[#result+1] = pixels[i]
end

View File

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

View File

@ -2,21 +2,34 @@
-- -*- coding: utf-8 -*-
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")
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)
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")
tga_encoder.image(
font:render_text(
font_2:render_text(
file:read("*all")
)
):save("UTF-8-demo.tga")