A text-display library centered around a label that prints over time, with both effects and styles.
In other words, this brings more features to text rendering in libGDX.
What does this look like? A little something like this...
Or perhaps like this...
If you'd rather watch a video than read this text, Raymond "raeleus" Buckley made a video covering most of TextraTypist! It covers some things this file doesn't, such as usage of Skin Composer, so it's a good watch regardless.
There's a "normal" label here in the form of TextraLabel, which acts almost exactly like Label in scene2d.ui, but allows the styles covered below. A lot of usage may prefer TypingLabel, though!
TypingLabel is a fairly normal scene2d.ui widget, and extends TextraLabel. However, it puts letters up on the screen one at a time, unless it is told to skip ahead. This is a nostalgic effect found in many older text-heavy games, and it looks like a typewriter is putting up each letter at some slower-than-instantaneous rate.
Yes, it has more than the typewriter mode! Text can hang above and then drop into place. It can jump up and down in a long wave. It can waver and shudder, as if it is sick. It can blink in different colors, move in a gradient smoothly between two colors, or go across a whole rainbow. Lots of options; lots of fun. Effects are almost the same as in typing-label, but there have been some changes. You can check the TextraTypist wiki for more information.
As of 0.10.0, there are many new effects. Jolt, Spiral, Spin, Crowd, Shrink, Emerge, Heartbeat, Carousel, Squash, Scale, Rotate, Attention, Highlight, Link, Trigger, Stylist, Cannon, Ocean, Sputter, and Instant are all new to TextraTypist (not in typing-label). You can see usage instructions and sample GIFs at the TextraTypist wiki's Tokens page. Most of these effects make use of the smooth scaling and rotation options that effects can use starting in TextraTypist 0.5.1 . Some make use of mouse tracking, new in 0.7.0, such as how Link only responds to a click on a range of text.
You may want to create TypingLabel
s even where you don't need the typing effect, because TextraLabel
doesn't handle
any effects. You can call skipToTheEnd()
on a TypingLabel or (in 0.7.0 and up) on some other classes to allow a
TypingLabel to be used for still text with effects.
Various standard tokens are also present, and these can manipulate the typing effect, variable replacement, and other useful things:
{WAIT=f}
causes the typing effect to pause and wait forf
seconds, as a float.{SPEED=f}
changes the time it takes to type a typical glyph, from a default of0.035
tof
.{SLOWER}
makes all glyphs take 2x as long to type.{SLOW}
makes all glyphs take 1.5x as long to type.{NORMAL}
makes all glyphs take the normal 1x as long to type.{FAST}
makes all glyphs take 0.5x as long to type.{FASTER}
makes all glyphs take 0.25x as long to type.{NATURAL=f}
makes glyphs take randomly more or less time to type, but otherwise is the same as{SPEED=f}
.{COLOR=s}
changes the color of text; this has lots of options, so you can have e.g. "dark grey pink".{STYLE=s}
changes the style of text (see below); this has a LOT of options.{SIZE=f}
changes the size of text (coarsely, in 25% increments); this takes f as a percentage from 0 to 375.{FONT=name}
changes the font, if there is a FontFamily available, by looking upname
.{CLEARCOLOR}
sets the text color to the default color, which is usually white.{CLEARSIZE}
sets the size to 100%.{CLEARFONT}
sets the font to the original font (not using the FontFamily).{ENDCOLOR}
sets the text color to the default color, which is usually white. This is the same as{CLEARCOLOR}
.{VAR=name}
gets replaced by whatever String was associated with the variablename
.{IF=name;choice0=cat;choice1=b;=default}
checks the variable name, and compares it to each choice in the token.- If the value assigned to
name
is equal to a choice, the token is replaced by the choice's value, such ascat
. - If no choice is equivalent and there is an empty String for a choice, the value associated with the empty String is
used as a
default
value.
- If the value assigned to
{EVENT=name}
triggers an event, sendingname
to the TypingListener, when the typing reaches this point.{RESET}
Sets all formatting and speed changes to their initial values.label.setDefaultToken()
can be used to change the initial values, so text defaults to some different settings.
{SKIP=n}
skips ahead in the typing effect, instantly displayingn
characters.
This library extends what the original typing-label can do -- it allows styles to be applied to text, such as bold,
underline, oblique, superscript, etc. Related to styles are scale changes, which can shrink or enlarge text without
changing your font, and the "font family" feature. A font can be assigned a "family" of other fonts and names to use to
refer to them; this acts like a normal style, but actually changes what Font is used to draw. The full list of styles is
long, but isn't as detailed as the effect tokens. You can enable styles with something like libGDX color markup, in
square brackets like [*]
, or (if the markup is used in a TypingLabel
) you can use {STYLE=BOLD}
to do the same
thing. Tags and style names are both case-insensitive, but color names are case-sensitive. The square-bracket syntax
uses primarily punctuation, and is inspired by Markdown syntax (which GitHub uses, among other places).
In the following list, each entry looks something like:
[*]
toggles bold mode. Can use style names *
, B
, BOLD
, STRONG
.
That means you can always use [*]
to turn bold mode on or off, and in a TypingLabel you can additionally use the
case-insensitive syntax {STYLE=*}
, {STYLE=B}
, {STYLE=BOLD}
, or {STYLE=STRONG}
to do the same thing.
The full list of styles and related square-bracket tags:
[]
undoes the last change to style/color/formatting, though it doesn't do anything to TypingLabel effects.- This acts much like
[]
does in libGDX BitmapFont markup, but works on more than colors.
- This acts much like
[ ]
resets all styles/colors/formatting and effects to the initial state.- This used different syntax before version 0.10.0; if updating from an older version, you probably want to change the
old
[]
to the new[ ]
.
- This used different syntax before version 0.10.0; if updating from an older version, you probably want to change the
old
[(label)]
temporarily stores the current formatting state aslabel
, so it can be re-applied later.- This can be useful if you want to insert a formatted snippet into an outer piece of text, without losing the formatting the outer text had before the insertion.
label
can be any alphanumeric String. It probably shouldn't have spaces in it, but can have underscores.
[ label]
re-applies the formatting state stored aslabel
, if there is one.[*]
toggles bold mode. Can use style names*
,B
,BOLD
,STRONG
.[/]
toggles oblique mode (like italics). Can use style names/
,I
,OBLIQUE
,ITALIC
.[^]
toggles superscript mode (and turns off subscript or midscript mode). Can use style names^
,SUPER
,SUPERSCRIPT
.[=]
toggles midscript mode (and turns off superscript or subscript mode). Can use style names=
,MID
,MIDSCRIPT
.[.]
toggles subscript mode (and turns off superscript or midscript mode). Can use style names.
,SUB
,SUBSCRIPT
.[_]
toggles underline mode. Can use style names_
,U
,UNDER
,UNDERLINE
.[~]
toggles strikethrough mode. Can use style names~
,STRIKE
,STRIKETHROUGH
.[!]
toggles all upper case mode (replacing any other case mode). Can use style names!
,UP
,UPPER
.[,]
toggles all lower case mode (replacing any other case mode). Can use style names,
,LOW
,LOWER
.[;]
toggles capitalize each word mode (replacing any other case mode). Can use style names;
,EACH
,TITLE
.[%DDD]
, where DDD is a percentage from 0 to 375, scales text to that multiple. Can be used with{SIZE=150%}
,{SIZE=%25}
, or similarly,{STYLE=200%}
or{STYLE=%125}
.[%]
on its own sets text to the default 100% scale. Can be used with{STYLE=%}
.[%?MODE]
removes the scale and sets a special mode; modes are listed below.[%^MODE]
removes the scale and sets a special mode at the same time as small-caps mode; modes are listed below.[@Name]
, where Name is a key/name in this Font'sfamily
variable, switches the current typeface to the named one. Can be used with{STYLE=@Name}
.[@]
on its own resets the typeface to this Font, ignoring its family. Can be used with{STYLE=@}
.[#HHHHHHHH]
, where HHHHHHHH is a hex RGB888 or RGBA8888 int color, changes the color. This is a normal{COLOR=#HHHHHHHH}
tag.[COLORNAME]
, where COLORNAME is a color name or description that will be looked up externally, changes the color.- By default, this looks up COLORNAME with
ColorUtils.describe()
, which tries to find any colors fromPalette
by name, and also allows describing mixes of colors or simple changes like "light" or "dull".- Palette contains all the UPPER_CASE names from libGDX's
Colors
class, and also about 50 additional lowercase color names (from colorful-gdx).- You can preview
Palette
by hue, by lightness, or by name.
- You can preview
- Adjectives can be "light", "dark", "rich", "dull", or stronger versions of those suffixed with "-er", "-est", or
"-most". You can use any combination of adjectives, and can also combine multiple colors, such as "red orange".
- There are some more adjectives that act like pairs of the above four adjectives, for convenience:
- Combining "light" and "rich" is the same as "bright".
- Combining "light" and "dull" is the same as "pale".
- Combining "dark" and "rich" is the same as "deep".
- Combining "dark" and "dull" is the same as "weak".
- There are some more adjectives that act like pairs of the above four adjectives, for convenience:
- Some examples:
[RED]
,[green yellow]
,[light blue]
,[duller orange]
,[darker rich BLUE lavender]
,[pale pink orange]
, and[deeper green navy]
. - There's a section at the bottom of this README.md that covers some tricky parts of color descriptions.
- Palette contains all the UPPER_CASE names from libGDX's
- You can use
Font
'ssetColorLookup()
method with your ownColorLookup
implementation to do what you want here.- This isn't a commonly-used feature, but could be handy for some more elaborate color handling.
- The name can optionally be preceded by
|
, which allows looking up colors with names that contain punctuation. For example,[|;_;]
would look up a color called;_;
, "the color of sadness," and would not act like[;]
.- Non-alphabetical characters are ignored by default, but a custom
ColorLookup
might not, nor doesColorLookup.INSTANCE
, which looks up String names in the libGDX Colors class verbatim.
- Non-alphabetical characters are ignored by default, but a custom
- This also can be used with a color tag, such as
{COLOR=SKY}
(which Colors can handle right away) or with a description, such as{COLOR=lighter orange-red}
, even inside a tag like{GRADIENT}
.
- By default, this looks up COLORNAME with
[+region name]
, where region name is the name of a TextureRegion from a registered TextureAtlas, won't change the style, but will produce that TextureRegion in-line with the text.- This is commonly used with
KnownFonts.addEmoji()
to add the 3000+ Twemoji icons to a Font.- If you use Twemoji, the phrases
[+saxophone]
and[+🎷]
will each show a saxophone icon. - This also works with multipart emoji, such as
[+call me hand, medium-dark skin tone]
and[+🤙🏾]
. - The emoji can be previewed here.
- If you use Twemoji, the phrases
- Another option is
KnownFonts.addGameIcons()
, which adds icons from the game-icons.net collection. These use the same syntax:[+crystal-wand]
.- The game icons can be previewed here.
- This is commonly used with
The special modes that can be used in place of scaling are:
black outline
orblacken
, which can be used with the style namesBLACK OUTLINE
orBLACKEN
.white outline
orwhiten
, which can be used with the style namesWHITE OUTLINE
orWHITEN
.shiny
, which can be used with the style namesSHINY
,SHINE
, orGLOSSY
.drop shadow
orshadow
, which can be used with the style namesSHADOW
,DROPSHADOW
, orDROP SHADOW
.error
, which can be used with the style namesERROR
,REDLINE
, orRED LINE
.- This adds a zigzag red line below text; the color can be changed using
Font.PACKED_ERROR_COLOR
.
- This adds a zigzag red line below text; the color can be changed using
warn
, which can be used with the style namesWARN
,YELLOWLINE
, orYELLOW LINE
.- This adds a dashed yellow line below text; the color can be changed using
Font.PACKED_WARN_COLOR
.
- This adds a dashed yellow line below text; the color can be changed using
note
, which can be used with the style namesNOTE
,INFO
,BLUELINE
, orBLUE LINE
.- This adds a wavy blue line below text; the color can be changed using
Font.PACKED_NOTE_COLOR
.
- This adds a wavy blue line below text; the color can be changed using
jostle
, which can be used with the style namesJOSTLE
,WOBBLE
, orSCATTER
.- The jostle mode can also be used with
[%?]
.
- The jostle mode can also be used with
small caps
, which can be used with the style namesSMALLCAPS
orSMALL CAPS
.- The small caps mode can also be used with
[%^]
. It cannot be used with the[%?small caps]
syntax; it needs a caret.
- The small caps mode can also be used with
The small caps mode can be used with any of the other modes except for jostle, by changing %?
to %^
. Other than
that, no two modes can be active at the same time, and no modes can be used at the same time as scaling.
Note that modes use slightly different syntax to avoid being confused with color names. When using square brackets, each
of the names given here in lower-case should be preceded by %?
most of the time (small caps and jostle are special).
That means to enable the red-underline mode "error", you use the square-bracket tag [%?error]
. If using the
curly-brace markup for TypingLabel, you would use the names given here in upper-case, and can use them like other style
names:{STYLE=ERROR}
, for example. Small caps mode is, as mentioned, special; it is usually enabled with
[%^small caps]
, but can also be enabled with [%^]
, and can also be mixed with any other mode except jostle by
changing the normal %?
to %^
. Whenever small caps is active, the square-bracket tag uses %^
instead of %?
.
Jostle mode is also special; it is usually enabled with [%?jostle]
, but can also be enabled with [%?]
on its own.
Jostle can't be mixed with small caps.
The special modes are a bit overcomplicated in terms of syntax because I ran out of punctuation I could use.
The common example of a black outline around white text can be achieved with [WHITE][%?blacken]Outlined![%][GRAY]
.
(The example uses GRAY
as the normal color, but you could also use [ ]
to reset the color to whatever base color was
configured on a Layout
or the label that holds it. Note that [ ]
also resets size, mode, and, well, everything.)
Several combinations of effects are available using the {VAR=ZOMBIE}urgh, brains...{VAR=ENDZOMBIE}
syntax:
{VAR=FIRE}
changes the following text to have fiery changing colors. You can end it with{VAR=ENDFIRE}
.{VAR=SPUTTERINGFIRE}
changes the following text to have fiery changing colors and resize like popping flames. You can end it with{VAR=ENDSPUTTERINGFIRE}
.{VAR=BLIZZARD}
changes the following text to waver in the wind and use icy colors, white to light blue. You can end it with{VAR=ENDBLIZZARD}
.{VAR=SHIVERINGBLIZZARD}
changes the following text to waver in the wind and use icy colors, white to light blue, plus it will randomly make glyphs "shiver" as if cold. You can end it with{VAR=ENDSHIVERINGBLIZZARD}
.{VAR=ELECTRIFY}
changes the following text to be a dull gray purple color and randomly makes glyphs turn light yellow and vibrate around. You can end it with{VAR=ENDELECTRIFY}
.{VAR=ZOMBIE}
changes the following text to be "dark olive sage" (a dull gray-green color), makes glyphs rotate left and right slowly and randomly, makes glyphs drop down and get back up randomly, and when they first appear, has the glyphs emerge from the baseline (as if clawing out of a grave). You can end it with{VAR=ENDZOMBIE}
.
These are defined in TypingConfig.initializeGlobalVars()
, and you can define your own combinations in
exactly the same way these are defined. For example, FIRE
is defined with
TypingConfig.GLOBAL_VARS.put("FIRE", "{OCEAN=0.7;1.25;0.11;1.0;0.65}");
TypingConfig.GLOBAL_VARS.put("ENDFIRE", "{ENDOCEAN}");
The OCEAN
effect doesn't care what colors it uses; it only defines an approximate pattern for how to transition
between those colors. That means, counterintuitively, FIRE
is best implemented with OCEAN
rather than GRADIENT
.
Using the name FIRE
is probably preferable to OCEAN
, though, so the global var is here for that reason.
The ability to store formatting states using a label allows some more complex assembly of markup Strings from multiple
sources. You can call something like font.storeState("spooky", "[/][darker gray][@?blacken]")
to permanently store
that formatting state (oblique darker gray text with a black outline) in font
, and can then reset to that state just
by entering [ spooky]
(note the opening space). You could also create some insert-able text that stores the current
formatting before it writes anything, and resets the formatting back when it is done writing. That would use something
like "[(previous)][ ][BLUE][^][[citation needed][ previous]"
-- if this String gets inserted in the middle of a larger
block of text, it won't change the surrounding formatting, but will use blue superscript for its own text (the immortal
[citation needed]
) and won't use any of the surrounding block's formatting for its own superscript note. If you have
multiple state-store tags with the same label, the value associated with that label will change as those tags are
encountered. You might want to use unique labels to avoid accidentally changing another label's value, but this usually
isn't needed.
Textratypist makes heavy use of its new Font
class, which is a full overhaul of libGDX's BitmapFont that shares
essentially no code with its ancestor. A Font has various qualities that give it more power than BitmapFont, mostly
derived from how it stores (and makes available) the glyph images as TextureRegions in a map. There's nothing strictly
preventing you from adding your own images to the mapping
of a Font, as long as they have the requisite information to
be used as a textual glyph, and then placing those images in with your text. Textratypist supports standard bitmap
fonts and also distance field fonts, using SDF or MSDF. TypingLabel
will automatically enable the ShaderProgram that
the appropriate distance field type needs (if it needs one) and disable it after rendering itself. You can change this
behavior by manually calling the Font.enableShader(Batch)
method on your Font, and changing the Batch back to your
other ShaderProgram of choice with its Batch.setShader()
method (often, you just pass null here to reset the shader).
There are several preconfigured font settings in KnownFonts
; the documentation for each font getter says what files
are needed to use that font. The old .fnt files have been moved here.
You can see previews and descriptions of all known fonts here.
Having KnownFonts in code is meant to save some hassle getting the xAdjust, yAdjust, widthAdjust, and heightAdjust
parameters just right, though you're still free to change them however you wish. The variety of font
types isn't amazing, but it should be a good starting point. One nice new thing to note is the
KnownFonts.getStandardFamily()
method, which requires having 13 fonts in your assets, but naturally lets you switch
between any of those 13 fonts using the [@Medieval]
syntax (where Medieval is one of the names it knows, in this case
for "KingThings Foundation"). All of these fonts work without a distance field effect, so they won't look as good at
very large sizes, but are compatible with each other.
The license files for each font are included in the same folder, in knownFonts
here. All fonts provided here were
checked to ensure their licenses permit commercial use without fees, and all do. Most require attribution; check the
licenses for details.
The Twemoji icons are also present in an atlas of over-3000 32x32 images;
KnownFonts.addEmoji()
can register them with
a Font so the [+name]
syntax mentioned above can draw emoji inline. Similarly, an atlas of over-4000 60x60 icons is
present from Game-Icons.net, and KnownFonts.addGameIcons()
can register them with a Font.
Both Twemoji and Game-Icons.net atlases cannot be registered in one Font at the same time; there isn't enough free space
in the portion of Unicode they can safely use. A way around this is to use the FontFamily feature, and add a font just
for icons or just for emoji to the family. There's an existing method for this; KnownFonts.getGameIconsFont()
lets you
obtain a Font that is intended just to display Game-Icons, with some ceremony around its usage. [@Icons][+rooster][@]
is a quick example of how you could switch to the Font produced by getGameIconsFont()
, draw an icon, and switch back.
There are previews for Twemoji here, with the emoji char and name to
look up each image. Likewise,there are previews for
Game-Icons.net icons here, with just the name needed to look up
each image. Remember that because the game-icons.net images are pure white with transparency, you can tint them any
color you want using the standard [RED]
, [light dull green]
, or [#0022EEFF]
syntax.
The license files for Twemoji and the Game-Icons.net images are included in knownFonts
, next to the license files for
fonts. While Twemoji has simple requirements for attribution, Game-Icons requires attribution to quite a few individual
contributors; see the end of this document for the list, which you can and should copy to give credit to everyone.
You can rotate individual glyphs (if you draw them individually) or rotate whole blocks of text as a Layout, using an
optional overload of Font.drawGlyph()
or Font.drawGlyphs()
. Custom effects for TypingLabel
can also individually
change the rotation of any glyph, as well as its position and scale on x and/or y. You can rotate a TextraLabel or
TypingLabel by using their setRotation()
methods, and the rotation will now act correctly for labels with backgrounds
and/or with different alignment settings. The origin for rotations can be set in the label, and the whole label will
rotate around that origin point.
You can also, for some fonts, have box-drawing characters and block elements be automatically generated. This needs a
solid white block character (of any size, typically 1x1) present in the font at id 9608 (the Unicode full block index,
'\u2588'
). This also enables a better guarantee of underline and strikethrough characters connecting properly, and
without smudging where two underscores or hyphens overlap each other. Font
attempts to enable this in some cases, or
it can be set with a parameter, but if it fails then it falls back to using underscores for underline and hyphens for
strikethrough. All the fonts in KnownFonts
either are configured to use a solid block or to specifically avoid it
because that font renders better without it. Note that if you create a Font
from a libGDX BitmapFont
, this defaults
to not even trying to make grid glyphs, because BitmapFonts rarely have a suitable solid block char.
Some extra configuration is possible for box drawing characters that are actually used for that purpose (not just
underline or strikethrough). You can set boxDrawingBreadth
on a Font
to some multiplier to make box-drawing lines
thicker or thinner, without changing how they connect to each other.
Various features allow extra configuration here. You can set boldStrength
to some value other than the default 1 if
you want more or less extra space applied from the bold style. You can also set obliqueStrength
to change the angle
of the skew that oblique text gets drawn with. Colors for various effects can be changed as-needed;
font.PACKED_SHADOW_COLOR
can be changed to use a darker, lighter, more opaque, or more transparent shadow color, for
instance. font.PACKED_BLACK
affects the black outline mode, and font.PACKED_WHITE
affects the white outline and
shiny modes. There's similar modes to change the colors of error, warning, and note underlines. All of these color
configurations apply per Font instance, so you could have two Font objects using the same typeface but with different
colors configured.
Starting in the 0.4.0 release, there are various widgets that replace their
scene2d.ui counterparts and swap out Label
for TextraLabel
, allowing you to use markup in them.
The widgets are ImageTextraButton
, TextraButton
, TextraCheckBox
, TextraDialog
, TextraLabel
, TextraTooltip
,
and TextraWindow
, at least, so far.
Future additions to these widgets should permit setting the TextraLabel
to a TypingLabel
of your choice.
While TextArea
is not yet supported, TextraLabel
defaults to supporting multiple lines, and may be able to stand-in
for some usage. A counterpart to TextArea
is planned.
Input tracking has been an option for TypingLabel
and code that uses it since 0.7.0 . This expanded in 0.7.4 to allow
the text in a TypingLabel
to be made selectable with label.setSelectable(true)
. You can access the
currently-selected text with label.getSelectedText()
or copy it directly with label.copySelectedText()
. When the
user completes a click and drag gesture over the TypingLabel (and it is selectable), an event is triggered as well; you
can listen for "*SELECTED"
in a TypingListener
and copy text as soon as it is selected, or only copy when some key
is pressed. Other useful features that use input tracking include the {LINK}
tag, which makes a span of text a
clickable link to an Internet address, {TRIGGER}
, which triggers an event on-click, and a few other tags that respond
to mouse hovering ({ATTENTION}
, {HIGHLIGHT}
, and {STYLIST}
). These only work for TypingLabel
, not TextraLabel
,
so you may want to use a TypingLabel
and call skipToTheEnd()
to treat it like still text that happens to respond to
user input and can use animated styles like {RAINBOW}
.
You probably want to get this with Gradle! The dependency for a libGDX project's core module looks like:
implementation "com.github.tommyettinger:textratypist:0.10.0"
This assumes you already depend on libGDX; TextraTypist depends on version 1.12.0 or higher. A requirement for 1.11.0 was added in TextraTypist 0.5.0 because of some breaking changes in tooltip code in libGDX. The requirement for 1.12.0 was added in 0.9.0 because some things probably changed, but 1.12.0 (or the subsequent SNAPSHOT releases) should be pretty easy to update to.
If you use GWT, this should be compatible. It needs these dependencies in the html module:
implementation "com.github.tommyettinger:textratypist:0.10.0:sources"
implementation "com.github.tommyettinger:regexodus:0.1.15:sources"
GWT also needs this in the GdxDefinition.gwt.xml file (since version 0.7.7):
<inherits name="regexodus.regexodus" />
<inherits name="com.github.tommyettinger.textratypist" />
In version 0.7.4 and earlier, you would an earlier version of both dependencies (note, this is an old version):
implementation "com.github.tommyettinger:textratypist:0.7.4:sources"
implementation "com.github.tommyettinger:regexodus:0.1.13:sources"
use these GWT inherits instead:
<inherits name="regexodus" />
<inherits name="textratypist" />
RegExodus is the GWT-compatible regular-expression library this uses to match some complex patterns internally. Other than libGDX itself, RegExodus is the only dependency this project has. The GWT inherits changed for TextraTypist and for RegExodus because it turns out using the default package can cause real problems.
There is at least one release in the Releases section of this repo, but you're still encouraged to use Gradle to handle this library and its dependencies.
You can also use JitPack to get a current commit, which can be handy if there's a long span between releases. Current gdx-liftoff and gdx-setup projects all can use JitPack dependencies without needing any extra configuration. You would use this dependency in your core module:
implementation "com.github.tommyettinger.textratypist:textratypist:dc41592e94"
(Note the extra "textratypist"; one refers to the repo, and one refers to the actual project inside the repo.)
You can change dc41592e94
to any commit in the Commits tab of https://jitpack.io/#tommyettinger/textratypist ,
but you should not use -SNAPSHOT
-- it can change without your requesting it to, which is not what you want!
Some parts of TextraTypist act differently from their counterparts in scene2d.ui and Rafa Skoberg's typing-label.
A big quirk is that Font
and BitmapFont
have some core disagreements about how to parse a .fnt
file, and the
results of creating a Font
with new Font("MyFont.fnt")
can be different from
new Font(new BitmapFont(Gdx.files.internal("MyFont.fnt")))
. BitmapFont
reads in padding information (and does so
incorrectly according to the BMFont spec), where Font
ignores padding
information entirely. Some .fnt
files have been made so they look right in libGDX by using padding, but they will look
wrong in other frameworks/engines without that padding. Font
compromises by allowing manual adjustment of x and y
position for all glyphs (y often needs to be adjusted, either to a positive or negative value), as well as the width and
height of glyphs (these are useful less frequently, but can be helpful to stretch or squash a font). It may take some
tweaking to get a Font made from a BitmapFont to line up correctly with other widgets. You also may need to adjust the
offsetX, offsetY, and maybe xAdvance parameters if you load an atlas (such as with addEmoji()
or addGameIcons()
),
and the adjustments may be quite different for a Font made from a BitmapFont vs. a Font made directly from a .fnt file.
Since 0.8.1, Font
can parse an extended version of the .fnt format that permits floats for any spatial metrics, and
not just ints. The only file that uses this is GoNotoUniversal-sdf.fnt
, currently, and it is mainly expected to be
useful for fonts that have been scaled down from some larger size. Because only TextraTypist permits floats (that I know
of), you can't load GoNotoUniversal-sdf.fnt
as a BitmapFont
successfully.
If you load text from a file and display it, you can sometimes get different results from creating that text in code, or
loading it on a different machine. This should only happen if the file actually is different -- that is, the files' line
endings use \r\n
when checked out with Git on a Windows machine, or \n
on MacOS or Linux machines. TextraTypist uses
\r
to mark some kinds of "soft" line breaks that can be re-wrapped, and \n
for "hard" line breaks that must always
create a new line. Having \r\n
present generally shows up as two lines for every line break. A simple solution that
works for many projects is to include a .gitattributes
file in your project root, like the one here.
This can be used to force all text files or all text files with a certain file extension to use LF
mode, where only a
single \n
is used for line breaks. It's still recommended to keep .bat
files using CRLF
mode, with \r\n
used,
for compatibility. Using .gitattributes
from the start is a good idea, and should keep files exactly the same on all
current OSes. Older Windows programs (like Notepad from Windows 7) aren't able to read \n
line endings, but the
versions distributed with recent Windows can use \n
easily, as can almost all code-oriented text editors.
Colors can be written out as hex strings, like #FF7700
or #9783EDFF
, given by name, or described using a simple
syntax. The full list of (case-sensitive!) names can be seen ordered
by hue,
by lightness,
or by name. You can take one or more of
these color names, optionally add adjectives like "light" or "dull", and get a color that mixes the named colors and
applies changes from the adjectives. There are some tricky things here:
- The only adjectives this understands are "light", "dark", "rich", "dull", "bright", "pale", "deep", and "weak", plus
the stronger versions of those such as "darker", "palest", and "dullmost". Any adjectives this doesn't know, this
ignores entirely. Color adjectives are case-insensitive.
- "light" and "dark" change lightness.
- "rich" and "dull" change saturation.
- "bright" raises lightness and raises saturation.
- "pale" raises lightness and lowers saturation.
- "deep" lowers lightness and raises saturation.
- "weak" lowers lightness and lowers saturation.
- Color names are case-sensitive. Some names are from the libGDX
Colors
class, and areALL_CAPS
, sometimes with underscores. Other names are from colorful-gdx, and arelowercased
single words. In a few cases, the same word refers to a different color value if you use ALL_CAPS or use lowercase (ORANGE
andorange
are a good example). - If you have multiple color names in a description, all color names will be mixed using
ColorUtils.unevenMix()
. You can have a number after any color name, which assigns a weight to that color for the mixing. Higher numbers will cause their preceding color to have more effect on the result; any non-negative integers are allowed. - If there isn't a color name present in a description, the result is the int 256 (
0x00000100
), or fully transparent very dark blue, which is used as a placeholder because visually it is the same as transparent black. If a color does wind up as 256 at the time it is finally rendered, it will probably be ignored. - You can add colors to
Palette
with its staticaddColor()
method. This makes another color name usable, but won't retroactively make that color name parse correctly. You may have to call methods likeFont.markup()
again, so it's best if you can change colors before using them.
If you encounter issues with TypingLabel tokens, and you use ProGuard, the configuration for that tool needs a small addition:
-keep class com.github.tommyettinger.textra.** { *; }
There may be more strict versions of this ProGuard instruction possible, but at the very least, the
com.github.tommyettinger.textra.effects
package needs to be kept as-is, for reflection reasons. You may also need to
ensure the com.github.tommyettinger.textra.Effect
class is kept. Keeping all of TextraTypist should be perfectly fine
for obfuscation purposes because this is an open-source library, but it does add a small amount to the size of the final
JAR or APK. Right now, that appears to be 202 KB if you don't include any assets, so I wouldn't worry about it.
Distance field fonts generally seem more useful than they actually are, as implemented here. Both SDF and MSDF fonts use
a shader to handle size changes, but the way things are now, the shader only changes when the scale of a Font
is
changed, not when it has its size changed inline using (for example) the [%200]
tag, or changed with an effect like
{SQUASH}
. In these cases, the enlarged text just looks blurry, though it is worse with MSDF. Using a standard font
actually looks a lot better for these small-to-moderate size adjustments. Because it doesn't need a different
shader, the standard fonts are more compatible with things like emoji, and can be used in the same batch as
graphics that use the default SpriteBatch shader. SDF fonts support kerning, and Gentium-sdf.fnt
uses both an SDF
effect and kerning. The newer Go Noto Universal font uses SDF and has an incredibly large amount of kerning data in its
.fnt file. MSDF fonts created with msdf-gdx-gen do support kerning,
and as long as the generated texture is fairly large, the fonts seem to work well. The best approach most of the time
seems to be to use a large standard font texture, without SDF or MSDF, and scale it down as needed. Since 0.8.1, SDF
fonts support emoji (just without smooth, anti-aliased edges) in full color, but MSDF fonts probably never will. MSDF
uses color information to represent distance information, so colorful emoji appear to the MSDF shader as very
complicated shapes, without any of their intended coloring.
If you happen to use both tommyettinger's TextraTypist library and tommyettinger's
colorful-gdx library, you may encounter various issues. ColorfulBatch
appeared to be incompatible because it uses an extra attribute per-vertex (compared to SpriteBatch), but an adjustment
it already does seems to make it compatible without changes. Color description can be done by both
colorful-gdx's SimplePalette
and ColorUtils.describe()
here, but descriptions would really need to use the RGBA
color space to work as expected. Alternative shaders from colorful-gdx's Shaders
class generally won't work correctly
with the known fonts here and the defaults for neutral colors (here, white is the neutral color, but in most shaders
that allow lightening, 50% gray is the neutral color). The easiest solution for all this is to use a normal, vanilla
SpriteBatch
for TextraTypist rendering, and whatever ShaderProgram
or ColorfulBatch
you want for colorful-gdx
rendering.
Games that use custom Batch
classes with additional attributes don't work out-of-the-box with Font
, but it provides
an extension point to allow subclasses to function with whatever attributes the Batch
needs. Overriding
Font.drawVertices()
allows quite a lot of flexibility to handle unusual batches, and you can generally leave the
custom Font unchanged other than the drawVertices()
override. If you implement Font
's copy constructor just by
calling super(font);
, and still allow it to take a Font argument, then you can quickly take Fonts from KnownFonts and
make copies using your subclass. The JavaDocs for Font.drawVertices()
detail what each of the 20 floats passed in via
an array to drawVertices are expected to do; custom Batches could have 24 or more floats and so would need to put the 20
existing floats in the positions their Batch expects.
Sometimes, you may need to enable or disable integer positioning for certain fonts to avoid a strange GPU-related visual artifact that seems to only happen on some Nvidia GPUs. When this happens, glyphs may appear a half-pixel or so away from where they should be, in seemingly randomly-picked directions. It looks awful, and the integer position code at least should resolve it most of the time. Integer positions don't work well if you use world units that span multiple pixels in length, but this bug is an absolute mystery, and also doesn't happen at all on integrated GPUs, and may not happen on AMD GPUs. How it behaves on Apple Silicon graphics, I also do not know. The Issues tab is always available for anyone who wants to try to debug this! It is possible that some fixes introduced in the 0.7.x releases may have already eliminated this bug, but I'm not especially optimistic that it is always gone.
The gdx-freetype extension produces BitmapFont outputs, and you can create a Font from a BitmapFont without any issues.
However, FreeType's "Auto" hinting settings both look worse than they normally should with Font, and can trigger the GPU
artifact covered immediately above. Instead of "AutoSlight", "AutoMedium", or "AutoFull" hinting, you can choose
"Slight", "Medium", or "Full", which makes the font look more legible and avoids the GPU half-pixel-offset issue. I
don't have any idea why this happens, but because hinting can be set either in the FreeType generator parameters or (if
you use Stripe) set in a Skin file with "hinting": "Full"
, it isn't hard to fix.
There are some known issues with scaling, rotation, and integer-positioning in 0.7.5 through 0.9.0. You may see labels slide a little relatively to their backgrounds when rotated smoothly, and some (typically very small) fonts may need integer positions enabled to keep a stable baseline. Font debug lines may be quite incorrect in some of these versions, also, even if the text displays correctly to users. Scaling has improved significantly in 0.7.8, as has the handling of debug lines, but rotation still has some subtle bugs. A bug was fixed starting in 0.8.0 that made extra images in a Font (such as emoji) scale differently and drift when the Font they were mixed with scaled. That same bug also made an ordinary Font drift slightly as its scale changed; this is also fixed. Positions and sizes for background color and for images from an atlas have improved in 0.8.2, so selecting text shouldn't cover up text as badly with the background, and emoji should be fully surrounded by their selection background. Positions along the line vertically, while the text is scaled, improved in 0.8.3 so that the scaling is relative to the center of the line, rather than the bottom of the line. Some other code already expected scaling to be centered like that, so this change makes scaling look better, usually. In 0.9.0, integer positioning can still be set, but it does nothing; in practice, setting it was causing more problems than improvements. The few fonts that one would think would need integer positions (pixel fonts) actually look better without it. There are still some rotation issues in 0.9.0, though they mostly happen when the descent is configured to an extreme value, or sometimes other metrics. Lining up underline/strikethrough with rotated text is also a challenge.
Word wrap periodically seems to break and need fixing across different releases. The most recent time this happened was in 0.7.9, which also affected 0.8.0 and was fixed (I hope) in 0.8.1. A different wrapping-related bug was fixed more recently, in 0.8.3 ; this was rare, and only affected TypingLabel when some effects were present.
There's other issues with word wrap if you expect it to behave exactly like Label
in libGDX. Here, we don't break
words, even if a single word is longer than the width of a TextraLabel
or TypingLabel
. The reason for this is
twofold: first, breaking words without proper hyphenation logic can change the meaning of those words, and second,
fixing this could be a ton of work. I do intend to try to make this configurable and match Label
by default in some
near-future version. The word wrap behavior for multiple whitespace characters changed in version 0.10.0, and should be
essentially correct now. Remember that word wrap only makes sense in the context of scene2d.ui if a widget (such as a
TypingLabel or TextraLabel) if that widget has been sized by scene2d.ui, usually by being in a Table cell, or sometimes
by being in a Container. You may need to add a label to a Table or Container, then set the width and/or height of that
Cell or Container, to get wrap to act correctly.
A possibly-frequent issue (with an easy fix) that may start occurring with version 0.9.0 and later is that TextraTypist now requires Java 8 or higher. All modern desktop OSes support Java 8, and this has been true for 9 years. Android has supported Java 8 (language level, though only some APIs) for several years, and older versions can use "desugaring" to translate more-recent Java code to be compatible with (much) older Android versions. GWT has supported language level 8 for years, as well; 2.8.2, which libGDX is built with, allows using Java 8 features, and 2.10.0, which an alternate libGDX backend supports, allows using even more. RoboVM doesn't support any new APIs added in Java 8, but it has supported language level 8 from the start. TextraTypist doesn't use any APIs from Java 8, but does now use functional interfaces and method references. Having these features allows us to remove some nasty reflection-based code, and that in turn helps usage on platforms where reflection is limited, such as GWT and Graal Native Image. GWT was able to work before, but Graal Native Image would have needed a lot of configuration to be added for every game/app that used TextraTypist. The other issue is that if TextraTypist continued to target Java 7 for its library code, it wouldn't compile with Java 20 or later, and the LTS release 21 is coming very soon.
This is based very closely on typing-label, by Rafa Skoberg.
Typing-label is MIT-licensed according to its repo LICENSE
file, but (almost certainly unintentionally) does not
include any license headers in any files. Since the only requirement of the MIT license is to leave any license text
as-is, this Apache-licensed project is fully compliant with MIT. The full MIT license text is in the file
typing-label.LICENSE
, and the Apache 2 license for this project is in the file LICENSE
. Apache license headers are
also present in all library source files here. The Apache license does not typically apply to non-code resources in the
src/test/resources
folder; individual fonts have their own licenses stored in that directory.
Twemoji isn't a font, so it might be best to mention it separately. It's licensed under CC-BY 4.0, and requires attribution to Twitter if used. Twemoji's guidelines for attribution are here. (The documentation still says Twitter, not X, and to my knowledge X doesn't employ any of the active Twemoji team, so... I would link back to the Twemoji repo, so that it is up to them).
Like Twemoji, Game-Icons.png isn't a font, and it has quite a few contributors to the project. Because all icons in the project are on one PNG file, you must credit all the contributors who licensed their art under CC-BY, and it may be ideal just to credit all the contributors, period. The list is in the license.
OpenMoji is also not a font, but it clearly has a CC-BY-SA 4.0 license, and the BY clause should be satisfied by attributing the OpenMoji Project. The SA clause should be satisfied by any users of OpenMoji continuing to provide attribution. There isn't a non-commercial clause for any assets here.
The logo was made by Raymond "raeleus" Buckley and contributed to this project. It can be used freely for any purpose, but I request that it only be used to refer to this project unless substantially modified.
Wow, raeleus has really helped a tremendous amount. Both by testing TextraTypist in his Skin Composer app (which found quite a lot of bugs, small and large), and advising on proper scene2d.ui layout practices (which were not easy to get 100% right), the large 0.5.2 release (and those after it) would not be what it is today without his input. Thank you!
Thanks to fraudo for helping me go step-by-step to figure out how badly I had screwed up rotation with backgrounds, and
for writing most of LabelRotationTest
. Release 0.5.5 would still probably be in development for months without that
help, so thanks are in order.
Thanks to piotr-j (evilentity), mas omenos, and DMC from the libGDX Discord, for really thoroughly testing TextraTypist.
IncongruityTest
was originally piotr-j's work, and it helped me figure out which fonts in KnownFonts had incorrect
bounds information. TableWrapTest
was based closely on mas omenos' work, and was useful to locate a wrapping bug. DMC
managed to track down a very elusive ProGuard issue, which is now documented in this README.md , as well as noticing and
helping debug a variety of issues with code that I had no idea people were already using. Sanda Moen, fourlastor,
tecksup, and Siavash Ranbar helped track down some maddening bugs affecting word wrap; thanks to everyone who's put up
with those kinds of bug! IgorApplications has helped track down various SDF-related bugs and pointed out that a feature
(full-color emoji in SDF fonts) was possible, so thanks as well!
Of course, I have to thank Rafa Skoberg for writing quite a lot of the code here! About 2/3 of the effects are almost purely by Rafa, much of the TypingLabel-related code is nearly unchanged from his work, and in general he showed what libGDX UIs could be just by making the initial code.
Thanks to all the font designers who made fonts we use here; by making your fonts freely available, you perform a great service to the people who depend on them.
Thanks to Twitter for generously contributing Twemoji to the world of open source; having broadly available emoji makes them much more usable. Note that because this was a generous action by Twitter, it happened before its acquisition/change to "X".
Thanks to the many contributors to game-icons.net for producing high-quality free icons to game developers everywhere. The icons in Game-Icons.png were made by:
- Lorc, http://lorcblog.blogspot.com
- Delapouite, https://delapouite.com
- John Colburn, http://ninmunanmu.com
- Felbrigg, http://blackdogofdoom.blogspot.co.uk
- John Redman, http://www.uniquedicetowers.com
- Carl Olsen, https://twitter.com/unstoppableCarl
- Sbed, http://opengameart.org/content/95-game-icons
- PriorBlue
- Willdabeast, http://wjbstories.blogspot.com
- Viscious Speed, http://viscious-speed.deviantart.com
- Lord Berandas, http://berandas.deviantart.com
- Irongamer, http://ecesisllc.wix.com/home
- HeavenlyDog, http://www.gnomosygoblins.blogspot.com
- Lucas
- Faithtoken, http://fungustoken.deviantart.com
- Skoll
- Andy Meneely, http://www.se.rit.edu/~andy/
- Cathelineau
- Kier Heyl
- Aussiesim
- Sparker, http://citizenparker.com
- Zeromancer
- Rihlsul
- Quoting
- Guard13007, https://guard13007.com
- DarkZaitzev, http://darkzaitzev.deviantart.com
- SpencerDub
- GeneralAce135
- Zajkonur
- Catsu
- Starseeker
- Pepijn Poolman
- Pierre Leducq
- Caro Asercion
(Projects that use TextraTypist can copy the above list of Game-Icons.png contributors to comply with its license.)
Thanks again to the OpenMoji project! That was clearly a lot of work.