mirror of
https://gitlab.acidiclight.dev/sociallydistant/sociallydistant.git
synced 2025-01-22 09:31:47 -05:00
Emoji support
* Add support for raw emoji parsing in TextWidget * Fix SDL not honouring Wayland Support setting * Fix improper markup parsing in TextWidget after emoji support was added * Use Twemoji for emoji, and add support for COLR font glyphs. * Add support for emoji rendering in TextWidget if the font is provided * Add support for parsing emoji shortcodes in TextWidget * Add emoji lookup table to AcidicGUI * Add Noto Color Emoji font for future use
This commit is contained in:
parent
f22f252aac
commit
d2565530ed
16 changed files with 3959 additions and 108 deletions
|
@ -3,13 +3,13 @@
|
|||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-sdcb": {
|
||||
"version": "1.0.0.456",
|
||||
"version": "1.0.0.568",
|
||||
"commands": [
|
||||
"sdcb"
|
||||
]
|
||||
},
|
||||
"dotnet-sdfxc": {
|
||||
"version": "1.0.0.456",
|
||||
"version": "1.0.0.568",
|
||||
"commands": [
|
||||
"sdfxc"
|
||||
]
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FreeTypeSharp" Version="3.0.0" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="1.0.0.456" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="1.0.0.568" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
3368
src/AcidicGUI/Common/Emojis.cs
Normal file
3368
src/AcidicGUI/Common/Emojis.cs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,22 +1,34 @@
|
|||
using System.Buffers.Binary;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using AcidicGUI.Rendering;
|
||||
using FreeTypeSharp;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Color = Microsoft.Xna.Framework.Color;
|
||||
using Point = Microsoft.Xna.Framework.Point;
|
||||
using Rectangle = Microsoft.Xna.Framework.Rectangle;
|
||||
|
||||
namespace AcidicGUI.TextRendering;
|
||||
|
||||
public class Typeface : Font
|
||||
{
|
||||
private static readonly FreeTypeLibrary library = new();
|
||||
private static readonly FreeTypeLibrary library = new();
|
||||
private static readonly Rune carriageReturn = new Rune('\r');
|
||||
private static readonly Rune lineFeed = new Rune('\n');
|
||||
private readonly GraphicsDevice graphicsDevice;
|
||||
private readonly Atlas referenceAtlas;
|
||||
private static bool hasInitialized;
|
||||
private readonly int baseFontSize;
|
||||
private readonly Memory<byte> fontData;
|
||||
private readonly Dictionary<GraphicsDevice, AtlasContainer> atlases = new();
|
||||
|
||||
private readonly Dictionary<GraphicsDevice, AtlasContainer> atlases = new();
|
||||
private readonly Dictionary<uint, Range> svgGlyphRanges = new();
|
||||
private bool hasReadSvgTable = false;
|
||||
|
||||
|
||||
private unsafe Typeface(byte[] data, int referenceFontSize, GraphicsDevice graphicsDevice)
|
||||
{
|
||||
this.graphicsDevice = graphicsDevice;
|
||||
|
@ -27,11 +39,89 @@ public class Typeface : Font
|
|||
referenceAtlas = GetAtlas(graphicsDevice, referenceFontSize);
|
||||
}
|
||||
|
||||
private string? GetSvgDocument(uint glyph)
|
||||
{
|
||||
var fontSpan = fontData.Span;
|
||||
Span<byte> identifierSpan = stackalloc byte[4];
|
||||
|
||||
identifierSpan[0] = (byte) 'S';
|
||||
identifierSpan[1] = (byte) 'V';
|
||||
identifierSpan[2] = (byte)'G';
|
||||
identifierSpan[3] = (byte)' ';
|
||||
|
||||
if (!hasReadSvgTable)
|
||||
{
|
||||
hasReadSvgTable = true;
|
||||
|
||||
int svgOffset = 0;
|
||||
int svgLength = 0;
|
||||
|
||||
var fontHeader = fontSpan.Slice(0, 12);
|
||||
var numTablesRaw = fontHeader.Slice(4, 2);
|
||||
var numTables = BinaryPrimitives.ReadUInt16BigEndian(numTablesRaw);
|
||||
|
||||
for (var i = 0; i < numTables; i++)
|
||||
{
|
||||
const int size = 16;
|
||||
var offset = 12 + size * i;
|
||||
|
||||
var descriptor = fontSpan.Slice(offset, size);
|
||||
var tag = descriptor.Slice(0, identifierSpan.Length);
|
||||
|
||||
if (!identifierSpan.SequenceEqual(tag))
|
||||
continue;
|
||||
|
||||
svgOffset = BinaryPrimitives.ReadInt32BigEndian(descriptor.Slice(8, 4));
|
||||
svgLength = BinaryPrimitives.ReadInt32BigEndian(descriptor.Slice(12, 4));
|
||||
}
|
||||
|
||||
if (svgOffset == 0 || svgLength == 0)
|
||||
return null;
|
||||
|
||||
var svgSpan = fontSpan.Slice((int)svgOffset, (int)svgLength);
|
||||
|
||||
var offsetToSvgDocIndex = BinaryPrimitives.ReadInt32BigEndian(svgSpan.Slice(2, 4));
|
||||
|
||||
var docIndex = svgSpan.Slice(offsetToSvgDocIndex);
|
||||
var numEntries = BinaryPrimitives.ReadUInt16BigEndian(docIndex.Slice(0, 2));
|
||||
|
||||
var entriesSpan = docIndex.Slice(2);
|
||||
|
||||
for (var i = 0; i < numEntries; i++)
|
||||
{
|
||||
const int size = 12;
|
||||
var entry = entriesSpan.Slice(size * i, size);
|
||||
|
||||
var startingGlyph = BinaryPrimitives.ReadUInt16BigEndian(entry.Slice(0, 2));
|
||||
var endingGlyph = BinaryPrimitives.ReadUInt16BigEndian(entry.Slice(2, 2));
|
||||
var offset = BinaryPrimitives.ReadInt32BigEndian(entry.Slice(4, 4));
|
||||
var length = BinaryPrimitives.ReadInt32BigEndian(entry.Slice(8, 4));
|
||||
|
||||
var range = new Range((int)(svgOffset + offsetToSvgDocIndex + offset), (int)(svgOffset + offsetToSvgDocIndex + offset + length));
|
||||
|
||||
for (var glyphIndex = startingGlyph; glyphIndex <= endingGlyph; glyphIndex++)
|
||||
{
|
||||
svgGlyphRanges.Add(glyphIndex, range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!svgGlyphRanges.TryGetValue(glyph, out var svgRange))
|
||||
return null;
|
||||
|
||||
var (stringOffset, stringLength) = svgRange.GetOffsetAndLength(fontSpan.Length);
|
||||
|
||||
var svgData = fontSpan.Slice(stringOffset, stringLength);
|
||||
|
||||
return Encoding.UTF8.GetString(svgData);
|
||||
}
|
||||
|
||||
public override float ReferenceFontSize => referenceAtlas.FontSize;
|
||||
public override float UnderlinePosition => referenceAtlas.UnderlinePosition;
|
||||
|
||||
public override unsafe Point Measure(string text, int? fontSize = null)
|
||||
{
|
||||
var runes = text.EnumerateRunes().ToArray();
|
||||
var atlas = GetAtlas(graphicsDevice, fontSize ?? referenceAtlas.FontSize);
|
||||
|
||||
float lineHeight = atlas.LineHeight;
|
||||
|
@ -40,27 +130,25 @@ public class Typeface : Font
|
|||
float width = 0;
|
||||
float height = 0;
|
||||
|
||||
var chars = text.AsSpan();
|
||||
|
||||
atlas.RasterizeGlyphs(chars);
|
||||
atlas.RasterizeGlyphs(runes);
|
||||
|
||||
var lastWasNewline = false;
|
||||
var info = default(GlyphInfo);
|
||||
for (var i = 0; i < chars.Length; i++)
|
||||
for (var i = 0; i < runes.Length; i++)
|
||||
{
|
||||
char character = chars[i];
|
||||
Rune character = runes[i];
|
||||
|
||||
// Newline characters
|
||||
if (character == '\r' || character == '\n')
|
||||
if (character == carriageReturn || character == lineFeed)
|
||||
{
|
||||
// Check the next char after this one. If it's also a newline, but isn't the same kind as
|
||||
// the current character, we treat both newlines as if they're one.
|
||||
//
|
||||
// This will gracefully handle "\r\n" and "\n\r".
|
||||
if (i + 1 < chars.Length)
|
||||
if (i + 1 < runes.Length)
|
||||
{
|
||||
char nextChar = chars[i + 1];
|
||||
if (nextChar != character && nextChar == '\r' || nextChar == '\n')
|
||||
var nextChar = runes[i + 1];
|
||||
if (nextChar != character && nextChar == carriageReturn || nextChar == lineFeed)
|
||||
i++;
|
||||
}
|
||||
|
||||
|
@ -95,8 +183,14 @@ public class Typeface : Font
|
|||
int? fontSize = null
|
||||
)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return;
|
||||
|
||||
|
||||
if (geometryHelper.GraphicsDevice != this.graphicsDevice)
|
||||
throw new InvalidOperationException("You may only use this typeface to render text to the same graphics device that the typeface was created with.");
|
||||
|
||||
var runes = text.EnumerateRunes().ToArray();
|
||||
|
||||
float sizeScale = (fontSize ?? referenceAtlas.FontSize) / (float) referenceAtlas.FontSize;
|
||||
float baselineOffset = referenceAtlas.Ascender * sizeScale;
|
||||
|
@ -112,28 +206,28 @@ public class Typeface : Font
|
|||
|
||||
var chars = text.AsSpan();
|
||||
|
||||
atlas.RasterizeGlyphs(chars);
|
||||
atlas.RasterizeGlyphs(runes);
|
||||
|
||||
var glyphInfo = default(GlyphInfo);
|
||||
|
||||
var lastTexture = null as Texture2D;
|
||||
var lastMesh = null as GuiMeshBuilder;
|
||||
|
||||
for (var i = 0; i < chars.Length; i++)
|
||||
for (var i = 0; i < runes.Length; i++)
|
||||
{
|
||||
char character = chars[i];
|
||||
var character = runes[i];
|
||||
|
||||
// Newline characters
|
||||
if (character == '\r' || character == '\n')
|
||||
if (character == carriageReturn || character == lineFeed)
|
||||
{
|
||||
// Check the next char after this one. If it's also a newline, but isn't the same kind as
|
||||
// the current character, we treat both newlines as if they're one.
|
||||
//
|
||||
// This will gracefully handle "\r\n" and "\n\r".
|
||||
if (i + 1 < chars.Length)
|
||||
if (i + 1 < runes.Length)
|
||||
{
|
||||
char nextChar = chars[i + 1];
|
||||
if (nextChar != character && nextChar == '\r' || nextChar == '\n')
|
||||
var nextChar = runes[i + 1];
|
||||
if (nextChar != character && nextChar == carriageReturn || nextChar == lineFeed)
|
||||
i++;
|
||||
}
|
||||
|
||||
|
@ -238,11 +332,12 @@ public class Typeface : Font
|
|||
private sealed unsafe class Atlas
|
||||
{
|
||||
private const int defaultAtlasSize = 1024;
|
||||
private readonly Typeface typeface;
|
||||
private readonly int fontSize;
|
||||
private readonly GraphicsDevice device;
|
||||
private readonly List<GlyphInfo> glyphInfo = new();
|
||||
private readonly List<Texture2D> atlasTextures = new();
|
||||
private readonly Dictionary<char, int> glyphMap = new();
|
||||
private readonly Dictionary<Rune, int> glyphMap = new();
|
||||
private readonly FT_FaceRec_* face;
|
||||
private readonly float ascender;
|
||||
private readonly float descender;
|
||||
|
@ -259,8 +354,12 @@ public class Typeface : Font
|
|||
public float UnderlinePosition => underlinePosition;
|
||||
public float LineHeight => lineHeight;
|
||||
|
||||
public Atlas(GraphicsDevice device, int fontSize, FT_FaceRec_* face)
|
||||
public Atlas(GraphicsDevice device, Typeface typeface, int fontSize, FT_FaceRec_* face)
|
||||
{
|
||||
this.typeface = typeface;
|
||||
|
||||
FT.FT_Select_Charmap(face, FT_Encoding_.FT_ENCODING_UNICODE);
|
||||
|
||||
this.device = device;
|
||||
this.fontSize = fontSize;
|
||||
this.face = face;
|
||||
|
@ -281,43 +380,60 @@ public class Typeface : Font
|
|||
underlinePosition = ascender + (fontSize * 0.1f);
|
||||
}
|
||||
|
||||
var flags = default(FT_LOAD);
|
||||
if (fontSize <= 12)
|
||||
{
|
||||
// Disable anti-aliasing for low font sizes to make them less blurry.
|
||||
loadFlags = FT_LOAD.FT_LOAD_MONOCHROME | FT_LOAD.FT_LOAD_FORCE_AUTOHINT;
|
||||
flags = FT_LOAD.FT_LOAD_MONOCHROME | FT_LOAD.FT_LOAD_FORCE_AUTOHINT;
|
||||
}
|
||||
else
|
||||
{
|
||||
loadFlags = FT_LOAD.FT_LOAD_FORCE_AUTOHINT;
|
||||
flags = FT_LOAD.FT_LOAD_FORCE_AUTOHINT;
|
||||
}
|
||||
|
||||
if ((face->face_flags & (uint)FT_FACE_FLAG.FT_FACE_FLAG_COLOR) != 0)
|
||||
flags |= FT_LOAD.FT_LOAD_COLOR;
|
||||
|
||||
loadFlags = flags;
|
||||
}
|
||||
|
||||
public void GetGlyphInfo(char character, ref GlyphInfo info)
|
||||
public void GetGlyphInfo(Rune character, ref GlyphInfo info)
|
||||
{
|
||||
var index = glyphMap[character];
|
||||
info = glyphInfo[index];
|
||||
}
|
||||
|
||||
public void RasterizeGlyphs( ReadOnlySpan<char> characters)
|
||||
public void RasterizeGlyphs( ReadOnlySpan<Rune> characters)
|
||||
{
|
||||
for (var i = 0; i < characters.Length; i++)
|
||||
{
|
||||
char character = characters[i];
|
||||
Rune character = characters[i];
|
||||
|
||||
if (glyphMap.TryGetValue(character, out int glyphIndex))
|
||||
continue;
|
||||
|
||||
glyphIndex = glyphInfo.Count;
|
||||
|
||||
FT.FT_Load_Char(face, (nuint)character, FT_LOAD.FT_LOAD_RENDER | loadFlags);
|
||||
FT.FT_Load_Char(face, (nuint) character.Value, FT_LOAD.FT_LOAD_RENDER | loadFlags);
|
||||
|
||||
uint rasterWidth = face->glyph->bitmap.width;
|
||||
uint rasterHeight = face->glyph->bitmap.rows;
|
||||
var isSvg = (face->face_flags & (uint)FT_FACE_FLAG.FT_FACE_FLAG_SVG) != 0;
|
||||
|
||||
var isVisual = !(rasterWidth == 0 || rasterHeight == 0 || char.IsWhiteSpace(character));
|
||||
var isVisual = isSvg || !(rasterWidth == 0 || rasterHeight == 0 || Rune.IsWhiteSpace(character));
|
||||
|
||||
Texture2D? texture = null;
|
||||
|
||||
byte[]? svgBitmapData = null;
|
||||
|
||||
if (isSvg)
|
||||
{
|
||||
var glyphIndexInFont = face->glyph->glyph_index;
|
||||
var svgString = typeface.GetSvgDocument(glyphIndexInFont);
|
||||
|
||||
throw new InvalidOperationException("Rendering SVG-based fonts is not supported yet. If you feel like tackling the immense performance problems involved in supporting it, then we're accepting merge requests. :)");
|
||||
}
|
||||
|
||||
if (isVisual)
|
||||
{
|
||||
if (atlasTextures.Count > 0)
|
||||
|
@ -350,18 +466,37 @@ public class Typeface : Font
|
|||
}
|
||||
}
|
||||
|
||||
CopyGlyph(
|
||||
texture,
|
||||
face,
|
||||
rasterWidth,
|
||||
rasterHeight,
|
||||
cursorX,
|
||||
cursorY,
|
||||
ref cursorLineHeight,
|
||||
out GlyphInfo info
|
||||
);
|
||||
if (isSvg)
|
||||
{
|
||||
CopyGlyph(
|
||||
texture,
|
||||
face,
|
||||
rasterWidth,
|
||||
rasterHeight,
|
||||
cursorX,
|
||||
cursorY,
|
||||
ref cursorLineHeight,
|
||||
out GlyphInfo info,
|
||||
svgBitmapData
|
||||
);
|
||||
|
||||
this.glyphInfo.Add(info);
|
||||
this.glyphInfo.Add(info);
|
||||
}
|
||||
else
|
||||
{
|
||||
CopyGlyph(
|
||||
texture,
|
||||
face,
|
||||
rasterWidth,
|
||||
rasterHeight,
|
||||
cursorX,
|
||||
cursorY,
|
||||
ref cursorLineHeight,
|
||||
out GlyphInfo info
|
||||
);
|
||||
|
||||
this.glyphInfo.Add(info);
|
||||
}
|
||||
|
||||
cursorX += (int) rasterWidth;
|
||||
glyphMap.Add(character, glyphIndex);
|
||||
|
@ -376,65 +511,94 @@ public class Typeface : Font
|
|||
int atlasX,
|
||||
int atlasY,
|
||||
ref int lineHeight,
|
||||
out GlyphInfo info
|
||||
out GlyphInfo info,
|
||||
byte[]? bitmapData = null
|
||||
)
|
||||
{
|
||||
info = default;
|
||||
|
||||
if (atlas != null)
|
||||
{
|
||||
var isMonochrome = loadFlags.HasFlag(FT_LOAD.FT_LOAD_MONOCHROME);
|
||||
var format = face->glyph->bitmap.pixel_mode;
|
||||
var colors = new Color[(int)rasterWidth * (int)rasterHeight];
|
||||
|
||||
if (isMonochrome)
|
||||
switch (format)
|
||||
{
|
||||
// thank you, chatgpt, for generating this cursed shit
|
||||
for (var y = 0; y < rasterHeight; y++)
|
||||
case FT_Pixel_Mode_.FT_PIXEL_MODE_GRAY:
|
||||
{
|
||||
for (var x = 0; x < rasterWidth; x++)
|
||||
for (var y = 0; y < rasterHeight; y++)
|
||||
{
|
||||
// Each byte in the buffer contains 8 pixels (1 bit per pixel)
|
||||
int byteIndex = y * face->glyph->bitmap.pitch + (x / 8);
|
||||
byte bitMask = (byte)(0x80 >> (x % 8)); // Get the specific bit for this pixel
|
||||
|
||||
bool isPixelSet = (face->glyph->bitmap.buffer[byteIndex] & bitMask) != 0;
|
||||
|
||||
int colorIndex = y * (int)rasterWidth + x;
|
||||
|
||||
if (isPixelSet)
|
||||
for (var x = 0; x < rasterWidth; x++)
|
||||
{
|
||||
// Set the pixel to black (opaque)
|
||||
int index = y * face->glyph->bitmap.pitch + x;
|
||||
byte alpha = face->glyph->bitmap.buffer[index];
|
||||
int colorIndex = y * (int)rasterWidth + x;
|
||||
|
||||
colors[colorIndex].R = 255;
|
||||
colors[colorIndex].G = 255;
|
||||
colors[colorIndex].B = 255;
|
||||
colors[colorIndex].A = 255;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the pixel to transparent (white background)
|
||||
colors[colorIndex].R = 0;
|
||||
colors[colorIndex].G = 0;
|
||||
colors[colorIndex].B = 0;
|
||||
colors[colorIndex].A = 0;
|
||||
colors[colorIndex].A = alpha;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var y = 0; y < rasterHeight; y++)
|
||||
case FT_Pixel_Mode_.FT_PIXEL_MODE_MONO:
|
||||
{
|
||||
for (var x = 0; x < rasterWidth; x++)
|
||||
// thank you, chatgpt, for generating this cursed shit
|
||||
for (var y = 0; y < rasterHeight; y++)
|
||||
{
|
||||
int index = y * face->glyph->bitmap.pitch + x;
|
||||
byte alpha = face->glyph->bitmap.buffer[index];
|
||||
int colorIndex = y * (int)rasterWidth + x;
|
||||
for (var x = 0; x < rasterWidth; x++)
|
||||
{
|
||||
// Each byte in the buffer contains 8 pixels (1 bit per pixel)
|
||||
int byteIndex = y * face->glyph->bitmap.pitch + (x / 8);
|
||||
byte bitMask = (byte)(0x80 >> (x % 8)); // Get the specific bit for this pixel
|
||||
|
||||
colors[colorIndex].R = 255;
|
||||
colors[colorIndex].G = 255;
|
||||
colors[colorIndex].B = 255;
|
||||
colors[colorIndex].A = alpha;
|
||||
bool isPixelSet = (face->glyph->bitmap.buffer[byteIndex] & bitMask) != 0;
|
||||
|
||||
int colorIndex = y * (int)rasterWidth + x;
|
||||
|
||||
if (isPixelSet)
|
||||
{
|
||||
// Set the pixel to black (opaque)
|
||||
colors[colorIndex].R = 255;
|
||||
colors[colorIndex].G = 255;
|
||||
colors[colorIndex].B = 255;
|
||||
colors[colorIndex].A = 255;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the pixel to transparent (white background)
|
||||
colors[colorIndex].R = 0;
|
||||
colors[colorIndex].G = 0;
|
||||
colors[colorIndex].B = 0;
|
||||
colors[colorIndex].A = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case FT_Pixel_Mode_.FT_PIXEL_MODE_BGRA:
|
||||
{
|
||||
for (int y = 0; y < face->glyph->bitmap.rows; y++)
|
||||
{
|
||||
for (int x = 0; x < face->glyph->bitmap.width; x++)
|
||||
{
|
||||
int offset = (y * face->glyph->bitmap.pitch) + (x * 4); // 4 bytes per pixel
|
||||
var colorIndex = y * rasterWidth + x;
|
||||
|
||||
colors[colorIndex].B = face->glyph->bitmap.buffer[offset + 0];
|
||||
colors[colorIndex].G = face->glyph->bitmap.buffer[offset + 1];
|
||||
colors[colorIndex].R = face->glyph->bitmap.buffer[offset + 2];
|
||||
colors[colorIndex].A = face->glyph->bitmap.buffer[offset + 3];
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new InvalidOperationException("Unsupported glyph format: " + format.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -474,10 +638,13 @@ public class Typeface : Font
|
|||
info.VisualOffsetX = face->glyph->bitmap_left;
|
||||
info.VisualOffsetY = face->glyph->bitmap_top;
|
||||
|
||||
if (info.VisualOffsetY == 0)
|
||||
info.VisualOffsetY = (int) rasterHeight;
|
||||
|
||||
lineHeight = Math.Max(lineHeight, (int) rasterHeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private sealed unsafe class AtlasContainer(GraphicsDevice device, Typeface typeface)
|
||||
{
|
||||
private readonly Typeface typeface = typeface;
|
||||
|
@ -488,7 +655,7 @@ public class Typeface : Font
|
|||
{
|
||||
if (!atlases.TryGetValue(fontSize, out var atlas))
|
||||
{
|
||||
atlas = new Atlas(device, fontSize, typeface.LoadFont());
|
||||
atlas = new Atlas(device, typeface, fontSize, typeface.LoadFont());
|
||||
atlases.Add(fontSize, atlas);
|
||||
}
|
||||
|
||||
|
@ -496,6 +663,8 @@ public class Typeface : Font
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public struct GlyphInfo
|
||||
{
|
||||
public bool IsVisual;
|
||||
|
|
|
@ -9,6 +9,7 @@ namespace AcidicGUI.VisualStyles;
|
|||
internal sealed class FallbackVisualStyle : IVisualStyle
|
||||
{
|
||||
public Font? IconFont => null;
|
||||
public Font? EmojiFont => null;
|
||||
public Padding DropdownButtonPadding { get; } = 3;
|
||||
public Color SelectionColor => Color.LightBlue;
|
||||
public Color TextSelectionBackground => Color.Blue;
|
||||
|
|
|
@ -29,6 +29,7 @@ public interface IVisualStyle :
|
|||
IVisualRenderer<Popover.PopoverVisualState>
|
||||
{
|
||||
Font? IconFont { get; }
|
||||
Font? EmojiFont { get; }
|
||||
Padding DropdownButtonPadding { get; }
|
||||
Color SelectionColor { get; }
|
||||
Color TextSelectionBackground { get; }
|
||||
|
|
|
@ -46,6 +46,12 @@ public static class StyleManager
|
|||
activeStyle = newStyle;
|
||||
}
|
||||
|
||||
internal static Font? GetEmojiFont(Widget widget)
|
||||
{
|
||||
var style = widget.GetVisualStyleOverride() ?? activeStyle;
|
||||
return style?.EmojiFont;
|
||||
}
|
||||
|
||||
internal static IFontFamily GetFont(PresetFontFamily family, IVisualStyle? styleOverride)
|
||||
{
|
||||
var style = styleOverride ?? activeStyle;
|
||||
|
|
|
@ -21,7 +21,8 @@ public class TextWidget : Widget,
|
|||
|
||||
private static readonly Dictionary<string, Texture2D> images = new();
|
||||
private static IImageLocator? _imageLocator;
|
||||
|
||||
|
||||
private bool parseEmoji = true;
|
||||
private int previousWrapWidth;
|
||||
private Color color = Color.White;
|
||||
private FontFamilyInfo fontFamily;
|
||||
|
@ -135,6 +136,20 @@ public class TextWidget : Widget,
|
|||
InvalidateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public bool AllowEmoji
|
||||
{
|
||||
get => parseEmoji;
|
||||
set
|
||||
{
|
||||
if (parseEmoji == value)
|
||||
return;
|
||||
|
||||
parseEmoji = value;
|
||||
RebuildText();
|
||||
InvalidateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseMarkup
|
||||
{
|
||||
|
@ -345,8 +360,28 @@ public class TextWidget : Widget,
|
|||
geometry.AddQuad(highlightRect, highlight);
|
||||
}
|
||||
|
||||
family.Draw(geometry, element.Position.ToVector2(), renderColor, element.Text, element.MarkupData.FontSize ?? FontSize,
|
||||
element.MarkupData.Weight ?? FontWeight, element.MarkupData.Italic);
|
||||
if (element.IsEmoji && element.EmojiFont != null)
|
||||
{
|
||||
element.EmojiFont.Draw(
|
||||
geometry,
|
||||
element.Position.ToVector2(),
|
||||
renderColor,
|
||||
element.Text,
|
||||
element.MarkupData.FontSize ?? fontSize
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
family.Draw(
|
||||
geometry,
|
||||
element.Position.ToVector2(),
|
||||
renderColor,
|
||||
element.Text,
|
||||
element.MarkupData.FontSize ?? FontSize,
|
||||
element.MarkupData.Weight ?? FontWeight,
|
||||
element.MarkupData.Italic
|
||||
);
|
||||
}
|
||||
|
||||
var underlineOffset = family.GetUnderlineOffset(element.MarkupData.FontSize ?? FontSize, element.MarkupData.Weight ?? FontWeight, element.MarkupData.Italic);
|
||||
var strikeLine = 1;
|
||||
|
@ -396,11 +431,27 @@ public class TextWidget : Widget,
|
|||
{
|
||||
if (!textElements[i].MarkupData.IsImage)
|
||||
{
|
||||
var family = (textElements[i].MarkupData.FontOverride ?? fontFamily).GetFont(this);
|
||||
var newMeasurement = family.Measure(textElements[i].Text.TrimEnd(), textElements[i].MarkupData.FontSize ?? FontSize, textElements[i].MarkupData.Weight ?? FontWeight, textElements[i].MarkupData.Italic);
|
||||
if (textElements[i].IsEmoji && textElements[i].EmojiFont != null)
|
||||
{
|
||||
var emojiFont = textElements[i].EmojiFont;
|
||||
var newMeasurement = emojiFont.Measure(textElements[i].Text.TrimEnd(), textElements[i].MarkupData.FontSize ?? FontSize);
|
||||
|
||||
newMeasurement.Y = family.GetLineHeight(textElements[i].MarkupData.FontSize ?? FontSize, textElements[i].MarkupData.Weight ?? FontWeight, textElements[i].MarkupData.Italic);
|
||||
textElements[i].MeasuredSize = newMeasurement;
|
||||
newMeasurement.Y = emojiFont.GetLineHeight(textElements[i].MarkupData.FontSize ?? FontSize);
|
||||
textElements[i].MeasuredSize = newMeasurement;
|
||||
}
|
||||
else
|
||||
{
|
||||
var family = (textElements[i].MarkupData.FontOverride ?? fontFamily).GetFont(this);
|
||||
var newMeasurement = family.Measure(
|
||||
textElements[i].Text.TrimEnd(),
|
||||
textElements[i].MarkupData.FontSize ?? FontSize,
|
||||
textElements[i].MarkupData.Weight ?? FontWeight,
|
||||
textElements[i].MarkupData.Italic
|
||||
);
|
||||
|
||||
newMeasurement.Y = family.GetLineHeight(textElements[i].MarkupData.FontSize ?? FontSize, textElements[i].MarkupData.Weight ?? FontWeight, textElements[i].MarkupData.Italic);
|
||||
textElements[i].MeasuredSize = newMeasurement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -417,14 +468,33 @@ public class TextWidget : Widget,
|
|||
{
|
||||
offset.X -= textElements[i - 1].MeasuredSize!.Value.X;
|
||||
|
||||
var newFamily = (textElements[i - 1].MarkupData.FontOverride ?? fontFamily).GetFont(this);
|
||||
if (textElements[i - 1].IsEmoji && textElements[i - 1].EmojiFont != null)
|
||||
{
|
||||
var newEmojiFont = textElements[i - 1].EmojiFont;
|
||||
|
||||
var newMeasurement = newFamily.Measure(textElements[i - 1].Text.TrimEnd(), textElements[i - 1].MarkupData.FontSize ?? FontSize, textElements[i - 1].MarkupData.Weight ?? FontWeight, textElements[i - 1].MarkupData.Italic);
|
||||
var newMeasurement = newEmojiFont!.Measure(textElements[i - 1].Text.TrimEnd(), textElements[i - 1].MarkupData.FontSize ?? FontSize);
|
||||
|
||||
newMeasurement.Y = newFamily.GetLineHeight(textElements[i - 1].MarkupData.FontSize ?? FontSize, textElements[i - 1].MarkupData.Weight ?? FontWeight, textElements[i - 1].MarkupData.Italic);
|
||||
textElements[i - 1].MeasuredSize = newMeasurement;
|
||||
newMeasurement.Y = newEmojiFont.GetLineHeight(textElements[i - 1].MarkupData.FontSize ?? FontSize);
|
||||
textElements[i - 1].MeasuredSize = newMeasurement;
|
||||
|
||||
offset.X += newMeasurement.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newFamily = (textElements[i - 1].MarkupData.FontOverride ?? fontFamily).GetFont(this);
|
||||
|
||||
offset.X += newMeasurement.X;
|
||||
var newMeasurement = newFamily.Measure(
|
||||
textElements[i - 1].Text.TrimEnd(),
|
||||
textElements[i - 1].MarkupData.FontSize ?? FontSize,
|
||||
textElements[i - 1].MarkupData.Weight ?? FontWeight,
|
||||
textElements[i - 1].MarkupData.Italic
|
||||
);
|
||||
|
||||
newMeasurement.Y = newFamily.GetLineHeight(textElements[i - 1].MarkupData.FontSize ?? FontSize, textElements[i - 1].MarkupData.Weight ?? FontWeight, textElements[i - 1].MarkupData.Italic);
|
||||
textElements[i - 1].MeasuredSize = newMeasurement;
|
||||
|
||||
offset.X += newMeasurement.X;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -654,11 +724,34 @@ public class TextWidget : Widget,
|
|||
images.Add(path, image);
|
||||
return image;
|
||||
}
|
||||
|
||||
private bool ParseEmoji(ReadOnlySpan<char> characters, int startIndex, out int rawLength, out string? codepoint)
|
||||
{
|
||||
codepoint = default;
|
||||
rawLength = 0;
|
||||
|
||||
var afterColon = characters.Slice(startIndex + 1);
|
||||
var nextColon = afterColon.IndexOf(':');
|
||||
|
||||
if (nextColon == -1)
|
||||
return false;
|
||||
|
||||
var identifier = afterColon.Slice(0, nextColon).ToString();
|
||||
|
||||
if (!Emojis.All.TryGetValue(identifier, out uint codepointValue))
|
||||
return false;
|
||||
|
||||
codepoint = char.ConvertFromUtf32((int)codepointValue);
|
||||
rawLength = 2 + identifier.Length;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RebuildText()
|
||||
{
|
||||
var markupData = new MarkupData();
|
||||
var newMarkupData = new MarkupData();
|
||||
var emojiFont = StyleManager.GetEmojiFont(this);
|
||||
var emojiSupported = parseEmoji && emojiFont != null;
|
||||
|
||||
var sourceStart = 0;
|
||||
|
||||
|
@ -707,9 +800,56 @@ public class TextWidget : Widget,
|
|||
|
||||
switch (character.Value)
|
||||
{
|
||||
case '<' when useMarkup:
|
||||
case { } when character.Value >= 0xd800 && character.Value <= 0xdbff:
|
||||
{
|
||||
if (!ParseMarkup(chars, i, ref newMarkupData))
|
||||
if (i + 1 >= chars.Length)
|
||||
continue;
|
||||
|
||||
char nextChar = chars[i + 1];
|
||||
if (nextChar < 0xdc00 || nextChar > 0xdfff)
|
||||
goto default;
|
||||
|
||||
Rune surrogatePair = new Rune(character.Value, nextChar);
|
||||
|
||||
if (!emojiSupported)
|
||||
goto default;
|
||||
|
||||
if (!Emojis.Codepoints.Contains((uint)surrogatePair.Value))
|
||||
goto default;
|
||||
|
||||
textElements.Add(new TextElement
|
||||
{
|
||||
Text = stringBuilder.ToString(),
|
||||
SourceStart = sourceStart,
|
||||
SourceEnd = i,
|
||||
MarkupData = markupData
|
||||
});
|
||||
|
||||
stringBuilder.Length = 0;
|
||||
sourceStart = i;
|
||||
|
||||
stringBuilder.Append($"{character.Value}{nextChar}");
|
||||
i += 2;
|
||||
|
||||
textElements.Add(new TextElement
|
||||
{
|
||||
Text = stringBuilder.ToString(),
|
||||
IsEmoji = true,
|
||||
EmojiFont = emojiFont,
|
||||
SourceStart = sourceStart,
|
||||
SourceEnd = i,
|
||||
MarkupData = markupData
|
||||
});
|
||||
|
||||
stringBuilder.Length = 0;
|
||||
sourceStart = i;
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
|
||||
case ':' when emojiSupported && useMarkup:
|
||||
{
|
||||
if (!ParseEmoji(chars, i, out int rawLength, out string? codepoint))
|
||||
goto default;
|
||||
|
||||
textElements.Add(new TextElement
|
||||
|
@ -719,6 +859,47 @@ public class TextWidget : Widget,
|
|||
SourceEnd = i,
|
||||
MarkupData = markupData
|
||||
});
|
||||
|
||||
stringBuilder.Length = 0;
|
||||
sourceStart = i;
|
||||
|
||||
stringBuilder.Append(codepoint);
|
||||
i += rawLength;
|
||||
|
||||
textElements.Add(new TextElement
|
||||
{
|
||||
Text = stringBuilder.ToString(),
|
||||
IsEmoji = true,
|
||||
EmojiFont = emojiFont,
|
||||
SourceStart = sourceStart,
|
||||
SourceEnd = i,
|
||||
MarkupData = markupData
|
||||
});
|
||||
|
||||
stringBuilder.Length = 0;
|
||||
sourceStart = i;
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
case '<' when useMarkup:
|
||||
{
|
||||
if (!ParseMarkup(chars, i, ref newMarkupData))
|
||||
goto default;
|
||||
|
||||
if (stringBuilder.Length > 0)
|
||||
{
|
||||
textElements.Add(
|
||||
new TextElement
|
||||
{
|
||||
Text = stringBuilder.ToString(),
|
||||
SourceStart = sourceStart,
|
||||
SourceEnd = i,
|
||||
MarkupData = markupData
|
||||
}
|
||||
);
|
||||
|
||||
stringBuilder.Length = 0;
|
||||
}
|
||||
|
||||
int markupLength = newMarkupData.Length;
|
||||
|
||||
|
@ -737,9 +918,6 @@ public class TextWidget : Widget,
|
|||
|
||||
markupData = newMarkupData;
|
||||
|
||||
stringBuilder.Length = 0;
|
||||
sourceStart = i;
|
||||
|
||||
i += markupLength - 1;
|
||||
break;
|
||||
}
|
||||
|
@ -956,6 +1134,12 @@ public class TextWidget : Widget,
|
|||
if (textElements[i].MeasuredSize != null)
|
||||
continue;
|
||||
|
||||
if (textElements[i].IsEmoji && textElements[i].EmojiFont != null)
|
||||
{
|
||||
textElements[i].MeasuredSize = textElements[i].EmojiFont!.Measure(textElements[i].Text, textElements[i].MarkupData.FontSize ?? fontSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (family == null || lastOverride != textElements[i].MarkupData.FontOverride)
|
||||
{
|
||||
family = (textElements[i].MarkupData.FontOverride ?? fontFamily).GetFont(this);
|
||||
|
@ -1068,6 +1252,8 @@ public class TextWidget : Widget,
|
|||
private class TextElement
|
||||
{
|
||||
public string Text = string.Empty;
|
||||
public bool IsEmoji;
|
||||
public Font? EmojiFont;
|
||||
public Point Position;
|
||||
public Point? MeasuredSize;
|
||||
public bool IsNewLine;
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Net.Mime;
|
||||
using Microsoft.DotNet.PlatformAbstractions;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using SociallyDistant.Core.Core.Config;
|
||||
using SociallyDistant.Core.Modules;
|
||||
|
||||
namespace SociallyDistant.Core.Config.SystemConfigCategories
|
||||
{
|
||||
|
@ -144,8 +146,14 @@ namespace SociallyDistant.Core.Config.SystemConfigCategories
|
|||
"Wayland Support",
|
||||
"Change whether Socially Distant uses Wayland or X11 to communicate with your desktop environment. Disabling Wayland support may work around bugs on certain desktops. Changing this setting requires a game restart.",
|
||||
UseWaylandBackend,
|
||||
x => UseWaylandBackend = x,
|
||||
linux
|
||||
x =>
|
||||
{
|
||||
UseWaylandBackend = x;
|
||||
Application.Instance.Restart();
|
||||
},
|
||||
linux,
|
||||
true,
|
||||
"Changing this setting requires the game to restart. Are you sure you want to continue? Any unsaved progress will be lost."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,9 @@ public class SociallyDistantVisualStyle : IVisualStyle,
|
|||
private readonly ChatMessageStyle chatMessageStyle;
|
||||
private readonly Color mainBackground = new Color(0x11, 0x13, 0x15);
|
||||
private readonly CompletionListSTyle completionListSTyle;
|
||||
private readonly PopoverStyle popoverStyle;
|
||||
private readonly PopoverStyle popoverStyle;
|
||||
private Font? emojiFont;
|
||||
|
||||
|
||||
private readonly Color statusBarColor = new Color(
|
||||
0x01,
|
||||
|
@ -125,6 +127,7 @@ public class SociallyDistantVisualStyle : IVisualStyle,
|
|||
private Texture2D? checkboxEmblem;
|
||||
|
||||
public Font? IconFont => iconFont;
|
||||
public Font? EmojiFont => emojiFont;
|
||||
|
||||
public Padding DropdownButtonPadding { get; } = new Padding(
|
||||
1,
|
||||
|
@ -173,6 +176,7 @@ public class SociallyDistantVisualStyle : IVisualStyle,
|
|||
checkboxEmblem = game.GameInstance.Content.Load<Texture2D>("/Core/UI/Textures/checkbox_emblem");
|
||||
|
||||
iconFont = Typeface.FromStream(graphicsDevice, game.GameInstance.Content.Load<Stream>("/Core/UI/lucide.ttf"), 16);
|
||||
emojiFont = Typeface.FromStream(graphicsDevice, game.GameInstance.Content.Load<Stream>("/Core/UI/Fonts/Emoji/Twemoji.Mozilla.ttf"), 16);
|
||||
|
||||
defaultFont = LoadFont("/Core/UI/Fonts/Rajdhani", graphicsDevice);
|
||||
monospace = LoadFont("/Core/UI/Fonts/Monospace/JetBrainsMono", graphicsDevice);
|
||||
|
|
|
@ -8,9 +8,11 @@ metadata() {
|
|||
}
|
||||
|
||||
email() {
|
||||
echo Hello world.
|
||||
echo ":earth_americas:" Hello world.
|
||||
echo This is a test.
|
||||
echo If you are reading this, "<b>this text should be bold.</b>"
|
||||
echo "💖 this is a test of raw unicode emoji parsing, if you saw the heart then it worked."
|
||||
echo If you are :book: reading this, "<b>this text should be bold.</b>"
|
||||
echo :eyes: Emoji are supported as well.
|
||||
}
|
||||
|
||||
start() {
|
||||
|
|
93
src/SociallyDistant/Content/UI/Fonts/Emoji/OFL.txt
Normal file
93
src/SociallyDistant/Content/UI/Fonts/Emoji/OFL.txt
Normal file
|
@ -0,0 +1,93 @@
|
|||
Copyright 2021 Google Inc. All Rights Reserved.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
src/SociallyDistant/Content/UI/Fonts/Emoji/Twemoji.Mozilla.ttf
Normal file
BIN
src/SociallyDistant/Content/UI/Fonts/Emoji/Twemoji.Mozilla.ttf
Normal file
Binary file not shown.
|
@ -1,4 +1,6 @@
|
|||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Serilog;
|
||||
using SociallyDistant.Core.Config;
|
||||
using SociallyDistant.Core.Config.SystemConfigCategories;
|
||||
|
@ -23,15 +25,20 @@ internal sealed class GameApplication : Application
|
|||
settingsManager.Load();
|
||||
|
||||
Environment.SetEnvironmentVariable("SDL_LOG_PRIORITY", "debug");
|
||||
|
||||
var waylandDisplayIsPresent = !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("WAYLAND_DISPLAY"));
|
||||
|
||||
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
var waylandDisplayIsPresent = isLinux && !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("WAYLAND_DISPLAY"));
|
||||
|
||||
var graphicsSettings = new GraphicsSettings(settingsManager);
|
||||
|
||||
if (waylandDisplayIsPresent && graphicsSettings.UseWaylandBackend)
|
||||
{
|
||||
Log.Information("Running SDL2 in Wayland mode.");
|
||||
Environment.SetEnvironmentVariable("SDL_VIDEODRIVER", "wayland");
|
||||
SdlPlatformSettings.PreferredVideoDriver = "wayland";
|
||||
}
|
||||
else if (isLinux)
|
||||
{
|
||||
SdlPlatformSettings.PreferredVideoDriver = "xcb"; // fallback to X11.
|
||||
}
|
||||
|
||||
game = new SociallyDistantGame(this);
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<PackageReference Include="AutoPipeline" Version="0.0.2" />
|
||||
<PackageReference Include="FuzzySharp" Version="2.0.2" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="1.0.0.456" />
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="1.0.0.568" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -99,6 +99,12 @@
|
|||
<None Update="Content\UI\lucide.ttf">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Content\UI\Fonts\Emoji\OFL.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Content\UI\Fonts\Emoji\Twemoji.Mozilla.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ImGui.NET" Version="1.90.1.1" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="1.0.0.456" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="1.0.0.568" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="RestoreDotnetTools" BeforeTargets="Restore">
|
||||
|
|
Loading…
Reference in a new issue