https://github.com/britzl/defold-richtext
Defold-RichText is a system to create styled text based on an HTML inspired markup language
https://github.com/britzl/defold-richtext
defold defold-library
Last synced: 11 months ago
JSON representation
Defold-RichText is a system to create styled text based on an HTML inspired markup language
- Host: GitHub
- URL: https://github.com/britzl/defold-richtext
- Owner: britzl
- License: mit
- Created: 2018-02-26T08:04:43.000Z (about 8 years ago)
- Default Branch: master
- Last Pushed: 2024-10-03T12:48:21.000Z (over 1 year ago)
- Last Synced: 2025-03-31T06:04:03.419Z (12 months ago)
- Topics: defold, defold-library
- Language: Lua
- Homepage:
- Size: 6.36 MB
- Stars: 75
- Watchers: 4
- Forks: 12
- Open Issues: 9
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
- awesome-defold - RichText
README
# Defold-RichText
Defold-RichText is a system to create styled text based on an HTML inspired markup language.
# Installation
You can use RichText in your own project by adding this project as a [Defold library dependency](http://www.defold.com/manuals/libraries/). Open your game.project file and in the dependencies field under project add:
https://github.com/britzl/defold-richtext/archive/master.zip
Or point to the ZIP file of a [specific release](https://github.com/britzl/defold-richtext/releases).
# Markup format
The markup format is HTML inspired, but not intended to be fully compatible with standard HTML. Just like in HTML the idea is that sections of text can be enclosed in matching start and end tags:
This is a bold statement!
This is a bold statement!
## Nested elements
Nested elements are supported. Use this to give a section of text a combination of styles:
This is a bold italic statement!
This is a bold italic statement!
## Supported tags
The following tags are supported:
| Tag | Description | Example |
|---------|------------------------------------------------|---------------------------------------------|
| a | Create a "hyperlink" that generates a message | `Foobar` |
| | when clicked (see `richtext.on_click`) | |
| b | The text should be bold | `Foobar` |
| br | Insert a line break (see notes on linebreak) | `
` |
| color | Change text color | `Foobar` |
| | | `Foobar` |
| | | `Foobar` |
| | | `Foobar` |
| shadow | Change text shadow | `Foobar` |
| | | `Foobar` |
| | | `Foobar` |
| | | `Foobar` |
| outline | Change text shadow | `Foobar` |
| | | `Foobar` |
| | | `Foobar` |
| | | `Foobar` |
| font | Change font | `Foobar` |
| i | The text should be italic | `Foobar` |
| img | Display image | `
` |
| | Display image in fixed square | `
` |
| | Display image in fixed rectangle | `
` |
| nobr | Prevent the text from breaking | `Words inside tag won't break` |
| size | Change text size, relative to default size | `Twice as large` |
| spine | Display spine model | `` |
| p | Adds extra spacing below the line where this | `
A paragraph
\nSome other text` |
| | tag ends. Adds a newline before its opening | `This has 2.5 lines of spacing
` |
| | tag if it doesn't already exist. | |
| repeat | Repeat the text enclosed by the tag | `Echo five times` |
### Line breaks
Note that there is no need for the HTML `
` tag since line breaks (i.e. `\n`) are parsed and presented by the system. Note that a single `
` (ie without a closing or empty tag) isn't supported (even though most browsers accept it).
### Named colors
The following named colors are supported:
| Name | Hex value | Swatch |
|-----------|:-----------:|:---------------------------------:|
| aqua | `#00ffffff` |  |
| black | `#000000ff` |  |
| blue | `#0000ffff` |  |
| brown | `#a52a2aff` |  |
| cyan | `#00ffffff` |  |
| darkblue | `#0000a0ff` |  |
| fuchsia | `#ff00ffff` |  |
| green | `#008000ff` |  |
| grey | `#808080ff` |  |
| lightblue | `#add8e6ff` |  |
| lime | `#00ff00ff` |  |
| magenta | `#ff00ffff` |  |
| maroon | `#800000ff` |  |
| navy | `#000080ff` |  |
| olive | `#808000ff` |  |
| orange | `#ffa500ff` |  |
| purple | `#800080ff` |  |
| red | `#ff0000ff` |  |
| silver | `#c0c0c0ff` |  |
| teal | `#008080ff` |  |
| white | `#ffffffff` |  |
| yellow | `#ffff00ff` |  |
You can add your own named colors as well:
```lua
local color = require "richtext.color"
color.add("blood", "#8A0303")
color.add("asparagus", "#87a96b")
color.add("denim", "#1560bd")
```
## HTML entities
The RichText library has limited support for HTML entities (reserved characters):
* `>` converts into `>`
* `<` converts into `<`
* ` ` converts into a non-breaking space between two words, ie `100 km` becomes `100 km` with `100` and `km` acting as a single word with a space between.
* `&zwsp;` converts into a zero-width space character (`\226\128\139`). This is not an official HTML entity but it is provided as a convenient way to insert one into text.
# Usage
The RichText library will create gui text nodes representing the markup in the text passed to the library. It will search for tags and split the entire text into words, where each word contains additional meta-data that is used to create and configure text nodes. This means that the library will create as many text nodes as there are words in the text.
## Basic example
A simple example with some color and linebreaks:
richtext.create("Single line text with a dash of color\nBy default left aligned.", "Roboto-Regular")

## Advanced example
A more complex example with different fonts, colors, inline images and automatic linebreaks:
```lua
local settings = {
fonts = {
Roboto = {
regular = hash("Roboto-Regular"),
italic = hash("Roboto-Italic"),
bold = hash("Roboto-Bold"),
bold_italic = hash("Roboto-BoldItalic"),
},
Nanum = {
regular = hash("Nanum-Regular"),
},
},
width = 400,
parent = gui.get_node("bg"),
color = vmath.vector4(0.95, 0.95, 1.0, 1.0),
shadow = vmath.vector4(0.0, 0.0, 0.0, 1.0),
}
local text = "RichTextLorem ipsum
dolor sit amet, consectetur adipiscing elit. Nunc tincidunt mattis libero non viverra.\n\nNullam ornare
accumsan rhoncus.\n\nNunc placerat nibh a purus auctor, id scelerisque massa rutrum."
richtext.create(text, "Roboto", settings)
```

## Custom tags
Custom tags can be accessed and used in two ways:
* Use `richtext.tagged(words, tag)` to get all words with a certain tag, even a custom one
* Use `tags.register(tag, fn)` to register a custom tag handler:
```lua
tags.register("boldred", function(params, settings)
tags.apply("color", "red", settings)
tags.apply("b", nil, settings)
end)
richtext.create("I am bold and red!", "Roboto", settings)
```
# API
### richtext.create(text, font, settings)
Creates rich text gui nodes from a text containing markup.
**PARAMETERS**
* `text` (string) - The text to create rich text from
* `font` (string) - Name of default font. Must match the name of a font in the gui scene or a key in the `fonts` table in `settings` (see below).
* `settings` (table) - Optional table containing settings
The `settings` table can contain the following values:
* `width` (number) - Maximum width of a line of text. Omit this value to present the entire text on a single line
* `position` (vector3) - The position that the text aligns to (refer to the `align` setting for details). Defaults to 0,0 if not specified.
* `size` (number) - Default size of any created node. Same as using a `` tag in the text. Defaults to 1 if not specified.
* `parent` (node) - GUI nodes will be attached to this node if specified.
* `fonts` (table) - Table with fonts, keyed on font name. See separate section below. If no `fonts` table is provided the font used will be the one passed to `richtext.create()`.
* `layers` (table) - Table with font, texture and spine scene mappings to layer names. See separate section below.
* `color` (vector4) - The default color of text. Will be white if not specified.
* `shadow` (vector4) - The default shadow color of text. Will be transparent if not specified.
* `outline` (vector4) - The default outline color of text. Will be transparent if not specified.
* `align` (hash) - One of `richtext.ALIGN_LEFT`, `richtext.ALIGN_CENTER`, `richtext.ALIGN_RIGHT` and `richtext.ALIGN_JUSTIFY`. Defaults to `richtext.ALIGN_LEFT`. Defines how the words of a line of text are positioned in relation the provided `position`. Width must be specified for `richtext.ALIGN_JUSTIFY`.
* `valign` (hash) - One of `richtext.VALIGN_TOP`, `richtext.VALIGN_MIDDLE` and `richtext.VALIGN_BOTTOM`. Defaults to `richtext.VALIGN_TOP`. Defines how the words of a line of text are positioned vertically on the line.
* `line_spacing` (number) - Value to multiply line height with. Set to a value lower than 1.0 to reduce space between lines and a value higher than 1.0 to increase space between lines. Defaults to 1.0.
* `paragraph_spacing` (number) - Space to leave after lines with where `
` tags end. Relative to the line height. Defaults to 0.5 lines.
* `image_pixel_grid_snap` (boolean) - Set to true to position image on full pixels (positions rounded to nearest integer) to avoid effects of anti-aliasing. Defaults to false.
* `combine_words` (boolean) - Set to true to combine words with the same style on a line into a single node. This is useful for very long texts where the maximum number of nodes would exceed the limit set in the GUI. The disadvantage is that any per word operations will not work since the words have been combined.
* `dryrun` (boolean) - Set to true to perform a complete layout of the words without creating gui nodes.
The `fonts` table should have the following format:
```lua
name (string) = {
regular (string) = font (hash),
italic (string) = font (hash),
bold (string) = font (hash),
bold_italic (string) = font (hash),
},
name (string) = {
...
},
```
Where `name` is the name specified in a `` tag and the `font` for each of `regular`, `italic`, `bold` and `bold_italic` should correspond to the name of a font added to a .gui scene.
The `layers` table should map fonts, textures and spine scenes to layer names. It should have the following format:
```lua
fonts = {
font (hash) = layer (hash),
...
font (hash) = layer (hash),
},
images = {
texture (hash) = layer (hash),
...
texture (hash) = layer (hash),
},
spinescenes = {
spinescene (hash) = layer (hash),
...
spinescene (hash) = layer (hash),
}
```
Where `layer` is the name of a layer in the .gui scene, `font` is the value returned from a call to `gui.get_font(node)`, `texture` is the value returned from a call to `gui.get_texture(node)` and finally `spinescene` is the value returned from a call to `gui.get_spine_scene(node)`.
**RETURNS**
* `words` (table) - A table with all the words that the text has been broken up into. Each word is represented by a table with keys such as `node`, `tags`, `text` etc
* `metrics` (table) - A table with text metrics.
The `metrics` table contains the following values:
* `width` (number) - Width of the text
* `height` (number) - Height of the text
* `char_count` (number) - Number of characters in the text including whitespace
* `img_count` (number) - Number of images in the text
* `spine_count` (number) - Number of spine models in the text
A word in the `words` table contains the following values:
* `size` (number) - Size of the word
* `color` (vector4) - Color of the word
* `shadow` (vector4) - Shadow color of the word
* `outline` (vector4) - Outline color of the word
* `node` (node) - The GUI node representing the word
* `metrics` (table) - Word metrics (`width`, `height`, `total_width` and additionally for text nodes: `max_descent`, `max_ascent`)
* `font` (string) - Font name
* `text` (string) - Word text (empty for non text nodes)
### richtext.tagged(words, tag)
Get all words with a specific tag.
**PARAMETERS**
* `words` (table) - The words to search, as received by a call to `richtext.create()`.
* `tag` (string) - Name of the tag to search for. Pass `nil` to get all words without tags.
**RETURNS**
* `words` (table) - A table with all the words that matches the tag.
### richtext.truncate(words, length, [options])
Truncate a text down to a specific length. This function has two modes of operation: 1) It can truncate the text on a per word basis or 2) on a per character/image basis. The function will disable nodes that shouldn't be visible and in the case of truncating on a per character basis also update the text in nodes that should be partially visible. The text metrics of a truncated word will be updated.
**PARAMETERS**
* `words` (table) - The words to truncate, as received by a call to `richtext.create()`.
* `length` (number) - Maximum number of words or characters and images to show.
* `options` (table) - Optional table with options when truncating.
Available options in the `option` table are:
* `words` (boolean) - True if the function should truncate down to the nearest full word instead of truncating to a partial word.
**RETURNS**
* `word` (table) - The last visible word.
### richtext.length(text)
Get the length of a text ignoring any tags except image and spine tags which are treated as having a length of 1.
**PARAMETERS**
* `text` (string|table) - The text to measure. This can either be a string or a list of words, as received by a call to `richtext.create()`.
**RETURNS**
* `length` (number) - The length of the provided text.
### richtext.characters(word)
Split a word into it's characters, including the creation of the gui nodes. Each of the characters will be given the same attributes as the word, and they will be positioned correctly within the word.
**PARAMETERS**
* `word` (table) - The word to split, as received from a call to `richtext.create()` or `richtext.tagged()`.
**RETURNS**
* `characters` (table) - The individual characters of the word.
### richtext.on_click(words, action)
Call this function when a click/touch has been detected and your text contains words with an `a` tag. These words act as "hyperlinks" and will generate a message when clicked. The generated message will contain the following values:
* `node_id` (hash) - Id of the node which was clicked
* `text` (string) - Text of the clicked word
* `screen_x` (number) - Horizontal position of the click
* `screen_y` (number) - Vertical position of the click
* `tags` (table) - All tags on the clicked word
**PARAMETERS**
* `words` (table) - A list of words, as received from `richtext.create()` or `richtext.tagged()`.
* `action` (table) - The `action` table from the `on_input` lifecycle function.
**RETURNS**
* `consumed` (boolean) - True if any word was clicked.
### richtext.remove(words)
Removes all gui text nodes created by `richtext.create()`.
**PARAMETERS**
* `words` (table) - Table of words, as received from a call to `richtext.create()`.
### richtext.plaintext(words)
Returns the words created by `richtext.create()` as a plain text string without any formatting or tags. Linebreaks are included in the returned string.
**PARAMETERS**
* `words` (table) - Table of words, as received from a call to `richtext.create()`.
**RETURNS**
* `plaintext` (string) - Plain text version of the words.
### richtext.ALIGN_LEFT
Left-align text. The words of a line starts at the specified position (see `richtext.create` settings above).
### richtext.ALIGN_CENTER
Center text. The words of a line are centered on the specified position (see `richtext.create` settings above).
### richtext.ALIGN_RIGHT
Right-align text. The words of a line ends at the specified position (see `richtext.create` settings above).
### richtext.ALIGN_JUSTIFY
Justify text. The words of a line start at the specified position and are spaced such that the last character of the last word ends at the right edge of the line bounds (see `richtext.create` settings above).
### richtext.VALIGN_TOP
Vertically align the words on a line so that the top of the words on the line align.
### richtext.VALIGN_MIDDLE
Vertically align the words on a line so that the middle of the words on the line align.
### richtext.VALIGN_BOTTOM
Vertically align the words on a line so that the bottom of the words on the line align.
# Credits
* Smiley icons in example app from [Flaticons](https://www.flaticon.com/packs/smileys-3)
* UTF8 decoding from [utf8.lua](https://github.com/Stepets/utf8.lua)