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]

Last edited by lemming; 04/16/13 21:32.