zplCore.comzplCore.com

10Label-Modell

ZplCore.Labels.Label beschreibt ein Etikett in Mm, Cm, Inch, Point oder Dots. Daraus entstehen ZPL, Vorschau-PNG/PDF, HTML und JSON.

Mehr dazu auf zplCloud.com: Vorlage: Vorlagen-Katalog

TextItem

Font, FontSize (pt), Rotation, Width/MaxLines/Alignment (→ ^FB), CounterStep, MaxLength, HideIfEmpty, AutoFit, ValidationRegex, InputMask, Bold (nativ per Doppeldruck), ShortHyphen (kurzer Bindestrich in Font 0)

BarcodeItem

alle 144 Symbologien (ZPL-Befehl oder ^GFA-Grafik), Generator-Optionen (Farben, Klarschrift, QR-Version/Maske, Modulformen), ModuleWidth, WideRatio, BarHeight, HumanReadable(Above), AddChecksum, QrErrorCorrection, HideIfEmpty (kein ZPL bei leeren Daten); Width/Height passen einen nativen Barcode in eine Box ein (größte passende Modulbreite, FitSample für gebundene Daten)

Formen

RectangleItem (CornerRounding, Fill, White), LineItem, EllipseItem, CircleItem

ImageItem

PNG/JPEG/SVG → ^GFA, Dither oder Threshold, Width/Height

TableItem · RepeaterItem

Tabellen mit [Feld]-Platzhaltern; Wiederholung pro Element einer Collection

RfidItem · RawZplItem

^RF-Schreibbefehl bzw. ZPL 1:1 als Notausgang

Gemeinsam für alle Elemente: Name, X/Y, Visible, DataField + DataFieldFormat, Expression, Reverse, PrintAsGraphic. Auf Etikettenebene: GapLength, Darkness, PrintSpeed, PrintInverted, PrintMirror, CutAfterPrinting.

Kulturen: DataFieldFormat formatiert mit TextItem.CultureName (z. B. "de-DE"; ohne: invariant mit Format, aktuelle Kultur ohne Format). Jede kulturabhängige Formatierung von zplCore – gebundene Felder, Zahltexte, Tabellenzellen, Ausdrücke, Uhrzeitfelder – druckt auf jedem Betriebssystem gleich: Datumswerte verwenden den Standardkalender der Kultur aus einer festen Tabelle (ExpressionEngine.StandardCalendar: ar-SA Umm al-Qura, th thailändisch-buddhistisch, persisch für fa, ps, ckb-IR, lrc, mzn, uz-Arab; alle anderen gregorianisch), und Sonder-Leerzeichen wie das schmale geschützte Leerzeichen in den Tausendergruppen von fr-FR oder in 2:30 PM werden zu normalen Leerzeichen. Details: Kulturen in der Ausdrucks-Referenz.

var label = new Label(LabelUnit.Mm, 100, 60)
    .Add(new RectangleItem { X = 1, Y = 1, Width = 98, Height = 58, StrokeThickness = 0.5, CornerRounding = 2 })
    .Add(new TextItem { X = 5, Y = 4, DataField = "Name", FontSize = 16 })
    .Add(new TextItem { X = 5, Y = 13, Expression = "\"Lieferant: \" + Upper([Supplier.Name])", FontSize = 9 })
    .Add(new TextItem { X = 62, Y = 13, DataField = "Price", DataFieldFormat = "€ 0.00", FontSize = 9 })
    .Add(new BarcodeItem { X = 5, Y = 22, DataField = "Sku", Symbology = Symbology.Ean13, BarHeight = 18 })
    .Add(new BarcodeItem { X = 70, Y = 22, DataField = "Url", Symbology = Symbology.QrCode, BarHeight = 4 })
    .Add(new TextItem { X = 5, Y = 50, FontSize = 9,
        Expression = "\"Etikett \" + LABEL_NUMBER + \" · \" + If([Price] > 5, \"PREMIUM\", \"STANDARD\")" });

string zpl = label.ToZpl(products);              // ein Etikett pro Datensatz
byte[] preview = label.RenderPng();              // Vorschau ohne Daten
File.WriteAllText("label.json", label.ToJson());
Label copy = Label.FromJson(File.ReadAllText("label.json"));
Ergebnis: zwei Datensätze → zwei Etiketten
Label-Modell 1 – gerendert mit zplCore.com
Label-Modell 2 – gerendert mit zplCore.com
Generiertes ZPL anzeigen

Barcode-Optionen im Label (BarcodeItem.Options)

