diff --git a/Inspectron.Epson.Tests/BitImagePrintingTests.cs b/Inspectron.Epson.Tests/BitImagePrintingTests.cs
new file mode 100644
index 0000000..492d577
--- /dev/null
+++ b/Inspectron.Epson.Tests/BitImagePrintingTests.cs
@@ -0,0 +1,137 @@
+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");
+ }
+}
diff --git a/Inspectron.Epson/EpsonPrinter.cs b/Inspectron.Epson/EpsonPrinter.cs
index d99319d..0d86085 100644
--- a/Inspectron.Epson/EpsonPrinter.cs
+++ b/Inspectron.Epson/EpsonPrinter.cs
@@ -608,26 +608,41 @@ public class EpsonPrinter : IEpsonPrinter
_logger?.LogDebug("Printing in {SliceCount} slices of {BitsPerSlice} dots each", totalSlices, bitsPerSlice);
- // Print each horizontal slice
- for (int slice = 0; slice < totalSlices; slice++)
+ // Each ESC * pass paints bitsPerSlice dot rows, but the LF between passes
+ // advances by the CURRENT line spacing, which ESC @ leaves at 1/6 inch -
+ // about 34 dots at 203 dpi. Left alone, every 24-dot band is followed by a
+ // ~10 dot white gap and the image prints striped. Match the spacing to the
+ // slice height for the duration of the image, then put it back: leaving it
+ // tight would compress every following line of text on the receipt.
+ await SetCustomLineSpacing(bitsPerSlice);
+
+ try
{
- int sliceOffset = slice * finalWidth * bytesPerColumn;
- int sliceLength = finalWidth * bytesPerColumn;
-
- var sliceData = new byte[sliceLength];
- Array.Copy(columnData, sliceOffset, sliceData, 0, sliceLength);
-
- // Create and send ESC * command for this slice
- var command = EpsonCommands.CreateBitImageCommand(mode, sliceData, finalWidth);
-
- await SendRawAsync(command);
-
- // Move to next line after each slice (except the last one)
- if (slice < totalSlices - 1)
+ // Print each horizontal slice
+ for (int slice = 0; slice < totalSlices; slice++)
{
- await SendRawAsync(EpsonCommands.LineFeed);
+ int sliceOffset = slice * finalWidth * bytesPerColumn;
+ int sliceLength = finalWidth * bytesPerColumn;
+
+ var sliceData = new byte[sliceLength];
+ Array.Copy(columnData, sliceOffset, sliceData, 0, sliceLength);
+
+ // Create and send ESC * command for this slice
+ var command = EpsonCommands.CreateBitImageCommand(mode, sliceData, finalWidth);
+
+ await SendRawAsync(command);
+
+ // Move to next line after each slice (except the last one)
+ if (slice < totalSlices - 1)
+ {
+ await SendRawAsync(EpsonCommands.LineFeed);
+ }
}
}
+ finally
+ {
+ await SetDefaultLineSpacing();
+ }
_logger?.LogInformation("Image printed successfully using bit-image mode");
}