Changelog — 3.0.0 Preview

Changelog

All notable changes to UniText.

Versions
to
  • Added
    New media.lightside.core package ("LightSide Core", v1.0.0, Unity 2021.3+, MIT / free). UniText's non-text foundation now lives in a standalone package — object pooling (ArrayPool), the worker-thread pool (WorkerPool, MainThread), math/hashing (XxHash64, HashNoise), collections (FastIntDictionary, SpanIntern), named catalogs (INamedCatalog/AssetNamedCatalog/InlineNamedCatalog), color/gradient parsing (ColorParsing, Gradient), the Cat logging system, the touch-gesture recognizer, a full custom-inspector toolkit, and the byte-preserving asset-migration framework (IMigration, IMigratedPackage, RenameManagedType/MoveFieldsToStruct, per-package ledger) — the machinery behind UniText's automatic v2→3 upgrades, open to any package on Core. Assemblies LightSide.Core / LightSide.Core.Editor, root namespace LightSide.
  • Added
    Open-core boundary. The shared foundation is MIT and free; UniText's text engine stays the commercial package. The two version independently, and UniText 3.0.0 takes a hard dependency on media.lightside.core 1.0.0, resolved automatically through the media.lightside scoped registry — one install pulls both.
  • Added
    Source-compatible split (two renames). Every moved type kept its namespace LightSide, so existing using LightSide; code still resolves. Two utilities were renamed in the move — UniTextArrayPool<T>ArrayPool<T> and RangeEx.WholeText/IsWholeTextRangeEx.All/IsAll, no aliases — everything else compiles unchanged.
  • Added
    Reusable inspector toolkit in Core. The [SerializeReference] type picker (Selector, TypeSelectorDrawer, ManagedReferenceTypeMenu), reorderable styled lists (StyledListUtility, ListReorder), and section/vector/padding widgets that power UniText's inspectors are now a Core editor toolkit any project can drop into its own editors.
  • Added
    GPU glyph-upload promoted to Core. The regional texture-upload backend (native plugin renamed unitext_gpulightside_gpu) became a public GpuUpload API in LightSide.Core/Runtime/Gpu, rebuilt around a device-epoch/device-reset-aware pool with a synchronous WebGL2 bridge — shared infrastructure across LightSide packages.
  • Added
    Built-in profiler (advanced/developer tooling). Core ships Prof — a lock-free per-thread Enter/Exit sink that reconstructs the call tree with self-time and per-method allocation — driven by ProfWeaver, a Unity ILPostProcessor (Mono.Cecil) that auto-wraps method bodies, inert unless PROF_ENABLE is set and the assembly is allowlisted (Window > LightSide > Profiler WeaverProjectSettings/ProfWeaver.txt). ProfSampler adds a zero-per-call statistical backend for IL2CPP players (a native plugin walks the real stack into a lock-free ring), and development-build captures stream to the editor over the player connection into Window > LightSide > Profiler.
  • Added
    One LightSide editor menu. Shared tools consolidated under Tools/LightSide/ — the Noise Generator moved there from Tools/UniText/, joined by a new Log Zones window (live on/off panel for the Cat logging system's zones).
  • Added
    UniTextSelectable: read-only text selection for both Canvas (uGUI) and world-space text. Click to place a caret, double-click to select a word (a drag continuing from it extends by whole words), triple-click a paragraph, drag to select, Shift to extend, right-click/long-press to select-word-and-open-menu, keyboard Copy and Select-All while focused. Public mutators (SetCaret, SetSelection, ExtendSelection, SelectWord/SelectLine/SelectParagraph, SelectAll, ClearSelection, DragSelectionHandle, each returning bool) plus GetSelectedText and gesture drivers (HandlePrimaryClick, DispatchTap/DispatchDoubleTap/DispatchTripleTap, BeginDrag/UpdateDrag/EndDrag). Single-selection-per-document with EventSystem-driven defocus.
  • Added
    UniTextEditable: editable text without input-field chrome — a sibling of UniTextSelectable on the same GameObject. Implements ITextDocument and ISavedStateProvider. Standalone it tracks the text size through the text component's ILayoutElement — add a ContentSizeFitter or place it under a layout group; wrapped by UniTextInputField for the form-field UX. All OS input is delivered independently of Unity's input system.
  • Added
    UniTextInputField: the form-field component, composing UniTextEditable + UniTextSelectable + UniTextBase on a child editing surface inside an Image background and a RectMask2D viewport. Implements ILayoutElement + ILayoutGroup to grow with content up to MaxHeight (then scroll internally; on uGUI 2.6+ the cap is also published as ILayoutElement.maxHeight), preserves scroll position across re-enable and list recycling, and reports its box screen-rect to native overlays. Field-level focus surface (ActivateInputField/DeactivateInputField, IsFocused, static ActiveField) and events (Focused, Defocused, EditEnded); an I-beam hover cursor that yields to occluding controls. Content typing is composed from input behaviors/filters/validators, not a fixed content-type enum.
  • Added
    Zero-alloc text access: CharCount/CodepointCount, CopyTextTo(Span<char>), TextEquals(ReadOnlySpan<char>), with surrogate-pair-safe codepoint indexing — poll and compare a field every frame without touching the GC (the Text getter is the opt-in allocating path).
  • Added
    TextSelection (readonly struct): Anchor/Focus/Affinity over codepoint indices, with IsCollapsed, Start/End/Length, Clamp, a Caret(index, affinity) factory, and full value equality. Direction is implicit in anchor/focus ordering, modeled on the W3C Selection API / Flutter TextSelection.
  • Added
    CaretAffinity (Downstream/Upstream): one unified hint that disambiguates caret rendering at both soft-wrap line breaks and LTR↔RTL BiDi run boundaries.
  • Added
    BiDi-aware hit-testing (SelectionHitTest, UniTextBase.HitTestCaret/IsOverText): edge-snapped caret placement (left half of a glyph → cluster N, right half → N+1), closest-edge resolution with swapped left/right semantics inside RTL runs, grapheme-cluster snapping, and a web line-box "over text" model for the I-beam affordance. Line resolution binary-searches a per-line advance prefix and the within-line scan touches only that line's glyphs — a click costs O(log lines + line), never the document.
  • Added
    Word/line/paragraph boundaries: UAX #29-aware word segmentation with VS Code / Windows / macOS Ctrl/Option+Arrow semantics — apostrophes in words (don't, and the curly don’t), digit separators (3.14, 1,000), Katakana as its own script run, Han ideographs and Hiragana broken per cluster, Thai/Lao/Khmer/Myanmar snapped to dictionary word boundaries (Thai dictionary in the box; register others via UniTextSettings.Dictionaries), emoji/ZWJ/variation-selector clusters never split.
  • Added
    Selection events: SelectionChanging (vetoable — Cancel or reassign Proposed to clamp a proposed change out of an atomic pill or read-only region), SelectionChanged (with a reason), and a 14-value SelectionChangeReason open-string vocabulary (select.pointer, select.word, select.extend, select.clamp, …) matching the CodeMirror 6 convention.
  • Added
    Grapheme-cluster caret movement: one arrow press or one Backspace crosses a whole emoji, ZWJ sequence, or base+combining-mark cluster — caret positions always sit on grapheme boundaries.
  • Added
    Vertical movement with a preserved column anchor: Up/Down/PageUp/PageDown keep a desired-X column across consecutive vertical moves, reset on any horizontal/word/edit/click.
  • Added
    RTL-base-aware horizontal movement: in an RTL line, Left/Right step toward logical start/end (Android getParagraphDirection convention); word movement obeys the same flip. The empty last line after a trailing newline follows the preceding paragraph's direction, matching mainstream editors.
  • Added
    InputCaretRenderer: the caret component — a RectMask2D-clipped quad with blink-on-interval, blink-idle-timeout-then-steady, immediate geometry on type (no one-frame lag), and configurable width/color; subclass it (override OnPopulateMesh) for a block, underline, or gradient caret.
  • Added
    Edit operations on UniTextEditable: InsertText (string and ReadOnlySpan<char>), DeletePrevious/DeleteNext, DeleteSelection, DeleteWordPrevious/DeleteWordNext, DeleteToLineStart/DeleteToLineEnd, TransposeCharacters (carries adjacent formatting through the swap), SelectAll, Select, MoveCaretTo. Delete/transpose boundaries are computed over the document with a bounded UAX #29 window — never a stale previous-frame layout — so key-repeat and frame hitches can't delete the wrong cluster.
  • Added
    Undo/redo: Undo/Redo/CanUndo/CanRedo/ClearUndoHistory, with word-aware token-class coalescing (the VS Code / CodeMirror 6 word↔separator rule, held-Backspace merged, Replace never coalesced), a configurable time window (UniText settings, default 0.5 s), and an allocation-free store — undo text lives in a shared char[] referenced by slices, capped by the settings' undo memory limit (default 1 MB, oldest-first eviction, the newest entry always survives; 0 = unlimited). Restores the pre-edit selection on undo; multi-step commands (styled typing, IME commit over a selection) group into one undo step.
  • Added
    Programmatic set-text with explicit undo policy: SetText (replace, reset selection, clear history) vs SetTextProgrammatic(value, recordUndo = false, preserveHistory = false, reason = null)preserveHistory:true rebases existing undo entries through the swap's minimal diff, so a collaborative/network echo that only appends keeps the user's local undo (CodeMirror addToHistory:false); reason tags the event (e.g. sync.network).
  • Added
    ITextDocument: a read-only, codepoint-indexed document abstraction (CodepointCount, CharCount, Version, CopyCodepointRange, GetCodepointAt) that validators, find/replace, and accessibility consumers depend on instead of the concrete editor.
  • Added
    EditApplied + EditShape: every mutation raises EditApplied(EditShape) synchronously (after Version advances, before the frame-coalesced TextChanged) carrying Start/Removed/Inserted/Delta/MapIndex — the zero-alloc damage hint for keeping indexed state (search results, CRDT positions, highlight ranges) in sync without rescanning.
  • Added
    Change-reason vocabulary (TextChangeReason): open dotted-prefix strings carried by DocumentChangedinput.type, input.type.compose, input.paste, input.delete[.backward|.forward], input.cut, input.format, input.restore (input.* is exclusively user input), program.set for programmatic mutation, sync.network for collaborative sync, and edit.style for formatting-command tag rewrites — so integrators react differently to typing vs paste vs programmatic vs IME edits with one prefix check.
  • Added
    Events: TextChanged (zero-alloc), ValueChanged, DocumentChanged (with reason), SelectionChanged (frame-coalesced), Submitted, Cancelled, Focused/Defocused, CompositionStateChanged, TouchKeyboardVisibilityChanged, ValidationChanged.
  • Added
    ISavedStateProvider: SaveState/RestoreState persist text, selection, affinity, and scroll into a unitext.*-prefixed bundle for host-driven view recreation (virtualized lists, mobile process-death restore, Web bfcache).
  • Added
    Input sanitization: lone surrogates and C0/DEL control chars dropped, \r\n collapsed to \n, NUL rejected, with a 1,000,000-char paste safety cap — the field is safe to point at arbitrary pasted data.
  • Added
    GraphemeCountCache: grapheme-cluster count cached by document Version, predicting a pending edit by re-segmenting only a boundary-safe window — live counters and grapheme length limits stay O(window) per keystroke instead of a full rescan.
  • Added
    EditAction enum: the full editing action set — movement (char/word/line/page/document), selection variants of each, delete (prev/next/word/line), TransposeChars, InsertNewline/InsertTab, Copy/Cut/Paste/PasteAsPlain, Undo/Redo, Submit/Cancel — plus Ignore (consume a key and block its platform default) and range constants for permission filtering.
  • Added
    Platform-aware key resolution (PlatformKeySemantics + a built-in key map): macOS (Cmd shortcuts, Option word-nav, Cmd+←/→ line, Cmd+↑/↓ document, plus default Emacs bindings Ctrl+A/E/F/B/P/N/K/T/D/H) vs Windows/Linux (Ctrl shortcuts, Ctrl+Y redo, Ctrl+Home/End, Shift+Insert/Ctrl+Insert/Shift+Delete, AltGr-safe Ctrl+Alt), with Cmd-vs-Ctrl resolved at runtime (a macOS-browser WebGL build and an iPad hardware keyboard get Cmd). Read-only mode runs only navigation/selection/Copy/Submit/Cancel; mutating actions are suppressed during IME composition.
  • Added
    Non-destructive composition: in-progress IME text renders without being written to the document until the IME commits, and the commit lands as a single undo entry. CJK candidate windows, dead keys (´+e→é), and accent menus work correctly; composition renders unmasked in password fields. The overlay commits synchronously on defocus/SetText so text is never lost when the user taps away or switches apps, and late echoes from a killed session are discarded.
  • Added
    Clause model (CompositionData, CompositionClauseStyle): per-clause attributes (macOS/iOS NSMarkedClauseSegment runs, Android composing spans) are normalized into one neutral clause vocabulary — Unconverted / TargetConverted / Converted / TargetNotConverted / Error, the Windows IMM ATTR_* taxonomy — so integrators reason about conversion state without per-platform code; a platform that delivers no clause detail reports one Unconverted clause.
  • Added
    Candidate-window placement: the caret position is reported in native client space (UniTextNativeInput.SetCursorScreenPos, Editor Game-View letterbox-compensated), and SetImeCaretScreenRect pins the window for inline Scene-view editing — so the candidate/emoji/accent window sits on the caret in a build, the Editor Game View, and the Scene View.
  • Added
    Text-input-context queries answered from UniText's own layout: TryGetCharRangeRect (first-rect-for-range), HitTestChar (closest position to point), WritingDirection (per-codepoint UAX #9 levels), and a grapheme/word TokenizerQuery — feeding macOS NSTextInputClient, iOS UITextInput, dictation, VoiceOver character/word reading, and candidate placement from real shaped glyphs, not an approximation.
  • Added
    ITextInputContext: the neutral document-context seam the native backends query. UniTextEditable installs itself while active; a console, terminal, or custom text host installs its own context and receives full native IME. CompleteComposition/CancelComposition give hosts programmatic commit/discard; IME-driven caret moves (Gboard spacebar-swipe, InputConnection.setSelection) are applied as input.
  • Added
    Android IME context mirror: the focused document and selection are mirrored into the platform InputConnection — the whole text when ≤ 1,024 UTF-16 chars, else a ~512-char window centered on the selection and snapped to grapheme boundaries, coalesced to one push per frame and cached by document version — so Gboard suggestions, autocorrect, and spacebar cursor control see real surrounding context with zero per-keystroke allocation; pull-model platforms (Windows, macOS, iOS) skip the push entirely.
  • Added
    UniTextNativeInput: delivers OS key, text, composition, selection, and keyboard-visibility events independently of Unity's input system (no Event.PopEvent), so editing works regardless of the project's Active Input Handling (Legacy / New Input System / Both) — removing the single biggest friction point of every Unity input-field asset. Control keys arrive only via key-down; printable text only after the OS resolves layout/dead-keys/IME. Cross-platform NativeKeyCode and [Flags] NativeModifiers.
  • Added
    Windows Text Services Framework text store: the new UniTextNativeInputWindows.dll (x86_64 + ARM64) hosts an ITextStoreACP document, so the Win11 emoji/GIF panel (Win+.), clipboard history (Win+V), and modern TSF IMEs insert into the field with the candidate window anchored at the caret — even under the New Input System.
  • Added
    Pluggable input backends (INativeInputBackend, RegisterBackend): a priority-ranked registry with a ManagedInputBackend fallback registered on every platform, so a missing/failed native plugin (or an unsupported platform) still types, and studios can add first-class input for consoles/TVs without forking the package.
  • Added
    NativeKeyboardConfig: soft-keyboard traits the OS reads on show — KeyboardType, ReturnKeyType, AutoCapitalization, AutoCorrection, SpellChecking, AutofillHint, iOS smart-quotes/dashes/insert-delete opt-out, KeyboardAppearance, return-key auto-dim, Done toolbar, password rules, AndroidImeFlags, and raw iOS keyboard/return-key overrides. Every option defaults to the user's OS preference. (iOS coverage is full; Android maps type/caps/correction/spell/return/ime-flags; WebGL maps inputmode/enterkeyhint/autocapitalize/autocorrect/spellcheck/autocomplete.)
  • Added
    NativeFieldOverlayStyle / NativeFieldHandle: opt into the OS's own field (UITextField/UITextView, EditText, DOM <input>/<textarea>) for system selection handles, autofill chrome, voice input, and per-platform styling — the only route to true native UX inside a WebGL browser sandbox. NativeFieldHandle returns the raw native-view pointer for further customization (functional on iOS today).
  • Added
    Keyboard-avoidance data (KeyboardEvent, KeyboardEventPhase, KeyboardEasing): will-show/animation-progress/did-show/will-hide/did-hide/will-change-frame phases with duration, easing, and a frame-synced animation fraction where the OS exposes it (Android API 30+), plus a documented client-side fallback (iOS, Android <30, WebGL).
  • Added
    Soft-keyboard action button (NativeEditorAction): the keyboard's action key is delivered as an open vocabulary — Submit/Next/Previous/Newline, with Go/Search/Send/Done all delivering Submit — so a chat composer submits and a form field advances focus, degrading to a synthesized Return with no subscriber.
  • Added
    Native plugins shipped and CI-built: Android UniTextInput.aar, macOS UniTextNativeInputMacOS.dylib (an NSTextInputClient view), Windows UniTextNativeInputWindows.dll (x64 + ARM64), iOS UniTextNativeInput.mm (a UITextInput view), and WebGL .jslib — full native IME and soft-keyboard support in the box, no external SDK.
  • Added
    Automatic hardware cursor: an I-beam appears over text and a hand over links, from a 25-shape internal cursor table mapped to per-platform IDs (Windows OCR_*, macOS NSCursor, WebGL CSS cursor; Linux keeps the default arrow); an interactive range's hover cursor is configurable per modifier in the inspector (serialized hoverCursor, default: link hand).
  • Added
    Multi-format, multi-channel clipboard: one Copy writes many formats atomically and Paste auto-selects the richest understood one — ClipboardFormat.PlainText, Html, Markdown, UniTextSource (lossless UniText vendor format), Url, and Png/Jpeg/Gif images. ClipboardItem pairs a MIME-string ClipboardFormat (with Custom(string) and TryDetectImage) with bytes; bypasses GUIUtility.systemCopyBuffer on the six supported platforms (a plain-text fallback covers anything else).
  • Added
    Format adapters (IClipboardAdapter): per-format translators between UniText markup and external formats, ordered by Priority on paste (lossless UniText source 100, HTML 50, Markdown 40, plain text 0 — the richest present format wins). Four shipping stateless built-ins: UniTextSourceClipboardAdapter, TagHtmlClipboardAdapter, MarkdownClipboardAdapter, PlainTextClipboardAdapter.
  • Added
    Data-driven modifier serialization schemas (ModifierClipboardSchema, ClipboardModifierBindMap): each modifier type declares how it maps to HTML (semantic element, CSS-on-<span>, or attribute) and Markdown (marker or link), registered per type with no assembly scan (IL2CPP-safe). Pre-registered for the 15 shipping modifiers (bold, italic, underline, strikethrough, color, size, font, link, language, line-height, letter-spacing, script position, small-caps, upper/lowercase). Register a custom modifier's mapping with one ClipboardModifierBindMap.Register<T> call — this is the per-modifier extension seam, not custom adapters.
  • Added
    Word / Outlook / Google-Docs interop: the HTML paste parser is a single forward pass with an explicit element stack — it strips leaked <style>/<script> blocks, collapses whitespace the way CSS renders it, resolves entities, closes mis-nested tags, and honors the Windows CF_HTML fragment header — so pasted rich text comes in clean, not as a wall of CSS and blank lines. CSS unit bridging (CssValueFormat) converts px/pt/rem, unitless line-heights, and rgb()/rgba() colors into UniText's own units on paste and emits valid CSS units on copy — including Google-Docs font-weight conventions.
  • Added
    Lossless UniText round-trip (ClipboardFormat.UniTextSource, application/vnd.lightside.unitext): a JSON fragment keyed by modifier signature re-resolves each span by modifier identity and re-emits it in the destination field's own syntax — copy between two of your fields and keep every style byte-perfect, even styles no HTML or Markdown could express.
  • Added
    Style-safe partial copy: copying part of a styled run re-synthesizes the open or close tag the selection cut off, in the pair's original syntax — a half-selected <b> span (or *bold* marker run) copies as valid, still-styled markup instead of a dangling tag.
  • Added
    Paste policies & commands: PlainTextPastePolicy (Auto/Literal/Parse), Paste(), PastePlain() (paste-and-match-style), Cut()/Copy(), async PasteAsync()/PastePlainAsync() (required for programmatic paste on WebGL), and PasteFromItems(IReadOnlyList<ClipboardItem>).
  • Added
    Media clipboard (MediaContent, MediaSource, IMediaClipboardProvider, the MediaReceived hook): receive pasted images and files before the text channels run, and read their bytes per platform (CF_HDROP paths on desktop, content:// on Android, browser blob on web; large files off-thread) — paste a screenshot into a chat field and upload it.
  • Added
    Windows screenshot & image interop: copies write the registered PNG format plus a synthesized CF_DIBV5, and reads convert CF_DIB/CF_DIBV5 back to PNG — so a copied image pastes into Paint/Word and a PrintScreen capture pastes straight into a field.
  • Added
    Provider abstraction (UniTextClipboard facade, IClipboardProvider / IAsyncClipboardProvider): swap the backend for tests or a custom platform behind one interface; static GetText/SetText/GetTextAsync cover the plain-text fast path.
  • Added
    Six native backends: Windows CF_HTML + registered formats + CF_DIBV5 + CF_HDROP; macOS NSPasteboard multi-format writes via the Obj-C runtime (no compiled plugin); iOS UIPasteboard items; Android newHtmlText + a bundled content:// image provider (no androidx dependency); WebGL Async Clipboard API + Web Custom Formats; Linux xclip/xsel/wl-copy — a real OS clipboard everywhere, not Unity's plain-text-only buffer.
  • Added
    Hostile-input hardening (ClipboardBudget): every paste parser caps at 4 MB / nesting depth 64 / 1,000,000 chars and stays linear on degenerate input, so a malicious clipboard payload can't hang or OOM the app.
  • Added
    Apply/toggle styles over a selection, in the style's own markup syntax: ApplyStyle<T>, ApplyStyleRange<T>, SetStyle<T> (range on/off), ToggleStyle<T>, ToggleStyle(BaseModifier, …), RemoveStyleRange<T>, ClearFormatting, InsertObject<T>. Reference-only — a style the field has no configured rule for is a no-op; nothing is ever minted. Toggle-off splits runs so the style survives outside the range, with single-undo semantics.
  • Added
    Pending typing styles (ProseMirror stored-marks model): at a collapsed caret a style command stores a pending style that wraps the next typed text; caret movement discards it, and ArrowKeyEscapesFormatting (off on a bare editable; TextFormattingBehavior turns it on) peels one formatted range at a time at its edge — the ProseMirror/Lexical mark-boundary model.
  • Added
    Toolbar-state queries: IsStyleActive<T>(), TryGetStyleParameter<T>(out …) (false on a mixed selection), ModifiersAtCaret, and a frame-coalesced CaretContextChanged event delivering a CaretContext diffed by modifier signature — bind a formatting toolbar's pressed/color state without per-frame polling.
  • Added
    MarkupVisibilityHidden (tags hidden and atomic but preserved so styles round-trip), RevealActiveRange (reveal the markup of the span under the caret, like a code editor), Raw (literal source view). Caret, selection, hit-testing, and deletion stay correct while tags are hidden — deleting the last styled char removes the whole <b>…</b> pair in one undo step.
  • Added
    TypingMarkupPolicy (Parse/Literal): markup the user types by hand is either recognized or escaped to literal text; affects only keystroke/IME input, not formatting commands or paste.
  • Added
    ChromeRule + IMarkupSelector: style the visible tag delimiters when revealed (AnyMarkup / ByModifier / ByRule, by specificity), with tag characters protected from the surrounding document style; runtime-mutable via MarkupChrome + RefreshMarkup.
  • Added
    Parse rules describe and re-emit their own syntax (ParseRule.Identity, SourceToken, MarkupTriggers/ScanTriggers, CanWrap, Apply, plus the literal-escape contract EscapePrefix/IsEscapable): this is the mechanism that lets one formatting command drive a tag rule (<b>) and a Markdown marker rule (*bold*) alike — the core "it is now an editor" story.
  • Added
    Style identity by modifier signature: toggling Bold finds the field's own <b> style instead of minting one — styles are matched by concrete modifier type, a composite by its ordered child types ({Bold,Italic} distinct from {Italic,Bold}) — and composite styles round-trip losslessly through the clipboard.
  • Added
    Multi-syntax binding (UniTextBase.EnsureStyleFor): one semantic modifier reacts to multiple input syntaxes (<i>, *…*, pasted <em>), merged into a CompositeParseRule on demand — no duplicate style entries.
  • Added
    IFormatStyleSource / ReferencedStyle: the serialized seam a formatting command toggles — pick a modifier in the inspector and a toolbar button applies it in that style's own syntax, inert on fields that lack it.
  • Added
    TriggerWordParseRule: built-in @mention / #hashtag auto-detection with a configurable trigger character, runs at negative priority so explicit markup wins.
  • Added
    Link rules carry a default style: MarkdownLinkParseRule and RawUrlParseRule gained a DefaultParameter merged positionally behind the matched URL, so an auto-detected or [text](url) link applies a fixed visual preset (e.g. a Link;Color;Underline composite) configured on the rule.
  • Added
    Markdown markers follow CommonMark flanking rules (MarkdownWrapRule): a marker opens only when left-flanking and closes only when right-flanking (CommonMark §6.2), and _-markers never toggle inside a word — 2 * 3 and snake_case_name stay literal. In 2.x every marker occurrence toggled.
  • Added
    Per-style enable toggle (Style.Enabled, an eye button on each style row): disable a style non-destructively — skipped by the parser, rendering, and every style query while its configuration is preserved; re-enabling restores its authored precedence among overlapping styles.
  • Added
    Composable, serialized, user-extensible behavior system (InputBehavior): drop-in policy units attached to a field as a [SerializeReference] type-picker list, authored like style modifiers — subclass the base, add serialized fields, override OnEnable/OnDisable to subscribe the editor's extension events. Teardown is fully reversible (an internal Saved<T> slot restores host state on disable).
  • Added
    Documented transaction seam: the four hooks a behavior subscribes — InputFilter (InputEdit), KeyResolver (KeyResolve), KeyboardResolver (KeyboardRequest), MediaReceived (MediaContent). InputEdit carries the inserted text, target range, caret, and a TextChangeReason, with a sticky Rejected — so a whole-field mask can rewrite the entire transaction. Hooks run in order, each seeing the previous result, only on committed text (never IME composition, undo replay, or programmatic writes).
  • Added
    Runtime hot-swap: AddBehavior, RemoveBehavior, GetBehavior<T>(), GetBehaviors<T>(List<T>) flip a field's policy live; shared presets attach via AddBehaviorPreset/RemoveBehaviorPreset.
  • Added
    InputBehaviorPreset: a reusable ScriptableObject field archetype (chat composer, password field, form field). Each editor instantiates a runtime copy so per-instance state never leaks back to the asset; editing the preset asset raises Changed and rebuilds every live field using it in place.
  • Added
    Built-in behaviors (15 concretes; KeyboardBehavior and MediaInputBehavior ship as ready-made bases for authoring keyboard-lifecycle and media-handling behaviors): PasswordBehavior (caret-aligned masking + secure entry + copy blocking + reveal toggle), SingleLineBehavior, LengthLimitBehavior (grapheme-counted, boundary-safe), CaseTransformBehavior (upper/lower/title), InputMaskBehavior (live (###) ###-####-style formatting, paste- and mid-field-edit aware, with RawText), SubmitKeyBehavior (Enter or Ctrl/Cmd+Enter), TabKeyBehavior, NativeKeyboardBehavior, NativeFieldOverlayBehavior, KeyboardAvoidanceBehavior (animated lift, canvas-scale and projection correct), TextFormattingBehavior, AutoValidateBehavior, LinkOnPasteBehavior, StripFormatOnPasteBehavior, CaretContextBehavior.
  • Added
    TextFormattingBehavior: a field's whole formatting layer on one behavior — a configurable Commands list (default B/I/U via FormatCommand), a clear-formatting shortcut (Ctrl/Cmd+\ by default), Toggle(string)/Toggle(int) for toolbar buttons, and ownership of MarkupVisibility / RichPaste / paste policy / TypingMarkup / arrow-key escape / tag chrome while enabled.
  • Added
    CaretContextBehavior + StyleStateHandler: wire a Bold button's highlight with zero code — an edge-triggered handler fires a UnityEvent<bool> only when "would typing now produce this style?" flips.
  • Added
    InputFilterBase: a self-wiring behavior that rejects characters as typed by judging the whole post-edit span (surrogate/grapheme-safe, native-multi-char aware) and requests a preferred mobile keyboard. Built-ins: IntegerFilter, DecimalFilter, AlphanumericFilter (Unicode letters + ASCII digits), EmailFilter, NameFilter (letters + space/hyphen/apostrophe). Numeric filters expose AllowNegative, which both accepts a leading minus and switches the requested keyboard to one that has a minus key.
  • Added
    InputValidatorBase + ValidationState: judge the whole value and publish an open-string status (ValidationStatus.Invalid/Pending; valid = empty status) + message, scheduled by AutoValidateBehavior (OnValueChanged/OnUnfocus/OnSubmit/Always) and wired straight into the field's error visuals. Async-capable (Pending).
  • Added
    TextMeasure / TextLengthUnit: measure and boundary-safe-truncate text by Graphemes (default — a family emoji counts as one), Utf16Units, Utf8Bytes, or Codepoints; LengthLimit published to counters — never the wrong number string.Length gives.
  • Added
    Decorator system (FieldDecorator): composable field chrome reacting to a pushed FieldState snapshot (IsEmpty, IsFocused, IsComposing, LengthLimit, Validation, Box) with attach/dispatch/tick/detach lifecycle; an idle decorator holds no per-frame subscription. Author your own via the type picker.
  • Added
    Built-in decorators: PlaceholderDecorator, FloatingLabelDecorator (Material-style animated label with easing curve/duration, snapping instantly under Reduce Motion), SupportingTextDecorator (helper text that swaps to the validation error message/color), CharacterCounterDecorator (grapheme-aware count/count/limit, recolors at the limit, cached by document version).
  • Added
    UniTextContextMenu (ITextContextMenu): a data-driven, bring-your-own-UI context menu — you build and style the panel/buttons in the scene, the component wires controls to commands, hides inapplicable items, positions and clamps the panel on-screen with an edge-aware pivot, and dismisses on outside click via a generated full-screen blocker. Visibility runs through a CanvasGroup (never deactivating the GameObject) so a FocusGuard on the panel keeps the editor focused.
  • Added
    Menu items (ContextMenuItem, BuiltInContextMenuItems): Cut/Copy/Paste/SelectAll plus custom ActionContextMenuItem (a ButtonUnityEvent) and ToggleContextMenuItem, each with per-item applicability rules (onlyWithSelection on the custom items). Standard commands route through the last-shown presenter so one menu instance safely serves every field; custom items fire their own UnityEvent. Raised on right-click or long-press via UniTextSelectable.RequestContextMenu, or subscribe ContextMenuRequested/ContextMenuDismissRequested for a fully custom or OS-native menu (capability-gated — no copy from a password field).
  • Added
    Gesture recognition (TouchGestureRecognizer, a public state machine now in LightSide.Core): platform-standard single/double/triple tap, long-press, and drag — disambiguating drag-to-select from swipe-to-scroll by tap count, with no single-tap flicker under a double-tap — and time-scale independent. Thresholds are serialized on UniTextSettings, dp-based and DPI-aware: long-press 0.5 s, drag slop 10 dp, multi-tap window 0.3 s / slop 100 dp, desktop multi-click 0.5 s / 8 dp (matching Android DOUBLE_TAP_*, Flutter kDoubleTap*, Windows GetDoubleClickTime).
  • Added
    Magnifier loupe (IMagnifier, shipped DefaultMagnifier): an iOS-style magnification bubble during long-press caret placement and handle dragging, rendered through a secondary camera into a render texture (Built-in RP via Camera.Render, URP/HDRP via SubmitRenderRequest on 2023.1+), with a documented graceful hide where capture isn't possible. Replaceable via the interface.
  • Added
    Selection handles (ISelectionHandles + IInsertionHandle, shipped DefaultSelectionHandles): draggable teardrop start/end handles that extend the selection cluster-by-cluster — never collapsing, swapping roles past the fixed endpoint, showing the loupe during their drag — plus a caret knob that drags the collapsed caret and taps to reopen the menu, all rendered in a canvas-level overlay so the field mask never clips them. Replaceable via the interfaces.
  • Added
    Replaceable touch UI: SelectionHandles, Magnifier, and ContextMenu are component slots on UniTextSelectable — assign the shipped defaults, your own custom-rendered components, or OS-native ones without subclassing; UniTextEditable only drives what's assigned.
  • Added
    UniTextPasteControl: on iOS 16+ overlays a native UIPasteControl to bypass the system paste-permission prompt and capture plain/HTML/lossless-UniText in one tap; falls back to a Button → Paste() elsewhere, raising Pasted either way; display and corner styles (PasteControlDisplayMode/PasteControlCornerStyle) map to Apple's UIPasteControl configuration.
  • Added
    TextPointerEvent: a consumable, reused (zero-alloc) pointer event on UniTextBase carrying the hit result, a PointerTrigger (PrimaryClick/SecondaryClick/LongPress/Hover), a PointerKind (Mouse/Touch/Pen, resolved authoritatively from the new Input System), screen position, camera, and live [Flags] PointerModifiers. Setting Consumed claims the click so a parent ScrollRect never sees it.
  • Added
    New events on UniTextBase: ContextRequested (right-click or touch/pen long-press, mirrors HTML contextmenu/WinUI/SwiftUI), TextLongPressProgress (0→1 for loupe/ring feedback, mouse holds excluded), PointerPressed/Released, PointerEntered/Exited (topmost-raycast model — an occluding child correctly suppresses the hover). No per-frame cost when unsubscribed; Canvas and world-space text raise the same events.
  • Added
    Drag-to-select events on UniTextSelectable (SelectionDragStarted/SelectionDragUpdated/SelectionDragEnded, anchored at the original press position) — kept off UniTextBase so a plain label never captures drags from an enclosing ScrollRect.
  • Added
    UniTextHighlights: named highlight layers over any text component — the CSS Custom Highlight model (selection, find-in-page, hover feedback, custom range marks). One-liner entry: UniTextHighlights.GetOrAdd(text).GetOrCreate("find-results"); layers can also be authored in the inspector. All layers of one render side draw as a single mesh on both Canvas and world-space text; an idle component costs nothing per frame.
  • Added
    HighlightLayer: codepoint ranges (Add/SetRange/SetRanges/Clear) painted as a merged union (overlaps never double-blend). Integer Priority with documented anchors (HighlightPriorities: Interactive −10, Custom 0, Selection 100, FindCurrent 200, Diagnostics 300), Order (behind/above text), Visible, animation-friendly Opacity (re-tints in place, no geometry rebuild), and edit-sticky rangesStickiness (GrowsAtEdges (Monaco), Shrinks, RemoveOnOverlap) maps ranges through every edit of a sibling UniTextEditable, tracked in document space and remapped under hidden markup (public MapThroughEdit lets an external document backend — a CRDT, a network host — drive the same mapping).
  • Added
    HighlightStyle: the layer's presentation — Paint (an inline colour or a solid/gradient swatch from the paint catalog), Height (LineBox / Content / LineAdvance), padding, corner radius, merge threshold, and BoxBreak (Slice/Clone/Block — Block collapses a range to one rounded rect for the code-block look).
  • Added
    Handle-based render backend (TextHighlightRenderer, HighlightGroup, HighlightRect): individually mutable rects with per-rect color, rounded corners, gradient paint, and a free CustomData (uv1) channel for authoring custom highlight shaders — yet a side's layers collapse into one draw call. Steady-state repaints mutate live rects in place, and a stale handle throws ObjectDisposedException instead of silently aliasing another rect.
  • Added
    Find & read API on every text component: UniTextBase.FindAll(query, comparison, results) — allocation-free for queries up to 64 chars, codepoint-correct, case-sensitive or -insensitive per the passed StringComparison (bundled Unicode simple case mappings; culture values compare ordinally), returning ranges that feed highlight layers and GetRangeBounds directly — plus the CodepointCount/RenderedCodepoints read surface.
  • Added
    Interactive ranges are now configuration, not a subclass: InteractiveModifier is a concrete, inspector-pickable modifier (new InteractiveModifier { RangeType = "mention" }) running a per-range Normal/Hovered/Pressed state machine, consuming pointer events, and exposing six events (RangeStateChanged, RangeClicked, RangeContextRequested, RangeEntered, RangeExited, RangeLongPressProgress) plus GetRangeState and a protected IsRangeEnabled override to disable ranges dynamically (visited links, permission gates) — the web LVHA / UIKit UITextItem / Android ClickableSpan model with no registry.
  • Added
    Range stylers (RangeStyler): the presentation seam assigned to InteractiveModifier.Styler — state transitions, activation flashes, and long-press progress arrive as hooks, typically drawn through highlight layers; idle stylers hold no per-frame subscription. StateHighlightStyler is the shipped default: animated hover tint, instant pressed fill, activation flash, optional long-press buildup — all Opacity fades, no geometry rebuild — plus a NormalEnabled persistent chip layer under the state layers (the Material state-layer model: a mention chip is one InteractiveModifier with this on, rebuilt at parse rate, never per frame).
  • Added
    Built-in interactive modifiers: the new SpoilerModifier (Discord/Telegram tap-to-reveal covers — RevealChanged, SetRevealed/ConcealAll/IsRevealed, per-range reveal reconciled across re-parses, animated covers via the shipped SpoilerCoverStyler) and LinkModifier rebuilt on the state machine: base colour/underline styling moved out to composition, an iOS-style grey pressed flash via its default StateHighlightStyler (its 2.x LinkClicked/LinkEntered/LinkExited events and AutoOpenUrl — on by default — carry over) — both worked examples for authoring your own stateful interactive text.
  • Added
    Selection style: UniTextSelectable.SelectionStyle (a HighlightStyle) drives the reserved "selection" layer at HighlightPriorities.Selection — recolor, round, or gradient-fill the selection bar from one serialized field.
  • Added
    One paint system for every layer (UniTextPaints catalog, PaintSwatch, IPaintProvider): a named swatch is a solid color, a Gradient, or a Texture2D, referenced straight from markup (<fill=ember>, <stroke=gold>) or configured on a modifier. The same paint drives fills, strokes, shadows, glows, and underlines.
  • Added
    Texture-filled text: any layer can be painted with an image — chrome, marble, foil, animated sheets — with PaintFit (Stretch/Contain/Cover/Tile).
  • Added
    Gradients rebuilt per-pixel: 2.x evaluated a gradient at the four corners of each glyph quad; gradients now sample a shared ramp atlas per fragment, so radial and conic shapes stay correct inside every glyph. New PaintMapping frames (Block across the text, Line per line, Glyph per glyph, Range across the styled span) plus scale/offset join the 2.x Linear/Radial/Angular shapes and angle.
  • Added
    FillModifier: fills the glyph interior with a paint; the first fill claims the suppressed base quad (no extra geometry), additional fills stack above it.
  • Added
    StrokeModifier (replaces the old outline blob): a true rim band with continuous Align (−1 inside → 0 centered → +1 outside; legacy inside/center/outside keywords still parse), Width, Softness, and a CornerStyle (artifact-free Round, or Sharp mitered up to MiterLimit — shared by the layer base, so shadows and glows get the same corner treatment) — from an MTSDF two-distance read, works with the fill off, and takes any paint.
  • Added
    GlowModifier (paintable soft halo, Radius), ShadowModifier rebuilt with independent Offset/Blur/Spread and any paint (a real blurred/spread/gradient shadow, not a hard offset copy), and InnerShadowModifier (inset shadow via a second SDF tap — embossed/letterpress depth, crisp at any size).
  • Added
    Paintable decorations (BaseLineModifier): underline & strikethrough gained per-range paint (color/gradient/texture, defaulting to the text fill like CSS text-decoration-color: currentColor), and the 2.x line-style set (solid/double/dotted/dashed/wavy, thickness, offset, skip-ink) gained serialized defaults and public properties (Style, Thickness, Offset, SkipInk) — CSS Text Decoration Level 4.
  • Added
    GlyphResolutionModifier: raises the atlas tile resolution of the glyphs in its range (ResolutionBoost 0–2) for crisp large headings without bumping the whole font's atlas.
  • Added
    UnitValue / UnitVector2: the px/em/%/delta parameter grammar became a serialized value+unit type — an inspector default now carries its unit, and every effect geometry parameter (stroke width, glow radius, shadow offset/blur/spread) takes px or em: pin to px for constant on-screen size, or em to scale with the glyph.
  • Added
    Serialized defaults + public property + per-range override on every modifier: a bare tag (<b>, <glow>, <color>) takes the inspector-configured default; an inline value overrides it — and each parameter is now a real C# property (ColorModifier.Color, BoldModifier.Weight/Mode, SizeModifier.Size).
  • Added
    PaintLayerModifier extension point: fill/stroke/shadow/glow/inner-shadow all derive from one layer that resolves the paint, precomputes gradient/texture mapping per range, and stamps coverage quads — a custom effect gets swatch resolution and the full override chain for free, and a layer's overlapping ranges (nested tags) resolve innermost-wins once per codepoint (O(1) per glyph, not per glyph×range).
  • Added
    Phase-driven model (IPhaseDriven): a modifier's visual state is a pure function of its Phase and never advances itself — drive it from the drop-in UniTextPhaseDriver (free-running Speed, optional UnscaledTime, scrubbable Phase), a tween, Timeline, an Animator (PhaseAnimationHandler), or code. The same phase always renders the same frame, so scrubbing, rewinding, and multi-instance sync all work.
  • Added
    Eleven built-in glyph animations (up from one wobble in 2.x), each configurable per range and most staggered along the text by a per-cluster Spread: WaveModifier, BounceModifier, PulseModifier, ShakeModifier, WobbleModifier, SpinModifier, PendulumModifier, FloatModifier, plus three effects that normally need custom shaders — GlitchModifier (RGB-split bursts), ScrambleModifier (hacker-style decode, grapheme-correct, no reflow), and RollingModifier (odometer / split-flap wheels — drive Roll toward 0 to settle).
  • Added
    Per-glyph reveal animation (RevealModifier.GlyphRevealing): the 2.8 typewriter modifier gains a mesh-build hook — each rendered glyph of a revealing range arrives as a RevealGlyphInfo (ordinal, count, front, fractional Progress), so fade/scale/drop-in appearance is a few lines of quad mutation; while subscribed, the frontier cluster renders at fractional progress instead of popping in whole.
  • Added
    Author your own (GlyphParamModifier<TParams> + the static GlyphQuad helpers): a phase-driven per-character effect in ~50 lines, riding the same driver/scrub/Timeline machinery as the built-ins; OnGlyph runs allocation-free during mesh generation, worker-thread-safe.
  • Added
    Write once, run everywhere: author a single half4 UniTextEffect(UniTextFrag i) holding only visual logic and the template shells wire it into every pass — Canvas + world-space, Built-in + URP, SDF/MSDF/emoji. The UniTextFrag struct exposes sdfAlpha/signedDist/glyphUV/tileId/tileHash/userA/userB/positionWS, plus an optional vertex hook and a world shadow-caster program. Custom effects on world text — Built-in or URP — did not exist in 2.x.
  • Added
    New authoring templates: every bundled effect (Rainbow, Dissolve, Hologram) now ships as a Canvas shell and a world/URP shell over a reusable logic include, plus a blank UniText_Effect-Example.hlsl starting point; shared building blocks live in UniText_EffectLib.hlsl.
  • Added
    One-click scaffolding (Assets > Create > UniText > Custom Effect, renamed from Custom Material Shader): one click writes the full trio into the selected folder — the pipeline-neutral effect include plus pre-wired Canvas and World shader shells, include paths resolved and a unique shader name stamped in — so a new effect compiles for every pass the moment it lands.
  • Added
    Cold-glyph rasterization now costs the main thread almost nothing. The async GPU rasterizer (shipped in 2.10.0) is rebuilt end-to-end — Burst outline extraction and contour union, CPU-precomputed 2D binning, per-font batching — leaving the main thread only a short dispatch while the GPU fills the atlas asynchronously. Measured (Galaxy S21 FE 5G, Adreno 660, Vulkan, IL2CPP release build), cold-atlas fill of a 3,029-glyph multilingual NotoSans corpus (TextMeshPro/UI Toolkit filled 3,091/3,092): UniText ≈ 33 ms of main-thread dispatch vs TextMeshPro 8,635 ms and UI Toolkit 6,446 ms of synchronous main-thread work — a 263× / 196× reduction in main-thread cost (per-glyph: 10.8 µs vs 2,793.6 / 2,084.8 µs). The atlas finishes filling ≈ 2.1 s later without ever blocking a frame. A decorative 609-glyph RubikStorm fill: 96 ms vs 2,025 ms (TMP) / 826 ms (UI Toolkit).
  • Added
    Binned GPU rasterizer (UniTextGlyphRaster.compute + GpuRasterPrep): CPU-precomputed 2D binning — 16 row-bands and a 16×16 control-hull cell grid — so each GPU texel evaluates only the local candidate segments instead of the glyph's whole outline. Heavy fonts (CJK, decorative, complex Arabic) rasterize far faster than v2's single 1D scan.
  • Added
    Burst contour resolution (ContourUnionBurst): a new zero-allocation [BurstCompile] stage on raw pointers resolves self-intersecting and stroked outlines up front; cleanly-resolved glyphs are tagged so the SDF/MSDF sampler skips the per-pixel silhouette heuristic 2.x ran for every glyph.
  • Added
    Parallel CPU rasterization (GlyphBatchRasterizer + per-font worker fan-out): a frame's glyph requests batch per font; fonts render concurrently, and within one font outline extraction and Burst SDF generation spread across every core — ≈ 9.7× over single-threaded on a 12-core desktop (3,029 NotoSans glyphs: 220 ms vs 2,138 ms; Editor measurement of the CPU-raster path). Platforms without compute support (WebGL) take this path automatically.
  • Added
    GPU raster robustness: GraphicsFence-tracked completion polled at frame granularity, a submission-cost budget (250 ms mobile / 500 ms desktop), and a DeviceResetSentinel that re-arms the atlas after a graphics-device loss or scene-reload — text survives alt-tab/driver resets without corruption.
  • Added
    Atlas growth tapers: past 16 slices the glyph atlas grows +25 % per step instead of doubling, and pre-allocation discounts reusable page area — large multilingual atlases stop overshooting their memory footprint, and a double-allocation crash risk on the CPU-raster path with very large glyph sets is gone.
  • Added
    Burst-compiled analysis with a byte-identical fallback: the four Unicode analysis passes — UAX #9 BiDi, #24 script, #29 grapheme, #14 line-break — each run as a single [BurstCompile], auto-vectorized kernel exposed as a FunctionPointer, with the exact same source available as managed IL where Burst is unavailable. Output is bit-for-bit identical either way (integer-only logic) — speed with zero behavioral risk. (These are Burst auto-vectorized kernels, not hand-written SIMD intrinsics.)
  • Added
    Per-paragraph, parallel, cached analysis: because the kernels are FunctionPointers (not Unity IJobs) they run on the WorkerPool threads; analysis is paragraph-scoped, and a content-addressed ParagraphAnalysisCache (XxHash64) reuses resolved levels/scripts/line-break and grapheme boundaries so editing one line never re-analyzes the document. In 2.x each document was analyzed whole, on one thread, uncached.
  • Added
    Unchanged paragraphs are never re-shaped (ParagraphShapeCache): each paragraph is fingerprinted over its codepoints plus everything that feeds shaping (font size, per-range font/language data, hidden markup), and a hit replays the stored pristine HarfBuzz runs and glyphs — the Blink shape-cache model. Typing re-shapes only the edited paragraph. Android release build, 100 multilingual objects: an incremental edit rebuilds in 43 ms vs 69 ms for the all-new cold path.
  • Added
    Cross-document word-shape cache (WordShapeCache): shaped words are reused across every text on the engine (XxHash64-keyed by codepoints, font, variation axes, script, direction, language) — changing labels, counters, and chat logs that share vocabulary splice stored glyphs with no HarfBuzz call at all. Only words HarfBuzz marks safe-to-break on both boundaries are cached — the exact per-font condition under which the isolated word shapes identically to its in-context form — and entries hold font-unit glyphs, so they survive font-size changes; a miss shapes the whole run in context, byte-identical to the uncached path.
  • Added
    One document parallelizes too: the parallel pipeline (on by default since 2.x) fanned out per component; analysis, shaping, and layout are now paragraph-scoped, so one large text also spreads its bidi/script/break/shaping work across every core — per-paragraph shape jobs from every dirty component flatten into a single worker-pool run. Release build (Galaxy S21 FE 5G, Vulkan, IL2CPP), cold rebuild of 100 multilingual objects: UniText 69 ms vs UI Toolkit 167 ms — both HarfBuzz-shaped, like-for-like — and TextMeshPro 455 ms (TMP does not shape Arabic/bidi; its output is not equivalent). Editor, 12-core Ryzen 9 5900X: 92 ms parallel vs 351 ms single-threaded (≈ 3.8×).
  • Added
    Viewport-windowed rendering (UniTextBase.VisibleWindow): a local-space window bounds mesh emission — paragraphs fully outside it produce no quads, while layout, selection, caret, and hit-testing stay whole-document. Canvas text feeds it automatically from the enclosing RectMask2D clip rect, so a long document in an input field or ScrollRect only meshes the visible band (a half-window hysteresis margin makes small scrolls free); set it explicitly for custom virtualized scrollers (chat, logs) — null renders everything.
  • Added
    Pure-LTR BiDi fast path: all-LTR text (Latin, Cyrillic, CJK — the common case) short-circuits the full X1–L1 bidirectional cascade after one classification scan, a fast path 2.x had no equivalent of.
  • Added
    Native-memory Unicode tables: the whole Unicode character database moved into NativeArray<T> (Persistent) — off the GC heap, shared by both the Burst and fallback paths.
  • Added
    Trigger-scan markup parsing: every ParseRule declares the characters that can start its match, and the parser bakes their union into an ASCII jump table — the match walk skips runs of plain text without probing any rule, degrading to the 2.x linear walk for exotic trigger sets.
  • Added
    Single source per algorithm: each UAX rule now exists in exactly one place, collapsing the engine classes to thin wrappers (BidiEngine 2,287 → 402 lines, LineBreakAlgorithm 680 → 122) — less surface to break, zero drift between fast path and fallback.
  • Added
    Unified coverage × paint SDF pipeline: every glyph quad picks a CoverageMode (Fill/Stroke/Shadow/InnerShadow) and a PaintKind (Solid/Gradient/Texture); plain glyphs leave both at 0 and pay nothing, while styled text composes Photoshop-style layer effects on the GPU with one draw call per material. Backed by UniText_Coverage.hlsl + UniText_Paint.hlsl and shared decode includes (UniText_AtlasDecode.hlsl, UniText_SdfSample.hlsl) that de-duplicate four hand-copied decode paths.
  • Added
    Lit world text is a checkbox (UniTextWorld.Lit): one per-component toggle renders the text with scene lighting — the lit SDF/emoji materials are selected automatically, and adjacent instances can independently be lit or unlit. In 2.x this required hand-assigning the shipped lit materials through a custom-material style. CastShadows (independent of Lit, off by default) opts the batched renderer into shadow casting, resolved per sorting context — 2.x lit text always cast, with no supported way to turn it off.
  • Added
    Unlit fast path on the Lit shaders: _LightInfluence 0 branches around the whole lighting computation (UNITY_BRANCH on a uniform — no extra keyword or variant), instead of 2.x's compute-then-lerp — a world-text material dialed to unlit pays no per-fragment lighting cost.
  • Added
    World shaders ship via the settings asset: UniTextSettings now retains the world-space UniText/Lit/SDF / UniText/Lit/Emoji and Canvas UniText/UI/Highlight shaders (six required-shader slots, up from three), so world text and highlight materials resolve in builds without touching Always Included Shaders; the new Include Lit Shaders toggle (Project Settings → UniText → Rendering, default on) drops the Lit pair from builds that use no lit text.
  • Added
    Shared gradient-ramp atlas (GradientRampAtlas): one 256-wide row per unique gradient, deduped by content and ref-counted, uploaded row-granularly — crisp gradients that stay allocation-free and animate at one texture-row upload per frame, shared across all text and highlights on the device.
  • Added
    Rounded, gradient highlight shaders: the world UniText/Highlight gained a rounded-rect SDF with gradient-ramp paint, and a new Canvas UniText/UI/Highlight provides the uGUI counterpart — rounded, gradient-filled selection/find highlights on both backends.
  • Added
    UniTextAnimationBridge: a sibling component that makes Unity Animator-driven changes update the text — add it next to a UniText/UniTextWorld and add the handlers for the fields and modifiers you animate; text without the component does no per-frame diff work.
  • Added
    Component field handlersUniTextFieldsAnimationHandler (font size, colour, word wrap, auto-size with min/max, alignment) and UniTextWorldFieldsAnimationHandler (the same, plus sorting order and layer).
  • Added
    Modifier handlersColorAnimationHandler, SizeAnimationHandler, LetterSpacingAnimationHandler, GlowAnimationHandler, ShadowAnimationHandler, StrokeAnimationHandler, and PhaseAnimationHandler (keyframe a phase-driven glyph animation — binds the host's first IPhaseDriven modifier), each with the correct per-parameter invalidation.
  • Added
    AnimationHandler / ModifierAnimationHandler: [SerializeReference] extension points — subclass to animate a custom component's fields or a custom modifier's [Parameter] fields from an Animator.
  • Added
    Inline Scene-view text editing: double-click any UniTextBase in the Scene, press F2, run Tools/UniText/Edit Text in Scene, or click the inspector's "✎ Edit in Scene View" button to open a live editing session — caret, selection, drag-select, word/paragraph click, keyboard nav, clipboard, undo/redo, and OS IME (candidate window anchored to the Scene-view caret) — all driven by the runtime engine, with a floating formatting overlay (B/I/U, colour, markup visibility) and a right-click menu. On exit the edited markup is written back through Undo.RecordObject, and every helper component the session spawned is removed, leaving the GameObject graph exactly as it was.
  • Added
    Automatic v2.x → 3.0 asset migrations: nine concrete IMigration classes upgrade serialized scenes/prefabs/assets on first load (byte-preserving YAML splices, auto-run on version change, no prompts, CI excluded) — Outline→Stroke, Gradient→Fill, Wobble→Wave, the gradient-provider trio → InlinePaintProvider/AssetPaintProvider/GlobalSettingsPaintProvider, a bold-weight sentinel fix, DefaultTextHighlighterStateHighlightStyler, and legacy UniTextGradients catalogs → UniTextPaints. A breaking major upgrade that costs the user nothing.
  • Added
    Custom inspectors for UniTextInputField, UniTextEditable, UniTextSelectable, and InputBehaviorPreset, backed by the shared [SerializeReference] type picker with grouped, icon-labelled, description-carrying "Add" menus for behaviors, decorators, filters/validators, and context-menu items. InputBehaviorPresetEditor audits preset assets for behaviors pointing at scene objects.
  • Added
    New property drawers: PaintSwatchDrawer, UnitValueDrawer (value + unit dropdown), InheritableFieldDrawer (NaN inherit sentinel), ChromeRuleDrawer, FormatCommandDrawer, and ModifierBodyDrawer (shows a modifier's [Parameter] fields as an opt-in override list — a field appears only when it differs from the default, and edits route through the live C# property for scoped invalidation).
  • Added
    Clipboard Inspector window (Tools/UniText/Clipboard Inspector): shows every format a copy wrote — plain text, UniText source, HTML, Markdown, URL, custom MIME, images (with a decoded preview) and files — with counts, decoded payload, and whitespace visualization.
  • Added
    FocusGuard (in LightSide.Core): marks a UI hierarchy (formatting toolbar, context panel) focus-preserving, so pressing its controls neither defocuses the editor nor drops the selection — the toolbar contract of a word processor. One drop-in component; FocusGuard.PointerIsOverGuarded() recovers the press target.
  • Added
    UniTextFocusable: a hidden, runtime-managed Selectable that makes an editable surface a Tab stop (Navigation.Mode.Automatic) while a selection-only surface stays pointer-focusable but Tab-excluded (the web model) — reconciled automatically, never added by hand.
  • Added
    Accessibility preferences: Accessibility.PrefersReducedMotion (LightSide.Core, with a change event; honored by FloatingLabelDecorator and any driver that consults it — maps to iOS/macOS Reduce Motion, Android Remove Animations, web prefers-reduced-motion) and InputCaretRenderer.PrefersNonBlinkingCaret (suppresses caret blink — iOS "Prefer Non-Blinking Cursor"). The app sets the flags from platform prefs.
  • Added
    One-click scene scaffolding: GameObject → UI (Canvas) → UniText → Selectable Text / Editable Text / Input Field instantiate the project's own prefab from UniTextSettings.{SelectableText,EditableText,InputField}Prefab (the Unity way — a designer's configured prefab, not a code-built hierarchy), provisioning a Canvas + EventSystem (wiring InputSystemUIInputModule under the new Input System) and warning when a slot is empty.
  • Added
    Shipped Defaults/ assets: Selectable Text, Editable Text, Input Field prefabs plus the three selection-handle sprites for the touch UI.
  • Added
    New EditableText sample: a complete working editor scene — the selectable → editable → input-field ladder, an InputBehaviorPreset, a password field with a reveal toggle, a caret-context formatting toolbar, and chat-style media paste turning pasted images into attachment cards.
  • Added
    Expanded Basic Usage sample: auto-detected @mention/#hashtag chips, tap-to-reveal spoilers, a live-swappable custom RangeStyler, Find-in-Page over FindAll painted onto highlight layers, and a LanguageFonts/ set covering 17 non-Latin scripts. The Modifiers sample is renamed Styles; FontExamples gains a variable-font EdgeCasesFonts/ set (Comfortaa and Outfit variable-weight fonts).
  • Added
    CI: a GitHub Actions urp-compile-check matrix imports the package into URP 12.1 / 14.0 / 17.0 projects and fails on any shader import error, and three workflows build the Windows/macOS/Android native input plugins reproducibly from committed source; LightSide.Core adds its own workflows for the GPU-upload and profiler-sampler plugins — shaders proven across three URP majors, and native binaries transparent, not opaque blobs.
  • Added
    Optional Input System: documented as an optional dependency (UNITEXT_INPUTSYSTEM via versionDefines) — works with the old Input Manager, the new Input System, or both. Native text input works regardless either way.
  • Changed
    UniText now depends on media.lightside.core 1.0.0 and is layered on top of the shared foundation extracted into that package (see Two packages). Source-compatible apart from two utility renames (UniTextArrayPool<T>ArrayPool<T>, RangeEx.WholeText/IsWholeTextRangeEx.All/IsAll); the moved types kept namespace LightSide.
  • Changed
    IParseRule interface replaced by the ParseRule abstract class (breaking, no alias). Custom parse rules now subclass ParseRule and override TryMatch; the base carries the syntax-introspection contract (Identity, SourceToken, MarkupTriggers/ScanTriggers, CanWrap, Apply) that lets the editor apply/toggle/round-trip a style in its own markup.
  • Changed
    StyleCore gained editor-grade introspection: ParseRule now describes and re-emits its own syntax (all public — see Rich-text formatting), and UniTextBase gained EnsureStyleFor and GetModifier(Type) — the surface that powers the formatting toolbar. Style addressing is by modifier signature (a concrete exemplar, resolved internally); there is no name-based style lookup.
  • Changed
    Pipeline events are per-component now: new UniTextBase instance events — FrameUpdated (once per frame while the component updates; the tick range stylers and modifier animations subscribe to) and LayoutCommitted (fires only when this component reprocessed and its glyph geometry is final — the moment to rebuild coordinate maps, caret geometry, or overlay positioning) — replace the removed public static UniTextBase.BeforeProcess/MeshApplied/AfterProcess (breaking, no alias). A subscriber now pays only for the component it watches, never for every text update in the scene.
  • Changed
    InteractiveModifier went from an abstract, subclass-required handler to a concrete, inspector-pickable modifier with a per-range state machine, six range events, and a RangeStyler seam (see Highlight layers, find-in-page & interactive ranges). Range pointer events moved off UniTextBase onto the modifier.
  • Changed
    Pointer/click events on UniTextBase use the TextPointerEvent type. TextClicked now carries a consumable TextPointerEvent (device kind, modifiers, trigger, consume flag) instead of a bare TextHitResult; the previous base range pointer events (RangeClicked/RangeEntered/RangeExited, IsHoveringRange/CurrentHoverRange) were removed in favor of InteractiveModifier's events.
  • Changed
    UniTextBase.HitTest/HitTestScreen renamed to HitTestRange (range/bounding-box "which entity is here" semantics), distinct from the new caret hit-testing (HitTestCaret).
  • Changed
    Style modifiers rebuilt around the paint system: the whole visual half of StyleCore now runs on one paint abstraction (fills/strokes/shadows/glows/decorations take any solid/gradient/texture paint), every parameter gained a serialized default + public property + per-range override, and files reorganized into TextStyle/Layout/Appearance/Utility/Decoration/Effects/Media subfolders (see Text effects & paint).
  • Changed
    Highlight rendering moved from a flat single-color rect list to named, prioritized highlight layers: the TextHighlighter/DefaultTextHighlighter family and the serialized highlighter field on UniTextBase were removed — selection visuals via UniTextSelectable.SelectionStyle, interactive-range feedback via RangeStyler, everything else through UniTextHighlights.
  • Changed
    The world batcher groups by sorting context, not material: a batch is keyed by (sorting layer, order, SortingGroup, GameObject layer, scene) with materials demoted to sub-meshes, so one component's cross-material layers — emoji beside SDF glyphs, texture-paint fills, per-style custom materials — draw in exact Styles order, the way Canvas sibling order does (in 2.x each material was its own batch mesh, with unspecified relative order). Inside a context, entries pack into size-bounded shards (target raised 8,192 → 16,384 vertices, tunable via UniTextSettings.WorldBatcherShardTargetVertexCount) so a structural change re-bakes only the owning shard; index buffers escalate UInt16 → UInt32 automatically past 65,535 vertices.
  • Changed
    UniTextDirtyFlags renamed to UniTextDirty (breaking, no alias) and re-cut to name pipeline stages instead of properties: ColorMesh, AlignmentPositions, FontSize folded into the existing Layout; Text/Font/Direction/Material keep their names; Sorting and the LayoutRebuild composite were removed (sorting propagates through events, not a rebuild flag). Recompile against the new enum; do not persist raw values.
  • Changed
    Text animation is now phase-driven, not self-animating: v2's WobbleAnimationModifier (read Time.time, rebuilt every frame) is renamed WaveModifier (auto-migrated, speedfrequency) and re-cut as a pure function of Phase — controllable and scrubbable instead of a fire-and-forget frame-eater (see Kinetic text animation).
  • Changed
    Animator support is opt-in via UniTextAnimationBridge: animating a component's own fields now requires the bridge with the matching field handler, instead of every Animator-driven component diffing its fields automatically.
  • Changed
    The GPU glyph-upload backend was rebuilt (ABI v3) and moved to LightSide.Core as the public GpuUpload API, with device-loss and multi-backend (D3D/Metal/Vulkan/WebGL2) handling.
  • Changed
    3D text shadows match the visual: the shadow casters now cast each visible layer's coverage, so an outlined or glowing glyph throws a shadow shaped like what you see, not the bare core glyph.
  • Removed
    Legacy gradient & outline modifiers: GradientModifier, OutlineModifier, IGradientProvider/IHasGradientProvider, and the Inline/Asset/GlobalSettings gradient-provider family — superseded by gradient paints on any layer via IPaintProvider and by StrokeModifier — along with the UniTextGradients catalog asset and UniTextSettings.Gradients (superseded by UniTextPaints and UniTextSettings.Paints; a migration converts each legacy catalog into a -Paints sibling asset). The <gradient>/<outline> tags survive as deprecated parse rules so existing markup still renders; the removed types have no alias (asset data auto-migrates).
  • Removed
    TextHighlighter / DefaultTextHighlighter and the serialized Highlighter field on UniTextBase — replaced by highlight layers, SelectionStyle, and RangeStyler.
  • Removed
    Interactive-range dispatch types InteractiveRangeRegistry, IInteractiveRangeProvider, IInteractiveRangeHandler, and InteractiveRange.priorityInteractiveModifier now self-subscribes to the pointer surface; overlapping ranges from different modifiers all dispatch, in registration order.
  • Removed
    Static pipeline events UniTextBase.BeforeProcess/MeshApplied/AfterProcess (breaking, no alias) — replaced by the per-component FrameUpdated/LayoutCommitted (see Changed).
  • Removed
    AnimationHandlerBase<T>: extending the Animator diff is now done by adding a field/modifier handler to a UniTextAnimationBridge.