FontSystem.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. using FontStashSharp.Interfaces;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Runtime.InteropServices;
  6. using System.Numerics;
  7. using Veldrid.FontStashSharp.Typography;
  8. using Typography.OpenFont;
  9. #if MONOGAME || FNA
  10. using Microsoft.Xna.Framework.Graphics;
  11. using Microsoft.Xna.Framework;
  12. #elif STRIDE
  13. using Stride.Core.Mathematics;
  14. using Stride.Graphics;
  15. using Texture2D = Stride.Graphics.Texture;
  16. #else
  17. using System.Drawing;
  18. using Texture2D = Veldrid.Texture;
  19. #endif
  20. namespace FontStashSharp
  21. {
  22. public class FontSystem : IDisposable
  23. {
  24. public const int GlyphPad = 2;
  25. private readonly List<IFontSource> _fontSources = new List<IFontSource>();
  26. private readonly Int32Map<DynamicSpriteFont> _fonts = new Int32Map<DynamicSpriteFont>();
  27. private readonly FontSystemSettings _settings;
  28. private FontAtlas _currentAtlas;
  29. public FontSystemEffect Effect => _settings.Effect;
  30. public int EffectAmount => _settings.EffectAmount;
  31. public int TextureWidth => _settings.TextureWidth;
  32. public int TextureHeight => _settings.TextureHeight;
  33. public bool PremultiplyAlpha => _settings.PremultiplyAlpha;
  34. public float FontResolutionFactor => _settings.FontResolutionFactor;
  35. public int KernelWidth => _settings.KernelWidth;
  36. public int KernelHeight => _settings.KernelHeight;
  37. public Texture2D ExistingTexture => _settings.ExistingTexture;
  38. public Rectangle ExistingTextureUsedSpace => _settings.ExistingTextureUsedSpace;
  39. public bool UseKernings { get; set; } = true;
  40. public int? DefaultCharacter { get; set; } = ' ';
  41. internal int BlurAmount => Effect == FontSystemEffect.Blurry ? EffectAmount : 0;
  42. internal int StrokeAmount => Effect == FontSystemEffect.Stroked ? EffectAmount : 0;
  43. internal List<IFontSource> FontSources => _fontSources;
  44. public List<FontAtlas> Atlases { get; } = new List<FontAtlas>();
  45. public event EventHandler CurrentAtlasFull;
  46. private readonly IFontLoader _fontLoader;
  47. public FontSystem(FontSystemSettings settings)
  48. {
  49. if (settings == null)
  50. {
  51. throw new ArgumentNullException(nameof(settings));
  52. }
  53. _settings = settings.Clone();
  54. if (_settings.FontLoader == null)
  55. {
  56. _fontLoader = new OpenFontLoader();
  57. }
  58. else
  59. {
  60. _fontLoader = _settings.FontLoader;
  61. }
  62. UseKernings = FontSystemDefaults.UseKernings;
  63. DefaultCharacter = FontSystemDefaults.DefaultCharacter;
  64. }
  65. public FontSystem() : this(new FontSystemSettings())
  66. {
  67. }
  68. public void Dispose()
  69. {
  70. if (_fontSources != null)
  71. {
  72. foreach (var font in _fontSources)
  73. font.Dispose();
  74. _fontSources.Clear();
  75. }
  76. Atlases?.Clear();
  77. _currentAtlas = null;
  78. _fonts.Clear();
  79. }
  80. public void AddFont(Typeface data)
  81. {
  82. var fontSource = _fontLoader.Load(data);
  83. _fontSources.Add(fontSource);
  84. }
  85. internal void AddFontSource(IEnumerable<IFontSource> sources)
  86. {
  87. _fontSources.AddRange(sources);
  88. }
  89. internal void AddFontSource(IFontSource fontSource)=>_fontSources.Add(fontSource);
  90. public DynamicSpriteFont GetFont(float fontSize)
  91. {
  92. var intSize = fontSize.FloatAsInt();
  93. DynamicSpriteFont result;
  94. if (_fonts.TryGetValue(intSize, out result))
  95. {
  96. return result;
  97. }
  98. if (_fontSources.Count == 0)
  99. {
  100. throw new Exception("Could not create a font without a single font source. Use AddFont to add at least one font source.");
  101. }
  102. var fontSource = _fontSources[0];
  103. double ascent, descent, lineHeight;
  104. fontSource.GetMetricsForSize(fontSize, out ascent, out descent, out lineHeight);
  105. result = new DynamicSpriteFont(this, fontSize, (int)lineHeight);
  106. _fonts[intSize] = result;
  107. return result;
  108. }
  109. public void Reset()
  110. {
  111. Atlases.Clear();
  112. _fonts.Clear();
  113. _currentAtlas = null;
  114. }
  115. internal int? GetCodepointIndex(int codepoint, out int fontSourceIndex)
  116. {
  117. fontSourceIndex = 0;
  118. var g = default(int?);
  119. for(var i = 0; i < _fontSources.Count; ++i)
  120. {
  121. var f = _fontSources[i];
  122. g = f.GetGlyphId(codepoint);
  123. if (g != null)
  124. {
  125. fontSourceIndex = i;
  126. break;
  127. }
  128. }
  129. return g;
  130. }
  131. #if MONOGAME || FNA || STRIDE
  132. private FontAtlas GetCurrentAtlas(GraphicsDevice device, int textureWidth, int textureHeight)
  133. #else
  134. private FontAtlas GetCurrentAtlas(ITexture2DManager device, int textureWidth, int textureHeight)
  135. #endif
  136. {
  137. if (_currentAtlas == null)
  138. {
  139. Texture2D existingTexture = null;
  140. if (ExistingTexture != null && Atlases.Count == 0)
  141. {
  142. existingTexture = ExistingTexture;
  143. }
  144. _currentAtlas = new FontAtlas(textureWidth, textureHeight, 256, existingTexture);
  145. // If existing texture is used, mark existing used rect as used
  146. if (existingTexture != null && !ExistingTextureUsedSpace.IsEmpty)
  147. {
  148. if (!_currentAtlas.AddSkylineLevel(0, ExistingTextureUsedSpace.X, ExistingTextureUsedSpace.Y, ExistingTextureUsedSpace.Width, ExistingTextureUsedSpace.Height))
  149. {
  150. throw new Exception(string.Format("Unable to specify existing texture used space: {0}", ExistingTextureUsedSpace));
  151. }
  152. // TODO: Clear remaining space
  153. }
  154. Atlases.Add(_currentAtlas);
  155. }
  156. return _currentAtlas;
  157. }
  158. #if MONOGAME || FNA || STRIDE
  159. internal void RenderGlyphOnAtlas(GraphicsDevice device, DynamicFontGlyph glyph)
  160. #else
  161. internal void RenderGlyphOnAtlas(ITexture2DManager device, DynamicFontGlyph glyph)
  162. #endif
  163. {
  164. var textureSize = new Size(TextureWidth, TextureHeight);
  165. if (ExistingTexture != null)
  166. {
  167. #if MONOGAME || FNA || STRIDE
  168. textureSize = new Point(ExistingTexture.Width, ExistingTexture.Height);
  169. #else
  170. textureSize = device.GetTextureSize(ExistingTexture);
  171. #endif
  172. }
  173. int gx = 0, gy = 0;
  174. var gw = glyph.Size.X + GlyphPad * 2;
  175. var gh = glyph.Size.Y + GlyphPad * 2;
  176. var currentAtlas = GetCurrentAtlas(device, textureSize.Width, textureSize.Height);
  177. if (!currentAtlas.AddRect(gw, gh, ref gx, ref gy))
  178. {
  179. CurrentAtlasFull?.Invoke(this, EventArgs.Empty);
  180. // This code will force creation of new atlas
  181. _currentAtlas = null;
  182. currentAtlas = GetCurrentAtlas(device, textureSize.Width, textureSize.Height);
  183. // Try to add again
  184. if (!currentAtlas.AddRect(gw, gh, ref gx, ref gy))
  185. {
  186. throw new Exception(string.Format("Could not add rect to the newly created atlas. gw={0}, gh={1}", gw, gh));
  187. }
  188. }
  189. glyph.TextureOffset.X = gx + GlyphPad;
  190. glyph.TextureOffset.Y = gy + GlyphPad;
  191. currentAtlas.RenderGlyph(device, glyph, FontSources[glyph.FontSourceIndex],
  192. BlurAmount, StrokeAmount, PremultiplyAlpha, KernelWidth, KernelHeight);
  193. glyph.Texture = currentAtlas.Texture;
  194. }
  195. }
  196. }