From eca5ced38afeb35ec11ff9073277524b6a02fcc8 Mon Sep 17 00:00:00 2001 From: EugeneTes Date: Wed, 4 Feb 2026 14:20:08 +0100 Subject: [PATCH] editor first run --- .claude/settings.local.json | 9 +- .../Templates/FinalReceipt.json | 166 +++++ Inspectron.Epson.slnx | 7 +- Inspectron.Epson/HtmlPrintBuilder.cs | 652 ++++++++++++++++++ .../PrintServer/PrintServices/IPrinter.cs | 1 - .../App.xaml | 17 + .../App.xaml.cs | 15 + .../Components/Layout/MainLayout.razor | 3 + .../Components/Layout/MainLayout.razor.css | 0 .../Components/Pages/Home.razor | 113 +++ .../Components/Pages/NotFound.razor | 5 + .../Components/Routes.razor | 6 + .../Components/_Imports.razor | 10 + ...spectron.PrintServer.TemplateEditor.csproj | 63 ++ .../MainPage.xaml | 14 + .../MainPage.xaml.cs | 10 + .../MauiProgram.cs | 27 + .../Platforms/Windows/App.xaml | 8 + .../Platforms/Windows/App.xaml.cs | 25 + .../Platforms/Windows/Package.appxmanifest | 46 ++ .../Platforms/Windows/app.manifest | 17 + .../Properties/launchSettings.json | 8 + .../Resources/AppIcon/appicon.svg | 4 + .../Resources/AppIcon/appiconfg.svg | 8 + .../Resources/Fonts/OpenSans-Regular.ttf | Bin 0 -> 107320 bytes .../Resources/Images/dotnet_bot.svg | 93 +++ .../Resources/Raw/AboutAssets.txt | 15 + .../Resources/Splash/splash.svg | 8 + .../Services/JsonPathExtractor.cs | 94 +++ .../Services/TemplateCompletionProvider.cs | 512 ++++++++++++++ .../wwwroot/app.css | 180 +++++ .../wwwroot/index.html | 33 + .../wwwroot/receiptLanguage.js | 158 +++++ 33 files changed, 2322 insertions(+), 5 deletions(-) create mode 100644 EpsonTest.Templates/Templates/FinalReceipt.json create mode 100644 Inspectron.Epson/HtmlPrintBuilder.cs create mode 100644 Inspectron.PrintServer.TemplateEditor/App.xaml create mode 100644 Inspectron.PrintServer.TemplateEditor/App.xaml.cs create mode 100644 Inspectron.PrintServer.TemplateEditor/Components/Layout/MainLayout.razor create mode 100644 Inspectron.PrintServer.TemplateEditor/Components/Layout/MainLayout.razor.css create mode 100644 Inspectron.PrintServer.TemplateEditor/Components/Pages/Home.razor create mode 100644 Inspectron.PrintServer.TemplateEditor/Components/Pages/NotFound.razor create mode 100644 Inspectron.PrintServer.TemplateEditor/Components/Routes.razor create mode 100644 Inspectron.PrintServer.TemplateEditor/Components/_Imports.razor create mode 100644 Inspectron.PrintServer.TemplateEditor/Inspectron.PrintServer.TemplateEditor.csproj create mode 100644 Inspectron.PrintServer.TemplateEditor/MainPage.xaml create mode 100644 Inspectron.PrintServer.TemplateEditor/MainPage.xaml.cs create mode 100644 Inspectron.PrintServer.TemplateEditor/MauiProgram.cs create mode 100644 Inspectron.PrintServer.TemplateEditor/Platforms/Windows/App.xaml create mode 100644 Inspectron.PrintServer.TemplateEditor/Platforms/Windows/App.xaml.cs create mode 100644 Inspectron.PrintServer.TemplateEditor/Platforms/Windows/Package.appxmanifest create mode 100644 Inspectron.PrintServer.TemplateEditor/Platforms/Windows/app.manifest create mode 100644 Inspectron.PrintServer.TemplateEditor/Properties/launchSettings.json create mode 100644 Inspectron.PrintServer.TemplateEditor/Resources/AppIcon/appicon.svg create mode 100644 Inspectron.PrintServer.TemplateEditor/Resources/AppIcon/appiconfg.svg create mode 100644 Inspectron.PrintServer.TemplateEditor/Resources/Fonts/OpenSans-Regular.ttf create mode 100644 Inspectron.PrintServer.TemplateEditor/Resources/Images/dotnet_bot.svg create mode 100644 Inspectron.PrintServer.TemplateEditor/Resources/Raw/AboutAssets.txt create mode 100644 Inspectron.PrintServer.TemplateEditor/Resources/Splash/splash.svg create mode 100644 Inspectron.PrintServer.TemplateEditor/Services/JsonPathExtractor.cs create mode 100644 Inspectron.PrintServer.TemplateEditor/Services/TemplateCompletionProvider.cs create mode 100644 Inspectron.PrintServer.TemplateEditor/wwwroot/app.css create mode 100644 Inspectron.PrintServer.TemplateEditor/wwwroot/index.html create mode 100644 Inspectron.PrintServer.TemplateEditor/wwwroot/receiptLanguage.js diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 88fe641..876b1aa 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -7,7 +7,14 @@ "Bash(tree:*)", "Bash(wc:*)", "Bash(dotnet build:*)", - "Bash(dotnet test:*)" + "Bash(dotnet test:*)", + "Bash(dotnet restore:*)", + "Bash(dotnet workload:*)", + "mcp__context7__resolve-library-id", + "WebFetch(domain:raw.githubusercontent.com)", + "WebSearch", + "WebFetch(domain:github.com)", + "Bash(curl:*)" ] } } diff --git a/EpsonTest.Templates/Templates/FinalReceipt.json b/EpsonTest.Templates/Templates/FinalReceipt.json new file mode 100644 index 0000000..5f82650 --- /dev/null +++ b/EpsonTest.Templates/Templates/FinalReceipt.json @@ -0,0 +1,166 @@ +{ + "CompanyName": "Klingler Gastro AG", + "Address1": "M\u00FCnzplatz 3", + "Address2": "CH-8001 Z\u00FCrich", + "Phone": "043 321 22 22", + "ReceiptNumber": null, + "DateTime": "2025-12-16T14:58:00", + "Guests": 2, + "Items": [ + { + "Quantity": 1, + "Description": "San Pellegrino 50cl", + "UnitPrice": 6.50, + "TotalPrice": 6.50, + "TaxCategory": "A", + "SubItems": null, + "PriceDisplay": "6.50 6.50 A" + }, + { + "Quantity": 1, + "Description": "Panna 50cl", + "UnitPrice": 6.50, + "TotalPrice": 6.50, + "TaxCategory": "A", + "SubItems": null, + "PriceDisplay": "6.50 6.50 A" + }, + { + "Quantity": 1, + "Description": "An item with really long description that should wrap", + "UnitPrice": 5.50, + "TotalPrice": 5.50, + "TaxCategory": "A", + "SubItems": null, + "PriceDisplay": "5.50 5.50 A" + }, + { + "Quantity": 2, + "Description": "Business Lunch Menu Seco", + "UnitPrice": 44.00, + "TotalPrice": 88.00, + "TaxCategory": "A", + "SubItems": [ + "Tomato Soup", + "Grilled Salmon", + "Tiramisu" + ], + "PriceDisplay": "44.00 88.00 A" + }, + { + "Quantity": 2, + "Description": "Brunello di Montalcino 1", + "UnitPrice": 16.00, + "TotalPrice": 32.00, + "TaxCategory": "A", + "SubItems": null, + "PriceDisplay": "16.00 32.00 A" + }, + { + "Quantity": 2, + "Description": "Espresso", + "UnitPrice": 5.50, + "TotalPrice": 11.00, + "TaxCategory": "A", + "SubItems": null, + "PriceDisplay": "5.50 11.00 A" + }, + { + "Quantity": 1, + "Description": "Tip", + "UnitPrice": 10.50, + "TotalPrice": 10.50, + "TaxCategory": "B", + "SubItems": null, + "PriceDisplay": "10.50 10.50 B" + } + ], + "Total": 160.00, + "Currency": "CHF", + "TotalInAlternateCurrency": 172.80, + "AlternateCurrency": "EUR", + "SplitPayments": [ + { + "PaymentMethod": "Cash", + "Amount": 50.00, + "Currency": "CHF" + }, + { + "PaymentMethod": "Card", + "Amount": 110.00, + "Currency": "CHF" + } + ], + "PaymentMethod": "MASTER", + "PaymentAmount": 160.00, + "TaxBreakdown": [ + { + "Category": "A", + "Rate": 8.1, + "Gross": 149.50, + "Net": 138.30, + "TaxAmount": 11.20, + "Currency": "CHF" + }, + { + "Category": "B", + "Rate": 0, + "Gross": 10.50, + "Net": 10.50, + "TaxAmount": 0.00, + "Currency": "CHF" + } + ], + "IsDebtor": true, + "WaiterName": "Yves", + "Terminal": "Hauptkasse ZH", + "TableNumber": "12", + "VatNumber": "CHE-449.635.880 MWST", + "ThankYouMessage": "Das Team bedankt sich herzlich f\u00FCr Ihren", + "GoodbyeMessageLine1": "Besuch.", + "GoodbyeMessageLine2": "Auf Wiedersehen.", + "TerminalReceipts": [ + { + "ReceiptType": "*** Kundenbeleg ***", + "BookingType": "Buchung", + "PaymentSystem": "TWINT", + "TransactionNumber": "XXXXXXXXXXXXXXX1494", + "TransactionDateTime": "2025-11-11T12:34:49", + "TerminalId": "31108834", + "AID": "A0000015749E", + "TransactionSeqCount": "6127", + "TransactionRefNo": "99036644599", + "AuthCode": "0d3396", + "AcquirerId": "2", + "EftAmount": 67.00, + "TipAmount": 6.70, + "TotalEftAmount": 73.70, + "Currency": "CHF" + }, + { + "ReceiptType": "*** Kundenbeleg ***", + "BookingType": "Buchung", + "PaymentSystem": "MASTER", + "TransactionNumber": "XXXXXXXXXXXXXXX2587", + "TransactionDateTime": "2025-11-11T12:36:12", + "TerminalId": "31108834", + "AID": "A0000000041010", + "TransactionSeqCount": "6128", + "TransactionRefNo": "99036644600", + "AuthCode": "1a4b2c", + "AcquirerId": "1", + "EftAmount": 93.00, + "TipAmount": 0.00, + "TotalEftAmount": 93.00, + "Currency": "CHF" + } + ], + "DiscountInfo": { + "Description": "Fine Dine", + "Amount": -50.00, + "Currency": "CHF" + }, + "ReceiptLabel": "", + "HasSplitPayments": true, + "HasTaxableCategories": true +} diff --git a/Inspectron.Epson.slnx b/Inspectron.Epson.slnx index d44e2eb..6cb719b 100644 --- a/Inspectron.Epson.slnx +++ b/Inspectron.Epson.slnx @@ -1,10 +1,11 @@ - + + - - + + diff --git a/Inspectron.Epson/HtmlPrintBuilder.cs b/Inspectron.Epson/HtmlPrintBuilder.cs new file mode 100644 index 0000000..55bffb4 --- /dev/null +++ b/Inspectron.Epson/HtmlPrintBuilder.cs @@ -0,0 +1,652 @@ +using System.Text; +using Microsoft.Extensions.Logging; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; + +namespace Inspectron.Epson; + +/// +/// HTML-based printer implementation that builds HTML in memory, +/// simulating Epson thermal printer output for testing and preview purposes. +/// No file I/O is performed; call to retrieve the result. +/// +public class HtmlPrintBuilder : IEpsonPrinter +{ + private readonly ILogger? _logger; + private readonly byte _id; + private readonly int _paperWidth; + + private bool _isConnected; + private readonly StringBuilder _content = new(); + + // 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 _isBiggerFontWidthTM220; + private bool _isBiggerFontHeightTM220; + private int _lineSpacing = 14; + private int _absolutePosition; + + // Loaded image buffer + private Image? _loadedImage; + private int _loadedImageMaxWidth; + + 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 HtmlPrintBuilder + /// + /// Paper width in pixels (default 384 for 80mm thermal printer) + /// Optional logger for diagnostic output + /// Printer ID byte + public HtmlPrintBuilder(int paperWidth = 384, ILogger? logger = null, byte id = 0x01) + { + _paperWidth = paperWidth; + _logger = logger; + _id = id; + } + + /// + /// Returns the generated HTML string. + /// + public string GetHtml() + { + return GenerateHtml(); + } + + /// + public Task ConnectAsync(string ip, int port = 9100, int timeoutSeconds = 10) + { + _logger?.LogInformation("HtmlPrintBuilder: Connecting (paper width: {Width}px)", _paperWidth); + + _content.Clear(); + ResetStylingState(); + _isConnected = true; + + _logger?.LogInformation("HtmlPrintBuilder: Connected successfully"); + return Task.CompletedTask; + } + + /// + public void Disconnect() + { + _logger?.LogDebug("HtmlPrintBuilder: Disconnecting"); + + _loadedImage?.Dispose(); + _loadedImage = null; + _isConnected = false; + } + + /// + public Task GetPrinterStatusAsync() + { + _logger?.LogDebug("HtmlPrintBuilder: Returning mock printer status (always ready)"); + return Task.FromResult(new PrinterStatus + { + RawByte = 0x12, + DrawerOpen = false, + IsOnline = true, + IsCoverClosed = true, + PaperFeedButtonPressed = false + }); + } + + /// + public Task GetOfflineStatusAsync() + { + _logger?.LogDebug("HtmlPrintBuilder: 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("HtmlPrintBuilder: 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("HtmlPrintBuilder: Returning mock paper sensor status (paper present)"); + return Task.FromResult(new PaperSensorStatus + { + RawByte = 0x12, + PaperNearEnd = false, + PaperPresent = true + }); + } + + /// + public Task GetTM220StatusAsync() + { + _logger?.LogDebug("HtmlPrintBuilder: Returning mock TM220 status"); + return Task.FromResult((byte)0x12); + } + + /// + public Task GetPrinterIdAsync() + { + return Task.FromResult(_id); + } + + /// + public Task GetOverallStatusAsync() + { + _logger?.LogInformation("HtmlPrintBuilder: Returning mock overall status (ready)"); + return Task.FromResult(new OverallStatus + { + PrinterModel = "HtmlPrintBuilder", + 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 Print Builder", + StatusIcon = "🟢", + IsReady = true, + Recommendations = new List() + }); + } + + /// + public Task InitAsync() + { + _logger?.LogInformation("HtmlPrintBuilder: Initializing (resetting styling state)"); + ResetStylingState(); + return Task.CompletedTask; + } + + /// + public Task SetQuadrupleMode(bool enable) + { + _logger?.LogInformation("HtmlPrintBuilder: {Action} quadruple mode", enable ? "Enabling" : "Disabling"); + _isQuadrupleMode = enable; + return Task.CompletedTask; + } + + /// + public Task SetRedColor(bool enable) + { + _logger?.LogInformation("HtmlPrintBuilder: {Action} red color mode", enable ? "Enabling" : "Disabling"); + _isRedColor = enable; + return Task.CompletedTask; + } + + /// + public Task SetEmphasized(bool enable) + { + _logger?.LogInformation("HtmlPrintBuilder: {Action} emphasized mode", enable ? "Enabling" : "Disabling"); + _isEmphasized = enable; + return Task.CompletedTask; + } + + /// + public Task SelectFont(EpsonCommands.PrinterFont font) + { + _logger?.LogInformation("HtmlPrintBuilder: Selecting font {Font}", font); + _currentFont = font; + return Task.CompletedTask; + } + + /// + public Task SetBiggerFontTM220(bool biggerWidth, bool biggerHeight = false, bool secondaryFont = true) + { + _logger?.LogInformation("HtmlPrintBuilder: Setting bigger font mode: Width={BiggerWidth}, Height={BiggerHeight}, SecondaryFont={SecondaryFont}", + biggerWidth, biggerHeight, secondaryFont); + _isBiggerFontWidthTM220 = biggerWidth; + _isBiggerFontHeightTM220 = biggerHeight; + return Task.CompletedTask; + } + + /// + public Task PrintTextAsync(string text) + { + EnsureConnected(); + _logger?.LogInformation("HtmlPrintBuilder: Printing text ({Length} characters)", text.Length); + + var style = BuildCurrentStyle(); + var escapedText = EscapeHtml(text); + + escapedText = escapedText.TrimEnd('\n').Replace("\n", "
"); + + var positionStyle = _absolutePosition > 0 ? $"margin-left: {_absolutePosition}px;" : ""; + + _content.AppendLine($"{escapedText}"); + + _absolutePosition = 0; + + 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("HtmlPrintBuilder: 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("HtmlPrintBuilder: 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("HtmlPrintBuilder: PrintBuffer (no-op)"); + return Task.CompletedTask; + } + + /// + public Task FeedLinesAsync(int lines) + { + EnsureConnected(); + _logger?.LogInformation("HtmlPrintBuilder: Feeding {Lines} lines", lines); + + for (int i = 0; i < lines; i++) + { + _content.AppendLine($"
"); + } + + return Task.CompletedTask; + } + + /// + public Task CutAsync(bool fullCut = true) + { + EnsureConnected(); + _logger?.LogInformation("HtmlPrintBuilder: Cutting paper"); + _content.AppendLine("
✂️
"); + return Task.CompletedTask; + } + + /// + public async Task LoadImageAsync(string imagePath, int maxWidth = 384) + { + _logger?.LogInformation("HtmlPrintBuilder: 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("HtmlPrintBuilder: Loading image from stream"); + + _loadedImage?.Dispose(); + _loadedImage = await Image.LoadAsync(imageStream); + _loadedImageMaxWidth = maxWidth; + } + + /// + public Task LoadImageAsync(Image image, int maxWidth = 384) + { + _logger?.LogInformation("HtmlPrintBuilder: 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("HtmlPrintBuilder: No image loaded to print"); + return; + } + + _logger?.LogInformation("HtmlPrintBuilder: 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("HtmlPrintBuilder: 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("HtmlPrintBuilder: 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("HtmlPrintBuilder: Printing image (bit mode) {Width}x{Height}", image.Width, image.Height); + + return RenderImageToHtml(image, maxWidth); + } + + /// + public Task SendRawCommandAsync(byte[] command) + { + _logger?.LogDebug("HtmlPrintBuilder: SendRawCommandAsync ignored ({Length} bytes)", command.Length); + 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("HtmlPrintBuilder: Setting absolute print position to {X}", x); + _absolutePosition = x; + return Task.CompletedTask; + } + + /// + public Task SetDefaultLineSpacing() + { + _logger?.LogInformation("HtmlPrintBuilder: Setting default line spacing"); + _lineSpacing = 14; + 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("HtmlPrintBuilder: Setting custom line spacing to {Spacing}", spacing); + _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; + _isBiggerFontWidthTM220 = false; + _isBiggerFontHeightTM220 = false; + _lineSpacing = 14; + _absolutePosition = 0; + } + + private string BuildCurrentStyle() + { + var styles = new List(); + + var (baseWidth, baseHeight) = FontSizes[_currentFont]; + + float effectiveWidthMultiplier = _fontWidthMultiplier; + float effectiveHeightMultiplier = _fontHeightMultiplier; + + if (_isQuadrupleMode) + { + effectiveWidthMultiplier *= 2; + effectiveHeightMultiplier *= 2; + } + + if (_isBiggerFontWidthTM220) + { + effectiveWidthMultiplier *= 2f; + } + + if (_isBiggerFontHeightTM220) + { + effectiveHeightMultiplier *= 1.2f; + } + + int fontSize = (int)(baseHeight * effectiveHeightMultiplier); + styles.Add($"font-size: {fontSize}px"); + + if (effectiveWidthMultiplier > 1) + { + int letterSpacing = (int)((effectiveWidthMultiplier) * baseWidth / 2); + styles.Add($"letter-spacing: {letterSpacing}px"); + } + + if (_isEmphasized) + { + styles.Add("font-weight: bold"); + } + + if (_isRedColor) + { + styles.Add("color: #cc0000"); + } + + styles.Add($"line-height: {_lineSpacing}px"); + + return string.Join("; ", styles) + ";"; + } + + private async Task RenderImageToHtml(Image sourceImage, int maxWidth) + { + using var processedImage = sourceImage.Clone(); + + if (processedImage.Width > maxWidth) + { + EpsonImageConverter.ResizeImage(processedImage, maxWidth); + } + + using var grayscaleImage = EpsonImageConverter.ConvertToGrayscale(processedImage); + + EpsonImageConverter.ApplyFloydSteinbergDithering(grayscaleImage); + + 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); + } + } + }); + + // Embed image as base64 data URI instead of saving to file + using var ms = new MemoryStream(); + await ditheredImage.SaveAsync(ms, new PngEncoder()); + var base64 = Convert.ToBase64String(ms.ToArray()); + var dataUri = $"data:image/png;base64,{base64}"; + + var positionStyle = _absolutePosition > 0 ? $"margin-left: {_absolutePosition}px;" : ""; + _content.AppendLine($"
\"Printed
"); + + _absolutePosition = 0; + + _logger?.LogInformation("HtmlPrintBuilder: Image embedded as base64 ({Bytes} bytes)", ms.Length); + } + + 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/PrintServer/PrintServices/IPrinter.cs b/Inspectron.Epson/PrintServer/PrintServices/IPrinter.cs index 296e07a..e86da58 100644 --- a/Inspectron.Epson/PrintServer/PrintServices/IPrinter.cs +++ b/Inspectron.Epson/PrintServer/PrintServices/IPrinter.cs @@ -6,7 +6,6 @@ public interface IPrinter { public Task InitAsync(); public Task PrintImageAsync(string path); - public Task SetFontSizeAsync(int fontSize); public Task PrintAsync(List commands); public Task Cut(); } \ No newline at end of file diff --git a/Inspectron.PrintServer.TemplateEditor/App.xaml b/Inspectron.PrintServer.TemplateEditor/App.xaml new file mode 100644 index 0000000..57fd44d --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/App.xaml @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/Inspectron.PrintServer.TemplateEditor/App.xaml.cs b/Inspectron.PrintServer.TemplateEditor/App.xaml.cs new file mode 100644 index 0000000..2d62d24 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/App.xaml.cs @@ -0,0 +1,15 @@ +namespace Inspectron.PrintServer.TemplateEditor +{ + public partial class App : Application + { + public App() + { + InitializeComponent(); + } + + protected override Window CreateWindow(IActivationState? activationState) + { + return new Window(new MainPage()) { Title = "Inspectron.PrintServer.TemplateEditor" }; + } + } +} diff --git a/Inspectron.PrintServer.TemplateEditor/Components/Layout/MainLayout.razor b/Inspectron.PrintServer.TemplateEditor/Components/Layout/MainLayout.razor new file mode 100644 index 0000000..e1a9a75 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Components/Layout/MainLayout.razor @@ -0,0 +1,3 @@ +@inherits LayoutComponentBase + +@Body diff --git a/Inspectron.PrintServer.TemplateEditor/Components/Layout/MainLayout.razor.css b/Inspectron.PrintServer.TemplateEditor/Components/Layout/MainLayout.razor.css new file mode 100644 index 0000000..e69de29 diff --git a/Inspectron.PrintServer.TemplateEditor/Components/Pages/Home.razor b/Inspectron.PrintServer.TemplateEditor/Components/Pages/Home.razor new file mode 100644 index 0000000..baf2fe4 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Components/Pages/Home.razor @@ -0,0 +1,113 @@ +@page "/" +@using BlazorMonaco +@using BlazorMonaco.Editor +@using BlazorMonaco.Languages +@using Inspectron.PrintServer.TemplateEditor.Services +@inject IJSRuntime JSRuntime + +
+
+ + + +
+ + @if (!string.IsNullOrEmpty(_loadedJsonFile)) + { + @_loadedJsonFile + } +
+ +
+
+
Editor
+ +
+ +
+
Preview
+
+
+
+
+
+ +@code { + private StandaloneCodeEditor _editor = null!; + private string _editorContent = ""; + private string? _loadedJsonFile; + private readonly TemplateCompletionProvider _completionProvider = new(); + + private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor) + { + return new StandaloneEditorConstructionOptions + { + AutomaticLayout = true, + Language = "receipt-xml", + Theme = "receipt-dark" + }; + } + + private async Task OnEditorInitAsync() + { + // Register custom language and theme + await JSRuntime.InvokeVoidAsync("receiptLanguage.register"); + await JSRuntime.InvokeVoidAsync("receiptLanguage.defineTheme"); + + // Apply the theme now that it's defined + await BlazorMonaco.Editor.Global.SetTheme(JSRuntime, "receipt-dark"); + + // Wire up content accessor for the completion provider + _completionProvider.SetEditorContentAccessor(() => _editorContent); + + // Register completion provider + await BlazorMonaco.Languages.Global.RegisterCompletionItemProvider( + JSRuntime, + "receipt-xml", + _completionProvider.ProvideCompletionItems, + _completionProvider.ResolveCompletionItem); + } + + private async Task OnContentChanged(ModelContentChangedEvent e) + { + _editorContent = await _editor.GetValue(); + } + + private async Task LoadJsonAsync() + { + try + { + var result = await FilePicker.Default.PickAsync(new PickOptions + { + PickerTitle = "Select a JSON data file", + FileTypes = new FilePickerFileType(new Dictionary> + { + { DevicePlatform.WinUI, new[] { ".json" } }, + { DevicePlatform.macOS, new[] { "json" } }, + { DevicePlatform.iOS, new[] { "public.json" } }, + { DevicePlatform.Android, new[] { "application/json" } }, + }) + }); + + if (result is null) + return; + + using var stream = await result.OpenReadAsync(); + using var reader = new StreamReader(stream); + var json = await reader.ReadToEndAsync(); + + var paths = JsonPathExtractor.Extract(json); + _completionProvider.SetJsonPaths(paths); + _loadedJsonFile = result.FileName; + + StateHasChanged(); + } + catch (Exception) + { + // User cancelled or file read failed + } + } +} diff --git a/Inspectron.PrintServer.TemplateEditor/Components/Pages/NotFound.razor b/Inspectron.PrintServer.TemplateEditor/Components/Pages/NotFound.razor new file mode 100644 index 0000000..091b6b8 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Components/Pages/NotFound.razor @@ -0,0 +1,5 @@ +@page "/not-found" +@layout MainLayout + +

Not Found

+

Sorry, the content you are looking for does not exist.

diff --git a/Inspectron.PrintServer.TemplateEditor/Components/Routes.razor b/Inspectron.PrintServer.TemplateEditor/Components/Routes.razor new file mode 100644 index 0000000..9059591 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Components/Routes.razor @@ -0,0 +1,6 @@ + + + + + + diff --git a/Inspectron.PrintServer.TemplateEditor/Components/_Imports.razor b/Inspectron.PrintServer.TemplateEditor/Components/_Imports.razor new file mode 100644 index 0000000..b6090a5 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Components/_Imports.razor @@ -0,0 +1,10 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using Inspectron.PrintServer.TemplateEditor +@using Inspectron.PrintServer.TemplateEditor.Components +@using Inspectron.PrintServer.TemplateEditor.Components.Layout diff --git a/Inspectron.PrintServer.TemplateEditor/Inspectron.PrintServer.TemplateEditor.csproj b/Inspectron.PrintServer.TemplateEditor/Inspectron.PrintServer.TemplateEditor.csproj new file mode 100644 index 0000000..56a8610 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Inspectron.PrintServer.TemplateEditor.csproj @@ -0,0 +1,63 @@ + + + + $(TargetFrameworks);net10.0-windows10.0.19041.0 + + + + + Exe + Inspectron.PrintServer.TemplateEditor + true + true + enable + false + enable + + + Inspectron.PrintServer.TemplateEditor + + + com.companyname.inspectron.printserver.templateeditor + + + 1.0 + 1 + + + None + + 10.0.17763.0 + 10.0.17763.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Inspectron.PrintServer.TemplateEditor/MainPage.xaml b/Inspectron.PrintServer.TemplateEditor/MainPage.xaml new file mode 100644 index 0000000..db10a6d --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/MainPage.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/Inspectron.PrintServer.TemplateEditor/MainPage.xaml.cs b/Inspectron.PrintServer.TemplateEditor/MainPage.xaml.cs new file mode 100644 index 0000000..56279c6 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/MainPage.xaml.cs @@ -0,0 +1,10 @@ +namespace Inspectron.PrintServer.TemplateEditor +{ + public partial class MainPage : ContentPage + { + public MainPage() + { + InitializeComponent(); + } + } +} diff --git a/Inspectron.PrintServer.TemplateEditor/MauiProgram.cs b/Inspectron.PrintServer.TemplateEditor/MauiProgram.cs new file mode 100644 index 0000000..37eb180 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/MauiProgram.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.Logging; + +namespace Inspectron.PrintServer.TemplateEditor +{ + public static class MauiProgram + { + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + builder + .UseMauiApp() + .ConfigureFonts(fonts => + { + fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); + }); + + builder.Services.AddMauiBlazorWebView(); + +#if DEBUG + builder.Services.AddBlazorWebViewDeveloperTools(); + builder.Logging.AddDebug(); +#endif + + return builder.Build(); + } + } +} diff --git a/Inspectron.PrintServer.TemplateEditor/Platforms/Windows/App.xaml b/Inspectron.PrintServer.TemplateEditor/Platforms/Windows/App.xaml new file mode 100644 index 0000000..377d498 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Platforms/Windows/App.xaml @@ -0,0 +1,8 @@ + + + diff --git a/Inspectron.PrintServer.TemplateEditor/Platforms/Windows/App.xaml.cs b/Inspectron.PrintServer.TemplateEditor/Platforms/Windows/App.xaml.cs new file mode 100644 index 0000000..c09da3f --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Platforms/Windows/App.xaml.cs @@ -0,0 +1,25 @@ +using Microsoft.UI.Xaml; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace Inspectron.PrintServer.TemplateEditor.WinUI +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + public partial class App : MauiWinUIApplication + { + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + } + +} diff --git a/Inspectron.PrintServer.TemplateEditor/Platforms/Windows/Package.appxmanifest b/Inspectron.PrintServer.TemplateEditor/Platforms/Windows/Package.appxmanifest new file mode 100644 index 0000000..44f254a --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Platforms/Windows/Package.appxmanifest @@ -0,0 +1,46 @@ + + + + + + + + + $placeholder$ + User Name + $placeholder$.png + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Inspectron.PrintServer.TemplateEditor/Platforms/Windows/app.manifest b/Inspectron.PrintServer.TemplateEditor/Platforms/Windows/app.manifest new file mode 100644 index 0000000..39cb4f4 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Platforms/Windows/app.manifest @@ -0,0 +1,17 @@ + + + + + + + + true/PM + PerMonitorV2, PerMonitor + + true + + + diff --git a/Inspectron.PrintServer.TemplateEditor/Properties/launchSettings.json b/Inspectron.PrintServer.TemplateEditor/Properties/launchSettings.json new file mode 100644 index 0000000..4f85793 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Windows Machine": { + "commandName": "Project", + "nativeDebugging": false + } + } +} \ No newline at end of file diff --git a/Inspectron.PrintServer.TemplateEditor/Resources/AppIcon/appicon.svg b/Inspectron.PrintServer.TemplateEditor/Resources/AppIcon/appicon.svg new file mode 100644 index 0000000..9d63b65 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Resources/AppIcon/appicon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Inspectron.PrintServer.TemplateEditor/Resources/AppIcon/appiconfg.svg b/Inspectron.PrintServer.TemplateEditor/Resources/AppIcon/appiconfg.svg new file mode 100644 index 0000000..21dfb25 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Resources/AppIcon/appiconfg.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Inspectron.PrintServer.TemplateEditor/Resources/Fonts/OpenSans-Regular.ttf b/Inspectron.PrintServer.TemplateEditor/Resources/Fonts/OpenSans-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fe25ce074f71c1f0fd1c1216b89adc358e9d02e9 GIT binary patch literal 107320 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 literal 0 HcmV?d00001 diff --git a/Inspectron.PrintServer.TemplateEditor/Resources/Images/dotnet_bot.svg b/Inspectron.PrintServer.TemplateEditor/Resources/Images/dotnet_bot.svg new file mode 100644 index 0000000..abfaff2 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Resources/Images/dotnet_bot.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Inspectron.PrintServer.TemplateEditor/Resources/Raw/AboutAssets.txt b/Inspectron.PrintServer.TemplateEditor/Resources/Raw/AboutAssets.txt new file mode 100644 index 0000000..6de1c15 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Resources/Raw/AboutAssets.txt @@ -0,0 +1,15 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories). Deployment of the asset to your application +is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. + + + +These files will be deployed with your package and will be accessible using Essentials: + + async Task LoadMauiAsset() + { + using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); + using var reader = new StreamReader(stream); + + var contents = reader.ReadToEnd(); + } diff --git a/Inspectron.PrintServer.TemplateEditor/Resources/Splash/splash.svg b/Inspectron.PrintServer.TemplateEditor/Resources/Splash/splash.svg new file mode 100644 index 0000000..21dfb25 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Resources/Splash/splash.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Inspectron.PrintServer.TemplateEditor/Services/JsonPathExtractor.cs b/Inspectron.PrintServer.TemplateEditor/Services/JsonPathExtractor.cs new file mode 100644 index 0000000..54f7cfc --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Services/JsonPathExtractor.cs @@ -0,0 +1,94 @@ +using System.Text.Json; + +namespace Inspectron.PrintServer.TemplateEditor.Services; + +public record JsonPropertyInfo(string Path, string Type, bool IsArray); + +public static class JsonPathExtractor +{ + public static List Extract(string json) + { + var results = new List(); + + try + { + using var doc = JsonDocument.Parse(json); + WalkElement(doc.RootElement, "", results); + } + catch (JsonException) + { + // Invalid JSON - return empty list + } + + return results; + } + + private static void WalkElement(JsonElement element, string prefix, List results) + { + switch (element.ValueKind) + { + case JsonValueKind.Object: + foreach (var prop in element.EnumerateObject()) + { + var path = string.IsNullOrEmpty(prefix) ? prop.Name : $"{prefix}.{prop.Name}"; + + var type = GetTypeName(prop.Value.ValueKind); + var isArray = prop.Value.ValueKind == JsonValueKind.Array; + + results.Add(new JsonPropertyInfo(path, type, isArray)); + + if (prop.Value.ValueKind == JsonValueKind.Object) + { + WalkElement(prop.Value, path, results); + } + else if (prop.Value.ValueKind == JsonValueKind.Array) + { + WalkArrayElement(prop.Value, path, results); + } + } + break; + } + } + + private static void WalkArrayElement(JsonElement array, string arrayPath, List results) + { + // Inspect first element to discover item property paths + foreach (var item in array.EnumerateArray()) + { + if (item.ValueKind == JsonValueKind.Object) + { + // Use "[item]" prefix to indicate these are array item properties + foreach (var prop in item.EnumerateObject()) + { + var itemPath = $"{arrayPath}[].{prop.Name}"; + var type = GetTypeName(prop.Value.ValueKind); + var isArray = prop.Value.ValueKind == JsonValueKind.Array; + + results.Add(new JsonPropertyInfo(itemPath, type, isArray)); + + if (prop.Value.ValueKind == JsonValueKind.Object) + { + WalkElement(prop.Value, itemPath, results); + } + else if (prop.Value.ValueKind == JsonValueKind.Array) + { + WalkArrayElement(prop.Value, itemPath, results); + } + } + } + + break; // Only inspect the first element + } + } + + private static string GetTypeName(JsonValueKind kind) => kind switch + { + JsonValueKind.String => "string", + JsonValueKind.Number => "number", + JsonValueKind.True or JsonValueKind.False => "boolean", + JsonValueKind.Array => "array", + JsonValueKind.Object => "object", + JsonValueKind.Null => "null", + _ => "unknown" + }; +} diff --git a/Inspectron.PrintServer.TemplateEditor/Services/TemplateCompletionProvider.cs b/Inspectron.PrintServer.TemplateEditor/Services/TemplateCompletionProvider.cs new file mode 100644 index 0000000..6660b7a --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/Services/TemplateCompletionProvider.cs @@ -0,0 +1,512 @@ +using System.Text.Json; +using BlazorMonaco; +using BlazorMonaco.Languages; + +namespace Inspectron.PrintServer.TemplateEditor.Services; + +public class TemplateCompletionProvider +{ + private List _jsonPaths = []; + + public void SetJsonPaths(List paths) => _jsonPaths = paths; + + public Task ProvideCompletionItems(string modelUri, Position position, CompletionContext context) + { + var items = new List(); + var editorContent = _editorContentAccessor?.Invoke(); + + if (string.IsNullOrEmpty(editorContent)) + return Task.FromResult(new CompletionList { Suggestions = items }); + + var lines = editorContent.Split('\n'); + if (position.LineNumber < 1 || position.LineNumber > lines.Length) + return Task.FromResult(new CompletionList { Suggestions = items }); + + var line = lines[position.LineNumber - 1]; + var col = Math.Min(position.Column - 1, line.Length); + var textBefore = line[..col]; + + var completionContext = DetectContext(textBefore, lines, position.LineNumber); + + switch (completionContext.Kind) + { + case ContextKind.ElementName: + items = GetElementCompletions(completionContext.Prefix); + break; + case ContextKind.AttributeName: + items = GetAttributeCompletions(completionContext.ElementName, completionContext.Prefix); + break; + case ContextKind.AttributeValue: + items = GetAttributeValueCompletions(completionContext.ElementName, completionContext.AttributeName); + break; + case ContextKind.Expression: + items = GetExpressionCompletions(completionContext.Prefix, lines, position.LineNumber); + break; + } + + return Task.FromResult(new CompletionList { Suggestions = items }); + } + + public Task ResolveCompletionItem(CompletionItem item) => Task.FromResult(item); + + private Func? _editorContentAccessor; + + public void SetEditorContentAccessor(Func accessor) => _editorContentAccessor = accessor; + + #region Context Detection + + private enum ContextKind { None, ElementName, AttributeName, AttributeValue, Expression } + + private record CompletionContextInfo(ContextKind Kind, string Prefix = "", string ElementName = "", string AttributeName = ""); + + private static CompletionContextInfo DetectContext(string textBefore, string[] lines, int lineNumber) + { + // Inside {{ }} expression? + var lastOpen = textBefore.LastIndexOf("{{", StringComparison.Ordinal); + var lastClose = textBefore.LastIndexOf("}}", StringComparison.Ordinal); + if (lastOpen >= 0 && lastOpen > lastClose) + { + var exprText = textBefore[(lastOpen + 2)..]; + return new CompletionContextInfo(ContextKind.Expression, exprText); + } + + // Inside attribute value (="...")? + var inAttrValue = IsInsideAttributeValue(textBefore); + if (inAttrValue.Inside) + return new CompletionContextInfo(ContextKind.AttributeValue, + ElementName: inAttrValue.ElementName, AttributeName: inAttrValue.AttributeName); + + // After < for element name? + var lastLt = textBefore.LastIndexOf('<'); + var lastGt = textBefore.LastIndexOf('>'); + if (lastLt >= 0 && lastLt > lastGt) + { + var afterLt = textBefore[(lastLt + 1)..]; + + // Closing tag = 0 ? attrText[(lastSpace + 1)..] : attrText; + // Filter out if we're right after = or inside a value + if (!attrPrefix.Contains('=')) + return new CompletionContextInfo(ContextKind.AttributeName, attrPrefix, elemName); + } + + return new CompletionContextInfo(ContextKind.None); + } + + private static (bool Inside, string ElementName, string AttributeName) IsInsideAttributeValue(string text) + { + // Find the last opening tag context + var lastLt = text.LastIndexOf('<'); + if (lastLt < 0) return (false, "", ""); + + var tagContent = text[(lastLt + 1)..]; + var lastGt = tagContent.LastIndexOf('>'); + if (lastGt >= 0) return (false, "", ""); + + // Extract element name + var spaceIdx = tagContent.IndexOf(' '); + if (spaceIdx < 0) return (false, "", ""); + var elemName = tagContent[..spaceIdx]; + if (elemName.StartsWith("/")) return (false, "", ""); + + // Check if cursor is inside a quoted attribute value + var attrPart = tagContent[spaceIdx..]; + var inDoubleQuote = false; + var inSingleQuote = false; + var lastAttrName = ""; + var i = 0; + + while (i < attrPart.Length) + { + if (attrPart[i] == '"' && !inSingleQuote) + { + inDoubleQuote = !inDoubleQuote; + } + else if (attrPart[i] == '\'' && !inDoubleQuote) + { + inSingleQuote = !inSingleQuote; + } + else if (!inDoubleQuote && !inSingleQuote && char.IsLetter(attrPart[i])) + { + // Capture attribute name + var start = i; + while (i < attrPart.Length && (char.IsLetterOrDigit(attrPart[i]) || attrPart[i] == '-' || attrPart[i] == '_')) + i++; + lastAttrName = attrPart[start..i]; + continue; + } + + i++; + } + + if (inDoubleQuote || inSingleQuote) + return (true, elemName, lastAttrName); + + return (false, "", ""); + } + + #endregion + + #region Element Schema + + private static readonly Dictionary Schema = new(StringComparer.OrdinalIgnoreCase) + { + ["receipt"] = new("Required root element", []), + ["line"] = new("Single line of text", new() + { + ["align"] = new(["left", "center", "right"], "Text alignment"), + ["bold"] = BoolAttr("Bold text"), + ["big"] = BoolAttr("Double-width font"), + ["tall"] = BoolAttr("Double-height font"), + ["red"] = BoolAttr("Red text (supported printers)"), + ["lineSpacing"] = new([], "Line spacing in dots"), + ["wrap"] = BoolAttr("Word-wrap text"), + ["wrapIndent"] = new([], "Indent for wrapped lines"), + }), + ["columns"] = new("Two-column layout", new() + { + ["left"] = new([], "Left column content"), + ["right"] = new([], "Right column content"), + ["bold"] = BoolAttr("Bold text"), + ["big"] = BoolAttr("Double-width font"), + ["tall"] = BoolAttr("Double-height font"), + ["red"] = BoolAttr("Red text"), + ["lineSpacing"] = new([], "Line spacing in dots"), + ["wrap"] = BoolAttr("Wrap left column"), + ["wrapIndent"] = new([], "Indent for wrapped lines"), + }), + ["row"] = new("Multi-column layout with children", new() + { + ["bold"] = BoolAttr("Bold text"), + ["big"] = BoolAttr("Double-width font"), + ["tall"] = BoolAttr("Double-height font"), + ["red"] = BoolAttr("Red text"), + ["lineSpacing"] = new([], "Line spacing in dots"), + }), + ["col"] = new("Column inside or ", new() + { + ["width"] = new([], "Column width (char count or percentage)"), + ["align"] = new(["left", "center", "right"], "Cell alignment"), + ["wrap"] = BoolAttr("Word-wrap cell text"), + }), + ["separator"] = new("Repeated character line", new() + { + ["char"] = new([], "Character to repeat (default: -)"), + }), + ["cut"] = new("Paper cut command", []), + ["feed"] = new("Empty lines", new() + { + ["lines"] = new([], "Number of empty lines"), + }), + ["table"] = new("ASCII box table", new() + { + ["items"] = new([], "JSON array path for data rows"), + ["var"] = new([], "Loop variable name"), + ["headerItems"] = new([], "JSON array path for header rows"), + }), + ["foreach"] = new("Loop over array", new() + { + ["items"] = new([], "Path to JSON array (required)"), + ["var"] = new([], "Loop variable name (required)"), + }), + ["if"] = new("Conditional block", new() + { + ["test"] = new([], "Condition expression (required)"), + }), + ["else"] = new("Else branch (must follow )", []), + }; + + private static AttributeSchema BoolAttr(string desc) => new(["true", "false"], desc); + + private record ElementSchema(string Description, Dictionary Attributes); + private record AttributeSchema(List Values, string Description); + + #endregion + + #region Completion Generators + + private static List GetElementCompletions(string prefix) + { + var items = new List(); + var sortIndex = 0; + + foreach (var (name, schema) in Schema) + { + items.Add(new CompletionItem + { + LabelAsString = name, + Kind = CompletionItemKind.Keyword, + Detail = schema.Description, + InsertText = GetElementSnippet(name, schema), + InsertTextRules = CompletionItemInsertTextRule.InsertAsSnippet, + SortText = sortIndex++.ToString("D3"), + }); + } + + return items; + } + + private static string GetElementSnippet(string name, ElementSchema schema) + { + return name switch + { + "line" => "line${1: align=\"${2:left}\"}>${3:$0}", + "columns" => "columns left=\"${1}\" right=\"${2}\" />", + "row" => "row>\n\t${2}\n\t${4}\n", + "col" => "col${1: align=\"${2:left}\"}>${3:$0}", + "separator" => "separator />", + "cut" => "cut />", + "feed" => "feed${1: lines=\"${2:1}\"} />", + "foreach" => "foreach items=\"${1}\" var=\"${2:item}\">\n\t$0\n", + "if" => "if test=\"${1}\">\n\t$0\n", + "else" => "else>\n\t$0\n", + "table" => "table items=\"${1}\" var=\"${2:item}\">\n\t${5:Header}\n
", + "receipt" => "receipt>\n\t$0\n", + _ => $"{name}>$0" + }; + } + + private static List GetAttributeCompletions(string elementName, string prefix) + { + var items = new List(); + + if (!Schema.TryGetValue(elementName, out var schema)) + return items; + + var sortIndex = 0; + foreach (var (attrName, attrSchema) in schema.Attributes) + { + var insertText = attrSchema.Values.Count > 0 + ? $"{attrName}=\"${{1|{string.Join(",", attrSchema.Values)}|}}\"" + : $"{attrName}=\"$1\""; + + items.Add(new CompletionItem + { + LabelAsString = attrName, + Kind = CompletionItemKind.Property, + Detail = attrSchema.Description, + InsertText = insertText, + InsertTextRules = CompletionItemInsertTextRule.InsertAsSnippet, + SortText = sortIndex++.ToString("D3"), + }); + } + + return items; + } + + private static List GetAttributeValueCompletions(string elementName, string attributeName) + { + var items = new List(); + + if (!Schema.TryGetValue(elementName, out var schema)) + return items; + + if (!schema.Attributes.TryGetValue(attributeName, out var attrSchema)) + return items; + + foreach (var value in attrSchema.Values) + { + items.Add(new CompletionItem + { + LabelAsString = value, + Kind = CompletionItemKind.Value, + InsertText = value, + }); + } + + return items; + } + + private List GetExpressionCompletions(string prefix, string[] lines, int lineNumber) + { + var items = new List(); + var sortIndex = 0; + + // Loop variables + var loopVars = FindLoopVariables(lines, lineNumber); + + // Special loop variables + foreach (var special in new[] { "$index", "$first", "$last" }) + { + if (loopVars.Count > 0) // Only suggest if inside a foreach + { + items.Add(new CompletionItem + { + LabelAsString = special, + Kind = CompletionItemKind.Variable, + Detail = special switch + { + "$index" => "Zero-based loop index", + "$first" => "True on first iteration", + "$last" => "True on last iteration", + _ => "" + }, + InsertText = special, + SortText = sortIndex++.ToString("D3"), + }); + } + } + + // Loop variable property paths from JSON + foreach (var loopVar in loopVars) + { + // Find array in JSON paths and suggest its item properties + var arrayPaths = FindMatchingArrayPaths(loopVar.ItemsPath); + + foreach (var prop in arrayPaths) + { + // prop.Path is like "Items[].Description" - we want "item.Description" + var suffix = prop.Path; + var bracketIdx = suffix.IndexOf("[].", StringComparison.Ordinal); + if (bracketIdx >= 0) + suffix = suffix[(bracketIdx + 3)..]; + else + continue; + + var label = $"{loopVar.VarName}.{suffix}"; + + items.Add(new CompletionItem + { + LabelAsString = label, + Kind = CompletionItemKind.Field, + Detail = $"{prop.Type} (from {loopVar.ItemsPath})", + InsertText = label, + SortText = sortIndex++.ToString("D3"), + }); + } + } + + // Top-level JSON paths + foreach (var prop in _jsonPaths.Where(p => !p.Path.Contains("[]."))) + { + var kind = prop.IsArray ? CompletionItemKind.Enum : CompletionItemKind.Field; + items.Add(new CompletionItem + { + LabelAsString = prop.Path, + Kind = kind, + Detail = prop.Type, + InsertText = prop.Path, + SortText = sortIndex++.ToString("D3"), + }); + } + + // Format specifiers + if (prefix.Contains(':')) + { + items.Clear(); + foreach (var (fmt, desc) in FormatSpecifiers) + { + items.Add(new CompletionItem + { + LabelAsString = fmt, + Kind = CompletionItemKind.Unit, + Detail = desc, + InsertText = fmt, + }); + } + } + + return items; + } + + private static readonly (string Format, string Description)[] FormatSpecifiers = + [ + ("F2", "Fixed-point 2 decimals (9.50)"), + ("F0", "Fixed-point no decimals (10)"), + ("N0", "Number with separators (1,234)"), + ("N2", "Number with separators + decimals (1,234.56)"), + ("dd.MM.yyyy", "Date (15.03.2024)"), + ("HH:mm", "Time (14:30)"), + ("HH:mm:ss", "Time with seconds (14:30:00)"), + ("dd-MMM-yy HH:mm", "Date + time (15-Mar-24 14:30)"), + ("dd.MM.yyyy HH:mm", "Date + time (15.03.2024 14:30)"), + ]; + + #endregion + + #region Loop Variable Detection + + private record LoopVarInfo(string VarName, string ItemsPath); + + private static List FindLoopVariables(string[] lines, int lineNumber) + { + var vars = new List(); + + // Walk backwards from current line to find enclosing foreach elements + var depth = 0; + for (var i = lineNumber - 1; i >= 0; i--) + { + var line = lines[i].Trim(); + + // Count closing foreach tags (increase depth needed) + if (line.Contains("")) + depth++; + + // Check for opening foreach tags + var foreachMatch = System.Text.RegularExpressions.Regex.Match(line, + @" 0) + { + depth--; + continue; + } + + string itemsPath, varName; + if (foreachMatch.Groups.Count >= 3) + { + // Determine which capture is items vs var based on pattern + if (line.IndexOf("items=", StringComparison.Ordinal) < line.IndexOf("var=", StringComparison.Ordinal)) + { + itemsPath = foreachMatch.Groups[1].Value; + varName = foreachMatch.Groups[2].Value; + } + else + { + varName = foreachMatch.Groups[1].Value; + itemsPath = foreachMatch.Groups[2].Value; + } + + vars.Add(new LoopVarInfo(varName, itemsPath)); + } + } + } + + return vars; + } + + private List FindMatchingArrayPaths(string itemsPath) + { + // itemsPath could be "Items" or "gang.Dishes" etc. + // We need to find JSON paths that start with the matching array path + "[]." + return _jsonPaths + .Where(p => p.Path.StartsWith(itemsPath + "[].", StringComparison.OrdinalIgnoreCase)) + .ToList(); + } + + #endregion +} diff --git a/Inspectron.PrintServer.TemplateEditor/wwwroot/app.css b/Inspectron.PrintServer.TemplateEditor/wwwroot/app.css new file mode 100644 index 0000000..c0732f8 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/wwwroot/app.css @@ -0,0 +1,180 @@ +h1:focus { + outline: none; +} + +html, body { + margin: 0; + padding: 0; + height: 100%; + overflow: hidden; +} + +.editor-container { + display: flex; + flex-direction: column; + height: 100vh; +} + +.toolbar { + display: flex; + gap: 4px; + padding: 6px 8px; + background: #2d2d2d; + border-bottom: 1px solid #404040; + flex-shrink: 0; +} + +.toolbar-btn { + padding: 4px 14px; + background: #3c3c3c; + color: #cccccc; + border: 1px solid #555; + border-radius: 3px; + cursor: pointer; + font-size: 13px; +} + +.toolbar-btn:hover { + background: #505050; +} + +.toolbar-separator { + width: 1px; + background: #555; + margin: 2px 6px; +} + +.json-indicator { + display: inline-flex; + align-items: center; + padding: 2px 10px; + background: #2b4a3e; + color: #4EC9B0; + border: 1px solid #3a6b57; + border-radius: 3px; + font-size: 12px; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.split-pane { + display: flex; + flex: 1; + min-height: 0; +} + +.pane { + display: flex; + flex-direction: column; + min-width: 0; +} + +.pane-editor { + flex: 1; + border-right: 1px solid #404040; +} + +.pane-preview { + flex: 1; + background: #1e1e1e; +} + +.pane-header { + padding: 4px 10px; + background: #252526; + color: #888; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.5px; + border-bottom: 1px solid #404040; + flex-shrink: 0; +} + +#my-editor-id { + flex: 1; + min-height: 0; +} + +.preview-content { + flex: 1; + padding: 16px; + overflow: auto; + color: #cccccc; +} +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid #e50000; +} + +.validation-message { + color: #e50000; +} + +#blazor-error-ui { + color-scheme: light only; + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + box-sizing: border-box; + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } + +.darker-border-checkbox.form-check-input { + border-color: #929292; +} + +.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder { + color: var(--bs-secondary-color); + text-align: end; +} + +.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder { + text-align: start; +} + +.status-bar-safe-area { + display: none; +} + +@supports (-webkit-touch-callout: none) { + .status-bar-safe-area { + display: flex; + position: sticky; + top: 0; + height: env(safe-area-inset-top); + background-color: #f7f7f7; + width: 100%; + z-index: 1; + } + + .flex-column, .navbar-brand { + padding-left: env(safe-area-inset-left); + } +} diff --git a/Inspectron.PrintServer.TemplateEditor/wwwroot/index.html b/Inspectron.PrintServer.TemplateEditor/wwwroot/index.html new file mode 100644 index 0000000..d96a143 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/wwwroot/index.html @@ -0,0 +1,33 @@ + + + + + + Inspectron.PrintServer.TemplateEditor + + + + + + + + + + + + +

+ +
Loading...
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + + + \ No newline at end of file diff --git a/Inspectron.PrintServer.TemplateEditor/wwwroot/receiptLanguage.js b/Inspectron.PrintServer.TemplateEditor/wwwroot/receiptLanguage.js new file mode 100644 index 0000000..61baf44 --- /dev/null +++ b/Inspectron.PrintServer.TemplateEditor/wwwroot/receiptLanguage.js @@ -0,0 +1,158 @@ +window.receiptLanguage = { + _registered: false, + + register: function () { + if (this._registered) return; + this._registered = true; + + monaco.languages.register({ id: 'receipt-xml' }); + + monaco.languages.setLanguageConfiguration('receipt-xml', { + comments: { + blockComment: [''] + }, + brackets: [ + ['<', '>'], + ['{{', '}}'] + ], + autoClosingPairs: [ + { open: '<', close: '>' }, + { open: '{{', close: '}}' }, + { open: '"', close: '"' }, + { open: "'", close: "'" } + ], + surroundingPairs: [ + { open: '<', close: '>' }, + { open: '"', close: '"' }, + { open: "'", close: "'" } + ] + }); + + var knownElements = [ + 'receipt', 'line', 'columns', 'row', 'col', 'separator', + 'cut', 'feed', 'table', 'foreach', 'if', 'else' + ]; + + monaco.languages.setMonarchTokensProvider('receipt-xml', { + knownElements: knownElements, + + tokenizer: { + root: [ + // Expression open + [/\{\{/, { token: 'delimiter.expression', next: '@expression' }], + + // XML comment + [//, { token: 'comment', next: '@pop' }], + [/./, 'comment'] + ] + } + }); + }, + + defineTheme: function () { + monaco.editor.defineTheme('receipt-dark', { + base: 'vs-dark', + inherit: true, + rules: [ + { token: 'tag.element', foreground: '569CD6', fontStyle: 'bold' }, + { token: 'tag.unknown', foreground: 'F44747' }, + { token: 'attribute.name', foreground: '9CDCFE' }, + { token: 'attribute.value', foreground: 'CE9178' }, + { token: 'attribute.value.delimiter', foreground: 'CE9178' }, + { token: 'delimiter.tag', foreground: '808080' }, + { token: 'delimiter', foreground: '808080' }, + { token: 'delimiter.expression', foreground: 'DCDCAA', fontStyle: 'bold' }, + { token: 'variable.property', foreground: '4EC9B0' }, + { token: 'variable.special', foreground: 'C586C0', fontStyle: 'italic' }, + { token: 'string.format', foreground: 'D7BA7D' }, + { token: 'comment', foreground: '6A9955', fontStyle: 'italic' }, + { token: 'text', foreground: 'D4D4D4' } + ], + colors: {} + }); + }, + + getEditorContent: function (editorId) { + var editor = blazorMonaco.editor.getEditorById(editorId); + if (editor) { + return editor.getValue(); + } + return ''; + } +};