/// <summary> /// Render a string from an atlas. /// </summary> /// <param name="position">The top left position of where to start drawing the string.</param> /// <param name="color">The text color.</param> /// <param name="text">The text itself.</param> /// <param name="atlas">The font atlas to use.</param> /// <param name="layouter">The layouter to use.</param> /// <param name="effect">Effect to apply</param> /// <param name="effectAmount">The effect amount.</param> /// <param name="effectColor">The effect color.</param> public void RenderString( Vector3 position, Color color, string text, DrawableFontAtlas atlas, TextLayouter layouter = null, FontEffect effect = FontEffect.None, float effectAmount = 0f, Color?effectColor = null) { layouter ??= new TextLayouter(atlas); atlas.SetupDrawing(this, text, effect, effectAmount, effectColor); var reUsableVector = new Vector3(); foreach (char c in text) { Vector2 gPos = layouter.AddLetter(c, out DrawableGlyph g); if (g == null || g.GlyphUV == Rectangle.Empty) { continue; } reUsableVector.X = gPos.X; reUsableVector.Y = gPos.Y; atlas.DrawGlyph(this, g, position + reUsableVector, color); } atlas.FinishDrawing(this); }
public static unsafe void CompareMetricsWithStb(Font f, DrawableFontAtlas atlas, StbTrueType.stbtt_fontinfo stbFont) { var pc = new StbTrueType.stbtt_pack_context(); StbTrueType.stbtt_PackBegin(pc, (byte *)0, 512, 512, 512, 1, null); var cd = new StbTrueType.stbtt_packedchar[f.LastCharIndex + 1]; StbTrueType.stbrp_rect[] rects; fixed(StbTrueType.stbtt_packedchar *charDataPtr = &cd[0]) { var range = new StbTrueType.stbtt_pack_range { first_unicode_codepoint_in_range = 0, array_of_unicode_codepoints = null, num_chars = (int)f.LastCharIndex + 1, chardata_for_range = charDataPtr, font_size = -atlas.FontSize }; rects = new StbTrueType.stbrp_rect[f.LastCharIndex + 1]; fixed(StbTrueType.stbrp_rect *rectPtr = &rects[0]) { int n = StbTrueType.stbtt_PackFontRangesGatherRects(pc, stbFont, &range, 1, rectPtr); StbTrueType.stbtt_PackFontRangesPackRects(pc, rectPtr, n); } } foreach ((char charIndex, DrawableGlyph atlasGlyph) in atlas.Glyphs) { FontGlyph glyph = atlasGlyph.FontGlyph; var advance = 0; var bearing = 0; StbTrueType.stbtt_GetCodepointHMetrics(stbFont, charIndex, &advance, &bearing); Assert.True(advance == glyph.AdvanceWidth); Assert.True(bearing == glyph.LeftSideBearing || glyph.LeftSideBearing == 0); // stb has junk data beyond valid var minX = 0; var maxX = 0; var minY = 0; var maxY = 0; StbTrueType.stbtt_GetCodepointBitmapBoxSubpixel(stbFont, charIndex, atlas.RenderScale, atlas.RenderScale, 0, 0, &minX, &minY, &maxX, &maxY); Rectangle bbox = glyph.GetBBox(atlas.RenderScale); Assert.Equal(minX, bbox.X); Assert.Equal(minY, bbox.Y); Assert.Equal(maxX, bbox.Width); Assert.Equal(maxY, bbox.Height); Rectangle drawBox = new Rectangle(0, 0, bbox.Width - bbox.X, bbox.Height - bbox.Y); drawBox.Size += Vector2.One; // Add padding from stb StbTrueType.stbrp_rect rect = rects[charIndex]; Assert.Equal(rect.w, drawBox.Width); Assert.Equal(rect.h, drawBox.Height); } }
/// <summary> /// Render a string from an atlas. /// </summary> /// <param name="position">The top left position of where to start drawing the string.</param> /// <param name="color">The text color.</param> /// <param name="text">The text itself.</param> /// <param name="atlas">The font atlas to use.</param> public void RenderString(Vector3 position, Color color, string text, DrawableFontAtlas atlas) { if (atlas?.Atlas?.Glyphs == null) { return; } RenderString(position, color, text, atlas, new TextLayouter(atlas.Atlas)); }
public void Load() { // Load assets _ubuntuFontAsset = Engine.AssetLoader.Get <FontAsset>(RESOURCE_NAME_FONT_UBUNTU).GetAtlas(12); _tileTexture = Engine.AssetLoader.Get <TextureAsset>(RESOURCE_NAME_TEXTURE_TILE_OUTLINED_CARDINAL); _shader = Engine.AssetLoader.Get <ShaderAsset>(RESOURCE_NAME_SHADER); // tile count is vertexCount-1, because end vertices are included in only 1 tile (per axis). tiles = new Quadrilateral[MAP_WIDTH - 1, MAP_HEIGHT - 1]; // Generate a random-ish height map. for (var x = 0; x < MAP_WIDTH; x++) { for (var y = 0; y < MAP_HEIGHT; y++) { // Assign in height map @ appropriate position, storing for later use _heightMap[x, y] = _random.Next((int)TileSize.Z); } } for (var y = 0; y < MAP_HEIGHT - 1; y++) { for (var x = 0; x < MAP_WIDTH - 1; x++) { // Assign in height map @ appropriate position, storing for later use var quad = new Quadrilateral( Vector3.Transform(new Vector3(x * TileSize.X, y * TileSize.Y, _heightMap[x, y]), _rotationMatrix), Vector3.Transform(new Vector3((x + 1) * TileSize.X, y * TileSize.Y, _heightMap[x + 1, y]), _rotationMatrix), Vector3.Transform(new Vector3((x + 1) * TileSize.X, (y + 1) * TileSize.Y, _heightMap[x + 1, y + 1]), _rotationMatrix), Vector3.Transform(new Vector3(x * TileSize.X, (y + 1) * TileSize.Y, _heightMap[x, y + 1]), _rotationMatrix) ); tiles[x, y] = quad; } } Rectangle boundsOfMap = Rectangle.BoundsFromPolygonPoints(new[] { tiles[0, 0].Vertex0.ToVec2(), tiles[MAP_WIDTH - 2, 0].Vertex1.ToVec2(), tiles[MAP_WIDTH - 2, MAP_HEIGHT - 2].Vertex2.ToVec2(), tiles[0, MAP_HEIGHT - 2].Vertex3.ToVec2(), }); quadTree = new QuadTree <Quadrilateral>(boundsOfMap, 100); foreach (var tile in tiles) { quadTree.Add(tile); } }
/// <summary> /// Render a string from an atlas. /// </summary> /// <param name="position">The top left position of where to start drawing the string.</param> /// <param name="color">The text color.</param> /// <param name="text">The text itself.</param> /// <param name="atlas">The font atlas to use.</param> /// <param name="layouter">The layouter to use.</param> public void RenderString(Vector3 position, Color color, string text, DrawableFontAtlas atlas, TextLayouter layouter) { if (atlas?.Atlas?.Glyphs == null) { return; } position = position.RoundClosest(); foreach (char c in text) { Vector2 gPos = layouter.AddLetter(c, out AtlasGlyph g); if (g == null) { continue; } var uv = new Rectangle(g.Location, g.UV); RenderSprite(new Vector3(position.X + gPos.X, position.Y + gPos.Y, position.Z), g.Size, color, atlas.Texture, uv); } }
protected override void RenderContent(RenderComposer composer) { Vector2 offset = ImGui.GetWindowPos(); offset.Y += ImGui.GetWindowHeight(); _lastOffset = offset; composer.RenderSprite(new Vector3(offset, 0), _anim.Texture.Size * _parent.Scale, Color.White, _anim.Texture); for (var i = 0; i < _anim.Frames.Length; i++) { composer.RenderOutline(new Vector3(offset + _anim.Frames[i].Position * _parent.Scale, 1), _anim.Frames[i].Size * _parent.Scale, _holdingIdx == i ? Color.Green : Color.Red); var stringPos = new Vector3(offset + _anim.Frames[i].Position * _parent.Scale, 1); DrawableFontAtlas atlas = _font.GetAtlas(15 * _parent.Scale); composer.RenderString(stringPos + new Vector3(1), Color.Black, i.ToString(), atlas); composer.RenderString(stringPos, Color.Red, i.ToString(), atlas); } ImGui.Text(_holdingIdx == -1 ? "Click on a rectangle to change it's position." : $"Select new position for frame {_holdingIdx}!"); }
public override void Draw(RenderComposer composer) { DrawableFontAtlas atlas = _font.GetAtlas(30); var l = new TextLayouter(atlas.Atlas); string text; if (!_myPaddle.Ready) { text = "Press 'Space' when ready!"; } else if (_pad1 == _myPaddle && !_pad2.Ready || _pad2 == _myPaddle && !_pad1.Ready) { text = "Waiting for other player."; } else { text = $"{_pad1.Score}/{_pad2.Score}"; } Vector2 textSize = l.MeasureString(text); float screenHorizontalCenter = Engine.Configuration.RenderSize.X / 2; composer.RenderString(new Vector3(screenHorizontalCenter - textSize.X / 2, 10, 0), Color.White, text, atlas); composer.RenderSprite(_pad1.VisualPosition, _pad1.Size, _pad1.Ready ? Color.White : Color.Red); composer.RenderSprite(_pad2.VisualPosition, _pad2.Size, _pad2.Ready ? Color.White : Color.Red); // Uncomment to view paddle collisions. //LineSegment[] padCol = _pad2.GetPaddleCollision(); //for (var i = 0; i < padCol.Length; i++) //{ // composer.RenderLine(ref padCol[i], Color.Red); //} NetworkTransform ball = IdToObject["Ball"]; composer.RenderSprite(ball.VisualPosition, ball.Size, Color.White); NetworkTransform upperWall = IdToObject["UpperWall"]; NetworkTransform lowerWall = IdToObject["LowerWall"]; composer.RenderSprite(upperWall.VisualPosition, upperWall.Size, Color.White); composer.RenderSprite(lowerWall.VisualPosition, lowerWall.Size, Color.White); }
public static unsafe DrawableFontAtlas RenderFontStbPacked(byte[] ttf, float fontSize, Vector2 atlasSize, int numChars, Font f, out StbTrueType.stbtt_fontinfo fontInfo) { var atlasObj = new DrawableFontAtlas(f, fontSize); fontSize = atlasObj.FontSize; fontInfo = new StbTrueType.stbtt_fontinfo(); fixed(byte *ttPtr = &ttf[0]) { StbTrueType.stbtt_InitFont(fontInfo, ttPtr, 0); float scaleFactor = StbTrueType.stbtt_ScaleForMappingEmToPixels(fontInfo, fontSize); int ascent, descent, lineGap; StbTrueType.stbtt_GetFontVMetrics(fontInfo, &ascent, &descent, &lineGap); atlasSize *= 3; // Needs to be big as the packing sucks, and glyphs getting cut out messes with the tests. var pixels = new byte[(int)atlasSize.X * (int)atlasSize.Y]; var pc = new StbTrueType.stbtt_pack_context(); fixed(byte *pixelsPtr = pixels) { StbTrueType.stbtt_PackBegin(pc, pixelsPtr, (int)atlasSize.X, (int)atlasSize.Y, (int)atlasSize.X, 1, null); } var cd = new StbTrueType.stbtt_packedchar[numChars]; fixed(StbTrueType.stbtt_packedchar *charPtr = cd) { StbTrueType.stbtt_PackFontRange(pc, ttPtr, 0, -fontSize, 0, numChars, charPtr); } StbTrueType.stbtt_PackEnd(pc); for (var i = 0; i < cd.Length; ++i) { var atlasGlyph = DrawableGlyph.CreateForTest(cd[i].xadvance, cd[i].xoff, cd[i].x1 - cd[i].x0, cd[i].y1 - cd[i].y0); atlasGlyph.GlyphUV = new Rectangle(cd[i].x0, cd[i].y0, atlasGlyph.Width, atlasGlyph.Height); atlasObj.Glyphs[(char)i] = atlasGlyph; } } return(atlasObj); }
public TextLayouter(DrawableFontAtlas atlas) { SetAtlas(atlas); }
/// <summary> /// Set a new font atlas. /// </summary> /// <param name="atlas">The atlas to set.</param> public void SetAtlas(DrawableFontAtlas atlas) { Restart(); _atlas = atlas; _hasZeroGlyph = atlas.Glyphs.ContainsKey((char)0); }
protected override bool RenderInternal(RenderComposer c) { var open = true; ImGui.SetNextWindowPos(new Vector2(0, 20), ImGuiCond.Always); ImGui.SetNextWindowSize(c.CurrentTarget.Size - new Vector2(0, 20)); ImGui.Begin(Title, ref open, ImGuiWindowFlags.MenuBar | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove); RenderImGui(); ImGui.End(); Position = Vector3.Zero; Size = c.CurrentTarget.Size; if (!open) { Parent?.RemoveChild(this); return(false); } if (_textureFb?.ColorAttachment == null) { return(true); // Disposed or uninitialized fb } if (_currentAssetTexture == null) { return(true); } _textureFb.ColorAttachment.Smooth = false; c.RenderToAndClear(_textureFb); c.RenderSprite(Vector3.Zero, _currentAssetTexture.Size, _currentAssetTexture); // Render meta overlay on the spritesheet texture. if (_currentAsset != null) { AnimatedSprite currentFileContext = _currentAsset.Content !; SpriteAnimationFrameSource frameSource = currentFileContext.FrameSource; if (frameSource != null) { for (var i = 0; i < frameSource.GetFrameCount(); i++) { Rectangle frameUv = frameSource.GetFrameUV(i); c.RenderOutline(frameUv, _selectedFrame == i ? Color.Green : Color.Red); if (_showFrameIdx && frameSource is SpriteArrayFrameSource) { Vector3 stringPos = frameUv.Position.ToVec3(); DrawableFontAtlas atlas = _debugFont.GetAtlas(20); c.RenderString(stringPos + new Vector3(1), Color.Black, i.ToString(), atlas); c.RenderString(stringPos, Color.Red, i.ToString(), atlas); } } } } c.RenderTo(null); RenderAnimationPreview(c); return(true); }
public void VerifyFontRendering() { var fonts = new[] { "Junction-Bold.otf", // Cff "CaslonOS.otf", // Cff 2 (covers other cases) "1980XX.ttf", // Ttf "LatoWeb-Regular.ttf", // Composite "Junction-Bold.otf", // 14 font size "Junction-Bold.otf" // 11 font size }; var names = new[] { "Junction-Bold", "CaslonOS-Regular", "1980XX", "Lato Regular", "Junction-Bold", "Junction-Bold" }; var unitsPerEm = new[] { 1000, 1000, 1024, 2000, 1000, 1000 }; int[] descender = { -250, -360, -128, -426, -250, -250 }; var ascender = new[] { 750, 840, 682, 1974, 750, 750 }; var glyphs = new[] { 270, 279, 141, 2164, 270, 270 }; string[] cachedRender = { ResultDb.EmotionCffAtlas, "", ResultDb.EmotionTtAtlas, ResultDb.EmotionCompositeAtlas, "", "" }; int[] fontSizes = { 17, 17, 17, 17, 14, 11 }; FrameBuffer b = null; for (var i = 0; i < fonts.Length; i++) { Engine.Log.Info($"Running font {fonts[i]} ({i})...", TestRunnerLogger.TestRunnerSrc); ReadOnlyMemory <byte> data = Engine.AssetLoader.Get <OtherAsset>($"Fonts/{fonts[i]}")?.Content ?? ReadOnlyMemory <byte> .Empty; var f = new Font(data); // Verify basic font data. Assert.True(f.Valid); Assert.True(f.FullName == names[i]); Assert.True(f.UnitsPerEm == unitsPerEm[i]); Assert.True(f.Descender == descender[i]); Assert.True(f.Ascender == ascender[i]); Assert.True(f.CharToGlyph.Count == glyphs[i]); // Get atlases. int fontSize = fontSizes[i]; var emotionAtlas = new StbDrawableFontAtlas(f, fontSize, false); Runner.ExecuteAsLoop(_ => { var str = ""; for (uint j = emotionAtlas.Font.FirstCharIndex; j < emotionAtlas.Font.LastCharIndex; j++) { str += (char)j; } emotionAtlas.CacheGlyphs(str); }).WaitOne(); DrawableFontAtlas packedStbAtlas = RenderFontStbPacked(data.ToArray(), fontSize, emotionAtlas.Texture.Size * 3, (int)f.LastCharIndex + 1, f, out StbTrueType.stbtt_fontinfo stbFont); // Compare glyph parsing. CompareMetricsWithStb(f, emotionAtlas, stbFont); // Compare render metrics. foreach (KeyValuePair <char, DrawableGlyph> g in emotionAtlas.Glyphs) { DrawableGlyph glyph = packedStbAtlas.Glyphs[g.Key]; Assert.Equal(glyph.XAdvance, g.Value.XAdvance); var fontGlyph = g.Value.FontGlyph; int width = (int)(MathF.Ceiling(fontGlyph.Max.X * emotionAtlas.RenderScale) - MathF.Floor(fontGlyph.Min.X * emotionAtlas.RenderScale)); int height = (int)(MathF.Ceiling(-fontGlyph.Min.Y * emotionAtlas.RenderScale) - MathF.Floor(-fontGlyph.Max.Y * emotionAtlas.RenderScale)); Assert.Equal(glyph.Width, width); Assert.Equal(glyph.Height, height); } // Check if there's a verified render. if (string.IsNullOrEmpty(cachedRender[i])) { continue; } // Compare with cached render. // ReSharper disable AccessToModifiedClosure Runner.ExecuteAsLoop(_ => { if (b == null) { b = new FrameBuffer(emotionAtlas.Texture.Size).WithColor(); } else { b.Resize(emotionAtlas.Texture.Size, true); } RenderComposer composer = Engine.Renderer.StartFrame(); composer.RenderToAndClear(b); composer.RenderSprite(Vector3.Zero, emotionAtlas.Texture.Size, Color.White, emotionAtlas.Texture); composer.RenderTo(null); Engine.Renderer.EndFrame(); Runner.VerifyScreenshot(cachedRender[i], b); }).WaitOne(); } }
public TextLayouterWrap(DrawableFontAtlas atlas) : base(atlas) { }