mirror of
https://git.minetest.land/VoxeLibre/VoxeLibre.git
synced 2025-04-23 07:35:16 +02:00
Use UTF-8 codepoint parsing and disable word wrap
characters get hyphenated and line broken instead
This commit is contained in:
parent
122b8b6ea0
commit
30c05c8e69
7 changed files with 276 additions and 199 deletions
|
@ -3,28 +3,40 @@
|
|||
## Functions
|
||||
|
||||
* `mcl_signs.register_sign(name, color, [definition])`
|
||||
* `name` is the part of the namestring that will follow `"mcl_signs:"`
|
||||
* `color` is the HEX color value to color the greyscale sign texture with.
|
||||
**Hint:** use `""` or `"#ffffff"` if you're overriding the texture fields
|
||||
in sign definition
|
||||
* `definition` is optional, see section below for reference
|
||||
|
||||
## Sign definition
|
||||
|
||||
```lua
|
||||
{
|
||||
-- This can contain any node definition fields which will ultimately make up the sign nodes.
|
||||
-- Usually you will want to at least supply "description" and "_doc_items_longdesc".
|
||||
-- This can contain any node definition fields which will ultimately make
|
||||
-- up the sign nodes.
|
||||
-- Usually you'll want to at least supply `description`.
|
||||
description = S("Significant Sign"),
|
||||
|
||||
-- If you don't want to use texture coloring, you'll have to supply the
|
||||
-- textures yourself:
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## `characters.txt`
|
||||
## Character map (`characters.tsv`)
|
||||
|
||||
It's a UTF-8 encoded text file that contains metadata for all supported
|
||||
characters. It contains a sequence of info blocks, one for each character. Each
|
||||
info block is made out of 3 lines:
|
||||
characters. Despite its file extension and the theoretical possibility of
|
||||
opening it in a spreadsheet editor, it's still plaintext values separated with
|
||||
`\t` (tab idents). The separated values are _columns_, and the lines they are
|
||||
located at are _cells_. 1 cell and 3 columns per character:
|
||||
|
||||
* **Line 1:** The literal UTF-8 encoded character
|
||||
* **Line 2:** Name of the texture file for this character minus the ".png"
|
||||
* **Column 1:** The literal UTF-8 encoded character
|
||||
* **Column 2:** Name of the texture file for this character minus the ".png"
|
||||
suffix (found in the "textures/" sub-directory in root)
|
||||
* **Line 3:** Currently ignored. Previously this was for the character width
|
||||
in pixels
|
||||
* **Column 3:** Currently ignored. This is reserved for character width in
|
||||
pixels in case the font will be made proportional
|
||||
|
||||
After line 3, another info block may follow. This repeats until the end of the file.
|
||||
|
||||
All character files must be 5 or 6 pixels wide (5 pixels are preferred).
|
||||
All character textures must be 12 pixels high and 5 or 6 pixels wide (5
|
||||
is preferred).
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
Code and font: MIT (see `LICENSE` file for details)
|
||||
|
||||
`utf8.lua` is taken from `modlib`, by Lars Mueller alias LMD or appguru(eu): [source](https://github.com/appgurueu/modlib/blob/master/utf8.lua)
|
||||
|
||||
License of models: GPLv3 (https://www.gnu.org/licenses/gpl-3.0.html)\
|
||||
Models author: 22i.\
|
||||
Source: <https://github.com/22i/amc>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
local S, charmap = ...
|
||||
local S, charmap, utf8 = ...
|
||||
|
||||
local function table_merge(t, ...)
|
||||
local t2 = table.copy(t)
|
||||
|
@ -13,6 +13,12 @@ local NUMBER_OF_LINES = 4
|
|||
local LINE_HEIGHT = 14
|
||||
local CHAR_WIDTH = 5
|
||||
|
||||
local SIGN_GLOW_INTENSITY = 14
|
||||
|
||||
local LF_CODEPOINT = utf8.codepoint("\n")
|
||||
local SP_CODEPOINT = utf8.codepoint(" ")
|
||||
local DS_CODEPOINT = utf8.codepoint("-") -- used as the wrapping character
|
||||
|
||||
local DEFAULT_COLOR = "#000000"
|
||||
local DYE_TO_COLOR = {
|
||||
["white"] = "#d0d6d7",
|
||||
|
@ -33,10 +39,6 @@ local DYE_TO_COLOR = {
|
|||
["pink"] = "#d56791",
|
||||
}
|
||||
|
||||
local wordwrap_enabled = core.settings:get_bool("vl_signs_word_wrap", true)
|
||||
|
||||
local SIGN_GLOW_INTENSITY = 14
|
||||
|
||||
local F = core.formspec_escape
|
||||
|
||||
-- Template definition
|
||||
|
@ -72,10 +74,15 @@ local function get_signdata(pos)
|
|||
local node = core.get_node(pos)
|
||||
local def = core.registered_nodes[node.name]
|
||||
if not def or core.get_item_group(node.name, "sign") < 1 then return end
|
||||
|
||||
local meta = core.get_meta(pos)
|
||||
local text = meta:get_string("text")
|
||||
local color = meta:get_string("color")
|
||||
if color == "" then
|
||||
color = DEFAULT_COLOR
|
||||
end
|
||||
local glow = core.is_yes(meta:get_string("glow"))
|
||||
|
||||
local yaw, spos
|
||||
local typ = "standing"
|
||||
if def.paramtype2 == "wallmounted" then
|
||||
|
@ -84,11 +91,11 @@ local function get_signdata(pos)
|
|||
spos = vector.add(vector.offset(pos, 0, -0.25, 0), dir * 0.41)
|
||||
yaw = core.dir_to_yaw(dir)
|
||||
else
|
||||
yaw = math.rad(((node.param2 * 1.5 ) + 1 ) % 360)
|
||||
yaw = math.rad(((node.param2 * 1.5) + 1) % 360)
|
||||
local dir = core.yaw_to_dir(yaw)
|
||||
spos = vector.add(vector.offset(pos, 0, 0.08, 0), dir * -0.05)
|
||||
end
|
||||
if color == "" then color = DEFAULT_COLOR end
|
||||
|
||||
return {
|
||||
text = text,
|
||||
color = color,
|
||||
|
@ -107,95 +114,48 @@ local function set_signmeta(pos, def)
|
|||
if def.glow then meta:set_string("glow", def.glow) end
|
||||
end
|
||||
|
||||
local function word_wrap(str)
|
||||
local output = {}
|
||||
for line in str:gmatch("[^\r\n]*") do
|
||||
local nline = ""
|
||||
for word in line:gmatch("%S+") do
|
||||
if #nline + #word + 1 > LINE_LENGTH then
|
||||
if nline ~= "" then table.insert(output, nline) end
|
||||
nline = word
|
||||
else
|
||||
if nline ~= "" then nline = nline .. " " end
|
||||
nline = nline .. word
|
||||
end
|
||||
end
|
||||
table.insert(output, nline)
|
||||
end
|
||||
return table.concat(output, "\n")
|
||||
end
|
||||
|
||||
local function string_to_line_array(str)
|
||||
local linechar_table = {}
|
||||
local current = 1
|
||||
local linechar = 1
|
||||
local cr_last = false
|
||||
linechar_table[current] = ""
|
||||
local lines = {}
|
||||
local line = {}
|
||||
|
||||
-- compile characters
|
||||
for char in str:gmatch(".") do
|
||||
local add
|
||||
local is_cr, is_lf = char == "\r", char == "\n"
|
||||
str = string.gsub(str, "\r\n?", "\n")
|
||||
for _, code in utf8.codes(str) do
|
||||
if #lines > NUMBER_OF_LINES then break end
|
||||
|
||||
if is_cr and not cr_last then
|
||||
cr_last = true
|
||||
add = false
|
||||
elseif is_lf or cr_last or linechar > LINE_LENGTH then
|
||||
cr_last = is_cr
|
||||
add = not (is_cr or is_lf)
|
||||
current = current + 1
|
||||
linechar_table[current] = ""
|
||||
linechar = 1
|
||||
if code == LF_CODEPOINT
|
||||
or code == SP_CODEPOINT and #line >= (LINE_LENGTH - 1) then
|
||||
table.insert(lines, line)
|
||||
line = {}
|
||||
elseif #line >= LINE_LENGTH then
|
||||
table.insert(line, DS_CODEPOINT)
|
||||
table.insert(lines, line)
|
||||
line = {code}
|
||||
else
|
||||
add = true
|
||||
end
|
||||
|
||||
if add then
|
||||
linechar_table[current] = linechar_table[current] .. char
|
||||
linechar = linechar + 1
|
||||
table.insert(line, code)
|
||||
end
|
||||
end
|
||||
if #line > 0 then table.insert(lines, line) end
|
||||
|
||||
return linechar_table
|
||||
return lines
|
||||
end
|
||||
mcl_signs.create_lines = string_to_line_array
|
||||
|
||||
function mcl_signs.create_lines(text)
|
||||
local text_table = {}
|
||||
for idx, line in ipairs(string_to_line_array(text)) do
|
||||
if idx > NUMBER_OF_LINES then
|
||||
break
|
||||
end
|
||||
table.insert(text_table, line)
|
||||
end
|
||||
return text_table
|
||||
end
|
||||
|
||||
function mcl_signs.generate_line(s, ypos)
|
||||
local i = 1
|
||||
local function generate_line(s, ypos)
|
||||
local parsed = {}
|
||||
local width = 0
|
||||
local chars = 0
|
||||
local printed_char_width = CHAR_WIDTH + 1
|
||||
while chars < LINE_LENGTH and i <= #s do
|
||||
local file
|
||||
-- Get and render character
|
||||
if charmap[s:sub(i, i)] then
|
||||
file = charmap[s:sub(i, i)]
|
||||
i = i + 1
|
||||
elseif i < #s and charmap[s:sub(i, i + 1)] then
|
||||
file = charmap[s:sub(i, i + 1)]
|
||||
i = i + 2
|
||||
else
|
||||
-- Use replacement character:
|
||||
file = "_rc"
|
||||
i = i + 1
|
||||
|
||||
for _, code in ipairs(s) do
|
||||
local file = "_rc"
|
||||
if charmap[utf8.char(code)] then
|
||||
file = charmap[utf8.char(code)]
|
||||
end
|
||||
if file then
|
||||
width = width + printed_char_width
|
||||
table.insert(parsed, file)
|
||||
chars = chars + 1
|
||||
end
|
||||
end
|
||||
|
||||
width = width - 1
|
||||
local texture = ""
|
||||
local xpos = math.floor((SIGN_WIDTH - width) / 2)
|
||||
|
@ -206,29 +166,106 @@ function mcl_signs.generate_line(s, ypos)
|
|||
end
|
||||
return texture
|
||||
end
|
||||
mcl_signs.generate_line = generate_line
|
||||
|
||||
function mcl_signs.generate_texture(data)
|
||||
data.text = data.text or ""
|
||||
--local lines = mcl_signs.create_lines(data.wordwrap and word_wrap(data.text) or data.text)
|
||||
local lines = mcl_signs.create_lines(wordwrap_enabled and word_wrap(data.text) or data.text)
|
||||
local function generate_texture(data)
|
||||
local lines = string_to_line_array(data.text or "")
|
||||
local texture = "[combine:" .. SIGN_WIDTH .. "x" .. SIGN_WIDTH
|
||||
local ypos = 0
|
||||
local letter_color = data.color or DEFAULT_COLOR
|
||||
|
||||
for _, line in ipairs(lines) do
|
||||
texture = texture .. mcl_signs.generate_line(line, ypos)
|
||||
texture = texture .. generate_line(line, ypos)
|
||||
ypos = ypos + LINE_HEIGHT
|
||||
end
|
||||
|
||||
texture = "(" .. texture .. "^[multiply:" .. letter_color .. ")"
|
||||
return texture
|
||||
end
|
||||
mcl_signs.generate_texture = generate_texture
|
||||
|
||||
function sign_tpl.on_place(itemstack, placer, pointed_thing)
|
||||
if pointed_thing.type ~= "node" then
|
||||
return itemstack
|
||||
-- Text entity handling
|
||||
local function get_text_entity(pos, force_remove)
|
||||
local objects = core.get_objects_inside_radius(pos, 0.5)
|
||||
local text_entity
|
||||
local i = 0
|
||||
for _, v in pairs(objects) do
|
||||
local ent = v:get_luaentity()
|
||||
if ent and ent.name == "mcl_signs:text" then
|
||||
i = i + 1
|
||||
if i > 1 or force_remove == true then
|
||||
v:remove()
|
||||
else
|
||||
text_entity = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return text_entity
|
||||
end
|
||||
mcl_signs.get_text_entity = get_text_entity
|
||||
|
||||
local function update_sign(pos)
|
||||
local data = get_signdata(pos)
|
||||
|
||||
local text_entity = get_text_entity(pos)
|
||||
if text_entity and not data then
|
||||
text_entity:remove()
|
||||
return false
|
||||
elseif not data then
|
||||
return false
|
||||
elseif not text_entity then
|
||||
text_entity = core.add_entity(data.text_pos, "mcl_signs:text")
|
||||
if not text_entity or not text_entity:get_pos() then return end
|
||||
end
|
||||
|
||||
local glow = 0
|
||||
if data.glow then
|
||||
glow = SIGN_GLOW_INTENSITY
|
||||
end
|
||||
text_entity:set_properties({
|
||||
textures = {generate_texture(data)},
|
||||
glow = glow,
|
||||
})
|
||||
text_entity:set_yaw(data.yaw)
|
||||
text_entity:set_armor_groups({immortal = 1})
|
||||
return true
|
||||
end
|
||||
mcl_signs.update_sign = update_sign
|
||||
|
||||
-- Formspec
|
||||
local function show_formspec(player, pos)
|
||||
if not pos then return end
|
||||
local meta = core.get_meta(pos)
|
||||
local old_text = meta:get_string("text")
|
||||
core.show_formspec(player:get_player_name(), "mcl_signs:set_text_"..pos.x.."_"..pos.y.."_"..pos.z, table.concat({
|
||||
"size[6,3]textarea[0.25,0.25;6,1.5;text;",
|
||||
F(S("Enter sign text:")), ";", F(old_text), "]",
|
||||
"label[0,1.5;",
|
||||
F(S("Maximum line length: @1", LINE_LENGTH)), "\n",
|
||||
F(S("Maximum lines: @1", NUMBER_OF_LINES)),
|
||||
"]",
|
||||
"button_exit[0,2.5;6,1;submit;", F(S("Done")), "]"
|
||||
}))
|
||||
end
|
||||
mcl_signs.show_formspec = show_formspec
|
||||
|
||||
core.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname:find("mcl_signs:set_text_") == 1 then
|
||||
local x, y, z = formname:match("mcl_signs:set_text_(.-)_(.-)_(.*)")
|
||||
local pos = vector.new(tonumber(x), tonumber(y), tonumber(z))
|
||||
if not fields or not fields.text then return end
|
||||
if not mcl_util.check_position_protection(pos, player) then
|
||||
set_signmeta(pos, {
|
||||
-- limit saved text to 256 characters
|
||||
-- (4 lines x 15 chars = 60 so this should be more than is ever needed)
|
||||
text = tostring(fields.text):sub(1, 256)
|
||||
})
|
||||
update_sign(pos)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function sign_tpl.on_place(itemstack, placer, pointed_thing)
|
||||
local under = pointed_thing.under
|
||||
local node = core.get_node(under)
|
||||
local def = core.registered_nodes[node.name]
|
||||
|
@ -243,24 +280,25 @@ function sign_tpl.on_place(itemstack, placer, pointed_thing)
|
|||
local wdir = core.dir_to_wallmounted(dir)
|
||||
|
||||
local itemstring = itemstack:get_name()
|
||||
local placestack = ItemStack(itemstack)
|
||||
local def = itemstack:get_definition()
|
||||
|
||||
local pos
|
||||
-- place on wall
|
||||
if wdir ~= 0 and wdir ~= 1 then
|
||||
placestack:set_name("mcl_signs:wall_sign_"..def._mcl_sign_wood)
|
||||
itemstack, pos = core.item_place_node(placestack, placer, pointed_thing, wdir)
|
||||
elseif wdir == 1 then -- standing, not ceiling
|
||||
local placestack = ItemStack(itemstack)
|
||||
if wdir < 1 then
|
||||
-- no placement on ceilings allowed yet
|
||||
return itemstack
|
||||
elseif wdir == 1 then
|
||||
placestack:set_name("mcl_signs:standing_sign_"..def._mcl_sign_wood)
|
||||
-- param2 value is degrees / 1.5
|
||||
local rot = normalize_rotation(placer:get_look_horizontal() * 180 / math.pi / 1.5)
|
||||
itemstack, pos = core.item_place_node(placestack, placer, pointed_thing, rot)
|
||||
else
|
||||
return itemstack
|
||||
placestack:set_name("mcl_signs:wall_sign_"..def._mcl_sign_wood)
|
||||
itemstack, pos = core.item_place_node(placestack, placer, pointed_thing, wdir)
|
||||
end
|
||||
|
||||
mcl_signs.show_formspec(placer, pos)
|
||||
show_formspec(placer, pos)
|
||||
-- restore canonical name as core.item_place_node might have changed it
|
||||
itemstack:set_name(itemstring)
|
||||
return itemstack
|
||||
end
|
||||
|
@ -274,7 +312,7 @@ function sign_tpl.on_rightclick(pos, _, clicker, itemstack)
|
|||
data.color = "#7e7e7e" -- black doesn't glow in the dark
|
||||
end
|
||||
set_signmeta(pos, {glow = "true", color = data.color})
|
||||
mcl_signs.update_sign(pos)
|
||||
update_sign(pos)
|
||||
if not core.is_creative_enabled(clicker:get_player_name()) then
|
||||
itemstack:take_item()
|
||||
end
|
||||
|
@ -284,22 +322,22 @@ function sign_tpl.on_rightclick(pos, _, clicker, itemstack)
|
|||
glow = "false",
|
||||
color = DEFAULT_COLOR,
|
||||
})
|
||||
mcl_signs.update_sign(pos)
|
||||
update_sign(pos)
|
||||
elseif iname:sub(1, 8) == "mcl_dye:" then
|
||||
local color = iname:sub(9)
|
||||
set_signmeta(pos, {color = DYE_TO_COLOR[color]})
|
||||
mcl_signs.update_sign(pos)
|
||||
update_sign(pos)
|
||||
if not core.is_creative_enabled(clicker:get_player_name()) then
|
||||
itemstack:take_item()
|
||||
end
|
||||
elseif not mcl_util.check_position_protection(pos, clicker) then
|
||||
mcl_signs.show_formspec(clicker, pos)
|
||||
show_formspec(clicker, pos)
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
|
||||
function sign_tpl.on_destruct(pos)
|
||||
mcl_signs.get_text_entity(pos, true)
|
||||
get_text_entity(pos, true)
|
||||
end
|
||||
|
||||
-- TODO: reactivate when a good dyes API is finished
|
||||
|
@ -321,91 +359,13 @@ local sign_wall = table_merge(sign_tpl, {
|
|||
_mcl_sign_type = "wall",
|
||||
})
|
||||
|
||||
-- Formspec
|
||||
function mcl_signs.show_formspec(player, pos)
|
||||
if not pos then return end
|
||||
local meta = core.get_meta(pos)
|
||||
local old_text = meta:get_string("text")
|
||||
core.show_formspec(player:get_player_name(), "mcl_signs:set_text_"..pos.x.."_"..pos.y.."_"..pos.z, table.concat({
|
||||
"size[6,3]textarea[0.25,0.25;6,1.5;text;",
|
||||
F(S("Enter sign text:")), ";", F(old_text), "]",
|
||||
"label[0,1.5;",
|
||||
F(S("Maximum line length: @1", LINE_LENGTH)), "\n",
|
||||
F(S("Maximum lines: @1", NUMBER_OF_LINES)),
|
||||
"]",
|
||||
"button_exit[0,2.5;6,1;submit;", F(S("Done")), "]"
|
||||
}))
|
||||
end
|
||||
|
||||
core.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname:find("mcl_signs:set_text_") == 1 then
|
||||
local x, y, z = formname:match("mcl_signs:set_text_(.-)_(.-)_(.*)")
|
||||
local pos = vector.new(tonumber(x), tonumber(y), tonumber(z))
|
||||
if not fields or not fields.text then return end
|
||||
if not mcl_util.check_position_protection(pos, player) then
|
||||
set_signmeta(pos, {
|
||||
-- limit saved text to 256 characters
|
||||
-- (4 lines x 15 chars = 60 so this should be more than is ever needed)
|
||||
text = tostring(fields.text):sub(1, 256)
|
||||
})
|
||||
mcl_signs.update_sign(pos)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Text entity handling
|
||||
function mcl_signs.get_text_entity(pos, force_remove)
|
||||
local objects = core.get_objects_inside_radius(pos, 0.5)
|
||||
local text_entity
|
||||
local i = 0
|
||||
for _, v in pairs(objects) do
|
||||
local ent = v:get_luaentity()
|
||||
if ent and ent.name == "mcl_signs:text" then
|
||||
i = i + 1
|
||||
if i > 1 or force_remove == true then
|
||||
v:remove()
|
||||
else
|
||||
text_entity = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return text_entity
|
||||
end
|
||||
|
||||
function mcl_signs.update_sign(pos)
|
||||
local data = get_signdata(pos)
|
||||
|
||||
local text_entity = mcl_signs.get_text_entity(pos)
|
||||
if text_entity and not data then
|
||||
text_entity:remove()
|
||||
return false
|
||||
elseif not data then
|
||||
return false
|
||||
elseif not text_entity then
|
||||
text_entity = core.add_entity(data.text_pos, "mcl_signs:text")
|
||||
if not text_entity or not text_entity:get_pos() then return end
|
||||
end
|
||||
|
||||
local glow = 0
|
||||
if data.glow then
|
||||
glow = SIGN_GLOW_INTENSITY
|
||||
end
|
||||
text_entity:set_properties({
|
||||
textures = {mcl_signs.generate_texture(data)},
|
||||
glow = glow,
|
||||
})
|
||||
text_entity:set_yaw(data.yaw)
|
||||
text_entity:set_armor_groups({immortal = 1})
|
||||
return true
|
||||
end
|
||||
|
||||
core.register_lbm({
|
||||
nodenames = {"group:sign"},
|
||||
name = "mcl_signs:restore_entities",
|
||||
label = "Restore sign text",
|
||||
run_at_every_load = true,
|
||||
action = function(pos)
|
||||
mcl_signs.update_sign(pos)
|
||||
update_sign(pos)
|
||||
end
|
||||
})
|
||||
|
||||
|
@ -418,7 +378,7 @@ core.register_entity("mcl_signs:text", {
|
|||
},
|
||||
on_activate = function(self)
|
||||
local pos = self.object:get_pos()
|
||||
mcl_signs.update_sign(pos)
|
||||
update_sign(pos)
|
||||
local props = self.object:get_properties()
|
||||
local t = props and props.textures
|
||||
if type(t) ~= "table" or #t == 0 then self.object:remove() end
|
||||
|
|
|
@ -76,7 +76,7 @@ end
|
|||
function mcl_signs.upgrade_sign_rot(pos, node)
|
||||
local numsign = false
|
||||
|
||||
for _,v in pairs(rotkeys) do
|
||||
for _,v in ipairs(rotkeys) do
|
||||
if mcl2rotsigns[node.name] then
|
||||
node.name = mcl2rotsigns[node.name]
|
||||
node.param2 = nidp2_degrotate[v][node.param2 + 1]
|
||||
|
@ -103,7 +103,8 @@ function mcl_signs.upgrade_sign_rot(pos, node)
|
|||
end
|
||||
end
|
||||
end
|
||||
core.swap_node(pos,node)
|
||||
|
||||
core.swap_node(pos, node)
|
||||
mcl_signs.upgrade_sign_meta(pos)
|
||||
mcl_signs.update_sign(pos)
|
||||
end
|
||||
|
|
|
@ -5,12 +5,15 @@ local modname = core.get_current_modname()
|
|||
local S = core.get_translator(modname)
|
||||
local modpath = core.get_modpath(modname)
|
||||
|
||||
-- UTF-8 library from Modlib
|
||||
local utf8 = dofile(modpath .. DIR_DELIM .. "utf8.lua")
|
||||
|
||||
-- Character map
|
||||
local charmap = {}
|
||||
for line in io.lines(modpath .. "/characters.tsv") do
|
||||
local split = line:split("\t")
|
||||
if #split > 0 then
|
||||
local char, img, _ = split[1], split[2], split[3]
|
||||
local char, img, _ = split[1], split[2], split[3] -- 3rd is ignored, reserved for width
|
||||
charmap[char] = img
|
||||
end
|
||||
end
|
||||
|
@ -22,6 +25,6 @@ local files = {
|
|||
"compat"
|
||||
}
|
||||
|
||||
for _, file in ipairs(files) do
|
||||
loadfile(modpath .. DIR_DELIM .. file .. ".lua")(S, charmap)
|
||||
for _, file in pairs(files) do
|
||||
loadfile(modpath .. DIR_DELIM .. file .. ".lua")(S, charmap, utf8)
|
||||
end
|
||||
|
|
102
mods/ITEMS/mcl_signs/utf8.lua
Normal file
102
mods/ITEMS/mcl_signs/utf8.lua
Normal file
|
@ -0,0 +1,102 @@
|
|||
local assert, error, select, string_char, table_concat
|
||||
= assert, error, select, string.char, table.concat
|
||||
|
||||
local utf8 = {}
|
||||
|
||||
-- Overly permissive pattern that greedily matches a single UTF-8 codepoint
|
||||
utf8.charpattern = "[%z-\127\194-\253][\128-\191]*"
|
||||
|
||||
function utf8.is_valid_codepoint(codepoint)
|
||||
-- Must be in bounds & must not be a surrogate
|
||||
return codepoint <= 0x10FFFF and (codepoint < 0xD800 or codepoint > 0xDFFF)
|
||||
end
|
||||
|
||||
local function utf8_bytes(codepoint)
|
||||
if codepoint <= 0x007F then
|
||||
return codepoint
|
||||
end if codepoint <= 0x7FF then
|
||||
local payload_2 = codepoint % 0x40
|
||||
codepoint = (codepoint - payload_2) / 0x40
|
||||
return 0xC0 + codepoint, 0x80 + payload_2
|
||||
end if codepoint <= 0xFFFF then
|
||||
local payload_3 = codepoint % 0x40
|
||||
codepoint = (codepoint - payload_3) / 0x40
|
||||
local payload_2 = codepoint % 0x40
|
||||
codepoint = (codepoint - payload_2) / 0x40
|
||||
return 0xE0 + codepoint, 0x80 + payload_2, 0x80 + payload_3
|
||||
end if codepoint <= 0x10FFFF then
|
||||
local payload_4 = codepoint % 0x40
|
||||
codepoint = (codepoint - payload_4) / 0x40
|
||||
local payload_3 = codepoint % 0x40
|
||||
codepoint = (codepoint - payload_3) / 0x40
|
||||
local payload_2 = codepoint % 0x40
|
||||
codepoint = (codepoint - payload_2) / 0x40
|
||||
return 0xF0 + codepoint, 0x80 + payload_2, 0x80 + payload_3, 0x80 + payload_4
|
||||
end error"codepoint out of range"
|
||||
end
|
||||
|
||||
function utf8.char(...)
|
||||
local n_args = select("#", ...)
|
||||
if n_args == 0 then
|
||||
return
|
||||
end if n_args == 1 then
|
||||
return string_char(utf8_bytes(...))
|
||||
end
|
||||
local chars = {}
|
||||
for i = 1, n_args do
|
||||
chars[i] = string_char(utf8_bytes(select(i, ...)))
|
||||
end
|
||||
return table_concat(chars)
|
||||
end
|
||||
|
||||
local function utf8_next_codepoint(str, i)
|
||||
local first_byte = str:byte(i)
|
||||
if first_byte < 0x80 then
|
||||
return i + 1, first_byte
|
||||
end
|
||||
|
||||
local len, head_bits
|
||||
if first_byte >= 0xC0 and first_byte <= 0xDF then -- 110_00000 to 110_11111
|
||||
len, head_bits = 2, first_byte % 0x20 -- last 5 bits
|
||||
elseif first_byte >= 0xE0 and first_byte <= 0xEF then -- 1110_0000 to 1110_1111
|
||||
len, head_bits = 3, first_byte % 0x10 -- last 4 bits
|
||||
elseif first_byte >= 0xF0 and first_byte <= 0xF7 then -- 11110_000 to 11110_111
|
||||
len, head_bits = 4, first_byte % 0x8 -- last 3 bits
|
||||
else error"invalid UTF-8" end
|
||||
|
||||
local codepoint = 0
|
||||
local pow = 1
|
||||
for j = i + len - 1, i + 1, -1 do
|
||||
local byte = assert(str:byte(j), "invalid UTF-8")
|
||||
local val_bits = byte % 0x40 -- extract last 6 bits xxxxxx from 10xxxxxx
|
||||
assert(byte - val_bits == 0x80) -- assert that first two bits are 10
|
||||
codepoint = codepoint + val_bits * pow
|
||||
pow = pow * 0x40
|
||||
end
|
||||
return i + len, codepoint + head_bits * pow
|
||||
end
|
||||
|
||||
function utf8.codepoint(str, i, j)
|
||||
i, j = i or 1, j or #str
|
||||
if i > j then return end
|
||||
local codepoint
|
||||
i, codepoint = utf8_next_codepoint(str, i)
|
||||
assert(i - j <= 1, "invalid UTF-8")
|
||||
return codepoint, utf8.codepoint(str, i)
|
||||
end
|
||||
|
||||
-- Iterator to loop over the UTF-8 characters as `index, codepoint`
|
||||
function utf8.codes(text, i)
|
||||
i = i or 1
|
||||
return function()
|
||||
if i > #text then
|
||||
return
|
||||
end
|
||||
local prev_index = i
|
||||
local codepoint
|
||||
i, codepoint = utf8_next_codepoint(text, i)
|
||||
return prev_index, codepoint
|
||||
end
|
||||
end
|
||||
|
||||
return utf8
|
|
@ -337,9 +337,6 @@ mcl_enable_hamburger (Enable Hamburger) bool true
|
|||
# Starting Inventory contents (given directly to the new player)
|
||||
give_starting_inv (Player Starter Pack) bool false
|
||||
|
||||
# Use word wrapping for signs to break lines between words rather than within them
|
||||
vl_signs_word_wrap (Word wrap sign text) bool true
|
||||
|
||||
[Debugging]
|
||||
# If enabled, this will show the itemstring of an item in the description.
|
||||
mcl_item_id_debug (Item ID Debug) bool false
|
||||
|
|
Loading…
Add table
Reference in a new issue