Colourful TEXT

Posted By: lemming

Colourful TEXT - 03/02/13 18:18

Hello everyone!

If you are like my annoyed that using different colours in TEXT objects is not possible, then I probably have exactly what you need:

COLOURFUL TEXT
aka CLTEXT

This lib wraps some TEXTs and arranges them in a way they look like a single TEXT with different colours.


(text from this pic)

Update 1.1:
CLTEXT is now part of the TUST-Project and will only be maintained there.

Changelog:
Code:
1.1: * modified for TUST (mainly doxygen commenting and splitting file)
      + cltext_align added



What it can beside beeing colourful:
- word wrap over different lines
- indent after the first line

What it can not (but will hopefully in the future):
- lf/cr as special character


A description of the functions is in the commentblock of the headerfile.

I coded this while having a cold, so there might be some small bugs or quirks. Please tell me them. wink

Click to reveal..

CLTEXT.H
Code:
////////////////////////////////////////////////////////////////////////////////
// COLOURFUL TEXT
/////////////////
//
//  author: lemming
//  version: 1.0
//  version history:
//      1.0: initial release
//
//  Free to use for every 3DGS user, also for commercial projects. As long as it
//  does not violate the 3DGS terms of use.
//  If you extend the script file, I'd appreciate if you commit it to the
//  community.
//
////////////////////////////////////////////////////////////////////////////////
//
//  This library lets you create a CLTEXT-object, that holds several TEXT-
//  objects with different colours and arranges them so they look as a single
//  TEXT. This is done by parsing a tagged string.
//  example: "[clff0000]red [clffff00]yellow [cl177935] dark green"
//  The RGB-values are as hexcodes inside the text. The text following the tags
//  will be coloured.
//
//  Features:
//    - word warapping
//    - indentation after word wrapping
//
//  Functions:
//
//    var     cltext_hextovar(STRING* hexs, var* i)
//
//    Converts a 2 digit hex to a var
//      return: 0 success, -1 no string, -2 too long string, -3 invalid chars
//
//
//    var     str_width_hack(STRING* str, FONT* font)
//
//    This is a "hacked" version of the str_width function. The original does
//    not take spaces at the end in account, this version does by adding a dot
//    at the end and then removing the width of the dot again.
//      return: The full length with trailing spaces taken in account.
//
//
//    var     cltext_removetags(STRING* str)
//
//    Removes tags from a string, for example when using it in a chat system
//    with user defined inputs.
//      return: how many tags has been removed
//
//
//    CLTEXT* cltext_create(STRING* text, FONT* font, var indent, var sizey,
//                          var flags, COLOR* defaultcolor)
//
//    Creates a CLTEXT object and parses the string.
//
//      text:   colourtagged text to display as a string
//      font:   display text with this font
//      indent: after a linefeed use this indent (not in the first line)
//      sizey:  max width for the text. Will word wrap at reaching it.
//      flags:  not used, but written to the struct and can then be read out
//      dfltclr:colour to use when text has no colour tags
// 
//      return: CLTEXT pointer or NULL if failed to create. This can happen if
//                the max width is smaller than the indent.
//
//
//    var     cltext_getheight(CLTEXT* cltext)
//
//    Returns the height of a CLTEXT. This needs a font to be set.
//      return: > 0 for height or 0 if no font is set.
//
//
//    void*   cltext_remove(CLTEXT* cltext);
//
//    Removes a CLTEXT object from memory.
//      return: NULL
//
//
//    void    cltext_show(CLTEXT* cltext, var posx, var posy, var alpha,
//                        var layer);
//
//    Shows, hides or moves the text.
//
//      cltext: the CLTEXT object to handle
//      posx/y: screen coordinate to move to
//      alpha:  alpha blending
//              0 disables the SHOW tag
//              100 disables the TRANSLUCENT flag
//      layer:  layer to move the text to
//
//
////////////////////////////////////////////////////////////////////////////////

#ifndef __CLTEXT_H
#define __CLTEXT_H

#include <strio.c>


//////////////////////////
// Structs and Prototypes

typedef struct CLLIST {
	TEXT* 		element;
	var 		linex;
	var 		liney;
	struct CLLIST* 	next;
} CLLIST;


