Defold-RichText

Oh! This is quite the useful change. We’d previously pad our textures to achieve this.

6 Likes

Is there a way to set a specific height for a richtext? I only see a width parameter in richtext.create().

My purpose will be to have a fixed “box” of text with width and heigth predetermined. If the text overflows the richtext box, it will not be shown.

Maybe with richtext.truncate() it would be done but it will force to measure the text every single time to properly cut the input.

No, there is no such setting, but that’s actually not a bad suggestion. It could be an option to richtext.create().

Another option is to use stencils to mask out text nodes outside the box: https://defold.com/manuals/gui-clipping/

A wonderful library! But I had a couple of questions :slight_smile:

1. Is vertical align works correctly?

I pass valign = richtext.VALIGN_MIDDLE in the settings table but it always creates top-aligmened text. What can I do to align the center of the text with unlimited number of lines to the root node?

10
richtext_vertical_align.zip (3.4 KB)

2. How to update the color of some words without unnecessary spaces?

SPOILER: Code is here!
function StoryView:setParagraphs(paragraphs, highlighted)  
  local text = ''

  for _, paragraph in ipairs(paragraphs) do
    if highlighted then
      paragraph = paragraph:gsub(highlighted, function()
        return '<color=red>' .. highlighted .. '</color>'
      end)
    end
    text = text .. '<p>' .. paragraph .. '</p>'
  end

  local settings = {
    width = self.width,
    parent = self.rootNode,
    valign = richtext.VALIGN_MIDDLE,
    align = richtext.ALIGN_CENTER,
    combine_words = false
  }
  
  if self.words then
    for _, word in ipairs(self.words) do
      gui.delete_node(word.node)
    end
  end
  
  self.words = richtext.create(text, 'neucha', settings)
end

A space between ‘test’ and ‘?’ signs in this example. I also tried combine_words = true but it creates even more additional spaces to existed spaces in highlighted parts.

This controls vertical alignment per line. Not for the entire text.

You need to manually position the text once you know the size. If you put all nodes as a child of a parent it’s quite simple to move the parent node vertically to align the entire text.

Sounds like a bug. Can you please create a ticket on GitHub?

1 Like

Sounds like a solution, thanks.

Done.

2 Likes

Is it possible to get clear text without HTML tags using RichText? Like strip_tags() in PHP.

UPD: solved using manual parsing

local text = "<p>Текст. </p><!-- Комментарий --> <a href=\"#fragment\">Еще  какой-то текст.</a>" 
print(text:gsub("<[^<>]+>", ""))

>> Текст. Еще какой-то текст.
2 Likes

I wanted to achieve text effects like in Celeste and Slay the Spire. I took the code from the “Characters” example in the main Richtext project and wrote a couple of effects functions. I then wrapped richtext.create in a new function that automatically calls these effect functions (richtext.create_with_animations). It’s dirty and probably not optimised, but it works for me and might be useful for someone else.

This is how it looks:

These are the tags, in order of their appearance in the clip:
shakeletters
waveletters
wavewords
shakewords

Use them like this:
<shakeletters>shake each letter!</shakeletters>

The code:

function richtext.wave_letters(words)

	local waves = richtext.tagged(words, "waveletters")
	
	for _,wave in pairs(waves) do
		for i,word in ipairs(words) do
			if word == wave then
				table.remove(words, i)
				break
			end
		end
		gui.delete_node(wave.node)
		local chars = richtext.characters(wave)
		for i,char in ipairs(chars) do
			local pos = gui.get_position(char.node)
			local pos_2 = gui.get_position(char.node)
			pos_2.y = pos_2.y - 1.5
			pos_2.x = pos_2.x + 0
			gui.set_position(char.node, pos_2)
			local amplitude = tonumber(wave.tags.wave) or 1.5
			gui.animate(char.node, gui.PROP_POSITION, pos + vmath.vector3(0, amplitude, 0), gui.EASING_INOUTSINE, 1.2, i * 0.12112, nil, gui.PLAYBACK_LOOP_PINGPONG)
			table.insert(words, char)
		end
	end
end

function richtext.shake_letters(words)

	local waves = richtext.tagged(words, "shakeletters")
	
	for _,wave in pairs(waves) do
		for i,word in ipairs(words) do
			if word == wave then
				table.remove(words, i)
				break
			end
		end
		gui.delete_node(wave.node)
		local chars = richtext.characters(wave)
		for i,char in ipairs(chars) do
			local pos = gui.get_position(char.node)
			local pos_2 = gui.get_position(char.node)
			pos_2.y = pos_2.y - 0.75
			pos_2.x = pos_2.x - 0.75
			gui.set_position(char.node, pos_2)
			gui.animate(char.node, "position.x", pos.x + 0.75 * math.random(), gui.EASING_INOUTBOUNCE, 0.1+0.05 * math.random(), i * 0.12112, nil, gui.PLAYBACK_LOOP_PINGPONG)
			gui.animate(char.node, "position.y", pos.y + 0.75 * math.random(), gui.EASING_INOUTBOUNCE, 0.1+0.05 * math.random(), i * 0.12112, nil, gui.PLAYBACK_LOOP_PINGPONG)
			table.insert(words, char)
		end
	end
