Skip to content

RichText

Overview

Displays rich text by interpreting the input text as HTML, supporting a limited set of tags that prevent any security vulnerabilities. It does this by parsing the input HTML and splitting it into multiple Text children recursively.

NOTE: Encoding HTML entities is required, and malformed HTML is not accepted.

NOTE: Currently it can line-wrap at the start and end of tags. This will probably be fixed in the future to only potentially break on whitespace.

It supports the following markup and features in the string content (in addition to other options as listed in RICH_TEXT_OPTION_KEYS): - <a href="{{placeholder}}"> for links (pass in { links: { placeholder: ACTUAL_HREF } }) - <b> and <strong> for bold text - <i> and <em> for italic text - <sub> and <sup> for subscripts / superscripts - <u> for underlined text - <s> for strikethrough text - <span> tags with a dir="ltr" / dir="rtl" attribute - <br> for explicit line breaks - <node id="id"> for embedding a Node into the text (pass in { nodes: { id: NODE } }), with optional align attribute - Custom Scenery wrapping around arbitrary tags, e.g. <blur>...</blur>, pass in { tags: { blur: ... } }, see below - Unicode bidirectional marks (present in PhET strings) for full RTL support - CSS style="..." attributes, with color and font settings, see https://github.com/phetsims/scenery/issues/807

Examples from the scenery-phet demo:

new RichText( 'RichText can have <b>bold</b> and <i>italic</i> text.' ), new RichText( 'Can do H<sub>2</sub>O (A<sub>sub</sub> and A<sup>sup</sup>), or nesting: x<sup>2<sup>2</sup></sup>' ), new RichText( 'Additionally: <span style="color: blue;">color</span>, <span style="font-size: 30px;">sizes</span>, <span style="font-family: serif;">faces</span>, <s>strikethrough</s>, and <u>underline</u>' ), new RichText( 'These <b><em>can</em> <u><span style="color: red;">be</span> mixed<sup>1</sup></u></b>.' ), new RichText( '\u202aHandles bidirectional text: \u202b<span style="color: #0a0;">مقابض</span> النص ثنائي <b>الاتجاه</b><sub>2</sub>\u202c\u202c' ), new RichText( '\u202b\u062a\u0633\u062a (\u0632\u0628\u0627\u0646)\u202c' ), new RichText( 'HTML entities need to be escaped, like &amp; and &lt;.' ), new RichText( 'Supports <a href="{{phetWebsite}}"><em>links</em> with <b>markup</b></a>, and <a href="{{callback}}">links that call functions</a>.', { links: { phetWebsite: 'https://phet.colorado.edu', callback: function() { console.log( 'Link was clicked' ); } } } ), new RichText( 'Or also <a href="https://phet.colorado.edu">links directly in the string</a>.', { links: true } ), new RichText( 'Links not found <a href="{{bogus}}">are ignored</a> for security.' ), new HBox( { spacing: 30, children: [ new RichText( 'Multi-line text with the<br>separator &lt;br&gt; and <a href="https://phet.colorado.edu">handles<br>links</a&gt; and other <b>tags<br>across lines</b>', { links: true } ), new RichText( 'Supposedly RichText supports line wrapping. Here is a lineWrap of 300, which should probably wrap multiple times here', { lineWrap: 300 } ) ] } )

@author Jonathan Olson <jonathan.olson@colorado.edu>

Class RichText

import { RichText } from 'scenerystack/scenery';

Constructor

new RichText( string : string | number | TReadOnlyProperty<string>, providedOptions? : RichTextOptions )

Instance Methods

setStringProperty( newTarget : TReadOnlyProperty<string> | null ) : this

See documentation for Node.setVisibleProperty, except this is for the text string.

NOTE: Setting the .string after passing a truly read-only Property will fail at runtime. We choose to allow passing in read-only Properties for convenience.

getStringProperty() : TProperty<string>

Like Node.getVisibleProperty, but for the text string. Note this is not the same as the Property provided in setStringProperty. Thus is the nature of TinyForwardingProperty.

getPhetioMouseHitTarget( fromLinking ) : PhetioObject | 'phetioNotSelectable'

RichText supports a "string" selection mode, in which it will map to its stringProperty (if applicable), otherwise is uses the default mouse-hit target from the supertype.

initializePhetioObject( baseOptions : Partial<PhetioObjectOptions>, providedOptions : RichTextOptions )

See documentation and comments in Node.initializePhetioObject

dispose()

Releases references.

setString( string : string | number ) : this

Sets the string displayed by our node.

NOTE: Encoding HTML entities is required, and malformed HTML is not accepted.

@param string - The string to display. If it's a number, it will be cast to a string

getString() : string

Returns the string displayed by our text Node.

setBoundsMethod( method : TextBoundsMethod ) : this

Sets the method that is used to determine bounds from the text. See Text.setBoundsMethod for details

getBoundsMethod() : TextBoundsMethod

Returns the current method to estimate the bounds of the text. See setBoundsMethod() for more information.

setFont( font : Font | string ) : this

Sets the font of our node.

getFont() : Font | string

Returns the current Font

setFill( fill : TPaint ) : this

Sets the fill of our text.

getFill() : TPaint

Returns the current fill.

setStroke( stroke : TPaint ) : this

Sets the stroke of our text.

getStroke() : TPaint

Returns the current stroke.

setLineWidth( lineWidth : number ) : this

Sets the lineWidth of our text.

getLineWidth() : number

Returns the current lineWidth.

setSubScale( subScale : number ) : this

Sets the scale (relative to 1) of any string under subscript (<sub>) elements.

getSubScale() : number

Returns the scale (relative to 1) of any string under subscript (<sub>) elements.

setSubXSpacing( subXSpacing : number ) : this

Sets the horizontal spacing before any subscript (<sub>) elements.

getSubXSpacing() : number

Returns the horizontal spacing before any subscript (<sub>) elements.

setSubYOffset( subYOffset : number ) : this

Sets the adjustment offset to the vertical placement of any subscript (<sub>) elements.

getSubYOffset() : number

Returns the adjustment offset to the vertical placement of any subscript (<sub>) elements.

setSupScale( supScale : number ) : this

Sets the scale (relative to 1) of any string under superscript (<sup>) elements.

getSupScale() : number

Returns the scale (relative to 1) of any string under superscript (<sup>) elements.

setSupXSpacing( supXSpacing : number ) : this

Sets the horizontal spacing before any superscript (<sup>) elements.

getSupXSpacing() : number

Returns the horizontal spacing before any superscript (<sup>) elements.

setSupYOffset( supYOffset : number ) : this

Sets the adjustment offset to the vertical placement of any superscript (<sup>) elements.

getSupYOffset() : number

Returns the adjustment offset to the vertical placement of any superscript (<sup>) elements.

setCapHeightScale( capHeightScale : number ) : this

Sets the expected cap height (baseline to top of capital letters) as a scale of the detected distance from the baseline to the top of the text bounds.

getCapHeightScale() : number

Returns the expected cap height (baseline to top of capital letters) as a scale of the detected distance from the baseline to the top of the text bounds.

setUnderlineLineWidth( underlineLineWidth : number ) : this

Sets the lineWidth of underline lines.

getUnderlineLineWidth() : number

Returns the lineWidth of underline lines.

setUnderlineHeightScale( underlineHeightScale : number ) : this

Sets the underline height adjustment as a proportion of the detected distance from the baseline to the top of the text bounds.

getUnderlineHeightScale() : number

Returns the underline height adjustment as a proportion of the detected distance from the baseline to the top of the text bounds.

setStrikethroughLineWidth( strikethroughLineWidth : number ) : this

Sets the lineWidth of strikethrough lines.

getStrikethroughLineWidth() : number

Returns the lineWidth of strikethrough lines.

setStrikethroughHeightScale( strikethroughHeightScale : number ) : this

Sets the strikethrough height adjustment as a proportion of the detected distance from the baseline to the top of the text bounds.

getStrikethroughHeightScale() : number

Returns the strikethrough height adjustment as a proportion of the detected distance from the baseline to the top of the text bounds.

setLinkFill( linkFill : TPaint ) : this

Sets the color of links. If null, no fill will be overridden.

getLinkFill() : TPaint

Returns the color of links.

setLinkEventsHandled( linkEventsHandled : boolean ) : this

Sets whether link clicks will call event.handle().

getLinkEventsHandled() : boolean

Returns whether link events will be handled.

Returns whether link events will be handled.

