Merge 9324143b47
into f54d209bc8
This commit is contained in:
commit
8e5c307d6f
@ -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
|
||||
-----------------
|
||||
|
||||
|
@ -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 },
|
||||
})
|
||||
|
@ -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 "
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user