typedef struct CLTEXT {
	STRING* 	label;
	CLLIST* 	texts;
	FONT*		font;
	var 		indent;
	var 		sizey;
	var 		flags;
	
	var 		layer;
	var 		posx;
	var 		posy;
} CLTEXT;


// for internal use:
var 	cltext_ValidClTag(STRING* text, var pos);
var 	cltext_SearchTag(STRING* text, var from);
var 	cltext_SearchSpaceBackw(STRING* str, var from);


// for external use:
var 	cltext_hextovar(STRING* hexs, var* i);
var 	str_width_hack(STRING* str, FONT* font);
var 	cltext_removetags(STRING* str);
CLTEXT* cltext_create(STRING* text, FONT* font, var indent, var sizey, var flags, COLOR* defaultcolor);
var 	cltext_getheight(CLTEXT* cltext);
void* 	cltext_remove(CLTEXT* cltext);
void 	cltext_show(CLTEXT* cltext, var posx, var posy, var alpha, var layer);



//////////////////
// Implementation

var 	cltext_hextovar(STRING* hexs, var* i)
// turns hex-strings into var i
// return: 0 success, -1 no string, -2 too long string, -3 invalid char
{
   var hexl = str_len(hexs);
   
   if (hexl <= -1)
      return -1;         // no string
   if (hexl > 2)
      return -2;        // too long string
   
   var c;
   char hex[2];
   for (c=0;c<hexl;c++)
      hex[c] = (hexs.chars)[c];
   
   var factor=1;
   var h = 0;
   
   for (c=hexl-1;c>=0;c--)
   {
      if ((hex[c] >= 48) && (hex[c] <= 57))         // 0 - 9
         h += (hex[c] - 48) * factor;
      else if ((hex[c] >= 65) && (hex[c] <= 70))    // A - F
         h += (hex[c] - 65 + 10) * factor;
      else if ((hex[c] >= 97) && (hex[c] <= 102))   // a - f
         h += (hex[c] - 97 + 10) * factor;
      else
         return -3;     // invalid char
      
      factor *= 16;
   }
   
   *i = h;
   
   return 0;            // success
}

var 	cltext_ValidClTag(STRING* text, var pos)
// checks if a valid colour tag is at a given position
// Does not check if the hex code is valid
// return: 0 yes, != 0 no
{
	if (pos > (str_len(text)-10)) return 5;
	
	var res = 0;
	if ((text.chars)[pos+0] != '[') res += 1;
	if ((text.chars)[pos+1] != 'c') res += 1;
	if ((text.chars)[pos+2] != 'l') res += 1;
	if ((text.chars)[pos+9] != ']') res += 1;
	return res;
}

var 	cltext_SearchTag(STRING* text, var from)
// searches for a colour tag
// return: position or -1
{
	var i;
	for (i = from; i < (str_len(text)-10); i++)
	{
		if (cltext_ValidClTag(text, i) == 0) return i;
	}
	return -1;
}

var 	str_width_hack(STRING* str, FONT* font)
// returns the width of a string in pixels with trailing spaces
// return: width
{
	STRING* wrk = str_create(str);
	str_cat(wrk,".");
	var strwidth = str_width(wrk,font) - str_width(".",font);
	ptr_remove(wrk);
	return strwidth;
}

var 	cltext_SearchSpaceBackw(STRING* str, var from)
// searches spaces in a STRING backwards beginning at from
// return: position or 0

{
	var i;
	for (i=from-1; i > 0; i--)
	{
		if (str_getchr(str,i) == 32) return i;
	}
	return 0;
}

var 	cltext_removetags(STRING* str)
// removes colour tags from a string
// return: count of tags removed
{
	STRING* work = str_create("");
	STRING* newstr = str_create("");
	var i=0, count = 0;
	
	do
	{
		i = cltext_SearchTag(str, i);
		if (i > -1)
		{
			if (i > 0)
				str_cut(newstr, str, 0, i);
			else
				str_cpy(newstr, "");
			str_cut(work, str, i+11, 0);
			str_cat(newstr,work);
			str_cpy(str,newstr);
			count += 1;
		}			
	} while (i > -1);
	ptr_remove(newstr);
	ptr_remove(work);
	return count;
}