BarcodeItemOptions bringt jede Render- und Symbologie-Option des Generators ins Label-Modell (Farben, Ränder, Abstände, Klarschrift-Position, Data-Matrix-Größe, Aztec-Lagen, TLC39-Abstand …) und wird im Label-JSON gespeichert. Ohne gesetzte Option bleibt der native ZPL-Befehl, mit Optionen wird der Barcode als ^GFA-Grafik ausgegeben.

var label = new Label(LabelUnit.Dots, 600, 600).Add(new BarcodeItem
{
    X = 20, Y = 20, Symbology = Symbology.DataMatrix, Code = "Label options",
    Options = new BarcodeItemOptions                // jede Generator-Option, auch im JSON
    {
        DataMatrixSize = "32x32", BorderColor = "#000000",
        PaddingLeft = 8, PaddingTop = 8, PaddingRight = 8, PaddingBottom = 8,
    },
}).Add(new BarcodeItem
{
    X = 20, Y = 300, Symbology = Symbology.Tlc39, Code = "123456,SERIAL1", ModuleWidth = 3, BarHeight = 90,
    Options = new BarcodeItemOptions { TextPosition = BC.BarcodeTextPosition.None, Tlc39Gap = 3 },
});
string zpl = label.ToZpl();                          // mit Optionen: ^GFA-Grafik des Generators

Ausdrücke

Vollständige Referenz: jede Funktion mit Parametern und Beispielen, Kulturen und Zahlen-/Datumsformate, leere Werte, Auftragsvariablen und Rezepte auf der Seite Ausdrücke: Funktionen, Formate, Kulturen. Ausdrücke mit eigenen Daten testen im Expression-Builder.

Felder als [Name] oder [Kunde.Adresse.Ort], Elementnamen ([TextItem1]), Literale, Operatoren + - * / % (+ verkettet Text), = <> < <= > >=, and/or/not. Variablen LABEL_INDEX, LABEL_NUMBER und eigene.

var label = new Label();
label.Expressions.Variables["SHIFT"] = "B";
label.Expressions.Functions["Euro"] = args => $"{ExpressionEngine.Num(args[0]):0.00} €";

label.Add(new TextItem { Expression = "Euro([Price] * 1.2) + \" / Schicht \" + SHIFT" });
label.Add(new TextItem { Y = 8, Expression = "Format(AddDays(Today(), 30), \"dd.MM.yyyy\")" });
label.Add(new TextItem { Y = 16, Expression = "Gs1CheckDigit(\"95060001343\")" });

var engine = new ExpressionEngine();
string s = engine.EvaluateToString("PadLeft(Str(42), 6, \"0\")");   // "000042"

Typen, eigene Funktionen und Variablen

Ausdrücke dürfen öffentliche Member registrierter Typen aufrufen (AddTypeReference) – alles andere bleibt gesperrt. SetCustomFunction/SetCustomVariable sind die verkettbaren Gegenstücke zu Functions[…]/Variables[…].

var label = new Label();
label.Expressions
    .AddTypeReference(typeof(Math))                 // Math.Max(1, [Qty]) …
    .AddTypeReference<DateTime>()                   // DateTime.Now.AddDays(3)
    .SetCustomVariable("WERK", "Graz")
    .SetCustomFunction("Kg", args => $"{ExpressionEngine.Num(args[0]):0.0} kg");

label.Add(new TextItem { Expression = "\"Werk \" + WERK + \": \" + Kg([Weight])" });
label.Add(new TextItem { Y = 8, Expression = "Math.Max(1, [Qty]) + \" Stk\"" });
label.Add(new TextItem { Y = 16, Expression = "DateTime.Now.AddDays(3).ToString(\"dd.MM.\")" });
// nicht registrierte Typen sind gesperrt → FormatException

Tabellen & Repeater

var label = new Label(LabelUnit.Mm, 100, 60)
    .Add(new TextItem { X = 3, Y = 3, Expression = "\"Lieferschein \" + [No]", FontSize = 14 })
    .Add(new TableItem
    {
        X = 3, Y = 12, ColumnWidths = [50, 44], RowHeights = [7, 7], HeaderRow = true, FontSize = 9,
        Cells = [["Kunde", "Datum"], ["[Customer]", "18.10.2026"]],
    })
    .Add(new RepeaterItem
    {
        X = 3, Y = 30, OffsetY = 7, MaxCount = 4, DataField = "Lines",   // eine Zeile pro Position
        Items = [new TextItem { DataField = "Sku" }, new TextItem { X = 60, DataField = "Qty", DataFieldFormat = "0 Stk" }],
    });

byte[] png = label.For<Order>().Render([order]).First();
Tabelle und Repeater

So funktioniert's

