aboutsummaryrefslogtreecommitdiff
path: root/advtrains/textrender.lua
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains/textrender.lua')
-rw-r--r--advtrains/textrender.lua402
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