CLTEXT* cltext_create(STRING* text, FONT* font, var indent, var sizey, var flags, COLOR* defaultcolor)
// creates a CLTEXT object
// text:	colourtagged text to display as a string
// font:	display text with this font
// indent:	after a linefeed use this indent (not in the first line)
// sizey: 	max width for the text. Will word wrap at reaching it.
// flags: 	not used, but written to the struct and can then be read out
// dfltclr: colour to use when text has no colour tags
{
	if (sizey < (indent+1)) return NULL;
	
	CLTEXT* cltext = sys_malloc(sizeof(CLTEXT));
	cltext.label = str_create(text);
	cltext.texts = NULL;
	cltext.font = font;
	cltext.indent = indent;
	cltext.sizey = sizey;
	cltext.flags = flags;
	
	// First element
	CLLIST* current = sys_malloc(sizeof(CLLIST));
	current.element = NULL;
	current.next = NULL;
	cltext.texts = current;
	
	STRING* newline = str_create("");
	STRING* curline = str_create("");
	STRING* worktext = str_create("");
	var found = -1, cutstart = 0, cutend = -1, text_len = str_len(text);
	
	while (cutstart < text_len)
	{
		current.element = txt_create(1,0);
		
		found = cltext_SearchTag(text, cutstart);
		if (found == cutstart)
		{	// Tagged Text
			cutstart = found + 11;
			cutend = cltext_SearchTag(text, cutstart);
			if (cutend < cutstart) cutend = text_len;
			
			str_cut(worktext, text, found+4, found+5);
			cltext_hextovar(worktext, current.element.red);
			str_cut(worktext, text, found+6, found+7);
			cltext_hextovar(worktext, current.element.green);
			str_cut(worktext, text, found+8, found+9);
			cltext_hextovar(worktext, current.element.blue);
		}
		else if (found > cutstart)
		{	// Untagged text
			cutend = found;
			vec_set(current.element.blue, defaultcolor);
		}
		else
		{	// No Tags found
			cutend = text_len;
		}
		
		str_cut((current.element.pstring)[0], text, cutstart, cutend);
		
		// If the text in the line is too long, it has to be wrapped
		while ( (current.linex + str_width((current.element.pstring)[0], font)) > sizey )
		{
			var current_len = str_len((current.element.pstring)[0]);
			var validspace = 0, possiblespace = 0;
			var lastspace = current_len;
			while ((lastspace > 0) && (validspace == 0))
			{
				lastspace = cltext_SearchSpaceBackw((current.element.pstring)[0], lastspace);
				if (lastspace > 0)
				{
					possiblespace = lastspace;
					str_cut(worktext, (current.element.pstring)[0], 0, possiblespace);
					if (str_width(worktext,font)+current.linex <= sizey)
					{
						validspace = lastspace;
					}
				}
			}

			// we now know if there is a valid space and where it is
			if (validspace > 0)
			{	// great, we have a space to lf
				str_cut(curline, (current.element.pstring)[0], 0, validspace);
				str_cut(newline, (current.element.pstring)[0], validspace+1, current_len);
			}
			else
			{	// too bad, there is no valid space
				if ( ((current.linex == indent) && (current.liney > 0)) || ((current.linex == 0) && (current.liney == 0)) )
				{	// text at the beginning of the line
					if (possiblespace > 0)
					{	// just use the next space possible
						str_cut(curline, (current.element.pstring)[0], 0, possiblespace);
						str_cut(newline, (current.element.pstring)[0], possiblespace+1, current_len);
					}
					else
					{	// no chance for a lf
						str_cpy(curline, (current.element.pstring)[0]);
						str_cpy(newline, "");
					}
				}
				else
				{	// text at the end of the line, so just lf it.
					str_cpy(curline, "");
					str_cpy(newline, (current.element.pstring)[0]);
				}
			}

			// so time for a new line
			current.next = sys_malloc(sizeof(CLLIST));
			current.next.next = NULL;
			current.next.element = txt_create(1,0);
			current.next.linex = indent;
			current.next.liney = current.liney + font.dy;
			vec_set(current.next.element.blue, current.element.blue);
			
			str_cpy((current.element.pstring)[0], curline);
			str_cpy((current.next.element.pstring)[0], newline);
			
			current = current.next;
		}
		
		// Create new entry for next cycle
		current.next = sys_malloc(sizeof(CLLIST));
		current.next.linex = current.linex + str_width_hack((current.element.pstring)[0], font);
		current.next.liney = current.liney;
		current.next.element = NULL;
		current.next.next = NULL;
		current = current.next;
		
		cutstart = cutend;
		
	}
	ptr_remove(worktext);
	ptr_remove(curline);
	ptr_remove(newline);
	return cltext;
}