Der Repeater liest die Collection Lines des Datensatzes und versetzt seine Kind-Elemente pro Eintrag um OffsetY; Kinder binden an die Member des Eintrags. MaxCount begrenzt die Zeilen.

Validierung, AutoFit, Micro QR, HTML

var label = new Label(LabelUnit.Mm, 100, 60)
    .Add(new TextItem { Name = "Plz", DataField = "Plz", ValidationRegex = @"^\d{4,5}$" })
    .Add(new TextItem { Name = "Lot", X = 30, DataField = "Lot", InputMask = "AA-0000", MaxLength = 7 })
    .Add(new TextItem { Y = 8, DataField = "Hinweis", HideIfEmpty = true })
    .Add(new TextItem { Y = 16, DataField = "Name", Width = 90, FontSize = 20, AutoFit = true })
    .Add(new BarcodeItem { Y = 30, Code = "01234567", Symbology = Symbology.MicroQr, BarHeight = 4 })
    .Add(new RfidItem { Data = "3034257BF7194E4000001A85" });

var record = new Dictionary<string, object?> { ["Plz"] = "x", ["Lot"] = "AB-12", ["Name"] = "Sehr langer Produktname" };
foreach (string error in label.Validate(record))
    Console.WriteLine(error);                        // "Plz: 'x' entspricht nicht ^\d{4,5}$" ...

File.WriteAllText("vorschau.html", label.ToHtml(records: [record]));

Masken-Zähler (Mask / MaskIncrement)

Zähler mit gemischten Stellen: Ziffern, Buchstaben und alphanumerische Positionen bilden eine Zahl mit gemischter Basis – der Übertrag läuft von 9999 in die Buchstaben, am Ende beginnt die Maske wieder von vorn. Das Inkrement ist "+1", "-2" oder positionsweise wie "0010". Funktioniert im ZPL-, EPL- und Fingerprint-Export.

var label = new Label(LabelUnit.Mm, 50, 25)
    .Add(new TextItem { X = 3, Y = 3, Text = "Charge", FontSize = 9 })
    // Maske: # / 0 Ziffer, A Buchstabe, a Kleinbuchstabe, ? alphanumerisch, sonst Literal
    .Add(new TextItem { X = 3, Y = 9, Text = "AZ-9998", Mask = "AA-0000", MaskIncrement = "+1", FontSize = 16 })
    // Inkrement pro Stelle: "0010" zählt die Zehnerstelle hoch (mit Übertrag)
    .Add(new TextItem { X = 30, Y = 3, Text = "0100", Mask = "####", MaskIncrement = "0010", FontSize = 9 });

string zpl = label.ToZpl();   // AZ-9998, AZ-9999, BA-0000 … (Übertrag in die Buchstaben)
Masken-Zähler über drei Etiketten

Etikettenbögen (LabelSheet)

LabelSheet setzt Etiketten auf A4-Bürobögen oder breite Medien: Zeilen, Spalten, Ränder, Abstände, Reihenfolge (RowWise/ColumnWise) und eine Startposition für angebrochene Bögen. Ausgabe als PNG pro Bogen, PDF oder ZPL (nebeneinander auf breiten Medien), speicherbar als JSON.

var label = new Label(LabelUnit.Mm, 45, 25)
    .Add(new RectangleItem { X = 0.5, Y = 0.5, Width = 44, Height = 24, CornerRounding = 2 })
    .Add(new TextItem { X = 3, Y = 3, DataField = "Sku", FontSize = 11 })
    .Add(new BarcodeItem { X = 3, Y = 10, DataField = "Sku", BarHeight = 10, HumanReadable = false });

var sheet = new LabelSheet
{
    PageWidth = 100, PageHeight = 85, Rows = 3, Columns = 2,       // mm, kleiner Bogen
    MarginLeft = 4, MarginTop = 4, HorizontalGap = 2, VerticalGap = 2,
    Order = LabelSheetOrder.RowWise,
    StartPosition = 2,                                            // angebrochenen Bogen weiterverwenden
};
var items = Enumerable.Range(1, 5).Select(i => new { Sku = $"ART-{i:000}" });
List<byte[]> pages = sheet.ToPng(label, items);   // ein PNG pro Bogen
byte[] pdf = sheet.ToPdf(label, items);           // Bürodrucker
string json = sheet.ToJson();
Bogen mit 2×3 Etiketten, Start bei Position 2

Mehrseitige Etiketten & Duplex (Label.Pages)

Items ist Seite 1, AddPage() fügt weitere Seiten hinzu (Seite 2 = Rückseite bei Duplexdruckern). Jede Seite wird ein eigenes Format; mit UseStoredFormat bekommt jede Seite ein eigenes ^DF-Format (R:NAME2.ZPL …).

