From b77cee146b0029d377f1028b942857d062bc1374 Mon Sep 17 00:00:00 2001
From: sfan5 <sfan5@live.de>
Date: Sat, 3 Sep 2016 17:53:15 +0200
Subject: [PATCH] Allow escaping of texture names when passed as an argument to
 a modifier

---
 doc/lua_api.txt     | 14 ++++++++--
 src/client/tile.cpp | 65 ++++++++++++++++++++++++++++++---------------
 2 files changed, 56 insertions(+), 23 deletions(-)

diff --git a/doc/lua_api.txt b/doc/lua_api.txt
index 1da45ad66..74d4b90d5 100644
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -263,7 +263,17 @@ Textures can be grouped together by enclosing them in `(` and `)`.
 Example: `cobble.png^(thing1.png^thing2.png)`
 
 A texture for `thing1.png^thing2.png` is created and the resulting
-texture is overlaid over `cobble.png`.
+texture is overlaid on top of `cobble.png`.
+
+### Escaping
+Modifiers that accept texture names (e.g. `[combine`) accept escaping to allow
+passing complex texture names as arguments. Escaping is done with backslash and
+is required for `^` and `:`.
+
+Example: `cobble.png^[lowpart:50:color.png\^[mask\:trans.png`
+
+The lower 50 percent of `color.png^[mask:trans.png` are overlaid
+on top of `cobble.png`.
 
 ### Advanced texture modifiers
 