var 	cltext_getheight(CLTEXT* cltext)
// returns the height in pixels of a CLTEXT. A font has to be set.
{
	CLLIST* current = cltext.texts;
	var maxliney = 0;
	if (cltext)
	{
		while (current != NULL)
		{
			maxliney = maxv(maxliney, current.liney);
			current = current.next;
		}
		if (cltext.font)
			maxliney += cltext.font.dy;
		else
			maxliney = 0;
	}
	return maxliney;
}
		

void* 	cltext_remove(CLTEXT* cltext)
// removes a CLTEXT
// return: NULL
{
	CLLIST* temp;
	CLLIST* current = cltext.texts;
	

	while (current != NULL)
	{
		if (current.element != NULL)
		{
			ptr_remove((current.element.pstring)[0]);
			ptr_remove(current.element);
		}
		temp = current;
		current = current.next;
		sys_free(temp);
	}
		

	ptr_remove(cltext.label);
	sys_free(cltext);
	return NULL;
}

void 	cltext_show(CLTEXT* cltext, var posx, var posy, var alpha, var layer)
// shows and moves a CLTEXT object
{
	CLLIST* current = cltext.texts;
	cltext.posx = posx;
	cltext.posy = posy;

	do
	{
		current.element.pos_x = posx + current.linex;
		current.element.pos_y = posy + current.liney;
		current.element.font = cltext.font;
		layer_sort(current.element,layer);
		current.element.alpha = clamp(alpha,0,100);
		if (alpha == 0)
		{
			reset(current.element, SHOW | LIGHT | TRANSLUCENT);
		}
		else if (alpha == 100)
		{
			reset(current.element, TRANSLUCENT);
			set(current.element, LIGHT | SHOW);
		}
		else
		{
			set(current.element, LIGHT | SHOW | TRANSLUCENT);
		}
		if (current.next != NULL) current = current.next;	
	} while (current.next != NULL);
}


#endif

/////////////////////////////////////////////////////////////////////// E O F //











The code for creating the scene in the picture is the following:
Code:
void main(void)
{
    level_load(NULL);
    wait(3);
    ENTITY* guard_mdl = ent_create("guard.mdl", _vec(30,0,-32),NULL);
    guard_mdl.pan = 150;
    PANEL* backpan = pan_create(NULL,0);
    backpan.size_x = 600; backpan.size_y = 100;
    backpan.pos_x = 60; backpan.pos_y = 340;
    backpan.alpha = 50; vec_set(backpan.blue, nullvector);
    set(backpan, LIGHT | SHOW | TRANSLUCENT);
    
    STRING** chatstr;
    CLTEXT** chattxt;
    
    chatstr = sys_malloc(sizeof(STRING*)*6);
    chattxt = sys_malloc(sizeof(CLTEXT*)*6);
    
    STRING* guardstr = str_create("[clff0000]Guard: [clffffff]I used to be an [cl2233ff]adventurer [clffffff]like you. Until I was offered this job as [clf2f222]city guard[clffffff]. It's a much more stable wage and less dangerous plus I get to spend more time with my [clff00ff]family[clffffff].");
    chatstr[0] = str_create("[clff0000][CyberGirl] [clffdd11][pwns] [cl0000ff][Witch]");
    chatstr[1] = str_create("[cl0000ff][Witch] [clffffff]    [all]: lol, hax0r");
    chatstr[2] = str_create("[clff0000][CyberGirl] [clffffff][all]: wtf? u sux, stfu n00b");
    chatstr[3] = str_create("[clff0000][Warlock] [clffffff]  [all]: My dear fellows, I barely understand a word of what you say. Why won't you speak in a clear language?");
    chatstr[4] = str_create("[clff0000][CyberGirl] [clffffff][all]: stfu");
    chatstr[5] = str_create("[cl0000ff][Witch] [clffffff]    [all]: stfu");
    FONT* myfont = font_create("Arial#18b");
    FONT* chatfont = font_create("Courier New#14b");
	
	CLTEXT* clguard = cltext_create(guardstr, myfont, 0, 540, 0, nullvector);
	cltext_show(clguard, 80, 360, 100, 1);
	
	var i;
	for (i = 0; i < 6; i++)
	{
		chattxt[i] = cltext_create(chatstr[i], chatfont, str_width_hack(_str("                   "), chatfont), 400, 0, nullvector);
		if (i > 0)
			cltext_show(chattxt[i], 40, cltext_getheight(chattxt[i-1]) + chattxt[i-1].posy, 100, 1);
		else
			cltext_show(chattxt[i], 40, 40, 100, 1);
	}
}

