public sealed class TransformedTextAn element's text after text-transform and white-space collapsing, and the map back.
Remarks
⚠ Two transformations share this type because they share the map, not because they are one idea. CSS Text 4 § 3's collapsing runs first and § 2.1's casing second, in one walk. What made it worth one type rather than two is that a second stage would need the first stage's map composed with its own at every index — and there is exactly one consumer shape here, UiElement handing out an index into the drawn string, which must land on a source index the author's own text has. ⚠ Collapsing is the first transformation in this engine that makes the drawn text shorter: Record was already general enough to take a drawn length of zero, which is what a removed run is.
The map is the point, and the four keywords are the easy part. A full Unicode case mapping changes the UTF-16 length — straße uppercases to STRASSE, one character becoming two — and what a text layout hands out is every one of them an index: a caret index, a selection range, the start of a line. Shipping the keywords without this puts the caret in the wrong character of an editable field, silently, and only on the strings where it expands.
⚠ .NET's own casing would never have shown that. string.ToUpperInvariant, ToUpper in every culture, and Rune.ToUpperInvariant over all 1 112 064 scalars implement the simple mappings, which are one code point to one by definition — measured, not assumed. So the naive implementation is index-safe and draws STRAßE, which is a different defect and a visible one. The expansions come from SpecialCasingTable, which is the UCD's unconditional rows and is what makes this type necessary at all.
⚠ ToSource is many-to-one and ToDrawn is not. Both units of the SS that came from one ß map back to that ß, because there is no caret position between them — the two letters are one character the author typed. So source → drawn → source is the identity and drawn → source → drawn is not, and a click in the middle of an expansion snaps to its start. That is the behaviour a text field wants and it is the reason the pair is not a single offset.
Identity is the overwhelmingly common case — no transform at all, or a transform under which every character keeps its length — and it allocates no arrays and returns the same string instance, which is what keeps the shaping cache's fast path and UiElement.Block's reference test meaning what they meant.
Fields and properties (3)
public string SourceThe text as it was written.
public string TextThe text as it is shaped and drawn.
public bool IsIdentityWhether every index means the same thing in both strings.
Methods (3)
public static TransformedText Of(string? source, TextTransform transform, string? language = null, WhiteSpaceCollapse collapse = Preserve, bool trimStart = false, bool trimEnd = false)Applies a transform, and builds the map if it moved anything.
public int ToDrawn(int index)Where a source index sits in Text.
public int ToSource(int index)Which source index a drawn index belongs to.
Used by (7)
- FinalSigmaTestsVixen.Ui.Text.Tests
- LithuanianCasingTestsVixen.Ui.Text.Tests
- TextLineVixen.Ui
- TransformedTextTestsVixen.Ui.Text.Tests
- TurkicCasingTestsVixen.Ui.Text.Tests
- UiElementVixen.Ui
- WhiteSpaceCollapseTestsVixen.Ui.Text.Tests