Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored the ASCII font management. #58

Merged
merged 2 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Demos/Sharpie.Demos.Events/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion Demos/Sharpie.Demos.Font/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[,]
Expand All @@ -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);
}
}
28 changes: 28 additions & 0 deletions Sharpie/Abstractions/IAsciiFont.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace Sharpie.Abstractions;

/// <summary>
/// Defines the traits implemented by ASCII font providers.
/// </summary>
[PublicAPI]
public interface IAsciiFont
{
/// <summary>
/// The font's name.
/// </summary>
string Name { get; }

/// <summary>
/// Checks if the font contains a given glyph.
/// </summary>
/// <param name="char">The character.</param>
/// <returns><c>true</c> if the font contains the given glyph; <c>false</c> otherwise.</returns>
bool HasGlyph(Rune @char);

/// <summary>
/// Tries to get a glyph for a given <paramref name="char"/>.
/// </summary>
/// <param name="char">The character.</param>
/// <param name="style">The style to apply to the glyph.</param>
/// <returns>The output glyph, if found. Otherwise, the font will substitute the glyph with something else.</returns>
IDrawable GetGlyph(Rune @char, Style style);
}
73 changes: 38 additions & 35 deletions Sharpie/AsciiGlyph.cs → Sharpie/DosCp866AsciiFont.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
namespace Sharpie;

/// <summary>
/// Allows for the drawing of large ASCII glyphs.
/// Base class implemented by ASCII font providers.
/// </summary>
[PublicAPI]
public sealed class AsciiGlyph: IDrawable
public sealed class DosCp866AsciiFont: IAsciiFont
{
private const int BitsPerLine = 8;
private const int Lines = 8;
Expand All @@ -15,9 +15,7 @@ public sealed class AsciiGlyph: IDrawable

private static readonly IReadOnlyList<bool[,]> Shapes;

private readonly Canvas _canvas;

static AsciiGlyph()
static DosCp866AsciiFont()
{
Debug.Assert(Raw.Length % CharsPerByte == 0);
var byteCount = Raw.Length / CharsPerByte;
Expand All @@ -34,52 +32,57 @@ static AsciiGlyph()
Shapes = glyphs.ToArray();
}

/// <summary>
/// Creates a new large ASCII glyph for a given <see cref="char" />.
/// </summary>
/// <param name="char">The character to obtain the glyph of.</param>
/// <param name="style">The text style to use.</param>
public AsciiGlyph(byte @char, Style style)
private static bool[,] ExtractGlyph(ReadOnlySpan<char> 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;
}

/// <inheritdoc cref="IDrawable.Size" />
public Size Size => _canvas.Size;
/// <inheritdoc cref="IAsciiFont.Name"/>
public string Name => "CP866 Block Characters";

/// <inheritdoc cref="IDrawable.DrawOnto" />
public void DrawOnto(IDrawSurface destination, Rectangle srcArea, Point destLocation) =>
_canvas.DrawOnto(destination, srcArea, destLocation);
/// <inheritdoc cref="IAsciiFont.HasGlyph"/>
public bool HasGlyph(Rune @char) => @char.Value >= 0 && @char.Value < Shapes.Count;

private static bool[,] ExtractGlyph(ReadOnlySpan<char> str)
/// <inheritdoc cref="IAsciiFont.GetGlyph"/>
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;
}
}