Support

Frequently Asked Questions

Straight answers about build size, fonts, the glyph atlas and runtime performance — distilled from real developer questions.

Build Size & Distribution

Does UniText bloat my build size? What actually ends up in the build?

No. UniText doesn't bake per-font texture atlases into your build. Glyphs are rasterized at runtime into a shared atlas (Texture2DArray-based) with per-glyph reference counting and LRU eviction. Only the font data ships in your build — never atlases. On top of that, built-in Font Compression (Zstd, level 22) shrinks font data by up to ~2.7× for Latin/Arabic (CJK is more modest, ~1.3×).

Do I need to keep and ship the .ttf files?

No. The UniTextFont asset embeds the font bytes inside itself. The reference to the source font is Editor-only (wrapped in #if UNITY_EDITOR) — it simply doesn't exist in a player build, so it's not a runtime dependency and won't be pulled into an AssetBundle through dependency lookup. You can delete the .ttf once the asset is created.

Do I even have to import the .ttf into my project?

No. You can build a UniTextFont from a .ttf located anywhere on your disk via the UniText tool window (the file dialog reads the bytes directly). Importing the font into the project is optional.

How much does UniText actually add to an empty build?

• WebGL: an empty build is ≈ 6–10 MB (it varies by Unity version and installed packages); UniText adds its native libraries (HarfBuzz, FreeType). These are the same industry-standard libs Unity's own UI Toolkit Advanced Text Generator ships — nothing exotic (UniText just carries its own copies). • Android: an APK with two architectures carries both — about +4 MB per architecture. But when you publish an AAB, Google Play delivers a device-specific bundle, so the user downloads only their own architecture (~4 MB), not both.

How does UniText compare to TextMesh Pro for CJK or large fonts?

With TMP in Static atlas-population mode, baking the full glyph set of a typical CJK font bloats the build by tens to hundreds of MB (several 4096²/8192² atlases; expect ~80–250 MB depending on glyph count and resolution). The key point: UniText's overhead doesn't scale with glyph count — it's the same few MB whether you ship Latin or full CJK.

Can I load fonts dynamically from a server or CDN?

Yes — that's on the developer's side, and UniText doesn't get in the way. Download the font bytes with your own code (UnityWebRequest) and build a UniTextFont from them at runtime via CreateFontAsset(byte[]). To trim a font down to just the characters you need (subsetting), use UniText's build-time tool. There is no built-in CDN auto-loader in the package — that's deliberately the project's responsibility, not the engine's.

Fonts & System Fonts

Can UniText use the OS system font? What does it cost in memory and startup?

Yes — SystemFont reads the font file straight from the OS at runtime, so it has no effect on build size (nothing is baked). Resolution is lazy (on first use), so it doesn't slow down app startup.

Does WebGL support system fonts?

No — and that's a browser limitation, not a UniText one. The browser sandbox doesn't expose OS fonts. For WebGL, embed a font in a UniTextFont asset (or load the bytes yourself — see the CDN question above).

Glyph Atlas — SDF Detail & Tile Size

What do “SDF Detail” and “Tile Size Offset” actually do?

Glyphs live in tiles of three sizes — 64, 128, 256 — and the size is chosen from contour complexity (many segments → 128/256, a simple contour → 64). • SDF Detail is a multiplier on the complexity estimate. Raise it and an already non-trivial glyph is more likely to move up to a larger tile. (A genuinely simple shape stays at 64 — the multiplier only affects glyphs above the threshold.) • Tile Size Offset is a hard step along the size ladder (chosen 64 + offset 1 → 128; range −2..+2).

Which of the two should I prioritize for performance?

Neither in isolation — both only decide the final tile size, and the cost is set by that size alone. Two combinations that resolve to the same tile size cost exactly the same. Aim for the smallest tile size that still gives acceptable quality.

Runtime Performance

When does UniText rasterize glyphs? Does it copy memory every frame?

Only when a glyph isn't already in the atlas — never per frame. For “Hello world” it rasterizes the unique glyphs H e l o w r d. A second H does nothing; an a rasterizes just the a.

Why is there a brief hitch when a lot of new text appears at once?

The cost is proportional to the number of unique, first-seen glyphs rasterized that frame — not to the length of the text. In our benchmark ~2000 unique glyphs on the first frame is ≈ 1 second on a typical Android device; TMP and UI Toolkit take 10+ seconds in the same scenario. Reusing cached glyphs is free.

I don't see a UniText job in the Profiler — is multithreading actually working?

UniText barely relies on the Unity Job System on the hot path — by design. Glyph rasterization (our proprietary lpSDF / lpMSDF) runs on the GPU via a compute shader by default; Burst jobs are only the CPU fallback when compute isn't available. The main multithreading — shaping, layout, mesh generation — runs on UniText's own OS-thread pool (not Unity Jobs, which is why they don't show up in the Jobs profiler). So “no UniText job” doesn't mean “threads are off”: either the work went to the GPU, or the atlas is already warm, or the text is below the parallelism threshold (short or single labels are processed sequentially — it's cheaper that way).

Does MonoBehaviour.Update() hurt performance? Do you need a single UniTextManager?

“Update() is slow” is a myth for release builds. The overhead of the magic-method call from native code is real but negligible (~1000 calls ≈ 0.16 ms) — it's not the bottleneck. The real cost in naive code is AoS memory layout, not the callback itself; UniText applies SoA where it matters (Unicode attributes, metrics, per-pixel buffers). (This is also why ECS is fast — because of SoA, not “ECS magic”.) And UniText is centralized anyway — just not as a MonoBehaviour manager: Update() itself is nearly empty, and the heavy work is gathered into a single batched pass on Canvas render callbacks (which is also what gives cross-component parallelism).

Didn't find your answer?

Ask in our Discord — questions like these are exactly what shaped this page, and we usually reply fast.