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); }