var label = new Label(LabelUnit.Mm, 80, 50)
    .Add(new TextItem { X = 4, Y = 4, Text = "Vorderseite", FontSize = 16 })
    .Add(new BarcodeItem { X = 4, Y = 16, DataField = "Sku", BarHeight = 14 });
label.AddPage("Rückseite")                     // Seite 2 = Rückseite bei Duplexdruckern
    .Add(new TextItem { X = 4, Y = 4, Text = "Rückseite", FontSize = 16 })
    .Add(new TextItem { X = 4, Y = 16, Expression = "\"Artikel \" + [Sku]" });

int count = label.PageCount;                     // 2 – jede Seite wird ein eigenes Format
string zpl = label.ToZpl(new[] { new { Sku = "9506000134352" } });

Seite 1

Seite 1 (Vorderseite)

Seite 2

Seite 2 (Rückseite)

Duplex, Rahmen pro Seite, Tabellen-Ecken, Zähler & Eingabemasken

Duplex-Konvention: Label.Duplex gibt jede Kopie als Paar Vorderseite/Rückseite aus (^FXDUPLEX FRONT/BACK, fehlende Rückseite leer). FrameThickness setzt Rahmen pro Seite, TableItem.CornerRounding runde Ecken, CounterUseLeadingZeros steuert führende Nullen, InputMaskPromptChar den Platzhalter und ValidationErrorMessage eine eigene Meldung ({name}, {value}). Das Label-JSON hat schemaVersion 3; Version 2 wird beim Laden migriert.

var label = new Label(LabelUnit.Mm, 50, 30) { Duplex = true };   // Vorder-/Rückseite als Paare
label.Add(new TextItem { Text = "FRONT", Width = 40, Height = 8,
    FrameThickness = new FrameThickness { Top = 0.5, Bottom = 0.3 } });   // Rahmen pro Seite
label.AddPage("back").Items.Add(new TextItem { Text = "BACK" });
label.Add(new TableItem { Y = 12, ColumnWidths = [20, 20], RowHeights = [6], CornerRounding = 4, Cells = [["a", "b"]] });
label.Add(new TextItem { Y = 20, Text = "A0001", CounterStep = 1, CounterUseLeadingZeros = true });
label.Add(new TextItem
{
    Name = "Lot", Y = 25, Text = "AB-1234", InputMask = "AA-0000", InputMaskPromptChar = '_',
    ValidationErrorMessage = "{name} ist ungültig: {value}",
});
IReadOnlyList<string> errors = label.Validate();
string zpl = label.ToZpl(copies: 2);       // FRONT, BACK, FRONT, BACK

Element-Referenzen & Mehrsprachigkeit

Ausdrücke und Tabellenzellen greifen mit [Items.Name] bzw. [Items.Name.Text] auf den Text anderer Elemente zu (auch vorwärts; Zirkelbezüge ergeben ""). Mehrsprachige Vorlagen: Label.Strings (per AddString), Label.Culture und Platzhalter {t:key}; Auflösung de-CH → de → "" → Schlüssel; pro Druckauftrag mit LabelBinding.WithCulture.

var label = new Label(LabelUnit.Dots, 400, 200)
    .Add(new TextItem { Name = "Total", Expression = "\"Summe: \" + [Items.Price.Text]" })
    .Add(new TextItem { Name = "Price", Y = 50, Expression = "Format(2 * 21, \"0.00\", \"de-DE\")" })
    .Add(new TableItem { Y = 100, ColumnWidths = [200], RowHeights = [30], Cells = [["[Items.Total]"]] });
var l = new Label(LabelUnit.Dots, 400, 200) { Culture = "de-CH" }
    .AddString("", "weight", "Weight")      // Fallback
    .AddString("de", "weight", "Gewicht")   // de-CH → de → "" → Schlüssel
    .AddString("fr", "weight", "Poids");
l.Add(new TextItem { Text = "{t:weight}: 5 kg" });
string de = l.ToZpl();                                                          // Gewicht: 5 kg
string fr = l.For<Dictionary<string, object?>>().WithCulture("fr-FR").ToZpl([new()]); // Poids: 5 kg

Text-Größen, Stile, Bilder, Formen, RFID & Schema-Version

