using Annulus; using Azimuth; using Blacklight.Core; using Blacklight.Core.Drawables; using System.Collections.Generic; using UnitTestSharp; using DrawingPoint = System.Drawing.Point; using DrawingSize = System.Drawing.Size; namespace Glass.Blacklight.UnitTests { public class FontRendererTests : TestFixture { public class CapturedGlyphs : EntityContainer.IVisitor { public readonly List Sprites = new List(); void EntityContainer.IVisitor.Visit(in Entity entity) { if (entity.drawable is Sprite sprite) { Sprites.Add(sprite); } } } public class SizedTexture : Texture { public SizedTexture(DrawingSize size) { Size = size; } } /// /// A font whose every glyph is an 8x8 square, and whose margins therefore work out to 16 horizontal and 8 /// vertical. /// public static SpriteFontDefinition EightPixelFont() { var font = new SpriteFontDefinition { LineHeight = 32, Baseline = 24 }; foreach (char glyph in "ab") { font.GlyphDefinitions[glyph] = new SpriteFontDefinition.GlyphDefinition { Size = new DrawingPoint(8, 8), XAdvance = 8, }; } return font; } /// /// EightPixelFont, plus an 8x8 placeholder for whatever it lacks. /// public static SpriteFontDefinition EightPixelFontWithPlaceholder() { SpriteFontDefinition font = EightPixelFont(); font.InvalidGlyph = new SpriteFontDefinition.GlyphDefinition { Size = new DrawingPoint(8, 8), XAdvance = 8, }; return font; } /// /// EightPixelFontWithPlaceholder, plus a narrow space, so a tab drawn as a space is distinguishable from /// one drawn as the placeholder by where the next glyph lands. /// public static SpriteFontDefinition EightPixelFontWithSpace() { SpriteFontDefinition font = EightPixelFontWithPlaceholder(); font.GlyphDefinitions[' '] = new SpriteFontDefinition.GlyphDefinition { Size = new DrawingPoint(0, 0), XAdvance = 4, }; return font; } public static List Draw(Rect bounds, SpriteFontDefinition font, StringView text) { var renderer = new FontRenderer { FontTexture = new SizedTexture(new DrawingSize(64, 64)) }; var material = new TextMaterial { Font = font, Text = text }; var cel = new Cel(); renderer.Render(cel, material, bounds, new DrawState()); var captured = new CapturedGlyphs(); cel.ForEach(captured); return captured.Sprites; } public static List Draw(Rect bounds) => Draw(bounds, EightPixelFont(), "ab"); public static Vector TopLeftOf(Sprite sprite) => sprite.Destination(sprite.Source).min; public class RenderTests : TestFixture { public void RoomForTheText_PlacesTheGlyphsInsideTheMargins() { List glyphs = Draw(Rect.FromPositionAndSize(Vector.Zero, new Vector(100, 100))); CheckEqual(2, glyphs.Count); CheckEqual(new Vector(16, 8), TopLeftOf(glyphs[0])); CheckEqual(new Vector(24, 8), TopLeftOf(glyphs[1])); } public void NarrowerThanItsHorizontalMargins_WrapsToOneGlyphPerLineDownTheMiddle() { List glyphs = Draw(Rect.FromPositionAndSize(Vector.Zero, new Vector(20, 100))); CheckEqual(2, glyphs.Count); CheckEqual(new Vector(10, 8), TopLeftOf(glyphs[0])); CheckEqual(new Vector(10, 40), TopLeftOf(glyphs[1])); } public void MissingGlyph_DrawsThePlaceholderAdvancedByItsOwnWidth() { List glyphs = Draw(Rect.FromPositionAndSize(Vector.Zero, new Vector(100, 100)), EightPixelFontWithPlaceholder(), "axb"); CheckEqual(3, glyphs.Count); CheckEqual(new Vector(16, 8), TopLeftOf(glyphs[0])); CheckEqual(new Vector(24, 8), TopLeftOf(glyphs[1])); CheckEqual(new Vector(32, 8), TopLeftOf(glyphs[2])); } public void Tab_DrawsAsASpaceRatherThanThePlaceholder() { List glyphs = Draw(Rect.FromPositionAndSize(Vector.Zero, new Vector(100, 100)), EightPixelFontWithSpace(), "a\tb"); CheckEqual(3, glyphs.Count); CheckEqual(new Vector(16, 8), TopLeftOf(glyphs[0])); CheckEqual(new Vector(24, 8), TopLeftOf(glyphs[1])); // The placeholder would have advanced 8 and put this at 32. CheckEqual(new Vector(28, 8), TopLeftOf(glyphs[2])); } public void MissingGlyphWithNoPlaceholder_DrawsNothingAndTakesNoWidth() { List glyphs = Draw(Rect.FromPositionAndSize(Vector.Zero, new Vector(100, 100)), EightPixelFont(), "axb"); CheckEqual(2, glyphs.Count); CheckEqual(new Vector(16, 8), TopLeftOf(glyphs[0])); CheckEqual(new Vector(24, 8), TopLeftOf(glyphs[1])); } } public class PageResourceNameTests : TestFixture { public void NamespacedDefinition_PlacesThePageBesideIt() { CheckEqual("Glass.Testbed.SampleMaterials.Philosopher-Regular_0.png", FontRenderer.PageResourceName( "Glass.Testbed.SampleMaterials.Philosopher-Regular.fnt", "Philosopher-Regular_0.png")); } public void TwoFontsSharingAFolder_ResolveToTheirOwnPages() { CheckEqual("Game.Fonts.LiberationSans-Regular_0.png", FontRenderer.PageResourceName( "Game.Fonts.LiberationSans-Regular.fnt", "LiberationSans-Regular_0.png")); CheckEqual("Game.Fonts.Philosopher-Regular_0.png", FontRenderer.PageResourceName( "Game.Fonts.Philosopher-Regular.fnt", "Philosopher-Regular_0.png")); } public void DefinitionWithNoFolderPrefix_ReturnsThePageAlone() { CheckEqual("Philosopher-Regular_0.png", FontRenderer.PageResourceName("Philosopher-Regular.fnt", "Philosopher-Regular_0.png")); } public void DefinitionWithNoExtension_ReturnsThePageAlone() { CheckEqual("page_0.png", FontRenderer.PageResourceName("Philosopher", "page_0.png")); } public void DefinitionThatIsNothingButAnExtension_ReturnsThePageAlone() { CheckEqual("page_0.png", FontRenderer.PageResourceName(".fnt", "page_0.png")); } public void DottedFontFileName_TreatsOnlyTheLastNameAndExtensionAsTheFileName() { CheckEqual("Game.Fonts.Philosopher.Philosopher-Regular_0.png", FontRenderer.PageResourceName( "Game.Fonts.Philosopher.Regular.fnt", "Philosopher-Regular_0.png")); } } } }