Implement stage 2 layout validation
This commit is contained in:
@@ -4,7 +4,7 @@ Windows-Tray-Anwendung für den späteren Etikettendruck aus JSON-Layouts.
|
||||
|
||||
## Etappe 1
|
||||
|
||||
Dieser Stand ist ein lauffähiges Grundgerüst:
|
||||
Die erste Etappe enthält das lauffähige Grundgerüst:
|
||||
|
||||
- .NET-9-Windows-Forms-Projekt
|
||||
- Tray-Icon mit Kontextmenü
|
||||
@@ -21,7 +21,19 @@ Dieser Stand ist ein lauffähiges Grundgerüst:
|
||||
- verschlüsselte Passwortspeicherung per Windows DPAPI
|
||||
- Auflistung installierter Windows-Drucker
|
||||
- Beispiel-Layout `dymo_57x32_standard`
|
||||
- Layout-JSON laden, validieren und speichern
|
||||
- Layout-JSON laden und speichern
|
||||
|
||||
## Etappe 2
|
||||
|
||||
Die zweite Etappe ergänzt das Layoutmodell und die JSON-Validierung:
|
||||
|
||||
- typisierte Layoutklassen für Text, Linie, Rechteck und QR-Code
|
||||
- JSON-Deserialisierung anhand der Element-Eigenschaft `type`
|
||||
- Validierung des Layoutkopfs und aller Elemente
|
||||
- Sammlung aller Validierungsfehler statt Abbruch beim ersten Fehler
|
||||
- Anzeige der Validierungsfehler im Layout-Tab
|
||||
- Speichern nur bei gültigem Layout
|
||||
- formatierte Speicherung des Layout-JSON
|
||||
|
||||
Noch nicht enthalten sind MySQL-Worker, echtes Drucken, QR-Code-Rendering und die vollständige Rendering-Engine.
|
||||
|
||||
|
||||
@@ -40,6 +40,14 @@ internal sealed class SettingsForm : Form
|
||||
Width = 900,
|
||||
Height = 460
|
||||
};
|
||||
private readonly TextBox _layoutValidationErrors = new()
|
||||
{
|
||||
Multiline = true,
|
||||
ReadOnly = true,
|
||||
ScrollBars = ScrollBars.Vertical,
|
||||
Width = 900,
|
||||
Height = 72
|
||||
};
|
||||
|
||||
private readonly DataGridView _errorJobs = new()
|
||||
{
|
||||
@@ -122,9 +130,12 @@ internal sealed class SettingsForm : Form
|
||||
panel.Controls.Add(_layoutJson);
|
||||
_layoutJson.Left = 20;
|
||||
_layoutJson.Top = 60;
|
||||
panel.Controls.Add(ButtonAt("Validieren", 20, 540, (_, _) => ValidateLayout()));
|
||||
panel.Controls.Add(ButtonAt("Speichern", 130, 540, (_, _) => SaveLayout()));
|
||||
panel.Controls.Add(LabelAt("Vorschau und Rendering folgen in Etappe 2.", 250, 546, 360));
|
||||
panel.Controls.Add(_layoutValidationErrors);
|
||||
_layoutValidationErrors.Left = 20;
|
||||
_layoutValidationErrors.Top = 530;
|
||||
panel.Controls.Add(ButtonAt("Validieren", 20, 620, (_, _) => ValidateLayout(showSuccessMessage: true)));
|
||||
panel.Controls.Add(ButtonAt("Speichern", 130, 620, (_, _) => SaveLayout()));
|
||||
panel.Controls.Add(LabelAt("Vorschau und Rendering folgen in einer späteren Etappe.", 250, 626, 420));
|
||||
return new TabPage("Layout") { Controls = { panel } };
|
||||
}
|
||||
|
||||
@@ -247,24 +258,51 @@ internal sealed class SettingsForm : Form
|
||||
MessageBox.Show("Druckereinstellungen gespeichert.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
|
||||
private void ValidateLayout()
|
||||
private bool ValidateLayout(bool showSuccessMessage, out LabelLayout? layout)
|
||||
{
|
||||
layout = null;
|
||||
try
|
||||
{
|
||||
_layoutStore.ValidateJson(_layoutJson.Text);
|
||||
MessageBox.Show("Layout ist gültig.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
var result = _layoutStore.ValidateJson(_layoutJson.Text, out layout);
|
||||
if (result.IsValid)
|
||||
{
|
||||
_layoutValidationErrors.Text = string.Empty;
|
||||
if (showSuccessMessage)
|
||||
{
|
||||
MessageBox.Show("Layout ist gültig.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var message = string.Join(Environment.NewLine, result.Errors);
|
||||
_layoutValidationErrors.Text = message;
|
||||
MessageBox.Show(message, "Layout ist ungültig", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowError(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateLayout(bool showSuccessMessage)
|
||||
{
|
||||
return ValidateLayout(showSuccessMessage, out _);
|
||||
}
|
||||
|
||||
private void SaveLayout()
|
||||
{
|
||||
try
|
||||
{
|
||||
_layoutStore.SaveJson(_layoutJson.Text);
|
||||
if (!ValidateLayout(showSuccessMessage: false, out var layout) || layout is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_layoutStore.SaveLayout(layout);
|
||||
_layoutJson.Text = LayoutJsonSerializer.Serialize(layout);
|
||||
LoadLayouts();
|
||||
MessageBox.Show("Layout gespeichert.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace LabelPrintAgent.Layout;
|
||||
|
||||
public sealed class LabelLayout
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public decimal WidthMm { get; set; } = 57;
|
||||
public decimal HeightMm { get; set; } = 32;
|
||||
public double WidthMm { get; set; } = 57;
|
||||
public double HeightMm { get; set; } = 32;
|
||||
public int Dpi { get; set; } = 300;
|
||||
public decimal MarginMm { get; set; } = 3;
|
||||
public double MarginMm { get; set; } = 3;
|
||||
public string Orientation { get; set; } = "landscape";
|
||||
public List<LayoutElement> Elements { get; set; } = [];
|
||||
}
|
||||
|
||||
[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
|
||||
[JsonDerivedType(typeof(TextElement), "text")]
|
||||
[JsonDerivedType(typeof(LineElement), "line")]
|
||||
[JsonDerivedType(typeof(RectangleElement), "rectangle")]
|
||||
[JsonDerivedType(typeof(QrCodeElement), "qr")]
|
||||
public abstract class LayoutElement
|
||||
{
|
||||
public string Type { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
namespace LabelPrintAgent.Layout;
|
||||
|
||||
public enum HorizontalAlign
|
||||
public abstract class LayoutElement
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public double XMm { get; set; }
|
||||
public double YMm { get; set; }
|
||||
}
|
||||
|
||||
public enum VerticalAlign
|
||||
internal sealed class UnknownLayoutElement : LayoutElement
|
||||
{
|
||||
Top,
|
||||
Middle,
|
||||
Bottom
|
||||
}
|
||||
|
||||
@@ -10,17 +10,18 @@ public sealed class LayoutElementJsonConverter : JsonConverter<LayoutElement>
|
||||
using var document = JsonDocument.ParseValue(ref reader);
|
||||
if (!document.RootElement.TryGetProperty("type", out var typeProperty))
|
||||
{
|
||||
throw new JsonException("Layout element has no type.");
|
||||
return new UnknownLayoutElement();
|
||||
}
|
||||
|
||||
var raw = document.RootElement.GetRawText();
|
||||
return typeProperty.GetString()?.ToLowerInvariant() switch
|
||||
var type = typeProperty.GetString()?.ToLowerInvariant() ?? string.Empty;
|
||||
return type switch
|
||||
{
|
||||
"text" => JsonSerializer.Deserialize<TextElement>(raw, options) ?? throw new JsonException(),
|
||||
"line" => JsonSerializer.Deserialize<LineElement>(raw, options) ?? throw new JsonException(),
|
||||
"rectangle" => JsonSerializer.Deserialize<RectangleElement>(raw, options) ?? throw new JsonException(),
|
||||
"qr" => JsonSerializer.Deserialize<QrCodeElement>(raw, options) ?? throw new JsonException(),
|
||||
_ => throw new JsonException($"Unknown layout element type '{typeProperty.GetString()}'.")
|
||||
"text" => WithType(JsonSerializer.Deserialize<TextElement>(raw, options), type),
|
||||
"line" => WithType(JsonSerializer.Deserialize<LineElement>(raw, options), type),
|
||||
"rectangle" => WithType(JsonSerializer.Deserialize<RectangleElement>(raw, options), type),
|
||||
"qr" => WithType(JsonSerializer.Deserialize<QrCodeElement>(raw, options), type),
|
||||
_ => new UnknownLayoutElement { Type = type }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,4 +29,15 @@ public sealed class LayoutElementJsonConverter : JsonConverter<LayoutElement>
|
||||
{
|
||||
JsonSerializer.Serialize(writer, (object)value, value.GetType(), options);
|
||||
}
|
||||
|
||||
private static T WithType<T>(T? element, string type) where T : LayoutElement
|
||||
{
|
||||
if (element is null)
|
||||
{
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
element.Type = type;
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace LabelPrintAgent.Layout;
|
||||
|
||||
public static class LayoutJsonSerializer
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Converters = { new LayoutElementJsonConverter() }
|
||||
};
|
||||
|
||||
public static LabelLayout? Deserialize(string json)
|
||||
{
|
||||
return JsonSerializer.Deserialize<LabelLayout>(json, JsonOptions);
|
||||
}
|
||||
|
||||
public static string Serialize(LabelLayout layout)
|
||||
{
|
||||
return JsonSerializer.Serialize(layout, JsonOptions);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,12 @@
|
||||
using System.Text.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace LabelPrintAgent.Layout;
|
||||
|
||||
public sealed class LayoutStore : IDisposable
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
Converters = { new LayoutElementJsonConverter() }
|
||||
};
|
||||
|
||||
private readonly string _layoutFolder;
|
||||
private readonly LayoutValidator _validator = new();
|
||||
|
||||
public LayoutStore(string layoutFolder)
|
||||
{
|
||||
@@ -22,7 +17,9 @@ public sealed class LayoutStore : IDisposable
|
||||
public IReadOnlyList<string> GetLayoutKeys()
|
||||
{
|
||||
return Directory.GetFiles(_layoutFolder, "*.json")
|
||||
.Select(path => Load(path).Key)
|
||||
.Select(TryLoad)
|
||||
.Where(layout => layout is not null)
|
||||
.Select(layout => layout!.Key)
|
||||
.Where(key => !string.IsNullOrWhiteSpace(key))
|
||||
.OrderBy(key => key)
|
||||
.ToList();
|
||||
@@ -56,30 +53,79 @@ public sealed class LayoutStore : IDisposable
|
||||
throw new FileNotFoundException($"Layout '{key}' wurde nicht gefunden.");
|
||||
}
|
||||
|
||||
public LabelLayout ValidateJson(string json)
|
||||
public LabelLayout DeserializeJson(string json)
|
||||
{
|
||||
return JsonSerializer.Deserialize<LabelLayout>(json, JsonOptions)
|
||||
return LayoutJsonSerializer.Deserialize(json)
|
||||
?? throw new InvalidOperationException("Layout-JSON ist leer.");
|
||||
}
|
||||
|
||||
public LayoutValidationResult ValidateJson(string json, out LabelLayout? layout)
|
||||
{
|
||||
layout = null;
|
||||
try
|
||||
{
|
||||
layout = DeserializeJson(json);
|
||||
return _validator.Validate(layout);
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
Log.Warning(ex, "Invalid layout JSON");
|
||||
var result = new LayoutValidationResult();
|
||||
result.AddError($"Layout-JSON ist ungültig: {ex.Message}");
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Could not parse layout JSON");
|
||||
var result = new LayoutValidationResult();
|
||||
result.AddError($"Layout-JSON konnte nicht gelesen werden: {ex.Message}");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveLayout(LabelLayout layout)
|
||||
{
|
||||
var validation = _validator.Validate(layout);
|
||||
if (!validation.IsValid)
|
||||
{
|
||||
throw new InvalidOperationException(string.Join(Environment.NewLine, validation.Errors));
|
||||
}
|
||||
|
||||
var fileName = $"{layout.Key}.json";
|
||||
File.WriteAllText(Path.Combine(_layoutFolder, fileName), LayoutJsonSerializer.Serialize(layout));
|
||||
}
|
||||
|
||||
public void SaveJson(string json)
|
||||
{
|
||||
var layout = ValidateJson(json);
|
||||
if (string.IsNullOrWhiteSpace(layout.Key))
|
||||
var validation = ValidateJson(json, out var layout);
|
||||
if (!validation.IsValid || layout is null)
|
||||
{
|
||||
throw new InvalidOperationException("Layout-Key fehlt.");
|
||||
throw new InvalidOperationException(string.Join(Environment.NewLine, validation.Errors));
|
||||
}
|
||||
|
||||
File.WriteAllText(Path.Combine(_layoutFolder, $"{layout.Key}.json"), json);
|
||||
SaveLayout(layout);
|
||||
}
|
||||
|
||||
private static LabelLayout Load(string path)
|
||||
{
|
||||
var json = File.ReadAllText(path);
|
||||
return JsonSerializer.Deserialize<LabelLayout>(json, JsonOptions)
|
||||
return LayoutJsonSerializer.Deserialize(json)
|
||||
?? throw new InvalidOperationException($"Layout '{path}' ist ungültig.");
|
||||
}
|
||||
|
||||
private static LabelLayout? TryLoad(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Load(path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Could not load layout file {LayoutPath}", path);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void EnsureExampleLayout(string layoutFolder)
|
||||
{
|
||||
Directory.CreateDirectory(layoutFolder);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace LabelPrintAgent.Layout;
|
||||
|
||||
public sealed class LayoutValidationResult
|
||||
{
|
||||
public List<string> Errors { get; } = [];
|
||||
|
||||
public bool IsValid => Errors.Count == 0;
|
||||
|
||||
public void AddError(string error)
|
||||
{
|
||||
Errors.Add(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
namespace LabelPrintAgent.Layout;
|
||||
|
||||
public sealed class LayoutValidator
|
||||
{
|
||||
private static readonly HashSet<string> Orientations = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"landscape",
|
||||
"portrait"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> HorizontalAlignments = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"left",
|
||||
"center",
|
||||
"right"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> VerticalAlignments = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"top",
|
||||
"middle",
|
||||
"bottom"
|
||||
};
|
||||
|
||||
public LayoutValidationResult Validate(LabelLayout? layout)
|
||||
{
|
||||
var result = new LayoutValidationResult();
|
||||
if (layout is null)
|
||||
{
|
||||
result.AddError("Layout: JSON enthält kein Layout.");
|
||||
return result;
|
||||
}
|
||||
|
||||
ValidateLayout(layout, result);
|
||||
ValidateElements(layout, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void ValidateLayout(LabelLayout layout, LayoutValidationResult result)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(layout.Name))
|
||||
{
|
||||
result.AddError("Layout: Name darf nicht leer sein.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(layout.Key))
|
||||
{
|
||||
result.AddError("Layout: Key darf nicht leer sein.");
|
||||
}
|
||||
|
||||
if (layout.WidthMm <= 0)
|
||||
{
|
||||
result.AddError("Layout: WidthMm muss größer als 0 sein.");
|
||||
}
|
||||
|
||||
if (layout.HeightMm <= 0)
|
||||
{
|
||||
result.AddError("Layout: HeightMm muss größer als 0 sein.");
|
||||
}
|
||||
|
||||
if (layout.Dpi <= 0)
|
||||
{
|
||||
result.AddError("Layout: Dpi muss größer als 0 sein.");
|
||||
}
|
||||
|
||||
if (layout.MarginMm < 0)
|
||||
{
|
||||
result.AddError("Layout: MarginMm darf nicht negativ sein.");
|
||||
}
|
||||
|
||||
if (!Orientations.Contains(layout.Orientation))
|
||||
{
|
||||
result.AddError("Layout: Orientation muss 'landscape' oder 'portrait' sein.");
|
||||
}
|
||||
|
||||
if (layout.Elements is null)
|
||||
{
|
||||
result.AddError("Layout: Elements darf nicht null sein.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateElements(LabelLayout layout, LayoutValidationResult result)
|
||||
{
|
||||
if (layout.Elements is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < layout.Elements.Count; i++)
|
||||
{
|
||||
var index = i + 1;
|
||||
var element = layout.Elements[i];
|
||||
switch (element)
|
||||
{
|
||||
case null:
|
||||
result.AddError($"Element {index}: Element darf nicht null sein.");
|
||||
break;
|
||||
case TextElement text:
|
||||
ValidateTextElement(layout, text, index, result);
|
||||
break;
|
||||
case LineElement line:
|
||||
ValidateLineElement(layout, line, index, result);
|
||||
break;
|
||||
case RectangleElement rectangle:
|
||||
ValidateRectangleElement(layout, rectangle, index, result);
|
||||
break;
|
||||
case QrCodeElement qrCode:
|
||||
ValidateQrCodeElement(layout, qrCode, index, result);
|
||||
break;
|
||||
default:
|
||||
result.AddError($"Element {index}: Unbekannter Elementtyp '{element.Type}'.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateTextElement(LabelLayout layout, TextElement element, int index, LayoutValidationResult result)
|
||||
{
|
||||
if (!IsBoxInside(layout, element.XMm, element.YMm, element.WidthMm, element.HeightMm))
|
||||
{
|
||||
result.AddError($"Element {index}: Textfeld liegt außerhalb des Etiketts.");
|
||||
}
|
||||
|
||||
if (element.WidthMm <= 0)
|
||||
{
|
||||
result.AddError($"Element {index}: Textfeld hat ungültige Breite.");
|
||||
}
|
||||
|
||||
if (element.HeightMm <= 0)
|
||||
{
|
||||
result.AddError($"Element {index}: Textfeld hat ungültige Höhe.");
|
||||
}
|
||||
|
||||
if (element.FontSizePt <= 0)
|
||||
{
|
||||
result.AddError($"Element {index}: Textfeld hat ungültige Schriftgröße.");
|
||||
}
|
||||
|
||||
if (element.MinFontSizePt <= 0)
|
||||
{
|
||||
result.AddError($"Element {index}: Textfeld hat ungültige Mindestschriftgröße.");
|
||||
}
|
||||
|
||||
if (element.MinFontSizePt > element.FontSizePt)
|
||||
{
|
||||
result.AddError($"Element {index}: MinFontSizePt darf nicht größer als FontSizePt sein.");
|
||||
}
|
||||
|
||||
if (!HorizontalAlignments.Contains(element.HorizontalAlign))
|
||||
{
|
||||
result.AddError($"Element {index}: HorizontalAlign muss left, center oder right sein.");
|
||||
}
|
||||
|
||||
if (!VerticalAlignments.Contains(element.VerticalAlign))
|
||||
{
|
||||
result.AddError($"Element {index}: VerticalAlign muss top, middle oder bottom sein.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(element.Value))
|
||||
{
|
||||
result.AddError($"Element {index}: Textfeld-Value darf nicht leer sein.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateLineElement(LabelLayout layout, LineElement element, int index, LayoutValidationResult result)
|
||||
{
|
||||
if (!IsPointInside(layout, element.X1Mm, element.Y1Mm) || !IsPointInside(layout, element.X2Mm, element.Y2Mm))
|
||||
{
|
||||
result.AddError($"Element {index}: Linie liegt außerhalb des Etiketts.");
|
||||
}
|
||||
|
||||
if (element.StrokeWidthMm <= 0)
|
||||
{
|
||||
result.AddError($"Element {index}: Linie hat ungültige Strichstärke.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateRectangleElement(LabelLayout layout, RectangleElement element, int index, LayoutValidationResult result)
|
||||
{
|
||||
if (!IsBoxInside(layout, element.XMm, element.YMm, element.WidthMm, element.HeightMm))
|
||||
{
|
||||
result.AddError($"Element {index}: Rechteck liegt außerhalb des Etiketts.");
|
||||
}
|
||||
|
||||
if (element.WidthMm <= 0)
|
||||
{
|
||||
result.AddError($"Element {index}: Rechteck hat ungültige Breite.");
|
||||
}
|
||||
|
||||
if (element.HeightMm <= 0)
|
||||
{
|
||||
result.AddError($"Element {index}: Rechteck hat ungültige Höhe.");
|
||||
}
|
||||
|
||||
if (element.StrokeWidthMm <= 0)
|
||||
{
|
||||
result.AddError($"Element {index}: Rechteck hat ungültige Strichstärke.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateQrCodeElement(LabelLayout layout, QrCodeElement element, int index, LayoutValidationResult result)
|
||||
{
|
||||
if (!IsBoxInside(layout, element.XMm, element.YMm, element.SizeMm, element.SizeMm))
|
||||
{
|
||||
result.AddError($"Element {index}: QR-Code liegt außerhalb des Etiketts.");
|
||||
}
|
||||
|
||||
if (element.SizeMm <= 0)
|
||||
{
|
||||
result.AddError($"Element {index}: QR-Code hat ungültige Größe.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(element.Value))
|
||||
{
|
||||
result.AddError($"Element {index}: QR-Code-Value darf nicht leer sein.");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPointInside(LabelLayout layout, double xMm, double yMm)
|
||||
{
|
||||
return xMm >= 0 && yMm >= 0 && xMm <= layout.WidthMm && yMm <= layout.HeightMm;
|
||||
}
|
||||
|
||||
private static bool IsBoxInside(LabelLayout layout, double xMm, double yMm, double widthMm, double heightMm)
|
||||
{
|
||||
return xMm >= 0
|
||||
&& yMm >= 0
|
||||
&& xMm + Math.Max(0, widthMm) <= layout.WidthMm
|
||||
&& yMm + Math.Max(0, heightMm) <= layout.HeightMm;
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,14 @@ namespace LabelPrintAgent.Layout;
|
||||
|
||||
public sealed class LineElement : LayoutElement
|
||||
{
|
||||
public decimal X1Mm { get; set; }
|
||||
public decimal Y1Mm { get; set; }
|
||||
public decimal X2Mm { get; set; }
|
||||
public decimal Y2Mm { get; set; }
|
||||
public decimal StrokeWidthMm { get; set; } = 0.3m;
|
||||
public LineElement()
|
||||
{
|
||||
Type = "line";
|
||||
}
|
||||
|
||||
public double X1Mm { get; set; }
|
||||
public double Y1Mm { get; set; }
|
||||
public double X2Mm { get; set; }
|
||||
public double Y2Mm { get; set; }
|
||||
public double StrokeWidthMm { get; set; } = 0.3;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ namespace LabelPrintAgent.Layout;
|
||||
|
||||
public sealed class QrCodeElement : LayoutElement
|
||||
{
|
||||
public decimal XMm { get; set; }
|
||||
public decimal YMm { get; set; }
|
||||
public decimal SizeMm { get; set; }
|
||||
public QrCodeElement()
|
||||
{
|
||||
Type = "qr";
|
||||
}
|
||||
|
||||
public double SizeMm { get; set; }
|
||||
public string Value { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ namespace LabelPrintAgent.Layout;
|
||||
|
||||
public sealed class RectangleElement : LayoutElement
|
||||
{
|
||||
public decimal XMm { get; set; }
|
||||
public decimal YMm { get; set; }
|
||||
public decimal WidthMm { get; set; }
|
||||
public decimal HeightMm { get; set; }
|
||||
public decimal StrokeWidthMm { get; set; } = 0.3m;
|
||||
public RectangleElement()
|
||||
{
|
||||
Type = "rectangle";
|
||||
}
|
||||
|
||||
public double WidthMm { get; set; }
|
||||
public double HeightMm { get; set; }
|
||||
public double StrokeWidthMm { get; set; } = 0.3;
|
||||
public bool Filled { get; set; }
|
||||
}
|
||||
|
||||
@@ -2,14 +2,17 @@ namespace LabelPrintAgent.Layout;
|
||||
|
||||
public sealed class TextElement : LayoutElement
|
||||
{
|
||||
public decimal XMm { get; set; }
|
||||
public decimal YMm { get; set; }
|
||||
public decimal WidthMm { get; set; }
|
||||
public decimal HeightMm { get; set; }
|
||||
public TextElement()
|
||||
{
|
||||
Type = "text";
|
||||
}
|
||||
|
||||
public double WidthMm { get; set; }
|
||||
public double HeightMm { get; set; }
|
||||
public string Value { get; set; } = string.Empty;
|
||||
public string FontFamily { get; set; } = "Arial";
|
||||
public float FontSizePt { get; set; } = 9;
|
||||
public float MinFontSizePt { get; set; } = 6;
|
||||
public double FontSizePt { get; set; } = 9;
|
||||
public double MinFontSizePt { get; set; } = 6;
|
||||
public bool AutoShrink { get; set; } = true;
|
||||
public bool Bold { get; set; }
|
||||
public bool Italic { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user