TextItem.Sizing wählt zwischen fester Größe, ShrinkToFit und FitToBox (nativ per ^FB), Stretch, Arc/OuterArc (als Grafik) und gestapelten Zeichen (^FPV). Dazu vertikale Ausrichtung, Padding, Zeichenabstand, Vorder-/Hintergrund, Rahmen, Outline, Fett/Kursiv/Unterstrichen/Durchgestrichen, Null mit Schrägstrich, RTL und Kultur. ImageItem skaliert mit Seitenverhältnis, spiegelt, dithert (Threshold, Floyd-Steinberg, Atkinson, Bayer, Otsu) und kann die Grafik einmal im Druckerspeicher ablegen. Formen haben Strichstile (Solid, Dash, Dot, DashDot), Füllung und Eckradien je Ecke; Tabellen Zeilen-/Spaltenfüllungen und einzeln abschaltbare Linien. RfidItem schreibt EPC/TID/User-Speicher mit Tag-Typ, Passwörtern und Sperren. Jedes Element kann eine eigene Einheit haben; das JSON trägt schemaVersion 3 und wird beim Laden migriert.

var label = new Label(LabelUnit.Mm, 100, 60)
    .Add(new TextItem
    {
        Name = "title", X = 5, Y = 5, Width = 90, Height = 12, Text = "Sehr langer Artikelname",
        Sizing = TextSizing.ShrinkToFit,              // auch FitToBox, Stretch, Arc, OuterArc …
        VerticalAlignment = TextVerticalAlignment.Middle, Padding = new ItemPadding(1),
        Bold = true, Underline = true, CharSpacing = 0.2,
        ForeColor = ItemColor.White, Background = ItemColor.Black, BorderThickness = 0.3,
    })
    .Add(new ImageItem
    {
        X = 70, Y = 20, Width = 25, LockAspectRatio = ImageAspectRatio.Width,
        ImageData = logoPng, DitherMethod = DitherMethod.Atkinson,   // Threshold, FloydSteinberg, Bayer, Otsu
        UsePrinterStorage = true, StorageName = "R:LOGO.GRF",          // ~DG einmal, danach ^XG
    })
    .Add(new RectangleItem { X = 5, Y = 20, Width = 60, Height = 30, StrokeStyle = StrokeStyle.Dash,
                             CornerRadius = new CornerRadius(2), Fill = ItemColor.None })
    .Add(new BarcodeItem { X = 8, Y = 24, Symbology = Symbology.Gs1DataMatrix,  // alle 144 Symbologien
                           Code = "(01)09521234543213(10)LOT42", ModuleWidth = 4 })
    .Add(new RfidItem { Data = "3034257BF7194E4000001A85", MemoryBank = RfidMemoryBank.Epc,
                        TagType = RfidTagType.EpcClass1Gen2, AccessPassword = "12345678",
                        MemoryBankLock = RfidLockValue.Lock });

string json = label.ToJson();            // "schemaVersion": 3; ältere JSONs werden beim Laden migriert
var copy = Label.FromJson(json);
var inch = label.ConvertToUnit(LabelUnit.Inch);
return label.ToZpl(dpi: 300);

Natives Fett, weiß auf Schwarz, Barcodes in einer Box & kompakte Klarschrift

TextItem.Bold druckt nativ: Das Feld wird zweimal gedruckt, das zweite Mal um Höhe / 18 (mindestens 1 Dot) in Schreibrichtung versetzt – residenter Font 0, Bitmap-Fonts und geladene ^A@-Fonts, auch umbrochen (^FB) und in 90°-Schritten gedreht. Kursiv, Unterstreichen, Durchstreichen, Kontur und Reverse bleiben eine ^GFA-Grafik. Ein schwarzer Background-Kasten (oder ForeColor = ItemColor.White über Schwarz) druckt weißen Text nativ: den Kasten als ^GB, darüber das Feld mit ^FR. Weißer Text, der den Grafikweg braucht – fett, kursiv, unterstrichen –, druckt den Kasten nativ und seine Glyphenmaske einmal mit ^FR darüber; so werden die Glyphen auch über allem, was unter dem Kasten liegt, weiß (ein zweiter ^FR-Druck für Fett würde den ersten aufheben).

BarcodeItem.Width / Height (Etiketteneinheiten) passen einen nativen Barcode in eine Box ein: die größte ganze Modulbreite, deren Balken in Width passen (ein um 90°/270° gedrehter Barcode nutzt Height), und eine Balkenhöhe, die Platz für die Klarschriftzeile lässt; QR, Data Matrix und Aztec bekommen die größte Modulgröße, die in beide Maße passt. Die Maße werden am nativen Symbol so gemessen, wie der Drucker es zeichnet – die Ausgabe bleibt ^BY + Barcode-Befehl, kein ^GFA. Bei gebundenen Daten könnte jeder Wert eine andere Modulbreite bekommen; FitSample wählt sie anhand eines vorgegebenen Werts, etwa des längsten, den das Feld bekommt. Dann teilen alle Etiketten eines Jobs dieselbe Modulbreite, und ein gespeichertes Format behält den Barcode als ^FN-Feld (ohne FitSample geht er mit jedem Datensatz vollständig mit).

