diff --git a/EpsonTest/Program.cs b/EpsonTest/Program.cs index 83585f5..04d2c17 100644 --- a/EpsonTest/Program.cs +++ b/EpsonTest/Program.cs @@ -1,12 +1,14 @@ using Inspectron.Epson; using Inspectron.Epson.PrintServer.Printers.Utils; using Inspectron.Epson.PrintServer.Printers.Utils.FinalRecipe; +using Inspectron.Epson.PrintServer.Printers.Utils.KitchenReceipt; using Inspectron.Epson.SOAP; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using System.Text; using System.Text.Json; using System.Xml.Linq; +using FinalReceipt = Inspectron.Epson.PrintServer.Printers.Utils.FinalRecipe.FinalReceipt; //SoapEpsonPrinter printer = new SoapEpsonPrinter("localhost:8888"); @@ -56,125 +58,202 @@ using System.Xml.Linq; //Console.ReadLine(); -//var receipt = new Receipt +var receipt = new FinalReceipt +{ + CompanyName = "Klingler Gastro AG", + Address1 = "Münzplatz 3", + Address2 = "CH-8001 Zürich", + Phone = "043 321 22 22", + ReceiptNumber = "1-81-202", + DateTime = new DateTime(2025, 12, 16, 14, 58, 0), + Guests = 2, + Total = 160.00m, + Currency = "CHF", + TotalInAlternateCurrency = 172.80m, + AlternateCurrency = "EUR", + PaymentMethod = "MASTER", + PaymentAmount = 160.00m, + WaiterName = "Yves", + Terminal = "Hauptkasse ZH", + TableNumber = "12", + VatNumber = "CHE-449.635.880 MWST", + ThankYouMessage = "Das Team bedankt sich herzlich für Ihren", + GoodbyeMessageLine1 = "Besuch.", + GoodbyeMessageLine2 = "Auf Wiedersehen." +}; + +// Add items +receipt.Items.Add(new ReceiptItem +{ + Quantity = 1, + Description = "San Pellegrino 50cl", + UnitPrice = 6.50m, + TotalPrice = 6.50m, + TaxCategory = "A" +}); + +receipt.Items.Add(new ReceiptItem +{ + Quantity = 1, + Description = "Panna 50cl", + UnitPrice = 6.50m, + TotalPrice = 6.50m, + TaxCategory = "A" +}); + +receipt.Items.Add(new ReceiptItem +{ + Quantity = 1, + Description = "Granini Tomatensaft", + UnitPrice = 5.50m, + TotalPrice = 5.50m, + TaxCategory = "A" +}); + +receipt.Items.Add(new ReceiptItem +{ + Quantity = 2, + Description = "Business Lunch Menu Seco", + UnitPrice = 44.00m, + TotalPrice = 88.00m, + TaxCategory = "A" +}); + +receipt.Items.Add(new ReceiptItem +{ + Quantity = 2, + Description = "Brunello di Montalcino 1", + UnitPrice = 16.00m, + TotalPrice = 32.00m, + TaxCategory = "A" +}); + +receipt.Items.Add(new ReceiptItem +{ + Quantity = 2, + Description = "Espresso", + UnitPrice = 5.50m, + TotalPrice = 11.00m, + TaxCategory = "A" +}); + +receipt.Items.Add(new ReceiptItem +{ + Quantity = 1, + Description = "Tip", + UnitPrice = 10.50m, + TotalPrice = 10.50m, + TaxCategory = "B" +}); + +// Add tax breakdown +receipt.TaxBreakdown.Add(new TaxInfo +{ + Category = "A", + Rate = 8.1m, + Gross = 149.50m, + Net = 138.30m, + TaxAmount = 11.20m, + Currency = "CHF" +}); + +receipt.TaxBreakdown.Add(new TaxInfo +{ + Category = "B", + Rate = 0m, + Gross = 10.50m, + Net = 10.50m, + TaxAmount = 0.00m, + Currency = "CHF" +}); + + +// Convert to print commands +var converter = new ReceiptConverter(lineWidth: 48, bigFontLineWidth: 24); +var printCommands = converter.ConvertToPrintCommands(receipt); + + + + +// Print the commands +Console.WriteLine("=== PRINT COMMANDS ===\n"); +var printer = new HtmlPrinter(@".\test.html", paperWidth: 384); +await printer.ConnectAsync(""); +await printer.SetAbsolutePrintPosition(97); +await printer.LoadImageAsync("ristorante-klinglers.ch-logo_white_bg.png", 390); +await printer.SetAbsolutePrintPosition(0); +await printer.PrintLoadedImage(); +await Task.Delay(200); +await printer.FeedLinesAsync(1); +await printer.SetCustomLineSpacing(22); +foreach (var command in printCommands) +{ + string attributes = ""; + if (command.IsBig) attributes += "[BIG] "; + if (command.IsBold) attributes += "[BOLD] "; + + Console.WriteLine($"{attributes}{command.Text}"); + + if (command.IsBig) + { + await printer.SetFontSizeAsync(2, 1); + } + else + { + await printer.SetFontSizeAsync(1, 1); + } + //await Task.Delay(200); + + await printer.SetEmphasized(command.IsBold); + //await Task.Delay(200); + + await printer.PrintTextAsync(command.Text + "\n"); + //await Task.Delay(200); + +} + + +//var receipt = new KitchenReceipt //{ -// CompanyName = "Klingler Gastro AG", -// Address1 = "Münzplatz 3", -// Address2 = "CH-8001 Zürich", -// Phone = "043 321 22 22", -// ReceiptNumber = "1-81-202", -// DateTime = new DateTime(2025, 12, 16, 14, 58, 0), -// Guests = 2, -// Total = 160.00m, -// Currency = "CHF", -// TotalInAlternateCurrency = 172.80m, -// AlternateCurrency = "EUR", -// PaymentMethod = "MASTER", -// PaymentAmount = 160.00m, -// ServerName = "Yves", -// Terminal = "Hauptkasse ZH", -// TableNumber = "12", -// VatNumber = "CHE-449.635.880 MWST", -// ThankYouMessage = "Das Team bedankt sich herzlich für Ihren", -// GoodbyeMessageLine1 = "Besuch.", -// GoodbyeMessageLine2 = "Auf Wiedersehen." +// Title = "Warme Küche", +// TransactionDateTime = new DateTime(2025, 10, 23, 12, 18, 0), +// ReceiptNumber = "132018166", +// WaiterName = "Mariano Amato", +// WaiterId = "32 (VK Restaurant)", +// TableNumber = 22 //}; -//// Add items -//receipt.Items.Add(new ReceiptItem -//{ -// Quantity = 1, -// Description = "San Pellegrino 50cl", -// UnitPrice = 6.50m, -// TotalPrice = 6.50m, -// TaxCategory = "A" -//}); +//// Add dishes (this section can be empty if no dishes were ordered) -//receipt.Items.Add(new ReceiptItem -//{ -// Quantity = 1, -// Description = "Panna 50cl", -// UnitPrice = 6.50m, -// TotalPrice = 6.50m, -// TaxCategory = "A" -//}); +//receipt.Gangs.Add(new Gang(1, "Gang")); +//receipt.Gangs.Add(new Gang(2, "Gang")); -//receipt.Items.Add(new ReceiptItem -//{ -// Quantity = 1, -// Description = "Granini Tomatensaft", -// UnitPrice = 5.50m, -// TotalPrice = 5.50m, -// TaxCategory = "A" -//}); - -//receipt.Items.Add(new ReceiptItem -//{ -// Quantity = 2, -// Description = "Business Lunch Menu Seco", -// UnitPrice = 44.00m, -// TotalPrice = 88.00m, -// TaxCategory = "A" -//}); - -//receipt.Items.Add(new ReceiptItem -//{ -// Quantity = 2, -// Description = "Brunello di Montalcino 1", -// UnitPrice = 16.00m, -// TotalPrice = 32.00m, -// TaxCategory = "A" -//}); - -//receipt.Items.Add(new ReceiptItem -//{ -// Quantity = 2, -// Description = "Espresso", -// UnitPrice = 5.50m, -// TotalPrice = 11.00m, -// TaxCategory = "A" -//}); - -//receipt.Items.Add(new ReceiptItem -//{ -// Quantity = 1, -// Description = "Tip", -// UnitPrice = 10.50m, -// TotalPrice = 10.50m, -// TaxCategory = "B" -//}); - -//// Add tax breakdown -//receipt.TaxBreakdown.Add(new TaxInfo -//{ -// Category = "A", -// Rate = 8.1m, -// Gross = 149.50m, -// Net = 138.30m, -// TaxAmount = 11.20m, -// Currency = "CHF" -//}); - -//receipt.TaxBreakdown.Add(new TaxInfo -//{ -// Category = "B", -// Rate = 0m, -// Gross = 10.50m, -// Net = 10.50m, -// TaxAmount = 0.00m, -// Currency = "CHF" -//}); +//receipt.Gangs[0].Dishes.Add(new Dish(1, "Avocado Sashimi")); +//receipt.Gangs[0].Dishes.Add(new Dish(1, "Baby Spinach Salad with Truffl")); +//receipt.Gangs[0].Dishes.Add(new Dish(1, "Beef Tataki")); +//receipt.Gangs[0].Dishes.Add(new Dish(1, "Salmon Taco")); -//// Convert to print commands -//var converter = new ReceiptConverter(lineWidth: 48, bigFontLineWidth: 24); +//receipt.Gangs[1].Dishes.Add(new Dish(1, "Avocado Sashimi")); +//receipt.Gangs[1].Dishes.Add(new Dish(1, "Baby Spinach Salad with Truffl")); +//receipt.Gangs[1].Dishes.Add(new Dish(1, "Beef Tataki")); +//receipt.Gangs[1].Dishes.Add(new Dish(1, "Salmon Taco")); + +//receipt.Dishes.Add(new Dish(1, "Avocado Sashimi")); +//receipt.Dishes.Add(new Dish(1, "Baby Spinach Salad with Truffl")); +//receipt.Dishes.Add(new Dish(1, "Beef Tataki")); +//receipt.Dishes.Add(new Dish(1, "Salmon Taco")); + + +//// Add additional info +//receipt.AdditionalInfo.Add("1 x Amuse Bouche"); + +//KitchenReceiptConverter converter = new KitchenReceiptConverter(bigLineWidth: 20, lineWidth:33); //var printCommands = converter.ConvertToPrintCommands(receipt); -//// Print the commands //Console.WriteLine("=== PRINT COMMANDS ===\n"); -//await printer.SetAbsolutePrintPosition(97); -//await printer.LoadImageAsync("ristorante-klinglers.ch-logo_white_bg.png",390); -//await printer.SetAbsolutePrintPosition(0); +//var printer = new HtmlPrinter(@".\test.html", paperWidth: 258); +//await printer.ConnectAsync(""); //await Task.Delay(200); //await printer.FeedLinesAsync(1); //await printer.SetCustomLineSpacing(22); @@ -183,6 +262,7 @@ using System.Xml.Linq; // string attributes = ""; // if (command.IsBig) attributes += "[BIG] "; // if (command.IsBold) attributes += "[BOLD] "; +// if (command.IsRed) attributes += "[RED] "; // Console.WriteLine($"{attributes}{command.Text}"); @@ -194,6 +274,9 @@ using System.Xml.Linq; // { // await printer.SetFontSizeAsync(1, 1); // } + +// await printer.SetRedColor(command.IsRed); + // //await Task.Delay(200); // await printer.SetEmphasized(command.IsBold); @@ -230,24 +313,24 @@ using System.Xml.Linq; //await Task.Delay(200); //KitchenReceiptPrinter krp = new KitchenReceiptPrinter(33); -var receipt = new KitchenReceipt( - Location: "Warme Küche", - Date: "23-Okt-25 12:18 Nr.:13201B166", - Owner: "Mariano Amato\n32 (VK Restaurant)", - Tisch: "22", - items: new List - { - new KitchenGang("1. Gang"), - new KitchenProduct(1, "Avocado Sashimi"), - new KitchenProduct(1, "Baby Spinach Salad with Truffl"), - new KitchenGang("2. Gang"), - new KitchenProduct(1, "Beef Tataki"), - new KitchenProduct(1, "Salmon Taco") - } -); +//var receipt = new TranslatedKitchenReceipt( +// Location: "Warme Küche", +// Date: "23-Okt-25 12:18 Nr.:13201B166", +// Owner: "Mariano Amato\n32 (VK Restaurant)", +// Tisch: "22", +// items: new List +// { +// new KitchenGang("1. Gang"), +// new KitchenProduct(1, "Avocado Sashimi"), +// new KitchenProduct(1, "Baby Spinach Salad with Truffl"), +// new KitchenGang("2. Gang"), +// new KitchenProduct(1, "Beef Tataki"), +// new KitchenProduct(1, "Salmon Taco") +// } +//); -var test = JsonSerializer.Serialize(receipt, new JsonSerializerOptions() { WriteIndented = true }); -Console.WriteLine(test); +//var test = JsonSerializer.Serialize(receipt, new JsonSerializerOptions() { WriteIndented = true }); +//Console.WriteLine(test); ////// Create printer with 42 character line width (default) //var commands = krp.ConvertToCommands(receipt); diff --git a/Inspectron.Epson/EpsonPrinter.cs b/Inspectron.Epson/EpsonPrinter.cs index 045bd8b..140a948 100644 --- a/Inspectron.Epson/EpsonPrinter.cs +++ b/Inspectron.Epson/EpsonPrinter.cs @@ -10,7 +10,7 @@ namespace Inspectron.Epson; /// /// Main SDK class for Epson TM-m30III printer operations /// -public class EpsonPrinter : IAsyncDisposable +public class EpsonPrinter : IEpsonPrinter { private readonly ILogger? _logger; private TcpClient? _tcpClient; diff --git a/Inspectron.Epson/HtmlPrinter.cs b/Inspectron.Epson/HtmlPrinter.cs new file mode 100644 index 0000000..1aa7413 --- /dev/null +++ b/Inspectron.Epson/HtmlPrinter.cs @@ -0,0 +1,700 @@ +using Microsoft.Extensions.Logging; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using System.Text; + +namespace Inspectron.Epson; + +/// +/// HTML-based printer implementation that outputs to an HTML file, +/// simulating Epson thermal printer output for testing and preview purposes. +/// +public class HtmlPrinter : IEpsonPrinter +{ + private readonly ILogger? _logger; + private readonly string _outputPath; + private readonly int _paperWidth; + private readonly string _imageDirectory; + + private bool _isConnected; + private readonly StringBuilder _content = new(); + private int _imageCounter; + + // Current styling state + private EpsonCommands.PrinterFont _currentFont = EpsonCommands.PrinterFont.A; + private int _fontWidthMultiplier = 1; + private int _fontHeightMultiplier = 1; + private bool _isEmphasized; + private bool _isRedColor; + private bool _isQuadrupleMode; + private bool _isBiggerFontTM220; + private int _lineSpacing = 14; // Default line spacing in pixels (scaled for screen) + private int _absolutePosition; // Current X position offset + + // Loaded image buffer + private Image? _loadedImage; + private int _loadedImageMaxWidth; + + /// + /// Font size mappings for different Epson fonts (scaled for screen display) + /// Original dots: Font A: 12x24, Font B: 9x17, Font C: 9x24, Font D: 10x24, Font E: 12x24 + /// Scaled to ~0.5x for reasonable screen display + /// + private static readonly Dictionary FontSizes = new() + { + { EpsonCommands.PrinterFont.A, (6, 12) }, + { EpsonCommands.PrinterFont.B, (5, 9) }, + { EpsonCommands.PrinterFont.C, (5, 12) }, + { EpsonCommands.PrinterFont.D, (5, 12) }, + { EpsonCommands.PrinterFont.E, (6, 12) } + }; + + /// + public bool IsConnected => _isConnected; + + /// + /// Initialize a new instance of HtmlPrinter + /// + /// Path to the output HTML file + /// Paper width in pixels (default 384 for 80mm thermal printer) + /// Optional logger for diagnostic output + public HtmlPrinter(string outputPath, int paperWidth = 384, ILogger? logger = null) + { + _outputPath = outputPath; + _paperWidth = paperWidth; + _logger = logger; + + // Create image directory next to HTML file + var directory = Path.GetDirectoryName(outputPath) ?? "."; + var fileName = Path.GetFileNameWithoutExtension(outputPath); + _imageDirectory = Path.Combine(directory, $"{fileName}_images"); + } + + /// + public Task ConnectAsync(string ip, int port = 9100, int timeoutSeconds = 10) + { + _logger?.LogInformation("HtmlPrinter: Connecting (output: {Path}, paper width: {Width}px)", _outputPath, _paperWidth); + + // Create image directory if it doesn't exist + if (!Directory.Exists(_imageDirectory)) + { + Directory.CreateDirectory(_imageDirectory); + } + + // Clear previous content + _content.Clear(); + _imageCounter = 0; + ResetStylingState(); + + _isConnected = true; + WriteHtmlFile(); + + _logger?.LogInformation("HtmlPrinter: Connected successfully"); + return Task.CompletedTask; + } + + /// + public void Disconnect() + { + _logger?.LogDebug("HtmlPrinter: Disconnecting"); + + if (_isConnected) + { + WriteHtmlFile(); + } + + _loadedImage?.Dispose(); + _loadedImage = null; + _isConnected = false; + } + + /// + public Task GetPrinterStatusAsync() + { + _logger?.LogDebug("HtmlPrinter: Returning mock printer status (always ready)"); + return Task.FromResult(new PrinterStatus + { + RawByte = 0x12, // Online, cover closed + DrawerOpen = false, + IsOnline = true, + IsCoverClosed = true, + PaperFeedButtonPressed = false + }); + } + + /// + public Task GetOfflineStatusAsync() + { + _logger?.LogDebug("HtmlPrinter: Returning mock offline status (no issues)"); + return Task.FromResult(new OfflineStatus + { + RawByte = 0x12, + CoverOpen = false, + PaperFeedButton = false, + PaperEnd = false, + ErrorOccurred = false + }); + } + + /// + public Task GetErrorStatusAsync() + { + _logger?.LogDebug("HtmlPrinter: Returning mock error status (no errors)"); + return Task.FromResult(new ErrorStatus + { + RawByte = 0x12, + RecoverableError = false, + AutoCutterError = false, + UnrecoverableError = false, + AutoRecoveryError = false + }); + } + + /// + public Task GetPaperSensorStatusAsync() + { + _logger?.LogDebug("HtmlPrinter: Returning mock paper sensor status (paper present)"); + return Task.FromResult(new PaperSensorStatus + { + RawByte = 0x12, + PaperNearEnd = false, + PaperPresent = true + }); + } + + /// + public Task GetTM220StatusAsync() + { + _logger?.LogDebug("HtmlPrinter: Returning mock TM220 status"); + return Task.FromResult((byte)0x12); + } + + /// + public Task GetPrinterIdAsync() + { + _logger?.LogDebug("HtmlPrinter: Returning mock printer ID (0x01 = TM-T30III)"); + return Task.FromResult(0x01); + } + + /// + public Task GetOverallStatusAsync() + { + _logger?.LogInformation("HtmlPrinter: Returning mock overall status (ready)"); + return Task.FromResult(new OverallStatus + { + PrinterModel = "HtmlPrinter", + PrinterStatus = new PrinterStatus { RawByte = 0x12, IsOnline = true, IsCoverClosed = true }, + OfflineStatus = new OfflineStatus { RawByte = 0x12 }, + ErrorStatus = new ErrorStatus { RawByte = 0x12 }, + PaperStatus = new PaperSensorStatus { RawByte = 0x12, PaperPresent = true }, + StatusText = "READY - HTML Printer Simulator", + StatusIcon = "🟢", + IsReady = true, + Recommendations = new List() + }); + } + + /// + public Task InitAsync() + { + _logger?.LogInformation("HtmlPrinter: Initializing (resetting styling state)"); + ResetStylingState(); + return Task.CompletedTask; + } + + /// + public Task SetQuadrupleMode(bool enable) + { + _logger?.LogInformation("HtmlPrinter: {Action} quadruple mode", enable ? "Enabling" : "Disabling"); + _isQuadrupleMode = enable; + return Task.CompletedTask; + } + + /// + public Task SetRedColor(bool enable) + { + _logger?.LogInformation("HtmlPrinter: {Action} red color mode", enable ? "Enabling" : "Disabling"); + _isRedColor = enable; + return Task.CompletedTask; + } + + /// + public Task SetEmphasized(bool enable) + { + _logger?.LogInformation("HtmlPrinter: {Action} emphasized mode", enable ? "Enabling" : "Disabling"); + _isEmphasized = enable; + return Task.CompletedTask; + } + + /// + public Task SelectFont(EpsonCommands.PrinterFont font) + { + _logger?.LogInformation("HtmlPrinter: Selecting font {Font}", font); + _currentFont = font; + return Task.CompletedTask; + } + + /// + public Task SetBiggerFontTM220(bool enabled) + { + _logger?.LogInformation("HtmlPrinter: {Action} bigger font mode for TM-T20/220", enabled ? "Enabling" : "Disabling"); + _isBiggerFontTM220 = enabled; + return Task.CompletedTask; + } + + /// + public Task PrintTextAsync(string text) + { + EnsureConnected(); + _logger?.LogInformation("HtmlPrinter: Printing text ({Length} characters)", text.Length); + + var style = BuildCurrentStyle(); + var escapedText = EscapeHtml(text); + + // Handle newlines + escapedText = escapedText.TrimEnd('\n').Replace("\n", "
"); + + // Add margin-left for absolute positioning + var positionStyle = _absolutePosition > 0 ? $"margin-left: {_absolutePosition}px;" : ""; + + _content.AppendLine($"{escapedText}"); + + // Reset absolute position after printing + _absolutePosition = 0; + + WriteHtmlFile(); + return Task.CompletedTask; + } + + /// + public Task SetFontSizeAsync(int width, int height) + { + if (width < 1 || width > 8 || height < 1 || height > 8) + throw new ArgumentException("Size must be between 1 and 8"); + + _logger?.LogInformation("HtmlPrinter: Setting font size to {Width}x{Height}", width, height); + _fontWidthMultiplier = width; + _fontHeightMultiplier = height; + return Task.CompletedTask; + } + + /// + public async Task PrintTextAndCutAsync(string text, int feedLines = 5) + { + _logger?.LogInformation("HtmlPrinter: Printing text with cut ({Length} characters, {FeedLines} feed lines)", + text.Length, feedLines); + + await InitAsync(); + await PrintTextAsync(text); + await PrintTextAsync("\n"); + await FeedLinesAsync(feedLines); + await CutAsync(); + } + + /// + public Task PrintBuffer() + { + _logger?.LogDebug("HtmlPrinter: PrintBuffer (no-op for HTML)"); + return Task.CompletedTask; + } + + /// + public Task FeedLinesAsync(int lines) + { + EnsureConnected(); + _logger?.LogInformation("HtmlPrinter: Feeding {Lines} lines", lines); + + for (int i = 0; i < lines; i++) + { + _content.AppendLine($"
"); + } + + WriteHtmlFile(); + return Task.CompletedTask; + } + + /// + public Task CutAsync() + { + EnsureConnected(); + _logger?.LogInformation("HtmlPrinter: Cutting paper"); + + _content.AppendLine("
"); + + WriteHtmlFile(); + return Task.CompletedTask; + } + + /// + public async Task LoadImageAsync(string imagePath, int maxWidth = 384) + { + _logger?.LogInformation("HtmlPrinter: Loading image from file: {Path}", imagePath); + + if (!File.Exists(imagePath)) + { + throw new FileNotFoundException($"Image file not found: {imagePath}", imagePath); + } + + _loadedImage?.Dispose(); + _loadedImage = await Image.LoadAsync(imagePath); + _loadedImageMaxWidth = maxWidth; + } + + /// + public async Task LoadImageAsync(Stream imageStream, int maxWidth = 384) + { + _logger?.LogInformation("HtmlPrinter: Loading image from stream"); + + _loadedImage?.Dispose(); + _loadedImage = await Image.LoadAsync(imageStream); + _loadedImageMaxWidth = maxWidth; + } + + /// + public Task LoadImageAsync(Image image, int maxWidth = 384) + { + _logger?.LogInformation("HtmlPrinter: Loading image from ImageSharp object"); + + _loadedImage?.Dispose(); + _loadedImage = image.Clone(); + _loadedImageMaxWidth = maxWidth; + return Task.CompletedTask; + } + + /// + public async Task PrintLoadedImage() + { + EnsureConnected(); + + if (_loadedImage == null) + { + _logger?.LogWarning("HtmlPrinter: No image loaded to print"); + return; + } + + _logger?.LogInformation("HtmlPrinter: Printing loaded image"); + await RenderImageToHtml(_loadedImage, _loadedImageMaxWidth); + + _loadedImage.Dispose(); + _loadedImage = null; + } + + /// + public async Task PrintImageBitModeAsync(string imagePath, EpsonCommands.BitImageMode mode = EpsonCommands.BitImageMode.SingleDensity8Dot, int maxWidth = 10) + { + _logger?.LogInformation("HtmlPrinter: Printing image (bit mode) from file: {Path}", imagePath); + + if (!File.Exists(imagePath)) + { + throw new FileNotFoundException($"Image file not found: {imagePath}", imagePath); + } + + using var image = await Image.LoadAsync(imagePath); + await PrintImageBitModeAsync(image, mode, maxWidth); + } + + /// + public async Task PrintImageBitModeAsync(Stream imageStream, EpsonCommands.BitImageMode mode = EpsonCommands.BitImageMode.DoubleDensity24Dot, int maxWidth = 384) + { + _logger?.LogInformation("HtmlPrinter: Printing image (bit mode) from stream"); + + using var image = await Image.LoadAsync(imageStream); + await PrintImageBitModeAsync(image, mode, maxWidth); + } + + /// + public Task PrintImageBitModeAsync(Image image, EpsonCommands.BitImageMode mode, int maxWidth) + { + EnsureConnected(); + _logger?.LogInformation("HtmlPrinter: Printing image (bit mode) {Width}x{Height}", image.Width, image.Height); + + return RenderImageToHtml(image, maxWidth); + } + + /// + public Task SendRawCommandAsync(byte[] command) + { + _logger?.LogDebug("HtmlPrinter: SendRawCommandAsync ignored ({Length} bytes)", command.Length); + // Raw commands are ignored in HTML output + return Task.CompletedTask; + } + + /// + public Task SetAbsolutePrintPosition(int x) + { + if (x < 0 || x > 65535) + throw new ArgumentOutOfRangeException(nameof(x), "Position must be between 0 and 65535"); + + _logger?.LogInformation("HtmlPrinter: Setting absolute print position to {X}", x); + _absolutePosition = x; + return Task.CompletedTask; + } + + /// + public Task SetDefaultLineSpacing() + { + _logger?.LogInformation("HtmlPrinter: Setting default line spacing"); + _lineSpacing = 14; // Default (scaled for screen) + return Task.CompletedTask; + } + + /// + public Task SetCustomLineSpacing(int spacing = 24) + { + if (spacing < 0 || spacing > 255) + throw new ArgumentOutOfRangeException(nameof(spacing), "Spacing must be between 0 and 255"); + + _logger?.LogInformation("HtmlPrinter: Setting custom line spacing to {Spacing}", spacing); + // Convert dot spacing to approximate pixel spacing (scaled ~0.5x for screen) + _lineSpacing = (int)(spacing * 0.6); + return Task.CompletedTask; + } + + /// + public ValueTask DisposeAsync() + { + Disconnect(); + return ValueTask.CompletedTask; + } + + #region Private Methods + + private void ResetStylingState() + { + _currentFont = EpsonCommands.PrinterFont.A; + _fontWidthMultiplier = 1; + _fontHeightMultiplier = 1; + _isEmphasized = false; + _isRedColor = false; + _isQuadrupleMode = false; + _isBiggerFontTM220 = false; + _lineSpacing = 14; + _absolutePosition = 0; + } + + private string BuildCurrentStyle() + { + var styles = new List(); + + // Base font size from selected font + var (baseWidth, baseHeight) = FontSizes[_currentFont]; + + // Apply multipliers + int effectiveWidthMultiplier = _fontWidthMultiplier; + int effectiveHeightMultiplier = _fontHeightMultiplier; + + if (_isQuadrupleMode) + { + effectiveWidthMultiplier *= 2; + effectiveHeightMultiplier *= 2; + } + + if (_isBiggerFontTM220) + { + effectiveHeightMultiplier *= 2; + } + + int fontSize = baseHeight * effectiveHeightMultiplier; + styles.Add($"font-size: {fontSize}px"); + + // Letter spacing for width scaling (approximate) + if (effectiveWidthMultiplier > 1) + { + int letterSpacing = (effectiveWidthMultiplier) * baseWidth / 2; + styles.Add($"letter-spacing: {letterSpacing}px"); + } + + // Font weight + if (_isEmphasized) + { + styles.Add("font-weight: bold"); + } + + // Color + if (_isRedColor) + { + styles.Add("color: #cc0000"); + } + + // Line height based on current line spacing + styles.Add($"line-height: {_lineSpacing}px"); + + return string.Join("; ", styles) + ";"; + } + + private async Task RenderImageToHtml(Image sourceImage, int maxWidth) + { + // Clone and process the image (apply dithering like real printer) + using var processedImage = sourceImage.Clone(); + + // Resize if needed + if (processedImage.Width > maxWidth) + { + EpsonImageConverter.ResizeImage(processedImage, maxWidth); + } + + // Convert to grayscale + using var grayscaleImage = EpsonImageConverter.ConvertToGrayscale(processedImage); + + // Apply Floyd-Steinberg dithering + EpsonImageConverter.ApplyFloydSteinbergDithering(grayscaleImage); + + // Convert back to RGBA for saving + using var ditheredImage = new Image(grayscaleImage.Width, grayscaleImage.Height); + grayscaleImage.ProcessPixelRows(ditheredImage, (sourceAccessor, targetAccessor) => + { + for (int y = 0; y < sourceAccessor.Height; y++) + { + var sourceRow = sourceAccessor.GetRowSpan(y); + var targetRow = targetAccessor.GetRowSpan(y); + + for (int x = 0; x < sourceAccessor.Width; x++) + { + byte value = sourceRow[x].PackedValue; + targetRow[x] = new Rgba32(value, value, value, 255); + } + } + }); + + // Save image to file + _imageCounter++; + var imageName = $"image_{_imageCounter:D4}.png"; + var imagePath = Path.Combine(_imageDirectory, imageName); + + await ditheredImage.SaveAsync(imagePath, new PngEncoder()); + + // Get relative path for HTML + var htmlDir = Path.GetDirectoryName(_outputPath) ?? "."; + var relativeImagePath = Path.GetRelativePath(htmlDir, imagePath).Replace('\\', '/'); + + // Add to HTML content + var positionStyle = _absolutePosition > 0 ? $"margin-left: {_absolutePosition}px;" : ""; + _content.AppendLine($"
\"Printed
"); + + // Reset absolute position + _absolutePosition = 0; + + WriteHtmlFile(); + + _logger?.LogInformation("HtmlPrinter: Image saved to {Path}", imagePath); + } + + private void WriteHtmlFile() + { + var html = GenerateHtml(); + File.WriteAllText(_outputPath, html, Encoding.UTF8); + _logger?.LogDebug("HtmlPrinter: HTML file written to {Path}", _outputPath); + } + + private string GenerateHtml() + { + return $@" + + + + + Epson Printer Output + + + +
+{_content} +
+ +"; + } + + private static string EscapeHtml(string text) + { + return text + .Replace("&", "&") + .Replace("<", "<") + .Replace(">", ">") + .Replace("\"", """) + .Replace("'", "'"); + } + + private void EnsureConnected() + { + if (!_isConnected) + { + throw new EpsonConnectionException("Not connected. Call ConnectAsync first."); + } + } + + #endregion +} diff --git a/Inspectron.Epson/IEpsonPrinter.cs b/Inspectron.Epson/IEpsonPrinter.cs new file mode 100644 index 0000000..d3008b1 --- /dev/null +++ b/Inspectron.Epson/IEpsonPrinter.cs @@ -0,0 +1,181 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace Inspectron.Epson; + +/// +/// Interface for Epson printer operations, allowing swappable implementations +/// (e.g., real printer vs HTML output for testing/preview) +/// +public interface IEpsonPrinter : IAsyncDisposable +{ + /// + /// Returns true if connected to the printer + /// + bool IsConnected { get; } + + /// + /// Connect to the printer + /// + /// Printer IP address (or identifier for non-network implementations) + /// Printer port (default 9100) + /// Connection timeout in seconds (default 10) + Task ConnectAsync(string ip, int port = 9100, int timeoutSeconds = 10); + + /// + /// Disconnect from the printer + /// + void Disconnect(); + + /// + /// Get general printer status + /// + Task GetPrinterStatusAsync(); + + /// + /// Get offline status (reasons for offline) + /// + Task GetOfflineStatusAsync(); + + /// + /// Get error status + /// + Task GetErrorStatusAsync(); + + /// + /// Get paper sensor status + /// + Task GetPaperSensorStatusAsync(); + + /// + /// Get TM-T20/220 specific status byte + /// + Task GetTM220StatusAsync(); + + /// + /// Get printer model/ID + /// + Task GetPrinterIdAsync(); + + /// + /// Get comprehensive overall status with health assessment + /// + Task GetOverallStatusAsync(); + + /// + /// Initialize the printer + /// + Task InitAsync(); + + /// + /// Enable or disable quadruple size mode + /// + Task SetQuadrupleMode(bool enable); + + /// + /// Enable or disable red color printing + /// + Task SetRedColor(bool enable); + + /// + /// Enable or disable emphasized (bold) mode + /// + Task SetEmphasized(bool enable); + + /// + /// Select printer font + /// + Task SelectFont(EpsonCommands.PrinterFont font); + + /// + /// Enable or disable bigger font mode for TM-T20/220 + /// + Task SetBiggerFontTM220(bool enabled); + + /// + /// Print text to the printer + /// + Task PrintTextAsync(string text); + + /// + /// Set font size (magnification) + /// + /// Width magnification (1-8) + /// Height magnification (1-8) + Task SetFontSizeAsync(int width, int height); + + /// + /// Print text, feed paper, and cut + /// + Task PrintTextAndCutAsync(string text, int feedLines = 5); + + /// + /// Print the current buffer + /// + Task PrintBuffer(); + + /// + /// Feed specified number of lines + /// + Task FeedLinesAsync(int lines); + + /// + /// Cut the paper + /// + Task CutAsync(); + + /// + /// Load an image from a file path into the printer buffer + /// + Task LoadImageAsync(string imagePath, int maxWidth = 384); + + /// + /// Load an image from a stream into the printer buffer + /// + Task LoadImageAsync(Stream imageStream, int maxWidth = 384); + + /// + /// Load an image from an ImageSharp Image object into the printer buffer + /// + Task LoadImageAsync(Image image, int maxWidth = 384); + + /// + /// Print the previously loaded image + /// + Task PrintLoadedImage(); + + /// + /// Print an image using bit-image mode from a file path + /// + Task PrintImageBitModeAsync(string imagePath, EpsonCommands.BitImageMode mode = EpsonCommands.BitImageMode.SingleDensity8Dot, int maxWidth = 10); + + /// + /// Print an image using bit-image mode from a stream + /// + Task PrintImageBitModeAsync(Stream imageStream, EpsonCommands.BitImageMode mode = EpsonCommands.BitImageMode.DoubleDensity24Dot, int maxWidth = 384); + + /// + /// Print an image using bit-image mode from an ImageSharp Image object + /// + Task PrintImageBitModeAsync(Image image, EpsonCommands.BitImageMode mode, int maxWidth); + + /// + /// Send raw ESC/POS command bytes to the printer + /// + Task SendRawCommandAsync(byte[] command); + + /// + /// Set the absolute horizontal print position + /// + Task SetAbsolutePrintPosition(int x); + + /// + /// Set default line spacing + /// + Task SetDefaultLineSpacing(); + + /// + /// Set custom line spacing + /// + Task SetCustomLineSpacing(int spacing = 24); +} diff --git a/Inspectron.Epson/PrintServer/Printers/TM-T30IIITranslated.cs b/Inspectron.Epson/PrintServer/Printers/TM-T30IIITranslated.cs index 5e1a24d..772ad55 100644 --- a/Inspectron.Epson/PrintServer/Printers/TM-T30IIITranslated.cs +++ b/Inspectron.Epson/PrintServer/Printers/TM-T30IIITranslated.cs @@ -2,6 +2,7 @@ using Inspectron.Epson.PrintServer.Printers.Utils; using Inspectron.Epson.PrintServer.Printers.Utils.FinalRecipe; using Inspectron.Epson.PrintServer.PrintServices; +using FinalReceipt = Inspectron.Epson.PrintServer.Printers.Utils.FinalRecipe.FinalReceipt; namespace Inspectron.Epson.PrintServer.Printers; @@ -35,10 +36,10 @@ public class TM_T30IIITranslated:IPrinter public async Task PrintTextAsync(string text) { - Receipt? receipt; + FinalReceipt? receipt; try { - receipt = JsonSerializer.Deserialize(text); + receipt = JsonSerializer.Deserialize(text); } catch { @@ -84,7 +85,7 @@ public class TM_T30IIITranslated:IPrinter ReceiptTranslator translator = new ReceiptTranslator(); var ast = translator.ParseReceipt(text); KitchenReceiptPrinter printer = new KitchenReceiptPrinter(lineWidth: 33, 21); - var commands = printer.ConvertToCommands((KitchenReceipt)ast); + var commands = printer.ConvertToCommands((TranslatedKitchenReceipt)ast); foreach (KitchenPrintCommand command in commands) { if (command.IsBig) diff --git a/Inspectron.Epson/PrintServer/Printers/TM-U220IITranslated.cs b/Inspectron.Epson/PrintServer/Printers/TM-U220IITranslated.cs index 261d0ac..0887143 100644 --- a/Inspectron.Epson/PrintServer/Printers/TM-U220IITranslated.cs +++ b/Inspectron.Epson/PrintServer/Printers/TM-U220IITranslated.cs @@ -37,7 +37,7 @@ public class TM_U220IITranslated:IPrinter ReceiptTranslator translator = new ReceiptTranslator(); var ast = translator.ParseReceipt(text); KitchenReceiptPrinter printer = new KitchenReceiptPrinter(lineWidth:33,21); - var commands = printer.ConvertToCommands((KitchenReceipt)ast); + var commands = printer.ConvertToCommands((TranslatedKitchenReceipt)ast); foreach (KitchenPrintCommand command in commands) { await _printer.SetBiggerFontTM220(command.IsBig); diff --git a/Inspectron.Epson/PrintServer/Printers/Utils/FinalReceipt/Models.cs b/Inspectron.Epson/PrintServer/Printers/Utils/FinalReceipt/Models.cs index 8dedbe0..4926a88 100644 --- a/Inspectron.Epson/PrintServer/Printers/Utils/FinalReceipt/Models.cs +++ b/Inspectron.Epson/PrintServer/Printers/Utils/FinalReceipt/Models.cs @@ -1,6 +1,6 @@ namespace Inspectron.Epson.PrintServer.Printers.Utils.FinalRecipe; -public class Receipt +public class FinalReceipt { public string CompanyName { get; set; } public string Address1 { get; set; } @@ -17,7 +17,7 @@ public class Receipt public string PaymentMethod { get; set; } public decimal PaymentAmount { get; set; } public List TaxBreakdown { get; set; } = new List(); - public string ServerName { get; set; } + public string WaiterName { get; set; } public string Terminal { get; set; } public string TableNumber { get; set; } public string VatNumber { get; set; } @@ -43,18 +43,4 @@ public class TaxInfo public decimal Net { get; set; } public decimal TaxAmount { get; set; } public string Currency { get; set; } -} - -public class PrintCommand -{ - public string Text { get; set; } - public bool IsBig { get; set; } - public bool IsBold { get; set; } - - public PrintCommand(string text, bool isBig = false, bool isBold = false) - { - Text = text; - IsBig = isBig; - IsBold = isBold; - } } \ No newline at end of file diff --git a/Inspectron.Epson/PrintServer/Printers/Utils/FinalReceipt/ReceiptConverter.cs b/Inspectron.Epson/PrintServer/Printers/Utils/FinalReceipt/ReceiptConverter.cs index b0ee66d..ab917ec 100644 --- a/Inspectron.Epson/PrintServer/Printers/Utils/FinalReceipt/ReceiptConverter.cs +++ b/Inspectron.Epson/PrintServer/Printers/Utils/FinalReceipt/ReceiptConverter.cs @@ -11,26 +11,26 @@ public class ReceiptConverter _bigFontLineWidth = bigFontLineWidth; } - public List ConvertToPrintCommands(Receipt receipt) + public List ConvertToPrintCommands(FinalReceipt finalReceipt) { var commands = new List(); // Header - Company info - commands.Add(new PrintCommand(Center(receipt.CompanyName, false))); - commands.Add(new PrintCommand(Center(receipt.Address1, false))); - commands.Add(new PrintCommand(Center(receipt.Address2, false))); - commands.Add(new PrintCommand(Center(receipt.Phone, false))); + commands.Add(new PrintCommand(Center(finalReceipt.CompanyName, false))); + commands.Add(new PrintCommand(Center(finalReceipt.Address1, false))); + commands.Add(new PrintCommand(Center(finalReceipt.Address2, false))); + commands.Add(new PrintCommand(Center(finalReceipt.Phone, false))); commands.Add(new PrintCommand("")); commands.Add(new PrintCommand("")); - // Receipt info - string receiptLine = $"Rechnung Nr. {receipt.ReceiptNumber}".PadRight(_lineWidth/2)+$"{receipt.DateTime:HH:mm dd.MM.yyyy}".PadLeft(_lineWidth/2); + // FinalReceipt info + string receiptLine = $"Rechnung Nr. {finalReceipt.ReceiptNumber}".PadRight(_lineWidth/2)+$"{finalReceipt.DateTime:HH:mm dd.MM.yyyy}".PadLeft(_lineWidth/2); commands.Add(new PrintCommand(receiptLine,isBold:true)); - commands.Add(new PrintCommand($"Guests: {receipt.Guests}")); + commands.Add(new PrintCommand($"Guests: {finalReceipt.Guests}")); commands.Add(new PrintCommand("")); // Items - foreach (var item in receipt.Items) + foreach (var item in finalReceipt.Items) { string quantityDesc = $"{item.Quantity}x {item.Description}"; string prices = $"{item.UnitPrice:F2}"+$"{item.TotalPrice:F2}".PadLeft(7)+$" {item.TaxCategory}"; @@ -48,31 +48,31 @@ public class ReceiptConverter commands.Add(new PrintCommand("")); //Reini // Total - string totalLine = $"Summe: {receipt.Total:F2} {receipt.Currency}"; + string totalLine = $"Summe: {finalReceipt.Total:F2} {finalReceipt.Currency}"; commands.Add(new PrintCommand(Center(totalLine, true), true, true)); commands.Add(new PrintCommand("")); // Alternate currency - if (receipt.TotalInAlternateCurrency.HasValue) + if (finalReceipt.TotalInAlternateCurrency.HasValue) { - string altCurrencyLine = $"{receipt.TotalInAlternateCurrency:F2} {receipt.AlternateCurrency}"; + string altCurrencyLine = $"{finalReceipt.TotalInAlternateCurrency:F2} {finalReceipt.AlternateCurrency}"; commands.Add(new PrintCommand(altCurrencyLine.PadLeft(_lineWidth))); commands.Add(new PrintCommand("")); } // Payment method - string paymentLine = $"{receipt.PaymentMethod}"; - string paymentAmount = $"{receipt.PaymentAmount:F2} {receipt.Currency}"; + string paymentLine = $"{finalReceipt.PaymentMethod}"; + string paymentAmount = $"{finalReceipt.PaymentAmount:F2} {finalReceipt.Currency}"; int paymentSpaces = _lineWidth - paymentLine.Length - paymentAmount.Length; if (paymentSpaces < 1) paymentSpaces = 1; commands.Add(new PrintCommand(paymentLine + new string(' ', paymentSpaces) + paymentAmount,isBold:true)); commands.Add(new PrintCommand("")); // Tax breakdown - foreach (var tax in receipt.TaxBreakdown) + foreach (var tax in finalReceipt.TaxBreakdown) { string taxLine = "MwSt %".PadRight(_lineWidth/4)+" Brutto".PadRight(_lineWidth / 4) + " Netto".PadRight(_lineWidth / 4) + "MwSt".PadLeft(_lineWidth / 4); - if (receipt.TaxBreakdown.IndexOf(tax) == 0) + if (finalReceipt.TaxBreakdown.IndexOf(tax) == 0) { commands.Add(new PrintCommand(taxLine)); } @@ -83,22 +83,22 @@ public class ReceiptConverter commands.Add(new PrintCommand("")); // Footer info - //commands.Add(new PrintCommand(Center($"Bedient von: {receipt.ServerName}", false))); - //commands.Add(new PrintCommand(Center($"Terminal: {receipt.Terminal}", false))); - //commands.Add(new PrintCommand(Center($"Tisch: {receipt.TableNumber}", false))); - commands.Add(new PrintCommand($"Bedient von:".PadLeft(_lineWidth / 2) + $" {receipt.ServerName}")); - commands.Add(new PrintCommand($"Terminal:".PadLeft(_lineWidth / 2) + $" {receipt.Terminal}")); - commands.Add(new PrintCommand($"Tisch:".PadLeft(_lineWidth / 2)+$" {receipt.TableNumber}")); + //commands.Add(new PrintCommand(Center($"Bedient von: {finalReceipt.WaiterName}", false))); + //commands.Add(new PrintCommand(Center($"Terminal: {finalReceipt.Terminal}", false))); + //commands.Add(new PrintCommand(Center($"Tisch: {finalReceipt.TableNumber}", false))); + commands.Add(new PrintCommand($"Bedient von:".PadLeft(_lineWidth / 2) + $" {finalReceipt.WaiterName}")); + commands.Add(new PrintCommand($"Terminal:".PadLeft(_lineWidth / 2) + $" {finalReceipt.Terminal}")); + commands.Add(new PrintCommand($"Tisch:".PadLeft(_lineWidth / 2)+$" {finalReceipt.TableNumber}")); commands.Add(new PrintCommand("")); commands.Add(new PrintCommand("")); // VAT number - commands.Add(new PrintCommand(Center(receipt.VatNumber, false))); + commands.Add(new PrintCommand(Center(finalReceipt.VatNumber, false))); // Thank you message - commands.Add(new PrintCommand(Center(receipt.ThankYouMessage, false))); - commands.Add(new PrintCommand(Center(receipt.GoodbyeMessageLine1, false))); - commands.Add(new PrintCommand(Center(receipt.GoodbyeMessageLine2, false))); + commands.Add(new PrintCommand(Center(finalReceipt.ThankYouMessage, false))); + commands.Add(new PrintCommand(Center(finalReceipt.GoodbyeMessageLine1, false))); + commands.Add(new PrintCommand(Center(finalReceipt.GoodbyeMessageLine2, false))); return commands; } diff --git a/Inspectron.Epson/PrintServer/Printers/Utils/KitchenReceipt/KitchenReceiptConverter.cs b/Inspectron.Epson/PrintServer/Printers/Utils/KitchenReceipt/KitchenReceiptConverter.cs new file mode 100644 index 0000000..1ebb2f1 --- /dev/null +++ b/Inspectron.Epson/PrintServer/Printers/Utils/KitchenReceipt/KitchenReceiptConverter.cs @@ -0,0 +1,109 @@ +namespace Inspectron.Epson.PrintServer.Printers.Utils.KitchenReceipt; + +public class KitchenReceiptConverter +{ + private readonly int _lineWidth; + private readonly int _BigLineWidth; + + public KitchenReceiptConverter(int lineWidth = 42, int bigLineWidth=22) + { + _BigLineWidth = bigLineWidth; + _lineWidth = lineWidth; + } + + public List ConvertToPrintCommands(KitchenReceipt kitchenReceipt) + { + var commands = new List(); + + // Header - Restaurant name in red + commands.Add(new PrintCommand(Center(kitchenReceipt.Title, true)) { IsRed = true , IsBig = true}); + commands.Add(new PrintCommand(CreateDashedLine())); + commands.Add(new PrintCommand("")); + + // Transaction info + string dateTimeLine = $"{kitchenReceipt.TransactionDateTime:dd-MMM-yy HH:mm} Nr.:{kitchenReceipt.ReceiptNumber}"; + commands.Add(new PrintCommand(Center(dateTimeLine, false))); + + string serverLine = $"{kitchenReceipt.WaiterName}"; + commands.Add(new PrintCommand(Center(serverLine, false))); + + string serverIdLine = $"{kitchenReceipt.WaiterId}"; + commands.Add(new PrintCommand(Center(serverIdLine, false))); + + string tableLine = $"Tisch: {kitchenReceipt.TableNumber}"; + commands.Add(new PrintCommand(Center(tableLine, true), true, true)); + + commands.Add(new PrintCommand(CreateDashedLine())); + + + + + // Gangs + + foreach (Gang gang in kitchenReceipt.Gangs) + { + + commands.Add(new PrintCommand(Center($"{gang.Id}. {gang.Name}", false)) { IsRed = true }); + foreach (var dish in gang.Dishes) + { + string dishLine = $"{dish.Number}x {dish.Name}"; + commands.Add(new PrintCommand(dishLine)); + } + } + + if (kitchenReceipt.Gangs.Count > 0) + { + commands.Add(new PrintCommand(CreateDashedLine())); + } + + foreach (var dish in kitchenReceipt.Dishes) + { + string dishLine = $"{dish.Number}x {dish.Name}"; + commands.Add(new PrintCommand(dishLine)); + } + + + commands.Add(new PrintCommand("")); + commands.Add(new PrintCommand(CreateDashedLine())); + commands.Add(new PrintCommand("")); + + //// Additional info section + //if (kitchenReceipt.AdditionalInfo != null && kitchenReceipt.AdditionalInfo.Count > 0) + //{ + // commands.Add(new PrintCommand(CreateAsterisksLine())); + // commands.Add(new PrintCommand("Info über zugehörige Artikel")); + // commands.Add(new PrintCommand(CreateAsterisksLine())); + + // foreach (var info in kitchenReceipt.AdditionalInfo) + // { + // commands.Add(new PrintCommand(info)); + // } + + // commands.Add(new PrintCommand("")); + //} + + return commands; + } + + private string Center(string text, bool isBigFont) + { + var actualLineWidth = isBigFont ? _BigLineWidth : _lineWidth; + if (string.IsNullOrEmpty(text) || text.Length >= actualLineWidth) + return text; + + int totalPadding = actualLineWidth - text.Length; + int leftPadding = totalPadding / 2; + + return new string(' ', leftPadding) + text; + } + + private string CreateDashedLine() + { + return new string('-', _lineWidth); + } + + private string CreateAsterisksLine() + { + return new string('*', _lineWidth); + } +} diff --git a/Inspectron.Epson/PrintServer/Printers/Utils/KitchenReceipt/Models.cs b/Inspectron.Epson/PrintServer/Printers/Utils/KitchenReceipt/Models.cs new file mode 100644 index 0000000..b9dde67 --- /dev/null +++ b/Inspectron.Epson/PrintServer/Printers/Utils/KitchenReceipt/Models.cs @@ -0,0 +1,92 @@ +namespace Inspectron.Epson.PrintServer.Printers.Utils.KitchenReceipt +{ + /// + /// Represents a restaurant receipt from Warme Küche + /// + public class KitchenReceipt + { + /// + /// Restaurant name + /// + public string Title { get; set; } + + /// + /// Date and time of the transaction + /// + public DateTime TransactionDateTime { get; set; } + + /// + /// Receipt number + /// + public string ReceiptNumber { get; set; } + + /// + /// Server/Waiter name + /// + public string WaiterName { get; set; } + + /// + /// Server/Waiter ID + /// + public string WaiterId { get; set; } + + /// + /// Table number + /// + public int TableNumber { get; set; } + + + public List Gangs { get; set; } = new List(); + + public List Dishes { get; set; } = new List(); + + /// + /// Additional items or notes (e.g., "Amuse Bouche") + /// + public List AdditionalInfo { get; set; } + + public KitchenReceipt() + { + Gangs = new List(); + AdditionalInfo = new List(); + } + } + + + public class Gang + { + public Gang(int id, string name) + { + Id = id; + Name = name; + } + + public int Id { get; set; } + public string Name { get; set; } + public List Dishes { get; set; } = new List(); + } + + /// + /// Represents a dish item on the receipt + /// + public class Dish + { + /// + /// Number ordered + /// + public int Number { get; set; } + + /// + /// Name of the dish + /// + public string Name { get; set; } + + public Dish(int number, string name) + { + Number = number; + Name = name; + } + } + + +} \ No newline at end of file diff --git a/Inspectron.Epson/PrintServer/Printers/Utils/KitchenReceiptPrinter.cs b/Inspectron.Epson/PrintServer/Printers/Utils/KitchenReceiptPrinter.cs index 950371c..8cbec8c 100644 --- a/Inspectron.Epson/PrintServer/Printers/Utils/KitchenReceiptPrinter.cs +++ b/Inspectron.Epson/PrintServer/Printers/Utils/KitchenReceiptPrinter.cs @@ -11,7 +11,7 @@ public class KitchenReceiptPrinter _bigFontLineWidth = bigFontLineWidth; } - public List ConvertToCommands(KitchenReceipt receipt) + public List ConvertToCommands(TranslatedKitchenReceipt receipt) { var commands = new List(); diff --git a/Inspectron.Epson/PrintServer/Printers/Utils/PrintCommand.cs b/Inspectron.Epson/PrintServer/Printers/Utils/PrintCommand.cs new file mode 100644 index 0000000..0424012 --- /dev/null +++ b/Inspectron.Epson/PrintServer/Printers/Utils/PrintCommand.cs @@ -0,0 +1,16 @@ +namespace Inspectron.Epson.PrintServer.Printers.Utils; + +public class PrintCommand +{ + public string Text { get; set; } + public bool IsBig { get; set; } + public bool IsBold { get; set; } + public bool IsRed { get; set; } + + public PrintCommand(string text, bool isBig = false, bool isBold = false) + { + Text = text; + IsBig = isBig; + IsBold = isBold; + } +} \ No newline at end of file diff --git a/Inspectron.Epson/PrintServer/Printers/Utils/ReceiptTranslator.cs b/Inspectron.Epson/PrintServer/Printers/Utils/ReceiptTranslator.cs index 5ce3283..15ddc50 100644 --- a/Inspectron.Epson/PrintServer/Printers/Utils/ReceiptTranslator.cs +++ b/Inspectron.Epson/PrintServer/Printers/Utils/ReceiptTranslator.cs @@ -1,7 +1,7 @@ namespace Inspectron.Epson.PrintServer.Printers.Utils; -public record KitchenReceipt(string Location, string Date, string Owner, string Tisch, List items) :ASTNode; +public record TranslatedKitchenReceipt(string Location, string Date, string Owner, string Tisch, List items) :ASTNode; public record KitchenItem(string Name) : ASTNode; public record KitchenProduct(int Amount, string Name) : KitchenItem(Name); public record KitchenGang(string Name) : KitchenItem(Name); @@ -112,7 +112,7 @@ public class ReceiptTranslator return new FinalReceipt(location, phone, url, date, items, total, mwst, comment, thanks); } - public static KitchenReceipt ParseKitchenReceipt(string receiptText) + public static TranslatedKitchenReceipt ParseKitchenReceipt(string receiptText) { var lines = receiptText.Split('\n', StringSplitOptions.RemoveEmptyEntries) .Select(l => l.Trim()) @@ -198,7 +198,7 @@ public class ReceiptTranslator currentIndex++; } - return new KitchenReceipt(location, date, owner, tisch, items); + return new TranslatedKitchenReceipt(location, date, owner, tisch, items); } diff --git a/Inspectron.Epson/PrintServer/WorkAreaSources/JamesWorkAreaSource.cs b/Inspectron.Epson/PrintServer/WorkAreaSources/JamesWorkAreaSource.cs index 13c2f83..bfec0ca 100644 --- a/Inspectron.Epson/PrintServer/WorkAreaSources/JamesWorkAreaSource.cs +++ b/Inspectron.Epson/PrintServer/WorkAreaSources/JamesWorkAreaSource.cs @@ -21,7 +21,7 @@ public class JamesWorkAreaSource: IWorkAreasSource response.Add(new WorkArea() { Id = "print-receipt", - Name = "Receipt" + Name = "FinalReceipt" }); return response!; } diff --git a/Screenshot 2025-12-14 201821_0bd9ce17.png b/Screenshot 2025-12-14 201821_0bd9ce17.png deleted file mode 100644 index 710de65..0000000 Binary files a/Screenshot 2025-12-14 201821_0bd9ce17.png and /dev/null differ diff --git a/kitchen_receipt.jpg b/kitchen_receipt.jpg new file mode 100644 index 0000000..dd9d65f Binary files /dev/null and b/kitchen_receipt.jpg differ