[/b][b]
Posted By: Kartoffel

Re: Colourful TEXT - 03/02/13 18:38

Wow very cool!
I'm sure this will be helpful for a lot of people, including me laugh

(I had to laugh about the chat dialogue... sounds familiar, lol)
Posted By: Dico

Re: Colourful TEXT - 03/03/13 10:35

wow thanks lemming, this will help me a lot in my project
Posted By: lemming

Re: Colourful TEXT - 03/03/13 12:45

I'm happy you like it. =)
Posted By: Loremaster

Re: Colourful TEXT - 03/18/13 00:27

Saved for later perusal. Looks interesting.

Say, is that Fox Moulder as City Guard?
Posted By: oliver2s

Re: Colourful TEXT - 03/18/13 18:41

Thank you for sharing. I think this will be very useful.
Posted By: lemming

Re: Colourful TEXT - 04/16/13 21:34

I did a small update and added it to the TUST project.
Posted By: Kartoffel

Re: Colourful TEXT - 05/26/13 21:56

hey there, smile

I was wondering why the colors looked kina wired and had a closer look at the source code.
You made a little mistake in the function which reads the color from a string.

It should be like this:

(actually, it should be right without the / 2 in line 179 but somehow it is exactly the half...)

...I also created a couple of additional functions (f.e. changing the text of an CLTEXT)
I can share them if anybody is interested.
Posted By: lemming

Re: Colourful TEXT - 05/27/13 17:56

Thank you a lot! Bug fixes and added features are always welcome! Feel free to update the file on TUST (which is an updated version of this one).

But please explain what you have changed. I don't get it at all. O_o
Posted By: Wiseguy

Re: Colourful TEXT - 05/27/13 18:18

I haven't tested it, but this method here should do the trick and is much saner in the handling of the string:

