Getting Started
This guide covers the basics of setting up and using UniText in your Unity project.
Installation#
Setup Tool#
- Check your email for a setup email from Light Side with your access token
- Download the setup tool from the email and import it into your Unity project. The setup window opens automatically — paste your token and click Set Up
- Done! UniText is installed. You can manage versions anytime via Light Side → UniText Setup in the Unity menu bar
1. Adding UniText to a Scene#
UniText ships two rendering components. Pick the one that matches your scene:
| Component | Use when | Renders via |
|---|---|---|
UniText | Text in a Canvas UI (screens, HUDs, inspector overlays) | CanvasRenderer (UGUI) |
UniTextWorld | Text placed in 3D world space, particle-like text, floating labels | Combined mesh via an invisible batcher (see §1.3) |
Both components share 100% of the text processing pipeline (parsing, shaping, layout, modifiers, emoji, variable fonts, language). Only the rendering surface differs.
1.1 Canvas text (UniText)#
Use the GameObject menu to create ready-to-use Canvas UniText objects:
- GameObject›UI (Canvas)›UniText›Text — text with default font and size
- GameObject›UI (Canvas)›UniText›Button — button with UniText label (Image + Button + UniText child)
Canvas and EventSystem are created automatically if not present. No font assignment is required on desktop or mobile — a component with no FontStack (a font plus its fallback chain, see §2.3) renders with the OS default font and fills coverage gaps from OS fonts (see §2.6). WebGL has no OS font access, so assign a regular UniTextFont for WebGL builds.
You can also override default prefabs in Project Settings → UniText (Text Prefab, Button Prefab) — the menu will instantiate your prefab instead.
FontStack.1.2 World-space text (UniTextWorld)#
Use the menu to create a ready-to-go world-space text object:
- GameObject›UI (World)›UniText›World Text
The menu creates a UniTextWorld scaled to 0.01 (so world units line up with your typical 3D scene), and auto-adds a UniTextWorldRaycaster to Camera.main so pointer events work out of the box (see §4.4). Override the prefab in Project Settings›UniText›World Text Prefab.
World-space text authoring is identical to Canvas text — same FontStack, FontSize, alignment, modifiers, styles, language, and so on. The component also exposes Unity's standard sorting knobs:
1.3 How world-space rendering works#
You never attach a MeshRenderer to a UniTextWorld. An invisible UniTextWorldBatcher assembles all world-space text into combined meshes automatically — grouping compatible components into single draw calls and respecting Unity's sorting model, so text interleaves correctly with SpriteRenderer and other renderers. It is fully transparent: you never configure it.
Advanced: UniTextWorld also exposes batching and lifecycle events (Activated, RenderDataAvailable, SortingChanged, …) plus a tunable batch-shard size, for custom render tooling — see the API reference.
2. Working with Fonts#
Fonts work out of the box on desktop and mobile. A component with no font assigned renders with the operating system's default sans-serif, and any codepoint your assigned fonts don't cover is resolved automatically from the OS's installed fonts (see §2.6). Create and assign your own fonts when you want a specific typeface — everything below — but you don't have to set anything up just to see text. WebGL is the exception: it has no OS font access, so a WebGL build must be given a regular UniTextFont.
Two properties assign a font — Font and FontStack — and you can set either, both, or neither:
Font | FontStack | Primary font | Fallback for uncovered glyphs |
|---|---|---|---|
| set | set | Font | families in FontStack, then OS fonts |
| set | — | Font | OS fonts |
| — | set | the stack's primary | rest of the stack, then OS fonts |
| — | — | OS default font | OS fonts |
Font is a single UniTextFont; FontStack is a multi-family collection with its own fallback chain (§2.3). Use Font for a quick single typeface, FontStack for multilingual or bold/italic-rich text — and set both to put one explicit primary in front of a shared fallback stack. The always-on OS fallback (§2.6) is always the last link.
Under the hood, UniText renders glyphs with its own font format in two modes:
| Mode | Description | Use Case |
|---|---|---|
| SDF | Single-channel Signed Distance Field | Default. Resolution-independent, supports outlines and shadows |
| MSDF | Multi-channel Signed Distance Field | Sharper corners on geometric/display fonts |
Both modes use Burst-compiled curve-based rasterization (no bitmap rendering). Glyphs are stored in a shared Texture2DArray atlas with adaptive tile sizes (64/128/256), reference counting, and LRU eviction. Set the mode per component via RenderMode.
2.1 Creating a UniTextFont Asset#
Context Menu (from fonts already in the project):
- Import your font files (
.ttf,.otf, or.ttc) into Unity - Select one or multiple fonts in the Project window
- Right-click → Create›UniText›Font Asset
- A
.assetfile is created next to each source font
Supports batch creation — select 10 fonts, get 10 assets in one click.
UniText Tools Window (also useful for creating from fonts outside the project):
If the font file is somewhere on your computer but not imported into the Unity project:
- Open Tools›UniText›Tools
- Drag-and-drop font files from the Project window, or click Browse Files to pick fonts from anywhere on your computer
- Click Create N UniText Font Asset(s)
- For external fonts, you will be prompted for an output folder within Assets
This is also useful for quick drag-and-drop workflow without manually importing fonts first.
2.2 Font Inspector Settings#
Select a UniTextFont asset to configure in the Inspector:
| Setting | Default | Description |
|---|---|---|
| Font Scale | 1.0 | Visual scale multiplier. Normalizes fonts that appear too small or too large by design |
| SDF Detail | 1.0 | Tile detail multiplier. Higher values force larger atlas tiles for fonts with thin strokes (e.g. calligraphic) |
| Glyph Overrides | — | Per-glyph tile size overrides (Auto/64/128/256) for fine-tuning quality on specific glyphs |
After changing SDF Detail or Glyph Overrides, click Apply to rebuild the atlas. Revert discards pending changes.
A Glyph Picker is built into the inspector: type text to preview glyph rendering, select individual glyphs from the grid, and add tile size overrides directly.
The Inspector also shows:
- Face Info — family name, style, weight class, italic flag (read-only, extracted from font data)
- Variable Font Axes — if the font is variable, shows available axes with min/default/max values
- Font Data Status — whether font bytes are embedded
- Runtime Data — glyph count, character count
- Atlas Preview — SDF, MSDF, and Emoji atlas texture slices
2.3 Creating a UniTextFontStack (Font Collection)#
UniTextFontStack organizes fonts into Font Families. Each family has:
- a primary font and optional faces (bold, italic, light, etc.) — the same family, different weights/styles;
- an optional
name— a user-facing identifier addressable from markup (see §5 andFontModifier); - an optional
preferredLanguage— a BCP 47 tag that biases codepoint resolution toward this family when the active language matches (see §5).
Families are searched in order for glyph fallback.
There are two creation modes when you select multiple UniTextFont assets:
Font Stack (Combined) — Grouped by Family#
- Select 2+ UniTextFont assets in the Project window
- Right-click → Create›UniText›Font Stack (Combined)
- Fonts are automatically grouped by
familyName. The closest-to-Regular font becomes the primary; others become faces.
Families are searched top to bottom; each run uses the first family that covers it. Anything still uncovered falls through to the OS fonts, then the emoji atlas (§2.6).
When <b> is applied, the system uses CSS §5.2 weight matching to find the best face within the same family (e.g., Inter-Bold). If no matching face exists, synthesis (fake bold/italic) is applied.
Use case: Multilingual text with real bold/italic variants. One component handles any language.
Font Stack (Per Font) — Individual Stacks#
- Select 1+ UniTextFont assets in the Project window
- Right-click → Create›UniText›Font Stack (Per Font)
- Creates one separate
UniTextFontStackfor each selected font
Use case: When different components use different fonts. Swap font stacks per component.
Variable Fonts#
Variable fonts are strongly recommended over static font files. A single variable font file replaces dozens of static weights/widths:
Variable font axes are controlled via modifiers. <b> and <i> automatically set the appropriate axes when the font supports them. For direct control, use the VariationModifier with <var> tags.
Three-Tier Face Resolution#
When a modifier requests bold or italic, the system resolves in order:
- Variable font axes — if the font has
wght/ital/slntaxes, set them directly - Static font faces — find the closest matching face by weight/italic in the family
- Synthesis — apply fake bold (SDF dilate) or fake italic (shear transform)
Fallback Stack Chaining#
UniTextFontStack has a fallbackStack field that references anotherUniTextFontStack. The system searches primary fonts in each family first, then walks the fallbackStack chain. Circular references are handled automatically.
All stacks get full language support through one shared reference. After the whole chain is exhausted, anything still uncovered falls back to the OS's installed fonts (see §2.6) — so you only need to add families for the fonts you want explicit control over.
2.4 UniText Tools Window#
Open via Tools›UniText›Tools. Three tabs:
Tab 1: Create Font Asset#
Batch creation of UniTextFont assets from source files.
Adding fonts:
- Drag & drop — drop
.ttf/.otf/.ttcfiles into the drop area - Browse Files — opens file dialog with multi-select
- Project selection — selecting font files in the Project window auto-adds them
Each entry shows the font name and file size. Click Create N UniText Font Asset(s) to generate all assets.
Additional features:
- Copy All Characters — extracts every codepoint the font supports and copies to clipboard. Useful for checking font coverage or as input for the Font Subsetter
Output: Project fonts (within Assets) are saved next to the source file. External fonts (outside Assets) prompt for output folder.
Tab 2: Font Subsetter#
Create optimized subset fonts by keeping or removing specific character ranges. Reduces font file size for builds where you don't need full Unicode coverage.
Keep Mode — only selected characters remain in the font:
- Select script ranges (Latin, Cyrillic, Arabic, etc.) and/or type custom text
- The output font contains only those characters (plus GSUB-related composed forms)
- Example: Keep only "Basic Latin + Cyrillic" for a game targeting English/Russian
Remove Mode — selected characters are removed from the font:
- Select script ranges and/or type custom text to remove
- Intelligent composition detection: combined characters (emoji sequences, ligatures) are removed as glyphs while preserving their component codepoints
- Two-pass process: (1) Codepoint removal with GSUB closure (handles contextual forms), (2) Composition glyph removal without closure (preserves components)
Available script ranges (30 sets in 10 groups):
| Group | Ranges |
|---|---|
| Latin | Basic Latin, Extended Latin, Vietnamese |
| European | Cyrillic, Greek, Armenian, Georgian |
| Semitic | Arabic, Hebrew |
| N. Indic | Devanagari, Bengali, Gujarati, Gurmukhi |
| S. Indic | Tamil, Telugu, Kannada, Malayalam |
| SE Asian | Thai, Lao, Myanmar, Khmer |
| E. Asian | Hiragana, Katakana |
| Other | Sinhala, Tibetan |
| Symbols (1) | Digits, Punctuation, Currency, Math |
| Symbols (2) | Arrows, Box Drawing |
Output: Saves a new .ttf file with the suffix _subset. Reports original size, subset size, and reduction percentage.
Practical scenarios:
| Scenario | Mode | Configuration |
|---|---|---|
| Mobile game, English only | Keep | Basic Latin + Digits + Punctuation |
| European app, no Asian scripts | Remove | Devanagari, Bengali, Tamil, Thai, CJK, etc. |
| Localized to Arabic + English | Keep | Basic Latin + Arabic + Digits + Punctuation |
| Remove unused emoji from Noto | Remove | Custom text with emoji codepoints |
Tab 3: Dictionary Builder#
Builds word segmentation dictionary assets for SE Asian scripts (Thai, Lao, Khmer, Myanmar) that don't use spaces between words.
- Drag-and-drop a word list text file (one word per line)
- Select the target script
- Click Build to compile a
WordSegmentationDictionaryasset
The compiled dictionary is configured via Project Settings›UniText›Word Segmentation›Dictionaries. UniText ships with a Thai dictionary (26K words from ICU).
2.5 Materials#
Materials for the base text pass (SDF Face, SDF Base, MSDF Face, MSDF Base) and the emoji pass are managed automatically by UniTextMaterialCache — there is no manual material assignment on UniText.
Outline and shadow effects render as extra quads appended to the same mesh as the face (not as separate CanvasRenderer objects). Any number of outline / shadow modifiers can be layered on the same text without extra sub-meshes.
If you want to apply a custom material / shader to a text range, see §6.
2.6 System Fonts#
Two independent mechanisms let UniText draw text with fonts installed on the player's OS — no font files bundled, no assignment required. Supported on Windows, macOS, Linux, iOS, and Android. WebGL has no OS font access — assign a regular UniTextFont for WebGL builds.
Automatic OS fallback (always on)#
Any codepoint none of your assigned fonts cover is resolved from the OS's installed fonts through the platform's native font-matching API, then cached. It is the last link in the fallback chain, after every family and fallbackStack. A component with no FontStack at all uses the OS default sans-serif as its primary font, so freshly created text renders immediately.
UniTextSystemFont asset (explicit)#
A font asset whose bytes load from the OS at runtime instead of being embedded. Create via Assets›Create›UniText›System Font Asset, then add it to a font stack like any other font.
The inspector has a tab per platform (Common / Win / macOS / Linux / iOS / Android). On each platform tab you pick a font guaranteed to ship with that platform. The Common tab maps an abstract choice (System Sans-Serif / Serif / Monospace) to the right family per platform — Segoe UI on Windows, Helvetica Neue on macOS, Roboto on Android, and so on — and is used as the fallback when a platform tab is left unset. Face metrics and SDF/tile settings can be overridden per platform; unset fields use values read from the resolved font. If the requested font isn't found at runtime, UniText falls back to a guaranteed platform font and logs a warning.
Use it to match the host UI's native font, or to ship a small build that leans on OS fonts instead of bundling typefaces.
2.7 Font Variants#
A UniTextFontVariant reuses another font's raw bytes but owns all of its own face metrics, render settings, and glyph overrides. Create via Assets›Create›UniText›Font Variant and assign a Source font. Use it to render one TTF/OTF two different ways — different scale, spacing, fake-bold weight, or line metrics — without duplicating the font bytes. Each variant keeps its own atlas, so a variant and its source coexist without overwriting each other's glyphs. Face metrics are seeded from the source on first assignment, then owned by the variant.
3. Markup System#
UniText features an extensible markup system based on Modifiers and Parse Rules.
3.1 Architecture: Rule + Modifier#
The system separates what to parse from what to do:
- Parse Rule (
IParseRule) — finds patterns in text and produces ranges with optional parameters - Modifier (
BaseModifier) — applies a visual or structural effect to those ranges
There is no hard coupling between tags and modifiers. Any parse rule can drive any modifier. The tag name, the syntax, and even the parsing strategy are all independent from the effect being applied. A <highlight> tag can trigger a ColorModifier. A **markdown** wrapper can trigger an OutlineModifier. You decide.
Example: The same BoldModifier works with completely different syntaxes:
Highlighted: the same <b> tag driving two different effects — BoldModifier and ColorModifier. There is no fixed coupling: any rule can drive any modifier.
And the same TagRule (tagName="b") can be paired with any modifier — BoldModifier, ColorModifier, or your own custom modifier.
3.2 Built-in Modifiers#
The table below shows default pairings (how presets configure them). These are conventions, not constraints — you can reassign any tag to any modifier.
| Default Tag | Modifier | Example |
|---|---|---|
<b> | BoldModifier | <b>bold</b> or <b=700>weight 700</b> |
<i> | ItalicModifier | <i>italic</i> |
<u> | UnderlineModifier | <u>underline</u> |
<s> | StrikethroughModifier | <s>strike</s> |
<color> | ColorModifier | <color=#FF0000>red</color> |
<size> | SizeModifier | <size=24>large</size> |
<gradient> | GradientModifier | <gradient=rainbow>text</gradient> |
<cspace> | LetterSpacingModifier | <cspace=5>wider</cspace> |
<line-height> | LineHeightModifier | <line-height=1.5>text</line-height> |
<line-spacing> | LineHeightModifier | <line-spacing=10>text</line-spacing> |
<upper> | UppercaseModifier | <upper>text</upper> |
<ellipsis> | EllipsisModifier | <ellipsis=1>long text</ellipsis> |
<li> | ListModifier | <li>bullet item</li> |
<link> | LinkModifier | <link=url>click</link> |
<obj> | ObjModifier | <obj=icon/> |
<outline> | OutlineModifier | <outline=#FF0000>text</outline> or <outline=#FF0000,0.3> (color, dilate) |
<shadow> | ShadowModifier | <shadow=#00000080>text</shadow> or <shadow=0.1,#000,2,2,0.5> |
<var> | VariationModifier | <var=700>weight</var> (direct axis control) |
<font> | FontModifier | <font=pixel>Score</font> — selects a family by FontFamily.name (see §5) |
<lang> | LanguageModifier | <lang=zh-Hans>汉字</lang> — BCP 47 tag (see §5) |
<mat> | MaterialModifier | <mat>text</mat> or <mat=#FF8800> — custom material (see §6) |
3.3 Custom Tags with Default Parameters#
TagRule has a defaultParameter field that lets you create custom tags with pre-configured values. This way your text stays clean — no need to repeat parameter values in every tag.
Example: Create a <warning> tag that always applies red color:
Now in text:
<warning>error occurred</warning>— uses default red (#FF0000)<warning=#FFA500>caution</warning>— overrides with orange
Multi-parameter defaults: For modifiers with multiple parameters (like OutlineModifier: dilate, color), defaults fill in missing values:
<glow>text</glow>— dilate 0.3, green outline<glow=0.5>text</glow>— dilate 0.5, green outline (color from default)
This works because TagRule merges text parameters with defaults: values from the tag take priority, remaining parameters come from defaultParameter.
MarkdownWrapRule also supports defaultParameter the same way.
3.4 Parse Rule Types#
Tag-Based Rules#
All tag-based rules use the universal TagRule class with a configurable tag name. Parameters are always optional. Self-closing is syntax-driven (<tag/> or <tag=value/>).
Markdown-Style Rules#
| Parse Rule | Syntax | Typical Modifier |
|---|---|---|
MarkdownWrapRule (**) | **bold** | BoldModifier |
MarkdownWrapRule (*) | *italic* | ItalicModifier |
MarkdownWrapRule (~~) | ~~strike~~ | StrikethroughModifier |
MarkdownLinkParseRule | [text](url) | LinkModifier |
MarkdownListParseRule | - item, * item, 1. item | ListModifier |
RawUrlParseRule | Auto-detects https://... URLs | LinkModifier |
Utility Rules#
| Parse Rule | Purpose |
|---|---|
RangeRule | Apply modifier to specific character ranges without any markup in text |
StringParseRule | Match and optionally replace literal string patterns |
CompositeParseRule | Groups multiple rules under one modifier — each position in text is checked against child rules in order until one matches |
Protection Rules (standalone)#
Protection rules shield their content from being consumed by any other parse rule. They are standalone — they implement IParseRule.IsStandalone = true and register without a paired modifier (the rule acts on its own). The text is passed through unaltered except that the delimiters themselves are stripped.
| Parse Rule | Syntax | Behavior |
|---|---|---|
NoparseTagRule | <noparse>...</noparse> | Everything inside is treated as literal text. Missing closer = rest of string protected |
CodeSpanRule | `x`, ``x``, ```x``` | Balanced backtick runs per CommonMark §6.1 |
BackslashEscapeRule | \*, \[, \#, … | Escapes a single ASCII punctuation character after \ |
Register standalone rules with AddRule:
AddRule enforces IParseRule.IsStandalone == true — passing a non-standalone rule logs an error and does nothing (use AddStyle with a modifier for those). Your own rules can opt into standalone behavior by overriding IsStandalone => true.
3.5 Parameter Formats Reference#
The tag names below (<color>, <font>, <mat>, …) are the conventional names used by the built-in presets — they are not hard-coded into the modifiers. Any modifier can be registered under any name via Style.Tag(new XxxModifier(), "yourName"), or driven by MarkdownWrapRule / RangeRule / StringParseRule / a custom rule with no tag at all (see §3.1). The parameter syntax shown for each modifier is what the modifier itself parses, regardless of how the range was matched.
Color (ColorModifier)#
- Hex:
#RGB,#RRGGBB,#RRGGBBAA - Named (20 colors): white, black, red, green, blue, yellow, cyan, magenta, orange, purple, gray, lime, brown, pink, navy, teal, olive, maroon, silver, gold
Size (SizeModifier)#
- Absolute:
<size=24>— 24 pixels - Percentage:
<size=150%>— 150% of base size - Relative:
<size=+10>/<size=-5>— offset from base
Gradient (GradientModifier)#
- Format:
<gradient=name[,shape][,angle]> - Shapes:
linear(default),radial,angular - Angle: 0–360 degrees (0=right, 90=up). Used by
linearandangular
Examples:
<gradient=rainbow>— linear, horizontal<gradient=rainbow,radial>— radial from center<gradient=rainbow,angular,90>— conic sweep, rotated 90°<gradient=rainbow,linear,45>— linear, rotated 45°
Gradients are defined in the UniTextGradients asset (Project Settings → UniText → Gradients).
Letter Spacing (LetterSpacingModifier)#
- Format:
spacing[,monospace] - Pixels:
<cspace=5>— 5px extra spacing - Em units:
<cspace=0.1em>— 0.1 em extra spacing - Monospace:
<cspace=0.5em,true>— equal advance width for all glyphs - For cursive scripts (Arabic, Syriac, etc.), positive spacing renders visual tatweel (kashida) to preserve connections
Outline (OutlineModifier)#
<outline>— default (dilate=0.2, black)<outline=#FF0000>— custom color<outline=,0.3>— custom dilate (empty leading slot keeps color at default)<outline=#FF0000,0.3>— both (color, dilate)<outline=rainbow>— gradient outline (requires anIGradientProvideron the modifier)<outline=rainbow,0.3,radial,45>— gradient + dilate + shape + angle<outline=rainbow,,radial,45>— gradient, default dilate, custom shape + angle
Shadow (ShadowModifier)#
<shadow>— default (black 50% alpha)<shadow=#00000080>— custom color<shadow=0.1,#000,2,2,0.5>— dilate, color, offsetX, offsetY, softness
Variable Font Axes (VariationModifier)#
- Positional axis values in order: wght, wdth, ital, slnt, opsz
- Use
~to skip an axis - Absolute:
<var=700>— weight 700 - Percentage:
<var=150%>— 150% of default weight - Delta:
<var=+200>— +200 from default weight - Multiple axes:
<var=700,80>— weight 700, width 80 - Skip axes:
<var=~,~,~,-12>— only set slant to -12
Ellipsis (EllipsisModifier)#
<ellipsis=1>— truncate end (default):Hello Wo...<ellipsis=0>— truncate start:...o World<ellipsis=0.5>— truncate middle:Hel...rld- Any float 0–1 for fine-grained control
Font (FontModifier)#
- Parameter is a
FontFamily.namefrom the component's font stack <font=pixel>Score</font>— render "Score" in the family namedpixel
Language (LanguageModifier)#
- Parameter is a BCP 47 tag
<lang=zh-Hans>汉字</lang>,<lang=ja>...</lang>,<lang=ko>...</lang>,<lang=en-US>...</lang>
Material (MaterialModifier)#
- Parameter is an optional tint color (same syntax as Color)
<mat>text</mat>— use the material's tint as-is<mat=#FF8800>text</mat>— multiply the vertex color by orange
3.6 Adding Styles to a Component#
In the Inspector#
- Expand Styles list on the UniText component
- Click + — a searchable selector opens with predefined presets (Bold, Italic, Color, Font, Language, Material, Markdown variants, Protection rules, and more)
- Select a preset — both the Rule and Modifier are configured automatically
Each entry is a Rule+Modifier pair. Tags from the Rule are parsed in text, and the Modifier applies the effect to matched ranges. You can also configure Rule and Modifier manually for custom combinations.
Via Code — Fluent Builders#
Style exposes three static builders that cover the common cases:
For custom combinations (StringParseRule, CompositeParseRule, custom rules) use the explicit form:
Remove at runtime:
Querying and Mutating Styles at Runtime#
SetWholeText / ClearWholeText / ToggleWholeText operate on the component's local Styles list only — they never mutate Style Presets (those are shared assets).
3.7 Style Preset — Shared Configuration#
Problem: You have 50 UniText components that all need the same set of modifiers (bold, italic, color, links). Setting up each one manually is tedious and error-prone.
Solution: Style Preset is a ScriptableObject that stores a reusable list of Rule+Modifier pairs.
Setup#
- Assets›Create›UniText›Style Preset
- Add your modifier pairs:
BoldModifier + TagRule (b)ItalicModifier + TagRule (i)ColorModifier + TagRule (color)LinkModifier + TagRule (link)UnderlineModifier + TagRule (u)- On each UniText component, add this config to the Style Presets list
Benefits#
- Single source of truth — change the config, all components update
- No duplication — define modifiers once, reference everywhere
- Combinable — a component can have multiple configs plus its own local Styles. They all work together
- Version control friendly — one asset to track rather than per-component settings
Local vs Config#
| Feature | Local Styles | Style Presets |
|---|---|---|
| Scope | Per-component | Shared across components |
| Edit location | UniText Inspector | Preset asset Inspector |
| Use case | Component-specific markup | Project-wide standard markup |
A component's effective set of modifiers = its local Styles + all Style Presets.
Runtime API#
Useful for toggling a markup configuration at runtime (e.g., apply a "chat formatting" preset while the chat panel is open, remove it when it closes) without building individual styles.
3.8 RangeRule — Applying Modifiers Without Markup#
RangeRule lets you apply a modifier to specific text ranges programmatically, without any tags in the text itself.
Apply to All Text#
To apply a modifier to the entire text (e.g., make everything a specific color), use the range "..":
Range Syntax#
RangeRule uses C#-style range notation:
| Range | Meaning |
|---|---|
".." | Entire text (start to end) |
"0..10" | Codepoints 0 through 9 |
"5.." | From codepoint 5 to end |
"..5" | From start to codepoint 4 |
"2..^3" | From codepoint 2 to 3 from end |
"^5.." | Last 5 codepoints |
RangeEx.WholeText is the canonical ".." constant, and RangeEx.IsWholeText(expr) accepts any equivalent form ("..", "..^0", "0..").
Multiple Ranges#
Practical Scenarios#
| Scenario | Range | Modifier |
|---|---|---|
| Bold the entire text | ".." | BoldModifier |
| Highlight first word (5 chars) | "0..5" | ColorModifier with color parameter |
| Underline last 10 chars | "^10.." | UnderlineModifier |
| Apply size to specific range | "3..8" | SizeModifier with size parameter |
3.9 StringParseRule — Literal Pattern Matching#
StringParseRule matches literal string patterns in text (no XML/HTML syntax):
3.10 CompositeParseRule — Combining Rules#
CompositeParseRule groups multiple rules into one. It tries child rules in order and returns the first match:
3.11 Priority System#
Parse rules have a Priority property that controls matching order (higher = matched first):
| Priority | Use Case | Example |
|---|---|---|
| Positive (e.g., 10) | Explicit markup should match before anything else | Custom rules |
| 0 (default) | Standard tag-based and markdown rules | TagRule, MarkdownWrapRule, MarkdownLinkParseRule |
| Negative (e.g., -100) | Auto-detection, should only match if nothing else did | RawUrlParseRule (-100) |
This prevents conflicts: <link=url>https://example.com</link> won't be double-matched by both TagRule and RawUrlParseRule.
3.12 Creating Custom Parse Rules#
Implement IParseRule to create your own markup syntax:
Simplest Approach — Use TagRule#
If your syntax follows the <tag>content</tag> pattern, use the built-in TagRule with a custom tag name — no subclassing needed:
Parameters are always optional. Self-closing is purely syntax-driven (<tag/> or <tag=value/>).
3.13 Creating Custom Modifiers#
UniText has several modifier base classes for different use cases:
Pattern 1: Text Transformation (BaseModifier)#
For modifiers that transform codepoints before rendering (like uppercase):
Pattern 2: Per-Glyph Visual Effect (GlyphModifier<T>)#
For modifiers that change glyph appearance during mesh generation (color, underline, etc.):
gen.faceBaseIdx to address the current glyph's face quad. Never use gen.vertexCount - 4 — other modifiers can append geometry before your onGlyph runs and shift the last-four assumption.Pattern 3: Effect Quads (EffectModifier)#
For effects like outline, shadow, glow — duplicate geometry rendered behind/ahead of the face, painted per effect:
EnqueueEffectQuad records a request for an extra quad that renders behind the face in registration order. All outline-modifier quads render before all shadow-modifier quads, which render before the face — painter order is grouped per modifier, not per glyph.
Pattern 4: Sub-mesh With Its Own Material (SubMeshModifier)#
For effects that need a separate Material / shader (like MaterialModifier). Inherit SubMeshModifier and override ShouldIncludeCurrentGlyph, GetMaterialForSlot, GetRenderOrder, GetSortIndex — see MaterialModifier.cs for a full reference.
Pattern 5: Interactive Region (InteractiveModifier)#
For clickable/hoverable text regions:
Modifier Lifecycle#
Best Practices for Custom Modifiers#
- No
new T[]at runtime — useUniTextArrayPool<T>.Rent/Returnorbuffers.GetOrCreateAttributeData<T>() - Subscribe in OnEnable, unsubscribe in OnDisable — prevents stale callbacks
- Use
PrepareForParallel()for anything that calls a Unity API (Material.GetFloat(), transform reads, etc.) - Address the face quad via
gen.faceBaseIdx, notgen.vertexCount - 4 - Skip color (emoji) glyphs in effects —
if (gen.font.IsColor) return;
4. Interactive Text#
UniText provides built-in support for clickable regions, hover detection, and visual feedback. Everything in this section works for both UniText (Canvas) and UniTextWorld (world-space) — only the raycasting setup differs (see §4.4 for world-space).
4.1 Click and Hover Events#
4.3 Text Highlighter#
The Highlighter property controls visual feedback — clicks, hover, and programmatic selection. It lives on UniTextBase, so it works identically on Canvas and world-space text.
The built-in DefaultTextHighlighter provides click flash (with fade-out), hover tint, and selection highlight:
Custom Highlighters#
Extend TextHighlighter (or DefaultTextHighlighter to keep its click/hover/selection logic). The two CreateHighlightRenderer overloads — one taking UniText, one taking UniTextWorld — are the type-safe extension points: override either or both to plug a custom visual on the chosen backend. Inside event handlers, call CreateHighlightRenderer(name, order) (no owner argument) — it dispatches to the correct typed overload based on the actual owner.
To customize only the visual on one backend while keeping the default click flash / hover / selection logic, subclass DefaultTextHighlighter and override only the relevant CreateHighlightRenderer overload(s).
HighlightOrder.Behind renders below the text (selection, hover glow), HighlightOrder.Above renders above it (click flash, cursor).
4.4 World-Space Pointer Routing (UniTextWorldRaycaster)#
Canvas text receives EventSystem pointer events automatically through the Canvas's GraphicRaycaster. For world-space text, add a UniTextWorldRaycaster component to the camera that should pick up pointer events:
The raycaster is not added automatically — pick the camera explicitly. If a UniTextWorld with RaycastTarget = true enters a play-mode scene without any UniTextWorldRaycaster, a one-time warning is logged with the same instruction.
Properties:
- BlockingObjects (
None/TwoD/ThreeD/All) — physical geometry that should occlude clicks between the camera and the text. Leave asNoneif the scene already has aPhysicsRaycaster/Physics2DRaycasteron the same camera (Unity'sEventSystemdistance-sorts across raycasters automatically). - BlockingMask — layer mask used when
BlockingObjectsis non-None.
Per-instance opt-out: UniTextWorld.RaycastTarget (default true). Set to false on purely decorative text — the raycaster skips it entirely, the same way Canvas Graphic.raycastTarget = false works for UniText.
Once the raycaster is on the camera, UniTextWorld receives the same events as UniText: TextClicked, RangeClicked, RangeEntered, RangeExited, HoverChanged, plus link / hashtag / custom interactive range events.
4.5 Text Resolver (IUniTextResolver)#
The resolver hook substitutes a component's source text before parsing / shaping / layout, without writing to the serialized text field. Scenes and prefabs stay clean — ideal for editor-time localization preview, template expansion, or runtime key-to-string binding.
OnDetached if you subscribe to anything in OnAttached — the resolver stays alive until GC collects it, and an orphan subscription keeps the owner reference around and fires SetDirty on a destroyed component.TryResolve may run on a worker thread — don't call Unity APIs directly inside it; populate caches in PrepareForParallel and read them from TryResolve. To know whether a resolver is currently active, inspect uniText.TextOverride & TextOverrideSource.Resolver.
5. Language & Internationalization#
UniText routes a BCP 47 language tag through the shaping pipeline. Three things depend on this tag:
- OpenType
loclfeature — pan-CJK fonts (Noto Sans CJK, Source Han Sans, etc.) render the correct regional form for Han ideographs depending on whether the text is tagged Simplified Chinese, Traditional Chinese, Japanese, or Korean. FontFamily.preferredLanguage— during codepoint-to-font resolution, families whosepreferredLanguagematches the current tag are preferred over the normal fallback order. Useful for holding SC/TC/JP/KR cuts in one stack.- Any custom modifier that reads per-codepoint language via
AttributeKeys.Language.
5.1 Three places to set the language#
| Scope | API | Wins over |
|---|---|---|
| Per-range | LanguageModifier via <lang=...>...</lang> or Style.Tag / Style.Range / Style.WholeText | Everything below |
| Per-component | uniText.Language = "zh-Hans" | Project-wide default |
| Project-wide | UniTextSettings.Language (Project Settings›UniText›Localization›Language) | (base) |
UniText.Language is a runtime shortcut: the setter finds or creates a whole-text LanguageModifier style in the component's local Styles list. There's no serialized inspector field — components that never set a language see nothing extra.
5.2 Per-range language in markup#
Itemization splits runs on language boundaries, so each run shapes with its own OpenType language tag.
5.3 Picking the right font family by language#
Attach preferredLanguage to each region-specific family in one stack:
With UniText.Language = "zh-Hans", codepoints are resolved against the SC family first; unmatched codepoints fall through the normal chain as usual. A matching family wins over the default fallback order for that codepoint.
5.4 Naming families (FontFamily.name + FontModifier)#
You can give each family a user-facing name and address it from markup or code:
A matched name wins over both preferredLanguage selection and the default fallback chain. If the chosen family doesn't have a glyph for a particular codepoint, the normal fallback chain still kicks in for that codepoint. Unknown names log a one-time warning.
6. Custom Materials & Shaders#
MaterialModifier applies an arbitrary Material to a text range by emitting a dedicated sub-mesh. Use it for dissolve effects, hologram shaders, flame text, custom SDF looks, anything a shader can do.
6.1 Quick start — use a ready material#
UniText ships example materials in UniText/Defaults/Materials/:
| Material | Effect |
|---|---|
UniTextLit | World-space lit SDF (ambient + directional light + fog) |
UniTextEmojiLit | World-space lit emoji |
UniTextHologram | Scanlines + flicker + edge glow |
UniTextDisolve | Noise-driven dissolve |
UniTextRainbow | Animated color cycle |
Set up a MaterialModifier in the inspector — paired with a TagRule whose tagName you choose (mat is the convention used here) — and point its Material field at one of these. From code:
For UniTextWorld, you can also assign these materials as the component's base material instead of using MaterialModifier (useful for whole-text effects, no tag setup required).
6.2 Authoring your own shader#
Use the asset creation menu:
Assets›Create›UniText›Custom Material Shader
This scaffolds a new .shader file pre-wired for MaterialModifier — includes UniText_Custom.cginc, binds _MainTex as the glyph atlas Texture2DArray, exposes the standard UV layout UniText writes. Rename it, tweak the fragment function, you're done.
Three example shaders ship as starting points (in UniText/Shaders/Templates/Examples/):
UniText/Custom/DissolveUniText/Custom/HologramUniText/Custom/Rainbow
6.3 Compose modes#
MaterialModifier.renderOrder controls how the custom material composes with the base text pass on the range:
| Mode | Effect |
|---|---|
Replace (default) | Base SDF pass is suppressed on the range (face alpha zeroed); only the custom material renders |
Over | Custom material renders in front of the base text |
Under | Custom material renders behind the base text |
Replace zeroes the face alpha during the onGlyph callback. UniText invokes onGlyph subscribers in the order styles appear in the component's Styles list. If a ColorModifier / GradientModifier comes after MaterialModifier, it will overwrite the zeroed alpha and make the base face reappear. Place MaterialModifier after any color-writing modifiers.6.4 Per-text and per-glyph shader data#
- Per-text constants —
ConstantUv2/ConstantUv3(Vector4each) are written intoTEXCOORD2/TEXCOORD3of every glyph vertex in this modifier's sub-mesh. Animate them at runtime without touchingMaterial.Set*(which would affect every component sharing the cached material clone):csharp - Per-glyph writer — set
glyphDataWriter(aMaterialGlyphWriterdelegate) to computeuv2/uv3per glyph at sub-mesh build time. Useful for staggered effects (wave, cascade, per-character dissolve). - Emoji material slot —
emojiMaterialaccepts a separate material for emoji glyphs in the range. Leave null and emoji render through the base emoji pass (the modifier does nothing for them).
6.5 Noise texture generator#
Many custom shaders need noise textures. Tools›UniText›Noise Generator produces seamless grayscale value-noise / FBM PNG assets (64–1024 px, configurable seed / frequency / octaves / lacunarity / gain / invert / tileable). The shipped Dissolve and Hologram examples use this.
6.6 Lit shaders for world-space text#
Two SDF/emoji shader variants with lighting are provided for 3D scenes:
UniText/Lit/SDF— SDF text that picks up ambient + one directional light + fogUniText/Lit/Emoji— same but for emoji
Assign them via UniTextWorld's material or through MaterialModifier. _LightInfluence controls the mix between unlit and fully lit.
7. RTL and Bidirectional Text#
UniText automatically handles:
- RTL scripts (Arabic, Hebrew) — text flows right-to-left
- BiDi mixing — "Hello עולם World" renders correctly
- Complex shaping — Arabic ligatures, Indic conjuncts, etc. (via HarfBuzz)
Direction Settings#
- Auto (default) — detects from first strong directional character
- LeftToRight — force left-to-right
- RightToLeft — force right-to-left
8. Emoji#
Emoji work automatically — the system emoji font is detected and used:
| Platform | Emoji Font |
|---|---|
| Windows | Segoe UI Emoji |
| macOS | Apple Color Emoji |
| iOS | Core Text (native API) |
| Android | NotoColorEmoji (via fonts.xml) |
| Linux | NotoColorEmoji / Symbola |
| WebGL | Browser Canvas 2D |
Emoji are rendered as color bitmaps in a separate atlas. The emoji font is checked first for emoji-presentation codepoints, then falls back to the regular font stack.
9. Text Model#
When you read uniText.Text, you see the serialized authored value — what's stored on disk. What's actually drawn can be different. Five properties cover the full pipeline from authoring to rendering:
What you read back from Text is the serialized value; what actually renders can differ. A resolver (if set) substitutes the source before shaping — everything except Text is zero-allocation.
| Property | Type | What it is |
|---|---|---|
Text | string | Serialized authored value (setter persists into the scene/prefab) |
RawText | ReadOnlyMemory<char> | Runtime source — Text, or the buffer passed to SetText, before any resolver |
ResolvedText | ReadOnlyMemory<char> | Resolver's substitute from the last rebuild, or empty if none |
RenderedText | ReadOnlyMemory<char> | What actually goes through shaping/layout: resolver output if active, else RawText |
CleanText | ReadOnlySpan<char> | RenderedText with markup stripped |
TextOverride | TextOverrideSource flags | Tells you which runtime source(s) currently diverge from Text: None, SetText, Resolver, or a combination |
Everything except Text is zero-allocation. CleanText's backing buffer is pooled and may be rewritten on the next rebuild — copy to a string via new string(span) if you need to keep it.
9.1 Assigning text at runtime#
Three ways:
After a SetText(buffer, ...) call, the Text getter returns the serialized value, not the buffer. Read RawText (or RenderedText) to see what the component actually holds.
10. Inspecting Text#
UniText ships an in-scene inspection overlay that draws per-glyph, per-run, per-line, and per-modifier data over live text — for debugging shaping, BiDi, fallback, and layout. It works in the editor, in play mode, and in player builds (debug builds).
Editor: toggle Tools›UniText›Inspection Mode (Ctrl+Shift+I). Hover any UniText / UniTextWorld to inspect it; press P to pin the current card so you can move the cursor freely.
Play mode / player builds: press F8 to toggle, P to pin. Everything is also driven from code via the static UniTextInspector:
Available layers: GlyphBox, Baseline, Advance, RunBounds, LineBounds, ModifierRanges. ToggleKey and PinKey are configurable.
11. Common Properties#
| Property | Type | Default | Description |
|---|---|---|---|
Text | string | "" | Text content with optional markup |
Font | UniTextFont | — | Optional single primary font; overrides the stack's primary, FontStack still serves as fallback (see §2 font table) |
FontStack | UniTextFontStack | — | Font families + fallback chain. With no Font and no stack, the OS default font is used (except WebGL) |
RenderMode | UniTextRenderMode | SDF | SDF (single-channel) or MSDF (multi-channel) |
FontSize | float | 36 | Base font size in points |
color | Color | white | Base text color |
BaseDirection | TextDirection | Auto | LTR, RTL, or Auto |
WordWrap | bool | true | Enable/disable word wrapping |
HorizontalAlignment | HorizontalAlignment | Left | Left, Center, Right |
VerticalAlignment | VerticalAlignment | Top | Top, Middle, Bottom |
AutoSize | bool | false | Auto-fit text to container |
MinFontSize | float | 10 | Auto-size minimum |
MaxFontSize | float | 72 | Auto-size maximum |
Language | string | null | Whole-text BCP 47 language tag (shortcut over LanguageModifier) |
Highlighter | TextHighlighter | DefaultTextHighlighter | Interaction visual feedback |
TextResolver | IUniTextResolver | null | Hook that overrides source text before parsing |
Additional on UniTextWorld#
| Property | Type | Default | Description |
|---|---|---|---|
SortingOrder | int | 0 | OrderInLayer for batching/sorting |
SortingLayerID | int | 0 (Default) | Sorting layer for batching/sorting |
Read-Only Properties#
Text pipeline stages (in order — each row reads its predecessor):
| Property | Type | Description |
|---|---|---|
RawText | ReadOnlyMemory<char> | Runtime input — Text or the buffer passed to SetText, before any resolver |
ResolvedText | ReadOnlyMemory<char> | Resolver substitute from the last rebuild, or empty |
RenderedText | ReadOnlyMemory<char> | What goes through shaping/layout: resolver output if active, else RawText |
CleanText | ReadOnlySpan<char> | RenderedText with markup stripped. Backing buffer is pooled — don't store the span |
TextOverride | TextOverrideSource [Flags] | None, SetText, Resolver, or a combination — the runtime source(s) overriding Text |
CurrentFontSize | float | Effective font size (after auto-sizing) |
ResultSize | Vector2 | Computed text dimensions |
ResultGlyphs | ReadOnlySpan<PositionedGlyph> | All positioned glyphs after layout |
Events#
| Event | Type | Description |
|---|---|---|
TextClicked | Action<TextHitResult> | Any text click |
RangeClicked | Action<InteractiveRangeHit> | Interactive range clicked |
RangeEntered | Action<InteractiveRangeHit> | Pointer enters interactive range |
RangeExited | Action<InteractiveRangeHit> | Pointer exits interactive range |
HoverChanged | Action<TextHitResult> | Pointer moved over text |
Rebuilding | Action | Before text rebuild |
RectHeightChanged | Action | RectTransform height changed |
UniTextWorld additionally raises RenderDataAvailable / RenderDataCleared / SortingChanged / ParentChanged (per-instance) and static Activated / Deactivated.
