kitchen receipt + html printer
This commit is contained in:
@@ -1,12 +1,14 @@
|
|||||||
using Inspectron.Epson;
|
using Inspectron.Epson;
|
||||||
using Inspectron.Epson.PrintServer.Printers.Utils;
|
using Inspectron.Epson.PrintServer.Printers.Utils;
|
||||||
using Inspectron.Epson.PrintServer.Printers.Utils.FinalRecipe;
|
using Inspectron.Epson.PrintServer.Printers.Utils.FinalRecipe;
|
||||||
|
using Inspectron.Epson.PrintServer.Printers.Utils.KitchenReceipt;
|
||||||
using Inspectron.Epson.SOAP;
|
using Inspectron.Epson.SOAP;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
using FinalReceipt = Inspectron.Epson.PrintServer.Printers.Utils.FinalRecipe.FinalReceipt;
|
||||||
|
|
||||||
|
|
||||||
//SoapEpsonPrinter printer = new SoapEpsonPrinter("localhost:8888");
|
//SoapEpsonPrinter printer = new SoapEpsonPrinter("localhost:8888");
|
||||||
@@ -56,125 +58,202 @@ using System.Xml.Linq;
|
|||||||
//Console.ReadLine();
|
//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",
|
// Title = "Warme Küche",
|
||||||
// Address1 = "Münzplatz 3",
|
// TransactionDateTime = new DateTime(2025, 10, 23, 12, 18, 0),
|
||||||
// Address2 = "CH-8001 Zürich",
|
// ReceiptNumber = "132018166",
|
||||||
// Phone = "043 321 22 22",
|
// WaiterName = "Mariano Amato",
|
||||||
// ReceiptNumber = "1-81-202",
|
// WaiterId = "32 (VK Restaurant)",
|
||||||
// DateTime = new DateTime(2025, 12, 16, 14, 58, 0),
|
// TableNumber = 22
|
||||||
// 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."
|
|
||||||
//};
|
//};
|
||||||
|
|
||||||
//// Add items
|
//// Add dishes (this section can be empty if no dishes were ordered)
|
||||||
//receipt.Items.Add(new ReceiptItem
|
|
||||||
//{
|
|
||||||
// Quantity = 1,
|
|
||||||
// Description = "San Pellegrino 50cl",
|
|
||||||
// UnitPrice = 6.50m,
|
|
||||||
// TotalPrice = 6.50m,
|
|
||||||
// TaxCategory = "A"
|
|
||||||
//});
|
|
||||||
|
|
||||||
//receipt.Items.Add(new ReceiptItem
|
//receipt.Gangs.Add(new Gang(1, "Gang"));
|
||||||
//{
|
//receipt.Gangs.Add(new Gang(2, "Gang"));
|
||||||
// Quantity = 1,
|
|
||||||
// Description = "Panna 50cl",
|
|
||||||
// UnitPrice = 6.50m,
|
|
||||||
// TotalPrice = 6.50m,
|
|
||||||
// TaxCategory = "A"
|
|
||||||
//});
|
|
||||||
|
|
||||||
//receipt.Items.Add(new ReceiptItem
|
//receipt.Gangs[0].Dishes.Add(new Dish(1, "Avocado Sashimi"));
|
||||||
//{
|
//receipt.Gangs[0].Dishes.Add(new Dish(1, "Baby Spinach Salad with Truffl"));
|
||||||
// Quantity = 1,
|
//receipt.Gangs[0].Dishes.Add(new Dish(1, "Beef Tataki"));
|
||||||
// Description = "Granini Tomatensaft",
|
//receipt.Gangs[0].Dishes.Add(new Dish(1, "Salmon Taco"));
|
||||||
// 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
|
//receipt.Gangs[1].Dishes.Add(new Dish(1, "Avocado Sashimi"));
|
||||||
//var converter = new ReceiptConverter(lineWidth: 48, bigFontLineWidth: 24);
|
//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);
|
//var printCommands = converter.ConvertToPrintCommands(receipt);
|
||||||
|
|
||||||
//// Print the commands
|
|
||||||
//Console.WriteLine("=== PRINT COMMANDS ===\n");
|
//Console.WriteLine("=== PRINT COMMANDS ===\n");
|
||||||
//await printer.SetAbsolutePrintPosition(97);
|
//var printer = new HtmlPrinter(@".\test.html", paperWidth: 258);
|
||||||
//await printer.LoadImageAsync("ristorante-klinglers.ch-logo_white_bg.png",390);
|
//await printer.ConnectAsync("");
|
||||||
//await printer.SetAbsolutePrintPosition(0);
|
|
||||||
//await Task.Delay(200);
|
//await Task.Delay(200);
|
||||||
//await printer.FeedLinesAsync(1);
|
//await printer.FeedLinesAsync(1);
|
||||||
//await printer.SetCustomLineSpacing(22);
|
//await printer.SetCustomLineSpacing(22);
|
||||||
@@ -183,6 +262,7 @@ using System.Xml.Linq;
|
|||||||
// string attributes = "";
|
// string attributes = "";
|
||||||
// if (command.IsBig) attributes += "[BIG] ";
|
// if (command.IsBig) attributes += "[BIG] ";
|
||||||
// if (command.IsBold) attributes += "[BOLD] ";
|
// if (command.IsBold) attributes += "[BOLD] ";
|
||||||
|
// if (command.IsRed) attributes += "[RED] ";
|
||||||
|
|
||||||
// Console.WriteLine($"{attributes}{command.Text}");
|
// Console.WriteLine($"{attributes}{command.Text}");
|
||||||
|
|
||||||
@@ -194,6 +274,9 @@ using System.Xml.Linq;
|
|||||||
// {
|
// {
|
||||||
// await printer.SetFontSizeAsync(1, 1);
|
// await printer.SetFontSizeAsync(1, 1);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// await printer.SetRedColor(command.IsRed);
|
||||||
|
|
||||||
// //await Task.Delay(200);
|
// //await Task.Delay(200);
|
||||||
|
|
||||||
// await printer.SetEmphasized(command.IsBold);
|
// await printer.SetEmphasized(command.IsBold);
|
||||||
@@ -230,24 +313,24 @@ using System.Xml.Linq;
|
|||||||
//await Task.Delay(200);
|
//await Task.Delay(200);
|
||||||
|
|
||||||
//KitchenReceiptPrinter krp = new KitchenReceiptPrinter(33);
|
//KitchenReceiptPrinter krp = new KitchenReceiptPrinter(33);
|
||||||
var receipt = new KitchenReceipt(
|
//var receipt = new TranslatedKitchenReceipt(
|
||||||
Location: "Warme Küche",
|
// Location: "Warme Küche",
|
||||||
Date: "23-Okt-25 12:18 Nr.:13201B166",
|
// Date: "23-Okt-25 12:18 Nr.:13201B166",
|
||||||
Owner: "Mariano Amato\n32 (VK Restaurant)",
|
// Owner: "Mariano Amato\n32 (VK Restaurant)",
|
||||||
Tisch: "22",
|
// Tisch: "22",
|
||||||
items: new List<KitchenItem>
|
// items: new List<KitchenItem>
|
||||||
{
|
// {
|
||||||
new KitchenGang("1. Gang"),
|
// new KitchenGang("1. Gang"),
|
||||||
new KitchenProduct(1, "Avocado Sashimi"),
|
// new KitchenProduct(1, "Avocado Sashimi"),
|
||||||
new KitchenProduct(1, "Baby Spinach Salad with Truffl"),
|
// new KitchenProduct(1, "Baby Spinach Salad with Truffl"),
|
||||||
new KitchenGang("2. Gang"),
|
// new KitchenGang("2. Gang"),
|
||||||
new KitchenProduct(1, "Beef Tataki"),
|
// new KitchenProduct(1, "Beef Tataki"),
|
||||||
new KitchenProduct(1, "Salmon Taco")
|
// new KitchenProduct(1, "Salmon Taco")
|
||||||
}
|
// }
|
||||||
);
|
//);
|
||||||
|
|
||||||
var test = JsonSerializer.Serialize(receipt, new JsonSerializerOptions() { WriteIndented = true });
|
//var test = JsonSerializer.Serialize(receipt, new JsonSerializerOptions() { WriteIndented = true });
|
||||||
Console.WriteLine(test);
|
//Console.WriteLine(test);
|
||||||
|
|
||||||
////// Create printer with 42 character line width (default)
|
////// Create printer with 42 character line width (default)
|
||||||
//var commands = krp.ConvertToCommands(receipt);
|
//var commands = krp.ConvertToCommands(receipt);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Inspectron.Epson;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Main SDK class for Epson TM-m30III printer operations
|
/// Main SDK class for Epson TM-m30III printer operations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EpsonPrinter : IAsyncDisposable
|
public class EpsonPrinter : IEpsonPrinter
|
||||||
{
|
{
|
||||||
private readonly ILogger? _logger;
|
private readonly ILogger? _logger;
|
||||||
private TcpClient? _tcpClient;
|
private TcpClient? _tcpClient;
|
||||||
|
|||||||
700
Inspectron.Epson/HtmlPrinter.cs
Normal file
700
Inspectron.Epson/HtmlPrinter.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTML-based printer implementation that outputs to an HTML file,
|
||||||
|
/// simulating Epson thermal printer output for testing and preview purposes.
|
||||||
|
/// </summary>
|
||||||
|
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<Rgba32>? _loadedImage;
|
||||||
|
private int _loadedImageMaxWidth;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Dictionary<EpsonCommands.PrinterFont, (int Width, int Height)> 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) }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsConnected => _isConnected;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize a new instance of HtmlPrinter
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputPath">Path to the output HTML file</param>
|
||||||
|
/// <param name="paperWidth">Paper width in pixels (default 384 for 80mm thermal printer)</param>
|
||||||
|
/// <param name="logger">Optional logger for diagnostic output</param>
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
_logger?.LogDebug("HtmlPrinter: Disconnecting");
|
||||||
|
|
||||||
|
if (_isConnected)
|
||||||
|
{
|
||||||
|
WriteHtmlFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadedImage?.Dispose();
|
||||||
|
_loadedImage = null;
|
||||||
|
_isConnected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<PrinterStatus> 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<OfflineStatus> 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<ErrorStatus> 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<PaperSensorStatus> GetPaperSensorStatusAsync()
|
||||||
|
{
|
||||||
|
_logger?.LogDebug("HtmlPrinter: Returning mock paper sensor status (paper present)");
|
||||||
|
return Task.FromResult(new PaperSensorStatus
|
||||||
|
{
|
||||||
|
RawByte = 0x12,
|
||||||
|
PaperNearEnd = false,
|
||||||
|
PaperPresent = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<byte> GetTM220StatusAsync()
|
||||||
|
{
|
||||||
|
_logger?.LogDebug("HtmlPrinter: Returning mock TM220 status");
|
||||||
|
return Task.FromResult((byte)0x12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<byte?> GetPrinterIdAsync()
|
||||||
|
{
|
||||||
|
_logger?.LogDebug("HtmlPrinter: Returning mock printer ID (0x01 = TM-T30III)");
|
||||||
|
return Task.FromResult<byte?>(0x01);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<OverallStatus> 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<string>()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task InitAsync()
|
||||||
|
{
|
||||||
|
_logger?.LogInformation("HtmlPrinter: Initializing (resetting styling state)");
|
||||||
|
ResetStylingState();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task SetQuadrupleMode(bool enable)
|
||||||
|
{
|
||||||
|
_logger?.LogInformation("HtmlPrinter: {Action} quadruple mode", enable ? "Enabling" : "Disabling");
|
||||||
|
_isQuadrupleMode = enable;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task SetRedColor(bool enable)
|
||||||
|
{
|
||||||
|
_logger?.LogInformation("HtmlPrinter: {Action} red color mode", enable ? "Enabling" : "Disabling");
|
||||||
|
_isRedColor = enable;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task SetEmphasized(bool enable)
|
||||||
|
{
|
||||||
|
_logger?.LogInformation("HtmlPrinter: {Action} emphasized mode", enable ? "Enabling" : "Disabling");
|
||||||
|
_isEmphasized = enable;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task SelectFont(EpsonCommands.PrinterFont font)
|
||||||
|
{
|
||||||
|
_logger?.LogInformation("HtmlPrinter: Selecting font {Font}", font);
|
||||||
|
_currentFont = font;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task SetBiggerFontTM220(bool enabled)
|
||||||
|
{
|
||||||
|
_logger?.LogInformation("HtmlPrinter: {Action} bigger font mode for TM-T20/220", enabled ? "Enabling" : "Disabling");
|
||||||
|
_isBiggerFontTM220 = enabled;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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", "<br>");
|
||||||
|
|
||||||
|
// Add margin-left for absolute positioning
|
||||||
|
var positionStyle = _absolutePosition > 0 ? $"margin-left: {_absolutePosition}px;" : "";
|
||||||
|
|
||||||
|
_content.AppendLine($"<span style=\"{style}{positionStyle}\">{escapedText}</span>");
|
||||||
|
|
||||||
|
// Reset absolute position after printing
|
||||||
|
_absolutePosition = 0;
|
||||||
|
|
||||||
|
WriteHtmlFile();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task PrintBuffer()
|
||||||
|
{
|
||||||
|
_logger?.LogDebug("HtmlPrinter: PrintBuffer (no-op for HTML)");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task FeedLinesAsync(int lines)
|
||||||
|
{
|
||||||
|
EnsureConnected();
|
||||||
|
_logger?.LogInformation("HtmlPrinter: Feeding {Lines} lines", lines);
|
||||||
|
|
||||||
|
for (int i = 0; i < lines; i++)
|
||||||
|
{
|
||||||
|
_content.AppendLine($"<div style=\"height: {_lineSpacing}px;\"></div>");
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteHtmlFile();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task CutAsync()
|
||||||
|
{
|
||||||
|
EnsureConnected();
|
||||||
|
_logger?.LogInformation("HtmlPrinter: Cutting paper");
|
||||||
|
|
||||||
|
_content.AppendLine("<div class=\"cut\"><span class=\"scissors\">✂</span></div>");
|
||||||
|
|
||||||
|
WriteHtmlFile();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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<Rgba32>(imagePath);
|
||||||
|
_loadedImageMaxWidth = maxWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task LoadImageAsync(Stream imageStream, int maxWidth = 384)
|
||||||
|
{
|
||||||
|
_logger?.LogInformation("HtmlPrinter: Loading image from stream");
|
||||||
|
|
||||||
|
_loadedImage?.Dispose();
|
||||||
|
_loadedImage = await Image.LoadAsync<Rgba32>(imageStream);
|
||||||
|
_loadedImageMaxWidth = maxWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task LoadImageAsync(Image<Rgba32> image, int maxWidth = 384)
|
||||||
|
{
|
||||||
|
_logger?.LogInformation("HtmlPrinter: Loading image from ImageSharp object");
|
||||||
|
|
||||||
|
_loadedImage?.Dispose();
|
||||||
|
_loadedImage = image.Clone();
|
||||||
|
_loadedImageMaxWidth = maxWidth;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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<Rgba32>(imagePath);
|
||||||
|
await PrintImageBitModeAsync(image, mode, maxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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<Rgba32>(imageStream);
|
||||||
|
await PrintImageBitModeAsync(image, mode, maxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task PrintImageBitModeAsync(Image<Rgba32> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task SendRawCommandAsync(byte[] command)
|
||||||
|
{
|
||||||
|
_logger?.LogDebug("HtmlPrinter: SendRawCommandAsync ignored ({Length} bytes)", command.Length);
|
||||||
|
// Raw commands are ignored in HTML output
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task SetDefaultLineSpacing()
|
||||||
|
{
|
||||||
|
_logger?.LogInformation("HtmlPrinter: Setting default line spacing");
|
||||||
|
_lineSpacing = 14; // Default (scaled for screen)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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<string>();
|
||||||
|
|
||||||
|
// 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<Rgba32> 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<Rgba32>(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($"<div class=\"image\" style=\"{positionStyle}\"><img src=\"{relativeImagePath}\" alt=\"Printed image\"></div>");
|
||||||
|
|
||||||
|
// 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 $@"<!DOCTYPE html>
|
||||||
|
<html lang=""en"">
|
||||||
|
<head>
|
||||||
|
<meta charset=""UTF-8"">
|
||||||
|
<meta name=""viewport"" content=""width=device-width, initial-scale=1.0"">
|
||||||
|
<title>Epson Printer Output</title>
|
||||||
|
<style>
|
||||||
|
* {{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}}
|
||||||
|
|
||||||
|
body {{
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
padding: 20px;
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.receipt {{
|
||||||
|
width: {_paperWidth}px;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 0 auto;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.receipt span {{
|
||||||
|
display: inline;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.receipt .image {{
|
||||||
|
display: block;
|
||||||
|
margin: 5px 0;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.receipt .image img {{
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.cut {{
|
||||||
|
position: relative;
|
||||||
|
border-top: 2px dashed #333;
|
||||||
|
margin: 20px 0;
|
||||||
|
height: 0;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.cut .scissors {{
|
||||||
|
position: absolute;
|
||||||
|
left: -5px;
|
||||||
|
top: -12px;
|
||||||
|
font-size: 18px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 0 3px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
/* Simulated paper texture */
|
||||||
|
.receipt::before {{
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
transparent,
|
||||||
|
transparent 1px,
|
||||||
|
rgba(0,0,0,0.02) 1px,
|
||||||
|
rgba(0,0,0,0.02) 2px
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class=""receipt"">
|
||||||
|
{_content}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>";
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
181
Inspectron.Epson/IEpsonPrinter.cs
Normal file
181
Inspectron.Epson/IEpsonPrinter.cs
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
|
||||||
|
namespace Inspectron.Epson;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for Epson printer operations, allowing swappable implementations
|
||||||
|
/// (e.g., real printer vs HTML output for testing/preview)
|
||||||
|
/// </summary>
|
||||||
|
public interface IEpsonPrinter : IAsyncDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if connected to the printer
|
||||||
|
/// </summary>
|
||||||
|
bool IsConnected { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connect to the printer
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ip">Printer IP address (or identifier for non-network implementations)</param>
|
||||||
|
/// <param name="port">Printer port (default 9100)</param>
|
||||||
|
/// <param name="timeoutSeconds">Connection timeout in seconds (default 10)</param>
|
||||||
|
Task ConnectAsync(string ip, int port = 9100, int timeoutSeconds = 10);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnect from the printer
|
||||||
|
/// </summary>
|
||||||
|
void Disconnect();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get general printer status
|
||||||
|
/// </summary>
|
||||||
|
Task<PrinterStatus> GetPrinterStatusAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get offline status (reasons for offline)
|
||||||
|
/// </summary>
|
||||||
|
Task<OfflineStatus> GetOfflineStatusAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get error status
|
||||||
|
/// </summary>
|
||||||
|
Task<ErrorStatus> GetErrorStatusAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get paper sensor status
|
||||||
|
/// </summary>
|
||||||
|
Task<PaperSensorStatus> GetPaperSensorStatusAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get TM-T20/220 specific status byte
|
||||||
|
/// </summary>
|
||||||
|
Task<byte> GetTM220StatusAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get printer model/ID
|
||||||
|
/// </summary>
|
||||||
|
Task<byte?> GetPrinterIdAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get comprehensive overall status with health assessment
|
||||||
|
/// </summary>
|
||||||
|
Task<OverallStatus> GetOverallStatusAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize the printer
|
||||||
|
/// </summary>
|
||||||
|
Task InitAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable or disable quadruple size mode
|
||||||
|
/// </summary>
|
||||||
|
Task SetQuadrupleMode(bool enable);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable or disable red color printing
|
||||||
|
/// </summary>
|
||||||
|
Task SetRedColor(bool enable);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable or disable emphasized (bold) mode
|
||||||
|
/// </summary>
|
||||||
|
Task SetEmphasized(bool enable);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Select printer font
|
||||||
|
/// </summary>
|
||||||
|
Task SelectFont(EpsonCommands.PrinterFont font);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable or disable bigger font mode for TM-T20/220
|
||||||
|
/// </summary>
|
||||||
|
Task SetBiggerFontTM220(bool enabled);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Print text to the printer
|
||||||
|
/// </summary>
|
||||||
|
Task PrintTextAsync(string text);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set font size (magnification)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="width">Width magnification (1-8)</param>
|
||||||
|
/// <param name="height">Height magnification (1-8)</param>
|
||||||
|
Task SetFontSizeAsync(int width, int height);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Print text, feed paper, and cut
|
||||||
|
/// </summary>
|
||||||
|
Task PrintTextAndCutAsync(string text, int feedLines = 5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Print the current buffer
|
||||||
|
/// </summary>
|
||||||
|
Task PrintBuffer();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Feed specified number of lines
|
||||||
|
/// </summary>
|
||||||
|
Task FeedLinesAsync(int lines);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cut the paper
|
||||||
|
/// </summary>
|
||||||
|
Task CutAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load an image from a file path into the printer buffer
|
||||||
|
/// </summary>
|
||||||
|
Task LoadImageAsync(string imagePath, int maxWidth = 384);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load an image from a stream into the printer buffer
|
||||||
|
/// </summary>
|
||||||
|
Task LoadImageAsync(Stream imageStream, int maxWidth = 384);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load an image from an ImageSharp Image object into the printer buffer
|
||||||
|
/// </summary>
|
||||||
|
Task LoadImageAsync(Image<Rgba32> image, int maxWidth = 384);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Print the previously loaded image
|
||||||
|
/// </summary>
|
||||||
|
Task PrintLoadedImage();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Print an image using bit-image mode from a file path
|
||||||
|
/// </summary>
|
||||||
|
Task PrintImageBitModeAsync(string imagePath, EpsonCommands.BitImageMode mode = EpsonCommands.BitImageMode.SingleDensity8Dot, int maxWidth = 10);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Print an image using bit-image mode from a stream
|
||||||
|
/// </summary>
|
||||||
|
Task PrintImageBitModeAsync(Stream imageStream, EpsonCommands.BitImageMode mode = EpsonCommands.BitImageMode.DoubleDensity24Dot, int maxWidth = 384);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Print an image using bit-image mode from an ImageSharp Image object
|
||||||
|
/// </summary>
|
||||||
|
Task PrintImageBitModeAsync(Image<Rgba32> image, EpsonCommands.BitImageMode mode, int maxWidth);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send raw ESC/POS command bytes to the printer
|
||||||
|
/// </summary>
|
||||||
|
Task SendRawCommandAsync(byte[] command);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the absolute horizontal print position
|
||||||
|
/// </summary>
|
||||||
|
Task SetAbsolutePrintPosition(int x);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set default line spacing
|
||||||
|
/// </summary>
|
||||||
|
Task SetDefaultLineSpacing();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set custom line spacing
|
||||||
|
/// </summary>
|
||||||
|
Task SetCustomLineSpacing(int spacing = 24);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
using Inspectron.Epson.PrintServer.Printers.Utils;
|
using Inspectron.Epson.PrintServer.Printers.Utils;
|
||||||
using Inspectron.Epson.PrintServer.Printers.Utils.FinalRecipe;
|
using Inspectron.Epson.PrintServer.Printers.Utils.FinalRecipe;
|
||||||
using Inspectron.Epson.PrintServer.PrintServices;
|
using Inspectron.Epson.PrintServer.PrintServices;
|
||||||
|
using FinalReceipt = Inspectron.Epson.PrintServer.Printers.Utils.FinalRecipe.FinalReceipt;
|
||||||
|
|
||||||
namespace Inspectron.Epson.PrintServer.Printers;
|
namespace Inspectron.Epson.PrintServer.Printers;
|
||||||
|
|
||||||
@@ -35,10 +36,10 @@ public class TM_T30IIITranslated:IPrinter
|
|||||||
|
|
||||||
public async Task PrintTextAsync(string text)
|
public async Task PrintTextAsync(string text)
|
||||||
{
|
{
|
||||||
Receipt? receipt;
|
FinalReceipt? receipt;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
receipt = JsonSerializer.Deserialize<Receipt>(text);
|
receipt = JsonSerializer.Deserialize<FinalReceipt>(text);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -84,7 +85,7 @@ public class TM_T30IIITranslated:IPrinter
|
|||||||
ReceiptTranslator translator = new ReceiptTranslator();
|
ReceiptTranslator translator = new ReceiptTranslator();
|
||||||
var ast = translator.ParseReceipt(text);
|
var ast = translator.ParseReceipt(text);
|
||||||
KitchenReceiptPrinter printer = new KitchenReceiptPrinter(lineWidth: 33, 21);
|
KitchenReceiptPrinter printer = new KitchenReceiptPrinter(lineWidth: 33, 21);
|
||||||
var commands = printer.ConvertToCommands((KitchenReceipt)ast);
|
var commands = printer.ConvertToCommands((TranslatedKitchenReceipt)ast);
|
||||||
foreach (KitchenPrintCommand command in commands)
|
foreach (KitchenPrintCommand command in commands)
|
||||||
{
|
{
|
||||||
if (command.IsBig)
|
if (command.IsBig)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public class TM_U220IITranslated:IPrinter
|
|||||||
ReceiptTranslator translator = new ReceiptTranslator();
|
ReceiptTranslator translator = new ReceiptTranslator();
|
||||||
var ast = translator.ParseReceipt(text);
|
var ast = translator.ParseReceipt(text);
|
||||||
KitchenReceiptPrinter printer = new KitchenReceiptPrinter(lineWidth:33,21);
|
KitchenReceiptPrinter printer = new KitchenReceiptPrinter(lineWidth:33,21);
|
||||||
var commands = printer.ConvertToCommands((KitchenReceipt)ast);
|
var commands = printer.ConvertToCommands((TranslatedKitchenReceipt)ast);
|
||||||
foreach (KitchenPrintCommand command in commands)
|
foreach (KitchenPrintCommand command in commands)
|
||||||
{
|
{
|
||||||
await _printer.SetBiggerFontTM220(command.IsBig);
|
await _printer.SetBiggerFontTM220(command.IsBig);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Inspectron.Epson.PrintServer.Printers.Utils.FinalRecipe;
|
namespace Inspectron.Epson.PrintServer.Printers.Utils.FinalRecipe;
|
||||||
|
|
||||||
public class Receipt
|
public class FinalReceipt
|
||||||
{
|
{
|
||||||
public string CompanyName { get; set; }
|
public string CompanyName { get; set; }
|
||||||
public string Address1 { get; set; }
|
public string Address1 { get; set; }
|
||||||
@@ -17,7 +17,7 @@ public class Receipt
|
|||||||
public string PaymentMethod { get; set; }
|
public string PaymentMethod { get; set; }
|
||||||
public decimal PaymentAmount { get; set; }
|
public decimal PaymentAmount { get; set; }
|
||||||
public List<TaxInfo> TaxBreakdown { get; set; } = new List<TaxInfo>();
|
public List<TaxInfo> TaxBreakdown { get; set; } = new List<TaxInfo>();
|
||||||
public string ServerName { get; set; }
|
public string WaiterName { get; set; }
|
||||||
public string Terminal { get; set; }
|
public string Terminal { get; set; }
|
||||||
public string TableNumber { get; set; }
|
public string TableNumber { get; set; }
|
||||||
public string VatNumber { get; set; }
|
public string VatNumber { get; set; }
|
||||||
@@ -43,18 +43,4 @@ public class TaxInfo
|
|||||||
public decimal Net { get; set; }
|
public decimal Net { get; set; }
|
||||||
public decimal TaxAmount { get; set; }
|
public decimal TaxAmount { get; set; }
|
||||||
public string Currency { 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -11,26 +11,26 @@ public class ReceiptConverter
|
|||||||
_bigFontLineWidth = bigFontLineWidth;
|
_bigFontLineWidth = bigFontLineWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PrintCommand> ConvertToPrintCommands(Receipt receipt)
|
public List<PrintCommand> ConvertToPrintCommands(FinalReceipt finalReceipt)
|
||||||
{
|
{
|
||||||
var commands = new List<PrintCommand>();
|
var commands = new List<PrintCommand>();
|
||||||
|
|
||||||
// Header - Company info
|
// Header - Company info
|
||||||
commands.Add(new PrintCommand(Center(receipt.CompanyName, false)));
|
commands.Add(new PrintCommand(Center(finalReceipt.CompanyName, false)));
|
||||||
commands.Add(new PrintCommand(Center(receipt.Address1, false)));
|
commands.Add(new PrintCommand(Center(finalReceipt.Address1, false)));
|
||||||
commands.Add(new PrintCommand(Center(receipt.Address2, false)));
|
commands.Add(new PrintCommand(Center(finalReceipt.Address2, false)));
|
||||||
commands.Add(new PrintCommand(Center(receipt.Phone, false)));
|
commands.Add(new PrintCommand(Center(finalReceipt.Phone, false)));
|
||||||
commands.Add(new PrintCommand(""));
|
commands.Add(new PrintCommand(""));
|
||||||
commands.Add(new PrintCommand(""));
|
commands.Add(new PrintCommand(""));
|
||||||
|
|
||||||
// Receipt info
|
// FinalReceipt info
|
||||||
string receiptLine = $"Rechnung Nr. {receipt.ReceiptNumber}".PadRight(_lineWidth/2)+$"{receipt.DateTime:HH:mm dd.MM.yyyy}".PadLeft(_lineWidth/2);
|
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(receiptLine,isBold:true));
|
||||||
commands.Add(new PrintCommand($"Guests: {receipt.Guests}"));
|
commands.Add(new PrintCommand($"Guests: {finalReceipt.Guests}"));
|
||||||
commands.Add(new PrintCommand(""));
|
commands.Add(new PrintCommand(""));
|
||||||
|
|
||||||
// Items
|
// Items
|
||||||
foreach (var item in receipt.Items)
|
foreach (var item in finalReceipt.Items)
|
||||||
{
|
{
|
||||||
string quantityDesc = $"{item.Quantity}x {item.Description}";
|
string quantityDesc = $"{item.Quantity}x {item.Description}";
|
||||||
string prices = $"{item.UnitPrice:F2}"+$"{item.TotalPrice:F2}".PadLeft(7)+$" {item.TaxCategory}";
|
string prices = $"{item.UnitPrice:F2}"+$"{item.TotalPrice:F2}".PadLeft(7)+$" {item.TaxCategory}";
|
||||||
@@ -48,31 +48,31 @@ public class ReceiptConverter
|
|||||||
commands.Add(new PrintCommand("")); //Reini
|
commands.Add(new PrintCommand("")); //Reini
|
||||||
|
|
||||||
// Total
|
// 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(Center(totalLine, true), true, true));
|
||||||
commands.Add(new PrintCommand(""));
|
commands.Add(new PrintCommand(""));
|
||||||
|
|
||||||
// Alternate currency
|
// 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(altCurrencyLine.PadLeft(_lineWidth)));
|
||||||
commands.Add(new PrintCommand(""));
|
commands.Add(new PrintCommand(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Payment method
|
// Payment method
|
||||||
string paymentLine = $"{receipt.PaymentMethod}";
|
string paymentLine = $"{finalReceipt.PaymentMethod}";
|
||||||
string paymentAmount = $"{receipt.PaymentAmount:F2} {receipt.Currency}";
|
string paymentAmount = $"{finalReceipt.PaymentAmount:F2} {finalReceipt.Currency}";
|
||||||
int paymentSpaces = _lineWidth - paymentLine.Length - paymentAmount.Length;
|
int paymentSpaces = _lineWidth - paymentLine.Length - paymentAmount.Length;
|
||||||
if (paymentSpaces < 1) paymentSpaces = 1;
|
if (paymentSpaces < 1) paymentSpaces = 1;
|
||||||
commands.Add(new PrintCommand(paymentLine + new string(' ', paymentSpaces) + paymentAmount,isBold:true));
|
commands.Add(new PrintCommand(paymentLine + new string(' ', paymentSpaces) + paymentAmount,isBold:true));
|
||||||
commands.Add(new PrintCommand(""));
|
commands.Add(new PrintCommand(""));
|
||||||
|
|
||||||
// Tax breakdown
|
// 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);
|
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));
|
commands.Add(new PrintCommand(taxLine));
|
||||||
}
|
}
|
||||||
@@ -83,22 +83,22 @@ public class ReceiptConverter
|
|||||||
commands.Add(new PrintCommand(""));
|
commands.Add(new PrintCommand(""));
|
||||||
|
|
||||||
// Footer info
|
// Footer info
|
||||||
//commands.Add(new PrintCommand(Center($"Bedient von: {receipt.ServerName}", false)));
|
//commands.Add(new PrintCommand(Center($"Bedient von: {finalReceipt.WaiterName}", false)));
|
||||||
//commands.Add(new PrintCommand(Center($"Terminal: {receipt.Terminal}", false)));
|
//commands.Add(new PrintCommand(Center($"Terminal: {finalReceipt.Terminal}", false)));
|
||||||
//commands.Add(new PrintCommand(Center($"Tisch: {receipt.TableNumber}", false)));
|
//commands.Add(new PrintCommand(Center($"Tisch: {finalReceipt.TableNumber}", false)));
|
||||||
commands.Add(new PrintCommand($"Bedient von:".PadLeft(_lineWidth / 2) + $" {receipt.ServerName}"));
|
commands.Add(new PrintCommand($"Bedient von:".PadLeft(_lineWidth / 2) + $" {finalReceipt.WaiterName}"));
|
||||||
commands.Add(new PrintCommand($"Terminal:".PadLeft(_lineWidth / 2) + $" {receipt.Terminal}"));
|
commands.Add(new PrintCommand($"Terminal:".PadLeft(_lineWidth / 2) + $" {finalReceipt.Terminal}"));
|
||||||
commands.Add(new PrintCommand($"Tisch:".PadLeft(_lineWidth / 2)+$" {receipt.TableNumber}"));
|
commands.Add(new PrintCommand($"Tisch:".PadLeft(_lineWidth / 2)+$" {finalReceipt.TableNumber}"));
|
||||||
commands.Add(new PrintCommand(""));
|
commands.Add(new PrintCommand(""));
|
||||||
commands.Add(new PrintCommand(""));
|
commands.Add(new PrintCommand(""));
|
||||||
|
|
||||||
// VAT number
|
// VAT number
|
||||||
commands.Add(new PrintCommand(Center(receipt.VatNumber, false)));
|
commands.Add(new PrintCommand(Center(finalReceipt.VatNumber, false)));
|
||||||
|
|
||||||
// Thank you message
|
// Thank you message
|
||||||
commands.Add(new PrintCommand(Center(receipt.ThankYouMessage, false)));
|
commands.Add(new PrintCommand(Center(finalReceipt.ThankYouMessage, false)));
|
||||||
commands.Add(new PrintCommand(Center(receipt.GoodbyeMessageLine1, false)));
|
commands.Add(new PrintCommand(Center(finalReceipt.GoodbyeMessageLine1, false)));
|
||||||
commands.Add(new PrintCommand(Center(receipt.GoodbyeMessageLine2, false)));
|
commands.Add(new PrintCommand(Center(finalReceipt.GoodbyeMessageLine2, false)));
|
||||||
|
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<PrintCommand> ConvertToPrintCommands(KitchenReceipt kitchenReceipt)
|
||||||
|
{
|
||||||
|
var commands = new List<PrintCommand>();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
namespace Inspectron.Epson.PrintServer.Printers.Utils.KitchenReceipt
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a restaurant receipt from Warme Küche
|
||||||
|
/// </summary>
|
||||||
|
public class KitchenReceipt
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Restaurant name
|
||||||
|
/// </summary>
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Date and time of the transaction
|
||||||
|
/// </summary>
|
||||||
|
public DateTime TransactionDateTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Receipt number
|
||||||
|
/// </summary>
|
||||||
|
public string ReceiptNumber { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server/Waiter name
|
||||||
|
/// </summary>
|
||||||
|
public string WaiterName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server/Waiter ID
|
||||||
|
/// </summary>
|
||||||
|
public string WaiterId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Table number
|
||||||
|
/// </summary>
|
||||||
|
public int TableNumber { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public List<Gang> Gangs { get; set; } = new List<Gang>();
|
||||||
|
|
||||||
|
public List<Dish> Dishes { get; set; } = new List<Dish>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Additional items or notes (e.g., "Amuse Bouche")
|
||||||
|
/// </summary>
|
||||||
|
public List<string> AdditionalInfo { get; set; }
|
||||||
|
|
||||||
|
public KitchenReceipt()
|
||||||
|
{
|
||||||
|
Gangs = new List<Gang>();
|
||||||
|
AdditionalInfo = new List<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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<Dish> Dishes { get; set; } = new List<Dish>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a dish item on the receipt
|
||||||
|
/// </summary>
|
||||||
|
public class Dish
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Number ordered
|
||||||
|
/// </summary>
|
||||||
|
public int Number { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the dish
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public Dish(int number, string name)
|
||||||
|
{
|
||||||
|
Number = number;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ public class KitchenReceiptPrinter
|
|||||||
_bigFontLineWidth = bigFontLineWidth;
|
_bigFontLineWidth = bigFontLineWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<KitchenPrintCommand> ConvertToCommands(KitchenReceipt receipt)
|
public List<KitchenPrintCommand> ConvertToCommands(TranslatedKitchenReceipt receipt)
|
||||||
{
|
{
|
||||||
var commands = new List<KitchenPrintCommand>();
|
var commands = new List<KitchenPrintCommand>();
|
||||||
|
|
||||||
|
|||||||
16
Inspectron.Epson/PrintServer/Printers/Utils/PrintCommand.cs
Normal file
16
Inspectron.Epson/PrintServer/Printers/Utils/PrintCommand.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
namespace Inspectron.Epson.PrintServer.Printers.Utils;
|
namespace Inspectron.Epson.PrintServer.Printers.Utils;
|
||||||
|
|
||||||
|
|
||||||
public record KitchenReceipt(string Location, string Date, string Owner, string Tisch, List<KitchenItem> items) :ASTNode;
|
public record TranslatedKitchenReceipt(string Location, string Date, string Owner, string Tisch, List<KitchenItem> items) :ASTNode;
|
||||||
public record KitchenItem(string Name) : ASTNode;
|
public record KitchenItem(string Name) : ASTNode;
|
||||||
public record KitchenProduct(int Amount, string Name) : KitchenItem(Name);
|
public record KitchenProduct(int Amount, string Name) : KitchenItem(Name);
|
||||||
public record KitchenGang(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);
|
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)
|
var lines = receiptText.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||||||
.Select(l => l.Trim())
|
.Select(l => l.Trim())
|
||||||
@@ -198,7 +198,7 @@ public class ReceiptTranslator
|
|||||||
currentIndex++;
|
currentIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new KitchenReceipt(location, date, owner, tisch, items);
|
return new TranslatedKitchenReceipt(location, date, owner, tisch, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class JamesWorkAreaSource: IWorkAreasSource
|
|||||||
response.Add(new WorkArea()
|
response.Add(new WorkArea()
|
||||||
{
|
{
|
||||||
Id = "print-receipt",
|
Id = "print-receipt",
|
||||||
Name = "Receipt"
|
Name = "FinalReceipt"
|
||||||
});
|
});
|
||||||
return response!;
|
return response!;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.8 KiB |
BIN
kitchen_receipt.jpg
Normal file
BIN
kitchen_receipt.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
Reference in New Issue
Block a user