mirror of
https://github.com/AlexMacocian/MonoGame.Extended.git
synced 2026-07-21 01:39:32 +00:00
Refactor BitmapFont (#887)
* Update file header * Created initial project to move BitmapFont too * XMl, Text, and Binar reading now supported for BMFont * Refactor BittmapFont * Update content pipeline from BitmapFont refactor * Update changelog
This commit is contained in:
committed by
GitHub
parent
1f0235c605
commit
66277568c8
@@ -1,41 +0,0 @@
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MonoGame.Extended.Content.Pipeline.BitmapFonts
|
||||
{
|
||||
// ---- AngelCode BmFont XML serializer ----------------------
|
||||
// ---- By DeadlyDan @ deadlydan@gmail.com -------------------
|
||||
// ---- There's no license restrictions, use as you will. ----
|
||||
// ---- Credits to http://www.angelcode.com/ -----------------
|
||||
public class BitmapFontChar
|
||||
{
|
||||
[XmlAttribute("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[XmlAttribute("x")]
|
||||
public int X { get; set; }
|
||||
|
||||
[XmlAttribute("y")]
|
||||
public int Y { get; set; }
|
||||
|
||||
[XmlAttribute("width")]
|
||||
public int Width { get; set; }
|
||||
|
||||
[XmlAttribute("height")]
|
||||
public int Height { get; set; }
|
||||
|
||||
[XmlAttribute("xoffset")]
|
||||
public int XOffset { get; set; }
|
||||
|
||||
[XmlAttribute("yoffset")]
|
||||
public int YOffset { get; set; }
|
||||
|
||||
[XmlAttribute("xadvance")]
|
||||
public int XAdvance { get; set; }
|
||||
|
||||
[XmlAttribute("page")]
|
||||
public int Page { get; set; }
|
||||
|
||||
[XmlAttribute("chnl")]
|
||||
public int Channel { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MonoGame.Extended.Content.Pipeline.BitmapFonts
|
||||
{
|
||||
// ---- AngelCode BmFont XML serializer ----------------------
|
||||
// ---- By DeadlyDan @ deadlydan@gmail.com -------------------
|
||||
// ---- There's no license restrictions, use as you will. ----
|
||||
// ---- Credits to http://www.angelcode.com/ -----------------
|
||||
public class BitmapFontCommon
|
||||
{
|
||||
[XmlAttribute("lineHeight")]
|
||||
public int LineHeight { get; set; }
|
||||
|
||||
[XmlAttribute("base")]
|
||||
public int Base { get; set; }
|
||||
|
||||
[XmlAttribute("scaleW")]
|
||||
public int ScaleW { get; set; }
|
||||
|
||||
[XmlAttribute("scaleH")]
|
||||
public int ScaleH { get; set; }
|
||||
|
||||
[XmlAttribute("pages")]
|
||||
public int Pages { get; set; }
|
||||
|
||||
[XmlAttribute("packed")]
|
||||
public int Packed { get; set; }
|
||||
|
||||
[XmlAttribute("alphaChnl")]
|
||||
public int AlphaChannel { get; set; }
|
||||
|
||||
[XmlAttribute("redChnl")]
|
||||
public int RedChannel { get; set; }
|
||||
|
||||
[XmlAttribute("greenChnl")]
|
||||
public int GreenChannel { get; set; }
|
||||
|
||||
[XmlAttribute("blueChnl")]
|
||||
public int BlueChannel { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MonoGame.Extended.Content.Pipeline.BitmapFonts
|
||||
{
|
||||
// ---- AngelCode BmFont XML serializer ----------------------
|
||||
// ---- By DeadlyDan @ deadlydan@gmail.com -------------------
|
||||
// ---- There's no license restrictions, use as you will. ----
|
||||
// ---- Credits to http://www.angelcode.com/ -----------------
|
||||
[XmlRoot("font")]
|
||||
public class BitmapFontFile
|
||||
{
|
||||
[XmlElement("info")]
|
||||
public BitmapFontInfo Info { get; set; }
|
||||
|
||||
[XmlElement("common")]
|
||||
public BitmapFontCommon Common { get; set; }
|
||||
|
||||
[XmlArray("pages")]
|
||||
[XmlArrayItem("page")]
|
||||
public List<BitmapFontPage> Pages { get; set; }
|
||||
|
||||
[XmlArray("chars")]
|
||||
[XmlArrayItem("char")]
|
||||
public List<BitmapFontChar> Chars { get; set; }
|
||||
|
||||
[XmlArray("kernings")]
|
||||
[XmlArrayItem("kerning")]
|
||||
public List<BitmapFontKerning> Kernings { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
using System.IO;
|
||||
using System.Xml.Serialization;
|
||||
using Microsoft.Xna.Framework.Content.Pipeline;
|
||||
using MonoGame.Extended.BitmapFonts;
|
||||
using MonoGame.Extended.BitmapFonts.BmfTypes;
|
||||
|
||||
namespace MonoGame.Extended.Content.Pipeline.BitmapFonts
|
||||
{
|
||||
[ContentImporter(".fnt", DefaultProcessor = "BitmapFontProcessor",
|
||||
DisplayName = "BMFont Importer - MonoGame.Extended")]
|
||||
public class BitmapFontImporter : ContentImporter<BitmapFontFile>
|
||||
public class BitmapFontImporter : ContentImporter<ContentImporterResult<BmfFile>>
|
||||
{
|
||||
public override BitmapFontFile Import(string filename, ContentImporterContext context)
|
||||
public override ContentImporterResult<BmfFile> Import(string filename, ContentImporterContext context)
|
||||
{
|
||||
context.Logger.LogMessage("Importing XML file: {0}", filename);
|
||||
context.Logger.LogMessage("Importing FNT file: {0}", filename);
|
||||
|
||||
using (var streamReader = new StreamReader(filename))
|
||||
{
|
||||
var deserializer = new XmlSerializer(typeof(BitmapFontFile));
|
||||
return (BitmapFontFile)deserializer.Deserialize(streamReader);
|
||||
}
|
||||
using FileStream stream = File.OpenRead(filename);
|
||||
BmfFile file = BmfFile.FromStream(stream);
|
||||
return new ContentImporterResult<BmfFile>(filename, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MonoGame.Extended.Content.Pipeline.BitmapFonts
|
||||
{
|
||||
// ---- AngelCode BmFont XML serializer ----------------------
|
||||
// ---- By DeadlyDan @ deadlydan@gmail.com -------------------
|
||||
// ---- There's no license restrictions, use as you will. ----
|
||||
// ---- Credits to http://www.angelcode.com/ -----------------
|
||||
public class BitmapFontInfo
|
||||
{
|
||||
[XmlAttribute("face")]
|
||||
public string Face { get; set; }
|
||||
|
||||
[XmlAttribute("size")]
|
||||
public int Size { get; set; }
|
||||
|
||||
[XmlAttribute("bold")]
|
||||
public int Bold { get; set; }
|
||||
|
||||
[XmlAttribute("italic")]
|
||||
public int Italic { get; set; }
|
||||
|
||||
[XmlAttribute("charset")]
|
||||
public string CharSet { get; set; }
|
||||
|
||||
[XmlAttribute("unicode")]
|
||||
public int Unicode { get; set; }
|
||||
|
||||
[XmlAttribute("stretchH")]
|
||||
public int StretchHeight { get; set; }
|
||||
|
||||
[XmlAttribute("smooth")]
|
||||
public int Smooth { get; set; }
|
||||
|
||||
[XmlAttribute("aa")]
|
||||
public int SuperSampling { get; set; }
|
||||
|
||||
[XmlAttribute("padding")]
|
||||
public string Padding { get; set; }
|
||||
|
||||
[XmlAttribute("spacing")]
|
||||
public string Spacing { get; set; }
|
||||
|
||||
[XmlAttribute("outline")]
|
||||
public int OutLine { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MonoGame.Extended.Content.Pipeline.BitmapFonts
|
||||
{
|
||||
// ---- AngelCode BmFont XML serializer ----------------------
|
||||
// ---- By DeadlyDan @ deadlydan@gmail.com -------------------
|
||||
// ---- There's no license restrictions, use as you will. ----
|
||||
// ---- Credits to http://www.angelcode.com/ -----------------
|
||||
public class BitmapFontKerning
|
||||
{
|
||||
[XmlAttribute("first")]
|
||||
public int First { get; set; }
|
||||
|
||||
[XmlAttribute("second")]
|
||||
public int Second { get; set; }
|
||||
|
||||
[XmlAttribute("amount")]
|
||||
public int Amount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MonoGame.Extended.Content.Pipeline.BitmapFonts
|
||||
{
|
||||
// ---- AngelCode BmFont XML serializer ----------------------
|
||||
// ---- By DeadlyDan @ deadlydan@gmail.com -------------------
|
||||
// ---- There's no license restrictions, use as you will. ----
|
||||
// ---- Credits to http://www.angelcode.com/ -----------------
|
||||
public class BitmapFontPage
|
||||
{
|
||||
[XmlAttribute("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[XmlAttribute("file")]
|
||||
public string File { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,24 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Xna.Framework.Content.Pipeline;
|
||||
using MonoGame.Extended.BitmapFonts.BmfTypes;
|
||||
|
||||
namespace MonoGame.Extended.Content.Pipeline.BitmapFonts
|
||||
{
|
||||
[ContentProcessor(DisplayName = "BMFont Processor - MonoGame.Extended")]
|
||||
public class BitmapFontProcessor : ContentProcessor<BitmapFontFile, BitmapFontProcessorResult>
|
||||
public class BitmapFontProcessor : ContentProcessor<ContentImporterResult<BmfFile>, BitmapFontProcessorResult>
|
||||
{
|
||||
public override BitmapFontProcessorResult Process(BitmapFontFile bitmapFontFile, ContentProcessorContext context)
|
||||
public override BitmapFontProcessorResult Process(ContentImporterResult<BmfFile> importerResult, ContentProcessorContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
BmfFile bmfFile = importerResult.Data;
|
||||
context.Logger.LogMessage("Processing BMFont");
|
||||
var result = new BitmapFontProcessorResult(bitmapFontFile);
|
||||
var result = new BitmapFontProcessorResult(bmfFile);
|
||||
|
||||
foreach (var fontPage in bitmapFontFile.Pages)
|
||||
foreach (var fontPage in bmfFile.Pages)
|
||||
{
|
||||
var assetName = Path.GetFileNameWithoutExtension(fontPage.File);
|
||||
var assetName = Path.GetFileNameWithoutExtension(fontPage);
|
||||
context.Logger.LogMessage("Expected texture asset: {0}", assetName);
|
||||
result.TextureAssets.Add(assetName);
|
||||
}
|
||||
@@ -30,4 +32,4 @@ namespace MonoGame.Extended.Content.Pipeline.BitmapFonts
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using MonoGame.Extended.BitmapFonts.BmfTypes;
|
||||
|
||||
namespace MonoGame.Extended.Content.Pipeline.BitmapFonts
|
||||
{
|
||||
public class BitmapFontProcessorResult
|
||||
{
|
||||
public List<string> TextureAssets { get; private set; }
|
||||
public BitmapFontFile FontFile { get; private set; }
|
||||
public BmfFile FontFile { get; private set; }
|
||||
|
||||
public BitmapFontProcessorResult(BitmapFontFile fontFile)
|
||||
public BitmapFontProcessorResult(BmfFile fontFile)
|
||||
{
|
||||
FontFile = fontFile;
|
||||
TextureAssets = new List<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,14 @@ namespace MonoGame.Extended.Content.Pipeline.BitmapFonts
|
||||
writer.Write(textureAsset);
|
||||
|
||||
var fontFile = result.FontFile;
|
||||
writer.Write(fontFile.FontName);
|
||||
writer.Write(fontFile.Info.FontSize);
|
||||
writer.Write(fontFile.Common.LineHeight);
|
||||
writer.Write(fontFile.Chars.Count);
|
||||
writer.Write(fontFile.Characters.Count);
|
||||
|
||||
foreach (var c in fontFile.Chars)
|
||||
foreach (var c in fontFile.Characters)
|
||||
{
|
||||
writer.Write(c.Id);
|
||||
writer.Write(c.ID);
|
||||
writer.Write(c.Page);
|
||||
writer.Write(c.X);
|
||||
writer.Write(c.Y);
|
||||
@@ -49,4 +51,4 @@ namespace MonoGame.Extended.Content.Pipeline.BitmapFonts
|
||||
return "MonoGame.Extended.BitmapFonts.BitmapFontReader, MonoGame.Extended";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ namespace MonoGame.Extended.Graphics
|
||||
var lineSpacing = bitmapFont.LineHeight;
|
||||
var offset = new Vector2(0, 0);
|
||||
|
||||
BitmapFontRegion lastGlyph = null;
|
||||
BitmapFontCharacter lastGlyph = null;
|
||||
for (var i = 0; i < text.Length;)
|
||||
{
|
||||
int character;
|
||||
@@ -253,7 +253,7 @@ namespace MonoGame.Extended.Graphics
|
||||
continue;
|
||||
}
|
||||
|
||||
var fontRegion = bitmapFont.GetCharacterRegion(character);
|
||||
var fontRegion = bitmapFont.GetCharacter(character);
|
||||
if (fontRegion == null)
|
||||
continue;
|
||||
|
||||
@@ -266,7 +266,7 @@ namespace MonoGame.Extended.Graphics
|
||||
DrawSprite(textureRegion.Texture, ref transform1Matrix, ref bounds, color, flags, depth);
|
||||
|
||||
var advance = fontRegion.XAdvance + bitmapFont.LetterSpacing;
|
||||
if (BitmapFont.UseKernings && lastGlyph != null)
|
||||
if (bitmapFont.UseKernings && lastGlyph != null)
|
||||
{
|
||||
int amount;
|
||||
if (lastGlyph.Kernings.TryGetValue(character, out amount))
|
||||
@@ -277,7 +277,7 @@ namespace MonoGame.Extended.Graphics
|
||||
|
||||
offset.X += i != text.Length - 1
|
||||
? advance
|
||||
: fontRegion.XOffset + fontRegion.Width;
|
||||
: fontRegion.XOffset + fontRegion.TextureRegion.Width;
|
||||
|
||||
lastGlyph = fontRegion;
|
||||
}
|
||||
@@ -355,7 +355,7 @@ namespace MonoGame.Extended.Graphics
|
||||
transform1Matrix.M31 += glyph.Position.X;
|
||||
transform1Matrix.M32 += glyph.Position.Y;
|
||||
|
||||
var texture = glyph.FontRegion.TextureRegion.Texture;
|
||||
var texture = glyph.Character.TextureRegion.Texture;
|
||||
var bounds = texture.Bounds;
|
||||
DrawSprite(texture, ref transform1Matrix, ref bounds, color, flags, depth);
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace MonoGame.Extended.Gui.Controls
|
||||
|
||||
foreach (var glyph in font.GetGlyphs(textInfo.Text, textInfo.Position))
|
||||
{
|
||||
var fontRegionWidth = glyph.FontRegion?.Width ?? 0;
|
||||
var fontRegionWidth = glyph.Character?.TextureRegion?.Width ?? 0;
|
||||
var glyphMiddle = (int) (glyph.Position.X + fontRegionWidth * 0.5f);
|
||||
|
||||
if (position.X >= glyphMiddle)
|
||||
|
||||
@@ -25,7 +25,7 @@ public static class GuiJsonSerializerOptionsProvider
|
||||
options.Converters.Add(new Size2JsonConverter());
|
||||
options.Converters.Add(new ColorJsonConverter());
|
||||
options.Converters.Add(new ThicknessJsonConverter());
|
||||
options.Converters.Add(new ContentManagerJsonConverter<BitmapFont>(contentManager, font => font.Name));
|
||||
options.Converters.Add(new ContentManagerJsonConverter<BitmapFont>(contentManager, font => font.Face));
|
||||
options.Converters.Add(new ControlStyleJsonConverter(customControlTypes));
|
||||
options.Converters.Add(new GuiTextureAtlasJsonConverter(contentManager, textureRegionService));
|
||||
options.Converters.Add(new GuiNinePatchRegion2DJsonConverter(textureRegionService));
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
//// Copyright (c) Craftwork Games. All rights reserved.
|
||||
//// Licensed under the MIT license.
|
||||
//// See LICENSE file in the project root for full license information.
|
||||
|
||||
//using System;
|
||||
//using System.Text;
|
||||
//using Microsoft.Xna.Framework;
|
||||
|
||||
//namespace MonoGame.Extended.BitmapFonts;
|
||||
|
||||
///// <summary>
|
||||
///// Provides extension methods for <see cref="BitmapFont"/>.
|
||||
///// </summary>
|
||||
//public static class BitmapFontExtensions
|
||||
//{
|
||||
// /// <summary>
|
||||
// /// Measures the size of the given text when rendered with the specified font.
|
||||
// /// </summary>
|
||||
// /// <param name="font">The <see cref="BitmapFont"/> used to render the text.</param>
|
||||
// /// <param name="text">The text to measure.</param>
|
||||
// /// <returns>The size of the rendered text.</returns>
|
||||
// public static SizeF MeasureString(this BitmapFont font, string text)
|
||||
// {
|
||||
// SizeF size = new SizeF(0, font.LineHeight);
|
||||
// SizeF offset = new SizeF(0, 0);
|
||||
// bool firstCharacterOfLine = true;
|
||||
// MeasureStringInternal(font, text, ref size, ref offset, ref firstCharacterOfLine);
|
||||
// return size;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Measures the size of the given text when rendered with the specified font.
|
||||
// /// </summary>
|
||||
// /// <param name="font">The <see cref="BitmapFont"/> used to render the text.</param>
|
||||
// /// <param name="text">The text to measure.</param>
|
||||
// /// <returns>The size of the rendered text.</returns>
|
||||
// public static SizeF MeasureString(this BitmapFont font, StringBuilder text)
|
||||
// {
|
||||
// SizeF size = new SizeF(0, font.LineHeight);
|
||||
// SizeF offset = new SizeF(0, 0);
|
||||
// bool firstCharacterOfLine = true;
|
||||
|
||||
// foreach (ReadOnlyMemory<char> chunk in text.GetChunks())
|
||||
// {
|
||||
// MeasureStringInternal(font, chunk.Span, ref size, ref offset, ref firstCharacterOfLine);
|
||||
// }
|
||||
|
||||
// return size;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Gets the rectangular bounds of the specified text when rendered with the specified font.
|
||||
// /// </summary>
|
||||
// /// <param name="font">The <see cref="BitmapFont"/> used to render the text.</param>
|
||||
// /// <param name="text">The text to measure.</param>
|
||||
// /// <param name="location">The top-left corner of the bounding rectangle.</param>
|
||||
// /// <returns>The rectangular bounds of the rendered text.</returns>
|
||||
// public static RectangleF GetStringBounds(this BitmapFont font, string text, Vector2 location)
|
||||
// {
|
||||
// SizeF size = font.MeasureString(text);
|
||||
// return new RectangleF(location, size);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Gets the rectangular bounds of the specified text when rendered with the specified font.
|
||||
// /// </summary>
|
||||
// /// <param name="font">The <see cref="BitmapFont"/> used to render the text.</param>
|
||||
// /// <param name="text">The text to measure.</param>
|
||||
// /// <param name="location">The top-left corner of the bounding rectangle.</param>
|
||||
// /// <returns>The rectangular bounds of the rendered text.</returns>
|
||||
// public static RectangleF GetStringBounds(this BitmapFont font, StringBuilder text, Vector2 location)
|
||||
// {
|
||||
// SizeF size = font.MeasureString(text);
|
||||
// return new RectangleF(location, size);
|
||||
// }
|
||||
|
||||
// private static void MeasureStringInternal(this BitmapFont font, ReadOnlySpan<char> text, ref SizeF size, ref SizeF offset, ref bool firstCharacterOfLine)
|
||||
// {
|
||||
// if (text.Length == 0)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// for (int i = 0; i < text.Length; i++)
|
||||
// {
|
||||
// char c = text[i];
|
||||
|
||||
// if (c == '\r')
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// if (c == '\n')
|
||||
// {
|
||||
// offset.Width = 0;
|
||||
// offset.Height += font.LineHeight;
|
||||
// firstCharacterOfLine = true;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// if (!font.Characters.TryGetValue(c, out BitmapFontCharacter? character))
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// if (firstCharacterOfLine)
|
||||
// {
|
||||
// offset.Width = Math.Max(character.XOffset, 0);
|
||||
// firstCharacterOfLine = false;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// offset.Width += character.XOffset;
|
||||
// }
|
||||
|
||||
// offset.Width += character.TextureRegion.Width;
|
||||
|
||||
// float proposedWidth = offset.Width + character.XAdvance;
|
||||
// if (proposedWidth > size.Width)
|
||||
// {
|
||||
// size.Width = proposedWidth;
|
||||
// }
|
||||
|
||||
// offset.Width += character.XAdvance;
|
||||
|
||||
// if (character.TextureRegion.Height > size.Height)
|
||||
// {
|
||||
// size.Height = character.TextureRegion.Height;
|
||||
// }
|
||||
// }
|
||||
|
||||
// size.Height += offset.Height;
|
||||
// }
|
||||
//}
|
||||
@@ -1,354 +1,410 @@
|
||||
// Copyright (c) Craftwork Games. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MonoGame.Extended.BitmapFonts.BmfTypes;
|
||||
using MonoGame.Extended.TextureAtlases;
|
||||
|
||||
namespace MonoGame.Extended.BitmapFonts
|
||||
namespace MonoGame.Extended.BitmapFonts;
|
||||
|
||||
public sealed class BitmapFont
|
||||
{
|
||||
public class BitmapFont
|
||||
private readonly Dictionary<int, BitmapFontCharacter> _characters;
|
||||
public string Face { get; }
|
||||
public int Size { get; }
|
||||
public int LineHeight { get; }
|
||||
|
||||
public int LetterSpacing { get; set; }
|
||||
|
||||
public bool UseKernings { get; set; } = true;
|
||||
|
||||
public BitmapFont(string face, int size, int lineHeight, IEnumerable<BitmapFontCharacter> characters)
|
||||
{
|
||||
private readonly Dictionary<int, BitmapFontRegion> _characterMap = new Dictionary<int, BitmapFontRegion>();
|
||||
Face = face;
|
||||
Size = size;
|
||||
LineHeight = lineHeight;
|
||||
_characters = new Dictionary<int, BitmapFontCharacter>();
|
||||
|
||||
public BitmapFont(string name, IEnumerable<BitmapFontRegion> regions, int lineHeight)
|
||||
foreach (BitmapFontCharacter character in characters)
|
||||
{
|
||||
foreach (var region in regions)
|
||||
_characterMap.Add(region.Character, region);
|
||||
_characters.Add(character.Character, character);
|
||||
}
|
||||
}
|
||||
|
||||
Name = name;
|
||||
LineHeight = lineHeight;
|
||||
public BitmapFontCharacter GetCharacter(int character) => _characters[character];
|
||||
public bool TryGetCharacter(int character, out BitmapFontCharacter value) => _characters.TryGetValue(character, out value);
|
||||
|
||||
public SizeF MeasureString(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return SizeF.Empty;
|
||||
|
||||
var stringRectangle = GetStringRectangle(text);
|
||||
return new SizeF(stringRectangle.Width, stringRectangle.Height);
|
||||
}
|
||||
|
||||
public SizeF MeasureString(StringBuilder text)
|
||||
{
|
||||
if (text == null || text.Length == 0)
|
||||
return SizeF.Empty;
|
||||
|
||||
var stringRectangle = GetStringRectangle(text);
|
||||
return new SizeF(stringRectangle.Width, stringRectangle.Height);
|
||||
}
|
||||
|
||||
public RectangleF GetStringRectangle(string text)
|
||||
{
|
||||
return GetStringRectangle(text, Vector2.Zero);
|
||||
}
|
||||
|
||||
public RectangleF GetStringRectangle(string text, Vector2 position)
|
||||
{
|
||||
var glyphs = GetGlyphs(text, position);
|
||||
var rectangle = new RectangleF(position.X, position.Y, 0, LineHeight);
|
||||
|
||||
foreach (var glyph in glyphs)
|
||||
{
|
||||
if (glyph.Character != null)
|
||||
{
|
||||
var right = glyph.Position.X + glyph.Character.TextureRegion.Width;
|
||||
|
||||
if (right > rectangle.Right)
|
||||
rectangle.Width = (int)(right - rectangle.Left);
|
||||
}
|
||||
|
||||
if (glyph.CharacterID == '\n')
|
||||
rectangle.Height += LineHeight;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public int LineHeight { get; }
|
||||
public int LetterSpacing { get; set; }
|
||||
public static bool UseKernings { get; set; } = true;
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
public BitmapFontRegion GetCharacterRegion(int character)
|
||||
public RectangleF GetStringRectangle(StringBuilder text, Vector2? position = null)
|
||||
{
|
||||
var position1 = position ?? new Vector2();
|
||||
var glyphs = GetGlyphs(text, position1);
|
||||
var rectangle = new RectangleF(position1.X, position1.Y, 0, LineHeight);
|
||||
|
||||
foreach (var glyph in glyphs)
|
||||
{
|
||||
return _characterMap.TryGetValue(character, out var region) ? region : null;
|
||||
if (glyph.Character != null)
|
||||
{
|
||||
var right = glyph.Position.X + glyph.Character.TextureRegion.Width;
|
||||
|
||||
if (right > rectangle.Right)
|
||||
rectangle.Width = (int)(right - rectangle.Left);
|
||||
}
|
||||
|
||||
if (glyph.CharacterID == '\n')
|
||||
rectangle.Height += LineHeight;
|
||||
}
|
||||
|
||||
public SizeF MeasureString(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return SizeF.Empty;
|
||||
|
||||
var stringRectangle = GetStringRectangle(text);
|
||||
return new SizeF(stringRectangle.Width, stringRectangle.Height);
|
||||
}
|
||||
|
||||
public SizeF MeasureString(StringBuilder text)
|
||||
{
|
||||
if (text == null || text.Length == 0)
|
||||
return SizeF.Empty;
|
||||
|
||||
var stringRectangle = GetStringRectangle(text);
|
||||
return new SizeF(stringRectangle.Width, stringRectangle.Height);
|
||||
}
|
||||
|
||||
public RectangleF GetStringRectangle(string text)
|
||||
{
|
||||
return GetStringRectangle(text, Vector2.Zero);
|
||||
}
|
||||
|
||||
public RectangleF GetStringRectangle(string text, Vector2 position)
|
||||
{
|
||||
var glyphs = GetGlyphs(text, position);
|
||||
var rectangle = new RectangleF(position.X, position.Y, 0, LineHeight);
|
||||
|
||||
foreach (var glyph in glyphs)
|
||||
{
|
||||
if (glyph.FontRegion != null)
|
||||
{
|
||||
var right = glyph.Position.X + glyph.FontRegion.Width;
|
||||
|
||||
if (right > rectangle.Right)
|
||||
rectangle.Width = (int)(right - rectangle.Left);
|
||||
}
|
||||
|
||||
if (glyph.Character == '\n')
|
||||
rectangle.Height += LineHeight;
|
||||
}
|
||||
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
public RectangleF GetStringRectangle(StringBuilder text, Vector2? position = null)
|
||||
{
|
||||
var position1 = position ?? new Vector2();
|
||||
var glyphs = GetGlyphs(text, position1);
|
||||
var rectangle = new RectangleF(position1.X, position1.Y, 0, LineHeight);
|
||||
|
||||
foreach (var glyph in glyphs)
|
||||
{
|
||||
if (glyph.FontRegion != null)
|
||||
{
|
||||
var right = glyph.Position.X + glyph.FontRegion.Width;
|
||||
|
||||
if (right > rectangle.Right)
|
||||
rectangle.Width = (int)(right - rectangle.Left);
|
||||
}
|
||||
|
||||
if (glyph.Character == '\n')
|
||||
rectangle.Height += LineHeight;
|
||||
}
|
||||
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
public StringGlyphEnumerable GetGlyphs(string text, Vector2? position = null)
|
||||
{
|
||||
return new StringGlyphEnumerable(this, text, position);
|
||||
}
|
||||
|
||||
public StringBuilderGlyphEnumerable GetGlyphs(StringBuilder text, Vector2? position)
|
||||
{
|
||||
return new StringBuilderGlyphEnumerable(this, text, position);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name}";
|
||||
}
|
||||
|
||||
public struct StringGlyphEnumerable : IEnumerable<BitmapFontGlyph>
|
||||
{
|
||||
private readonly StringGlyphEnumerator _enumerator;
|
||||
|
||||
public StringGlyphEnumerable(BitmapFont font, string text, Vector2? position)
|
||||
{
|
||||
_enumerator = new StringGlyphEnumerator(font, text, position);
|
||||
}
|
||||
|
||||
public StringGlyphEnumerator GetEnumerator()
|
||||
{
|
||||
return _enumerator;
|
||||
}
|
||||
|
||||
IEnumerator<BitmapFontGlyph> IEnumerable<BitmapFontGlyph>.GetEnumerator()
|
||||
{
|
||||
return _enumerator;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _enumerator;
|
||||
}
|
||||
}
|
||||
|
||||
public struct StringGlyphEnumerator : IEnumerator<BitmapFontGlyph>
|
||||
{
|
||||
private readonly BitmapFont _font;
|
||||
private readonly string _text;
|
||||
private int _index;
|
||||
private readonly Vector2 _position;
|
||||
private Vector2 _positionDelta;
|
||||
private BitmapFontGlyph _currentGlyph;
|
||||
private BitmapFontGlyph? _previousGlyph;
|
||||
|
||||
object IEnumerator.Current
|
||||
{
|
||||
get
|
||||
{
|
||||
// casting a struct to object will box it, behaviour we want to avoid...
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public BitmapFontGlyph Current => _currentGlyph;
|
||||
|
||||
public StringGlyphEnumerator(BitmapFont font, string text, Vector2? position)
|
||||
{
|
||||
_font = font;
|
||||
_text = text;
|
||||
_index = -1;
|
||||
_position = position ?? new Vector2();
|
||||
_positionDelta = new Vector2();
|
||||
_currentGlyph = new BitmapFontGlyph();
|
||||
_previousGlyph = null;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (++_index >= _text.Length)
|
||||
return false;
|
||||
|
||||
var character = GetUnicodeCodePoint(_text, ref _index);
|
||||
_currentGlyph.Character = character;
|
||||
_currentGlyph.FontRegion = _font.GetCharacterRegion(character);
|
||||
_currentGlyph.Position = _position + _positionDelta;
|
||||
|
||||
if (_currentGlyph.FontRegion != null)
|
||||
{
|
||||
_currentGlyph.Position.X += _currentGlyph.FontRegion.XOffset;
|
||||
_currentGlyph.Position.Y += _currentGlyph.FontRegion.YOffset;
|
||||
_positionDelta.X += _currentGlyph.FontRegion.XAdvance + _font.LetterSpacing;
|
||||
}
|
||||
|
||||
if (UseKernings && _previousGlyph?.FontRegion != null)
|
||||
{
|
||||
if (_previousGlyph.Value.FontRegion.Kernings.TryGetValue(character, out var amount))
|
||||
{
|
||||
_positionDelta.X += amount;
|
||||
_currentGlyph.Position.X += amount;
|
||||
}
|
||||
}
|
||||
|
||||
_previousGlyph = _currentGlyph;
|
||||
|
||||
if (character != '\n')
|
||||
return true;
|
||||
|
||||
_positionDelta.Y += _font.LineHeight;
|
||||
_positionDelta.X = 0;
|
||||
_previousGlyph = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int GetUnicodeCodePoint(string text, ref int index)
|
||||
{
|
||||
return char.IsHighSurrogate(text[index]) && ++index < text.Length
|
||||
? char.ConvertToUtf32(text[index - 1], text[index])
|
||||
: text[index];
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_positionDelta = new Vector2();
|
||||
_index = -1;
|
||||
_previousGlyph = null;
|
||||
}
|
||||
}
|
||||
|
||||
public struct StringBuilderGlyphEnumerable : IEnumerable<BitmapFontGlyph>
|
||||
{
|
||||
private readonly StringBuilderGlyphEnumerator _enumerator;
|
||||
|
||||
public StringBuilderGlyphEnumerable(BitmapFont font, StringBuilder text, Vector2? position)
|
||||
{
|
||||
_enumerator = new StringBuilderGlyphEnumerator(font, text, position);
|
||||
}
|
||||
|
||||
public StringBuilderGlyphEnumerator GetEnumerator()
|
||||
{
|
||||
return _enumerator;
|
||||
}
|
||||
|
||||
IEnumerator<BitmapFontGlyph> IEnumerable<BitmapFontGlyph>.GetEnumerator()
|
||||
{
|
||||
return _enumerator;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _enumerator;
|
||||
}
|
||||
}
|
||||
|
||||
public struct StringBuilderGlyphEnumerator : IEnumerator<BitmapFontGlyph>
|
||||
{
|
||||
private readonly BitmapFont _font;
|
||||
private readonly StringBuilder _text;
|
||||
private int _index;
|
||||
private readonly Vector2 _position;
|
||||
private Vector2 _positionDelta;
|
||||
private BitmapFontGlyph _currentGlyph;
|
||||
private BitmapFontGlyph? _previousGlyph;
|
||||
|
||||
object IEnumerator.Current
|
||||
{
|
||||
get
|
||||
{
|
||||
// casting a struct to object will box it, behaviour we want to avoid...
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public BitmapFontGlyph Current => _currentGlyph;
|
||||
|
||||
public StringBuilderGlyphEnumerator(BitmapFont font, StringBuilder text, Vector2? position)
|
||||
{
|
||||
_font = font;
|
||||
_text = text;
|
||||
_index = -1;
|
||||
_position = position ?? new Vector2();
|
||||
_positionDelta = new Vector2();
|
||||
_currentGlyph = new BitmapFontGlyph();
|
||||
_previousGlyph = null;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (++_index >= _text.Length)
|
||||
return false;
|
||||
|
||||
var character = GetUnicodeCodePoint(_text, ref _index);
|
||||
_currentGlyph = new BitmapFontGlyph
|
||||
{
|
||||
Character = character,
|
||||
FontRegion = _font.GetCharacterRegion(character),
|
||||
Position = _position + _positionDelta
|
||||
};
|
||||
|
||||
if (_currentGlyph.FontRegion != null)
|
||||
{
|
||||
_currentGlyph.Position.X += _currentGlyph.FontRegion.XOffset;
|
||||
_currentGlyph.Position.Y += _currentGlyph.FontRegion.YOffset;
|
||||
_positionDelta.X += _currentGlyph.FontRegion.XAdvance + _font.LetterSpacing;
|
||||
}
|
||||
|
||||
if (UseKernings && _previousGlyph.HasValue && _previousGlyph.Value.FontRegion != null)
|
||||
{
|
||||
int amount;
|
||||
if (_previousGlyph.Value.FontRegion.Kernings.TryGetValue(character, out amount))
|
||||
{
|
||||
_positionDelta.X += amount;
|
||||
_currentGlyph.Position.X += amount;
|
||||
}
|
||||
}
|
||||
|
||||
_previousGlyph = _currentGlyph;
|
||||
|
||||
if (character != '\n')
|
||||
return true;
|
||||
|
||||
_positionDelta.Y += _font.LineHeight;
|
||||
_positionDelta.X = _position.X;
|
||||
_previousGlyph = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int GetUnicodeCodePoint(StringBuilder text, ref int index)
|
||||
{
|
||||
return char.IsHighSurrogate(text[index]) && ++index < text.Length
|
||||
? char.ConvertToUtf32(text[index - 1], text[index])
|
||||
: text[index];
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_positionDelta = new Vector2();
|
||||
_index = -1;
|
||||
_previousGlyph = null;
|
||||
}
|
||||
}
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
public struct BitmapFontGlyph
|
||||
{
|
||||
public int Character;
|
||||
public int CharacterID;
|
||||
public Vector2 Position;
|
||||
public BitmapFontRegion FontRegion;
|
||||
public BitmapFontCharacter Character;
|
||||
}
|
||||
public StringGlyphEnumerable GetGlyphs(string text, Vector2? position = null)
|
||||
{
|
||||
return new StringGlyphEnumerable(this, text, position);
|
||||
}
|
||||
|
||||
public StringBuilderGlyphEnumerable GetGlyphs(StringBuilder text, Vector2? position)
|
||||
{
|
||||
return new StringBuilderGlyphEnumerable(this, text, position);
|
||||
}
|
||||
|
||||
public struct StringGlyphEnumerable : IEnumerable<BitmapFontGlyph>
|
||||
{
|
||||
private readonly StringGlyphEnumerator _enumerator;
|
||||
|
||||
public StringGlyphEnumerable(BitmapFont font, string text, Vector2? position)
|
||||
{
|
||||
_enumerator = new StringGlyphEnumerator(font, text, position);
|
||||
}
|
||||
|
||||
public StringGlyphEnumerator GetEnumerator()
|
||||
{
|
||||
return _enumerator;
|
||||
}
|
||||
|
||||
IEnumerator<BitmapFontGlyph> IEnumerable<BitmapFontGlyph>.GetEnumerator()
|
||||
{
|
||||
return _enumerator;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _enumerator;
|
||||
}
|
||||
}
|
||||
|
||||
public struct StringGlyphEnumerator : IEnumerator<BitmapFontGlyph>
|
||||
{
|
||||
private readonly BitmapFont _font;
|
||||
private readonly string _text;
|
||||
private int _index;
|
||||
private readonly Vector2 _position;
|
||||
private Vector2 _positionDelta;
|
||||
private BitmapFontGlyph _currentGlyph;
|
||||
private BitmapFontGlyph? _previousGlyph;
|
||||
|
||||
object IEnumerator.Current
|
||||
{
|
||||
get
|
||||
{
|
||||
// casting a struct to object will box it, behaviour we want to avoid...
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public BitmapFontGlyph Current => _currentGlyph;
|
||||
|
||||
public StringGlyphEnumerator(BitmapFont font, string text, Vector2? position)
|
||||
{
|
||||
_font = font;
|
||||
_text = text;
|
||||
_index = -1;
|
||||
_position = position ?? new Vector2();
|
||||
_positionDelta = new Vector2();
|
||||
_currentGlyph = new BitmapFontGlyph();
|
||||
_previousGlyph = null;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (++_index >= _text.Length)
|
||||
return false;
|
||||
|
||||
var character = GetUnicodeCodePoint(_text, ref _index);
|
||||
_currentGlyph.CharacterID = character;
|
||||
_font.TryGetCharacter(character, out _currentGlyph.Character);
|
||||
_currentGlyph.Position = _position + _positionDelta;
|
||||
|
||||
if (_currentGlyph.Character != null)
|
||||
{
|
||||
_currentGlyph.Position.X += _currentGlyph.Character.XOffset;
|
||||
_currentGlyph.Position.Y += _currentGlyph.Character.YOffset;
|
||||
_positionDelta.X += _currentGlyph.Character.XAdvance + _font.LetterSpacing;
|
||||
}
|
||||
|
||||
if (_font.UseKernings && _previousGlyph?.Character != null)
|
||||
{
|
||||
if (_previousGlyph.Value.Character.Kernings.TryGetValue(character, out var amount))
|
||||
{
|
||||
_positionDelta.X += amount;
|
||||
_currentGlyph.Position.X += amount;
|
||||
}
|
||||
}
|
||||
|
||||
_previousGlyph = _currentGlyph;
|
||||
|
||||
if (character != '\n')
|
||||
return true;
|
||||
|
||||
_positionDelta.Y += _font.LineHeight;
|
||||
_positionDelta.X = 0;
|
||||
_previousGlyph = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int GetUnicodeCodePoint(string text, ref int index)
|
||||
{
|
||||
return char.IsHighSurrogate(text[index]) && ++index < text.Length
|
||||
? char.ConvertToUtf32(text[index - 1], text[index])
|
||||
: text[index];
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_positionDelta = new Vector2();
|
||||
_index = -1;
|
||||
_previousGlyph = null;
|
||||
}
|
||||
}
|
||||
|
||||
public struct StringBuilderGlyphEnumerable : IEnumerable<BitmapFontGlyph>
|
||||
{
|
||||
private readonly StringBuilderGlyphEnumerator _enumerator;
|
||||
|
||||
public StringBuilderGlyphEnumerable(BitmapFont font, StringBuilder text, Vector2? position)
|
||||
{
|
||||
_enumerator = new StringBuilderGlyphEnumerator(font, text, position);
|
||||
}
|
||||
|
||||
public StringBuilderGlyphEnumerator GetEnumerator()
|
||||
{
|
||||
return _enumerator;
|
||||
}
|
||||
|
||||
IEnumerator<BitmapFontGlyph> IEnumerable<BitmapFontGlyph>.GetEnumerator()
|
||||
{
|
||||
return _enumerator;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _enumerator;
|
||||
}
|
||||
}
|
||||
|
||||
public struct StringBuilderGlyphEnumerator : IEnumerator<BitmapFontGlyph>
|
||||
{
|
||||
private readonly BitmapFont _font;
|
||||
private readonly StringBuilder _text;
|
||||
private int _index;
|
||||
private readonly Vector2 _position;
|
||||
private Vector2 _positionDelta;
|
||||
private BitmapFontGlyph _currentGlyph;
|
||||
private BitmapFontGlyph? _previousGlyph;
|
||||
|
||||
object IEnumerator.Current
|
||||
{
|
||||
get
|
||||
{
|
||||
// casting a struct to object will box it, behaviour we want to avoid...
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public BitmapFontGlyph Current => _currentGlyph;
|
||||
|
||||
public StringBuilderGlyphEnumerator(BitmapFont font, StringBuilder text, Vector2? position)
|
||||
{
|
||||
_font = font;
|
||||
_text = text;
|
||||
_index = -1;
|
||||
_position = position ?? new Vector2();
|
||||
_positionDelta = new Vector2();
|
||||
_currentGlyph = new BitmapFontGlyph();
|
||||
_previousGlyph = null;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (++_index >= _text.Length)
|
||||
return false;
|
||||
|
||||
var character = GetUnicodeCodePoint(_text, ref _index);
|
||||
_currentGlyph = new BitmapFontGlyph
|
||||
{
|
||||
CharacterID = character,
|
||||
Character = _font.GetCharacter(character),
|
||||
Position = _position + _positionDelta
|
||||
};
|
||||
|
||||
if (_currentGlyph.Character != null)
|
||||
{
|
||||
_currentGlyph.Position.X += _currentGlyph.Character.XOffset;
|
||||
_currentGlyph.Position.Y += _currentGlyph.Character.YOffset;
|
||||
_positionDelta.X += _currentGlyph.Character.XAdvance + _font.LetterSpacing;
|
||||
}
|
||||
|
||||
if (_font.UseKernings && _previousGlyph.HasValue && _previousGlyph.Value.Character != null)
|
||||
{
|
||||
int amount;
|
||||
if (_previousGlyph.Value.Character.Kernings.TryGetValue(character, out amount))
|
||||
{
|
||||
_positionDelta.X += amount;
|
||||
_currentGlyph.Position.X += amount;
|
||||
}
|
||||
}
|
||||
|
||||
_previousGlyph = _currentGlyph;
|
||||
|
||||
if (character != '\n')
|
||||
return true;
|
||||
|
||||
_positionDelta.Y += _font.LineHeight;
|
||||
_positionDelta.X = _position.X;
|
||||
_previousGlyph = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int GetUnicodeCodePoint(StringBuilder text, ref int index)
|
||||
{
|
||||
return char.IsHighSurrogate(text[index]) && ++index < text.Length
|
||||
? char.ConvertToUtf32(text[index - 1], text[index])
|
||||
: text[index];
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_positionDelta = new Vector2();
|
||||
_index = -1;
|
||||
_previousGlyph = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => $"{Face}";
|
||||
|
||||
public static BitmapFont FromFile(GraphicsDevice graphicsDevice, string path)
|
||||
{
|
||||
using FileStream stream = File.OpenRead(path);
|
||||
return FromStream(graphicsDevice, stream);
|
||||
}
|
||||
|
||||
public static BitmapFont FromStream(GraphicsDevice graphicsDevice, FileStream stream)
|
||||
{
|
||||
BmfFile bmfFile = BmfFile.FromStream(stream);
|
||||
|
||||
// Load page textures
|
||||
Dictionary<string, Texture2D> pages = new Dictionary<string, Texture2D>();
|
||||
for (int i = 0; i < bmfFile.Pages.Count; i++)
|
||||
{
|
||||
if (!pages.ContainsKey(bmfFile.Pages[i]))
|
||||
{
|
||||
string texturePath = Path.Combine(bmfFile.Path, bmfFile.Pages[i]);
|
||||
Texture2D texture = Texture2D.FromFile(graphicsDevice, texturePath);
|
||||
pages.Add(bmfFile.Pages[i], texture);
|
||||
}
|
||||
}
|
||||
|
||||
// Load Characters
|
||||
Dictionary<int, BitmapFontCharacter> characters = new Dictionary<int, BitmapFontCharacter>();
|
||||
for (int i = 0; i < bmfFile.Characters.Count; i++)
|
||||
{
|
||||
BmfCharsBlock charBlock = bmfFile.Characters[i];
|
||||
Texture2D texture = pages[bmfFile.Pages[charBlock.Page]];
|
||||
TextureRegion2D region = new TextureRegion2D(texture, charBlock.X, charBlock.Y, charBlock.Width, charBlock.Height);
|
||||
BitmapFontCharacter character = new BitmapFontCharacter((int)charBlock.ID, region, charBlock.XOffset, charBlock.YOffset, charBlock.XAdvance);
|
||||
characters.Add(character.Character, character);
|
||||
}
|
||||
|
||||
// Load kernings
|
||||
for (int i = 0; i < bmfFile.Kernings.Count; i++)
|
||||
{
|
||||
BmfKerningPairsBlock kerningBlock = bmfFile.Kernings[i];
|
||||
if (characters.TryGetValue((int)kerningBlock.First, out BitmapFontCharacter character))
|
||||
{
|
||||
character.Kernings.Add((int)kerningBlock.Second, kerningBlock.Amount);
|
||||
}
|
||||
}
|
||||
|
||||
return new BitmapFont(bmfFile.FontName, bmfFile.Info.FontSize, bmfFile.Common.LineHeight, characters.Values);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) Craftwork Games. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MonoGame.Extended.TextureAtlases;
|
||||
|
||||
namespace MonoGame.Extended.BitmapFonts;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a character in a bitmap font. This class cannot be inherited.
|
||||
/// </summary>
|
||||
public sealed class BitmapFontCharacter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the character code.
|
||||
/// </summary>
|
||||
public int Character { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture region that contains the character's image.
|
||||
/// </summary>
|
||||
public TextureRegion2D TextureRegion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the horizontal offset for rendering the character.
|
||||
/// </summary>
|
||||
public int XOffset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the vertical offset for rendering the character.
|
||||
/// </summary>
|
||||
public int YOffset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the horizontal advance value for rendering the next character.
|
||||
/// </summary>
|
||||
public int XAdvance { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dictionary of kerning values for pairs of characters.
|
||||
/// </summary>
|
||||
public Dictionary<int, int> Kernings { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BitmapFontCharacter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="character">The character code.</param>
|
||||
/// <param name="textureRegion">The texture region that contains the character's image.</param>
|
||||
/// <param name="xOffset">The horizontal offset for rendering the character.</param>
|
||||
/// <param name="yOffset">The vertical offset for rendering the character.</param>
|
||||
/// <param name="xAdvance">The horizontal advance value for rendering the next character.</param>
|
||||
public BitmapFontCharacter(int character, TextureRegion2D textureRegion, int xOffset, int yOffset, int xAdvance)
|
||||
{
|
||||
Character = character;
|
||||
TextureRegion = textureRegion;
|
||||
XOffset = xOffset;
|
||||
YOffset = yOffset;
|
||||
XAdvance = xAdvance;
|
||||
Kernings = new Dictionary<int, int>();
|
||||
}
|
||||
}
|
||||
@@ -40,10 +40,10 @@ namespace MonoGame.Extended.BitmapFonts
|
||||
var glyphs = bitmapFont.GetGlyphs(text, position);
|
||||
foreach (var glyph in glyphs)
|
||||
{
|
||||
if (glyph.FontRegion == null)
|
||||
if (glyph.Character == null)
|
||||
continue;
|
||||
var characterOrigin = position - glyph.Position + origin;
|
||||
spriteBatch.Draw(glyph.FontRegion.TextureRegion, position, color, rotation, characterOrigin, scale, effect, layerDepth, clippingRectangle);
|
||||
spriteBatch.Draw(glyph.Character.TextureRegion, position, color, rotation, characterOrigin, scale, effect, layerDepth, clippingRectangle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,10 +58,10 @@ namespace MonoGame.Extended.BitmapFonts
|
||||
var glyphs = bitmapFont.GetGlyphs(text, position);
|
||||
foreach (var glyph in glyphs)
|
||||
{
|
||||
if (glyph.FontRegion == null)
|
||||
if (glyph.Character == null)
|
||||
continue;
|
||||
var characterOrigin = position - glyph.Position + origin;
|
||||
spriteBatch.Draw(glyph.FontRegion.TextureRegion, position, color, rotation, characterOrigin, scale, effect, layerDepth, clippingRectangle);
|
||||
spriteBatch.Draw(glyph.Character.TextureRegion, position, color, rotation, characterOrigin, scale, effect, layerDepth, clippingRectangle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace MonoGame.Extended.BitmapFonts
|
||||
/// <param name="clippingRectangle">Clips the boundaries of the text so that it's not drawn outside the clipping rectangle</param>
|
||||
public static void DrawString(this SpriteBatch spriteBatch, BitmapFont font, string text, Vector2 position, Color color, float layerDepth, Rectangle? clippingRectangle = null)
|
||||
{
|
||||
DrawString(spriteBatch, font, text, position, color, rotation: 0, origin: Vector2.Zero, scale: Vector2.One, effect: SpriteEffects.None,
|
||||
DrawString(spriteBatch, font, text, position, color, rotation: 0, origin: Vector2.Zero, scale: Vector2.One, effect: SpriteEffects.None,
|
||||
layerDepth: layerDepth, clippingRectangle: clippingRectangle);
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace MonoGame.Extended.BitmapFonts
|
||||
/// <param name="clippingRectangle">Clips the boundaries of the text so that it's not drawn outside the clipping rectangle</param>
|
||||
public static void DrawString(this SpriteBatch spriteBatch, BitmapFont font, string text, Vector2 position, Color color, Rectangle? clippingRectangle = null)
|
||||
{
|
||||
DrawString(spriteBatch, font, text, position, color, rotation: 0, origin: Vector2.Zero, scale: Vector2.One, effect: SpriteEffects.None,
|
||||
DrawString(spriteBatch, font, text, position, color, rotation: 0, origin: Vector2.Zero, scale: Vector2.One, effect: SpriteEffects.None,
|
||||
layerDepth: 0, clippingRectangle: clippingRectangle);
|
||||
}
|
||||
|
||||
@@ -153,4 +153,4 @@ namespace MonoGame.Extended.BitmapFonts
|
||||
layerDepth: 0, clippingRectangle: clippingRectangle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Copyright (c) Craftwork Games. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
@@ -24,23 +28,25 @@ namespace MonoGame.Extended.BitmapFonts
|
||||
.Select(textureName => reader.ContentManager.Load<Texture2D>(reader.GetRelativeAssetName(textureName)))
|
||||
.ToArray();
|
||||
|
||||
var lineHeight = reader.ReadInt32();
|
||||
var face = reader.ReadString();
|
||||
var fontSize = reader.ReadInt16();
|
||||
var lineHeight = reader.ReadUInt16();
|
||||
var regionCount = reader.ReadInt32();
|
||||
var regions = new BitmapFontRegion[regionCount];
|
||||
var regions = new BitmapFontCharacter[regionCount];
|
||||
|
||||
for (var r = 0; r < regionCount; r++)
|
||||
{
|
||||
var character = reader.ReadInt32();
|
||||
var textureIndex = reader.ReadInt32();
|
||||
var x = reader.ReadInt32();
|
||||
var y = reader.ReadInt32();
|
||||
var width = reader.ReadInt32();
|
||||
var height = reader.ReadInt32();
|
||||
var xOffset = reader.ReadInt32();
|
||||
var yOffset = reader.ReadInt32();
|
||||
var xAdvance = reader.ReadInt32();
|
||||
var character = reader.ReadUInt32();
|
||||
var textureIndex = reader.ReadByte();
|
||||
var x = reader.ReadUInt16();
|
||||
var y = reader.ReadUInt16();
|
||||
var width = reader.ReadUInt16();
|
||||
var height = reader.ReadUInt16();
|
||||
var xOffset = reader.ReadInt16();
|
||||
var yOffset = reader.ReadInt16();
|
||||
var xAdvance = reader.ReadInt16();
|
||||
var textureRegion = new TextureRegion2D(textures[textureIndex], x, y, width, height);
|
||||
regions[r] = new BitmapFontRegion(textureRegion, character, xOffset, yOffset, xAdvance);
|
||||
regions[r] = new BitmapFontCharacter((int)character, textureRegion, xOffset, yOffset, xAdvance);
|
||||
}
|
||||
|
||||
var characterMap = regions.ToDictionary(r => r.Character);
|
||||
@@ -48,18 +54,18 @@ namespace MonoGame.Extended.BitmapFonts
|
||||
|
||||
for (var k = 0; k < kerningsCount; k++)
|
||||
{
|
||||
var first = reader.ReadInt32();
|
||||
var second = reader.ReadInt32();
|
||||
var amount = reader.ReadInt32();
|
||||
var first = reader.ReadUInt32();
|
||||
var second = reader.ReadUInt32();
|
||||
var amount = reader.ReadInt16();
|
||||
|
||||
// Find region
|
||||
if (!characterMap.TryGetValue(first, out var region))
|
||||
if (!characterMap.TryGetValue((int)first, out var region))
|
||||
continue;
|
||||
|
||||
region.Kernings[second] = amount;
|
||||
region.Kernings[(int)second] = amount;
|
||||
}
|
||||
|
||||
return new BitmapFont(reader.AssetName, regions, lineHeight);
|
||||
return new BitmapFont(face, fontSize, lineHeight, regions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
using System;
|
||||
using MonoGame.Extended.TextureAtlases;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MonoGame.Extended.BitmapFonts
|
||||
{
|
||||
public class BitmapFontRegion
|
||||
{
|
||||
public BitmapFontRegion(TextureRegion2D textureRegion, int character, int xOffset, int yOffset, int xAdvance)
|
||||
{
|
||||
TextureRegion = textureRegion;
|
||||
Character = character;
|
||||
XOffset = xOffset;
|
||||
YOffset = yOffset;
|
||||
XAdvance = xAdvance;
|
||||
Kernings = new Dictionary<int, int>();
|
||||
}
|
||||
|
||||
public int Character { get; }
|
||||
public TextureRegion2D TextureRegion { get; }
|
||||
public int XOffset { get; }
|
||||
public int YOffset { get; }
|
||||
public int XAdvance { get; }
|
||||
public int Width => TextureRegion.Width;
|
||||
public int Height => TextureRegion.Height;
|
||||
public Dictionary<int, int> Kernings { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Convert.ToChar(Character)} {TextureRegion}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Craftwork Games. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MonoGame.Extended.BitmapFonts.BmfTypes;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct BmfCharsBlock
|
||||
{
|
||||
public const int StructSize = 20;
|
||||
|
||||
[FieldOffset(0)] public uint ID;
|
||||
[FieldOffset(4)] public ushort X;
|
||||
[FieldOffset(6)] public ushort Y;
|
||||
[FieldOffset(8)] public ushort Width;
|
||||
[FieldOffset(10)] public ushort Height;
|
||||
[FieldOffset(12)] public short XOffset;
|
||||
[FieldOffset(14)] public short YOffset;
|
||||
[FieldOffset(16)] public short XAdvance;
|
||||
[FieldOffset(18)] public byte Page;
|
||||
[FieldOffset(19)] public byte Chnl;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Craftwork Games. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MonoGame.Extended.BitmapFonts.BmfTypes;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct BmfCommonBlock
|
||||
{
|
||||
public const int StructSize = 15;
|
||||
|
||||
[FieldOffset(0)] public ushort LineHeight;
|
||||
[FieldOffset(2)] public ushort Base;
|
||||
[FieldOffset(4)] public ushort ScaleW;
|
||||
[FieldOffset(6)] public ushort ScaleH;
|
||||
[FieldOffset(8)] public ushort Pages;
|
||||
[FieldOffset(10)] public byte BitField;
|
||||
[FieldOffset(11)] public byte AlphaChnl;
|
||||
[FieldOffset(12)] public byte RedChnl;
|
||||
[FieldOffset(13)] public byte GreenChnl;
|
||||
[FieldOffset(14)] public byte BlueChnl;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) Craftwork Games. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using MonoGame.Extended.BitmapFonts.Serialization;
|
||||
|
||||
namespace MonoGame.Extended.BitmapFonts.BmfTypes;
|
||||
|
||||
public sealed class BmfFile
|
||||
{
|
||||
public string Path;
|
||||
public BmfHeader Header;
|
||||
public BmfCommonBlock Common;
|
||||
public BmfInfoBlock Info;
|
||||
public string FontName;
|
||||
public readonly List<string> Pages = new List<string>();
|
||||
public readonly List<BmfCharsBlock> Characters = new List<BmfCharsBlock>();
|
||||
public readonly List<BmfKerningPairsBlock> Kernings = new List<BmfKerningPairsBlock>();
|
||||
|
||||
public static BmfFile FromStream(FileStream stream)
|
||||
{
|
||||
using BinaryReader reader = new BinaryReader(stream);
|
||||
|
||||
long position = stream.Position;
|
||||
ReadOnlySpan<byte> binaryHeader = stackalloc byte[] { 66, 77, 70, 3 };
|
||||
ReadOnlySpan<byte> xmlHeader = stackalloc byte[] { 60, 63, 120, 109 };
|
||||
ReadOnlySpan<byte> textHeader = stackalloc byte[] { 105, 110, 102, 111 };
|
||||
|
||||
Span<byte> buffer = stackalloc byte[4];
|
||||
stream.Read(buffer);
|
||||
stream.Position = position;
|
||||
|
||||
BmfFile bmfFile = new BmfFile();
|
||||
bmfFile.Path = stream.Name;
|
||||
|
||||
|
||||
if (buffer.SequenceEqual(binaryHeader))
|
||||
{
|
||||
BmfBinaryLoader.Load(bmfFile, stream);
|
||||
return bmfFile;
|
||||
}
|
||||
|
||||
if (buffer.SequenceEqual(xmlHeader))
|
||||
{
|
||||
BmfXmlLoader.Load(bmfFile, stream);
|
||||
return bmfFile;
|
||||
}
|
||||
|
||||
if (buffer.SequenceEqual(textHeader))
|
||||
{
|
||||
BmfTextLoader.Load(bmfFile, stream);
|
||||
return bmfFile;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("This does not appear to be a valid BMFont file!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Craftwork Games. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MonoGame.Extended.BitmapFonts.BmfTypes;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct BmfHeader
|
||||
{
|
||||
public const int StructSize = 4;
|
||||
|
||||
[FieldOffset(0)] public byte B;
|
||||
[FieldOffset(1)] public byte M;
|
||||
[FieldOffset(2)] public byte F;
|
||||
[FieldOffset(3)] public byte Version;
|
||||
|
||||
public readonly bool IsValid => B == 0x42 && M == 0x4D && F == 0x46 && Version == 3;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MonoGame.Extended.BitmapFonts.BmfTypes;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct BmfInfoBlock
|
||||
{
|
||||
public const int StructSize = 14;
|
||||
|
||||
[FieldOffset(0)] public short FontSize;
|
||||
[FieldOffset(2)] public byte BitField;
|
||||
[FieldOffset(3)] public byte CharSet;
|
||||
[FieldOffset(4)] public ushort StretchH;
|
||||
[FieldOffset(6)] public byte AA;
|
||||
[FieldOffset(7)] public byte PaddingUp;
|
||||
[FieldOffset(8)] public byte PaddingRight;
|
||||
[FieldOffset(9)] public byte PaddingDown;
|
||||
[FieldOffset(10)] public byte PaddingLeft;
|
||||
[FieldOffset(11)] public byte SpacingHoriz;
|
||||
[FieldOffset(12)] public byte SpacingVert;
|
||||
[FieldOffset(13)] public byte Outline;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Craftwork Games. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MonoGame.Extended.BitmapFonts.BmfTypes;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct BmfKerningPairsBlock
|
||||
{
|
||||
public const int StructSize = 10;
|
||||
|
||||
[FieldOffset(0)] public uint First;
|
||||
[FieldOffset(4)] public uint Second;
|
||||
[FieldOffset(8)] public short Amount;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) Craftwork Games. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using MonoGame.Extended.BitmapFonts.BmfTypes;
|
||||
|
||||
namespace MonoGame.Extended.BitmapFonts.Serialization;
|
||||
|
||||
public class BmfBinaryLoader
|
||||
{
|
||||
public static void Load(BmfFile bmfFile, Stream stream)
|
||||
{
|
||||
using BinaryReader reader = new BinaryReader(stream);
|
||||
bmfFile.Header = LoadHeader(reader);
|
||||
BmfCharsBlock[] characters = Array.Empty<BmfCharsBlock>();
|
||||
BmfKerningPairsBlock[] kernings = Array.Empty<BmfKerningPairsBlock>();
|
||||
|
||||
while (reader.BaseStream.Position < reader.BaseStream.Length)
|
||||
{
|
||||
byte blockType = reader.ReadByte();
|
||||
int blockSize = reader.ReadInt32();
|
||||
|
||||
switch (blockType)
|
||||
{
|
||||
case 1:
|
||||
bmfFile.Info = AsType<BmfInfoBlock>(reader.ReadBytes(BmfInfoBlock.StructSize));
|
||||
int stringLen = blockSize - BmfInfoBlock.StructSize;
|
||||
bmfFile.FontName = Encoding.UTF8.GetString(reader.ReadBytes(stringLen)).Replace("\0", string.Empty);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
bmfFile.Common = AsType<BmfCommonBlock>(reader.ReadBytes(BmfCommonBlock.StructSize));
|
||||
break;
|
||||
|
||||
case 3:
|
||||
string[] pages = Encoding.UTF8.GetString(reader.ReadBytes(blockSize)).Split('\0', StringSplitOptions.RemoveEmptyEntries);
|
||||
bmfFile.Pages.AddRange(pages);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
int count = blockSize / BmfCharsBlock.StructSize;
|
||||
for (int c = 0; c < count; c++)
|
||||
{
|
||||
BmfCharsBlock character = AsType<BmfCharsBlock>(reader.ReadBytes(BmfCharsBlock.StructSize));
|
||||
bmfFile.Characters.Add(character);
|
||||
}
|
||||
break;
|
||||
|
||||
case 5:
|
||||
int kerningCount = blockSize / BmfKerningPairsBlock.StructSize;
|
||||
kernings = new BmfKerningPairsBlock[kerningCount];
|
||||
for (int k = 0; k < kerningCount; k++)
|
||||
{
|
||||
BmfKerningPairsBlock kerning = AsType<BmfKerningPairsBlock>(reader.ReadBytes(BmfKerningPairsBlock.StructSize));
|
||||
bmfFile.Kernings.Add(kerning);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
reader.BaseStream.Seek(blockSize, SeekOrigin.Current);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static BmfHeader LoadHeader(BinaryReader reader)
|
||||
{
|
||||
BmfHeader header = AsType<BmfHeader>(reader.ReadBytes(BmfHeader.StructSize));
|
||||
if (!header.IsValid)
|
||||
{
|
||||
throw new InvalidOperationException($"The BMFont file header is invalid, this does not appear to be a valid BMFont file.");
|
||||
}
|
||||
return header;
|
||||
}
|
||||
|
||||
private static T AsType<T>(ReadOnlySpan<byte> buffer) where T : struct
|
||||
{
|
||||
T value;
|
||||
try
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = buffer)
|
||||
{
|
||||
value = Marshal.PtrToStructure<T>((IntPtr)ptr);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
// Copyright (c) Craftwork Games. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using MonoGame.Extended.BitmapFonts.BmfTypes;
|
||||
|
||||
namespace MonoGame.Extended.BitmapFonts.Serialization;
|
||||
|
||||
public static class BmfTextLoader
|
||||
{
|
||||
public static void Load(BmfFile bmfFile, Stream stream)
|
||||
{
|
||||
using StreamReader reader = new StreamReader(stream);
|
||||
|
||||
// Text does not contain the header like binary so we manually create it
|
||||
bmfFile.Header = new BmfHeader() { B = (byte)'B', M = (byte)'M', F = (byte)'F', Version = 3 };
|
||||
|
||||
string line = default;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
List<string> tokens = GetTokens(line);
|
||||
if (tokens.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (tokens[0])
|
||||
{
|
||||
case "info":
|
||||
LoadInfoLine(bmfFile, CollectionsMarshal.AsSpan(tokens)[1..]);
|
||||
break;
|
||||
case "common":
|
||||
LoadCommonLine(bmfFile, CollectionsMarshal.AsSpan(tokens)[1..]);
|
||||
break;
|
||||
case "page":
|
||||
LoadPageLine(bmfFile, CollectionsMarshal.AsSpan(tokens)[1..]);
|
||||
break;
|
||||
case "char":
|
||||
LoadCharacterLine(bmfFile, CollectionsMarshal.AsSpan(tokens)[1..]);
|
||||
break;
|
||||
case "kerning":
|
||||
LoadKerningLine(bmfFile, CollectionsMarshal.AsSpan(tokens)[1..]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void LoadInfoLine(BmfFile bmfFile, ReadOnlySpan<string> tokens)
|
||||
{
|
||||
for (int i = 0; i < tokens.Length; ++i)
|
||||
{
|
||||
string[] split = tokens[i].Split('=');
|
||||
|
||||
if (split.Length != 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (split[0])
|
||||
{
|
||||
case "face":
|
||||
bmfFile.FontName = split[1].Replace("\"", string.Empty);
|
||||
break;
|
||||
case "size":
|
||||
bmfFile.Info.FontSize = Convert.ToInt16(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "smooth":
|
||||
byte smooth = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
|
||||
bmfFile.Info.BitField |= (byte)(smooth << 7);
|
||||
break;
|
||||
case "unicode":
|
||||
byte unicode = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
|
||||
bmfFile.Info.BitField |= (byte)(unicode << 6);
|
||||
break;
|
||||
case "italic":
|
||||
byte italic = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
|
||||
bmfFile.Info.BitField |= (byte)(italic << 5);
|
||||
break;
|
||||
case "bold":
|
||||
byte bold = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
|
||||
bmfFile.Info.BitField |= (byte)(bold << 4);
|
||||
break;
|
||||
case "fixedHeight":
|
||||
byte fixedHeight = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
|
||||
bmfFile.Info.BitField |= (byte)(fixedHeight << 3);
|
||||
break;
|
||||
case "stretchH":
|
||||
bmfFile.Info.StretchH = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "aa":
|
||||
bmfFile.Info.AA = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "padding":
|
||||
string[] paddingValues = split[1].Split(',');
|
||||
if (paddingValues.Length == 4)
|
||||
{
|
||||
bmfFile.Info.PaddingUp = Convert.ToByte(paddingValues[0], CultureInfo.InvariantCulture);
|
||||
bmfFile.Info.PaddingRight = Convert.ToByte(paddingValues[1], CultureInfo.InvariantCulture);
|
||||
bmfFile.Info.PaddingDown = Convert.ToByte(paddingValues[2], CultureInfo.InvariantCulture);
|
||||
bmfFile.Info.PaddingLeft = Convert.ToByte(paddingValues[3], CultureInfo.InvariantCulture);
|
||||
}
|
||||
break;
|
||||
case "spacing":
|
||||
string[] spacingValues = split[1].Split(',');
|
||||
if (spacingValues.Length == 2)
|
||||
{
|
||||
bmfFile.Info.SpacingHoriz = Convert.ToByte(spacingValues[0], CultureInfo.InvariantCulture);
|
||||
bmfFile.Info.SpacingVert = Convert.ToByte(spacingValues[1], CultureInfo.InvariantCulture);
|
||||
}
|
||||
break;
|
||||
case "outline":
|
||||
bmfFile.Info.Outline = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadCommonLine(BmfFile bmfFile, ReadOnlySpan<string> tokens)
|
||||
{
|
||||
for (int i = 0; i < tokens.Length; ++i)
|
||||
{
|
||||
string[] split = tokens[i].Split('=');
|
||||
|
||||
if (split.Length != 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (split[0])
|
||||
{
|
||||
case "lineHeight":
|
||||
bmfFile.Common.LineHeight = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "base":
|
||||
bmfFile.Common.Base = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "scaleW":
|
||||
bmfFile.Common.ScaleW = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "scaleH":
|
||||
bmfFile.Common.ScaleH = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "pages":
|
||||
bmfFile.Common.Pages = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "packed":
|
||||
byte packed = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
|
||||
bmfFile.Common.BitField |= (byte)(packed << 7);
|
||||
break;
|
||||
case "alphaChnl":
|
||||
bmfFile.Common.AlphaChnl = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "redChnl":
|
||||
bmfFile.Common.RedChnl = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "greenChnl":
|
||||
bmfFile.Common.GreenChnl = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "blueChnl":
|
||||
bmfFile.Common.BlueChnl = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadPageLine(BmfFile bmfFile, ReadOnlySpan<string> tokens)
|
||||
{
|
||||
for (int i = 0; i < tokens.Length; ++i)
|
||||
{
|
||||
string[] split = tokens[i].Split('=');
|
||||
|
||||
if (split.Length != 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (split[0] == "file")
|
||||
{
|
||||
string page = split[1].Replace("\"", string.Empty);
|
||||
bmfFile.Pages.Add(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadCharacterLine(BmfFile bmfFile, ReadOnlySpan<string> tokens)
|
||||
{
|
||||
BmfCharsBlock character = default;
|
||||
|
||||
for (int i = 0; i < tokens.Length; ++i)
|
||||
{
|
||||
string[] split = tokens[i].Split('=');
|
||||
|
||||
if (split.Length != 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (split[0])
|
||||
{
|
||||
case "id":
|
||||
character.ID = Convert.ToUInt32(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "x":
|
||||
character.X = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "y":
|
||||
character.Y = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "width":
|
||||
character.Width = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "height":
|
||||
character.Height = Convert.ToUInt16(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "xoffset":
|
||||
character.XOffset = Convert.ToInt16(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "yoffset":
|
||||
character.YOffset = Convert.ToInt16(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "xadvance":
|
||||
character.XAdvance = Convert.ToInt16(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "page":
|
||||
character.Page = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "chnl":
|
||||
character.Chnl = Convert.ToByte(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bmfFile.Characters.Add(character);
|
||||
}
|
||||
|
||||
private static void LoadKerningLine(BmfFile bmfFile, ReadOnlySpan<string> tokens)
|
||||
{
|
||||
BmfKerningPairsBlock kerning = default;
|
||||
|
||||
for (int i = 0; i < tokens.Length; ++i)
|
||||
{
|
||||
string[] split = tokens[i].Split('=');
|
||||
|
||||
if (split.Length != 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (split[0])
|
||||
{
|
||||
case "first":
|
||||
kerning.First = Convert.ToUInt32(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "second":
|
||||
kerning.Second = Convert.ToUInt32(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case "amount":
|
||||
kerning.Amount = Convert.ToInt16(split[1], CultureInfo.InvariantCulture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bmfFile.Kernings.Add(kerning);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static List<string> GetTokens(string line)
|
||||
{
|
||||
List<string> tokens = new List<string>();
|
||||
StringBuilder currentToken = new StringBuilder();
|
||||
bool inQuotes = false;
|
||||
|
||||
for (int i = 0; i < line.Length; i++)
|
||||
{
|
||||
char c = line[i];
|
||||
|
||||
if (c == ' ' && !inQuotes)
|
||||
{
|
||||
if (currentToken.Length > 0)
|
||||
{
|
||||
tokens.Add(currentToken.ToString());
|
||||
currentToken.Clear();
|
||||
}
|
||||
}
|
||||
else if (c == '"')
|
||||
{
|
||||
inQuotes = !inQuotes;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentToken.Append(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToken.Length > 0)
|
||||
{
|
||||
tokens.Add(currentToken.ToString());
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) Craftwork Games. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using MonoGame.Extended.BitmapFonts.BmfTypes;
|
||||
|
||||
namespace MonoGame.Extended.BitmapFonts.Serialization;
|
||||
|
||||
public static class BmfXmlLoader
|
||||
{
|
||||
public static void Load(BmfFile bmfFile, Stream stream)
|
||||
{
|
||||
// XML does not contain the header like binary so we manually create it
|
||||
bmfFile.Header = new BmfHeader() { B = (byte)'B', M = (byte)'M', F = (byte)'F', Version = 3 };
|
||||
|
||||
XmlDocument document = new XmlDocument();
|
||||
document.Load(stream);
|
||||
XmlNode root = document.DocumentElement;
|
||||
|
||||
LoadInfoNode(bmfFile, root);
|
||||
LoadCommonNode(bmfFile, root);
|
||||
LoadPageNodes(bmfFile, root);
|
||||
LoadCharacterNodes(bmfFile, root);
|
||||
LoadKerningNodes(bmfFile, root);
|
||||
}
|
||||
|
||||
private static void LoadInfoNode(BmfFile bmfFile, XmlNode root)
|
||||
{
|
||||
BmfInfoBlock info;
|
||||
|
||||
XmlNode node = root.SelectSingleNode("info");
|
||||
bmfFile.FontName = node.GetStringAttribute("face");
|
||||
bmfFile.Info.FontSize = node.GetInt16Attribute("size");
|
||||
|
||||
byte smooth = node.GetByteAttribute("smooth");
|
||||
byte unicode = node.GetByteAttribute("unicode");
|
||||
byte italic = node.GetByteAttribute("italic");
|
||||
byte bold = node.GetByteAttribute("bold");
|
||||
byte fixedHeight = node.GetByteAttribute("fixedHeight");
|
||||
bmfFile.Info.BitField = (byte)(smooth << 7 | unicode << 6 | italic << 5 | bold << 4 | fixedHeight << 3);
|
||||
|
||||
bmfFile.Info.CharSet = node.GetByteAttribute("charSet");
|
||||
bmfFile.Info.StretchH = node.GetUInt16Attribute("stretchH");
|
||||
bmfFile.Info.AA = node.GetByteAttribute("aa");
|
||||
|
||||
byte[] paddingValues = node.GetByteDelimitedAttribute("padding", 4);
|
||||
bmfFile.Info.PaddingUp = paddingValues[0];
|
||||
bmfFile.Info.PaddingRight = paddingValues[1];
|
||||
bmfFile.Info.PaddingDown = paddingValues[2];
|
||||
bmfFile.Info.PaddingLeft = paddingValues[3];
|
||||
|
||||
byte[] spacingValues = node.GetByteDelimitedAttribute("spacing", 2);
|
||||
bmfFile.Info.SpacingHoriz = spacingValues[0];
|
||||
bmfFile.Info.SpacingVert = spacingValues[1];
|
||||
|
||||
bmfFile.Info.Outline = node.GetByteAttribute("outline");
|
||||
}
|
||||
|
||||
private static void LoadCommonNode(BmfFile bmfFile, XmlNode root)
|
||||
{
|
||||
BmfCommonBlock common;
|
||||
|
||||
XmlNode node = root.SelectSingleNode("common");
|
||||
bmfFile.Common.LineHeight = node.GetUInt16Attribute("lineHeight");
|
||||
bmfFile.Common.Base = node.GetUInt16Attribute("base");
|
||||
bmfFile.Common.ScaleW = node.GetUInt16Attribute("scaleW");
|
||||
bmfFile.Common.ScaleH = node.GetUInt16Attribute("scaleH");
|
||||
bmfFile.Common.Pages = node.GetUInt16Attribute("pages");
|
||||
|
||||
byte packed = node.GetByteAttribute("packed");
|
||||
bmfFile.Common.BitField = (byte)(packed << 7);
|
||||
|
||||
bmfFile.Common.AlphaChnl = node.GetByteAttribute("alphaChnl");
|
||||
bmfFile.Common.RedChnl = node.GetByteAttribute("redChnl");
|
||||
bmfFile.Common.GreenChnl = node.GetByteAttribute("greenChnl");
|
||||
bmfFile.Common.BlueChnl = node.GetByteAttribute("blueChnl");
|
||||
}
|
||||
|
||||
private static void LoadPageNodes(BmfFile bmfFile, XmlNode root)
|
||||
{
|
||||
XmlNodeList nodes = root.SelectNodes("pages/page");
|
||||
|
||||
for (int i = 0; i < nodes.Count; ++i)
|
||||
{
|
||||
XmlNode node = nodes[i];
|
||||
|
||||
string file = node.GetStringAttribute("file");
|
||||
bmfFile.Pages.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadCharacterNodes(BmfFile bmfFile, XmlNode root)
|
||||
{
|
||||
XmlNodeList characterNodes = root.SelectNodes("chars/char");
|
||||
|
||||
for (int i = 0; i < characterNodes.Count; ++i)
|
||||
{
|
||||
XmlNode node = characterNodes[i];
|
||||
|
||||
BmfCharsBlock character;
|
||||
character.ID = node.GetUInt32Attribute("id");
|
||||
character.X = node.GetUInt16Attribute("x");
|
||||
character.Y = node.GetUInt16Attribute("y");
|
||||
character.Width = node.GetUInt16Attribute("width");
|
||||
character.Height = node.GetUInt16Attribute("height");
|
||||
character.XOffset = node.GetInt16Attribute("xoffset");
|
||||
character.YOffset = node.GetInt16Attribute("yoffset");
|
||||
character.XAdvance = node.GetInt16Attribute("xadvance");
|
||||
character.Page = node.GetByteAttribute("page");
|
||||
character.Chnl = node.GetByteAttribute("chnl");
|
||||
|
||||
bmfFile.Characters.Add(character);
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadKerningNodes(BmfFile bmfFile, XmlNode root)
|
||||
{
|
||||
XmlNodeList kerningNodes = root.SelectNodes("kernings/kerning");
|
||||
|
||||
for (int i = 0; i < kerningNodes.Count; ++i)
|
||||
{
|
||||
XmlNode node = kerningNodes[i];
|
||||
|
||||
BmfKerningPairsBlock kerning;
|
||||
kerning.First = node.GetUInt32Attribute("first");
|
||||
kerning.Second = node.GetUInt32Attribute("second");
|
||||
kerning.Amount = node.GetInt16Attribute("amount");
|
||||
|
||||
bmfFile.Kernings.Add(kerning);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>It makes MonoGame more awesome.</Description>
|
||||
<PackageTags>monogame extended pipeline bmfont tiled texture atlas input viewport fps shapes sprite</PackageTags>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Description>It makes MonoGame more awesome.</Description>
|
||||
<PackageTags>monogame extended pipeline bmfont tiled texture atlas input viewport fps shapes sprite</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Allow Test project to access internals -->
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>MonoGame.Extended.Tests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>MonoGame.Extended.Content.Pipeline</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Xml;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for the <see cref="XmlNode"/> class.
|
||||
/// </summary>
|
||||
public static class XmlNodeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves the value of the specified attribute from the <see cref="XmlNode"/>.
|
||||
/// </summary>
|
||||
/// <param name="node">The XML node.</param>
|
||||
/// <param name="attribute">The name of the attribute.</param>
|
||||
/// <param name="value">
|
||||
/// When this method returns, contains the value of the attribute, if found; otherwise, an empty string.
|
||||
/// </param>
|
||||
/// <returns><c>true</c> if the attribute is found and has a non-empty value; otherwise, <c>false</c>.</returns>
|
||||
private static bool GetAttributeValue(this XmlNode node, string attribute, out string value)
|
||||
{
|
||||
value = string.Empty;
|
||||
XmlAttribute attr = node.Attributes[attribute];
|
||||
if (attr is not null)
|
||||
{
|
||||
value = attr.Value;
|
||||
}
|
||||
return !string.IsNullOrEmpty(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the string value of the specified attribute.
|
||||
/// </summary>
|
||||
/// <param name="node">The XML node.</param>
|
||||
/// <param name="attribute">The name of the attribute.</param>
|
||||
/// <returns>
|
||||
/// The string value of the attribute, or the default value if the attribute is not found or is empty.
|
||||
/// </returns>
|
||||
public static string GetStringAttribute(this XmlNode node, string attribute)
|
||||
{
|
||||
if (node.GetAttributeValue(attribute, out string value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the byte value of the specified attribute.
|
||||
/// </summary>
|
||||
/// <param name="node">The XML node.</param>
|
||||
/// <param name="attribute">The name of the attribute.</param>
|
||||
/// <returns>
|
||||
/// The byte value of the attribute, or the default value if the attribute is not found or is empty.
|
||||
/// </returns>
|
||||
public static byte GetByteAttribute(this XmlNode node, string attribute)
|
||||
{
|
||||
if (node.GetAttributeValue(attribute, out string value))
|
||||
{
|
||||
return Convert.ToByte(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the ushort value of the specified attribute.
|
||||
/// </summary>
|
||||
/// <param name="node">The XML node.</param>
|
||||
/// <param name="attribute">The name of the attribute.</param>
|
||||
/// <returns>
|
||||
/// The ushort value of the attribute, or the default value if the attribute is not found or is empty.
|
||||
/// </returns>
|
||||
public static ushort GetUInt16Attribute(this XmlNode node, string attribute)
|
||||
{
|
||||
if (node.GetAttributeValue(attribute, out string value))
|
||||
{
|
||||
return Convert.ToUInt16(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the short value of the specified attribute.
|
||||
/// </summary>
|
||||
/// <param name="node">The XML node.</param>
|
||||
/// <param name="attribute">The name of the attribute.</param>
|
||||
/// <returns>
|
||||
/// The short value of the attribute, or the default value if the attribute is not found or is empty.
|
||||
/// </returns>
|
||||
public static short GetInt16Attribute(this XmlNode node, string attribute)
|
||||
{
|
||||
if (node.GetAttributeValue(attribute, out string value))
|
||||
{
|
||||
return Convert.ToInt16(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the uint value of the specified attribute.
|
||||
/// </summary>
|
||||
/// <param name="node">The XML node.</param>
|
||||
/// <param name="attribute">The name of the attribute.</param>
|
||||
/// <returns>
|
||||
/// The uint value of the attribute, or the default value if the attribute is not found or is empty.
|
||||
/// </returns>
|
||||
public static uint GetUInt32Attribute(this XmlNode node, string attribute)
|
||||
{
|
||||
if (node.GetAttributeValue(attribute, out string value))
|
||||
{
|
||||
return Convert.ToUInt32(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the int value of the specified attribute.
|
||||
/// </summary>
|
||||
/// <param name="node">The XML node.</param>
|
||||
/// <param name="attribute">The name of the attribute.</param>
|
||||
/// <returns>
|
||||
/// The int value of the attribute, or the default value if the attribute is not found or is empty.
|
||||
/// </returns>
|
||||
public static int GetInt32Attribute(this XmlNode node, string attribute)
|
||||
{
|
||||
if (node.GetAttributeValue(attribute, out string value))
|
||||
{
|
||||
return Convert.ToInt32(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the float value of the specified attribute.
|
||||
/// </summary>
|
||||
/// <param name="node">The XML node.</param>
|
||||
/// <param name="attribute">The name of the attribute.</param>
|
||||
/// <returns>
|
||||
/// The float value of the attribute, or the default value if the attribute is not found or is empty.
|
||||
/// </returns>
|
||||
public static float GetSingleAttribute(this XmlNode node, string attribute)
|
||||
{
|
||||
if (node.GetAttributeValue(attribute, out string value))
|
||||
{
|
||||
return Convert.ToSingle(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the double value of the specified attribute.
|
||||
/// </summary>
|
||||
/// <param name="node">The XML node.</param>
|
||||
/// <param name="attribute">The name of the attribute.</param>
|
||||
/// <returns>
|
||||
/// The double value of the attribute, or the default value if the attribute is not found or is empty.
|
||||
/// </returns>
|
||||
public static double GetDoubleAttribute(this XmlNode node, string attribute)
|
||||
{
|
||||
if (node.GetAttributeValue(attribute, out string value))
|
||||
{
|
||||
return Convert.ToDouble(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the boolean value of the specified attribute.
|
||||
/// </summary>
|
||||
/// <param name="node">The XML node.</param>
|
||||
/// <param name="attribute">The name of the attribute.</param>
|
||||
/// <returns>
|
||||
/// The boolean value of the attribute, or the default value if the attribute is not found or is empty.
|
||||
/// </returns>
|
||||
public static bool GetBoolAttribute(this XmlNode node, string attribute)
|
||||
{
|
||||
if (node.GetAttributeValue(attribute, out string value))
|
||||
{
|
||||
return Convert.ToBoolean(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the byte values from the specified delimited attribute and returns them in an array.
|
||||
/// </summary>
|
||||
/// <param name="node">The XML node.</param>
|
||||
/// <param name="attribute">The name of the attribute.</param>
|
||||
/// <param name="expectedCount">The expected number of byte values.</param>
|
||||
/// <returns>
|
||||
/// An array of byte values parsed from the attribute, or an array of default values if the attribute is not found
|
||||
/// or is empty.
|
||||
/// </returns>
|
||||
public static byte[] GetByteDelimitedAttribute(this XmlNode node, string attribute, int expectedCount)
|
||||
{
|
||||
byte[] result = new byte[expectedCount];
|
||||
|
||||
if (node.GetAttributeValue(attribute, out string value))
|
||||
{
|
||||
string[] split = value.Split(',');
|
||||
for (int i = 0; i < expectedCount; ++i)
|
||||
{
|
||||
result[i] = Convert.ToByte(split[i], CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user