Use Rune for rendering text instead of char.

Fixes crashes with surrogates.
This commit is contained in:
Pieter-Jan Briers
2021-03-09 23:25:27 +01:00
parent fc6dc6f4e1
commit 1bf8b2a52b
10 changed files with 79 additions and 95 deletions
@@ -385,16 +385,16 @@ namespace Robust.Client.UserInterface.Controls
var offsetY = (int) (box.Height - font.GetHeight(UIScale)) / 2;
var baseLine = new Vector2i(0, offsetY + font.GetAscent(UIScale)) + box.TopLeft;
foreach (var chr in text)
foreach (var rune in text.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
if (!(baseLine.X < box.Left || baseLine.X + metrics.Advance > box.Right))
{
font.DrawChar(handle, chr, baseLine, UIScale, color);
font.DrawChar(handle, rune, baseLine, UIScale, color);
}
baseLine += (metrics.Advance, 0);
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using Robust.Client.Graphics;
using Robust.Shared.Animations;
using Robust.Shared.Maths;
@@ -180,15 +181,15 @@ namespace Robust.Client.UserInterface.Controls
var baseLine = CalcBaseline();
foreach (var chr in _text)
foreach (var rune in _text.EnumerateRunes())
{
if (chr == '\n')
if (rune == new Rune('\n'))
{
newlines += 1;
baseLine = CalcBaseline();
}
var advance = font.DrawChar(handle, chr, baseLine, UIScale, actualFontColor);
var advance = font.DrawChar(handle, rune, baseLine, UIScale, actualFontColor);
baseLine += (advance, 0);
}
}
@@ -252,16 +253,16 @@ namespace Robust.Client.UserInterface.Controls
var font = ActualFont;
var height = font.GetHeight(UIScale);
foreach (var chr in _text)
foreach (var rune in _text.EnumerateRunes())
{
if (chr == '\n')
if (rune == new Rune('\n'))
{
_cachedTextWidths.Add(0);
height += font.GetLineHeight(UIScale);
}
else
{
var metrics = font.GetCharMetrics(chr, UIScale);
var metrics = font.GetCharMetrics(rune, UIScale);
if (metrics == null)
{
continue;
@@ -555,9 +555,9 @@ namespace Robust.Client.UserInterface.Controls
var index = 0;
var chrPosX = contentBox.Left - _drawOffset;
var lastChrPostX = contentBox.Left - _drawOffset;
foreach (var chr in _text)
foreach (var rune in _text.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
index += 1;
continue;
@@ -767,7 +767,7 @@ namespace Robust.Client.UserInterface.Controls
var posX = 0;
var actualCursorPosition = 0;
var actualSelectionStartPosition = 0;
foreach (var chr in renderedText)
foreach (var chr in renderedText.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
{
@@ -816,9 +816,9 @@ namespace Robust.Client.UserInterface.Controls
var baseLine = (-drawOffset, offsetY + font.GetAscent(UIScale)) +
contentBox.TopLeft;
foreach (var chr in renderedText)
foreach (var rune in renderedText.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
@@ -832,7 +832,7 @@ namespace Robust.Client.UserInterface.Controls
// Make sure we're not off the left edge of the box.
if (baseLine.X + metrics.BearingX + metrics.Width >= contentBox.Left)
{
font.DrawChar(handle, chr, baseLine, UIScale, renderedTextColor);
font.DrawChar(handle, rune, baseLine, UIScale, renderedTextColor);
}
baseLine += (metrics.Advance, 0);
@@ -163,9 +163,9 @@ namespace Robust.Client.UserInterface.Controls
var titleLength = 0;
// Get string length.
foreach (var chr in title)
foreach (var rune in title.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
@@ -196,14 +196,14 @@ namespace Robust.Client.UserInterface.Controls
var baseLine = new Vector2(0, font.GetAscent(UIScale)) + contentBox.TopLeft;
foreach (var chr in title)
foreach (var rune in title.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
font.DrawChar(handle, chr, baseLine, UIScale, active ? fontColorActive : fontColorInactive);
font.DrawChar(handle, rune, baseLine, UIScale, active ? fontColorActive : fontColorInactive);
baseLine += new Vector2(metrics.Advance, 0);
}
@@ -295,9 +295,9 @@ namespace Robust.Client.UserInterface.Controls
var titleLength = 0;
// Get string length.
foreach (var chr in title)
foreach (var rune in title.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
+2 -2
View File
@@ -219,9 +219,9 @@ namespace Robust.Client.UserInterface.Controls
{
var offset = itemSelected.GetContentOffset(Vector2.Zero);
var baseLine = offset + (hOffset, vOffset + font.GetAscent(UIScale));
foreach (var chr in item.Text)
foreach (var rune in item.Text.EnumerateRunes())
{
baseLine += (font.DrawChar(handle, chr, baseLine, UIScale, Color.White), 0);
baseLine += (font.DrawChar(handle, rune, baseLine, UIScale, Color.White), 0);
}
}
+18 -16
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
@@ -65,7 +66,7 @@ namespace Robust.Client.UserInterface
var wordSizePixels = 0;
// The horizontal position of the text cursor.
var posX = 0;
var lastChar = 'A';
var lastRune = new Rune('A');
// If a word is larger than maxSizeX, we split it.
// We need to keep track of some data to split it into two words.
(int breakIndex, int wordSizePixels)? forceSplitData = null;
@@ -83,14 +84,14 @@ namespace Robust.Client.UserInterface
var text = tagText.Text;
// And go over every character.
for (var i = 0; i < text.Length; i++, breakIndexCounter++)
foreach (var rune in text.EnumerateRunes())
{
var chr = text[i];
breakIndexCounter += 1;
if (IsWordBoundary(lastChar, chr) || chr == '\n')
if (IsWordBoundary(lastRune, rune) || rune == new Rune('\n'))
{
// Word boundary means we know where the word ends.
if (posX > maxSizeX && lastChar != ' ')
if (posX > maxSizeX && lastRune != new Rune(' '))
{
DebugTools.Assert(wordStartBreakIndex.HasValue,
"wordStartBreakIndex can only be null if the word begins at a new line, in which case this branch shouldn't be reached as the word would be split due to being longer than a single line.");
@@ -109,22 +110,22 @@ namespace Robust.Client.UserInterface
forceSplitData = null;
// Just manually handle newlines.
if (chr == '\n')
if (rune == new Rune('\n'))
{
LineBreaks.Add(breakIndexCounter);
Height += font.GetLineHeight(uiScale);
maxUsedWidth = Math.Max(maxUsedWidth, posX);
posX = 0;
lastChar = chr;
lastRune = rune;
wordStartBreakIndex = null;
continue;
}
}
// Uh just skip unknown characters I guess.
if (!font.TryGetCharMetrics(chr, uiScale, out var metrics))
if (!font.TryGetCharMetrics(rune, uiScale, out var metrics))
{
lastChar = chr;
lastRune = rune;
continue;
}
@@ -165,7 +166,7 @@ namespace Robust.Client.UserInterface
}
}
lastChar = chr;
lastRune = rune;
}
}
@@ -185,7 +186,7 @@ namespace Robust.Client.UserInterface
Logger.Error("wordStartBreakIndex: null (duh)");
Logger.Error($"wordSizePixels: {wordSizePixels}");
Logger.Error($"posX: {posX}");
Logger.Error($"lastChar: {lastChar}");
Logger.Error($"lastChar: {lastRune}");
Logger.Error($"forceSplitData: {forceSplitData}");
Logger.Error($"LineBreaks: {string.Join(", ", LineBreaks)}");
@@ -247,9 +248,10 @@ namespace Robust.Client.UserInterface
case FormattedMessage.TagText tagText:
{
var text = tagText.Text;
for (var i = 0; i < text.Length; i++, globalBreakCounter++)
foreach (var rune in text.EnumerateRunes())
{
var chr = text[i];
globalBreakCounter += 1;
if (lineBreakIndex < LineBreaks.Count &&
LineBreaks[lineBreakIndex] == globalBreakCounter)
{
@@ -257,7 +259,7 @@ namespace Robust.Client.UserInterface
lineBreakIndex += 1;
}
var advance = font.DrawChar(handle, chr, baseLine, uiScale, currentColorTag.Color);
var advance = font.DrawChar(handle, rune, baseLine, uiScale, currentColorTag.Color);
baseLine += new Vector2(advance, 0);
}
@@ -268,9 +270,9 @@ namespace Robust.Client.UserInterface
}
[Pure]
private static bool IsWordBoundary(char a, char b)
private static bool IsWordBoundary(Rune a, Rune b)
{
return a == ' ' || b == ' ' || a == '-' || b == '-';
return a == new Rune(' ') || b == new Rune(' ') || a == new Rune('-') || b == new Rune('-');
}
}
}