HumanReadableHeight (Etiketteneinheiten) druckt eine kompakte Klarschrift: Der Barcode geht ohne die eigene Zeile des Druckers hinaus, deren Schrift mit der Modulbreite wächst und Balkenhöhe kostet, und der Text steht in Font 0 dieser Höhe unter den Balken (darüber mit HumanReadableAbove) – EAN-13, EAN-8 und UPC-A in ihren Zifferngruppen unter den Balkenhälften, andere lineare Symbologien zentriert, auch in 90°-Schritten gedreht; das Einpassen in die Box zieht diese Zeile statt der des Druckers ab. Das gilt für lineare Barcodes (nicht für 2D-, gestapelte, GS1-DataBar- und Post-Symbologien), die Ausgabe bleibt nativ.

var label = new Label(LabelUnit.Mm, 100, 38);
label.Add(new TextItem { X = 4, Y = 3, Text = "Bestellung 4711", FontSize = 16,
    Bold = true });                                                    // nativ doppelt gedruckt, kein ^GFA
label.Add(new TextItem { X = 62, Y = 2, Width = 34, Height = 10, DataField = "Service", FontSize = 14,
    Alignment = TextAlignment.Center, VerticalAlignment = TextVerticalAlignment.Middle,
    Background = ItemColor.Black });                                   // weiß auf schwarz: ^GB-Kasten + ^FR-Feld
label.Add(new BarcodeItem { X = 4, Y = 16, Symbology = Symbology.Code128, DataField = "TrackingNo",
    Width = 56, Height = 18, FitSample = "ZP0000000000" });           // größte Modulbreite, die in 56 × 18 mm passt
label.Add(new BarcodeItem { X = 66, Y = 16, Symbology = Symbology.Ean13, Code = "590123412345",
    Height = 18, HumanReadableHeight = 2.5 });                       // kompakte Klarschrift, 2,5 mm hoch
string zpl = label.For<IReadOnlyDictionary<string, object?>>().ToZpl(records);
Etikett mit nativ fettem Text, weißem EXPRESS auf schwarzem Kasten, eingepasstem Code 128 und EAN-13 mit kompakter Klarschrift
ZPL
^XA
^CI28
^PW799
^LL304
^LH0,0
^FO32,24^A0N,45,45^FDBestellung 4711^FS^FO34,24^A0N,45,45^FDBestellung 4711^FS
^FO496,16^GB272,80,80,B,0^FS^FO496,36^A0N,39,39^FB272,1,0,C,0^FR^FDEXPRESS^FS
^FO32,128^BY3,3.0,118^BCN,118,Y,N,N,A^FDZP0000004711^FS
^FO527,128^BY2,3.0,115^BEN,115,N,N^FD590123412345^FS^FO507,252^A0N,20,20^FB18,1,0,R,0^FD5^FS^FO533,252^A0N,20,20^FB84,1,0,C,0^FD901234^FS^FO627,252^A0N,20,20^FB84,1,0,C,0^FD123457^FS
^XZ

Label-JSON-Schema & Kompatibilität

LabelSchema.JsonSchema() erzeugt aus dem Modell ein JSON Schema (Draft 2020-12) des Label-JSON: Elementtypen über ihren type-Diskriminator, Enums mit Namen und den Anfangswert jeder Eigenschaft als default – eine im Label-JSON fehlende Eigenschaft hat diesen Wert, denn ToJson() schreibt nur, was von einer neuen Instanz abweicht. Das Schema der aktuellen Version liegt als label.schema.json bereit – für Editor-Vervollständigung, Validierung und generierte Typen in anderen Sprachen. Kompatibilitätsregel: Innerhalb einer Schemaversion (derzeit 3, Label.CurrentSchemaVersion) sind Änderungen nur additiv – neue optionale Eigenschaften, deren Anfangswert druckt, was Etiketten bisher gedruckt haben. Eine Eigenschaft umbenennen oder entfernen oder ihren Typ, ihre Bedeutung oder ihren Anfangswert ändern erfordert eine neue Schemaversion mit Migration; Label.FromJson migriert ältere JSONs beim Laden. Tests im Build halten das veröffentlichte Schema gleich dem Modell, das eingefrorene Schema der aktuellen Version darin enthalten und Etiketten jeder älteren Version bei ihrem eingefrorenen ZPL. Label.FromJson(json, strict: true) lehnt Eigenschaften, die das Modell nicht kennt (Tippfehler, Eigenschaften eines anderen Werkzeugs), mit einer JsonException ab; standardmäßig bleibt das Laden tolerant und überspringt sie. Strict empfiehlt sich überall, wo JSON von außen kommt – APIs, generierte Vorlagen.