setNodes( nodes : Record<string, Node> ) : this

getNodes() : Record<string, Node>

setTags( tags : Record<string, ( node: Node ) => Node> ) : this

getTags() : Record<string, ( node: Node ) => Node>

setReplaceNewlines( replaceNewlines : boolean ) : this

Sets whether newlines are replaced with <br>

getReplaceNewlines() : boolean

setAlign( align : RichTextAlign ) : this

Sets the alignment of text (only relevant if there are multiple lines).

getAlign() : RichTextAlign

Returns the current alignment of the text (only relevant if there are multiple lines).

setLeading( leading : number ) : this

Sets the leading (spacing between lines)

getLeading() : number

Returns the leading (spacing between lines)

setLineWrap( lineWrap : RequiredOption<SelfOptions, 'lineWrap'> ) : this

Sets the line wrap width for the text (or null if none is desired). Lines longer than this length will wrap automatically to the next line.

@param lineWrap - If it's a number, it should be greater than 0.

getLineWrap() : RequiredOption<SelfOptions, 'lineWrap'>

Returns the line wrap width.

mutate( options? : RichTextOptions ) : this

Static Methods

stringWithFont( str : string, font : Font ) : string

Returns a wrapped version of the string with a font specifier that uses the given font object.

NOTE: Does an approximation of some font values (using <b> or <i>), and cannot force the lack of those if it is included in bold/italic exterior tags.

himalayaElementToString( element : HimalayaNode ) : string

Stringifies an HTML subtree defined by the given element.

himalayaElementToAccessibleString( element : HimalayaNode ) : string

Stringifies an HTML subtree defined by the given element, but removing certain tags that we don't need for accessibility (like <a>, <span>, etc.), and adding in tags we do want (see ACCESSIBLE_TAGS).

getAccessibleStringProperty( stringProperty : TReadOnlyProperty<string> ) : TReadOnlyProperty<string>

Transforms a given string with HTML markup into a string suitable for screen readers. Preserves basic styling tags while removing non-accessible markup.

contentToString( content : string, isLTR? : boolean ) : string

Takes the element.content from himalaya, unescapes HTML entities, and applies the proper directional tags.

See https://github.com/phetsims/scenery-phet/issues/315

Static Properties

STRING_PROPERTY_TANDEM_NAME

(readonly)

Text and RichText currently use the same tandem name for their stringProperty.

RichTextIO : IOType

Type RichTextAlign

import type { RichTextAlign } from 'scenerystack/scenery';

"left" | "center" | "right"

Type RichTextHref

import type { RichTextHref } from 'scenerystack/scenery';

( () => void ) | string

import type { RichTextLinks } from 'scenerystack/scenery';

RichTextLinksObject | true

Type RichTextOptions

import type { RichTextOptions } from 'scenerystack/scenery';
  • boundsMethod?: TextBoundsMethod
    Sets how bounds are determined for text
  • font?: Font | string
    Sets the font for the text
  • fill?: TPaint
    Sets the fill of the text
  • stroke?: TPaint
    Sets the stroke around the text
  • lineWidth?: number
    Sets the lineWidth around the text
  • subScale?: number
    Sets the scale of any subscript elements
  • subXSpacing?: number
    Sets horizontal spacing before any subscript elements
  • subYOffset?: number
    Sets vertical offset for any subscript elements
  • supScale?: number
    Sets the scale for any superscript elements
  • supXSpacing?: number
    Sets the horizontal offset before any superscript elements
  • supYOffset?: number
    Sets the vertical offset for any superscript elements
  • capHeightScale?: number
    Sets the expected cap height cap height (baseline to top of capital letters) as a scale
  • underlineLineWidth?: number
    Sets the line width for underlines
  • underlineHeightScale?: number
    Sets the underline height as a scale relative to text bounds height
  • strikethroughLineWidth?: number
    Sets line width for strikethrough
  • strikethroughHeightScale?: number
    Sets height of strikethrough as a scale relative to text bounds height
  • linkFill?: TPaint
    Sets the fill for links within the text
  • linkEventsHandled?: boolean
    Sets whether link clicks will call event.handle()
  • links?: RichTextLinks
    Sets the map of href placeholder => actual href/callback used for links. However, if set to true ({boolean}) as a full object, links in the string will not be mapped, but will be directly added.

