Rich Text Tag Syntax

Texturge Beta binds animations to text content through XML-like tags. Tags are parsed by FTagParser into an FTagNode tree, then FRenderTreeBuilder builds a render tree (FRenderNode) with UTextAnimationDataAsset for UAnimatedRichTextBlock.

NOTE

This is not XML — closing tags uniformly use </> (closes the most recently opened tag); no need to repeat the tag name.

Tag Structure

Animation Tags

<anim id="wave">content</>
  • Opening tag: <anim id="..."> — tag name is anim (case-insensitive), matched against FAnimationEntry::TagName in UTextAnimationDataAsset::Entries[] via the id attribute (also case-insensitive)
  • Closing tag: </> — closes the most recently opened tag, no tag name needed
  • An <anim> tag matching no Entry is processed as StyleLayer with a [RenderTreeBuilder] <anim id="..."> does not match any Entry warning
  • Content can be plain text or other nested tags

Self-Closing Tags

<img src="icon" />

Self-closing tags with no children are classified as Decorator (decorator nodes, e.g. embedded images).

Syntax Rules

  1. Opening tags use angle brackets <>: <tagname attr="value">
  2. Closing tags are uniformly </> — each closes the most recently opened tag (write multiple </> from inside out when nested, or use explicit closing tags for style tags)
  3. Self-closing tags end with />
  4. Tag names consist of [A-Za-z0-9_-], max 32 characters
  5. Attributes support both quoted and unquoted forms; attribute values support escaping (&lt; &gt; &amp; &quot; &apos;)
  6. Unclosed tags are auto-closed at parse end (AutoCloseRemaining, one UE_LOG warning each)

Nesting Examples

<anim id="wave"><color style="red">colorful waving text</color></>
<anim id="typewriter">typewriter text with<anim id="shake">some shake</></>
  • <color>...</color> is a style tag using an explicit closing tag (required by the engine’s rich text style system)
  • <anim> tags close with </> (the inner </> closes shake first, the outer </> then closes typewriter)

Parsing Pipeline

Step 1: FTagParser

FTagParser::Parse(const FString& InRichText, FString& OutPlainText) parses the raw text into an FTagNode tree:

  1. Scans character by character; on <, enters tag parsing mode
  2. Recognizes tag names (ParseTagName) and attributes (ParseAttributes)
  3. Invalid tags do not error — < is output as plain text
  4. </> closes the most recently opened tag; </name> searches the stack from the top for a matching tag name
  5. AutoCloseRemaining auto-closes unpaired tags (one UE_LOG warning each)
  6. After tag stripping + escape decoding, outputs OutPlainText (plain text, with code point index baseline)

Step 2: FRenderTreeBuilder

FRenderTreeBuilder::Build(TSharedPtr<FTagNode> ParseRoot, const FString& PlainText, UTextAnimationDataAsset* AnimationData) converts the parse tree into a render tree:

  1. DFS traversal of the FTagNode tree, auto-inserting TextContent nodes between siblings (carrying plain-text Ranges)
  2. ClassifyNode classifies each tag node (see below)
  3. AnimationLayer nodes resolve EntryIndex via FindEntryIndex (matched by id in Entries[])
  4. Outputs the TSharedPtr<FRenderNode> root node

Serialization helpers:

FunctionDescription
SerializeToDisplayText(FRenderNode)render tree → display text: AnimationLayer skips tags and outputs children, StyleLayer outputs fully-wrapped tags, Decorator outputs self-closing tags
SerializeSubstring(FRenderNode, int32 MaxCharIndex)slices the revealed portion by glyph index with style wrapping, auto-unescaping
images/tag-parse-pipeline.png — Tag parsing pipeline: raw rich text → FTagParser parses into FTagNode tree → FRenderTreeBuilder classifies with DataAsset into FRenderNode render tree, with RangeBegin/RangeEnd plain-text indices and EntryIndex matching annotated

Node Classification

FRenderTreeBuilder::ClassifyNode determines in the following order (ERenderNodeType):

Enum valueConditionDescription
AnimationLayertag name anim (case-insensitive) and id attribute matches an Entryanimation layer, EntryIndex points into Entries[]
Decoratorself-closing (/>) with no childrendecorator node (e.g. image placeholder)
StyleLayerall other tagsstyle layer, delegated to the UE5 rich text style system
TextContentplain-text leaf (non-tag node)
Unknownempty tag name / invalid nodeexceptional state

UTextAnimationDataAsset

The core mapping asset mapping animation ids to animation Blueprints:

PropertyTypeDescription
EntriesTArray<FAnimationEntry>tag → animation Blueprint mapping array

FAnimationEntry Fields

FieldTypeDescription
TagNameFNamematching animation id (compared with <anim id="...">, case-insensitive)
TypeTObjectPtr<UTextAnimationBlueprint>animation Blueprint asset reference
ParameterOverridesTMap<FName, float>Float parameter overrides
IntParameterOverridesTMap<FName, int32>Int parameter overrides
BoolParameterOverridesTMap<FName, bool>Bool parameter overrides
VectorParameterOverridesTMap<FName, FVector>Vector parameter overrides
ColorParameterOverridesTMap<FName, FLinearColor>Color parameter overrides
Vector2DParameterOverridesTMap<FName, FVector2D>Vector2D parameter overrides
bHideFirstFrameboolentries not yet executed are transparent ahead during sequential playback

The 6 override maps correspond one-to-one with the UAnimParameterOverrides type structure, written into the temporary instance via ApplyAnimParamOverridesToInstance<FAnimationEntry> during baking.

UAnimatedRichTextBlock

UAnimatedRichTextBlock (inherits URichTextBlock) drives rich text animation:

  1. The widget holds an AnimationData (UTextAnimationDataAsset*) property
  2. On text change, the private FRichTextMarshaller internally completes parsing and render-tree construction
  3. Each AnimationLayer node instantiates the corresponding animation entry; UTextAnimator orchestrates multi-entry playback (bSequentialPlayback sequential / simultaneous)
  4. Style tags (StyleLayer) cooperate with the engine URichTextBlock’s style sets, decorators, and data tables
// C++ setup flow
AnimatedRichTextBlock->AnimationData = MyDataAsset;
AnimatedRichTextBlock->SetText(FText::FromString(
    TEXT("Hello <anim id=\"wave\">World</>!")));

Nesting Rules

  1. Nesting must pair legally (inner tags’ closing tags appear before outer tags’)
  2. anim tags can nest other style tags (<anim id="wave"><color ...>...</color></>)
  3. Unmatched <anim> is processed as StyleLayer (tag preserved, text not animated)

NOTE

There is no hard nesting depth limit, but overly deep structures increase FTagParser parsing and render-tree build cost; keep within 4 levels.

<anim id="wave"><color style="red">colorful waving text</color></>
<anim id="typewriter">typewriter text with<anim id="shake">some shake</></>
images/decorator-injection.png — Decorator node diagram: Decorator leaf (self-closing tags like ) and TextContent leaves side by side in the render tree, AnimationLayer nodes wrapping their children