Code:
var cltext_hextovar(STRING *hexs, var *output)
{
    if(!hexs || !output)
        return -1;

    if(str_len(hexs) > 2)
        return -2;
        
    const char *hex = hexs->chars;
    int acc = 0;
    
    while(*hex)
    {
        char c = *(hex ++);
        
        if(c >= '0' && c <= '9)
            c -= '0';
        else if(c >= 'A' && <= 'Z')
            c -= 'A' - 10;
        else if(c >= 'a' && <= 'z')
            c -= 'a' - 10; 
        else
            return -3;
            
        acc = (acc * 16) + c;
    }
    
    *output = acc;
    return 0;
}



Further more, it can be expanded to convert strings with any base, by simply replacing the 16 in line 27 with the required base (and by adding a sanity check wether c is inside the base). Also, Kartoffel, could you please not post screenshots of code? I would have loved to copy and paste it instead of having to write the whole body myself.

(PS: Lemming: It's your code, you should be the one who maintains it in the git repo ;))
Posted By: Kartoffel

Re: Colourful TEXT - 05/27/13 18:19

if you have a two-digit hex value the maximum number is (16 * 16 + 16);
So if you divide 'h' by this value, you have it in range [0-1].

The I just added a '* 256' to get to the default [0-255] range.

...and the '/ 2' is somehow necessary, I don't know why.

Edit: make sure you read Wiseguy's post, we wrote something at the same time.
Posted By: lemming

Re: Colourful TEXT - 05/27/13 18:38

I have to carefully read and think about your posts. This kinda throws an unhandled exception in my brain. What you say make sense, but at the same time not. Because I sure have 16 values, but F is 15, so if I have two F (maximum) it's 15*16+15=255. If I have to zeros (minimum) it's 0*16+0=0.

For the code, I'd be happy if you send it to me, then I can update it. This whole Open-Source-Process is still new to me. laugh
Posted By: Kartoffel

Re: Colourful TEXT - 05/27/13 18:57

now I don't understand it either crazy

I think the problem is the way 3dgs colors texts... and I just made a little workaround for that
Posted By: lemming

Re: Colourful TEXT - 05/28/13 14:44

So, I did a test with my unchanged original hextovar function and the following string:
Code:
"[clff0000]ff0000 [cl00ff00]00ff00 [cl0000ff]0000ff [clffffff]ffffff [cl22ff33]22ff33 [cl123456]123456 [cl2234dc]2234dc [cldcf1ac]dcf1ac [cl103004]103004"



I made a screenshot and loaded it into gimp to check all the colours. And they were all correct.



So I donno what's happening in your script, Kartoffel. Can you also do a test like that?
Posted By: Kartoffel

Re: Colourful TEXT - 05/28/13 19:38

umm, I used a bitmap-font, maybe thats it? (just use a completely white image)
Posted By: Kartoffel

Re: Colourful TEXT - 05/28/13 20:51

That's the result I had with this bmap-font I made:




(I'm always increasing like: 11, 22, 33, 44, ...)
Posted By: Uhrwerk

Re: Colourful TEXT - 05/28/13 21:46

The color of the bitmap font should be 0x808080. Then it will work.
Posted By: Kartoffel

Re: Colourful TEXT - 05/28/13 21:48

oh, well I guess that's the problem smirk
(should be in the manual...)
Posted By: lemming

Re: Colourful TEXT - 05/30/13 08:10

Okay, thank you. I will also add this to the readme.

@Kartoffel, I'm still interested in your functions. wink
Posted By: djfeeler

Re: Colourful TEXT - 08/17/13 06:19

Thanks for this contribution ^^

I made this function just to not display the text. You can display it in the code.

Code:
void cltext_reset(CLTEXT* cltext)
{
	CLLIST* current = cltext.texts;
	
	do
	{
		reset(current.element, SHOW | LIGHT | TRANSLUCENT);
		if (current.next != NULL) current = current.next;
	}while (current.next != NULL);	
}

Posted By: Ch40zzC0d3r

Re: Colourful TEXT - 10/02/13 18:57

Well thanks for the contribution!
There are some bugs, for example this function:
Code:
var cltext_valid_cltag(STRING *text, var pos)



I recoded it so it wont crash:
Code:
var cltext_valid_cltag(STRING *text, var pos)
{
	if (pos > (str_len(text)-10)) return 5;
	
	var res = 0;
	if(str_getchr(text, pos+1) != '[') res++;
	if(str_getchr(text, pos+2) != 'c') res++;
	if(str_getchr(text, pos+3) != 'l') res++;
	if(str_getchr(text, pos+10) != ']') res++;
	
	return res;
}



I tried to make a function to add new text and it worked just fine, but Ive got a problem. I wanted to use this for my chat but "\n" dont move other text elements down and I didnt know how to fix that..
Any ideas?
Posted By: lemming

Re: Colourful TEXT - 10/11/13 16:20

Thank you for bugreporting and fixing!

I didn't implemented a manual CR+LF, though it is planned as well as replacing TEXT with MasterQs function. But currently I have a lot of problems finishing my PC so it might take some time.
Posted By: Reconnoiter

Re: Colourful TEXT - 05/17/15 15:47



Where to get this or the t(r?)ust-project? Would be nice to use color (/hex) codes to color individual strings.
Posted By: PadMalcom

Re: Colourful TEXT - 05/17/15 15:58

Hey Reconnoiter, TUST is here:

https://github.com/MasterQ32/TUST
Posted By: Reconnoiter

Re: Colourful TEXT - 05/17/15 18:18

wow I did not knew that project was so big, thanks alot PadMalcom for the link.
© 2024 lite-C Forums