Changing text color right before they get printed out

Godot Version

4.5stable

Question

I was hoping to change the text color of all rendered text through a dictionary such that whenever the word “red” appears on screen they always appear red, without manually setting label color or using bbcode. Is this possible without compiling your own engine?

Yes, absolutely. You can make a custom Control or Node2D node for example to do this.

class_name ColorLabel extends Control

@export var label_settings: LabelSettings:

var _line = TextLine.new()

func _draw() -> void:
	# Get the font, font size and color from the ThemeDB
	var font = ThemeDB.fallback_font
	var font_size = ThemeDB.fallback_font_size
	var font_color = Color.WHITE

	# If the label_settings is valid, then use the values from it
	if is_instance_valid(label_settings):
		font = label_settings.font
		font_size = label_settings.font_size
		font_color = label_settings.font_color

	# Clear the line and add the new string
	_line.clear()
	_line.add_string(_text, font, font_size)
	# Get the primary TextServer
	var ts = TextServerManager.get_primary_interface()
	# And get the glyph information from the line
	var glyphs = ts.shaped_text_get_glyphs(_line.get_rid())

	var offset = 0.0
	for glyph_data in glyphs:
		# draw the glyph
		ts.font_draw_glyph(glyph_data["font_rid"], get_canvas_item(), font_size, Vector2.ZERO, glyph_data["index"], font_color, 2.0)
		# add the advance to the offset
		offset += glyph_data.get("advance", 0.0)

Inside this, you’d have to match every word against the dictionary, then change the color property for each glyph (letter) in the word by determining the offsets. Totally do-able.

I pulled this code from an example @mrcdk game in this thread: Circular/Curved Text Effects - #3 by mrcdk I’ve been playing around with it for a Control version derived from a Label, which is significantly harder than using a Node2D. At any rate, this is where I’d start. Note that this code is going to run every time a draw call is made (60 times a second), so keep that in mind in case you have performance issue with it.

2 Likes

It could be 60 times a second but not necessarily more than 1 time in total.
Draw calls are only called when necessary (visible onscreen graphics changes or when manually called) except for the guaranteed initial call.

Your proposed solution is perfect for multi-color text inside a label. It is a kind of bbcode itself once you start parsing the words as key words trigger colour changes.
I would also like to point out (to the OP) that since we are in the _draw() function there exists draw_string() and draw_char() methods which would eliminate the need for a label but comes with its own challenges.

I don’t fully understand what the OP wants here though and a muliti-colour-text label might not be it. (or it might be it)
Why does he not want to access the labels font colour and why too does he not want to use bbcode?
I think we need more info from OP.

1 Like

sounds like a good way to do it. Just out of curiosity, Is it possible to intercept the textserver before it draws text to edit it so no custom label class is needed?

The main idea is that I don’t want to manually manage all text nodes, whether programically created or manually created, and insert bbcode or color. There may be one specific word, say “red”, that I wish to be red-colored across a thound places where this text appears, and I couldn’t think of a easy way to manage them.

Inherit RichTextLabel, implement a string property you set instead of text. Intercept changes to this property in its setter, parse the text and assign it to text property. Parse in _ready() or _init() as well to handle manually set text. Use regex for more sophisticated parsing if needed.

It is theoretically possible, since there is only one TextServer. So this line:

var text_server = TextServerManager.get_primary_interface()

Gets you a reference to the same object every time you call it. (I.E. ts above and text_server here are exactly the same variable.) However the class has no signals, so there’s nothing to hang onto as far as writing a preprocessor for all text in the game. So I’m not sure how you’d do it without compiling your own version of Godot to add signals to TextServer.

Keep in mind that the example above is specifically not a custom Label class. I have not figured out a way to overwrite the existing text from the Label - only how to delete it and super-impose this over it. Hence my suggestion of a custom Control. Also, you could use a custom Node2D. Both inherit from CanavsItem - which is where the _draw() functionality comes from.

The draw_string() and draw_char() methods will work and do not require a reference to TextServer. In that case, you could pass a string, parse it by the spaces, and output each string, changing the color for the strings that match a dictionary (and getting the color). Then output each string with a trailing space until you get to the end. this would likely simplify the code, but I don’t have time to write you an example right now.

I do agree with @sancho that more information on why you want this might give you alternative solutions. As for the supposition of only being drawn once - I kinda assumed this was going to either be an interactive text game, this was for dialog, a chat server, or HUD. Any of those will result in the text likely moving around the screen, which would necessitate multiple draw calls.

1 Like

Thanks for the detailed explanation. I was hoping for a easy way to implement a preprocessor that could do all the heavy lifting for me, but given the complexity of diving into actual textservers, I’d stick with your earlier proposal of implementing an alternative control node.

1 Like