Initial commit

Initial commit til Git.
V2 er deployed
This commit is contained in:
2026-06-13 17:31:50 +02:00
parent 9fcd2b145e
commit 41e23b6184
375 changed files with 15956 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
using System.Text;
namespace EpsonReceiptPrinter;
public static class EncodingHelper
{
static EncodingHelper()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
}
public static Encoding GetEncodingForCodePage(PrinterCodePage codePage) =>
codePage switch
{
PrinterCodePage.PC865_Nordic => Encoding.GetEncoding(865),
PrinterCodePage.PC850_Multilingual => Encoding.GetEncoding(850),
PrinterCodePage.PC858_Euro => Encoding.GetEncoding(858),
PrinterCodePage.WPC1252 => Encoding.GetEncoding(1252),
_ => Encoding.GetEncoding(865)
};
}

View File

@@ -0,0 +1,42 @@
using System.Net.Sockets;
namespace EpsonReceiptPrinter;
public sealed class EpsonNetworkReceiptPrinter : IReceiptPrinter
{
private readonly string _host;
private readonly int _port;
private readonly PrinterCodePage _codePage;
public EpsonNetworkReceiptPrinter(string host, int port = 9100, PrinterCodePage codePage = PrinterCodePage.PC865_Nordic)
{
if (string.IsNullOrWhiteSpace(host))
{
throw new ArgumentException("Host must be provided.", nameof(host));
}
_host = host;
_port = port;
_codePage = codePage;
}
public async Task PrintAsync(Receipt receipt, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(receipt);
var data = ReceiptFormatter.Format(receipt, _codePage);
await PrintRawAsync(data, cancellationToken);
}
public async Task PrintRawAsync(byte[] data, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(data);
using var client = new TcpClient();
await client.ConnectAsync(_host, _port, cancellationToken);
await using var stream = client.GetStream();
await stream.WriteAsync(data, cancellationToken);
await stream.FlushAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,147 @@
using System.Text;
namespace EpsonReceiptPrinter;
public sealed class EscPosBuilder
{
private readonly List<byte> _buffer = new();
private readonly Encoding _encoding;
private PrinterCodePage _codePage;
public EscPosBuilder(PrinterCodePage codePage = PrinterCodePage.PC865_Nordic, Encoding? encoding = null)
{
_codePage = codePage;
_encoding = encoding ?? EncodingHelper.GetEncodingForCodePage(codePage);
}
public EscPosBuilder Initialize()
{
_buffer.AddRange(EscPosCommands.Initialize);
_buffer.AddRange(EscPosCommands.SelectCodePage(_codePage));
return this;
}
public EscPosBuilder SelectCodePage(PrinterCodePage codePage)
{
_codePage = codePage;
_buffer.AddRange(EscPosCommands.SelectCodePage(codePage));
return this;
}
public EscPosBuilder AlignLeft()
{
_buffer.AddRange(EscPosCommands.AlignLeft);
return this;
}
public EscPosBuilder AlignCenter()
{
_buffer.AddRange(EscPosCommands.AlignCenter);
return this;
}
public EscPosBuilder AlignRight()
{
_buffer.AddRange(EscPosCommands.AlignRight);
return this;
}
public EscPosBuilder BoldOn()
{
_buffer.AddRange(EscPosCommands.BoldOn);
return this;
}
public EscPosBuilder BoldOff()
{
_buffer.AddRange(EscPosCommands.BoldOff);
return this;
}
public EscPosBuilder UnderlineOn()
{
_buffer.AddRange(EscPosCommands.UnderlineOn);
return this;
}
public EscPosBuilder UnderlineOff()
{
_buffer.AddRange(EscPosCommands.UnderlineOff);
return this;
}
public EscPosBuilder SetTextSize(TextSize size)
{
_buffer.AddRange(EscPosCommands.TextSize(size));
return this;
}
public EscPosBuilder SetTextSize(int widthMultiplier, int heightMultiplier)
{
_buffer.AddRange(EscPosCommands.TextSize(widthMultiplier, heightMultiplier));
return this;
}
public EscPosBuilder Write(string text)
{
if (!string.IsNullOrEmpty(text))
{
_buffer.AddRange(_encoding.GetBytes(text));
}
return this;
}
public EscPosBuilder WriteLine(string? text = null)
{
if (!string.IsNullOrEmpty(text))
{
_buffer.AddRange(_encoding.GetBytes(text));
}
_buffer.Add(0x0A);
return this;
}
public EscPosBuilder Feed(int lines = 1)
{
_buffer.AddRange(EscPosCommands.Feed(lines));
return this;
}
public EscPosBuilder Cut(bool partial = false)
{
_buffer.AddRange(partial ? EscPosCommands.CutPartial() : EscPosCommands.CutFull());
return this;
}
public EscPosBuilder OpenCashDrawer()
{
_buffer.AddRange(EscPosCommands.OpenCashDrawer());
return this;
}
public EscPosBuilder Separator(int width = 42, char character = '-')
{
return WriteLine(new string(character, width));
}
public EscPosBuilder WriteColumns(string left, string? right, int totalWidth = 42)
{
left ??= string.Empty;
right ??= string.Empty;
if (left.Length + right.Length >= totalWidth)
{
WriteLine(left);
WriteLine(right.PadLeft(totalWidth));
return this;
}
var spaces = totalWidth - left.Length - right.Length;
WriteLine(left + new string(' ', spaces) + right);
return this;
}
public byte[] Build() => _buffer.ToArray();
}

View File

@@ -0,0 +1,63 @@
namespace EpsonReceiptPrinter;
public static class EscPosCommands
{
public static readonly byte[] Initialize = [0x1B, 0x40];
public static readonly byte[] BoldOn = [0x1B, 0x45, 0x01];
public static readonly byte[] BoldOff = [0x1B, 0x45, 0x00];
public static readonly byte[] UnderlineOn = [0x1B, 0x2D, 0x01];
public static readonly byte[] UnderlineOff = [0x1B, 0x2D, 0x00];
public static readonly byte[] AlignLeft = [0x1B, 0x61, 0x00];
public static readonly byte[] AlignCenter = [0x1B, 0x61, 0x01];
public static readonly byte[] AlignRight = [0x1B, 0x61, 0x02];
public static readonly byte[] FontA = [0x1B, 0x4D, 0x00];
public static readonly byte[] FontB = [0x1B, 0x4D, 0x01];
public static byte[] Feed(int lines)
{
if (lines < 0 || lines > 255)
{
throw new ArgumentOutOfRangeException(nameof(lines));
}
return [0x1B, 0x64, (byte)lines];
}
public static byte[] CutFull() => [0x1D, 0x56, 0x00];
public static byte[] CutPartial() => [0x1D, 0x56, 0x01];
public static byte[] OpenCashDrawer() => [0x1B, 0x70, 0x00, 0x19, 0xFA];
public static byte[] SelectCodePage(PrinterCodePage codePage) => [0x1B, 0x74, (byte)codePage];
public static byte[] TextSize(TextSize size) =>
size switch
{
TextSize.Normal => [0x1D, 0x21, 0x00],
TextSize.DoubleWidth => [0x1D, 0x21, 0x10],
TextSize.DoubleHeight => [0x1D, 0x21, 0x01],
TextSize.DoubleWidthAndHeight => [0x1D, 0x21, 0x11],
_ => [0x1D, 0x21, 0x00]
};
public static byte[] TextSize(int widthMultiplier, int heightMultiplier)
{
if (widthMultiplier < 1 || widthMultiplier > 8)
{
throw new ArgumentOutOfRangeException(nameof(widthMultiplier));
}
if (heightMultiplier < 1 || heightMultiplier > 8)
{
throw new ArgumentOutOfRangeException(nameof(heightMultiplier));
}
var value = (byte)(((widthMultiplier - 1) << 4) | (heightMultiplier - 1));
return [0x1D, 0x21, value];
}
}

View File

@@ -0,0 +1,7 @@
namespace EpsonReceiptPrinter;
public interface IReceiptPrinter
{
Task PrintAsync(Receipt receipt, CancellationToken cancellationToken = default);
Task PrintRawAsync(byte[] data, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,13 @@
namespace EpsonReceiptPrinter;
public enum PrinterCodePage
{
PC437_USA = 0,
Katakana = 1,
PC850_Multilingual = 2,
PC860_Portuguese = 3,
PC863_CanadianFrench = 4,
PC865_Nordic = 5,
WPC1252 = 16,
PC858_Euro = 19
}

View File

@@ -0,0 +1,31 @@
namespace EpsonReceiptPrinter;
public sealed class Receipt
{
public string? Header { get; set; }
public string? SubHeader { get; set; }
public List<ReceiptLine> Lines { get; } = new();
public string? Footer { get; set; }
public bool CutPaper { get; set; } = true;
public int PaperWidthCharacters { get; set; } = 42;
}
public sealed class ReceiptLine
{
public ReceiptLine()
{
}
public ReceiptLine(string left, string? right = null, bool emphasize = false, TextSize textSize = TextSize.Normal)
{
Left = left;
Right = right;
Emphasize = emphasize;
TextSize = textSize;
}
public string Left { get; set; } = string.Empty;
public string? Right { get; set; }
public bool Emphasize { get; set; }
public TextSize TextSize { get; set; } = TextSize.Normal;
}

View File

@@ -0,0 +1,72 @@
namespace EpsonReceiptPrinter;
public static class ReceiptFormatter
{
public static byte[] Format(Receipt receipt, PrinterCodePage codePage = PrinterCodePage.PC865_Nordic)
{
ArgumentNullException.ThrowIfNull(receipt);
var width = receipt.PaperWidthCharacters <= 0 ? 42 : receipt.PaperWidthCharacters;
var builder = new EscPosBuilder(codePage)
.Initialize()
.AlignCenter();
if (!string.IsNullOrWhiteSpace(receipt.Header))
{
builder
.SetTextSize(TextSize.DoubleWidthAndHeight)
.BoldOn()
.WriteLine(receipt.Header)
.BoldOff()
.SetTextSize(TextSize.Normal);
}
if (!string.IsNullOrWhiteSpace(receipt.SubHeader))
{
builder.WriteLine(receipt.SubHeader);
}
builder
.Feed(1)
.AlignLeft()
.Separator(width);
foreach (var line in receipt.Lines)
{
builder.SetTextSize(line.TextSize);
if (line.Emphasize)
{
builder.BoldOn();
}
else
{
builder.BoldOff();
}
builder.WriteColumns(line.Left, line.Right, width);
builder.SetTextSize(TextSize.Normal);
builder.BoldOff();
}
builder
.Separator(width)
.Feed(1)
.AlignCenter();
if (!string.IsNullOrWhiteSpace(receipt.Footer))
{
builder.WriteLine(receipt.Footer);
}
builder.Feed(3);
if (receipt.CutPaper)
{
builder.Cut();
}
return builder.Build();
}
}

View File

@@ -0,0 +1,9 @@
namespace EpsonReceiptPrinter;
public enum TextSize
{
Normal,
DoubleWidth,
DoubleHeight,
DoubleWidthAndHeight
}