From e76e88771c0ac16aa028a48b32499f9ac3630dff Mon Sep 17 00:00:00 2001 From: jingkaimori Date: Thu, 19 Sep 2024 00:10:06 +0800 Subject: [PATCH 1/6] feat: add text tile modifier fix stability and format --- games/devtest/mods/testnodes/textures.lua | 17 ++++ src/client/imagesource.cpp | 97 +++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/games/devtest/mods/testnodes/textures.lua b/games/devtest/mods/testnodes/textures.lua index 0d4194753..4aeb25048 100644 --- a/games/devtest/mods/testnodes/textures.lua +++ b/games/devtest/mods/testnodes/textures.lua @@ -367,3 +367,20 @@ 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:regular font::::white".. + "^[text:large font(16pt):16::0,32:white".. + "^[text:larger font(32pt):32::0,64:white".. + "^[text:bold font(20pt):20:bold:0,96:white".. + "^[text:italic font(20pt):20:italic:0,128:white".. + "^[text:monospace font(20pt):20:mono:0,160:white".. + "^[text:red monospace font(20pt):20:mono:0,192:red", + align_style = "world", + scale = 8, + }}, + groups = { dig_immediate = 2 }, +}) diff --git a/src/client/imagesource.cpp b/src/client/imagesource.cpp index e2538c372..82201c214 100644 --- a/src/client/imagesource.cpp +++ b/src/client/imagesource.cpp @@ -14,6 +14,8 @@ #include "util/base64.h" #include "util/numeric.h" #include "util/strfnd.h" +#include "client/fontengine.h" +#include "irrlicht_changes/CGUITTFont.h" //////////////////////////////// @@ -1762,6 +1764,101 @@ bool ImageSource::generateImagePart(std::string_view part_of_name, apply_brightness_contrast(baseimg, v2u32(0, 0), baseimg->getDimension(), brightness, contrast); } + /* + [text:string:size:mono,bold,italic:x,y:color + Render a character at given position + size and font is optional, but colon should be kept + */ + else if (str_starts_with(part_of_name, "[text:")) { + Strfnd sf(part_of_name); + sf.next(":"); + std::string textdef = sf.next(":"); + core::stringw textdefW = utf8_to_stringw(textdef); + + unsigned int fontSize = FONT_SIZE_UNSPECIFIED; + std::string sizeStr = sf.next(":"); + if (is_number(sizeStr)) + { + fontSize = mystoi(sizeStr,0,200); + } + + FontMode mode = FM_Standard; + bool bold = false, italic = false; + std::string fontStyle = sf.next(":"); + std::vector styleWords = str_split(fontStyle, ','); + for(auto word : styleWords){ + if (word == "mono") + { + mode = FM_Mono; + }else if (word == "bold") + { + bold = true; + }else if(word == "italic"){ + italic = true; + } + } + FontSpec spec(fontSize, mode, bold, italic); + + core::position2di pos(0,0); + std::string positionStr = sf.next(":"); + std::vector positionStrs = str_split(positionStr, ','); + if (positionStrs.size() >= 2) + { + if (is_number(positionStrs[0])) { + pos.X = mystoi(positionStrs[0]); + } + if (is_number(positionStrs[1])) { + pos.Y = mystoi(positionStrs[1]); + } + } + + video::SColor color(0xff,0xff,0xff,0xff); + std::string colorStr = sf.next(""); + if (!parseColorString(colorStr,color,false)){ + return false; + }; + + irr::gui::CGUITTFont *font = + static_cast(g_fontengine->getFont(spec)); + core::dimension2d sizeText = font->getDimension(textdefW.c_str()); + + video::ECOLOR_FORMAT colorFormat = video::ECF_A8R8G8B8; + core::dimension2d size =sizeText; + if (baseimg) + { + colorFormat = baseimg->getColorFormat(); + size =baseimg->getDimension(); + } + std::string textureName("text_renderer__"); + textureName.append(part_of_name); + + auto texture = + driver->addRenderTargetTexture(size, textureName, 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::vector2di(0,0)); + driver->removeTexture(baseTexture); + } + font->draw(textdefW, core::recti(pos, sizeText), color); + driver->setRenderTarget(NULL); + void* lockedData = texture->lock(); + if (lockedData) { + if (baseimg) { + baseimg->drop(); + } + baseimg = driver->createImageFromData(colorFormat, size, lockedData, false); + texture->unlock(); + } else { + errorstream << "no data inside texture, internal error" << std::endl; + } + } else { + errorstream << "fails to set render target, " + "can this driver renders to target:" << + driver->queryFeature(video::EVDF_RENDER_TO_TARGET) << std::endl; + } + driver->removeTexture(texture); + } else { errorstream << "generateImagePart(): Invalid " From ab54dc19452099e8677950e2c896cc40665b9e0b Mon Sep 17 00:00:00 2001 From: jingkaimori Date: Fri, 20 Sep 2024 11:44:29 +0800 Subject: [PATCH 2/6] refactor on textdrawer --- src/gui/guiHyperText.cpp | 35 +++++++++++++++++------------------ src/gui/guiHyperText.h | 12 +++++------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/gui/guiHyperText.cpp b/src/gui/guiHyperText.cpp index 5446038b0..5d66db217 100644 --- a/src/gui/guiHyperText.cpp +++ b/src/gui/guiHyperText.cpp @@ -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& 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 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 &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 &clip_rect, - const core::position2d &dest_offset) + const core::position2d dest_offset, irr::video::IVideoDriver *driver, Client *client) { - irr::video::IVideoDriver *driver = m_guienv->getVideoDriver(); core::position2d offset = dest_offset; offset.Y += m_voffset; @@ -976,10 +973,10 @@ void TextDrawer::draw(const core::rect &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( core::position2d(0, 0), @@ -988,13 +985,13 @@ void TextDrawer::draw(const core::rect &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 &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(); diff --git a/src/gui/guiHyperText.h b/src/gui/guiHyperText.h index d11e80dd7..59cc87ac0 100644 --- a/src/gui/guiHyperText.h +++ b/src/gui/guiHyperText.h @@ -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& texture_getter); void place(const core::rect &dest_rect); inline s32 getHeight() { return m_height; }; void draw(const core::rect &clip_rect, - const core::position2d &dest_offset); + const core::position2d dest_offset, video::IVideoDriver* driver, Client *client); ParsedText::Element *getElementAt(core::position2d 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 m_texture_getter; s32 m_height; s32 m_voffset; std::vector m_floating; @@ -202,7 +200,7 @@ public: protected: // GUI members - ISimpleTextureSource *m_tsrc; + Client *m_client; GUIScrollBar *m_vscrollbar; TextDrawer m_drawer; From beb18cbba65fac6ecb34d3e7eeac5bfd74727e75 Mon Sep 17 00:00:00 2001 From: jingkaimori Date: Fri, 20 Sep 2024 14:21:09 +0800 Subject: [PATCH 3/6] extend str_split to string_view --- src/util/string.h | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/util/string.h b/src/util/string.h index 8b0848f8e..24fa3d293 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -300,6 +300,36 @@ inline std::vector > str_split( return parts; } +/** + * Splits a string_view into its component parts separated by the character + * \p delimiter. + * + * @return An std::vector > of the component parts + */ +template +inline std::vector > str_split( + const std::basic_string_view &strv, + T delimiter) +{ + std::vector> 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 From c27a40360a6204769cdb0e39121066d99c296ef9 Mon Sep 17 00:00:00 2001 From: jingkaimori Date: Fri, 18 Oct 2024 19:15:47 +0800 Subject: [PATCH 4/6] store and render formatted text with hypertext so that text style can differ in a flow --- games/devtest/mods/testnodes/textures.lua | 33 +++-- src/client/imagesource.cpp | 139 +++++++++++++--------- 2 files changed, 106 insertions(+), 66 deletions(-) diff --git a/games/devtest/mods/testnodes/textures.lua b/games/devtest/mods/testnodes/textures.lua index 4aeb25048..90a71af94 100644 --- a/games/devtest/mods/testnodes/textures.lua +++ b/games/devtest/mods/testnodes/textures.lua @@ -372,13 +372,32 @@ minetest.register_node("testnodes:glyph_font", { description = S("Combine Test Node"), tiles = {{ name = "testnodes_generated_mb.png".. - "^[text:regular font::::white".. - "^[text:large font(16pt):16::0,32:white".. - "^[text:larger font(32pt):32::0,64:white".. - "^[text:bold font(20pt):20:bold:0,96:white".. - "^[text:italic font(20pt):20:italic:0,128:white".. - "^[text:monospace font(20pt):20:mono:0,160:white".. - "^[text:red monospace font(20pt):20:mono:0,192:red", + "^[text:" .. minetest.encode_base64([[A hypertext element +Normal test +This is a normal text. 中文也应当可以渲染。special characters ^[:, + +style test + + . + ]]), + align_style = "world", + scale = 8, + }, + { + name = "testnodes_generated_mb.png".. + "^[text:" .. minetest.encode_base64([[ +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]]) .. ":500x240", + align_style = "world", + scale = 8, + }, + { + name = "testnodes_generated_mb.png".. + "^[text:" .. minetest.encode_base64([[ +Custom tag test + +color=green font=mono size=24]]) .. ":500x260:4,3", align_style = "world", scale = 8, }}, diff --git a/src/client/imagesource.cpp b/src/client/imagesource.cpp index 82201c214..2c3e65974 100644 --- a/src/client/imagesource.cpp +++ b/src/client/imagesource.cpp @@ -16,6 +16,8 @@ #include "util/strfnd.h" #include "client/fontengine.h" #include "irrlicht_changes/CGUITTFont.h" +#include "gui/guiHyperText.h" +#include //////////////////////////////// @@ -1765,97 +1767,116 @@ bool ImageSource::generateImagePart(std::string_view part_of_name, baseimg->getDimension(), brightness, contrast); } /* - [text:string:size:mono,bold,italic:x,y:color - Render a character at given position - size and font is optional, but colon should be kept + [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:")) { - Strfnd sf(part_of_name); - sf.next(":"); - std::string textdef = sf.next(":"); + 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); - unsigned int fontSize = FONT_SIZE_UNSPECIFIED; - std::string sizeStr = sf.next(":"); - if (is_number(sizeStr)) - { - fontSize = mystoi(sizeStr,0,200); - } - - FontMode mode = FM_Standard; - bool bold = false, italic = false; - std::string fontStyle = sf.next(":"); - std::vector styleWords = str_split(fontStyle, ','); - for(auto word : styleWords){ - if (word == "mono") - { - mode = FM_Mono; - }else if (word == "bold") - { - bold = true; - }else if(word == "italic"){ - italic = true; + core::dimension2du size(0,0); + if (numPartsOfName >= 3) { + auto sizeStr = colonSeparatedPartsOfName[2]; + std::vector 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() <getDimension(); + } else { + errorstream << "generateImagePart(): width and height is not specified and cannot be inferred from base image" << std::endl; + return false; } - FontSpec spec(fontSize, mode, bold, italic); core::position2di pos(0,0); - std::string positionStr = sf.next(":"); - std::vector positionStrs = str_split(positionStr, ','); - if (positionStrs.size() >= 2) + if (numPartsOfName >= 4) { - if (is_number(positionStrs[0])) { - pos.X = mystoi(positionStrs[0]); - } - if (is_number(positionStrs[1])) { - pos.Y = mystoi(positionStrs[1]); + auto posStr = colonSeparatedPartsOfName[3]; + std::vector 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::SColor color(0xff,0xff,0xff,0xff); - std::string colorStr = sf.next(""); - if (!parseColorString(colorStr,color,false)){ - return false; - }; - - irr::gui::CGUITTFont *font = - static_cast(g_fontengine->getFont(spec)); - core::dimension2d sizeText = font->getDimension(textdefW.c_str()); - - video::ECOLOR_FORMAT colorFormat = video::ECF_A8R8G8B8; - core::dimension2d size =sizeText; - if (baseimg) - { - colorFormat = baseimg->getColorFormat(); - size =baseimg->getDimension(); + 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); } - std::string textureName("text_renderer__"); - textureName.append(part_of_name); + + 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(size, textureName, colorFormat); + 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::vector2di(0,0)); + driver->draw2DImage(baseTexture, core::position2di(0,0)); driver->removeTexture(baseTexture); } - font->draw(textdefW, core::recti(pos, sizeText), color); + drawer.draw(drawingArea, pos, driver, nullptr); driver->setRenderTarget(NULL); void* lockedData = texture->lock(); if (lockedData) { if (baseimg) { baseimg->drop(); } - baseimg = driver->createImageFromData(colorFormat, size, lockedData, false); + baseimg = driver->createImageFromData(colorFormat, canvasSize, lockedData, false); texture->unlock(); } else { - errorstream << "no data inside texture, internal error" << std::endl; + errorstream << "generateImagePart(): no data inside texture, internal error" << std::endl; + return false; } } else { - errorstream << "fails to set render target, " + 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); } From 09c5778184aa0b5e4f0ed262c7cfdf6ae8032842 Mon Sep 17 00:00:00 2001 From: jingkaimori Date: Fri, 18 Oct 2024 19:44:38 +0800 Subject: [PATCH 5/6] add document for [text modifier --- doc/lua_api.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/doc/lua_api.md b/doc/lua_api.md index e45fa3c4e..97f47cf55 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -881,6 +881,41 @@ embedding a whole image, this may vary by use case. *See notes: `TEXMOD_UPSCALE`* +#### `[text::x:,` + +* ``: width +* ``: height +* ``: x position +* ``: y position +* ``: a formatted text encoded with base64 + +Creates a texture containing rendered texture, optionally with an `,` +position and `x` 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 ``. + +The optional `,` 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, `x` 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, `x` 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 ----------------- From cfaf3ab71bd17c2dce555ee71e7d93e4077ff4b9 Mon Sep 17 00:00:00 2001 From: jingkaimori Date: Wed, 22 Jan 2025 11:10:46 +0800 Subject: [PATCH 6/6] fix inconsistent indent space --- src/util/string.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/util/string.h b/src/util/string.h index 24fa3d293..d40e39b95 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -311,23 +311,23 @@ inline std::vector > str_split( const std::basic_string_view &strv, T delimiter) { - std::vector> output; - size_t first = 0; + std::vector> output; + size_t first = 0; - while (first < strv.size()) - { - const auto second = strv.find_first_of(delimiter, first); + 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 (first != second) + output.push_back(strv.substr(first, second - first)); - if (second == std::string_view::npos) - break; + if (second == std::string_view::npos) + break; - first = second + 1; - } + first = second + 1; + } - return output; + return output; }