using System.Net;
using System.Net.Sockets;
using Inspectron.Epson;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace Inspectron.Epson.Tests;
///
/// Byte-level tests for ESC * bit-image printing. EpsonPrinter writes to a TCP
/// socket, so these point it at a loopback listener and assert on what actually
/// goes on the wire.
///
public class BitImagePrintingTests
{
private const byte ESC = 0x1B;
///
/// Runs an EpsonPrinter against a loopback listener and returns every byte it sent.
///
private static async Task CaptureAsync(Func work)
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
int port = ((IPEndPoint)listener.LocalEndpoint).Port;
var captured = new List();
var pump = Task.Run(async () =>
{
using var client = await listener.AcceptTcpClientAsync();
using var stream = client.GetStream();
var buffer = new byte[16384];
int read;
while ((read = await stream.ReadAsync(buffer)) > 0)
{
captured.AddRange(buffer.AsSpan(0, read).ToArray());
}
});
await using (var printer = new EpsonPrinter())
{
await printer.ConnectAsync("127.0.0.1", port);
await work(printer);
}
await pump;
listener.Stop();
return captured.ToArray();
}
private static int IndexOf(byte[] haystack, byte[] needle, int start = 0)
{
for (int i = start; i <= haystack.Length - needle.Length; i++)
{
bool match = true;
for (int j = 0; j < needle.Length; j++)
{
if (haystack[i + j] != needle[j]) { match = false; break; }
}
if (match) return i;
}
return -1;
}
private static int LastIndexOf(byte[] haystack, byte[] needle)
{
int found = -1, at = 0;
while ((at = IndexOf(haystack, needle, at)) >= 0)
{
found = at;
at++;
}
return found;
}
[Fact]
public async Task Sets_line_spacing_to_the_slice_height_before_the_first_slice()
{
// Each ESC * 33 pass paints 24 dot rows, but the LF between passes advances
// by the current line spacing - 1/6 inch, about 34 dots at 203 dpi - unless
// told otherwise. Left at the default, every band is followed by a ~10 dot
// white gap and the image comes out striped. Confirmed on real hardware.
byte[] wire = await CaptureAsync(async printer =>
{
using var image = new Image(64, 48); // two 24-dot slices
await printer.PrintImageBitModeAsync(
image, EpsonCommands.BitImageMode.DoubleDensity24Dot, 64);
});
int spacing = IndexOf(wire, new byte[] { ESC, 0x33, 24 }); // ESC 3 24
int firstSlice = IndexOf(wire, new byte[] { ESC, 0x2A }); // ESC *
Assert.True(spacing >= 0, "expected ESC 3 24 to set line spacing to the slice height");
Assert.True(firstSlice >= 0, "expected at least one ESC * bit-image slice");
Assert.True(spacing < firstSlice,
"line spacing must be set BEFORE the first slice, or the first gap still appears");
}
[Fact]
public async Task Restores_default_line_spacing_after_the_last_slice()
{
// Leaving the image's tight spacing set would compress every subsequent
// line of text on the receipt.
byte[] wire = await CaptureAsync(async printer =>
{
using var image = new Image(64, 48);
await printer.PrintImageBitModeAsync(
image, EpsonCommands.BitImageMode.DoubleDensity24Dot, 64);
});
int lastSlice = LastIndexOf(wire, new byte[] { ESC, 0x2A });
int restore = LastIndexOf(wire, new byte[] { ESC, 0x32 }); // ESC 2
Assert.True(restore >= 0, "expected ESC 2 to restore the default line spacing");
Assert.True(restore > lastSlice, "default spacing must be restored after the last slice");
}
[Fact]
public async Task Uses_the_eight_dot_slice_height_for_eight_dot_modes()
{
// The spacing has to track the mode, not be hardcoded to 24.
byte[] wire = await CaptureAsync(async printer =>
{
using var image = new Image(64, 16); // two 8-dot slices
await printer.PrintImageBitModeAsync(
image, EpsonCommands.BitImageMode.SingleDensity8Dot, 64);
});
Assert.True(IndexOf(wire, new byte[] { ESC, 0x33, 8 }) >= 0,
"expected ESC 3 8 for an 8-dot bit-image mode");
}
}