end

function richtext.wave_words(words)

	local waves = richtext.tagged(words, "wavewords")

	local i = 0
	for _,wave in pairs(waves) do
		i = i + 1
		local pos = gui.get_position(wave.node)
		gui.set_position(wave.node, pos - vmath.vector3(0,1.5,0))
		gui.animate(wave.node, gui.PROP_POSITION, pos + vmath.vector3(0, 1.5, 0), gui.EASING_INOUTSINE, 1.2, i * 0.12112, nil, gui.PLAYBACK_LOOP_PINGPONG)
	end
end

function richtext.shake_words(words)

	local waves = richtext.tagged(words, "shakewords")

	local i = 0
	for _,wave in pairs(waves) do
		i = i + 1
		local pos = gui.get_position(wave.node)
		gui.set_position(wave.node, pos - vmath.vector3(1,1,0))
		gui.animate(wave.node, "position.x", pos.x + 1, gui.EASING_INOUTBOUNCE, 0.075+0.05*math.random(), i * 0.12112, nil, gui.PLAYBACK_LOOP_PINGPONG)
		gui.animate(wave.node, "position.y", pos.y + 1, gui.EASING_INOUTBOUNCE, 0.075+0.05*math.random(), i * 0.12112, nil, gui.PLAYBACK_LOOP_PINGPONG)
	end
end

function richtext.create_with_animations(text, font, settings)

	local words, metrics = richtext.create(text, font, settings)
	richtext.wave_letters(words)
	richtext.shake_letters(words)
	richtext.wave_words(words)
	richtext.shake_words(words)

	return words, metrics
end

If you find this useful I encourage you to write your own effect functions! I think the main use of my example is to illustrate a) how to wrap richtext.create in a new function which applies all of your effects, b) how to animate each tagged word and c) how to animate each tagged letter.

Very happy with this extension, thanks @britzl.

9 Likes

Cool, thank you for sharing how you use RichText!

2 Likes

I have several questions regarding RichText.

  1. Is there a way to align text?

On the top my current regular text node with pivot Center. On the bottom Rich Text text with such settings:

	local setup = {
		width = t.t_size_x,
		parent = gui.get_node("Tutorial Text"),
		align = hash("richtext.ALIGN_CENTER"),
		valign = hash("richtext.VALIGN_MIDDLE"),
		position = vmath.vector3(-metrics.width/2 + 50, -300, 0)
	}

TLDR: how to make Rich Text align like regular text node above? Changing align types does not affect it or at least visually.

  1. Is there is a way to decrease distance between words? Like in the regular text node? It’s not a big deal, but I still wonder.
2 Likes

very cool! I added the full use of the entire emoji set to mine… you embed: <emoji=1F47D/> the standard code for each emoji in and it embeds it.

It uses Twitters open source full directory of the 13.X emoji set.

Still tweaking/optimizing it but in my case I’m building a framework for others to make stuff with and having the emoji is an easy way for people to embed small graphics in a standard way into their games especially for like character text bubbles, etc. One thing I’m hoping to do is eventually add standard “tags” like frowning to be able to be used instead of the emoji code but the tags and such aren’t quite as universal and perfectly delineated.

6 Likes

I’d be happy to look into both request if you can create issues on GitHub!

1 Like

Done!

2 Likes

I recently opened an old project, which had the text of the cutscenes truncated in word-by-word, and I found that the spacing between the truncated words is entirely gone.
Here’s what the text should look like, from an exported demo:
image
And here’s what the same text looks like now, from the latest project build:
image
in other cutscenes it seems like there’s no spacing between words whatsoever.

Is there a reason for this?

The width of the space character was calculated manually in old versions of RichText. This was changed recently:

The reason is that in newer versions of Defold the actual width of the space character (as defined in the font) is included in the text metrics.

Which version of Defold are you using?

2 Likes

Ahh that definitely makes sense, I was running an older version (1.2.169). Downloading the new version (1.2.187) resolved it. Thank you!

4 Likes

Hi!

Sorry, beginner’s question. I have included the library, I have fetched the library, I have required richtext.richtext at the beginning of my script, and I am still getting

" /Bootstrap/Splash.gui_script:22: attempt to index global ‘richtext’ (a nil value)
stack traceback:"

When I try “richtext.create(“Single line text with a dash of <color=red>color\nBy default left aligned.”, “Standard”)”

any ideas?

I have recently added it also and that what it looked):

richtext = require("richtext.richtext")
-- other code here--
richtext.create("text", "UI Bold 40")

Maybe this will help

1 Like

required richtext.richtext

?

Really useful and impressive, thanks for making this. Just one question, is richtext.remove(words) required manually or does Defold handle the garbage collection when the parent collection is destroyed?

1 Like