English | 日本語
Font utilities for Unity UI Toolkit editor UIs: resolves a monospace font and a Latin+CJK UI font that are guaranteed to actually load, applies them with the one composition pattern that survives UI Toolkit style inheritance, and keeps free-form text from tripping known TextCore rendering traps.
Zero dependencies. Editor-first (Unity 2022.3 LTS or newer; the runtime assembly is structurally prepared for future runtime support). MIT.
UI Toolkit editor UIs on Unity 2022.3 share a set of font failure modes that are easy to hit and hard to diagnose. Some of them the resolvers avoid for you automatically; for the others this package gives you a one-call helper or a test to wire in — each item below says which.
ApplyCjkUi on your window root.Font.CreateDynamicFontFromOSFont can produce a face UI Toolkit
cannot load (“Unable to load font face”), and FontEngine cannot
validate such a Font first. Avoided automatically: the resolvers
never hand a raw OS Font to UI Toolkit.style.unityFontDefinition) are the reliable route, and this
package wraps them for you.SanitizeDisplayText before display, and let GlyphAudit fail a
test when an unsafe glyph is baked into your sources.Application.systemLanguage throws during serialization, so
naive language gating can take a whole settings asset down with it.
Steered, not solved: the policy helpers take the language as a
parameter, pushing the query to a safe callback — calling it there
is still on you (Recipe 3 shows the pattern).FontAsset.CreateFontAsset leaves the atlas material and
atlas textures it creates at HideFlags.None, so the editor destroys
them on a Play Mode transition, a New Scene or an opened scene while
the FontAsset itself survives — after which every draw throws
MissingReferenceException from inside TextCore, or
NullReferenceException from the UI Toolkit renderer when only an
atlas page died. Avoided automatically: the resolvers flag those
child objects, flag every page TextCore adds later within one editor
update, and repair an asset in place if it is damaged anyway, so the
elements you already applied keep working (see item 12 under
Verified behavior).FontFix packages the verified workarounds behind a small facade:
resolvers that cache, never throw, and report which candidate won;
apply helpers with a documented inheritance contract; display-text
sanitizers; a source-level glyph audit; and a diagnostics window.
Via git URL (Package Manager > + > Add package from git URL…):
https://github.com/c-colloid/UITKFontFix.git?path=jp.colloid.uitk-font-fix
Or drop the jp.colloid.uitk-font-fix folder into your project’s
Packages/ directory (embedded package).
Both package assemblies (Colloid.UitkFontFix,
Colloid.UitkFontFix.Editor) are auto-referenced, so loose scripts
under Assets/ can use the API immediately. Code inside your own
asmdefs adds an explicit assembly reference as usual.
Save as Assets/Editor/FontFixQuickStart.cs, then open
Window > Font Fix Quick Start:
The window below shows the full composition end to end. First, on the
container root, apply the Latin+CJK font so every descendant inherits
it; reading the system language is safe here because CreateGUI runs
long after serialization completes. Second, ordinary labels need no
special handling and simply inherit the root font. Third, a code leaf
gets the monospace font applied inline; an inline style always beats
an inherited one, so it stays monospaced even inside the CJK
container. Fourth, free-form text such as model output, user input,
files or the clipboard should be sanitized before display — only
invisible codepoints are removed, so visible content never changes.
using Colloid.UitkFontFix;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
public class FontFixQuickStart : EditorWindow
{
[MenuItem("Window/Font Fix Quick Start")]
public static void Open()
{
GetWindow<FontFixQuickStart>("Font Fix Quick Start");
}
public void CreateGUI()
{
if (FontFix.ShouldPreferCjkUi(Application.systemLanguage))
{
FontFix.ApplyCjkUi(rootVisualElement);
}
rootVisualElement.Add(new Label("Ready")); // inherits the root font
var code = new Label("if (x == 0) { return; }");
FontFix.ApplyMono(code);
rootVisualElement.Add(code);
string raw = EditorGUIUtility.systemCopyBuffer;
rootVisualElement.Add(new Label(FontFix.SanitizeDisplayText(raw)));
}
}
The composition rule: ApplyCjkUi goes on a container root (all
descendants inherit it), ApplyMono goes on leaf elements (an
inline style always beats an inherited value, so code stays monospaced
inside a CJK container). Never call both on the same element — both
are inline writes and the last one silently wins.
This example builds an editor window that mixes CJK UI text with
monospaced code. Applying one CJK-capable font to the root means
every Label below it inherits that font, so Japanese, Chinese and
Korean strings render in a single family instead of a patchy
per-glyph OS fallback. The result label simply inherits the root
font. In the row that follows, the message label also inherits the
root font and stays proportional, while the adjacent code label calls
ApplyMono inline, which wins locally over the inherited font. The
multiline log body at the bottom works the same way: a single
ApplyMono call on that leaf applies regardless of how deep it sits
under the CJK root.
using Colloid.UitkFontFix;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
public class BuildLogWindow : EditorWindow
{
[MenuItem("Window/Build Log")]
public static void Open()
{
GetWindow<BuildLogWindow>("Build Log");
}
public void CreateGUI()
{
VisualElement root = rootVisualElement;
if (FontFix.ShouldPreferCjkUi(Application.systemLanguage))
{
FontFix.ApplyCjkUi(root);
}
root.Add(new Label("Build result")); // inherits the root font
var row = new VisualElement();
row.style.flexDirection = FlexDirection.Row;
var message = new Label("Shader compilation finished ");
row.Add(message);
var code = new Label("ShaderLab.ParseError:0x2F");
FontFix.ApplyMono(code);
row.Add(code);
root.Add(row);
var log = new TextField { multiline = true, isReadOnly = true };
log.value = "0x0042 OK\n0x0043 RETRY";
FontFix.ApplyMono(log);
root.Add(log);
}
}
Rule of thumb: call ApplyCjkUi on container roots and ApplyMono on
leaves, and never call both on the same element — both are inline
writes, and the last call silently wins.
Since 0.2.0 the package wires the resolved family’s real Bold face
into the base asset’s weight table, so -unity-font-style: bold (and
<b> in rich text) renders the actual Bold face instead of the
synthetic thickened outline. Families without a Bold face keep the old
synthetic rendering, and FontFixSettings.CjkUiBoldStyleName = ""
restores it explicitly. Note that a real Bold face has different
advances than the synthetic one, so existing bold labels can wrap
slightly differently after upgrading.
Faces that USS cannot reach (Semibold, Light, Medium, …) are
available per element instead: GetCjkUiFontAsset returns a cached
asset for any face of the resolved family, and ApplyCjkUiFace
assigns one to a LEAF element — same leaf semantics as ApplyMono,
distinct on purpose from the container-root ApplyCjkUi. Style names
must match the face name exactly as the OS reports it (some families
name their regular face “Book”); a missing face makes ApplyCjkUiFace
no-op so the element degrades visibly to the inherited base face.
using Colloid.UitkFontFix;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
public class ReleaseNotesWindow : EditorWindow
{
[MenuItem("Window/Release Notes")]
public static void Open()
{
GetWindow<ReleaseNotesWindow>("Release Notes");
}
public void CreateGUI()
{
if (FontFix.ShouldPreferCjkUi(Application.systemLanguage))
{
FontFix.ApplyCjkUi(rootVisualElement);
}
var heading = new Label("Release highlights");
FontFix.ApplyCjkUiFace(heading, "Semibold");
rootVisualElement.Add(heading);
var body = new Label("Bold runs in this text use the real Bold face.");
body.style.unityFontStyleAndWeight = FontStyle.Bold;
rootVisualElement.Add(body);
}
}
Every FontAsset the package creates (base, Bold, per-face) is
name-tagged [UITK Font Fix] — that is the name shown in the UITK
Debugger’s resolved-style panel and the string to search for in the
Memory Profiler. The diagnostics window lists which faces resolved,
what is wired into the weight table, and every atlas page name.
Application.systemLanguage throws a UnityException when read
during serialization (constructors, field initializers), and one
throwing field initializer can break an entire asset load. The policy
helpers are therefore pure: they take the language as a parameter,
and you control when it is read; CjkLanguage.ShouldPreferCjkUi
itself never reads the language. The safe pattern is to query the
language once from OnEnable (or any later callback) and cache the
result, as shown below.
using Colloid.UitkFontFix;
using UnityEngine;
public class MyToolState : ScriptableObject
{
// WRONG: throws during serialization
// private bool _preferCjk =
// CjkLanguage.ShouldPreferCjkUi(Application.systemLanguage);
private bool _preferCjk;
private void OnEnable()
{
// RIGHT: query from OnEnable or later
_preferCjk = CjkLanguage.ShouldPreferCjkUi(
Application.systemLanguage);
}
public bool PreferCjk
{
get { return _preferCjk; }
}
}
CjkLanguage lives in the runtime assembly, so this pattern works in
any script. In editor code, FontFix.ShouldPreferCjkUi is the same
policy behind the facade.
The example below is editor UI code, for example under an Editor
folder. ShowMessage is lossless and always safe to call: it strips
only invisible codepoints — variation selectors (including the
ideographic ones), zero-width characters, the byte-order mark and
emoji tag characters. What the user sees never changes; what stops
happening is per-draw “not found in [Inter-Regular SDF]” warning spam
and placeholder squares. ShowMessageBmpOnly is an optional, lossy
second step for surfaces that must stay strictly within the Basic
Multilingual Plane, since the editor fonts have no emoji-plane glyphs
at all: every supplementary-plane character becomes a visible
substitute instead of a placeholder square, so opt into it
deliberately. The two-argument overload it calls composes
strip-then-replace in the safe order — ideographic variation
selectors are stripped, since they are invisible, before non-BMP
replacement, so selector-bearing kanji do not grow a stray asterisk.
The granular operations live on TextSanitizer if you need them
individually.
using Colloid.UitkFontFix;
using UnityEngine.UIElements;
public static class ChatView
{
public static void ShowMessage(Label target, string rawModelText)
{
target.text = FontFix.SanitizeDisplayText(rawModelText);
}
public static void ShowMessageBmpOnly(Label target, string rawModelText)
{
target.text = FontFix.SanitizeDisplayText(rawModelText, "*");
}
}
All sanitizers are pure, never throw, return the same string instance
when nothing needs changing, and map null to string.Empty.
The defaults resolve a Japanese-priority chain. Products that ship
primarily for Chinese or Korean users replace the CJK candidate list —
code-first, no asset required. The example below configures a
Simplified-Chinese-first product: Microsoft YaHei UI and Microsoft YaHei cover Simplified Chinese on Windows, Microsoft JhengHei UI
and Microsoft JhengHei cover Traditional Chinese on Windows, Noto Sans CJK SC covers Linux, and Yu Gothic UI is kept as a Japanese
fallback.
using Colloid.UitkFontFix;
using UnityEditor;
public static class MyProjectFontConfig
{
[InitializeOnLoadMethod]
private static void Configure()
{
FontFixSettings.CjkUiFontNames = new[]
{
"Microsoft YaHei UI", "Microsoft YaHei",
"Microsoft JhengHei UI", "Microsoft JhengHei",
"Noto Sans CJK SC",
"Yu Gothic UI"
};
}
}
A Korean-first product would assign the following list instead, in
place of the block above: Malgun Gothic for Windows, Noto Sans CJK KR for Linux, and Yu Gothic UI kept as the Japanese fallback.
FontFixSettings.CjkUiFontNames = new[]
{
"Malgun Gothic",
"Noto Sans CJK KR",
"Yu Gothic UI"
};
Names are probed one at a time, most preferred first, and must be the
English family names, since GetOSInstalledFontNames reports English
names even on localized Windows. A value that actually changes
invalidates the FontFix caches automatically; re-assigning an equal
value keeps them warm, and assigning null restores the defaults.
The same pattern applies to EditorMonoFontPaths and OsMonoFontNames
for the monospace side, and CjkUiStyleName for the style passed to
FontAsset.CreateFontAsset.
Fixed UI strings (icons, bullets, arrows baked into your sources)
should stick to printable ASCII plus the proven-safe SafeGlyphs
whitelist. GlyphAudit turns that policy into a test that fails with
the exact file and codepoint when someone bakes in a glyph the editor
font cannot draw. The example assumes an EditMode test assembly; in
your test asmdef, reference both Colloid.UitkFontFix and
Colloid.UitkFontFix.Editor.
ExtraCodepoints holds project-specific additions to the whitelist —
add a codepoint there only after confirming it renders in the target
editor font, for example by putting it in a label and watching the
console; the sample adds U+2192, RIGHTWARDS ARROW.
EditorSources_PassGlyphAudit recursively scans every *.cs file for
codepoints constructed outside the whitelist (ConvertFromUtf32,
(char) casts, \uXXXX and \UXXXXXXXX escapes), variation-selector
literals and escapes, and non-ASCII bytes; an empty list means the
sources are clean, and offenders come back as “file: reason” strings
so the failure message says exactly what slipped in and where.
GlyphStrings_UseOnlySafeCodepoints checks individual codepoints
directly: fixed UI strings are best assembled from codepoints at
runtime with char.ConvertFromUtf32 so source files stay ASCII, and
the assertions below confirm that U+2713 (CHECK MARK) and the
extended U+2192 pass while U+1F4CE, which is in the emoji plane, does
not.
using System.Collections.Generic;
using Colloid.UitkFontFix;
using NUnit.Framework;
public class UiGlyphSafetyTests
{
private static readonly int[] ExtraCodepoints =
{
0x2192
};
[Test]
public void EditorSources_PassGlyphAudit()
{
List<string> offenders = GlyphAudit.AuditSourceDirectory(
"Assets/Editor", ExtraCodepoints);
Assert.IsEmpty(offenders, string.Join("\n", offenders));
}
[Test]
public void GlyphStrings_UseOnlySafeCodepoints()
{
Assert.IsTrue(SafeGlyphs.IsSafeCodepoint(0x2713));
Assert.IsTrue(SafeGlyphs.IsSafeCodepoint(0x2192, ExtraCodepoints));
Assert.IsFalse(SafeGlyphs.IsSafeCodepoint(0x1F4CE));
}
}
This package runs the same audit over its own shipped sources as part of its test suite.
Open Window > UITK Font Fix > Diagnostics for a read-only report
with Re-probe (drops caches, resolves again) and Copy report
buttons. The same report is available from code — for example as part
of a bug-report bundle. The example below is editor code and should
be placed under an Editor folder, since FontFixDiagnostics lives in
the editor-only assembly. BuildReport returns a plain-text ASCII
report covering which candidates resolved and from which tier, the
CJK atlas state, candidate availability on this machine, and the
known-trap checklist; it never throws and is safe to call in batch
mode.
using Colloid.UitkFontFix;
using UnityEngine;
public static class SupportBundle
{
public static void LogFontReport()
{
Debug.Log(FontFixDiagnostics.BuildReport());
}
}
Reading the report:
-- Resolution --
mono : editor:Fonts/RobotoMono/RobotoMono-Regular.ttf (RobotoMono-Regular)
cjk-ui : osasset:Yu Gothic UI (Yu Gothic UI)
-- Environment --
language : Japanese (prefer CJK UI: yes)
cjk-ui candidates:
[x] Yu Gothic UI
[ ] Noto Sans CJK JP
Source prefixes: editor: = editor-bundled TTF, os: = OS font that
passed the face probe, label = default editor label font (last
resort), osasset: = DynamicOS FontAsset created from an installed
family, (none) = nothing resolved (apply helpers no-op).
Everything FontFixSettings exposes can also be edited from the
editor UI, no code required. Open Edit > Project Settings > UITK
Font Fix: candidate lists are edited one family name per line, and
three buttons cover the lifecycle — Apply (this session) pushes the
values into the running editor, Save to project also writes them to
ProjectSettings/Packages/jp.colloid.uitk-font-fix/settings.json
(versioned with your project, so the whole team gets them), and Use
package defaults resets everything and deletes that file.
The diagnostics window carries the same form in its Edit configuration foldout, right next to the resolution report — edit, Apply, Re-probe, read the result, repeat; once you like what you see, Save to project from there writes the same file.
How it takes effect: the saved file is applied once per domain load.
Unity customarily initializes a referenced assembly (this package)
before its consumers, so consumer code that assigns FontFixSettings
typically runs later and deliberately wins (explicit code is the
stronger signal) — though that ordering is convention, not a
documented guarantee. Either way the diagnostics report’s “Project
settings file” section shows whether the effective values are still in
sync with the file. As always,
changes affect every window that calls the apply helpers; windows
already open pick them up when rebuilt or reopened, and nothing
outside this package’s calls is touched.
Everything lives in the Colloid.UitkFontFix namespace. All resolvers
cache their result, never throw, and are safe in batch mode.
FontFix (static facade, editor assembly)| Member | Behavior |
|---|---|
EditorMonoFont | Resolved monospace Font: bundled RobotoMono first, face-probed single-name OS fonts second, default label font last. Cached; effectively never null in a functioning editor. |
EditorMonoFontSource | Which mono candidate won: "editor:<path>", "os:<name>", "label", or empty. |
CjkUiFontAsset | Latin+CJK FontAsset (DynamicOS mode) from the first installed candidate, or null when none resolves. Glyphs populate lazily at render time; the transient asset is destroyed before every domain reload. Never returns an asset whose atlas material or in-use atlas pages were destroyed (a Play Mode transition or a scene load): it is repaired in place, so the instance — and every element already using it — stays valid. Read it each time instead of caching the FontAsset in your own field. |
CjkUiFontSource | Which CJK candidate won: "osasset:<name>", or empty. |
ApplyMono(VisualElement) | Inline unityFontDefinition assignment on one leaf; survives any ancestor ApplyCjkUi. No-ops (keeps the inherited font) on null or when nothing resolved. |
ApplyCjkUi(VisualElement) | Font assignment on a container root that descendants inherit. No-ops on null or when nothing resolved — callers must not assume the font changed. |
GetCjkUiFontAsset(string) | The resolved family in a given face style (exact face name, e.g. "Semibold"). Null/empty/base-style aliases to CjkUiFontAsset (same instance); other faces are created once, cached (misses too), kit-owned and name-tagged. Null when the base did not resolve or the face does not exist. |
ApplyCjkUiFace(VisualElement, string) | Inline assignment of a specific face on a leaf element (Semibold headers etc.). No-ops on null element or a face miss, degrading to the inherited base face. |
CreatedObjectNameTag | "[UITK Font Fix]" — the suffix stamped on every kit-created object name (assets, materials, first atlas pages, owned OS fonts); the Memory Profiler search string. |
ShouldPreferCjkUi(SystemLanguage) | Pure policy: true for Japanese, Chinese (all variants) and Korean. |
SanitizeDisplayText(string) | Lossless display hygiene: strips every variation selector (including ideographic ones), zero-width characters, the BOM and emoji tag characters. Returns the same instance when clean, string.Empty for null; never removes a character that draws its own glyph. |
SanitizeDisplayText(string, string) | Lossy overload: the strip above, then every non-BMP codepoint and unpaired surrogate becomes the given replacement (the parameter is the opt-in). Strip-then-replace order is a documented guarantee. |
ResetCaches() | Drops every cached resolution (destroying package-owned transient objects) so the next access re-probes. Raises CachesInvalidated. |
CachesInvalidated | static event Action, raised synchronously when a resolved object this package handed out was destroyed or replaced: ResetCaches(), a FontFixSettings change (including the settings UIs), or the rare rebuild after unrepairable damage. Elements keep the FontAsset in their inline style, so subscribe and re-apply (ApplyCjkUi/ApplyMono again). It does not fire for an in-place repair (same instance, nothing to do) or before a domain reload. Subscriptions are lost on every domain reload: subscribe from CreateGUI/OnEnable, unsubscribe in OnDisable. A throwing handler is logged and the others still run. |
FontFixSettings (static configuration, editor assembly)Code-first candidate configuration. A value that actually changes
invalidates the FontFix caches; assigning an equal value keeps them
warm. Assigning null to any property restores that property’s default.
| Member | Behavior |
|---|---|
EditorMonoFontPaths | EditorGUIUtility.Load paths tried for the bundled mono TTF. Empty array disables the tier. |
OsMonoFontNames | OS monospace family names, probed one at a time. Empty array disables the tier. |
CjkUiFontNames | Latin+CJK family names, probed one at a time, most preferred first. Empty array disables CJK resolution entirely (ApplyCjkUi then no-ops). |
CjkUiStyleName | Style name passed to FontAsset.CreateFontAsset (default "Regular"). Null/empty restores the default. |
CjkUiBoldStyleName | Face wired into the base asset’s weight table so bold text renders it (default "Bold"). Null restores the default; "" disables wiring and restores the pre-0.2.0 synthetic bold. |
ResetToDefaults() | Restores every property; only invalidates caches when something actually changed (safe in test teardown). |
TextSanitizer — pure display-text hygiene; never throws, never
allocates on the clean fast path, maps null to string.Empty.
| Member | Behavior |
|---|---|
StripVariationSelectors(string) | Removes every Unicode variation selector: U+FE00..U+FE0F, Mongolian FVS U+180B..U+180D and U+180F, and ideographic selectors U+E0100..U+E01EF (matched only as valid surrogate pairs). The shaping-safe granular op — never touches ZWJ/ZWNJ. |
StripInvisibleCharacters(string) | Superset of the above: also removes zero-width space/non-joiner/joiner (U+200B..U+200D), word joiner and invisible math operators (U+2060..U+2064), the BOM (U+FEFF) and emoji tag characters (U+E0000..U+E007F). This is what FontFix.SanitizeDisplayText forwards to. |
ReplaceNonBmpCharacters(string, string) | Lossy: replaces each supplementary-plane codepoint (one replacement per surrogate pair) and each unpaired surrogate with the replacement string; when the replacement itself contains no surrogates, the result contains no surrogate code units. For surfaces that must stay strictly BMP. |
CjkLanguage
| Member | Behavior |
|---|---|
ShouldPreferCjkUi(SystemLanguage) | Pure ja/zh/ko policy. Takes the language as a parameter on purpose: reading Application.systemLanguage during serialization throws (see Recipe 3). |
SafeGlyphs
| Member | Behavior |
|---|---|
DefaultCodepoints | Read-only whitelist of non-ASCII BMP codepoints verified to render in the 2022.3 editor font (bullets, arrows, check marks, text-presentation gear/warning, etc.). |
IsSafeCodepoint(int) | True for printable ASCII or a whitelist member. |
IsSafeCodepoint(int, int[]) | Same, plus a project-specific extension list (extend only after verifying editor-font coverage). |
FontFixDefaults
| Member | Behavior |
|---|---|
EditorMonoFontPaths | Default bundled-mono probe paths (RobotoMono). |
OsMonoFontNames | Default OS mono candidates (Consolas, Menlo, DejaVu Sans Mono, Courier New). |
CjkUiFontNames | Default Latin+CJK chain (Yu Gothic UI > Yu Gothic > Meiryo UI > Meiryo > Noto Sans CJK JP > MS UI Gothic). |
CjkUiStyleName | "Regular". |
Treat the arrays as read-only; customize through FontFixSettings.
GlyphAudit — source-level glyph safety audit, usable from
consumer test assemblies (see Recipe 6).
| Member | Behavior |
|---|---|
PackageRootPath() | Root directory of this package’s own sources (embedded, junction and package-cache installs), or null. |
AuditSourceDirectory(string, int[]) | Runs every audit over all *.cs under a directory (recursive); returns "file: reason" offenders. A missing directory yields one offender instead of throwing, so a moved path fails a test visibly. |
AuditSourceFile(string, int[]) | Every audit over one file. |
AuditConstructedCodepoints(string, string, int[]) | Flags codepoints constructed in source outside the whitelist. (char) casts of U+FE0F/U+FE0E/U+FEFF are exempt: sanitizers legitimately compare against those values, and a comparison never renders. |
AuditVariationSelectors(string, string) | Flags literal U+FE0F/U+FE0E and their escape forms. Ordinal scans on purpose — culture-sensitive searches treat selectors as collation-ignorable. |
AuditAsciiOnly(string, byte[]) | Flags the first non-ASCII byte per file (strict-ASCII source policy keeps glyph content reviewable and diff-safe). |
FontFixProjectSettings
| Member | Behavior |
|---|---|
FilePath / Exists | The project-shared settings file (ProjectSettings/Packages/jp.colloid.uitk-font-fix/settings.json) and whether this project pins its configuration. |
Save() | Snapshots the current effective FontFixSettings values into the file (returns false instead of throwing on IO failure). |
TryLoadAndApply() | Applies the file when present and parseable; runs automatically once per domain load. Corrupt files are ignored. |
Delete() | Removes the file so the project tracks package defaults again (in-memory settings unchanged). |
MatchesCurrentSettings() | True when the file exists and matches the effective values — the drift signal the diagnostics report shows. |
FontFixDiagnostics
| Member | Behavior |
|---|---|
BuildReport() | Plain-text ASCII report: resolution results, CJK atlas page state, candidate availability, known-trap checklist. Never throws; batch-safe. |
FontFixDiagnosticsWindow
| Member | Behavior |
|---|---|
Open() / Window > UITK Font Fix > Diagnostics | Read-only diagnostics window with Re-probe and Copy report; dogfoods the package’s own composition (CJK root, mono report body). |
The following facts about Unity 2022.3 TextCore / UI Toolkit were established empirically, in both interactive and batch editors (primarily on Windows; the headless subset re-verified on Linux). Every non-obvious design choice above traces back to one of them.
FontEngine.LoadFontFace(Font) returns Invalid_File for every
OS dynamic font (Font.CreateDynamicFontFromOSFont). OS fonts
cannot be validated on the Font route — which is why this package
never hands an OS Font to UI Toolkit.TextCore.Text.FontAsset.CreateFontAsset(family, style) (DynamicOS
mode) assigned via FontDefinition. Glyphs are fetched lazily at
render time (HasCharacter returning false right after creation is
normal), and creation works headless.Font.CreateDynamicFontFromOSFont with a name array produces an
unloadable face (“Unable to load font face” / “Can’t Generate Mesh”)
and text renders empty. Single names do not pass face validation
either; the route is avoided entirely.EditorGUIUtility.Load("Fonts/RobotoMono/RobotoMono-Regular.ttf")
resolves on 2022.3 — a real TTF asset TextCore always accepts,
hence the preferred mono candidate.style.unityFontDefinition. Inline styles beat inherited
values, which is what makes the root/leaf composition reliable.Font.GetOSInstalledFontNames() reports English family names
even on localized (e.g. Japanese) Windows — which is why the
candidate defaults are English names.<mark> rich-text tag is incompatible with DynamicOS
FontAssets: mark quads draw above or below glyphs depending on their
atlas page, producing patchy bold / faded runs (or fully hidden text
with opaque marks). Atlas prefill cannot fix it (ASCII alone spans 4
pages; CJK can never be single-page). Do not combine <mark> with
these fonts.SanitizeDisplayText; fixed UI
strings stay inside the SafeGlyphs whitelist.Application.systemLanguage throws when read from ScriptableObject
constructors or field initializers. Query it in OnEnable or later
and pass the value to ShouldPreferCjkUi.FontAsset.CreateFontAsset creates its atlas material and atlas
texture with HideFlags.None (the TextCore source sets hideFlags
nowhere), while Unity’s own cache of runtime font assets stamps
HideFlags.DontSave on the asset and on atlasTextures[0]
and on material. HideFlags.DontSave is documented as “will
not be destroyed when a new Scene is loaded”, which a Play Mode
transition, File > New Scene and opening a scene all perform: an
unflagged child dies, the flagged FontAsset survives holding a
destroyed reference, and the next draw throws
MissingReferenceException from inside TextCore (material dead)
or NullReferenceException from UIRStylePainter.DrawTextInfo
(only a page dead: Material.mainTexture reads back as null). The
page case is the common one — CreateFontAsset(family, style)
samples at 90 pt into 1024x1024 pages, about 80 CJK glyphs each,
so Japanese UI text grows several pages, all added lazily during
rendering and all born unflagged. A FontAsset assigned directly
through unityFontDefinition never passes through Unity’s stamping
code, so this is the package’s responsibility: it flags the
children at creation, flags every later page within one editor
update (the page count is compared each tick, no native call),
verifies liveness on every cached access, after each scene load,
on entering/leaving Play Mode and at a low rate from the editor
update, and repairs a damaged asset in place so
already-applied elements heal without re-applying. The repair
always installs a new material even when the old one survived: UI
Toolkit regenerates an element’s cached text mesh only when its
generation-settings hash changes, and that hash covers the font
asset and its material — the one thing an in-place repair can
change. Elements drawn with the repaired asset are then marked for
repaint, because an element whose last draw threw is no longer
dirty and would otherwise stay broken until something else touched
it.The primary target is Unity 2022.3 LTS — every item above was verified there. The package also compiles and passes its EditMode suite headless on Linux editors (CJK resolution depends on installed OS fonts, so it correctly reports “none resolved” on machines without a CJK candidate).
All version-sensitive TextCore/UI Toolkit calls are concentrated in a
single internal seam (FontShims). Verification against the
UnityCsReference sources: FontDefinition.FromSDFFont(FontAsset),
FontAsset.CreateFontAsset(family, style), atlasPopulationMode and
atlasTextures are identical across the 2022.3, 2023.2 and 6000.0
branches, so the package needs no version branching; if a future Unity
version ever changes one of these APIs, the fix is contained to that
one file.
Two importable samples ship with the package (Package Manager > UITK Font Fix > Samples):
EditorWindow demonstrating the
container/leaf composition, safe language gating and display-text
sanitization. Import, open its window from the Window menu, and use
it as a starting point for your own tool windows.GlyphAudit and SafeGlyphs into your own project sources. Adjust
the scanned directory and the extra-codepoint whitelist to match your
project.MIT — see LICENSE.md.
jp.colloid.uitk-font-fix
未設定
0.4.1
2022.3 以降
なし
なし
なし
colloid