var schema = LabelSchema.JsonSchema();      // JSON Schema (Draft 2020-12), aus dem Modell erzeugt
File.WriteAllText("label.schema.json", schema.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));

try
{
    var label = Label.FromJson(jsonFromApi, strict: true);   // unbekannte Eigenschaft → JsonException
}
catch (JsonException ex)
{
    Console.WriteLine(ex.Message);   // The JSON property 'fontSzie' could not be mapped to any .NET member …
}

Label-Tag, Code-128-Funktionscodes, Job-Fortschritt, PDF-Bildqualität & Überlauf-Trennung

Label.Tag speichert eigene Daten (eine ID, Notizen) im Label-JSON; es druckt nichts. Für GS1-128 die Klammerschreibweise "(01)…(10)…(17)…" verwenden oder sie mit GS1(ai1, wert1, ai2, wert2, …) bauen: die Funktion prüft jeden AI und Wert (bekannter AI, Länge, Zeichensatz, Prüfziffer) und bricht mit einer klaren Meldung ab, statt einen falschen Code zu drucken; mit Symbology.Gs1_128 werden die FNC1-Trenner nach Werten variabler Länge automatisch gesetzt und der Barcode bleibt ein natives ^BC – Validate() weist auf handgesetzte >8 hin. LabelBinding.OnProgress meldet jedes gedruckte Etikett (LabelProgress: Datensatz-Index, Kopie, Kopien, Etikett-Index, Datensatzanzahl falls bekannt) in Etikettenreihenfolge, sobald sein Format ausgegeben wird – auch bei paralleler Erzeugung, auf dem Thread, der den Job aufzählt. ZplRenderSettings.PdfImageQuality (Raster-PDF, PdfVector = false) und RenderPdf(records, dpi, imageQuality) betten die Seiten als JPEG der Qualität 1–100 statt verlustfrei (101, Standard) ein – lohnt sich für Etiketten mit Fotos oder Graustufenbildern; reine Schwarz-Weiß-Etiketten sind verlustfrei meist kleiner. TextSizing.OverflowSplit trennt an Leerzeichen, Tabs und Zeilenumbrüchen, sucht den längsten passenden Teil binär und bricht ein Wort, das länger als die Box ist, um, statt es überlaufen zu lassen; Text, der passt, druckt wie bisher.

var label = new Label(LabelUnit.Mm, 100, 50) { Tag = "erp:4711" };   // eigene Daten, ohne Druckwirkung
label.Add(new BarcodeItem { X = 5, Y = 5, Symbology = Symbology.Gs1_128,
    Expression = "GS1(\"01\", [Gtin], \"10\", [Lot], \"17\", [Expiry])" });   // geprüft, FNC1-Trenner automatisch
label.Add(new TextItem { Name = "Text1", X = 5, Y = 25, Width = 40, MaxLines = 2, DataField = "Description",
    Sizing = TextSizing.OverflowSplit, OverflowItemName = "Text2" });   // trennt an Leerzeichen, Tab, Zeilenumbruch
label.Add(new TextItem { Name = "Text2", X = 50, Y = 25, Width = 45, MaxLines = 4 });

var job = label.For<IReadOnlyDictionary<string, object?>>()
    .OnProgress(p => Console.WriteLine($"Etikett {p.LabelIndex + 1}: Datensatz {p.RecordIndex + 1}/{p.RecordCount}, Kopie {p.CopyIndex + 1}/{p.Copies}"));
string zpl = job.ToZpl(records);                          // Fortschritt in Reihenfolge, auch parallel
byte[] pdf = job.RenderPdf(records, 203, imageQuality: 80); // Raster-PDF mit JPEG (101 = verlustfrei)

HTML-Richtext, nativ gedruckt

