Get the Ascender value from mod.yaml instead from the Font
This commit is contained in:
37
OpenRA.Game/Fonts.cs
Normal file
37
OpenRA.Game/Fonts.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright 2007-2019 The OpenRA Developers (see AUTHORS)
|
||||||
|
* This file is part of OpenRA, which is free software. It is made
|
||||||
|
* available to you under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version. For more
|
||||||
|
* information, see COPYING.
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace OpenRA
|
||||||
|
{
|
||||||
|
public class FontData
|
||||||
|
{
|
||||||
|
public readonly string Font;
|
||||||
|
public readonly int Size;
|
||||||
|
public readonly int Ascender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Fonts : IGlobalModData
|
||||||
|
{
|
||||||
|
[FieldLoader.LoadUsing("LoadFonts")]
|
||||||
|
public readonly Dictionary<string, FontData> FontList;
|
||||||
|
|
||||||
|
static object LoadFonts(MiniYaml y)
|
||||||
|
{
|
||||||
|
var ret = new Dictionary<string, FontData>();
|
||||||
|
foreach (var node in y.Nodes)
|
||||||
|
ret.Add(node.Key, FieldLoader.Load<FontData>(node.Value));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -133,9 +133,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
public interface IFont : IDisposable
|
public interface IFont : IDisposable
|
||||||
{
|
{
|
||||||
FontGlyph CreateGlyph(char c);
|
FontGlyph CreateGlyph(char c, int size, float deviceScale);
|
||||||
void SetSize(int size, float deviceScale);
|
|
||||||
int Height { get; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct FontGlyph
|
public struct FontGlyph
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
float deviceScale;
|
float deviceScale;
|
||||||
|
|
||||||
public SpriteFont(string name, byte[] data, int size, float scale, SheetBuilder builder)
|
public SpriteFont(string name, byte[] data, int size, int ascender, float scale, SheetBuilder builder)
|
||||||
{
|
{
|
||||||
if (builder.Type != SheetType.BGRA)
|
if (builder.Type != SheetType.BGRA)
|
||||||
throw new ArgumentException("The sheet builder must create BGRA sheets.", "builder");
|
throw new ArgumentException("The sheet builder must create BGRA sheets.", "builder");
|
||||||
@@ -38,7 +38,6 @@ namespace OpenRA.Graphics
|
|||||||
this.builder = builder;
|
this.builder = builder;
|
||||||
|
|
||||||
font = Game.Renderer.CreateFont(data);
|
font = Game.Renderer.CreateFont(data);
|
||||||
font.SetSize(size, deviceScale);
|
|
||||||
|
|
||||||
glyphs = new Cache<Pair<char, Color>, GlyphInfo>(CreateGlyph, Pair<char, Color>.EqualityComparer);
|
glyphs = new Cache<Pair<char, Color>, GlyphInfo>(CreateGlyph, Pair<char, Color>.EqualityComparer);
|
||||||
|
|
||||||
@@ -49,17 +48,13 @@ namespace OpenRA.Graphics
|
|||||||
if (size <= 24)
|
if (size <= 24)
|
||||||
PrecacheColor(Color.White, name);
|
PrecacheColor(Color.White, name);
|
||||||
|
|
||||||
TopOffset = size - font.Height;
|
TopOffset = size - ascender;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetScale(float scale)
|
public void SetScale(float scale)
|
||||||
{
|
{
|
||||||
deviceScale = scale;
|
deviceScale = scale;
|
||||||
|
|
||||||
font.SetSize(size, scale);
|
|
||||||
glyphs.Clear();
|
glyphs.Clear();
|
||||||
|
|
||||||
TopOffset = size - font.Height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrecacheColor(Color c, string name)
|
void PrecacheColor(Color c, string name)
|
||||||
@@ -139,7 +134,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
GlyphInfo CreateGlyph(Pair<char, Color> c)
|
GlyphInfo CreateGlyph(Pair<char, Color> c)
|
||||||
{
|
{
|
||||||
var glyph = font.CreateGlyph(c.First);
|
var glyph = font.CreateGlyph(c.First, size, deviceScale);
|
||||||
|
|
||||||
if (glyph.Data == null)
|
if (glyph.Data == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ namespace OpenRA
|
|||||||
public readonly IReadOnlyDictionary<string, string> Packages;
|
public readonly IReadOnlyDictionary<string, string> Packages;
|
||||||
public readonly IReadOnlyDictionary<string, string> MapFolders;
|
public readonly IReadOnlyDictionary<string, string> MapFolders;
|
||||||
public readonly MiniYaml LoadScreen;
|
public readonly MiniYaml LoadScreen;
|
||||||
public readonly Dictionary<string, Pair<string, int>> Fonts;
|
|
||||||
|
|
||||||
public readonly string[] SoundFormats = { };
|
public readonly string[] SoundFormats = { };
|
||||||
public readonly string[] SpriteFormats = { };
|
public readonly string[] SpriteFormats = { };
|
||||||
@@ -77,7 +76,7 @@ namespace OpenRA
|
|||||||
"Metadata", "Folders", "MapFolders", "Packages", "Rules",
|
"Metadata", "Folders", "MapFolders", "Packages", "Rules",
|
||||||
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
|
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
|
||||||
"Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions", "Hotkeys",
|
"Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions", "Hotkeys",
|
||||||
"ServerTraits", "LoadScreen", "Fonts", "SupportsMapsFrom", "SoundFormats", "SpriteFormats",
|
"ServerTraits", "LoadScreen", "SupportsMapsFrom", "SoundFormats", "SpriteFormats",
|
||||||
"RequiresMods", "PackageFormats"
|
"RequiresMods", "PackageFormats"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -123,12 +122,6 @@ namespace OpenRA
|
|||||||
if (!yaml.TryGetValue("LoadScreen", out LoadScreen))
|
if (!yaml.TryGetValue("LoadScreen", out LoadScreen))
|
||||||
throw new InvalidDataException("`LoadScreen` section is not defined.");
|
throw new InvalidDataException("`LoadScreen` section is not defined.");
|
||||||
|
|
||||||
Fonts = yaml["Fonts"].ToDictionary(my =>
|
|
||||||
{
|
|
||||||
var nd = my.ToDictionary();
|
|
||||||
return Pair.New(nd["Font"].Value, Exts.ParseIntegerInvariant(nd["Size"].Value));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Allow inherited mods to import parent maps.
|
// Allow inherited mods to import parent maps.
|
||||||
var compat = new List<string> { Id };
|
var compat = new List<string> { Id };
|
||||||
|
|
||||||
|
|||||||
@@ -90,9 +90,9 @@ namespace OpenRA
|
|||||||
if (fontSheetBuilder != null)
|
if (fontSheetBuilder != null)
|
||||||
fontSheetBuilder.Dispose();
|
fontSheetBuilder.Dispose();
|
||||||
fontSheetBuilder = new SheetBuilder(SheetType.BGRA, 512);
|
fontSheetBuilder = new SheetBuilder(SheetType.BGRA, 512);
|
||||||
Fonts = modData.Manifest.Fonts.ToDictionary(x => x.Key,
|
Fonts = modData.Manifest.Get<Fonts>().FontList.ToDictionary(x => x.Key,
|
||||||
x => new SpriteFont(x.Value.First, modData.DefaultFileSystem.Open(x.Value.First).ReadAllBytes(),
|
x => new SpriteFont(x.Value.Font, modData.DefaultFileSystem.Open(x.Value.Font).ReadAllBytes(),
|
||||||
x.Value.Second, Window.WindowScale, fontSheetBuilder)).AsReadOnly();
|
x.Value.Size, x.Value.Ascender, Window.WindowScale, fontSheetBuilder)).AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
Window.OnWindowScaleChanged += (before, after) =>
|
Window.OnWindowScaleChanged += (before, after) =>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace OpenRA.Mods.Common.Traits.Render
|
|||||||
|
|
||||||
void IRulesetLoaded<ActorInfo>.RulesetLoaded(Ruleset rules, ActorInfo info)
|
void IRulesetLoaded<ActorInfo>.RulesetLoaded(Ruleset rules, ActorInfo info)
|
||||||
{
|
{
|
||||||
if (!Game.ModData.Manifest.Fonts.ContainsKey(Font))
|
if (!Game.ModData.Manifest.Get<Fonts>().FontList.ContainsKey(Font))
|
||||||
throw new YamlException("Font '{0}' is not listed in the mod.yaml's Fonts section".F(Font));
|
throw new YamlException("Font '{0}' is not listed in the mod.yaml's Fonts section".F(Font));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ namespace OpenRA.Mods.Common.Traits.Render
|
|||||||
|
|
||||||
public override void RulesetLoaded(Ruleset rules, ActorInfo ai)
|
public override void RulesetLoaded(Ruleset rules, ActorInfo ai)
|
||||||
{
|
{
|
||||||
if (!Game.ModData.Manifest.Fonts.ContainsKey(Font))
|
if (!Game.ModData.Manifest.Get<Fonts>().FontList.ContainsKey(Font))
|
||||||
throw new YamlException("Font '{0}' is not listed in the mod.yaml's Fonts section".F(Font));
|
throw new YamlException("Font '{0}' is not listed in the mod.yaml's Fonts section".F(Font));
|
||||||
|
|
||||||
base.RulesetLoaded(rules, ai);
|
base.RulesetLoaded(rules, ai);
|
||||||
|
|||||||
@@ -27,11 +27,6 @@ namespace OpenRA.Platforms.Default
|
|||||||
internal const int FT_LOAD_RENDER = 0x04;
|
internal const int FT_LOAD_RENDER = 0x04;
|
||||||
|
|
||||||
internal static readonly int FaceRecGlyphOffset = IntPtr.Size == 8 ? 152 : 84; // offsetof(FT_FaceRec, glyph)
|
internal static readonly int FaceRecGlyphOffset = IntPtr.Size == 8 ? 152 : 84; // offsetof(FT_FaceRec, glyph)
|
||||||
internal static readonly int FaceRecSizeOffset = IntPtr.Size == 8 ? 160 : 88; // offsetof(FT_FaceRec, size)
|
|
||||||
internal static readonly int SizeRecMetricsOffset = IntPtr.Size == 8 ? 24 : 12; // offsetof(FT_SizeRec, metrics)
|
|
||||||
internal static readonly int SizeMetricsAscenderOffset = IntPtr.Size == 8 ? 24 : 12; // offsetof(FT_Size_Metrics, ascender)
|
|
||||||
internal static readonly int SizeMetricsDescenderOffset = IntPtr.Size == 8 ? 32 : 16; // offsetof(FT_Size_Metrics, descender)
|
|
||||||
|
|
||||||
internal static readonly int GlyphSlotMetricsOffset = IntPtr.Size == 8 ? 48 : 24; // offsetof(FT_GlyphSlotRec, metrics)
|
internal static readonly int GlyphSlotMetricsOffset = IntPtr.Size == 8 ? 48 : 24; // offsetof(FT_GlyphSlotRec, metrics)
|
||||||
internal static readonly int GlyphSlotBitmapOffset = IntPtr.Size == 8 ? 152 : 76; // offsetof(FT_GlyphSlotRec, bitmap)
|
internal static readonly int GlyphSlotBitmapOffset = IntPtr.Size == 8 ? 152 : 76; // offsetof(FT_GlyphSlotRec, bitmap)
|
||||||
internal static readonly int GlyphSlotBitmapLeftOffset = IntPtr.Size == 8 ? 192 : 100; // offsetof(FT_GlyphSlotRec, bitmap_left)
|
internal static readonly int GlyphSlotBitmapLeftOffset = IntPtr.Size == 8 ? 192 : 100; // offsetof(FT_GlyphSlotRec, bitmap_left)
|
||||||
@@ -73,8 +68,6 @@ namespace OpenRA.Platforms.Default
|
|||||||
readonly IntPtr face;
|
readonly IntPtr face;
|
||||||
bool disposed;
|
bool disposed;
|
||||||
|
|
||||||
public int Height { get; private set; }
|
|
||||||
|
|
||||||
public FreeTypeFont(byte[] data)
|
public FreeTypeFont(byte[] data)
|
||||||
{
|
{
|
||||||
if (library == IntPtr.Zero && FreeType.FT_Init_FreeType(out library) != FreeType.OK)
|
if (library == IntPtr.Zero && FreeType.FT_Init_FreeType(out library) != FreeType.OK)
|
||||||
@@ -85,25 +78,12 @@ namespace OpenRA.Platforms.Default
|
|||||||
throw new InvalidDataException("Failed to initialize font");
|
throw new InvalidDataException("Failed to initialize font");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetSize(int size, float deviceScale)
|
public FontGlyph CreateGlyph(char c, int size, float deviceScale)
|
||||||
{
|
{
|
||||||
var scaledSize = (uint)(size * deviceScale);
|
var scaledSize = (uint)(size * deviceScale);
|
||||||
if (FreeType.FT_Set_Pixel_Sizes(face, scaledSize, scaledSize) != FreeType.OK)
|
if (FreeType.FT_Set_Pixel_Sizes(face, scaledSize, scaledSize) != FreeType.OK)
|
||||||
throw new InvalidDataException("Failed to set size");
|
return EmptyGlyph;
|
||||||
|
|
||||||
var faceSize = Marshal.ReadIntPtr(IntPtr.Add(face, FreeType.FaceRecSizeOffset)); // face->size
|
|
||||||
var metrics = IntPtr.Add(faceSize, FreeType.SizeRecMetricsOffset); // face->size->metrics
|
|
||||||
var ascender = Marshal.ReadIntPtr(IntPtr.Add(metrics, FreeType.SizeMetricsAscenderOffset)); // face->size->metrics.ascender
|
|
||||||
var descender = Marshal.ReadIntPtr(IntPtr.Add(metrics, FreeType.SizeMetricsDescenderOffset)); // face->size->metrics.descender
|
|
||||||
|
|
||||||
var ascenderValue = (int)ascender >> 6;
|
|
||||||
var descenderValue = (int)descender >> 6;
|
|
||||||
|
|
||||||
Height = (int)((ascenderValue - Math.Abs(descenderValue)) / deviceScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FontGlyph CreateGlyph(char c)
|
|
||||||
{
|
|
||||||
if (FreeType.FT_Load_Char(face, c, FreeType.FT_LOAD_RENDER) != FreeType.OK)
|
if (FreeType.FT_Load_Char(face, c, FreeType.FT_LOAD_RENDER) != FreeType.OK)
|
||||||
return EmptyGlyph;
|
return EmptyGlyph;
|
||||||
|
|
||||||
|
|||||||
@@ -168,27 +168,35 @@ Fonts:
|
|||||||
Tiny:
|
Tiny:
|
||||||
Font: common|FreeSans.ttf
|
Font: common|FreeSans.ttf
|
||||||
Size: 10
|
Size: 10
|
||||||
|
Ascender: 8
|
||||||
TinyBold:
|
TinyBold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 10
|
Size: 10
|
||||||
|
Ascender: 8
|
||||||
Small:
|
Small:
|
||||||
Font: common|FreeSans.ttf
|
Font: common|FreeSans.ttf
|
||||||
Size: 12
|
Size: 12
|
||||||
|
Ascender: 9
|
||||||
Regular:
|
Regular:
|
||||||
Font: common|FreeSans.ttf
|
Font: common|FreeSans.ttf
|
||||||
Size: 14
|
Size: 14
|
||||||
|
Ascender: 11
|
||||||
Bold:
|
Bold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 14
|
Size: 14
|
||||||
|
Ascender: 11
|
||||||
MediumBold:
|
MediumBold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 18
|
Size: 18
|
||||||
|
Ascender: 14
|
||||||
BigBold:
|
BigBold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 24
|
Size: 24
|
||||||
|
Ascender: 18
|
||||||
Title:
|
Title:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 32
|
Size: 32
|
||||||
|
Ascender: 24
|
||||||
|
|
||||||
Missions:
|
Missions:
|
||||||
./mods/cnc/missions.yaml
|
./mods/cnc/missions.yaml
|
||||||
|
|||||||
@@ -155,27 +155,35 @@ Fonts:
|
|||||||
Tiny:
|
Tiny:
|
||||||
Font: common|FreeSans.ttf
|
Font: common|FreeSans.ttf
|
||||||
Size: 10
|
Size: 10
|
||||||
|
Ascender: 8
|
||||||
TinyBold:
|
TinyBold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 10
|
Size: 10
|
||||||
|
Ascender: 8
|
||||||
Small:
|
Small:
|
||||||
Font: common|FreeSans.ttf
|
Font: common|FreeSans.ttf
|
||||||
Size: 12
|
Size: 12
|
||||||
|
Ascender: 9
|
||||||
Regular:
|
Regular:
|
||||||
Font: common|FreeSans.ttf
|
Font: common|FreeSans.ttf
|
||||||
Size: 14
|
Size: 14
|
||||||
|
Ascender: 11
|
||||||
Bold:
|
Bold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 14
|
Size: 14
|
||||||
|
Ascender: 11
|
||||||
MediumBold:
|
MediumBold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 18
|
Size: 18
|
||||||
|
Ascender: 14
|
||||||
BigBold:
|
BigBold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 24
|
Size: 24
|
||||||
|
Ascender: 18
|
||||||
Title:
|
Title:
|
||||||
Font: d2k|Dune2k.ttf
|
Font: d2k|Dune2k.ttf
|
||||||
Size: 32
|
Size: 32
|
||||||
|
Ascender: 23
|
||||||
|
|
||||||
Missions:
|
Missions:
|
||||||
d2k|missions.yaml
|
d2k|missions.yaml
|
||||||
|
|||||||
@@ -171,27 +171,35 @@ Fonts:
|
|||||||
Tiny:
|
Tiny:
|
||||||
Font: common|FreeSans.ttf
|
Font: common|FreeSans.ttf
|
||||||
Size: 10
|
Size: 10
|
||||||
|
Ascender: 8
|
||||||
TinyBold:
|
TinyBold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 10
|
Size: 10
|
||||||
|
Ascender: 8
|
||||||
Small:
|
Small:
|
||||||
Font: common|FreeSans.ttf
|
Font: common|FreeSans.ttf
|
||||||
Size: 12
|
Size: 12
|
||||||
|
Ascender: 9
|
||||||
Regular:
|
Regular:
|
||||||
Font: common|FreeSans.ttf
|
Font: common|FreeSans.ttf
|
||||||
Size: 14
|
Size: 14
|
||||||
|
Ascender: 11
|
||||||
Bold:
|
Bold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 14
|
Size: 14
|
||||||
|
Ascender: 11
|
||||||
MediumBold:
|
MediumBold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 18
|
Size: 18
|
||||||
|
Ascender: 14
|
||||||
BigBold:
|
BigBold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 24
|
Size: 24
|
||||||
|
Ascender: 18
|
||||||
Title:
|
Title:
|
||||||
Font: ra|ZoodRangmah.ttf
|
Font: ra|ZoodRangmah.ttf
|
||||||
Size: 48
|
Size: 48
|
||||||
|
Ascender: 26
|
||||||
|
|
||||||
Missions:
|
Missions:
|
||||||
ra|missions.yaml
|
ra|missions.yaml
|
||||||
|
|||||||
@@ -213,27 +213,35 @@ Fonts:
|
|||||||
Tiny:
|
Tiny:
|
||||||
Font: common|FreeSans.ttf
|
Font: common|FreeSans.ttf
|
||||||
Size: 10
|
Size: 10
|
||||||
|
Ascender: 8
|
||||||
TinyBold:
|
TinyBold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 10
|
Size: 10
|
||||||
|
Ascender: 8
|
||||||
Small:
|
Small:
|
||||||
Font: common|FreeSans.ttf
|
Font: common|FreeSans.ttf
|
||||||
Size: 12
|
Size: 12
|
||||||
|
Ascender: 9
|
||||||
Regular:
|
Regular:
|
||||||
Font: common|FreeSans.ttf
|
Font: common|FreeSans.ttf
|
||||||
Size: 14
|
Size: 14
|
||||||
|
Ascender: 11
|
||||||
Bold:
|
Bold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 14
|
Size: 14
|
||||||
|
Ascender: 11
|
||||||
MediumBold:
|
MediumBold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 18
|
Size: 18
|
||||||
|
Ascender: 14
|
||||||
BigBold:
|
BigBold:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 24
|
Size: 24
|
||||||
|
Ascender: 18
|
||||||
Title:
|
Title:
|
||||||
Font: common|FreeSansBold.ttf
|
Font: common|FreeSansBold.ttf
|
||||||
Size: 32
|
Size: 32
|
||||||
|
Ascender: 24
|
||||||
|
|
||||||
SupportsMapsFrom: ts
|
SupportsMapsFrom: ts
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user