editor first run
This commit is contained in:
652
Inspectron.Epson/HtmlPrintBuilder.cs
Normal file
652
Inspectron.Epson/HtmlPrintBuilder.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="GetHtml"/> to retrieve the result.
|
||||
/// </summary>
|
||||
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<Rgba32>? _loadedImage;
|
||||
private int _loadedImageMaxWidth;
|
||||
|
||||
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 HtmlPrintBuilder
|
||||
/// </summary>
|
||||
/// <param name="paperWidth">Paper width in pixels (default 384 for 80mm thermal printer)</param>
|
||||
/// <param name="logger">Optional logger for diagnostic output</param>
|
||||
/// <param name="id">Printer ID byte</param>
|
||||
public HtmlPrintBuilder(int paperWidth = 384, ILogger? logger = null, byte id = 0x01)
|
||||
{
|
||||
_paperWidth = paperWidth;
|
||||
_logger = logger;
|
||||
_id = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the generated HTML string.
|
||||
/// </summary>
|
||||
public string GetHtml()
|
||||
{
|
||||
return GenerateHtml();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Disconnect()
|
||||
{
|
||||
_logger?.LogDebug("HtmlPrintBuilder: Disconnecting");
|
||||
|
||||
_loadedImage?.Dispose();
|
||||
_loadedImage = null;
|
||||
_isConnected = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<PrinterStatus> 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
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<OfflineStatus> 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
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ErrorStatus> 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
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<PaperSensorStatus> GetPaperSensorStatusAsync()
|
||||
{
|
||||
_logger?.LogDebug("HtmlPrintBuilder: 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("HtmlPrintBuilder: Returning mock TM220 status");
|
||||
return Task.FromResult((byte)0x12);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<byte?> GetPrinterIdAsync()
|
||||
{
|
||||
return Task.FromResult<byte?>(_id);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<OverallStatus> 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<string>()
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task InitAsync()
|
||||
{
|
||||
_logger?.LogInformation("HtmlPrintBuilder: Initializing (resetting styling state)");
|
||||
ResetStylingState();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SetQuadrupleMode(bool enable)
|
||||
{
|
||||
_logger?.LogInformation("HtmlPrintBuilder: {Action} quadruple mode", enable ? "Enabling" : "Disabling");
|
||||
_isQuadrupleMode = enable;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SetRedColor(bool enable)
|
||||
{
|
||||
_logger?.LogInformation("HtmlPrintBuilder: {Action} red color mode", enable ? "Enabling" : "Disabling");
|
||||
_isRedColor = enable;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SetEmphasized(bool enable)
|
||||
{
|
||||
_logger?.LogInformation("HtmlPrintBuilder: {Action} emphasized mode", enable ? "Enabling" : "Disabling");
|
||||
_isEmphasized = enable;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SelectFont(EpsonCommands.PrinterFont font)
|
||||
{
|
||||
_logger?.LogInformation("HtmlPrintBuilder: Selecting font {Font}", font);
|
||||
_currentFont = font;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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", "<br>");
|
||||
|
||||
var positionStyle = _absolutePosition > 0 ? $"margin-left: {_absolutePosition}px;" : "";
|
||||
|
||||
_content.AppendLine($"<span style=\"{style}{positionStyle}\">{escapedText}</span>");
|
||||
|
||||
_absolutePosition = 0;
|
||||
|
||||
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("HtmlPrintBuilder: 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("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();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task PrintBuffer()
|
||||
{
|
||||
_logger?.LogDebug("HtmlPrintBuilder: PrintBuffer (no-op)");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task FeedLinesAsync(int lines)
|
||||
{
|
||||
EnsureConnected();
|
||||
_logger?.LogInformation("HtmlPrintBuilder: Feeding {Lines} lines", lines);
|
||||
|
||||
for (int i = 0; i < lines; i++)
|
||||
{
|
||||
_content.AppendLine($"<div style=\"height: {_lineSpacing}px;\"></div>");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CutAsync(bool fullCut = true)
|
||||
{
|
||||
EnsureConnected();
|
||||
_logger?.LogInformation("HtmlPrintBuilder: Cutting paper");
|
||||
_content.AppendLine("<div class=\"cut\"><span class=\"scissors\">✂️</span></div>");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<Rgba32>(imagePath);
|
||||
_loadedImageMaxWidth = maxWidth;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task LoadImageAsync(Stream imageStream, int maxWidth = 384)
|
||||
{
|
||||
_logger?.LogInformation("HtmlPrintBuilder: 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("HtmlPrintBuilder: 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("HtmlPrintBuilder: No image loaded to print");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger?.LogInformation("HtmlPrintBuilder: 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("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<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("HtmlPrintBuilder: 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("HtmlPrintBuilder: Printing image (bit mode) {Width}x{Height}", image.Width, image.Height);
|
||||
|
||||
return RenderImageToHtml(image, maxWidth);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendRawCommandAsync(byte[] command)
|
||||
{
|
||||
_logger?.LogDebug("HtmlPrintBuilder: SendRawCommandAsync ignored ({Length} bytes)", command.Length);
|
||||
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("HtmlPrintBuilder: Setting absolute print position to {X}", x);
|
||||
_absolutePosition = x;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SetDefaultLineSpacing()
|
||||
{
|
||||
_logger?.LogInformation("HtmlPrintBuilder: Setting default line spacing");
|
||||
_lineSpacing = 14;
|
||||
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("HtmlPrintBuilder: Setting custom line spacing to {Spacing}", spacing);
|
||||
_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;
|
||||
_isBiggerFontWidthTM220 = false;
|
||||
_isBiggerFontHeightTM220 = false;
|
||||
_lineSpacing = 14;
|
||||
_absolutePosition = 0;
|
||||
}
|
||||
|
||||
private string BuildCurrentStyle()
|
||||
{
|
||||
var styles = new List<string>();
|
||||
|
||||
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<Rgba32> 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<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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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($"<div class=\"image\" style=\"{positionStyle}\"><img src=\"{dataUri}\" alt=\"Printed image\"></div>");
|
||||
|
||||
_absolutePosition = 0;
|
||||
|
||||
_logger?.LogInformation("HtmlPrintBuilder: Image embedded as base64 ({Bytes} bytes)", ms.Length);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -6,7 +6,6 @@ public interface IPrinter
|
||||
{
|
||||
public Task InitAsync();
|
||||
public Task PrintImageAsync(string path);
|
||||
public Task SetFontSizeAsync(int fontSize);
|
||||
public Task PrintAsync(List<PrintCommand> commands);
|
||||
public Task Cut();
|
||||
}
|
||||
Reference in New Issue
Block a user