diff --git a/Demos/Sharpie.Demos.Events/Program.cs b/Demos/Sharpie.Demos.Events/Program.cs index 794474f..cf8927e 100644 --- a/Demos/Sharpie.Demos.Events/Program.cs +++ b/Demos/Sharpie.Demos.Events/Program.cs @@ -35,7 +35,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE [assembly: ExcludeFromCodeCoverage] // Create the terminal instance without any non-standard settings. -using var terminal = new Terminal(CursesBackend.Load(), new(UseStandardKeySequenceResolvers: false)); +using var terminal = new Terminal(CursesBackend.Load(), new(UseStandardKeySequenceResolvers: true)); // Set the main screen attributes for text and drawings. terminal.Screen.ColorMixture = terminal.Colors.MixColors(StandardColor.Green, StandardColor.Blue); diff --git a/Demos/Sharpie.Demos.Font/Program.cs b/Demos/Sharpie.Demos.Font/Program.cs index b975f86..2384d11 100644 --- a/Demos/Sharpie.Demos.Font/Program.cs +++ b/Demos/Sharpie.Demos.Font/Program.cs @@ -48,6 +48,8 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE }) .ToArray(); +var font = new DosCp866AsciiFont(); + // This method draws the given string and applies color starting with a specific shift. void DrawFunAsciiMessage(ITerminal t, string str, int colorShift) { @@ -56,7 +58,7 @@ void DrawFunAsciiMessage(ITerminal t, string str, int colorShift) foreach (var ch in str) { - var gl = new AsciiGlyph((byte) ch, styles[colorShift % styles.Length]); + var gl = font.GetGlyph(new(ch), styles[colorShift % styles.Length]); t.Screen.Draw(new(x, y), gl); x += gl.Size.Width; diff --git a/Sharpie.Tests/AsciiGlyphTests.cs b/Sharpie.Tests/DosCp866AsciiFontTests.cs similarity index 63% rename from Sharpie.Tests/AsciiGlyphTests.cs rename to Sharpie.Tests/DosCp866AsciiFontTests.cs index abf1d57..187b37e 100644 --- a/Sharpie.Tests/AsciiGlyphTests.cs +++ b/Sharpie.Tests/DosCp866AsciiFontTests.cs @@ -31,21 +31,31 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE namespace Sharpie.Tests; [TestClass] -public class AsciiGlyphTests +public class DosCp866AsciiFontTests { + private readonly DosCp866AsciiFont _font = new(); private readonly Style _style1 = new() { Attributes = VideoAttribute.Bold, ColorMixture = new() { Handle = 99 } }; + [TestMethod, DataRow(0, true), DataRow(255, true), DataRow(256, false)] + public void HasGlyph_ChecksIfGlyphInRange(int c, bool t) + { + _font.HasGlyph(new(c)) + .ShouldBe(t); + } + + [TestMethod] public void Name_ReturnsTheExpectedValue() { _font.Name.ShouldBe("CP866 Block Characters"); } + [TestMethod] - public void Size_Returns_4By4() + public void GetGlyph_ReturnsA4By4Drawable() { - var glyph = new AsciiGlyph((byte) 'A', _style1); + var glyph = _font.GetGlyph(new('A'), _style1); glyph.Size.ShouldBe(new(4, 4)); } [TestMethod] - public void DrawTo_DrawsTheExpectedGlyph() + public void GetGlyph_ReturnsTheExpectedGlyph_IfFound() { - var glyph = new AsciiGlyph((byte) 'A', _style1); + var glyph = _font.GetGlyph(new('A'), _style1); var contents = glyph.GetContents(); var cols = new[,] @@ -58,4 +68,21 @@ public void DrawTo_DrawsTheExpectedGlyph() contents.ShouldBe(cols); } + + [TestMethod] + public void GetGlyph_ReturnsTheDefault_IfNotFound() + { + var glyph = _font.GetGlyph(new(256), _style1); + var contents = glyph.GetContents(); + + var cols = new[,] + { + { (new('┌'), _style1), (new('│'), _style1), (new('│'), _style1), (new('└'), _style1) }, + { (new('─'), _style1), (new(' '), _style1), (new(' '), _style1), (new('─'), _style1) }, + { (new('─'), _style1), (new(' '), _style1), (new(' '), _style1), (new('─'), _style1) }, + { (new('┐'), _style1), (new('│'), _style1), (new('│'), _style1), (new Rune('┘'), _style1) } + }; + + contents.ShouldBe(cols); + } } diff --git a/Sharpie/Abstractions/IAsciiFont.cs b/Sharpie/Abstractions/IAsciiFont.cs new file mode 100644 index 0000000..2955073 --- /dev/null +++ b/Sharpie/Abstractions/IAsciiFont.cs @@ -0,0 +1,28 @@ +namespace Sharpie.Abstractions; + +/// +/// Defines the traits implemented by ASCII font providers. +/// +[PublicAPI] +public interface IAsciiFont +{ + /// + /// The font's name. + /// + string Name { get; } + + /// + /// Checks if the font contains a given glyph. + /// + /// The character. + /// true if the font contains the given glyph; false otherwise. + bool HasGlyph(Rune @char); + + /// + /// Tries to get a glyph for a given . + /// + /// The character. + /// The style to apply to the glyph. + /// The output glyph, if found. Otherwise, the font will substitute the glyph with something else. + IDrawable GetGlyph(Rune @char, Style style); +} diff --git a/Sharpie/AsciiGlyph.cs b/Sharpie/DosCp866AsciiFont.cs similarity index 82% rename from Sharpie/AsciiGlyph.cs rename to Sharpie/DosCp866AsciiFont.cs index 99e4567..871816e 100644 --- a/Sharpie/AsciiGlyph.cs +++ b/Sharpie/DosCp866AsciiFont.cs @@ -1,10 +1,10 @@ namespace Sharpie; /// -/// Allows for the drawing of large ASCII glyphs. +/// Base class implemented by ASCII font providers. /// [PublicAPI] -public sealed class AsciiGlyph: IDrawable +public sealed class DosCp866AsciiFont: IAsciiFont { private const int BitsPerLine = 8; private const int Lines = 8; @@ -15,9 +15,7 @@ public sealed class AsciiGlyph: IDrawable private static readonly IReadOnlyList Shapes; - private readonly Canvas _canvas; - - static AsciiGlyph() + static DosCp866AsciiFont() { Debug.Assert(Raw.Length % CharsPerByte == 0); var byteCount = Raw.Length / CharsPerByte; @@ -34,52 +32,57 @@ static AsciiGlyph() Shapes = glyphs.ToArray(); } - /// - /// Creates a new large ASCII glyph for a given . - /// - /// The character to obtain the glyph of. - /// The text style to use. - public AsciiGlyph(byte @char, Style style) + private static bool[,] ExtractGlyph(ReadOnlySpan str) { - _canvas = new(new(BitsPerLine / 2, Lines / 2)); - _canvas.Fill(new(new(0, 0), _canvas.Size), new Rune(ControlCharacter.Whitespace), style); - - var shape = Shapes[@char]; + Debug.Assert(str.Length == Lines * CharsPerByte); - for (var x = 0; x < BitsPerLine; x++) + var shape = new bool[8, 8]; + for (var y = 0; y < Lines; y++) { - for (var y = 0; y < Lines; y++) + var pb = byte.Parse(str.Slice(y * CharsPerByte, CharsPerByte), NumberStyles.HexNumber); + for (var x = 0; x < BitsPerLine; x++) { - var ap = new PointF(x / 2F, y / 2F); - if (shape[x, y]) - { - _canvas.Point(ap, style); - } + shape[7 - x, y] = (pb & (1 << x)) != 0; } } + + return shape; } - /// - public Size Size => _canvas.Size; + /// + public string Name => "CP866 Block Characters"; - /// - public void DrawOnto(IDrawSurface destination, Rectangle srcArea, Point destLocation) => - _canvas.DrawOnto(destination, srcArea, destLocation); + /// + public bool HasGlyph(Rune @char) => @char.Value >= 0 && @char.Value < Shapes.Count; - private static bool[,] ExtractGlyph(ReadOnlySpan str) + /// + public IDrawable GetGlyph(Rune @char, Style style) { - Debug.Assert(str.Length == Lines * CharsPerByte); - - var shape = new bool[8, 8]; - for (var y = 0; y < Lines; y++) + var canvas = new Canvas(new(BitsPerLine / 2, Lines / 2)); + var canvasRect = new Rectangle(new(0, 0), canvas.Size); + + canvas.Fill(canvasRect, new Rune(ControlCharacter.Whitespace), style); + + if (!HasGlyph(@char)) { - var pb = byte.Parse(str.Slice(y * CharsPerByte, CharsPerByte), NumberStyles.HexNumber); + canvas.Box(canvasRect, Canvas.LineStyle.Light, style); + } else + { + var shape = Shapes[@char.Value]; + for (var x = 0; x < BitsPerLine; x++) { - shape[7 - x, y] = (pb & (1 << x)) != 0; + for (var y = 0; y < Lines; y++) + { + var ap = new PointF(x / 2F, y / 2F); + if (shape[x, y]) + { + canvas.Point(ap, style); + } + } } } - return shape; + return canvas; } }