* Move font-rendering properties to hexfont class
This commit is contained in:
parent
25ea8cb779
commit
3d59899f78
235
hexfont.lua
235
hexfont.lua
|
@ -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
|
||||||
|
|
1
init.lua
1
init.lua
|
@ -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
|
||||||
|
|
29
test.lua
29
test.lua
|
@ -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 😀\
|
||||||
\
|
\
|
||||||
wð♥𐍈")
|
wð♥𐍈"
|
||||||
|
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")
|
||||||
|
|
Loading…
Reference in New Issue