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"); } }