443 lines
19 KiB
C#
443 lines
19 KiB
C#
using PCL.Core.App;
|
|||
|
|
using PCL.Core.Utils;
|
||
|
|
|
||
|
|
namespace PCL.Core.UI.Controls;
|
||
|
|
|
||
|
|
using System;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.Windows;
|
||
|
|
using System.Windows.Controls;
|
||
|
|
using System.Windows.Media;
|
||
|
|
using System.Windows.Threading;
|
||
|
|
using System.Linq;
|
||
|
|
using System.Windows.Media.Imaging;
|
||
|
|
|
||
|
|
public partial class MotdRenderer {
|
||
|
|
// Default Color for originalColorMap: #808080
|
||
|
|
// Minecraft color code mapping
|
||
|
|
private static readonly Dictionary<string, Brush> _ColorMapWithBlackBackground = new() {
|
||
|
|
{ "0", Brushes.Black }, // Black
|
||
|
|
{ "1", new SolidColorBrush(Color.FromRgb(0, 0, 170)) }, // Dark Blue
|
||
|
|
{ "2", new SolidColorBrush(Color.FromRgb(0, 170, 0)) }, // Dark Green
|
||
|
|
{ "3", new SolidColorBrush(Color.FromRgb(0, 170, 170)) }, // Cyan
|
||
|
|
{ "4", new SolidColorBrush(Color.FromRgb(170, 0, 0)) }, // Dark Red
|
||
|
|
{ "5", new SolidColorBrush(Color.FromRgb(170, 0, 170)) }, // Purple
|
||
|
|
{ "6", new SolidColorBrush(Color.FromRgb(255, 170, 0)) }, // Gold
|
||
|
|
{ "7", Brushes.LightGray }, // Gray
|
||
|
|
{ "8", Brushes.DarkGray }, // Dark Gray
|
||
|
|
{ "9", Brushes.Blue }, // Blue
|
||
|
|
{ "a", Brushes.Lime }, // Green
|
||
|
|
{ "b", Brushes.Cyan }, // Cyan
|
||
|
|
{ "c", Brushes.Red }, // Red
|
||
|
|
{ "d", Brushes.Magenta }, // Magenta
|
||
|
|
{ "e", Brushes.Yellow }, // Yellow
|
||
|
|
{ "f", Brushes.White } // White
|
||
|
|
};
|
||
|
|
|
||
|
|
// Color code mapping optimized for white background (#f3f6fa)
|
||
|
|
private static readonly Dictionary<string, Brush> _ColorMapWithWhiteBackground = new() {
|
||
|
|
{ "0", new SolidColorBrush(Color.FromRgb(51, 51, 51)) }, // Deep Gray #333333
|
||
|
|
{ "1", new SolidColorBrush(Color.FromRgb(0, 48, 135)) }, // Navy Blue #003087
|
||
|
|
{ "2", new SolidColorBrush(Color.FromRgb(0, 128, 0)) }, // Forest Green #008000
|
||
|
|
{ "3", new SolidColorBrush(Color.FromRgb(0, 122, 122)) }, // Cyan #007A7A
|
||
|
|
{ "4", new SolidColorBrush(Color.FromRgb(161, 0, 0)) }, // Deep Red #A10000
|
||
|
|
{ "5", new SolidColorBrush(Color.FromRgb(128, 0, 128)) }, // Deep Purple #800080
|
||
|
|
{ "6", new SolidColorBrush(Color.FromRgb(204, 112, 0)) }, // Deep Orange #CC7000
|
||
|
|
{ "7", new SolidColorBrush(Color.FromRgb(102, 102, 102)) }, // Medium Gray #666666
|
||
|
|
{ "8", new SolidColorBrush(Color.FromRgb(68, 68, 68)) }, // Charcoal #444444
|
||
|
|
{ "9", new SolidColorBrush(Color.FromRgb(0, 68, 204)) }, // Royal Blue #0044CC
|
||
|
|
{ "a", new SolidColorBrush(Color.FromRgb(0, 153, 0)) }, // Green #009900
|
||
|
|
{ "b", new SolidColorBrush(Color.FromRgb(0, 161, 161)) }, // Cyan #00A1A1
|
||
|
|
{ "c", new SolidColorBrush(Color.FromRgb(204, 0, 0)) }, // Red #CC0000
|
||
|
|
{ "d", new SolidColorBrush(Color.FromRgb(194, 0, 194)) }, // Magenta #C200C2
|
||
|
|
{ "e", new SolidColorBrush(Color.FromRgb(179, 160, 0)) }, // Deep Yellow #B3A000
|
||
|
|
{ "f", new SolidColorBrush(Color.FromRgb(136, 136, 136)) } // White
|
||
|
|
};
|
||
|
|
|
||
|
|
// Format code mapping
|
||
|
|
private readonly Dictionary<string, bool> _formatMap = new() {
|
||
|
|
{ "l", true }, // Bold
|
||
|
|
{ "o", true }, // Italic
|
||
|
|
{ "n", true }, // Underline
|
||
|
|
{ "m", true }, // Strikethrough
|
||
|
|
{ "k", true }, // Obfuscated (not supported)
|
||
|
|
{ "r", false } // Reset
|
||
|
|
};
|
||
|
|
|
||
|
|
// Store TextBlock and original text for §k obfuscated text
|
||
|
|
private readonly List<(TextBlock TextBlock, string OriginalText)> _obfuscatedTextBlocks = [];
|
||
|
|
|
||
|
|
private readonly Random _random = new();
|
||
|
|
private const string RandomChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()";
|
||
|
|
private readonly Color _backgroundColor = Color.FromRgb(243, 246, 250); // #f3f6fa
|
||
|
|
|
||
|
|
public MotdRenderer() {
|
||
|
|
InitializeComponent(); // 初始化 XAML 定义的控件
|
||
|
|
// Start timer to update §k text
|
||
|
|
var timer = new DispatcherTimer {
|
||
|
|
Interval = TimeSpan.FromMilliseconds(20)
|
||
|
|
};
|
||
|
|
timer.Tick += _UpdateObfuscatedText;
|
||
|
|
timer.Start();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void _UpdateObfuscatedText(object? sender, EventArgs e) {
|
||
|
|
foreach (var (textBlock, originalText) in _obfuscatedTextBlocks) {
|
||
|
|
// Generate random characters of the same length as the original text
|
||
|
|
var obfuscated = string.Join("",
|
||
|
|
Enumerable.Range(0, originalText.Length).Select(_ => RandomChars[_random.Next(RandomChars.Length)]));
|
||
|
|
textBlock.Text = obfuscated;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static double _GetRelativeLuminance(Color color) {
|
||
|
|
var r = color.R / 255.0;
|
||
|
|
var g = color.G / 255.0;
|
||
|
|
var b = color.B / 255.0;
|
||
|
|
var rL = r <= 0.03928 ? r / 12.92 : Math.Pow((r + 0.055) / 1.055, 2.4);
|
||
|
|
var gL = g <= 0.03928 ? g / 12.92 : Math.Pow((g + 0.055) / 1.055, 2.4);
|
||
|
|
var bL = b <= 0.03928 ? b / 12.92 : Math.Pow((b + 0.055) / 1.055, 2.4);
|
||
|
|
return 0.2126 * rL + 0.7152 * gL + 0.0722 * bL;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static double _GetContrastRatio(Color foreground, Color background) {
|
||
|
|
var l1 = _GetRelativeLuminance(foreground);
|
||
|
|
var l2 = _GetRelativeLuminance(background);
|
||
|
|
return (Math.Max(l1, l2) + 0.05) / (Math.Min(l1, l2) + 0.05);
|
||
|
|
}
|
||
|
|
|
||
|
|
private Color _AdjustColorForContrast(Color inputColor) {
|
||
|
|
var contrastRatio = _GetContrastRatio(inputColor, _backgroundColor);
|
||
|
|
if (contrastRatio >= 4.5) return inputColor; // Contrast is sufficient
|
||
|
|
|
||
|
|
// Convert RGB to HSL
|
||
|
|
var r = inputColor.R / 255.0;
|
||
|
|
var g = inputColor.G / 255.0;
|
||
|
|
var b = inputColor.B / 255.0;
|
||
|
|
var max = Math.Max(Math.Max(r, g), b);
|
||
|
|
var min = Math.Min(Math.Min(r, g), b);
|
||
|
|
var l = (max + min) / 2.0;
|
||
|
|
double s;
|
||
|
|
double h;
|
||
|
|
|
||
|
|
if (Math.Abs(max - min) < double.Epsilon) {
|
||
|
|
h = 0.0;
|
||
|
|
s = 0.0;
|
||
|
|
} else {
|
||
|
|
var d = max - min;
|
||
|
|
s = l > 0.5 ? d / (2.0 - max - min) : d / (max + min);
|
||
|
|
h = max switch {
|
||
|
|
_ when Math.Abs(max - r) < double.Epsilon => (g - b) / d + (g < b ? 6.0 : 0.0),
|
||
|
|
_ when Math.Abs(max - g) < double.Epsilon => (b - r) / d + 2.0,
|
||
|
|
_ => (r - g) / d + 4.0
|
||
|
|
};
|
||
|
|
h /= 6.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Decrease lightness until contrast ratio ≥ 4.5:1
|
||
|
|
var newL = l;
|
||
|
|
var adjustedColor = inputColor;
|
||
|
|
while (newL > 0.1 && _GetContrastRatio(adjustedColor, _backgroundColor) < 4.5) {
|
||
|
|
newL -= 0.05; // Gradually decrease lightness
|
||
|
|
double newR, newG, newB;
|
||
|
|
if (s == 0) {
|
||
|
|
newR = newL;
|
||
|
|
newG = newL;
|
||
|
|
newB = newL;
|
||
|
|
} else {
|
||
|
|
var q = newL < 0.5 ? newL * (1.0 + s) : newL + s - newL * s;
|
||
|
|
var p = 2.0 * newL - q;
|
||
|
|
newR = _HueToRgb(p, q, h + 1.0 / 3.0);
|
||
|
|
newG = _HueToRgb(p, q, h);
|
||
|
|
newB = _HueToRgb(p, q, h - 1.0 / 3.0);
|
||
|
|
}
|
||
|
|
|
||
|
|
adjustedColor = Color.FromRgb((byte)(newR * 255), (byte)(newG * 255), (byte)(newB * 255));
|
||
|
|
}
|
||
|
|
|
||
|
|
// If contrast is still insufficient, use default color #555555
|
||
|
|
return _GetContrastRatio(adjustedColor, _backgroundColor) < 4.5 ? Color.FromRgb(85, 85, 85) : adjustedColor;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static double _HueToRgb(double p, double q, double t) {
|
||
|
|
if (t < 0) t += 1.0;
|
||
|
|
if (t > 1) t -= 1.0;
|
||
|
|
if (t < 1.0 / 6.0) return p + (q - p) * 6.0 * t;
|
||
|
|
if (t < 0.5) return q;
|
||
|
|
if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6.0;
|
||
|
|
return p;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static bool TryGetColorFromCode(string code, bool isDarkMode, out String? color) {
|
||
|
|
var colorMap = isDarkMode ? _ColorMapWithBlackBackground : _ColorMapWithWhiteBackground;
|
||
|
|
var success = colorMap.TryGetValue(code.ToLower(), out var brush);
|
||
|
|
if (!success) {
|
||
|
|
color = null;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
var solidColorBrush = ((SolidColorBrush)brush!).Color;
|
||
|
|
color = $"#{solidColorBrush.R:X2}{solidColorBrush.G:X2}{solidColorBrush.B:X2}";
|
||
|
|
return success;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void RenderMotd(string motd, bool isDarkMode = true, int maxLines = int.MaxValue, double fontSize = 12, bool isCentered = true) {
|
||
|
|
MotdCanvas.Children.Clear();
|
||
|
|
_obfuscatedTextBlocks.Clear();
|
||
|
|
|
||
|
|
motd ??= string.Empty;
|
||
|
|
|
||
|
|
var colorMap = isDarkMode ? _ColorMapWithBlackBackground : _ColorMapWithWhiteBackground;
|
||
|
|
var font = Config.Preference.MotdFont;
|
||
|
|
var fontFamily = new FontFamily(string.IsNullOrWhiteSpace(font)
|
||
|
|
? "./Resources/#PCL English, Segoe UI, Microsoft YaHei UI"
|
||
|
|
: font);
|
||
|
|
var canvasWidth = MotdCanvas.ActualWidth > 0 ? MotdCanvas.ActualWidth : 300; // Prevent zero width
|
||
|
|
var canvasHeight = MotdCanvas.ActualHeight > 0 ? MotdCanvas.ActualHeight : 34; // Prevent zero height
|
||
|
|
double y = 10;
|
||
|
|
|
||
|
|
// Split multi-line MOTD
|
||
|
|
motd = motd.Replace("\n", "\r\n");
|
||
|
|
var lines = motd.Split("\r\n");
|
||
|
|
var currentColor = colorMap["f"];
|
||
|
|
var isBold = false;
|
||
|
|
var isItalic = false;
|
||
|
|
var isUnderline = false;
|
||
|
|
var isStrikethrough = false;
|
||
|
|
var isObfuscated = false;
|
||
|
|
|
||
|
|
// 限制显示的行数不超过maxLines
|
||
|
|
var lineCount = Math.Min(lines.Length, maxLines);
|
||
|
|
for (var lineIndex = 0; lineIndex < lineCount; lineIndex++) {
|
||
|
|
var line = lines[lineIndex].Trim();
|
||
|
|
var parts = RegexPatterns.MotdCode.Split(line);
|
||
|
|
|
||
|
|
// Calculate line width
|
||
|
|
double lineWidth = 0;
|
||
|
|
double lineHeight = 0;
|
||
|
|
double tempX = 0; // Temporary x-coordinate for width calculation
|
||
|
|
var textBlocks = new List<TextBlock>(); // Store TextBlocks for the line
|
||
|
|
var positions = new List<double>(); // Store x-coordinates for each TextBlock
|
||
|
|
|
||
|
|
// 找出第一个和最后一个不是格式符的文本部分的索引
|
||
|
|
var firstNonFormatPartIndex = -1;
|
||
|
|
var lastNonFormatPartIndex = -1;
|
||
|
|
for (var j = 0; j < parts.Length; j++)
|
||
|
|
{
|
||
|
|
var part = parts[j];
|
||
|
|
if (!string.IsNullOrEmpty(part) && !(part.StartsWith('§') && part.Length == 2) && !RegexPatterns.HexColor.IsMatch(part))
|
||
|
|
{
|
||
|
|
if (firstNonFormatPartIndex == -1)
|
||
|
|
{
|
||
|
|
firstNonFormatPartIndex = j;
|
||
|
|
}
|
||
|
|
lastNonFormatPartIndex = j;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for (var i = 0; i < parts.Length; i++) {
|
||
|
|
var part = parts[i];
|
||
|
|
var partTrimmed = part;
|
||
|
|
if (i == firstNonFormatPartIndex) {
|
||
|
|
partTrimmed = part.TrimStart();
|
||
|
|
} else if (i == lastNonFormatPartIndex) {
|
||
|
|
partTrimmed = part.TrimEnd();
|
||
|
|
}
|
||
|
|
if (string.IsNullOrEmpty(partTrimmed)) continue;
|
||
|
|
|
||
|
|
// Handle § color codes
|
||
|
|
if (partTrimmed.StartsWith('§') && partTrimmed.Length == 2) {
|
||
|
|
var code = partTrimmed[1..].ToLower();
|
||
|
|
if (colorMap.TryGetValue(code, out var brush)) {
|
||
|
|
currentColor = brush;
|
||
|
|
isBold = false;
|
||
|
|
isItalic = false;
|
||
|
|
isUnderline = false;
|
||
|
|
isStrikethrough = false;
|
||
|
|
isObfuscated = false;
|
||
|
|
} else if (_formatMap.ContainsKey(code)) {
|
||
|
|
switch (code) {
|
||
|
|
case "l":
|
||
|
|
isBold = true;
|
||
|
|
break;
|
||
|
|
case "o":
|
||
|
|
isItalic = true;
|
||
|
|
break;
|
||
|
|
case "n":
|
||
|
|
isUnderline = true;
|
||
|
|
break;
|
||
|
|
case "m":
|
||
|
|
isStrikethrough = true;
|
||
|
|
break;
|
||
|
|
case "k":
|
||
|
|
isObfuscated = true;
|
||
|
|
break;
|
||
|
|
case "r":
|
||
|
|
currentColor = colorMap["f"];
|
||
|
|
isBold = false;
|
||
|
|
isItalic = false;
|
||
|
|
isUnderline = false;
|
||
|
|
isStrikethrough = false;
|
||
|
|
isObfuscated = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Handle RGB color codes
|
||
|
|
if (RegexPatterns.HexColor.IsMatch(partTrimmed)) {
|
||
|
|
try {
|
||
|
|
var hex = partTrimmed[1..];
|
||
|
|
var r = Convert.ToByte(hex[..2], 16);
|
||
|
|
var g = Convert.ToByte(hex.Substring(2, 2), 16);
|
||
|
|
var b = Convert.ToByte(hex.Substring(4, 2), 16);
|
||
|
|
var inputColor = Color.FromRgb(r, g, b);
|
||
|
|
currentColor = new SolidColorBrush(_AdjustColorForContrast(inputColor));
|
||
|
|
isBold = false;
|
||
|
|
isItalic = false;
|
||
|
|
isUnderline = false;
|
||
|
|
isStrikethrough = false;
|
||
|
|
isObfuscated = false;
|
||
|
|
} catch {
|
||
|
|
// Invalid RGB color, keep current color
|
||
|
|
}
|
||
|
|
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Render text, always use original text for width calculation
|
||
|
|
var displayText = partTrimmed;
|
||
|
|
TextBlock textBlock;
|
||
|
|
if (isObfuscated) {
|
||
|
|
// Generate initial random characters for §k text
|
||
|
|
foreach (var singleChar in partTrimmed) {
|
||
|
|
displayText = RandomChars[_random.Next(RandomChars.Length)].ToString();
|
||
|
|
textBlock = _RenderText(displayText, fontFamily, fontSize, currentColor, isBold, isItalic,
|
||
|
|
isUnderline, isStrikethrough, tempX, y, true,
|
||
|
|
_MeasureTextWidth(singleChar.ToString(), fontFamily, fontSize, isBold, isItalic));
|
||
|
|
_obfuscatedTextBlocks.Add((textBlock, singleChar.ToString()));
|
||
|
|
textBlocks.Add(textBlock);
|
||
|
|
positions.Add(tempX);
|
||
|
|
tempX += _MeasureTextWidth(singleChar.ToString(), fontFamily, fontSize, isBold, isItalic);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
textBlock = _RenderText(displayText, fontFamily, fontSize, currentColor, isBold, isItalic,
|
||
|
|
isUnderline, isStrikethrough, tempX, y);
|
||
|
|
textBlocks.Add(textBlock);
|
||
|
|
positions.Add(tempX);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update tempX coordinate using original text width
|
||
|
|
if (!isObfuscated) {
|
||
|
|
tempX += _MeasureTextWidth(partTrimmed, fontFamily, fontSize, isBold, isItalic);
|
||
|
|
}
|
||
|
|
|
||
|
|
var textHeight = _MeasureTextHeight(partTrimmed, fontFamily, fontSize, isBold, isItalic);
|
||
|
|
lineHeight = textHeight > lineHeight ? textHeight : lineHeight;
|
||
|
|
lineWidth = tempX; // Update line width
|
||
|
|
}
|
||
|
|
|
||
|
|
if (isCentered) {
|
||
|
|
// Center-align: Adjust x-coordinates for each TextBlock
|
||
|
|
var offsetX = (canvasWidth - lineWidth) / 2;
|
||
|
|
for (var i = 0; i < textBlocks.Count; i++) {
|
||
|
|
Canvas.SetLeft(textBlocks[i], positions[i] + offsetX);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 计算当前行的垂直位置
|
||
|
|
double offsetY;
|
||
|
|
if (lineCount == 1) {
|
||
|
|
// 单行文本居中
|
||
|
|
offsetY = (canvasHeight - lineHeight) / 2;
|
||
|
|
} else {
|
||
|
|
// 多行文本的位置计算
|
||
|
|
var totalHeight = lineCount * lineHeight; // 使用实际显示的行数计算总高度
|
||
|
|
var startOffset = (canvasHeight - totalHeight) / 2;
|
||
|
|
offsetY = startOffset + (lineIndex * lineHeight);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 设置所有文本块的垂直位置
|
||
|
|
foreach (var textBlock in textBlocks) {
|
||
|
|
Canvas.SetTop(textBlock, offsetY);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private TextBlock _RenderText(string text, FontFamily fontFamily, double fontSize, Brush color,
|
||
|
|
bool isBold, bool isItalic, bool isUnderline, bool isStrikethrough,
|
||
|
|
double x, double y, bool withClip = false, double clipWidth = 15) {
|
||
|
|
var textBlock = new TextBlock {
|
||
|
|
Text = text,
|
||
|
|
FontFamily = fontFamily,
|
||
|
|
FontSize = fontSize,
|
||
|
|
Foreground = color,
|
||
|
|
FontWeight = isBold ? FontWeights.Bold : FontWeights.Normal,
|
||
|
|
FontStyle = isItalic ? FontStyles.Italic : FontStyles.Normal
|
||
|
|
};
|
||
|
|
|
||
|
|
if (isUnderline || isStrikethrough) {
|
||
|
|
textBlock.TextDecorations = new TextDecorationCollection();
|
||
|
|
if (isUnderline) textBlock.TextDecorations.Add(TextDecorations.Underline);
|
||
|
|
if (isStrikethrough) textBlock.TextDecorations.Add(TextDecorations.Strikethrough);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (withClip) {
|
||
|
|
var clipRect = new RectangleGeometry {
|
||
|
|
Rect = new Rect(0, 0, clipWidth, _MeasureTextHeight(text, fontFamily, fontSize, isBold, isItalic))
|
||
|
|
};
|
||
|
|
textBlock.Clip = clipRect;
|
||
|
|
}
|
||
|
|
|
||
|
|
Canvas.SetLeft(textBlock, x);
|
||
|
|
Canvas.SetTop(textBlock, y);
|
||
|
|
if (Content is Canvas canvas) {
|
||
|
|
canvas.Children.Add(textBlock);
|
||
|
|
}
|
||
|
|
|
||
|
|
return textBlock;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static FormattedText _CreateFormattedText(string text, FontFamily fontFamily, double fontSize, bool isBold, bool isItalic) {
|
||
|
|
return new FormattedText(
|
||
|
|
text,
|
||
|
|
System.Globalization.CultureInfo.InvariantCulture,
|
||
|
|
FlowDirection.LeftToRight,
|
||
|
|
new Typeface(fontFamily, isItalic ? FontStyles.Italic : FontStyles.Normal,
|
||
|
|
isBold ? FontWeights.Bold : FontWeights.Normal, FontStretches.Normal),
|
||
|
|
fontSize,
|
||
|
|
Brushes.White,
|
||
|
|
96);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static double _MeasureTextWidth(string text, FontFamily fontFamily, double fontSize, bool isBold, bool isItalic) {
|
||
|
|
return _CreateFormattedText(text, fontFamily, fontSize, isBold, isItalic).WidthIncludingTrailingWhitespace;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static double _MeasureTextHeight(string text, FontFamily fontFamily, double fontSize, bool isBold, bool isItalic) {
|
||
|
|
return _CreateFormattedText(text, fontFamily, fontSize, isBold, isItalic).Height;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void RenderCanvas() {
|
||
|
|
// Ensure Canvas is rendered
|
||
|
|
MotdCanvas.UpdateLayout();
|
||
|
|
|
||
|
|
// Generate static random characters for §k text
|
||
|
|
foreach (var (textBlock, originalText) in _obfuscatedTextBlocks) {
|
||
|
|
textBlock.Text = string.Join("",
|
||
|
|
Enumerable.Range(0, originalText.Length).Select(_ => RandomChars[_random.Next(RandomChars.Length)]));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Capture Canvas using RenderTargetBitmap
|
||
|
|
var rtb = new RenderTargetBitmap(
|
||
|
|
(int)MotdCanvas.Width, (int)MotdCanvas.Height, 96, 96, PixelFormats.Pbgra32);
|
||
|
|
rtb.Render(MotdCanvas);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void ClearCanvas() {
|
||
|
|
MotdCanvas.Children.Clear();
|
||
|
|
_obfuscatedTextBlocks.Clear();
|
||
|
|
}
|
||
|
|
}
|