diff --git a/hexfont.lua b/hexfont.lua index 927da37..ed69363 100755 --- a/hexfont.lua +++ b/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 diff --git a/init.lua b/init.lua index 8cf4571..ee96569 100644 --- a/init.lua +++ b/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 diff --git a/test.lua b/test.lua index 34b25d8..5ee09c1 100755 --- a/test.lua +++ b/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")