@@ -351,7 +361,7 @@ Example:
     default_stone.png^[transformFXR90
 
 #### `[inventorycube{<top>{<left>{<right>`
-`^` is replaced by `&` in texture names.
+Escaping does not apply here and `^` is replaced by `&` in texture names instead.
 
 Create an inventory cube texture using the side textures.
 
diff --git a/src/client/tile.cpp b/src/client/tile.cpp
index 3b5d2a3ae..67d5d8d1a 100644
--- a/src/client/tile.cpp
+++ b/src/client/tile.cpp
@@ -948,11 +948,10 @@ video::ITexture* TextureSource::generateTextureFromMesh(
 
 video::IImage* TextureSource::generateImage(const std::string &name)
 {
-	/*
-		Get the base image
-	*/
+	// Get the base image
 
 	const char separator = '^';
+	const char escape = '\\';
 	const char paren_open = '(';
 	const char paren_close = ')';
 
@@ -960,7 +959,9 @@ video::IImage* TextureSource::generateImage(const std::string &name)
 	s32 last_separator_pos = -1;
 	u8 paren_bal = 0;
 	for (s32 i = name.size() - 1; i >= 0; i--) {
-		switch(name[i]) {
+		if (i > 0 && name[i-1] == escape)
+			continue;
+		switch (name[i]) {
 		case separator:
 			if (paren_bal == 0) {
 				last_separator_pos = i;
@@ -1028,10 +1029,12 @@ video::IImage* TextureSource::generateImage(const std::string &name)
 			return NULL;
 		}
 		core::dimension2d<u32> dim = tmp->getDimension();
-		if (!baseimg)
-			baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
-		blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
-		tmp->drop();
+		if (baseimg) {
+			blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
+			tmp->drop();
+		} else {
+			baseimg = tmp;
+		}
 	} else if (!generateImagePart(last_part_of_name, baseimg)) {
 		// Generate image according to part of name
 		errorstream << "generateImage(): "
@@ -1099,9 +1102,27 @@ video::IImage * Align2Npot2(video::IImage * image,
 
 #endif
 
+static std::string unescape_string(const std::string &str, const char esc = '\\')
+{
+	std::string out;
+	size_t pos = 0, cpos;
+	out.reserve(str.size());
+	while (1) {
+		cpos = str.find_first_of(esc, pos);
+		if (cpos == std::string::npos) {
+			out += str.substr(pos);
+			break;
+		}
+		out += str.substr(pos, cpos - pos) + str[cpos + 1];
+		pos = cpos + 2;
+	}
+	return out;
+}
+
 bool TextureSource::generateImagePart(std::string part_of_name,
 		video::IImage *& baseimg)
 {
+	const char escape = '\\'; // same as in generateImage()
 	video::IVideoDriver* driver = m_device->getVideoDriver();
 	sanity_check(driver);
 
@@ -1251,7 +1272,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
 		}
 		/*
 			[combine:WxH:X,Y=filename:X,Y=filename2
-			Creates a bigger texture from an amount of smaller ones
+			Creates a bigger texture from any amount of smaller ones
 		*/
 		else if (str_starts_with(part_of_name, "[combine"))
 		{
@@ -1259,7 +1280,6 @@ bool TextureSource::generateImagePart(std::string part_of_name,
 			sf.next(":");
 			u32 w0 = stoi(sf.next("x"));
 			u32 h0 = stoi(sf.next(":"));
-			//infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
 			core::dimension2d<u32> dim(w0,h0);
 			if (baseimg == NULL) {
 				baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
@@ -1268,11 +1288,11 @@ bool TextureSource::generateImagePart(std::string part_of_name,
 			while (sf.at_end() == false) {
 				u32 x = stoi(sf.next(","));
 				u32 y = stoi(sf.next("="));
-				std::string filename = sf.next(":");
+				std::string filename = unescape_string(sf.next_esc(":", escape), escape);
 				infostream<<"Adding \""<<filename
 						<<"\" to combined ("<<x<<","<<y<<")"
 						<<std::endl;
-				video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
+				video::IImage *img = generateImage(filename);
 				if (img) {
 					core::dimension2d<u32> dim = img->getDimension();
 					infostream<<"Size "<<dim.Width
@@ -1295,7 +1315,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
 			}
 		}
 		/*
-			"[brighten"
+			[brighten
 		*/
 		else if (str_starts_with(part_of_name, "[brighten"))
 		{
@@ -1309,7 +1329,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
 			brighten(baseimg);
 		}
 		/*
-			"[noalpha"
+			[noalpha
 			Make image completely opaque.
 			Used for the leaves texture when in old leaves mode, so
 			that the transparent parts don't look completely black
@@ -1336,7 +1356,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
 			}
 		}
 		/*
-			"[makealpha:R,G,B"
+			[makealpha:R,G,B
 			Convert one color to transparent.
 		*/
 		else if (str_starts_with(part_of_name, "[makealpha:"))
@@ -1375,7 +1395,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
 			}
 		}
 		/*
-			"[transformN"
+			[transformN
 			Rotates and/or flips the image.
 
 			N can be a number (between 0 and 7) or a transform name.
@@ -1543,12 +1563,11 @@ bool TextureSource::generateImagePart(std::string part_of_name,
 			Strfnd sf(part_of_name);
 			sf.next(":");
 			u32 percent = stoi(sf.next(":"));
-			std::string filename = sf.next(":");
-			//infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
+			std::string filename = unescape_string(sf.next_esc(":", escape), escape);
 
 			if (baseimg == NULL)
 				baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
-			video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
+			video::IImage *img = generateImage(filename);
 			if (img)
 			{
 				core::dimension2d<u32> dim = img->getDimension();
@@ -1628,9 +1647,9 @@ bool TextureSource::generateImagePart(std::string part_of_name,
 			}
 			Strfnd sf(part_of_name);
 			sf.next(":");
-			std::string filename = sf.next(":");
+			std::string filename = unescape_string(sf.next_esc(":", escape), escape);
 
-			video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
+			video::IImage *img = generateImage(filename);
 			if (img) {
 				apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
 						img->getDimension());
@@ -1673,6 +1692,10 @@ bool TextureSource::generateImagePart(std::string part_of_name,
 
 			apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
 		}
+		/*
+			[applyfiltersformesh
+			Internal modifier
+		*/
 		else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
 		{
 			// Apply the "clean transparent" filter, if configured.
-- 
GitLab