For instance, the default is to map hrefs for security purposes:

new RichText( '<a href="{{alink}}">content</a>', { links: { alink: 'https://phet.colorado.edu' } } );

But links with an href not matching will be ignored. This can be avoided by passing links: true to directly embed links:

new RichText( '<a href="https://phet.colorado.edu">content</a&gt;', { links: true } );

Callbacks (instead of a URL) are also supported, e.g.:

new RichText( '<a href="{{acallback}}">content</a>', { links: { acallback: function() { console.log( 'clicked' ) } } } );

See https://github.com/phetsims/scenery-phet/issues/316 for more information. - nodes?: Record<string, Node>
A map of string => Node, where &lt;node id="string"/&gt; will get replaced by the given Node (DAG supported)

For example:

new RichText( 'This is a <node id="test"/>', { nodes: { test: new Text( 'Node' ) } }

Alignment is also supported, with the align attribute (center/top/bottom/origin). This alignment is in relation to the current text/font size in the HTML where the <node> tag is placed. An example:

new RichText( 'This is a <node id="test" align="top"/>', { nodes: { test: new Text( 'Node' ) } } NOTE: When alignment isn't supplied, origin is used as a default. Origin means "y=0 is placed at the baseline of the text". - tags?: Record<string, ( node: Node ) => Node>
A map of string => Node replacement function ( node: Node ) => Node, where RichText will "render" line content inside the tag, and then pass it to the function to replace the content of the Node. Each tag function should return a new Node.

For example:

new RichText( 'There is &lt;blue&gt;blue text&lt;/blue&gt; and &lt;blur&gt;blurry text&lt;/blur&gt;', {
  tags: {
    blur: node =&gt; new Node( { children: [ node ], filters: [ new GaussianBlur( 1.5 ) ] } ),
    blue: node =&gt; new Node( { children: [ Rectangle.bounds( node.bounds, { fill: '#88f' } ), node ] } )
  }
} )

NOTE: This does NOT affect the layout or bounds of the resulting content, since the layout is done BEFORE this wrapping is done. If we ever need the wrapping to be done BEFORE, the wrapping will need to be called many times when line-wrapping is done.

Here is a more in-depth example with many elements:

new RichText( 'This is a test with &lt;blue&gt;blue&lt;/blue&gt; text being &lt;blue&gt;wrapped in a blue color that should support line wrap&lt;/blue&gt;. Text can also be &lt;translucent&gt;more transparent&lt;/translucent&gt;, or can be &lt;blur&gt;blurred&lt;/blur&gt; or have &lt;shadow&gt;drop shadow&lt;/shadow&gt;. Tags can be &lt;blue&gt;repeated or &lt;blur&gt;nested&lt;/blur&gt;&lt;/blue&gt;', {
  lineWrap: 300,
  tags: {
    blur: node =&gt; {
      return new Node( {
        children: [ node ],
        filters: [ new GaussianBlur( 1.5 ) ]
      } );
    },
    shadow: node =&gt; {
      return new Node( {
        children: [ node ],
        filters: [ new DropShadow( new Vector2( 2, 1 ), 2, 'red' ) ]
      } );
    },
    translucent: node =&gt; {
      return new Node( {
        children: [ node ],
        opacity: 0.5
      } );
    },
    blue: node =&gt; {
      return new Node( {
        children: [
          Rectangle.bounds( node.bounds.dilated( 1 ), { fill: '#88f' } ),
          node
        ]
      } );
    }
  }
} )
  • replaceNewlines?: boolean
    Will replace newlines (\n) with <br>, similar to the old MultiLineText (defaults to false)
  • align?: RichTextAlign
    Sets text alignment if there are multiple lines
  • leading?: number
    Sets the spacing between lines if there are multiple lines
  • lineWrap?: number | "stretch" | null
    Sets width of text before creating a new line. When set to 'stretch' controls whether its WidthSizable. In this case it will use the preferred width to determine the line wrap.
  • stringProperty?: TReadOnlyProperty<string> | null
    Sets forwarding of the stringProperty, see setStringProperty() for more documentation
  • stringPropertyOptions?: PropertyOptions<string>
  • string?: string | number
    Sets the string to be displayed by this Node
  • & NodeOptions

Source Code

See the source for RichText.ts in the scenery repository.