BitmapFont.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. // Copyright (c) Craftwork Games. All rights reserved.
  2. // Licensed under the MIT license.
  3. // See LICENSE file in the project root for full license information.
  4. using System;
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. using System.IO;
  8. using System.Text;
  9. using Microsoft.Xna.Framework;
  10. using Microsoft.Xna.Framework.Graphics;
  11. using MonoGame.Extended.Content.BitmapFonts;
  12. using MonoGame.Extended.Graphics;
  13. namespace MonoGame.Extended.BitmapFonts;
  14. public sealed class BitmapFont
  15. {
  16. private readonly Dictionary<int, BitmapFontCharacter> _characters;
  17. public string Face { get; }
  18. public int Size { get; }
  19. public int LineHeight { get; }
  20. public int LetterSpacing { get; set; }
  21. public bool UseKernings { get; set; } = true;
  22. public BitmapFont(string face, int size, int lineHeight, IEnumerable<BitmapFontCharacter> characters)
  23. {
  24. Face = face;
  25. Size = size;
  26. LineHeight = lineHeight;
  27. _characters = new Dictionary<int, BitmapFontCharacter>();
  28. foreach (BitmapFontCharacter character in characters)
  29. {
  30. _characters.Add(character.Character, character);
  31. }
  32. }
  33. public BitmapFontCharacter GetCharacter(int character) => _characters.TryGetValue(character, out BitmapFontCharacter fontCharacter) ? fontCharacter : null;/* _characters[character];*/
  34. public bool TryGetCharacter(int character, out BitmapFontCharacter value) => _characters.TryGetValue(character, out value);
  35. public SizeF MeasureString(string text)
  36. {
  37. if (string.IsNullOrEmpty(text))
  38. return SizeF.Empty;
  39. var stringRectangle = GetStringRectangle(text);
  40. return new SizeF(stringRectangle.Width, stringRectangle.Height);
  41. }
  42. public SizeF MeasureString(StringBuilder text)
  43. {
  44. if (text == null || text.Length == 0)
  45. return SizeF.Empty;
  46. var stringRectangle = GetStringRectangle(text);
  47. return new SizeF(stringRectangle.Width, stringRectangle.Height);
  48. }
  49. public RectangleF GetStringRectangle(string text)
  50. {
  51. return GetStringRectangle(text, Vector2.Zero);
  52. }
  53. public RectangleF GetStringRectangle(string text, Vector2 position)
  54. {
  55. var glyphs = GetGlyphs(text, position);
  56. var rectangle = new RectangleF(position.X, position.Y, 0, LineHeight);
  57. foreach (var glyph in glyphs)
  58. {
  59. if (glyph.Character != null)
  60. {
  61. var right = glyph.Position.X + glyph.Character.TextureRegion.Width;
  62. if (right > rectangle.Right)
  63. rectangle.Width = (int)(right - rectangle.Left);
  64. }
  65. if (glyph.CharacterID == '\n')
  66. rectangle.Height += LineHeight;
  67. }
  68. return rectangle;
  69. }
  70. public RectangleF GetStringRectangle(StringBuilder text, Vector2? position = null)
  71. {
  72. var position1 = position ?? new Vector2();
  73. var glyphs = GetGlyphs(text, position1);
  74. var rectangle = new RectangleF(position1.X, position1.Y, 0, LineHeight);
  75. foreach (var glyph in glyphs)
  76. {
  77. if (glyph.Character != null)
  78. {
  79. var right = glyph.Position.X + glyph.Character.TextureRegion.Width;
  80. if (right > rectangle.Right)
  81. rectangle.Width = (int)(right - rectangle.Left);
  82. }
  83. if (glyph.CharacterID == '\n')
  84. rectangle.Height += LineHeight;
  85. }
  86. return rectangle;
  87. }
  88. public struct BitmapFontGlyph
  89. {
  90. public int CharacterID;
  91. public Vector2 Position;
  92. public BitmapFontCharacter Character;
  93. }
  94. public StringGlyphEnumerable GetGlyphs(string text, Vector2? position = null)
  95. {
  96. return new StringGlyphEnumerable(this, text, position);
  97. }
  98. public StringBuilderGlyphEnumerable GetGlyphs(StringBuilder text, Vector2? position)
  99. {
  100. return new StringBuilderGlyphEnumerable(this, text, position);
  101. }
  102. public struct StringGlyphEnumerable : IEnumerable<BitmapFontGlyph>
  103. {
  104. private readonly StringGlyphEnumerator _enumerator;
  105. public StringGlyphEnumerable(BitmapFont font, string text, Vector2? position)
  106. {
  107. _enumerator = new StringGlyphEnumerator(font, text, position);
  108. }
  109. public StringGlyphEnumerator GetEnumerator()
  110. {
  111. return _enumerator;
  112. }
  113. IEnumerator<BitmapFontGlyph> IEnumerable<BitmapFontGlyph>.GetEnumerator()
  114. {
  115. return _enumerator;
  116. }
  117. IEnumerator IEnumerable.GetEnumerator()
  118. {
  119. return _enumerator;
  120. }
  121. }
  122. public struct StringGlyphEnumerator : IEnumerator<BitmapFontGlyph>
  123. {
  124. private readonly BitmapFont _font;
  125. private readonly string _text;
  126. private int _index;
  127. private readonly Vector2 _position;
  128. private Vector2 _positionDelta;
  129. private BitmapFontGlyph _currentGlyph;
  130. private BitmapFontGlyph? _previousGlyph;
  131. object IEnumerator.Current
  132. {
  133. get
  134. {
  135. // casting a struct to object will box it, behaviour we want to avoid...
  136. throw new InvalidOperationException();
  137. }
  138. }
  139. public BitmapFontGlyph Current => _currentGlyph;
  140. public StringGlyphEnumerator(BitmapFont font, string text, Vector2? position)
  141. {
  142. _font = font;
  143. _text = text;
  144. _index = -1;
  145. _position = position ?? new Vector2();
  146. _positionDelta = new Vector2();
  147. _currentGlyph = new BitmapFontGlyph();
  148. _previousGlyph = null;
  149. }
  150. public bool MoveNext()
  151. {
  152. if (++_index >= _text.Length)
  153. return false;
  154. var character = GetUnicodeCodePoint(_text, ref _index);
  155. _currentGlyph.CharacterID = character;
  156. _font.TryGetCharacter(character, out _currentGlyph.Character);
  157. _currentGlyph.Position = _position + _positionDelta;
  158. if (_currentGlyph.Character != null)
  159. {
  160. _currentGlyph.Position.X += _currentGlyph.Character.XOffset;
  161. _currentGlyph.Position.Y += _currentGlyph.Character.YOffset;
  162. _positionDelta.X += _currentGlyph.Character.XAdvance + _font.LetterSpacing;
  163. }
  164. if (_font.UseKernings && _previousGlyph?.Character != null)
  165. {
  166. if (_previousGlyph.Value.Character.Kernings.TryGetValue(character, out var amount))
  167. {
  168. _positionDelta.X += amount;
  169. _currentGlyph.Position.X += amount;
  170. }
  171. }
  172. _previousGlyph = _currentGlyph;
  173. if (character != '\n')
  174. return true;
  175. _positionDelta.Y += _font.LineHeight;
  176. _positionDelta.X = 0;
  177. _previousGlyph = null;
  178. return true;
  179. }
  180. private static int GetUnicodeCodePoint(string text, ref int index)
  181. {
  182. return char.IsHighSurrogate(text[index]) && ++index < text.Length
  183. ? char.ConvertToUtf32(text[index - 1], text[index])
  184. : text[index];
  185. }
  186. public void Dispose()
  187. {
  188. }
  189. public void Reset()
  190. {
  191. _positionDelta = new Vector2();
  192. _index = -1;
  193. _previousGlyph = null;
  194. }
  195. }
  196. public struct StringBuilderGlyphEnumerable : IEnumerable<BitmapFontGlyph>
  197. {
  198. private readonly StringBuilderGlyphEnumerator _enumerator;
  199. public StringBuilderGlyphEnumerable(BitmapFont font, StringBuilder text, Vector2? position)
  200. {
  201. _enumerator = new StringBuilderGlyphEnumerator(font, text, position);
  202. }
  203. public StringBuilderGlyphEnumerator GetEnumerator()
  204. {
  205. return _enumerator;
  206. }
  207. IEnumerator<BitmapFontGlyph> IEnumerable<BitmapFontGlyph>.GetEnumerator()
  208. {
  209. return _enumerator;
  210. }
  211. IEnumerator IEnumerable.GetEnumerator()
  212. {
  213. return _enumerator;
  214. }
  215. }
  216. public struct StringBuilderGlyphEnumerator : IEnumerator<BitmapFontGlyph>
  217. {
  218. private readonly BitmapFont _font;
  219. private readonly StringBuilder _text;
  220. private int _index;
  221. private readonly Vector2 _position;
  222. private Vector2 _positionDelta;
  223. private BitmapFontGlyph _currentGlyph;
  224. private BitmapFontGlyph? _previousGlyph;
  225. object IEnumerator.Current
  226. {
  227. get
  228. {
  229. // casting a struct to object will box it, behaviour we want to avoid...
  230. throw new InvalidOperationException();
  231. }
  232. }
  233. public BitmapFontGlyph Current => _currentGlyph;
  234. public StringBuilderGlyphEnumerator(BitmapFont font, StringBuilder text, Vector2? position)
  235. {
  236. _font = font;
  237. _text = text;
  238. _index = -1;
  239. _position = position ?? new Vector2();
  240. _positionDelta = new Vector2();
  241. _currentGlyph = new BitmapFontGlyph();
  242. _previousGlyph = null;
  243. }
  244. public bool MoveNext()
  245. {
  246. if (++_index >= _text.Length)
  247. return false;
  248. var character = GetUnicodeCodePoint(_text, ref _index);
  249. _currentGlyph = new BitmapFontGlyph
  250. {
  251. CharacterID = character,
  252. Character = _font.GetCharacter(character),
  253. Position = _position + _positionDelta
  254. };
  255. if (_currentGlyph.Character != null)
  256. {
  257. _currentGlyph.Position.X += _currentGlyph.Character.XOffset;
  258. _currentGlyph.Position.Y += _currentGlyph.Character.YOffset;
  259. _positionDelta.X += _currentGlyph.Character.XAdvance + _font.LetterSpacing;
  260. }
  261. if (_font.UseKernings && _previousGlyph.HasValue && _previousGlyph.Value.Character != null)
  262. {
  263. int amount;
  264. if (_previousGlyph.Value.Character.Kernings.TryGetValue(character, out amount))
  265. {
  266. _positionDelta.X += amount;
  267. _currentGlyph.Position.X += amount;
  268. }
  269. }
  270. _previousGlyph = _currentGlyph;
  271. if (character != '\n')
  272. return true;
  273. _positionDelta.Y += _font.LineHeight;
  274. _positionDelta.X = _position.X;
  275. _previousGlyph = null;
  276. return true;
  277. }
  278. private static int GetUnicodeCodePoint(StringBuilder text, ref int index)
  279. {
  280. return char.IsHighSurrogate(text[index]) && ++index < text.Length
  281. ? char.ConvertToUtf32(text[index - 1], text[index])
  282. : text[index];
  283. }
  284. public void Dispose()
  285. {
  286. }
  287. public void Reset()
  288. {
  289. _positionDelta = new Vector2();
  290. _index = -1;
  291. _previousGlyph = null;
  292. }
  293. }
  294. /// <inheritdoc/>
  295. public override string ToString() => $"{Face}";
  296. public static BitmapFont FromFile(GraphicsDevice graphicsDevice, string path)
  297. {
  298. using Stream stream = TitleContainer.OpenStream(path);
  299. return FromStream(graphicsDevice, stream, path);
  300. }
  301. [Obsolete("Use the FromStream() overload that takes an explicit name.")]
  302. public static BitmapFont FromStream(GraphicsDevice graphicsDevice, FileStream stream)
  303. {
  304. return FromStream(graphicsDevice, stream, stream.Name);
  305. }
  306. public static BitmapFont FromStream(GraphicsDevice graphicsDevice, Stream stream, string name)
  307. {
  308. var bmfFile = BitmapFontFileReader.Read(stream, name);
  309. // Load page textures
  310. Dictionary<string, Texture2D> pages = new Dictionary<string, Texture2D>();
  311. for (int i = 0; i < bmfFile.Pages.Count; i++)
  312. {
  313. if (!pages.ContainsKey(bmfFile.Pages[i]))
  314. {
  315. string texturePath = Path.Combine(Path.GetDirectoryName(bmfFile.Path), bmfFile.Pages[i]);
  316. using (Stream textureStream = TitleContainer.OpenStream(texturePath))
  317. {
  318. Texture2D texture = Texture2D.FromStream(graphicsDevice, textureStream);
  319. pages.Add(bmfFile.Pages[i], texture);
  320. }
  321. }
  322. }
  323. // Load Characters
  324. Dictionary<int, BitmapFontCharacter> characters = new Dictionary<int, BitmapFontCharacter>();
  325. for (int i = 0; i < bmfFile.Characters.Count; i++)
  326. {
  327. var charBlock = bmfFile.Characters[i];
  328. Texture2D texture = pages[bmfFile.Pages[charBlock.Page]];
  329. Texture2DRegion region = new Texture2DRegion(texture, charBlock.X, charBlock.Y, charBlock.Width, charBlock.Height);
  330. BitmapFontCharacter character = new BitmapFontCharacter((int)charBlock.ID, region, charBlock.XOffset, charBlock.YOffset, charBlock.XAdvance);
  331. characters.Add(character.Character, character);
  332. }
  333. // Load kernings
  334. for (int i = 0; i < bmfFile.Kernings.Count; i++)
  335. {
  336. var kerningBlock = bmfFile.Kernings[i];
  337. if (characters.TryGetValue((int)kerningBlock.First, out BitmapFontCharacter character))
  338. {
  339. character.Kernings.Add((int)kerningBlock.Second, kerningBlock.Amount);
  340. }
  341. }
  342. return new BitmapFont(bmfFile.FontName, bmfFile.Info.FontSize, bmfFile.Common.LineHeight, characters.Values);
  343. }
  344. }