diff --git a/src/Extensions/Spectre.Console.ImageSharp/CanvasImage.cs b/src/Extensions/Spectre.Console.ImageSharp/CanvasImage.cs
index aba8b016..c4d36cc6 100644
--- a/src/Extensions/Spectre.Console.ImageSharp/CanvasImage.cs
+++ b/src/Extensions/Spectre.Console.ImageSharp/CanvasImage.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
@@ -33,6 +34,7 @@ public sealed class CanvasImage : Renderable
///
/// Gets or sets the render width of the canvas.
///
+ [Obsolete("Not used anymore. Will be removed in future update.")]
public int PixelWidth { get; set; } = 2;
///
@@ -73,27 +75,23 @@ public sealed class CanvasImage : Renderable
///
protected override Measurement Measure(RenderOptions options, int maxWidth)
{
- if (PixelWidth < 0)
- {
- throw new InvalidOperationException("Pixel width must be greater than zero.");
- }
-
+ var pixelWidth = options.Unicode ? 1 : 2;
var width = MaxWidth ?? Width;
- if (maxWidth < width * PixelWidth)
+ if (maxWidth < width * pixelWidth)
{
return new Measurement(maxWidth, maxWidth);
}
- return new Measurement(width * PixelWidth, width * PixelWidth);
+ return new Measurement(width * pixelWidth, width * pixelWidth);
}
///
protected override IEnumerable Render(RenderOptions options, int maxWidth)
{
var image = Image;
-
var width = Width;
var height = Height;
+ var pixelWidth = options.Unicode ? 1 : 2;
// Got a max width?
if (MaxWidth != null)
@@ -103,10 +101,10 @@ public sealed class CanvasImage : Renderable
}
// Exceed the max width when we take pixel width into account?
- if (width * PixelWidth > maxWidth)
+ if (width * pixelWidth > maxWidth)
{
- height = (int)(height * (maxWidth / (float)(width * PixelWidth)));
- width = maxWidth / PixelWidth;
+ height = (int)(height * (maxWidth / (float)(width * pixelWidth)));
+ width = maxWidth / pixelWidth;
}
// Need to rescale the pixel buffer?
@@ -120,7 +118,6 @@ public sealed class CanvasImage : Renderable
var canvas = new Canvas(width, height)
{
MaxWidth = MaxWidth,
- PixelWidth = PixelWidth,
Scale = false,
};
diff --git a/src/Extensions/Spectre.Console.ImageSharp/CanvasImageExtensions.cs b/src/Extensions/Spectre.Console.ImageSharp/CanvasImageExtensions.cs
index 0da36c36..88724357 100644
--- a/src/Extensions/Spectre.Console.ImageSharp/CanvasImageExtensions.cs
+++ b/src/Extensions/Spectre.Console.ImageSharp/CanvasImageExtensions.cs
@@ -47,6 +47,7 @@ public static class CanvasImageExtensions
/// The canvas image.
/// The pixel width.
/// The same instance so that multiple calls can be chained.
+ [Obsolete("Not used anymore. Will be removed in future update.")]
public static CanvasImage PixelWidth(this CanvasImage image, int width)
{
if (image is null)
diff --git a/src/Spectre.Console.Testing/Extensions/TestConsoleExtensions.cs b/src/Spectre.Console.Testing/Extensions/TestConsoleExtensions.cs
index ffe6a8b1..47f681e7 100644
--- a/src/Spectre.Console.Testing/Extensions/TestConsoleExtensions.cs
+++ b/src/Spectre.Console.Testing/Extensions/TestConsoleExtensions.cs
@@ -29,6 +29,18 @@ public static partial class TestConsoleExtensions
return console;
}
+ ///
+ /// Sets whether or not Unicode is supported.
+ ///
+ /// The console.
+ /// Whether or not Unicode is supported.
+ /// The same instance so that multiple calls can be chained.
+ public static TestConsole SupportsUnicode(this TestConsole console, bool enable)
+ {
+ console.Profile.Capabilities.Unicode = enable;
+ return console;
+ }
+
///
/// Makes the console interactive.
///
diff --git a/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render.Output.verified.txt b/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render.NonUnicode.verified.txt
similarity index 100%
rename from src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render.Output.verified.txt
rename to src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render.NonUnicode.verified.txt
diff --git a/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render.Unicode.verified.txt b/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render.Unicode.verified.txt
new file mode 100644
index 00000000..cd18c3cc
--- /dev/null
+++ b/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render.Unicode.verified.txt
@@ -0,0 +1,3 @@
+[91m▀[0m [32m▀[0m
+
+[94m▀[0m [93m▀[0m
diff --git a/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_MaxWidth.Output.verified.txt b/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_MaxWidth.NonUnicode.verified.txt
similarity index 100%
rename from src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_MaxWidth.Output.verified.txt
rename to src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_MaxWidth.NonUnicode.verified.txt
diff --git a/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_MaxWidth.Unicode.verified.txt b/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_MaxWidth.Unicode.verified.txt
new file mode 100644
index 00000000..8b5944e7
--- /dev/null
+++ b/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_MaxWidth.Unicode.verified.txt
@@ -0,0 +1,3 @@
+[96m▀[0m
+
+
diff --git a/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_NarrowTerminal.Output.verified.txt b/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_NarrowTerminal.NonUnicode.verified.txt
similarity index 100%
rename from src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_NarrowTerminal.Output.verified.txt
rename to src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_NarrowTerminal.NonUnicode.verified.txt
diff --git a/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_NarrowTerminal.Unicode.verified.txt b/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_NarrowTerminal.Unicode.verified.txt
new file mode 100644
index 00000000..8b5944e7
--- /dev/null
+++ b/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_NarrowTerminal.Unicode.verified.txt
@@ -0,0 +1,3 @@
+[96m▀[0m
+
+
diff --git a/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_Nested.Output.verified.txt b/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_Nested.NonUnicode.verified.txt
similarity index 100%
rename from src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_Nested.Output.verified.txt
rename to src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_Nested.NonUnicode.verified.txt
diff --git a/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_Nested.Unicode.verified.txt b/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_Nested.Unicode.verified.txt
new file mode 100644
index 00000000..de0fc78b
--- /dev/null
+++ b/src/Spectre.Console.Tests/Expectations/Widgets/Canvas/Render_Nested.Unicode.verified.txt
@@ -0,0 +1,3 @@
+┌────┐
+│ [96m▀[0m[90m▄[0m │
+└────┘
diff --git a/src/Spectre.Console.Tests/Unit/Widgets/CanvasTests.cs b/src/Spectre.Console.Tests/Unit/Widgets/CanvasTests.cs
index cd6891e2..bbad2676 100644
--- a/src/Spectre.Console.Tests/Unit/Widgets/CanvasTests.cs
+++ b/src/Spectre.Console.Tests/Unit/Widgets/CanvasTests.cs
@@ -28,13 +28,16 @@ public class CanvasTests
}
}
- [Fact]
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
[Expectation("Render")]
- public async Task Should_Render_Canvas_Correctly()
+ public async Task Should_Render_Canvas_Correctly(bool supportsUnicode)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
+ .SupportsUnicode(supportsUnicode)
.EmitAnsiSequences();
var canvas = new Canvas(width: 5, height: 5);
@@ -47,16 +50,21 @@ public class CanvasTests
console.Write(canvas);
// Then
- await Verifier.Verify(console.Output);
+ await Verifier
+ .Verify(console.Output)
+ .UseMethodName(supportsUnicode ? "Unicode" : "NonUnicode");
}
- [Fact]
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
[Expectation("Render_Nested")]
- public async Task Simple_Measure()
+ public async Task Simple_Measure(bool supportsUnicode)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
+ .SupportsUnicode(supportsUnicode)
.EmitAnsiSequences();
var panel = new Panel(new Canvas(width: 2, height: 2)
@@ -67,17 +75,22 @@ public class CanvasTests
console.Write(panel);
// Then
- await Verifier.Verify(console.Output);
+ await Verifier
+ .Verify(console.Output)
+ .UseMethodName(supportsUnicode ? "Unicode" : "NonUnicode");
}
- [Fact]
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
[Expectation("Render_NarrowTerminal")]
- public async Task Should_Scale_Down_Canvas_Is_Bigger_Than_Terminal()
+ public async Task Should_Scale_Down_Canvas_Is_Bigger_Than_Terminal(bool supportsUnicode)
{
// Given
var console = new TestConsole()
.Width(10)
.Colors(ColorSystem.Standard)
+ .SupportsUnicode(supportsUnicode)
.EmitAnsiSequences();
var canvas = new Canvas(width: 20, height: 10);
@@ -88,19 +101,27 @@ public class CanvasTests
console.Write(canvas);
// Then
- await Verifier.Verify(console.Output);
+ await Verifier
+ .Verify(console.Output)
+ .UseMethodName(supportsUnicode ? "Unicode" : "NonUnicode");
}
- [Fact]
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
[Expectation("Render_MaxWidth")]
- public async Task Should_Scale_Down_Canvas_If_MaxWidth_Is_Set()
+ public async Task Should_Scale_Down_Canvas_If_MaxWidth_Is_Set(bool supportsUnicode)
{
// Given
var console = new TestConsole()
.Colors(ColorSystem.Standard)
+ .SupportsUnicode(supportsUnicode)
.EmitAnsiSequences();
- var canvas = new Canvas(width: 20, height: 10) { MaxWidth = 10 };
+ var canvas = new Canvas(width: 20, height: 10)
+ {
+ MaxWidth = 10
+ };
canvas.SetPixel(0, 0, Color.Aqua);
canvas.SetPixel(19, 9, Color.Aqua);
@@ -108,16 +129,22 @@ public class CanvasTests
console.Write(canvas);
// Then
- await Verifier.Verify(console.Output);
+ await Verifier
+ .Verify(console.Output)
+ .UseMethodName(supportsUnicode ? "Unicode" : "NonUnicode");
}
- [Fact]
- public void Should_Not_Render_Canvas_If_Canvas_Cannot_Be_Scaled_Down()
+ [Theory]
+ [InlineData(true, 5)]
+ [InlineData(false, 10)]
+ public void Should_Not_Render_Canvas_If_Canvas_Cannot_Be_Scaled_Down(
+ bool supportsUnicode, int consoleWidth)
{
// Given
var console = new TestConsole()
- .Width(10)
+ .Width(consoleWidth)
.Colors(ColorSystem.Standard)
+ .SupportsUnicode(supportsUnicode)
.EmitAnsiSequences();
var canvas = new Canvas(width: 20, height: 2);
diff --git a/src/Spectre.Console/Widgets/Canvas.cs b/src/Spectre.Console/Widgets/Canvas.cs
index cdabca05..5106a4f2 100644
--- a/src/Spectre.Console/Widgets/Canvas.cs
+++ b/src/Spectre.Console/Widgets/Canvas.cs
@@ -7,6 +7,11 @@ public sealed class Canvas : Renderable
{
private readonly Color?[,] _pixels;
+ private const string Transparent = " ";
+ private const string DoubleTransparent = " ";
+ private const string UpperHalfBlock = "▀";
+ private const string LowerHalfBlock = "▄";
+
///
/// Gets the width of the canvas.
///
@@ -31,6 +36,7 @@ public sealed class Canvas : Renderable
///
/// Gets or sets the pixel width.
///
+ [Obsolete("Not used anymore. Will be removed in future update.")]
public int PixelWidth { get; set; } = 2;
///
@@ -72,31 +78,25 @@ public sealed class Canvas : Renderable
///
protected override Measurement Measure(RenderOptions options, int maxWidth)
{
- if (PixelWidth < 0)
- {
- throw new InvalidOperationException("Pixel width must be greater than zero.");
- }
-
+ var pixelWidth = options.Unicode ? 1 : 2;
var width = MaxWidth ?? Width;
- if (maxWidth < width * PixelWidth)
- {
- return new Measurement(maxWidth, maxWidth);
- }
-
- return new Measurement(width * PixelWidth, width * PixelWidth);
+ return maxWidth < width * pixelWidth
+ ? new Measurement(maxWidth, maxWidth)
+ : new Measurement(width * pixelWidth, width * pixelWidth);
}
///
protected override IEnumerable Render(RenderOptions options, int maxWidth)
{
- if (PixelWidth < 0)
- {
- throw new InvalidOperationException("Pixel width must be greater than zero.");
- }
+ return options.Unicode
+ ? RenderUnicode(maxWidth)
+ : RenderNonUnicode(maxWidth);
+ }
+ private IEnumerable RenderUnicode(int maxWidth)
+ {
var pixels = _pixels;
- var pixel = new string(' ', PixelWidth);
var width = Width;
var height = Height;
@@ -108,14 +108,86 @@ public sealed class Canvas : Renderable
}
// Exceed the max width when we take pixel width into account?
- if (width * PixelWidth > maxWidth)
+ if (width > maxWidth)
{
- height = (int)(height * (maxWidth / (float)(width * PixelWidth)));
- width = maxWidth / PixelWidth;
+ height = (int)(height * (maxWidth / (float)(width)));
+ width = maxWidth;
- // If it's not possible to scale the canvas sufficiently, it's too small to render.
if (height == 0)
{
+ // If it's not possible to scale the canvas sufficiently, it's too small to render.
+ yield break;
+ }
+ }
+
+ // Need to rescale the pixel buffer?
+ if (Scale && (width != Width || height != Height))
+ {
+ pixels = ScaleDown(width, height);
+ }
+
+ for (var y = 0; y < height; y += 2)
+ {
+ for (var x = 0; x < width; x++)
+ {
+ var upper = pixels[x, y];
+ var lower = y < height - 1 ? pixels[x, y + 1] : null;
+
+ if (upper == null && lower == null)
+ {
+ // None visible
+ yield return new Segment(Transparent);
+ }
+ else if (upper != null && lower != null)
+ {
+ // Both pixels visible
+ yield return new Segment(
+ UpperHalfBlock, new Style(
+ foreground: upper,
+ background: lower));
+ }
+ else if (upper != null)
+ {
+ // Upper visible
+ yield return new Segment(
+ UpperHalfBlock, new Style(
+ foreground: upper));
+ }
+ else if (lower != null)
+ {
+ // Lower visible
+ yield return new Segment(
+ LowerHalfBlock, new Style(
+ foreground: lower));
+ }
+ }
+
+ yield return Segment.LineBreak;
+ }
+ }
+
+ private IEnumerable RenderNonUnicode(int maxWidth)
+ {
+ var pixels = _pixels;
+ var width = Width;
+ var height = Height;
+
+ // Got a max width?
+ if (MaxWidth != null)
+ {
+ height = (int)(height * ((float)MaxWidth.Value) / Width);
+ width = MaxWidth.Value;
+ }
+
+ // Exceed the max width when we take pixel width into account?
+ if (width * 2 > maxWidth)
+ {
+ height = (int)(height * (maxWidth / (float)(width * 2)));
+ width = maxWidth / 2;
+
+ if (height == 0)
+ {
+ // If it's not possible to scale the canvas sufficiently, it's too small to render.
yield break;
}
}
@@ -133,11 +205,11 @@ public sealed class Canvas : Renderable
var color = pixels[x, y];
if (color != null)
{
- yield return new Segment(pixel, new Style(background: color));
+ yield return new Segment(DoubleTransparent, new Style(background: color));
}
else
{
- yield return new Segment(pixel);
+ yield return new Segment(DoubleTransparent);
}
}