XIVChat/XIVChat Desktop/MessageFormatter.cs

211 lines
9.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using MdXaml;
using XIVChatCommon.Message;
using XIVChatCommon.Message.Server;
namespace XIVChat_Desktop {
public static class MessageFormatter {
public enum CrossWorldIconMode {
Icon,
Text,
}
public class Options {
public double LineHeight { get; set; }
public bool ProcessMarkdown { get; set; }
public bool ShowTimestamp { get; set; }
public CrossWorldIconMode CrossWorldIconMode { get; set; } = CrossWorldIconMode.Icon;
}
private static readonly BitmapFrame FontIcon = BitmapFrame.Create(new Uri("pack://application:,,,/Resources/fonticon_ps4.tex.png"));
private static readonly Markdown Markdown = new() {
HyperlinkCommand = MainWindow.OpenLink,
};
public static IEnumerable<Inline> ChunksToTextBlock(ServerMessage message, Options options) {
var elements = new List<Inline>();
if (options.ShowTimestamp) {
var timestampString = message.Timestamp.ToLocalTime().ToString("t", CultureInfo.CurrentUICulture);
elements.Add(new Run($"[{timestampString}]") {
Foreground = new SolidColorBrush(Colors.White),
});
}
foreach (var chunk in message.Chunks) {
switch (chunk) {
case TextChunk textChunk:
var colour = textChunk.Foreground ?? textChunk.FallbackColour ?? 0;
var r = (byte) ((colour >> 24) & 0xFF);
var g = (byte) ((colour >> 16) & 0xFF);
var b = (byte) ((colour >> 8) & 0xFF);
var a = (byte) (colour & 0xFF);
var brush = new SolidColorBrush(Color.FromArgb(a, r, g, b));
var style = textChunk.Italic ? FontStyles.Italic : FontStyles.Normal;
if (options.ProcessMarkdown) {
var inlines = Markdown.RunSpanGamut(textChunk.Content);
foreach (var inline in inlines) {
inline.Foreground = brush;
if (inline.FontStyle == FontStyles.Normal) {
inline.FontStyle = style;
}
elements.Add(inline);
}
} else {
elements.Add(new Run(textChunk.Content) {
Foreground = brush,
FontStyle = style,
});
}
break;
case IconChunk iconChunk:
if (options.CrossWorldIconMode == CrossWorldIconMode.Text && iconChunk.index == 88) {
// find the last non-timestamp run
var lastRun = elements.FindLast(elem => (!options.ShowTimestamp || elements.Count > 1) && elem is Run);
var foreground = lastRun?.Foreground;
var fontStyle = lastRun?.FontStyle ?? FontStyles.Normal;
elements.Add(new Run("@") {
Foreground = foreground,
FontStyle = fontStyle,
});
break;
}
var bounds = GetBounds(iconChunk.index);
if (bounds == null) {
break;
}
var width = options.LineHeight / bounds.Value.Height * bounds.Value.Width;
var cropped = new CroppedBitmap(FontIcon, bounds.Value);
var image = new Image {
Source = cropped,
Width = width,
Height = options.LineHeight,
};
elements.Add(new InlineUIContainer(image) {
BaselineAlignment = BaselineAlignment.Bottom,
});
break;
}
}
// add a trailing empty run if there's a hyperlink
// this works around a crash
if (elements.Any(elem => elem is Hyperlink)) {
elements.Add(new Run(""));
}
return elements;
}
private static Int32Rect? GetBounds(byte id) => id switch {
1 => new Int32Rect(0, 342, 40, 40),
2 => new Int32Rect(40, 342, 40, 40),
3 => new Int32Rect(80, 342, 40, 40),
4 => new Int32Rect(120, 342, 40, 40),
5 => new Int32Rect(160, 342, 40, 40),
6 => new Int32Rect(0, 382, 40, 40),
7 => new Int32Rect(40, 382, 40, 40),
8 => new Int32Rect(80, 382, 40, 40),
9 => new Int32Rect(120, 382, 40, 40),
10 => new Int32Rect(160, 382, 40, 40),
11 => new Int32Rect(0, 422, 40, 40),
12 => new Int32Rect(40, 422, 40, 40),
13 => new Int32Rect(80, 422, 40, 40),
14 => new Int32Rect(120, 422, 40, 40),
15 => new Int32Rect(160, 422, 40, 40),
16 => new Int32Rect(120, 542, 40, 40),
17 => new Int32Rect(160, 542, 40, 40),
18 => new Int32Rect(0, 462, 108, 40),
19 => new Int32Rect(108, 462, 108, 40),
20 => new Int32Rect(120, 502, 40, 40),
21 => new Int32Rect(0, 502, 56, 40),
22 => new Int32Rect(56, 502, 64, 40),
23 => new Int32Rect(160, 502, 40, 40),
24 => new Int32Rect(0, 542, 56, 40),
25 => new Int32Rect(56, 542, 64, 40),
51 => new Int32Rect(248, 342, 40, 40),
52 => new Int32Rect(288, 342, 40, 40),
53 => new Int32Rect(328, 342, 40, 40),
54 => new Int32Rect(200, 342, 24, 40),
55 => new Int32Rect(224, 342, 24, 40),
56 => new Int32Rect(200, 382, 40, 40),
57 => new Int32Rect(240, 382, 40, 40),
58 => new Int32Rect(280, 382, 40, 40),
59 => new Int32Rect(200, 422, 40, 40),
60 => new Int32Rect(240, 422, 40, 40),
61 => new Int32Rect(280, 422, 40, 40),
62 => new Int32Rect(320, 382, 40, 40),
63 => new Int32Rect(320, 422, 40, 40),
64 => new Int32Rect(368, 342, 40, 40),
65 => new Int32Rect(408, 342, 40, 40),
66 => new Int32Rect(448, 342, 40, 40),
67 => new Int32Rect(360, 382, 40, 40),
68 => new Int32Rect(400, 382, 40, 40),
70 => new Int32Rect(360, 422, 40, 40),
71 => new Int32Rect(400, 422, 40, 40),
72 => new Int32Rect(440, 422, 40, 40),
73 => new Int32Rect(440, 382, 40, 40),
74 => new Int32Rect(216, 462, 40, 40),
75 => new Int32Rect(256, 462, 40, 40),
76 => new Int32Rect(296, 462, 40, 40),
77 => new Int32Rect(336, 462, 40, 40),
78 => new Int32Rect(376, 462, 40, 40),
79 => new Int32Rect(416, 462, 40, 40),
80 => new Int32Rect(456, 462, 40, 40),
81 => new Int32Rect(200, 502, 40, 40),
82 => new Int32Rect(240, 502, 40, 40),
83 => new Int32Rect(280, 502, 40, 40),
84 => new Int32Rect(320, 502, 40, 40),
85 => new Int32Rect(360, 502, 40, 40),
86 => new Int32Rect(400, 502, 40, 40),
87 => new Int32Rect(440, 502, 40, 40),
88 => new Int32Rect(200, 542, 40, 40),
89 => new Int32Rect(240, 542, 40, 40),
90 => new Int32Rect(280, 542, 40, 40),
91 => new Int32Rect(320, 542, 40, 40),
92 => new Int32Rect(360, 542, 40, 40),
93 => new Int32Rect(400, 542, 40, 40),
94 => new Int32Rect(440, 542, 40, 40),
95 => new Int32Rect(0, 582, 40, 40),
96 => new Int32Rect(40, 582, 40, 40),
97 => new Int32Rect(80, 582, 40, 40),
98 => new Int32Rect(120, 582, 40, 40),
99 => new Int32Rect(160, 582, 40, 40),
100 => new Int32Rect(200, 582, 40, 40),
101 => new Int32Rect(240, 582, 40, 40),
102 => new Int32Rect(280, 582, 40, 40),
103 => new Int32Rect(320, 582, 40, 40),
104 => new Int32Rect(360, 582, 40, 40),
105 => new Int32Rect(400, 582, 40, 40),
106 => new Int32Rect(440, 582, 40, 40),
107 => new Int32Rect(0, 622, 40, 40),
108 => new Int32Rect(40, 622, 40, 40),
109 => new Int32Rect(80, 622, 40, 40),
110 => new Int32Rect(120, 622, 40, 40),
111 => new Int32Rect(160, 622, 40, 40),
112 => new Int32Rect(200, 622, 40, 40),
_ => null,
};
}
}