This commit is contained in:
Yiheng Wu 2025-01-01 17:15:33 +08:00 committed by GitHub
commit 8e5c307d6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 241 additions and 25 deletions

View File

@ -857,6 +857,41 @@ embedding a whole image, this may vary by use case.
*See notes: `TEXMOD_UPSCALE`*
#### `[text:<encoded text>:<w>x<h>:<x>,<y>`
* `<w>`: width
* `<h>`: height
* `<x>`: x position
* `<y>`: y position
* `<encoded text>`: a formatted text encoded with base64
Creates a texture containing rendered texture, optionally with an `<x>,<y>`
position and `<w>x<h>` size.
To avoid potential conflict between text and grammar of texture modifiers,
text must be encoded with base64 encoding. Global properties of text
is detected from global styles of hypertext, such as alignment and background
color. Most of the tags in hypertext is supported, except for item image and
interaction feature `<action>`.
The optional `<x>,<y>` position is used if provided, no matter whether the
`[text` is being overlaid onto another texture with '^' or not.
When `[text` is overlaid onto another texture, it will not upscale or change
the resolution of the texture, the base texture will determine the output
resolution. In such case, `<w>x<h>` could be optional, and size of base
texture is considered as the size of typesetted text.
In contrast, if `[text` is not overlaid onto another texture, `<w>x<h>` size
must be provided, and size of output texture is determined from provided size.
Examples:
[text:SGVsbG8gd29ybGQ=:64x64
texture.png^[text:SGVsbG8gd29ybGQ=
texture.png^[text:SGVsbG8gd29ybGQ=:50x50:4,4
Hardware coloring
-----------------

View File

@ -367,3 +367,39 @@ core.register_node("testnodes:tga_type10_32bpp_tb", {
use_texture_alpha = "blend",
groups = { dig_immediate = 2 },
})
minetest.register_node("testnodes:glyph_font", {
description = S("Combine Test Node"),
tiles = {{
name = "testnodes_generated_mb.png"..
"^[text:" .. minetest.encode_base64([[A hypertext element
<bigger>Normal test</bigger>
This is a normal text. special characters ^[:,
<bigger><mono>style</mono> test</bigger>
<style color="#FFFF00">Yellow text.</style> <style color='#FF0000'>Red text.</style>
<style size="24">Size 24.</style> <style size=16>Size 16</style>. <style size=12>Size 12.</style>
<style font="normal">Normal font.</style> <style font=mono>Mono font.</style>]]),
align_style = "world",
scale = 8,
},
{
name = "testnodes_generated_mb.png"..
"^[text:" .. minetest.encode_base64([[<global background=#80AAAAAA margin=20 valign=bottom halign=right color=pink hovercolor=purple size=12 font=mono>
This is a test of the global tag. The parameters are:
background=#80AAAAAA margin=20 valign=bottom halign=right color=pink hovercolor=purple size=12 font=mono
<action name=global>action</action>]]) .. ":500x240",
align_style = "world",
scale = 8,
},
{
name = "testnodes_generated_mb.png"..
"^[text:" .. minetest.encode_base64([[<global background=#80AAAAAA margin=20 valign=middle halign=center color=pink hovercolor=purple size=12 font=mono>
<bigger>Custom tag test</bigger>
<tag name="t_multi" color=green font=mono size=24>
<t_multi>color=green font=mono size=24</t_multi>]]) .. ":500x260:4,3",
align_style = "world",
scale = 8,
}},
groups = { dig_immediate = 2 },
})

View File

@ -14,6 +14,10 @@
#include "util/base64.h"
#include "util/numeric.h"
#include "util/strfnd.h"
#include "client/fontengine.h"
#include "irrlicht_changes/CGUITTFont.h"
#include "gui/guiHyperText.h"
#include <string>
////////////////////////////////
@ -1762,6 +1766,120 @@ bool ImageSource::generateImagePart(std::string_view part_of_name,
apply_brightness_contrast(baseimg, v2u32(0, 0),
baseimg->getDimension(), brightness, contrast);
}
/*
[text:string:WxH:x,y
[text:string:WxH
[text:string
Render a character at given position, string is encoded by base64
coordinate is optional, but colon should be kept
*/
else if (str_starts_with(part_of_name, "[text:")) {
const auto colonSeparatedPartsOfName = str_split(part_of_name, ':');
auto numPartsOfName = colonSeparatedPartsOfName.size();
if (numPartsOfName < 2) {
errorstream << "generateImagePart(): "
<< "text is missing in [text" << std::endl;
return false;
}
std::string textdef;
{
auto blob = colonSeparatedPartsOfName[1];
if (!base64_is_valid(blob)) {
errorstream << "generateImagePart(): "
<< "malformed base64 in [text" << std::endl;
return false;
}
textdef = base64_decode(blob);
}
core::stringw textdefW = utf8_to_stringw(textdef);
core::dimension2du size(0,0);
if (numPartsOfName >= 3) {
auto sizeStr = colonSeparatedPartsOfName[2];
std::vector<std::string_view> sizeStringArr = str_split(sizeStr, 'x');
if (sizeStringArr.size() >= 2) {
if (is_number(sizeStringArr[0])) {
size.Width = mystoi(std::string(sizeStringArr[0]));
}
if (is_number(sizeStringArr[1])) {
size.Height = mystoi(std::string(sizeStringArr[1]));
}
}
infostream << "size string: " << sizeStringArr.size() <<std::endl;
} else if (baseimg) {
size = baseimg->getDimension();
} else {
errorstream << "generateImagePart(): width and height is not specified and cannot be inferred from base image" << std::endl;
return false;
}
core::position2di pos(0,0);
if (numPartsOfName >= 4)
{
auto posStr = colonSeparatedPartsOfName[3];
std::vector<std::string_view> posStringArr = str_split(posStr, ',');
if (posStringArr.size() >= 2) {
if (is_number(posStringArr[0])) {
pos.X = mystoi(std::string(posStringArr[0]));
}
if (is_number(posStringArr[1])) {
pos.Y = mystoi(std::string(posStringArr[1]));
}
}
}
video::ITexture* referredTexture = nullptr;
TextDrawer drawer(textdefW.c_str(), [&, driver, baseimg](auto texturePath){
if (referredTexture) {
driver->removeTexture(referredTexture);
}
video::IImage *image = m_sourcecache.getOrLoad(texturePath);
referredTexture = driver->addTexture("text_renderer_base__", baseimg);
return referredTexture;
});
if (referredTexture) {
driver->removeTexture(referredTexture);
}
core::dimension2du canvasSize = size;
if (baseimg) {
canvasSize = baseimg->getDimension();
}
core::recti drawingArea(pos, size);
video::ECOLOR_FORMAT colorFormat =
baseimg ? baseimg->getColorFormat() : video::ECF_A8R8G8B8;
drawer.place(drawingArea);
auto texture =
driver->addRenderTargetTexture(canvasSize, "text_renderer__", colorFormat);
if (driver->setRenderTarget(texture, video::ECBF_ALL, video::SColor(0,0,0,0))) {
if (baseimg) {
auto baseTexture = driver->addTexture("text_renderer_base__", baseimg);
driver->draw2DImage(baseTexture, core::position2di(0,0));
driver->removeTexture(baseTexture);
}
drawer.draw(drawingArea, pos, driver, nullptr);
driver->setRenderTarget(NULL);
void* lockedData = texture->lock();
if (lockedData) {
if (baseimg) {
baseimg->drop();
}
baseimg = driver->createImageFromData(colorFormat, canvasSize, lockedData, false);
texture->unlock();
} else {
errorstream << "generateImagePart(): no data inside texture, internal error" << std::endl;
return false;
}
} else {
errorstream << "generateImagePart(): fails to set render target, "
"can this driver renders to target:" <<
driver->queryFeature(video::EVDF_RENDER_TO_TARGET) << std::endl;
return false;
}
driver->removeTexture(texture);
}
else
{
errorstream << "generateImagePart(): Invalid "

View File

@ -617,9 +617,9 @@ u32 ParsedText::parseTag(const wchar_t *text, u32 cursor)
// -----------------------------------------------------------------------------
// Text Drawer
TextDrawer::TextDrawer(const wchar_t *text, Client *client,
gui::IGUIEnvironment *environment, ISimpleTextureSource *tsrc) :
m_text(text), m_client(client), m_tsrc(tsrc), m_guienv(environment)
TextDrawer::TextDrawer(const wchar_t *text,
const std::function<video::ITexture*(const std::string&)>& texture_getter) :
m_text(text), m_texture_getter(texture_getter)
{
// Size all elements
for (auto &p : m_text.m_paragraphs) {
@ -649,9 +649,7 @@ TextDrawer::TextDrawer(const wchar_t *text, Client *client,
core::dimension2d<u32> dim(80, 80);
if (e.type == ParsedText::ELEMENT_IMAGE) {
video::ITexture *texture =
m_tsrc->
getTexture(stringw_to_utf8(e.text));
video::ITexture* texture = m_texture_getter(stringw_to_utf8(e.text));
if (texture)
dim = texture->getOriginalSize();
}
@ -930,9 +928,8 @@ void TextDrawer::place(const core::rect<s32> &dest_rect)
// Draw text in a rectangle with a given offset. Items are actually placed in
// relative (to upper left corner) coordinates.
void TextDrawer::draw(const core::rect<s32> &clip_rect,
const core::position2d<s32> &dest_offset)
const core::position2d<s32> dest_offset, irr::video::IVideoDriver *driver, Client *client)
{
irr::video::IVideoDriver *driver = m_guienv->getVideoDriver();
core::position2d<s32> offset = dest_offset;
offset.Y += m_voffset;
@ -976,10 +973,10 @@ void TextDrawer::draw(const core::rect<s32> &clip_rect,
case ParsedText::ELEMENT_IMAGE: {
video::ITexture *texture =
m_tsrc->getTexture(
m_texture_getter(
stringw_to_utf8(el.text));
if (texture != 0)
m_guienv->getVideoDriver()->draw2DImage(
driver->draw2DImage(
texture, rect,
irr::core::rect<s32>(
core::position2d<s32>(0, 0),
@ -988,13 +985,13 @@ void TextDrawer::draw(const core::rect<s32> &clip_rect,
} break;
case ParsedText::ELEMENT_ITEM: {
if (m_client) {
IItemDefManager *idef = m_client->idef();
if (client) {
IItemDefManager *idef = client->idef();
ItemStack item;
item.deSerialize(stringw_to_utf8(el.text), idef);
drawItemStack(m_guienv->getVideoDriver(),
g_fontengine->getFont(), item, rect, &clip_rect, m_client,
drawItemStack(driver,
g_fontengine->getFont(), item, rect, &clip_rect, client,
IT_ROT_OTHER, el.angle, el.rotation);
}
} break;
@ -1011,8 +1008,10 @@ GUIHyperText::GUIHyperText(const wchar_t *text, IGUIEnvironment *environment,
IGUIElement *parent, s32 id, const core::rect<s32> &rectangle,
Client *client, ISimpleTextureSource *tsrc) :
IGUIElement(EGUIET_ELEMENT, environment, parent, id, rectangle),
m_tsrc(tsrc), m_vscrollbar(nullptr),
m_drawer(text, client, environment, tsrc), m_text_scrollpos(0, 0)
m_vscrollbar(nullptr), m_client(client),
m_drawer(text, [=](const std::string& s){
return tsrc->getTexture(s);
}), m_text_scrollpos(0, 0)
{
IGUISkin *skin = 0;
@ -1095,7 +1094,7 @@ bool GUIHyperText::OnEvent(const SEvent &event)
m_vscrollbar->setPosInterpolated(m_vscrollbar->getTargetPos() -
event.MouseInput.Wheel * m_vscrollbar->getSmallStep());
m_text_scrollpos.Y = -m_vscrollbar->getPos();
m_drawer.draw(m_display_text_rect, m_text_scrollpos);
m_drawer.draw(m_display_text_rect, m_text_scrollpos, Environment->getVideoDriver(), m_client);
checkHover(event.MouseInput.X, event.MouseInput.Y);
return true;
@ -1171,7 +1170,7 @@ void GUIHyperText::draw()
m_vscrollbar->setVisible(false);
}
m_drawer.draw(AbsoluteClippingRect,
m_display_text_rect.UpperLeftCorner + m_text_scrollpos);
m_display_text_rect.UpperLeftCorner + m_text_scrollpos, Environment->getVideoDriver(), m_client);
// draw children
IGUIElement::draw();

View File

@ -155,13 +155,13 @@ protected:
class TextDrawer
{
public:
TextDrawer(const wchar_t *text, Client *client, gui::IGUIEnvironment *environment,
ISimpleTextureSource *tsrc);
TextDrawer(const wchar_t *text,
const std::function<video::ITexture*(const std::string&)>& texture_getter);
void place(const core::rect<s32> &dest_rect);
inline s32 getHeight() { return m_height; };
void draw(const core::rect<s32> &clip_rect,
const core::position2d<s32> &dest_offset);
const core::position2d<s32> dest_offset, video::IVideoDriver* driver, Client *client);
ParsedText::Element *getElementAt(core::position2d<s32> pos);
ParsedText::Tag *m_hovertag;
@ -173,9 +173,7 @@ protected:
};
ParsedText m_text;
Client *m_client; ///< null in the mainmenu
ISimpleTextureSource *m_tsrc;
gui::IGUIEnvironment *m_guienv;
std::function<video::ITexture*(const std::string&)> m_texture_getter;
s32 m_height;
s32 m_voffset;
std::vector<RectWithMargin> m_floating;
@ -202,7 +200,7 @@ public:
protected:
// GUI members
ISimpleTextureSource *m_tsrc;
Client *m_client;
GUIScrollBar *m_vscrollbar;
TextDrawer m_drawer;

View File

@ -300,6 +300,36 @@ inline std::vector<std::basic_string<T> > str_split(
return parts;
}
/**
* Splits a string_view into its component parts separated by the character
* \p delimiter.
*
* @return An std::vector<std::basic_string<T> > of the component parts
*/
template <typename T>
inline std::vector<std::basic_string_view<T> > str_split(
const std::basic_string_view<T> &strv,
T delimiter)
{
std::vector<std::basic_string_view<T>> output;
size_t first = 0;
while (first < strv.size())
{
const auto second = strv.find_first_of(delimiter, first);
if (first != second)
output.push_back(strv.substr(first, second - first));
if (second == std::string_view::npos)
break;
first = second + 1;
}
return output;
}
/**
* @param str