Font escape sequence for nametags

This commit is contained in:
cx384 2024-08-14 00:52:13 +02:00
parent c49ff76955
commit bb712686ef
8 changed files with 355 additions and 9 deletions

View File

@ -548,6 +548,10 @@ function core.get_background_escape_sequence(color)
return ESCAPE_CHAR .. "(b@" .. color .. ")"
end
function core.get_font_escape_sequence(font)
return ESCAPE_CHAR .. "(f@" .. font .. ")"
end
function core.colorize(color, message)
local lines = tostring(message):split("\n", true)
local color_code = core.get_color_escape_sequence(color)
@ -572,6 +576,10 @@ function core.strip_colors(str)
return (str:gsub(ESCAPE_CHAR .. "%([bc]@[^)]+%)", ""))
end
function core.strip_font(str)
return (str:gsub(ESCAPE_CHAR .. "%(f@[^)]+%)", ""))
end
local function translate(textdomain, str, num, ...)
local start_seq
if textdomain == "" and num == "" then

View File

@ -3823,6 +3823,13 @@ The following functions provide escape sequences:
* Removes background colors added by `get_background_escape_sequence`.
* `core.strip_colors(str)`
* Removes all color escape sequences.
* `minetest.get_font_escape_sequence(font_modifier)`
* Can currently only be used for nametags.
* `font_modifier` can be "mono", "unmono", "bold", "unbold", "italic" or "unitalic"
* The escape sequence modifies the font of the subsequent string.
* You can concatenate them, to for example get bold italic text.
* `minetest.strip_font(str)`
* Removes all font escape sequences.
@ -5666,6 +5673,8 @@ Utilities
biome_weights = true,
-- Particles can specify a "clip" blend mode (5.11.0)
particle_blend_clip = true,
-- Nametags support color escaped sequences and new font escape sequences. (5.10.0)
nametag_font_escape_sequences = true,
}
```
@ -9274,6 +9283,8 @@ Player properties need to be saved manually.
nametag_color = <ColorSpec>,
-- Sets text color of nametag
-- If the text contains color escape sequences, only the part before the first
-- color escape sequences will have the `nametag_color`.
nametag_bgcolor = <ColorSpec>,
-- Sets background color of nametag

View File

@ -162,3 +162,89 @@ core.register_entity("testentities:nametag", {
return core.serialize({ color = self.color, bgcolor = self.bgcolor })
end,
})
-- Enriched nametag strings
do
local bold = minetest.get_font_escape_sequence("bold")
local unbold = minetest.get_font_escape_sequence("unbold")
local italic = minetest.get_font_escape_sequence("italic")
local unitalic = minetest.get_font_escape_sequence("unitalic")
local mono = minetest.get_font_escape_sequence("mono")
local unmono = minetest.get_font_escape_sequence("unmono")
local all_off = unbold..unitalic..unmono
local nametags = {
"normal "..bold.."bold "..unbold..italic.."italic "..unitalic..mono.."mono",
bold..italic.."bold+italic"..unitalic..mono.." bold+mono"..unbold..italic.." mono+italic",
bold..italic..mono.."bold+mono+italic",
bold.."bold colored"..minetest.colorize("red", " red").." text",
"colored"..minetest.colorize("red", " red"..bold.." bold"..unbold).." text",
minetest.strip_font(bold.."striped"..italic.." font fomats"),
minetest.get_background_escape_sequence("green").."background is not supported",
"colored"..minetest.colorize("gray", " gray"..bold.." bold multi\nline").." text",
bold..unbold.."\nempty lines"..italic.." before and after\n"..unitalic,
"m"..mono.."m"..unmono.."m"..mono.."m"..unmono.."m"..mono.."m"..unmono.."m"..mono.."m",
"i"..italic.."i"..unitalic.."i"..italic.."i"..unitalic.."i"..italic.."i"..unitalic.."i"..
italic.."i",
"b"..bold.."b"..unbold.."b"..bold.."b"..unbold.."b"..bold.."b"..unbold.."b"..bold.."b",
}
-- Remove this if you want to look them separately
local all_lines = "Enriched nametag text:"
for _, nametag in ipairs(nametags) do
all_lines = all_lines.."\n"..all_off..nametag
end
nametags = {all_lines}
local nametag_iterator = 0
minetest.register_entity("testentities:nametag_enriched_text", {
initial_properties = {
visual = "sprite",
textures = { "testentities_sprite.png" },
},
on_activate = function(self, staticdata)
local color
local bgcolor
if (nametag_iterator/#nametags) % 2 < 1 then
color = {r = 0, g = 0, b = 255}
bgcolor = {r = 200, g = 200, b = 10,a = 150}
end
local nametag = nametags[(nametag_iterator % #nametags)+1]
nametag_iterator = nametag_iterator + 1
self.object:set_properties({
nametag = nametag,
nametag_color = color,
nametag_bgcolor = bgcolor,
})
end,
})
-- Who doesn't like ascii art
minetest.register_entity("testentities:nametag_ascii_art", {
initial_properties = {
visual = "sprite",
textures = { "testentities_sprite.png" },
},
on_activate = function(self, staticdata)
local nametag = mono..
" __. __. __. \n" ..
" _____ |__| ____ _____ / |_ _____ _____ / |_ \n" ..
" / \\| |/ \\ / __ \\ _\\/ __ \\/ __> _\\\n" ..
"| Y Y \\ | | \\ ___/| | | ___/\\___ \\| | \n" ..
"|__|_| / |___| /\\______> | \\______>_____/| | \n" ..
" \\/ \\/ \\/ \\/ \\/ "
self.object:set_properties({
nametag = nametag,
nametag_color = "green",
nametag_bgcolor = "black",
})
end,
})
end

View File

@ -49,6 +49,7 @@ set(client_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/content_mapblock.cpp
${CMAKE_CURRENT_SOURCE_DIR}/filecache.cpp
${CMAKE_CURRENT_SOURCE_DIR}/fontengine.cpp
${CMAKE_CURRENT_SOURCE_DIR}/font_enriched_string_composite.cpp
${CMAKE_CURRENT_SOURCE_DIR}/game.cpp
${CMAKE_CURRENT_SOURCE_DIR}/gameui.cpp
${CMAKE_CURRENT_SOURCE_DIR}/game_formspec.cpp

View File

@ -25,6 +25,7 @@
#include <SViewFrustum.h>
#include <IGUIFont.h>
#include <IVideoDriver.h>
#include "font_enriched_string_composite.h"
#define CAMERA_OFFSET_STEP 200
#define WIELDMESH_OFFSET_X 55.0f
@ -632,7 +633,6 @@ void Camera::drawNametags()
core::matrix4 trans = m_cameranode->getProjectionMatrix();
trans *= m_cameranode->getViewMatrix();
gui::IGUIFont *font = g_fontengine->getFont();
video::IVideoDriver *driver = RenderingEngine::get_video_driver();
v2u32 screensize = driver->getScreenSize();
@ -643,10 +643,10 @@ void Camera::drawNametags()
f32 transformed_pos[4] = { pos.X, pos.Y, pos.Z, 1.0f };
trans.multiplyWith1x4Matrix(transformed_pos);
if (transformed_pos[3] > 0) {
std::wstring nametag_colorless =
unescape_translate(utf8_to_wide(nametag->text));
core::dimension2d<u32> textsize = font->getDimension(
nametag_colorless.c_str());
FontEnrichedStringComposite fesc(translate_string(utf8_to_wide(nametag->text)).c_str(),
nametag->textcolor);
core::dimension2d<u32> textsize = fesc.getDimension();
f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f :
core::reciprocal(transformed_pos[3]);
v2s32 screen_pos;
@ -661,10 +661,7 @@ void Camera::drawNametags()
core::rect<s32> bg_size(-2, 0, textsize.Width + 2, textsize.Height);
driver->draw2DRectangle(bgcolor, bg_size + screen_pos);
}
font->draw(
translate_string(utf8_to_wide(nametag->text)).c_str(),
size + screen_pos, nametag->textcolor);
fesc.draw(size + screen_pos);
}
}
}

View File

@ -0,0 +1,166 @@
/*
Minetest
Copyright (C) 2024 cx384
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "font_enriched_string_composite.h"
#include "irrlicht_changes/CGUITTFont.h"
#include "util/string.h"
#include <utility>
static bool parseFontModifier(std::string_view s, FontModifier &modifier) {
if (s == "mono")
modifier = FontModifier::Mono;
else if (s == "unmono")
modifier = FontModifier::Unmono;
else if (s == "bold")
modifier = FontModifier::Bold;
else if (s == "unbold")
modifier = FontModifier::Unbold;
else if (s == "italic")
modifier = FontModifier::Italic;
else if (s == "unitalic")
modifier = FontModifier::Unitalic;
else
return false;
return true;
};
FontEnrichedStringComposite::FontEnrichedStringComposite(const std::wstring &s,
const video::SColor &initial_color, const FontSpec &initial_font) {
FontSpec font = initial_font;
video::SColor color = initial_color;
// Handle font escape sequence like in EnrichedString and translate_string
Line line;
size_t fragmen_start = 0;
size_t i = 0;
while (i < s.length()) {
if (s[i] == L'\n') {
// Split lines
EnrichedString fragment(std::wstring(s, fragmen_start, i-fragmen_start), color);
line.push_back(std::make_pair(fragment, font));
auto colors = fragment.getColors();
if (!colors.empty())
color = colors.back();
if (!line.empty()) {
m_lines.emplace_back(std::move(line));
line = Line();
}
i++;
fragmen_start = i;
continue;
} else if (s[i] != L'\x1b') {
i++;
continue;
}
i++;
size_t start_index = i;
size_t length;
if (i == s.length())
break;
if (s[i] == L'(') {
++i;
++start_index;
while (i < s.length() && s[i] != L')') {
if (s[i] == L'\\') {
++i;
}
++i;
}
length = i - start_index;
++i;
} else {
++i;
length = 1;
}
std::wstring escape_sequence(s, start_index, length);
std::vector<std::wstring> parts = split(escape_sequence, L'@');
if (parts[0] == L"f") {
if (parts.size() < 2) {
continue;
}
FontModifier modifier;
if (parseFontModifier(wide_to_utf8(parts[1]), modifier)) {
EnrichedString fragment(std::wstring(s, fragmen_start, start_index-fragmen_start), color);
line.push_back(std::make_pair(fragment, font));
fragmen_start = start_index + length + 1;
font.applyFontModifier(modifier);
auto colors = fragment.getColors();
if (!colors.empty())
color = colors.back();
}
}
}
if (fragmen_start < s.length()) {
EnrichedString fragment(std::wstring(s, fragmen_start), color);
line.push_back(std::make_pair(fragment, font));
}
if (!line.empty())
m_lines.push_back(line);
};
void FontEnrichedStringComposite::draw(core::rect<s32> position) const {
u32 start_pos_x = position.UpperLeftCorner.X;
for (auto line : m_lines) {
position.UpperLeftCorner.X = start_pos_x;
u32 max_h = 0;
for (auto [es, spec] : line) {
gui::IGUIFont *font = g_fontengine->getFont(spec);
gui::CGUITTFont *ttfont = dynamic_cast<gui::CGUITTFont*>(font);
if (ttfont) { // Don't draw other fonts
ttfont->draw(es, position);
auto frag_dim = ttfont->getDimension(es.c_str());
position.UpperLeftCorner.X += frag_dim.Width;
if (frag_dim.Height > max_h)
max_h = frag_dim.Height;
}
}
position.UpperLeftCorner.Y += max_h;
}
};
core::dimension2d<u32> FontEnrichedStringComposite::getDimension() const {
core::dimension2d<u32> dim(0, 0);
for (auto line : m_lines) {
u32 max_h = 0;
u32 sum_w = 0;
for (auto [es, spec] : line) {
gui::IGUIFont *font = g_fontengine->getFont(spec);
gui::CGUITTFont *ttfont = dynamic_cast<gui::CGUITTFont*>(font);
if (ttfont) {
auto frag_dim = ttfont->getDimension(es.c_str());
sum_w += frag_dim.Width;
if (frag_dim.Height > max_h)
max_h = frag_dim.Height;
}
}
dim.Height += max_h;
if (dim.Width < sum_w)
dim.Width = sum_w;
}
return dim;
};

View File

@ -0,0 +1,41 @@
/*
Minetest
Copyright (C) 2024 cx384
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "util/enriched_string.h"
#include "fontengine.h"
#include "rect.h"
// Note this is client code, because of the draw function.
// A text consisting of multiple enriched strings which are drawn with different fonts
struct FontEnrichedStringComposite {
FontEnrichedStringComposite(const std::wstring &s,
const video::SColor &initial_color = video::SColor(255, 255, 255, 255),
const FontSpec &initial_font = FontSpec(FONT_SIZE_UNSPECIFIED, FM_Standard, false, false));
void draw(core::rect<s32> position) const;
core::dimension2d<u32> getDimension() const;
private:
using Line = std::vector<std::pair<EnrichedString, FontSpec>>;
std::vector<Line> m_lines;
};

View File

@ -19,6 +19,17 @@ namespace irr {
#define FONT_SIZE_UNSPECIFIED 0xFFFFFFFF
using namespace irr;
enum class FontModifier {
Mono,
Unmono,
Bold,
Unbold,
Italic,
Unitalic,
};
enum FontMode : u8 {
FM_Standard = 0,
FM_Mono,
@ -39,6 +50,31 @@ struct FontSpec {
return (mode << 2) | (static_cast<u8>(bold) << 1) | static_cast<u8>(italic);
}
void applyFontModifier(FontModifier modifier) {
switch(modifier) {
case FontModifier::Mono :
mode = FM_Mono;
break;
case FontModifier::Unmono :
mode = FM_Standard;
break;
case FontModifier::Bold :
bold = true;
break;
case FontModifier::Unbold :
bold = false;
break;
case FontModifier::Italic :
italic = true;
break;
case FontModifier::Unitalic :
italic = false;
break;
default:
break;
}
}
unsigned int size;
FontMode mode;
bool bold;