mirror of
https://github.com/luanti-org/luanti.git
synced 2025-07-22 17:18:39 +00:00
store and render formatted text with hypertext
so that text style can differ in a flow
This commit is contained in:
parent
beb18cbba6
commit
c27a40360a
2 changed files with 106 additions and 66 deletions
|
@ -372,13 +372,32 @@ minetest.register_node("testnodes:glyph_font", {
|
||||||
description = S("Combine Test Node"),
|
description = S("Combine Test Node"),
|
||||||
tiles = {{
|
tiles = {{
|
||||||
name = "testnodes_generated_mb.png"..
|
name = "testnodes_generated_mb.png"..
|
||||||
"^[text:regular font::::white"..
|
"^[text:" .. minetest.encode_base64([[A hypertext element
|
||||||
"^[text:large font(16pt):16::0,32:white"..
|
<bigger>Normal test</bigger>
|
||||||
"^[text:larger font(32pt):32::0,64:white"..
|
This is a normal text. 中文也应当可以渲染。special characters ^[:,
|
||||||
"^[text:bold font(20pt):20:bold:0,96:white"..
|
|
||||||
"^[text:italic font(20pt):20:italic:0,128:white"..
|
<bigger><mono>style</mono> test</bigger>
|
||||||
"^[text:monospace font(20pt):20:mono:0,160:white"..
|
<style color="#FFFF00">Yellow text.</style> <style color='#FF0000'>Red text.</style>
|
||||||
"^[text:red monospace font(20pt):20:mono:0,192:red",
|
<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",
|
align_style = "world",
|
||||||
scale = 8,
|
scale = 8,
|
||||||
}},
|
}},
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
#include "util/strfnd.h"
|
#include "util/strfnd.h"
|
||||||
#include "client/fontengine.h"
|
#include "client/fontengine.h"
|
||||||
#include "irrlicht_changes/CGUITTFont.h"
|
#include "irrlicht_changes/CGUITTFont.h"
|
||||||
|
#include "gui/guiHyperText.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
|
@ -1765,97 +1767,116 @@ bool ImageSource::generateImagePart(std::string_view part_of_name,
|
||||||
baseimg->getDimension(), brightness, contrast);
|
baseimg->getDimension(), brightness, contrast);
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
[text:string:size:mono,bold,italic:x,y:color
|
[text:string:WxH:x,y
|
||||||
Render a character at given position
|
[text:string:WxH
|
||||||
size and font is optional, but colon should be kept
|
[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:")) {
|
else if (str_starts_with(part_of_name, "[text:")) {
|
||||||
Strfnd sf(part_of_name);
|
const auto colonSeparatedPartsOfName = str_split(part_of_name, ':');
|
||||||
sf.next(":");
|
auto numPartsOfName = colonSeparatedPartsOfName.size();
|
||||||
std::string textdef = sf.next(":");
|
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::stringw textdefW = utf8_to_stringw(textdef);
|
||||||
|
|
||||||
unsigned int fontSize = FONT_SIZE_UNSPECIFIED;
|
core::dimension2du size(0,0);
|
||||||
std::string sizeStr = sf.next(":");
|
if (numPartsOfName >= 3) {
|
||||||
if (is_number(sizeStr))
|
auto sizeStr = colonSeparatedPartsOfName[2];
|
||||||
{
|
std::vector<std::string_view> sizeStringArr = str_split(sizeStr, 'x');
|
||||||
fontSize = mystoi(sizeStr,0,200);
|
if (sizeStringArr.size() >= 2) {
|
||||||
}
|
if (is_number(sizeStringArr[0])) {
|
||||||
|
size.Width = mystoi(std::string(sizeStringArr[0]));
|
||||||
FontMode mode = FM_Standard;
|
}
|
||||||
bool bold = false, italic = false;
|
if (is_number(sizeStringArr[1])) {
|
||||||
std::string fontStyle = sf.next(":");
|
size.Height = mystoi(std::string(sizeStringArr[1]));
|
||||||
std::vector<std::string> 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;
|
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
FontSpec spec(fontSize, mode, bold, italic);
|
|
||||||
|
|
||||||
core::position2di pos(0,0);
|
core::position2di pos(0,0);
|
||||||
std::string positionStr = sf.next(":");
|
if (numPartsOfName >= 4)
|
||||||
std::vector<std::string> positionStrs = str_split(positionStr, ',');
|
|
||||||
if (positionStrs.size() >= 2)
|
|
||||||
{
|
{
|
||||||
if (is_number(positionStrs[0])) {
|
auto posStr = colonSeparatedPartsOfName[3];
|
||||||
pos.X = mystoi(positionStrs[0]);
|
std::vector<std::string_view> posStringArr = str_split(posStr, ',');
|
||||||
}
|
if (posStringArr.size() >= 2) {
|
||||||
if (is_number(positionStrs[1])) {
|
if (is_number(posStringArr[0])) {
|
||||||
pos.Y = mystoi(positionStrs[1]);
|
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);
|
video::ITexture* referredTexture = nullptr;
|
||||||
std::string colorStr = sf.next("");
|
TextDrawer drawer(textdefW.c_str(), [&, driver, baseimg](auto texturePath){
|
||||||
if (!parseColorString(colorStr,color,false)){
|
if (referredTexture) {
|
||||||
return false;
|
driver->removeTexture(referredTexture);
|
||||||
};
|
}
|
||||||
|
video::IImage *image = m_sourcecache.getOrLoad(texturePath);
|
||||||
irr::gui::CGUITTFont *font =
|
referredTexture = driver->addTexture("text_renderer_base__", baseimg);
|
||||||
static_cast<irr::gui::CGUITTFont *>(g_fontengine->getFont(spec));
|
return referredTexture;
|
||||||
core::dimension2d<u32> sizeText = font->getDimension(textdefW.c_str());
|
});
|
||||||
|
if (referredTexture) {
|
||||||
video::ECOLOR_FORMAT colorFormat = video::ECF_A8R8G8B8;
|
driver->removeTexture(referredTexture);
|
||||||
core::dimension2d<u32> size =sizeText;
|
|
||||||
if (baseimg)
|
|
||||||
{
|
|
||||||
colorFormat = baseimg->getColorFormat();
|
|
||||||
size =baseimg->getDimension();
|
|
||||||
}
|
}
|
||||||
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 =
|
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 (driver->setRenderTarget(texture, video::ECBF_ALL, video::SColor(0,0,0,0))) {
|
||||||
if (baseimg) {
|
if (baseimg) {
|
||||||
auto baseTexture = driver->addTexture("text_renderer_base__", 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);
|
driver->removeTexture(baseTexture);
|
||||||
}
|
}
|
||||||
font->draw(textdefW, core::recti(pos, sizeText), color);
|
drawer.draw(drawingArea, pos, driver, nullptr);
|
||||||
driver->setRenderTarget(NULL);
|
driver->setRenderTarget(NULL);
|
||||||
void* lockedData = texture->lock();
|
void* lockedData = texture->lock();
|
||||||
if (lockedData) {
|
if (lockedData) {
|
||||||
if (baseimg) {
|
if (baseimg) {
|
||||||
baseimg->drop();
|
baseimg->drop();
|
||||||
}
|
}
|
||||||
baseimg = driver->createImageFromData(colorFormat, size, lockedData, false);
|
baseimg = driver->createImageFromData(colorFormat, canvasSize, lockedData, false);
|
||||||
texture->unlock();
|
texture->unlock();
|
||||||
} else {
|
} else {
|
||||||
errorstream << "no data inside texture, internal error" << std::endl;
|
errorstream << "generateImagePart(): no data inside texture, internal error" << std::endl;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errorstream << "fails to set render target, "
|
errorstream << "generateImagePart(): fails to set render target, "
|
||||||
"can this driver renders to target:" <<
|
"can this driver renders to target:" <<
|
||||||
driver->queryFeature(video::EVDF_RENDER_TO_TARGET) << std::endl;
|
driver->queryFeature(video::EVDF_RENDER_TO_TARGET) << std::endl;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
driver->removeTexture(texture);
|
driver->removeTexture(texture);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue