diff --git a/.gitmodules b/.gitmodules index a9bee96..599a7e2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "GlitchyEngine\\vendor\\freetype"] path = GlitchyEngine\\vendor\\freetype url = https://github.com/aharabada/FreeType-beef.git +[submodule "GlitchyEngine/vendor/freetype"] + path = GlitchyEngine/vendor/freetype + url = https://github.com/aharabada/FreeType-beef.git diff --git a/GlitchyEngine/src/Renderer/Text/Font.bf b/GlitchyEngine/src/Renderer/Text/Font.bf index dabca53..51c0ff2 100644 --- a/GlitchyEngine/src/Renderer/Text/Font.bf +++ b/GlitchyEngine/src/Renderer/Text/Font.bf @@ -11,7 +11,6 @@ namespace GlitchyEngine.Renderer.Text { internal class GlyphDescriptor { - //public FT_Face Face; public Font Font; public FT_UInt GlyphIndex; @@ -32,13 +31,13 @@ namespace GlitchyEngine.Renderer.Text private Font _fallback ~ _?.ReleaseRef(); - private uint32 _fontSize; + internal uint32 _fontSize; private int32 _faceIndex; private bool _hasColor; private Int32_3 _penPos; private int32 _lastRowHeight; - private Texture2D _atlas ~ _?.ReleaseRef(); + internal Texture2D _atlas ~ _?.ReleaseRef(); private Int32_3 _atlasSize; private Dictionary _glyphs = new .() ~ DeleteDictionaryAndValues!(_); diff --git a/GlitchyEngine/src/Renderer/Text/FontRenderer.bf b/GlitchyEngine/src/Renderer/Text/FontRenderer.bf index 48717b8..0725cde 100644 --- a/GlitchyEngine/src/Renderer/Text/FontRenderer.bf +++ b/GlitchyEngine/src/Renderer/Text/FontRenderer.bf @@ -8,7 +8,7 @@ using internal GlitchyEngine.Renderer.Text; namespace GlitchyEngine.Renderer.Text { - public class FontRenderer + public static class FontRenderer { internal static FT_Library Library ~ FreeType.Done_FreeType(_); @@ -21,84 +21,77 @@ namespace GlitchyEngine.Renderer.Text } } - private GraphicsContext _context ~ _.ReleaseRef(); - - public this(GraphicsContext context) + static this() { FontRenderer.InitLibrary(); - - _context = context..AddRef(); } - public void DrawText(Renderer2D renderer, Font font, String text, float x, float y, Color fontColor = .White, Color bitmapColor = .White) + /** @brief Draws a given text using a specified font stack and renderer. + * @param renderer The 2D renderer that will be used to draw the text. + * @param font the Fontstack that will be used to draw the text. + * @param x The horizontal position of the text (i.e. distance between left side of viewport and the left side of the left-most character, well not really but close enough). + * @param y The vertical position of the text (i.e. distance between top side of viewport and the top side of the top-most character, well not really but close enough). + * @param fontColor The (default) color used for glyphs that don't have color (e.g. letters or emojies if the font doesn't use bitmaps for them). + * @param bitmapColor The (default) color used for glyphs that are bitmaps (e.g. emojies, if the font provies them as bitmap). + * @param fontSize The font size in pixels. If set to 0 the default size of the font will be used. Note: due to technical reasons the actual size might deviate from the specified size. + * @param lineGapOffset Can be used to manually increase or decrease the gap between lines. + */ + public static void DrawText(Renderer2D renderer, Font font, String text, float x, float y, Color fontColor = .White, Color bitmapColor = .White, float fontSize = 0, float lineGapOffset = 0) { if(text.IsWhiteSpace) return; - // Todo: float scale = 1.0f; + if(fontSize != 0) + { + scale = (float)fontSize / (float)font._fontSize; + } + // Space between two baselines - float linespace = ((font._face.size.metrics.ascender - font._face.size.metrics.descender) >> 6);// + lineGap; + float linespace = (((font._face.size.metrics.ascender - font._face.size.metrics.descender) / 64) + lineGapOffset) * scale; // The line we are writing on float baseline = y + linespace; - // Where we are writing the next glyph on the line + // Position of the next character on the line float penPosition = x; - int32 lastLine = 0; - int32 line = 0; + // how many lines we moved up or down (e.g. after a \n) + int movedLines = 0; List atlasses = scope .(); - //GlyphDescriptor missingGlyph = font.CharMap.GetValue('\0'); - String.UTF8Enumerator textEnumerator = String.UTF8Enumerator(text, 0, text.Length); - for(char32 char in textEnumerator) + // freetype identifies glyphs using utf32 (which makes sense), so we enumerate the text as char32 + for(char32 char in String.UTF8Enumerator(text, 0, text.Length)) { if(char == '\n') { - line++; + movedLines++; continue; } - // number of lines that the cursor moved up or down - int32 lineDiff = line - lastLine; - if(lineDiff != 0) + // move baseline if necessary + if(movedLines != 0) { - baseline += linespace * lineDiff; + // move baseline + baseline += linespace * movedLines; // TODO: make carriage return optional? - penPosition = x; + // return pen to start of line + penPosition = x; - lastLine = line; + movedLines = 0; } // Get glyph from Font var glyphDesc = font.GetGlyph(char); // This never happens - if(glyphDesc == null) - { - Debug.Break(); - continue; - } + Debug.Assert(glyphDesc != null); - // Rectangle on the screen - Vector4 viewportRect = .(penPosition + (glyphDesc.Metrics.horiBearingX / 64) * scale, baseline - (glyphDesc.Metrics.horiBearingY / 64) * scale, glyphDesc.SizeX * scale, glyphDesc.SizeY * scale); - // Rectangle on the font atlas - Vector4 texRect = .(glyphDesc.MapCoord.X, glyphDesc.MapCoord.Y, glyphDesc.SizeX, glyphDesc.SizeY); - - Color glyphColor = glyphDesc.IsBitmap ? bitmapColor : fontColor; - - /* - if(QueueQuad!(viewportRect, texRect, glyphDesc.MapZ, glyphColor)) - { - DrawQueue(context); - } - */ - - Texture2D atlas = glyphDesc.Font.[Friend]_atlas; + Texture2D atlas = glyphDesc.Font._atlas; + // Add reference to atlas in case it is recreated during rendering if(!atlasses.Contains(atlas)) { atlasses.Add(atlas..AddRef()); @@ -106,6 +99,18 @@ namespace GlitchyEngine.Renderer.Text Vector2 atlasSize = .(atlas.Width, atlas.Height); + // Rectangle on the screen + Vector4 viewportRect = .( + penPosition + (glyphDesc.Metrics.horiBearingX / 64) * scale, + baseline - (glyphDesc.Metrics.horiBearingY / 64) * scale, + glyphDesc.SizeX * scale, + glyphDesc.SizeY * scale); + + // Rectangle on the font atlas + Vector4 texRect = .(glyphDesc.MapCoord.X, glyphDesc.MapCoord.Y, glyphDesc.SizeX, glyphDesc.SizeY); + + Color glyphColor = glyphDesc.IsBitmap ? bitmapColor : fontColor; + texRect /= Vector4(atlasSize, atlasSize); renderer.Draw(atlas, viewportRect.X, viewportRect.Y, viewportRect.Z, viewportRect.W, glyphColor, 0.0f, texRect); @@ -114,7 +119,8 @@ namespace GlitchyEngine.Renderer.Text } renderer.End(); - + + // release all atlas textures for(int i < atlasses.Count) { atlasses[i].ReleaseRef(); diff --git a/GlitchyEngine/vendor/freetype b/GlitchyEngine/vendor/freetype new file mode 160000 index 0000000..8b28962 --- /dev/null +++ b/GlitchyEngine/vendor/freetype @@ -0,0 +1 @@ +Subproject commit 8b289627bd8b99388f128ddfca861996322c9487 diff --git a/Sandbox/src/SandboxApp2D.bf b/Sandbox/src/SandboxApp2D.bf index 184d945..543e33e 100644 --- a/Sandbox/src/SandboxApp2D.bf +++ b/Sandbox/src/SandboxApp2D.bf @@ -9,6 +9,7 @@ using GlitchyEngine.Renderer; using GlitchyEngine.Math; using GlitchyEngine.World; using GlitchyEngine.Renderer.Text; +using System.IO; namespace Sandbox { @@ -60,6 +61,8 @@ namespace Sandbox Texture2D _testTexture ~ _?.ReleaseRef(); + String testText ~ delete _; + [AllowAppend] public this() : base("Example") { @@ -119,16 +122,20 @@ namespace Sandbox _testTexture.SetData(&colors, 0, 1, 1, 1); fonty = new Font(_context, "C:\\Windows\\Fonts\\arial.ttf", 64, true, 'A', 16); + var japanese = new Font(_context, "C:\\Windows\\Fonts\\YuGothM.ttc", 64, true, '\0', 1); var emojis = new Font(_context, "C:\\Windows\\Fonts\\seguiemj.ttf", 64, true, 'πŸ˜‚' - 10, 1); - fonty.Fallback = emojis..ReleaseRefNoDelete(); + var mathstuff = new Font(_context, "C:\\Windows\\Fonts\\cambria.ttc", 64, true, 'Ξ±', 1); + fonty.Fallback = japanese..ReleaseRefNoDelete(); + japanese.Fallback = emojis..ReleaseRefNoDelete(); + emojis.Fallback = mathstuff..ReleaseRefNoDelete(); - fontRenderer = new FontRenderer(_context); + // Load test text + var result = File.ReadAllText("test.txt", testText = new String(), true); + //Log.EngineLogger.Assert(result) } Font fonty ~ _.ReleaseRef(); - FontRenderer fontRenderer ~ delete _; - VertexLayout layout ~ delete _; GeometryBinding quadBinding ~ _?.ReleaseRef(); @@ -304,7 +311,8 @@ namespace Sandbox _alphaBlendState.Bind(); Renderer2D.Begin(.FrontToBack, .(_context.SwapChain.Width, _context.SwapChain.Height)); - fontRenderer.DrawText(Renderer2D, fonty, "Hallo Welt, wie geht es dir? πŸ˜‚πŸ˜‚πŸ˜‚\nMir geht es gut, danke der Nachfrage, wie geht es dir?\nMir geht es hervorragend: Meine Engine kann endlich Text rendern\n und es scheint ganz in ordnung zu sein.", 0, 0); + // Hallo Welt, wie geht es dir? πŸ˜‚πŸ˜‚πŸ˜‚\nMir geht es gut, danke der Nachfrage. Wie geht es dir?\nMir geht es hervorragend und besser noch: meine Engine kann endlich Text rendern!πŸ’•β€\n und es scheint ganz in ordnung zu sein. + FontRenderer.DrawText(Renderer2D, fonty, testText, 0, 0, .White, .White, 32); Renderer2D.End(); } diff --git a/Sandbox/test.txt b/Sandbox/test.txt new file mode 100644 index 0000000..3744c27 --- /dev/null +++ b/Sandbox/test.txt @@ -0,0 +1,5 @@ +Hello World, how are you? πŸ˜‚πŸ˜‚πŸ˜‚ +Mir geht es gut, danke der Nachfrage. Wie geht es dir? +ι †θͺΏγ«ι€²γ‚“γ§γ„γΎγ™γ—γ€δ½•γ‚ˆγ‚Šγ‚‚γ‚¨γƒ³γ‚Έγƒ³γŒγ‚„γ£γ¨ζ–‡ε­—γ‚’θ‘¨η€Ίγ§γγ‚‹γ‚ˆγ†γ«γͺγ‚ŠγΎγ—γŸγ€‚ +Γ„Γ–ΓœΓΌΓΆΓ€ΓŸΓ‰Γ©Γ‘ΓΓ¨ΓˆΓ€Γ Γ’ΓŠπŸ’•β€ +Ξ»β‰™Ξ©β¨ΞΌΓ—βˆž