diff options
Diffstat (limited to 'advtrains/textrender.lua')
-rw-r--r-- | advtrains/textrender.lua | 402 |
1 files changed, 0 insertions, 402 deletions
diff --git a/advtrains/textrender.lua b/advtrains/textrender.lua deleted file mode 100644 index acba0d8..0000000 --- a/advtrains/textrender.lua +++ /dev/null @@ -1,402 +0,0 @@ ---[[ Simple text rendering (= texture generating) library -Definitions: -* A string ("str") refers to a regular Lua string -* A codepoint ("cp") refers to a Unicode codepoint -* A character ("char") refers to the smallest unit of rendering -* A segment ("seg") refers to a unit of text that should not be broken across two or more lines unless the renderer is explicitly given the option to do so -* A sequence ("seq") is a Lua table t where: - * Every element with a numeric index are of the same type - * Numeric indices are continuous positive integers starting from 1 - * String indices can exist to indicate certain properties of the sequence - (Note that a "numeric index" refers to an index of a table that is a number. This includes floating-point numbers, inf, -inf, and NaN, if present) -For simplicity, the above definitions may differ from that of Unicode -Codepoints and characters differ in that characters can be composed out of multiple codepoints (e.g. the character "รก" can be represented with U+00E1 or with a sequence of U+0061 followed by U+0301) - -Exposed API: -* mbstoucs(str) a utility function that converts a string to a sequence of Unicode codepoints -* texture_escape(str) a utility function that escapes the texture string -]] - --- Lookup tables -local characters = {} -do -- prevent LUT creation code from polluting the local environment - local function ensure_entry_exists(char) - local e = characters[char] - if not e then - e = {} - characters[char] = e - end - return e - end - local charwidth_lut = {} - local charwidth_ranges = { - [8] = { - 0x20,0x7e, -- Latin - 0xa0,0x052f, -- LGC - 0x1e00,0x1fff, -- Latin, Greek - }, - } - for k, v in pairs(charwidth_lut) do - ensure_entry_exists(k) - characters[k].width = v - end - for k, v in pairs(charwidth_ranges) do - for i = 1, #v, 2 do - for j = v[i], v[i+1] do - ensure_entry_exists(j) - if not characters[j].width then - characters[j].width = k - end - end - end - end - local charprops = { - can_break_lines = {0x20,}, - force_linebreak = {0x0a,}, - } - for k, v in pairs(charprops) do - for i = 1, #v do - local char = v[i] - ensure_entry_exists(char) - characters[char][k] = true - end - end - -- import "interesting" data from UnicodeData - local f = io.open(advtrains.modpath.."/UCD/UnicodeData.txt","r") or error("Failed to open UnicodeData") - for line in f:lines() do - local cp, decomp = string.match(line, "^(%x+);[^;]*;[^;]*;[^;]*;[^;]*;([^;]*);.+$") - if cp and decomp then - cp = tonumber(cp, 16) - decomp = string.split(decomp, " ") - end - if cp then - if decomp[1] then - local compatfmt = string.match(decomp[1], "^<([^>]+)>$") - if compatfmt then - table.remove(decomp, 1) - end - if not compatfmt then - if #decomp == 2 then - local base, modifier = tonumber(decomp[1], 16), tonumber(decomp[2], 16) - if base and modifier then - local bt = ensure_entry_exists(base) - if not bt.modifiers then - bt.modifiers = {} - end - bt.modifiers[modifier] = cp - end - end - end - end - end - end - f:close() -end - -local function texture_escape(str) - return string.gsub(str, "[%[%()^:]", "\\%1") -end - --- Functions -local function mbstoucs(str) - if type(str) ~= "string" then - return {} - end - local i = 1 - local strlen = #str - local cps = {} - while i <= strlen do - local bytes = {string.byte(str, i, math.max(i+3, strlen))} - local nbytes - local codepoint - local msb = bytes[1] - if msb < 128 then - nbytes = 1 - elseif msb >= 192 and bytes[2] and bytes[2] >= 128 and bytes[2] < 192 then - if msb < 224 then - nbytes = 2 - elseif bytes[3] and bytes[3] >= 128 and bytes[3] < 192 then - if msb < 240 then - nbytes = 3 - elseif msb < 148 and bytes[4] and bytes[4] >= 128 and bytes[4] < 192 then - nbytes = 4 - end - end - end - if not nbytes then - return {} -- invalid MB character encountered - elseif nbytes == 1 then - codepoint = msb - else - codepoint = msb%(2^(7-nbytes)) - for i = 2, nbytes do - codepoint = codepoint * 64 + bytes[i] - 128 - end - end - cps[#cps+1] = codepoint - i = i + nbytes - end - return cps -end - -local function ucstombs(ucs) - local s = "" - for i = 1, #ucs do - local cp = math.floor(ucs[i]) - local len - if cp < 0 or cp > 0x10ffff then - return "" - end - if cp < 0x80 then - len = 1 - elseif cp < 0x0800 then - len = 2 - elseif cp < 0x010000 then - len = 3 - else - len = 4 - end - if len == 1 then - s = s .. string.char(cp) - else - local t = {} - for i = len, 2, -1 do - local rem = cp % 64 - cp = math.floor(cp/64) - t[i] = 128+rem - end - t[1] = cp + 256 - 2^(8-len) - s = s .. string.char(unpack(t)) - end - end - return s -end - -local function basechar(char) - if type(char) == "table" then - return char[1] - else - return char - end -end - -local function charwidth(char) - return (characters[basechar(char)] or {}).width or 0 -end - --- Splits the text into segments -local function split_into_segments(cs) - local sl = {} - for i = 1, #cs do - local char = cs[i] - local props = characters[basechar(char)] or {} - local cwidth = charwidth(char) - if props.can_break_lines then - sl[#sl+1] = {bchar = char, bwidth = cwidth, width = cwidth, seq = {}, size = {}} - elseif props.force_linebreak then - sl[#sl+1] = {width = 0, seq = {}, size = {}} - else - if not sl[1] then - sl[1] = {width = cwidth, seq = {char}, size = {cwidth}} - else - seg = sl[#sl] - local seq, size = seg.seq, seg.size - seq[#seq+1] = char - size[#size+1] = (size[#size] or 0) + cwidth - seg.width = seg.width + cwidth - end - end - end - return sl -end - -local function split_lines(cs, width, height, options) - local lines = {} - local nlines = options.lines - local lwidth = width+1 -- force newline for the first line - local wrap_overflow = options.wrap_overflow - local align = options.align - local sl = split_into_segments(cs) - for i = 1, #sl do - seg = sl[i] - local seq = seg.seq - if not seg.bchar then - lwidth = width+1 --force newline - end - if lwidth + seg.width <= width then - local line = lines[#lines] - lwidth = (line.width or 0) + seg.width - line.width = lwidth - if seg.bchar then - line[#line+1] = seg.bchar - end - local seq = seg.seq - for i = 1, #seq do - line[#line+1] = seq[i] - end - elseif lwidth > 0 then - -- create a new line - local line = seq - line.width = seg.size[#line] - lines[#lines+1] = line - lwidth = 0 - else -- now we need to deal with segments longer than the given width - if wrap_overflow then - -- forcibly break the sequence - local st = seg.size - local ci = 1 - local tw = 0 - while ci <= #seq do - local l = {} - local j = ci - while j <= #seq and st[j]-tw <= width do - l[j-ci+1] = seq[j] - j = j+1 - end - if l[1] then - lwidth = st[j-1]-tw - l.width = lwidth - lines[#lines+1] = l - end - ci = j - end - else - -- only add the sequence that will get rendered - local minx = align*(seq.width-width) - local maxx = minx+width - local l = {width = 0} - local st = seg.size - local i = 1 - while st[i] and st[i] < minx do - i = i + 1 - end - while st[i] and st[i] <= maxx do - l[#l+1] = seq[i] - l.width = l.width + charwidth(seq[i]) - end - lines[#lines+1] = l - lwidth = l.width - end - end - if #lines > nlines then - for i = nlines+1, #lines do - lines[i] = nil - end - break - end - end - return lines -end - -local function cpstocs(cps) - local cs = {} - local i = 1 - local lastchar - while i <= #cps do - local cp = cps[i] - local addchar = true - if lastchar then - local lcp = characters[lastchar] - if lcp and lcp.modifiers and lcp.modifiers[cp] then - lastchar = lcp.modifiers[cp] - cs[#cs] = lastchar - addchar = false - end - end - if addchar then - cs[#cs+1] = cp - lastchar = cp - end - i = i + 1 - end - return cs -end - -local function check_options(width, height, options) - local w, h = tonumber(width), tonumber(height) - if not (w and h) then - return nil - end - if w < 8 or h < 18 then - return nil - end - local opt = options - if type(options) ~= "table" then - opt = {} - end - local ret = {} - -- check text alignment option - ret.align = math.min(math.max(tonumber(opt.align) or 0, 0),1) - -- check "wrap overflow text segment" option - ret.wrap_overflow = opt.wrap_overflow and true or false -- convert to boolean - -- check line count option - local maxlines = math.max(h/18) - ret.lines = math.min(maxlines, tonumber(opt.lines) or maxlines) - -- check text color option - ret.color = string.match(tostring(options.color) or "", "^#%x%x%x%x%x%x$") or "#000000" - return w, h, ret -end - -local function get_char_texture(char) - if type(char) == "number" then - return string.format("(advtrains_unifont_%04x.png\\^[sheet\\:256x1\\:%d,0)", - math.floor(char/256), - char%256 - ) - end -end - -local function render(str, ...) - local width, height, opts = check_options(...) - if not (width and height and opts) then - return nil - end - local lines = split_lines(cpstocs(mbstoucs(str)), width, height, opts) - local tst = {string.format("(([combine:%dx%d", width, height)} - local vpad = (height-18*#lines)/2 - local align = opts.align - for i = 1, #lines do - local y = vpad+18*(i-1) - local line = lines[i] - if line.width then - local x = (width-line.width)*align - for i = 1, #line do - local char = line[i] - local cw = charwidth(char) - if cw > 0 then - tst[#tst+1] = string.format("%d,%d=%s", x, y, get_char_texture(char)) - x = x + cw - end - end - end - end - return table.concat(tst,":")..string.format(")^[makealpha:0,0,0^[multiply:%s)", opts.color) -end - -local tilefmt = "advtrains_hud_bg.png^[resize:128x128^[colorize:#0000ff^(advtrains_hud_bg.png^[resize:128x64)^%s" -local sampletext = "Hello world\n\n".. - ucstombs{0xe1, 0x65, 0x0301, 0xef, 0x0301, 0x6f, 0x0303, 0x0301, 0x01d8, 0x20, -- Latin alphabet test - 0x03b1, 0x0301, 0x0345, 0x03b2, 0x03b3, 0x03b4, 0x03ad, 0x20, -- Greek alphabet test - 0x0430, 0x0308, 0x0431, 0x0432, 0x0433, 0x0301, 0x0434} -minetest.register_node("advtrains:textrender_demo",{ - description = "Text rendering demo", - tiles = {string.format(tilefmt, render(sampletext, 128, 128, {color="#00ff00"}))}, - groups = {cracky=3, not_in_creative_inventory=1} -}) -minetest.register_on_mods_loaded(function() -- wait until the trainhud module is loaded - minetest.register_craft{ - output = "advtrains:textrender_demo", - recipe = { - {"default:paper","default:paper","default:paper"}, - {"default:paper","advtrains:hud_demo","default:paper"}, - {"default:paper","default:paper","default:paper"}, - } - } -end) - -return { - mbstoucs = mbstoucs, - ucstombs = ucstombs, - render = render, - texture_escape = texture_escape, -}
\ No newline at end of file |