* 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("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
|
||||
|
|
1
init.lua
1
init.lua
|
@ -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
|
||||
|
|
29
test.lua
29
test.lua
|
@ -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 😀\
|
||||
\
|
||||
wð♥𐍈")
|
||||
wð♥𐍈"
|
||||
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")
|
||||
|
|
Loading…
Reference in New Issue