TextItem.Markup = TextMarkup.Html liest den Text (oder den gebundenen Wert) als HTML: <b> <i> <u> <s> <mark>, <span style="font-family/font-size/color/background-color/font-weight/font-style/text-decoration">, <font>, <p>/<div> (auch text-align) und <br>. zplCore bricht die Zeilen über die gestalteten Textstücke selbst um – gemessen mit den Schriften des Renderers – und druckt je Stück ein natives ^FO/^A-Feld mit eigener Schrift und Größe auf gemeinsamer Grundlinie: fett als nativer Doppeldruck, Unter- und Durchstreichen als ^GB-Balken, Hervorheben als schwarze ^GB-Fläche mit invertiertem Stück (^FR). Nur kursive und graue Stücke werden kleine ^GFA-Grafiken; Farben drucken schwarz, nahezu Weiß druckt weiß. Ausrichtung (auch Blocksatz), Innenabstand, Box, Zeilenabstand und die Größenmodi gelten: ShrinkToFit verkleinert alle Größen mit demselben Faktor, bis der Text passt, FitToBox skaliert sie auf die Box hoch oder herunter, OverflowSplit gibt den Rest – weiter gestaltet – an das nächste Element. Text in einem Stil druckt exakt wie der ^FB-Block. Schriftfamilien sind ZPL-Schriften (0, A …) oder Schriften aus Label.Fonts nach Name. RtfConverter.ToHtml wandelt RTF (RichTextBox, WordPad) in dieses HTML: Schriften, Größen, Farben, Hervorhebung, fett/kursiv/unterstrichen/durchgestrichen, Formatvorlagen, Ausrichtung und Unicode; die Schreibrichtung rechts-nach-links wird nicht übernommen.

var label = new Label(LabelUnit.Mm, 100, 60);
label.Add(new TextItem
{
    X = 4, Y = 4, Width = 92, Height = 30, FontSize = 11, Markup = TextMarkup.Html,
    Alignment = TextAlignment.Justify,                 // Blocksatz über alle Textstücke
    Sizing = TextSizing.ShrinkToFit,              // ganzer Absatz proportional kleiner, bis er passt
    Text = "<b>Zutaten:</b> Weizenmehl, <u>Milch</u>, <i>Haselnüsse</i> (12 %), "
         + "<mark>Allergene</mark>, <span style=\"font-size:16pt\">groß</span><br>"
         + "<span style=\"color:#808080\">grauer Hinweis</span>",
});                                                    // je Stück ein ^FO/^A-Feld; nur kursiv/grau als ^GFA
label.Add(new TextItem { X = 4, Y = 40, Width = 92, Markup = TextMarkup.Html,
    Text = RtfConverter.ToHtml(rtfFromRichTextBox) }); // RTF → HTML
string zpl = label.ToZpl();
Richtext-Etikett: fett, unterstrichen, kursiv, hervorgehoben, Größen, grau und Blocksatz

Runde Etiketten, QR-Code mit Logo & Render-Cache

Label.Contour = LabelContour.Ellipse beschreibt runde oder ovale Etiketten (die Ellipse in Breite × Höhe): Vorschauen (Render, LabelBinding.Render/RenderPdf, HTML, Bögen) tönen den Bereich außerhalb und zeichnen die Kontur (auch ZplRenderSettings.LabelContour für beliebiges ZPL), Validate() meldet jedes Element, dessen Druckbild die Ellipse verlässt; das gedruckte ZPL bleibt unverändert. BarcodeItem.LogoImageData (PNG/JPEG/SVG), LogoSizePercent (5–30, Standard 20) und LogoPosition setzen ein Logo in einen QR-Code und halten ihn nativ: ^BQ mit Fehlerkorrektur H, eine weiße ^GB-Fläche aus ganzen Modulen im Modulraster (mittig oder oben/unten zwischen den Suchmustern) und das Logo als kleines ^GFA darin – ZXing liest den Code in jeder Ausrichtung. Ein gebundener QR-Code mit Logo wird bei gespeicherten Formaten mit jedem Datensatz gesendet. LabelBinding.Render/RenderPdf rastern die führenden Elemente, die auf jedem Etikett gleich sind (bis zum ersten variablen), einmal pro Job und zeichnen jedes Etikett darüber – pixelgleich; bei einem Etikett mit großer statischer Fotoebene sank das Rastern von 5,1 auf 1,3 ms pro Etikett (dazu kommt die PNG-Kodierung).

var label = new Label(LabelUnit.Mm, 50, 50) { Contour = LabelContour.Ellipse };   // rundes Etikett
label.Add(new BarcodeItem
{
    X = 13, Y = 10, Symbology = Symbology.QrCode, DataField = "Url", BarHeight = 4,
    LogoImageData = logoPng, LogoSizePercent = 22,   // ^BQ nativ (Fehlerkorrektur H) + ^GB + kleines ^GFA
});
IReadOnlyList<string> errors = label.Validate();    // meldet Elemente außerhalb der Ellipse
var pngs = label.For<IReadOnlyDictionary<string, object?>>()
    .Render(records).ToList();                     // statische Ebene einmal gerastert, Kontur in der Vorschau
Visueller Editor für dieses Modell: Der zplCore Designer bearbeitet genau dieses Label-JSON im Browser – einbettbar in Ihre Anwendung per NuGet, Docker, JavaScript, Web Component oder Blazor. Ausprobieren im Live-Editor.