Home

Awesome

pixi-tagged-text

NPM

TaggedText is a multi-style text component for pixi.js that supports multiple TextStyles using HTML-like tags. Includes many additional features not found in PIXI.Text such as embedded images (sprites), underlines, justified layout, and more.

Inspired by the original pixi-multistyle-text

Usage

// constructor
new TaggedText(text, styles, options);

All parameters are optional.

Text & tags

Text

The text can be any UTF-8 string.

You can get the text with tags stripped out with the .untaggedText implicit getter.

Tags

Styles

styles is a plain object containing one or more keys. Each key is the name of a tag to style. 'Global' styles that apply everywhere can be set under the default key. Note, some values such as wordWrapWidth are ignored if they are included in any styles other than default.

The style objects are modified versions (supersets) of PIXI.TextStyle (referred to as TextStyleExtended in the source code). In addition to the properties allowed in TextStyle, the following extended properties are added.

(By the way, there is an excellent TextStyle visual editor that you can use to preview how your style will appear. The style objects generated can be used in this component.)

Additionally, the following changes have been made to the default style values:

'Default' default styles

Some styles (fontSize, color, etc.) are set by default when you call new TaggedText() but they can all be overridden. The most important default styles are:

{
  align: "left",
  valign: "baseline",
  wordWrap: true,
  wordWrapWidth: 500,
  fill: 0x000000,
}

To get a complete list, you can view the static property TaggedText.defaultStyles or look in the source code for DEFAULT_STYLES

Options

The third parameter in the TaggedText constructor is a set of options.

To see a list of the default options, you can view the static property TaggedText.defaultOptions or look in the source code for DEFAULT_OPTIONS

A note about the component architecture

TaggedText allows you to control text with multiple styles and embedded images as though it were a single Pixi component. It was inspired by pixi-multistyle-text but is structurally very different.

pixi-multistyle-text composes bitmap snapshots of text objects into a single canvas. Conversely, pixi-tagged-text creates a separate PIXI.Text component for each word, or word segment. Using multiple Text components allows developers to have control over individual words or even characters for the purposes of animation or other effects. It also makes embedding sprites into the layout easier. Cosmetically, they're very similar, however, the overhead of creating multiple Text objects is much larger potentially making TaggedText a heavier slower component.

Another similar component is @pixi/text-html which renders a bitmap from an HTML element using the browser's native rendering. While pixi-tagged-text should theoretically render more consistently cross-browsers, this could be a good option if pixel perfection and cross-browser support is not a concern.

Child DisplayObjects

TaggedText generates multiple display objects when it renders the text with draw(). Developers can access these children if desired for example to add additional interactivity or to animated individual elements.

Please note that by default, these are recreated every time text or style properties change (technically, whenever draw() is called). Manipulating the children directly may cause your view to become out of sync with the text and styles properties.

These properties are available:

Life-cycle & optional updates

In order to maximize performance of TaggedText, it helps to understand how it renders the input.

  1. Constructor - creating the component is the first step. You can set text, styles, and options in the constructor.
  2. update() - Update generates a list of plain JS object tokens that hold information on what type of text to create and where to position it. The tokens contain all the information you need to draw the text and are saved as the instance member tokens but also returned by the update() method. Aside from decoupling from the render code, this allows us to write tests to verify every step of the lexing, styling and layout independently without drawing anything at all. By default, this is called every time the text or style definitions are changed (e.g. setTagStyles(), setText()). This is a fairly expensive process but usually faster than draw().
  3. draw() - Draw creates the child objects based on the data generated by update(). It clears any existing children (if needed) then recreates and positions them. This is probably the costliest method in the life-cycle. By default, this is called automatically by update().
  4. Of course, you won't see anything on your screen until your component is added to a visible PIXI container that's part of the stage in a pixi app.

The methods that normally trigger an update are:

The methods that normally trigger a draw:

Please note that direct changes to styles or other objects will not trigger an automatic update unless you use one of the above methods. For example:

const t = new TaggedText("<big>Big text</big>", { big: { fontSize: 25 } }); // renders "Big text" at 25px

t.getStyleForTag("big").fontSize = 100; // The change to the style wasn't detected. It still renders "Big text" at 25px

t.update(); // now it renders correctly.

t.textFields[0].visible = false; // Makes the word "Big" disappear.

t.draw(); // recreates the text fields restoring "Big"

skipUpdates & skipDraw

If performance is becoming an issue, you can use the skipUpdates and skipDraw flags in the options object with new TaggedText() to disable automatic updates and automatic drawing (more on that below). This gives you control over when the update() or draw() function will be called. However, the component can become out of sync with what you see on your screen so use this with caution.

Several other individual functions, such as setText() also give you the option to skipUpdate on an as needed basis.

// Create a new TaggedText but disable automatic updates and draws.
const t = new TaggedText("", {}, {skipUpdate: true, skipDraw: true});
const words = ["lorem", "ipsum", ... ];
// add words until the length of text is > 500 characters.
while (t.untaggedText.length <= 500) {
  t.text += words[i];
}

// Normally, update() will draw() also, but we've disabled that.
// t.tokens will be up dated to match the new text but it will not appear on the screen.
t.update();
t.textContainer.length; // 0 - text fields never got created.

// Manually call draw to generate the PIXI.Text fields
t.draw();
t.textContainer.length; // This will now contain all the PIXI.Text objects created by draw.

Destructor

Like with other Pixi components, when you're ready to remove your TaggedText object from the stage and make it ready for garbage collection, use the .destroy() method. Unlike some other Pixi components, TaggedText will automatically destroy its child objects, such as fragments of text and debug drawings (with the exception of imgMap, see below). Child objects can still be retained by passing options to the destructor, e.g. .destroy(false).

Destroying imgMap sources

destroy() will not by default destroy the textures used for the imgMap since these are often shared between multiple TaggedText objects.

To destroy the source textures in imgMap use the method .destroyImgMap(). Please note that this must be called before destroying the TaggedText instance and will throw an error if you try to call it on an already-destroyed instance.

Contributing

If you'd like to contribute, a great place to start is to log an issue. If you are interested in bug-fixing, please take a look at the issues list and notify one of the owners that you're interested in working on it. Please make sure to test the code you write! If you're not sure how, just ask for help!

Build instructions

yarn install
yarn build

The node-canvas package, used for testing, has some additional dependencies. For Mac users, there's a homebrew Brewfile that you can install using brew bundle. For other users, see the instructions for the package. On Apple Silicon, this issue was helpful with troubleshooting.

Yarn should automatically install peer dependencies (including the very important pixi.js) but in my experience, you may have to run yarn install again after adding any additional packages.

VSCode Users

If you're using the vscode-jest extension, you may need some additional packages to get the tests to run in your IDE. If you're on a Mac you can use brew bundle install to install these packages.

Demo

You can view examples using the command:

yarn demo

This will start a simple HTTP server running locally on port 8080. Navigate to http://localhost:8080/demo

Dependencies

The build process is slightly complex and some of the build dependencies are at the max major version that doesn't break. These depdencies seem to cause issues at certain versions...

packagemax version
jest, @types/jest, ts-jest26
emoji-regex9

License

MIT, see LICENSE.md for details.