FontService.cs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. using System;
  2. using System.IO;
  3. using System.Runtime.InteropServices;
  4. using OpenTK;
  5. using OpenTK.Graphics.OpenGL;
  6. using SharpFont;
  7. namespace Ryujinx.Profiler.UI.SharpFontHelpers
  8. {
  9. public class FontService
  10. {
  11. private struct CharacterInfo
  12. {
  13. public float Left;
  14. public float Right;
  15. public float Top;
  16. public float Bottom;
  17. public int Width;
  18. public float Height;
  19. public float AspectRatio;
  20. public float BearingX;
  21. public float BearingY;
  22. public float Advance;
  23. }
  24. private const int SheetWidth = 1024;
  25. private const int SheetHeight = 512;
  26. private int ScreenWidth, ScreenHeight;
  27. private int CharacterTextureSheet;
  28. private CharacterInfo[] characters;
  29. public Color fontColor { get; set; } = Color.Black;
  30. private string GetFontPath()
  31. {
  32. string fontFolder = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
  33. // Only uses Arial, add more fonts here if wanted
  34. string path = Path.Combine(fontFolder, "arial.ttf");
  35. if (File.Exists(path))
  36. {
  37. return path;
  38. }
  39. throw new Exception($"Profiler exception. Required font Courier New or Arial not installed to {fontFolder}");
  40. }
  41. public void InitializeTextures()
  42. {
  43. // Create and init some vars
  44. uint[] rawCharacterSheet = new uint[SheetWidth * SheetHeight];
  45. int x;
  46. int y;
  47. int lineOffset;
  48. int maxHeight;
  49. x = y = lineOffset = maxHeight = 0;
  50. characters = new CharacterInfo[94];
  51. // Get font
  52. var font = new FontFace(File.OpenRead(GetFontPath()));
  53. // Update raw data for each character
  54. for (int i = 0; i < 94; i++)
  55. {
  56. var surface = RenderSurface((char)(i + 33), font, out float xBearing, out float yBearing, out float advance);
  57. characters[i] = UpdateTexture(surface, ref rawCharacterSheet, ref x, ref y, ref lineOffset);
  58. characters[i].BearingX = xBearing;
  59. characters[i].BearingY = yBearing;
  60. characters[i].Advance = advance;
  61. if (maxHeight < characters[i].Height)
  62. maxHeight = (int)characters[i].Height;
  63. }
  64. // Fix height for characters shorter than line height
  65. for (int i = 0; i < 94; i++)
  66. {
  67. characters[i].BearingX /= characters[i].Width;
  68. characters[i].BearingY /= maxHeight;
  69. characters[i].Advance /= characters[i].Width;
  70. characters[i].Height /= maxHeight;
  71. characters[i].AspectRatio = (float)characters[i].Width / maxHeight;
  72. }
  73. // Convert raw data into texture
  74. CharacterTextureSheet = GL.GenTexture();
  75. GL.BindTexture(TextureTarget.Texture2D, CharacterTextureSheet);
  76. GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
  77. GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
  78. GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Clamp);
  79. GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Clamp);
  80. GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, SheetWidth, SheetHeight, 0, PixelFormat.Rgba, PixelType.UnsignedInt8888, rawCharacterSheet);
  81. GL.BindTexture(TextureTarget.Texture2D, 0);
  82. }
  83. public void UpdateScreenHeight(int height)
  84. {
  85. ScreenHeight = height;
  86. }
  87. public float DrawText(string text, float x, float y, float height, bool draw = true)
  88. {
  89. float originalX = x;
  90. // Skip out of bounds draw
  91. if (y < height * -2 || y > ScreenHeight + height * 2)
  92. {
  93. draw = false;
  94. }
  95. if (draw)
  96. {
  97. // Use font map texture
  98. GL.BindTexture(TextureTarget.Texture2D, CharacterTextureSheet);
  99. // Enable blending and textures
  100. GL.Enable(EnableCap.Texture2D);
  101. GL.Enable(EnableCap.Blend);
  102. GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
  103. // Draw all characters
  104. GL.Begin(PrimitiveType.Triangles);
  105. GL.Color4(fontColor);
  106. }
  107. for (int i = 0; i < text.Length; i++)
  108. {
  109. if (text[i] == ' ')
  110. {
  111. x += height / 4;
  112. continue;
  113. }
  114. CharacterInfo charInfo = characters[text[i] - 33];
  115. float width = (charInfo.AspectRatio * height);
  116. x += (charInfo.BearingX * charInfo.AspectRatio) * width;
  117. float right = x + width;
  118. if (draw)
  119. {
  120. DrawChar(charInfo, x, right, y + height * (charInfo.Height - charInfo.BearingY), y - height * charInfo.BearingY);
  121. }
  122. x = right + charInfo.Advance * charInfo.AspectRatio + 1;
  123. }
  124. if (draw)
  125. {
  126. GL.End();
  127. // Cleanup for caller
  128. GL.BindTexture(TextureTarget.Texture2D, 0);
  129. GL.Disable(EnableCap.Texture2D);
  130. GL.Disable(EnableCap.Blend);
  131. }
  132. // Return width of rendered text
  133. return x - originalX;
  134. }
  135. private void DrawChar(CharacterInfo charInfo, float left, float right, float top, float bottom)
  136. {
  137. GL.TexCoord2(charInfo.Left, charInfo.Bottom); GL.Vertex2(left, bottom);
  138. GL.TexCoord2(charInfo.Left, charInfo.Top); GL.Vertex2(left, top);
  139. GL.TexCoord2(charInfo.Right, charInfo.Top); GL.Vertex2(right, top);
  140. GL.TexCoord2(charInfo.Right, charInfo.Top); GL.Vertex2(right, top);
  141. GL.TexCoord2(charInfo.Right, charInfo.Bottom); GL.Vertex2(right, bottom);
  142. GL.TexCoord2(charInfo.Left, charInfo.Bottom); GL.Vertex2(left, bottom);
  143. }
  144. public unsafe Surface RenderSurface(char c, FontFace font, out float xBearing, out float yBearing, out float advance)
  145. {
  146. var glyph = font.GetGlyph(c, 64);
  147. xBearing = glyph.HorizontalMetrics.Bearing.X;
  148. yBearing = glyph.RenderHeight - glyph.HorizontalMetrics.Bearing.Y;
  149. advance = glyph.HorizontalMetrics.Advance;
  150. var surface = new Surface
  151. {
  152. Bits = Marshal.AllocHGlobal(glyph.RenderWidth * glyph.RenderHeight),
  153. Width = glyph.RenderWidth,
  154. Height = glyph.RenderHeight,
  155. Pitch = glyph.RenderWidth
  156. };
  157. var stuff = (byte*)surface.Bits;
  158. for (int i = 0; i < surface.Width * surface.Height; i++)
  159. *stuff++ = 0;
  160. glyph.RenderTo(surface);
  161. return surface;
  162. }
  163. private CharacterInfo UpdateTexture(Surface surface, ref uint[] rawCharMap, ref int posX, ref int posY, ref int lineOffset)
  164. {
  165. int width = surface.Width;
  166. int height = surface.Height;
  167. int len = width * height;
  168. byte[] data = new byte[len];
  169. // Get character bitmap
  170. Marshal.Copy(surface.Bits, data, 0, len);
  171. // Find a slot
  172. if (posX + width > SheetWidth)
  173. {
  174. posX = 0;
  175. posY += lineOffset;
  176. lineOffset = 0;
  177. }
  178. // Update lineOffset
  179. if (lineOffset < height)
  180. {
  181. lineOffset = height + 1;
  182. }
  183. // Copy char to sheet
  184. for (int y = 0; y < height; y++)
  185. {
  186. int destOffset = (y + posY) * SheetWidth + posX;
  187. int sourceOffset = y * width;
  188. for (int x = 0; x < width; x++)
  189. {
  190. rawCharMap[destOffset + x] = (uint)((0xFFFFFF << 8) | data[sourceOffset + x]);
  191. }
  192. }
  193. // Generate character info
  194. CharacterInfo charInfo = new CharacterInfo()
  195. {
  196. Left = (float)posX / SheetWidth,
  197. Right = (float)(posX + width) / SheetWidth,
  198. Top = (float)(posY - 1) / SheetHeight,
  199. Bottom = (float)(posY + height) / SheetHeight,
  200. Width = width,
  201. Height = height,
  202. };
  203. // Update x
  204. posX += width + 1;
  205. // Give the memory back
  206. Marshal.FreeHGlobal(surface.Bits);
  207. return charInfo;
  208. }
  209. }
  210. }