zplCore.com – gerendert mit zplCore.comzplCore.com – gerendert mit zplCore.com Startseite PDF-Demo EN
.NET 10SkiaSharpHarfBuzzZPL II + EPL2

zplCore.com – High Performance .NET Labelprint Engine

ZPL rendern.
Etiketten designen.

zplCore.com ist ein virtueller Zebra-Drucker und ein Label-Designer-Modell in einer Bibliothek: ZPL II und EPL2 werden zu PNG, PDF & Co., Etiketten aus C#-Objekten werden zu ZPL – millionenfach und streamend. Dazu ein eigenständiger Barcode-Generator mit 144 Symbologien und GS1-Validierung – plus Druckaufträge mit Vor- und Nachlauf-Etikett und Transporte für TCP, seriell, USB, Windows-Spooler und CUPS.

–unterstützte ZPL-Befehle
144Barcode-Symbologien
1656grüne Unit-Tests
0,974Labelary-Score (Mittel, 122 Fälle)
Versandetikett, gerendert aus samples/zpl/five-labels.zpl
🖨️

Virtueller Drucker

Hält Grafiken, Formate und Schriften zwischen Aufrufen im Speicher – wie ein echter Zebra.

▦

Barcodes, dekodierbar

144 Symbologien, bit-genau gegen bwip-js bzw. zint geprüft und, wo möglich, mit ZXing zurückgelesen.

🔁

EPL2 & Konverter

EPL2 rendern oder nach ZPL übersetzen – inklusive Formularen, Variablen und Zählern.

✅

GS1

AI-Prüfung (534 AIs), GS1-128, SSCC-18, ITF-14, Composite CC-A/B/C.

🧩

Label-Modell

Elemente in mm/inch, Ausdrücke, Masken-Zähler, Tabellen, Repeater, Bögen, Duplex-Seiten, JSON.

⚡

Massendaten & Druckaufträge

Typisierte Bindung an LINQ, EF Core, LiteDB – streamend mit ^DF/^XF, Vor-/Nachlauf-Etikett und Schnitt nach dem letzten Etikett.

🌍

Alle Schriften

UTF-8, Arabisch/Hebräisch mit Bidi, CJK, Thai, Devanagari via HarfBuzz.

📤

Export

PNG, JPEG, WebP, BMP, PDF, GRF, TIFF (CCITT G3/G4, LZW), PCX, PCL, EPL, Fingerprint, ESC/POS, GIF, SVG, HTML, EPS – plus DPI-Umrechnung von ZPL.

01Quickstart

Projekt referenzieren, ZPL hineingeben, Bilder herausbekommen.

dotnet add reference src/ZplCore/ZplCore.csproj
dotnet build -c Release     # src/ZplCore/bin/Release/net10.0/ZplCore.dll
dotnet test
var renderer = new ZplRenderer(new ZplRenderSettings { Dpi = 203 }.WithLabelSizeMm(100, 150));

List<byte[]> pngs = renderer.ProcessCommands(
    "^XA^FO50,50^A0N,40,40^FDHallo Welt^FS^BY2^FO50,120^BCN,80^FD12345^FS^XZ");
File.WriteAllBytes("label.png", pngs[0]);

renderer.Settings.OutputFormat = ZplOutputFormat.Pdf;      // ein PDF, eine Seite pro Etikett
File.WriteAllBytes("labels.pdf", renderer.ProcessFile("labels.zpl")[0]);

02ZplRenderer

ZplRenderer verarbeitet ZPL-Strings, Byte-Arrays (Codepage 850 wie am Drucker oder UTF-8 mit BOM) und Dateien. Pro gedrucktem Etikett entsteht ein kodiertes Bild – bei PDF ein Dokument mit einer Seite pro Etikett.

Einstellungen (ZplRenderSettings)

var settings = new ZplRenderSettings
{
    Dpi = 300,                                   // 152, 203, 300 oder 600
    OutputFormat = ZplOutputFormat.Webp,         // Png, Jpeg, Webp, Bmp, Pdf, Grf
    Quality = 90,                                // JPEG/WebP
    AntiAlias = false,                           // reines 1-Bit wie am Thermodrucker
    InkColor = new SKColor(0x1d, 0x3b, 0x8a),    // Farbband
    BackgroundColor = SKColors.LightYellow,      // Etikettenpapier
    OutputRotation = ZplRotation.Rotate90,
    TraceElements = true,                        // Positionen aller Felder sammeln
    Clock = () => new DateTime(2026, 10, 18),    // deterministische ^FC-Felder
}.WithLabelSizeInches(4, 6);

string json = settings.ToJson();                 // speichern ...
var restored = ZplRenderSettings.FromJson(json); // ... und laden
EigenschaftStandardBedeutung
Dpi203Druckauflösung 152/203/300/600
LabelWidth / LabelHeight4×6 Zollin Punkten; ^PW/^LL überschreiben, außer ForceLabelWidth/Height
InkColor / BackgroundColorSchwarz / WeißFarbband- und Papierfarbe
OutputFormat / QualityPng / 95Png, Jpeg, Webp, Bmp, Pdf, Grf
OutputRotationNoneAusgabe um 90/180/270° drehen
AntiAlias / InvertColorstrue / falseGlättung bzw. Negativ
BackgroundImage / WatermarkImage + WatermarkOpacity– / 0.25Vordruck unter bzw. Wasserzeichen über dem Inhalt
RfidIndicator + RfidIndicatorColor / RfidIndicatorOpacityWatermark / #C0C0C0 / 1graues RFID-Symbol hinter dem Inhalt bei Etiketten mit ^RF/^WT/^WF/^RQ (nur Vorschauformate, siehe ^RF)
ThumbnailSize0Ausgabe auf max. N Pixel verkleinern
RenderOutputIndexesnullnur ausgewählte Etiketten kodieren
DefaultCodePage850Feldkodierung ohne ^CI
TraceElementsfalsePosition/Inhalt jedes Felds sammeln (Editoren, Tests)
ClockDateTime.NowUhr für ^FC
DrawRfidfalse^RF-Daten sichtbar einzeichnen
PdfTitle/Author/Subject/Keywords/Creator–PDF-Metadaten
PaperWidth/PaperHeight/PaperMargin/LabelGap0PDF-Bogenlayout: mehrere Etiketten pro Seite
using var watermark = SKBitmap.Decode("muster.png");
var renderer = new ZplRenderer(new ZplRenderSettings
{
    WatermarkImage = watermark,
    WatermarkOpacity = 0.35f,
    ThumbnailSize = 200,              // Ausgabe auf max. 200 px verkleinern
    RenderOutputIndexes = [0, 2],     // nur Etikett 1 und 3 kodieren
    DrawRfid = true,                  // ^RF-Daten als Kasten einzeichnen
});

// PDF: Metadaten + mehrere Etiketten pro Bogen (A4 @ 203 dpi)
var pdf = new ZplRenderer(new ZplRenderSettings
{
    OutputFormat = ZplOutputFormat.Pdf,
    PdfTitle = "Lieferung 42", PdfAuthor = "Lager", PdfSubject = "Versand", PdfKeywords = "zpl",
    PaperWidth = 1680, PaperHeight = 2376, PaperMargin = 40, LabelGap = 16,
}.WithLabelSizeMm(100, 50));
byte[] sheet = pdf.ProcessCommands(zpl)[0];

Wasserzeichen

WatermarkImage mit Opacity 0.35

RFID sichtbar (DrawRfid)

^RF-Daten mit DrawRfid = true

Ausgabeformate & Bitmaps

PNG · JPEG · WebP · BMP

Ein Bild pro Etikett. BMP als 24-Bit (eigener Encoder).

PDF

Ein Dokument, eine Seite pro Etikett in physischer Größe – oder Bogenlayout. Beispiel-PDF

GRF

Das gerenderte Etikett als einzelnes ^GFA in ^XA…^XZ – für Drucker ohne passende Schriften.

TIFF · PCX

Tiff: alle Etiketten in einem mehrseitigen 1-Bit-TIFF (TiffCompression); Pcx: 1-Bit-PCX pro Etikett.

PCL · EPL · Fingerprint

Das Rasterbild als PCL-5-Seite (Laserdrucker), EPL2-GW-Grafik oder Intermec-Fingerprint – ZPL auf Fremddruckern ausgeben.

ESC/POS

Rasterbild für Bondrucker inklusive Schnitt.

var renderer = new ZplRenderer(new ZplRenderSettings { OutputFormat = ZplOutputFormat.Tiff }.WithLabelSizeMm(100, 60));
byte[] tiff = renderer.ProcessCommands(zpl)[0];         // EIN mehrseitiges 1-Bit-TIFF

foreach (var format in new[] { ZplOutputFormat.Pcx, ZplOutputFormat.Pcl, ZplOutputFormat.Epl,
                               ZplOutputFormat.Fingerprint, ZplOutputFormat.EscPos })
{
    renderer.Settings.OutputFormat = format;            // je Etikett ein Byte-Array
    List<byte[]> outputs = renderer.ProcessCommands(zpl);
}

// Hintergrund (z. B. vorgedrucktes Formular) und Wasserzeichen aus Datei, data:-URI oder Base64;
// URLs nur asynchron (größenbegrenzt): ZplRenderSettings.LoadImageFromUrlAsync(new Uri("https://…"))
var preview = new ZplRenderSettings()
    .WithBackgroundImage("formular.png")
    .WithWatermarkImage("muster.png", opacity: 0.3f);
SKBitmap logo = ZplRenderSettings.LoadImage("data:image/png;base64,iVBORw0KGgo…");
var renderer = new ZplRenderer(new ZplRenderSettings { TraceElements = true });
foreach (ZplRenderResult result in renderer.Render(zpl))
{
    using SKBitmap bmp = result.Bitmap;              // Aufrufer besitzt die Bitmaps
    foreach (var e in result.Elements)
        Console.WriteLine($"{e.Command} @ {e.X},{e.Y} {e.Width}x{e.Height}: {e.Content}");
    File.WriteAllText("label.grf.zpl", ZplExport.ToGrfZpl(bmp));  // als ^GFA-Grafik
}

Hintergrund & Wasserzeichen aus Datei, URL oder Base64

ZplRenderSettings.LoadImage(source) lädt Bilder aus Dateipfad, http(s)-URL, data:-URI oder reinem Base64. WithBackgroundImage legt ein Bild (z. B. ein eingescanntes Vordruck-Formular) unter das Etikett, WithWatermarkImage eines darüber – siehe Code oben und das Wasserzeichen-Beispiel.

Ereignisse, Statusabfragen & ~WC

Befehle ohne Bildwirkung (Medien, Schwärzung, Kalibrierung, ~HS …) lösen PrinterControl aus; Antworten auf Host-Abfragen landen in Responses. LabelRendered feuert nach jedem kodierten Etikett. ~WC druckt ein Konfigurationsetikett.

var renderer = new ZplRenderer();
renderer.PrinterControl += (_, e) =>
{
    Console.WriteLine($"Steuerbefehl {e.Command.Prefix}{e.Command.Name} {e.Command.Parameters}");
    // e.Response ist für ~HS vorbelegt und kann überschrieben werden
};
renderer.LabelRendered += (_, e) =>
    File.WriteAllBytes($"label{e.Index + 1}.png", e.Data);

renderer.ProcessCommands("~SD25^XA^MD10^FO50,50^GB100,100,4^FS^XZ~HS");
var status = ZplPrinterStatus.Parse(renderer.Responses[0]);
Console.WriteLine(status.IsReady ? "bereit" : "Fehler");

~WC Konfigurationsetikett

~WC Konfigurationsetikett – gerendert mit zplCore.com

Gespeicherte Formate

Formate (^DF/^XF), Grafiken (~DG, ~DY, ^IS) und Schriften liegen in renderer.Store (ZplResourceStore), der auch zwischen mehreren Renderern geteilt werden kann (new ZplRenderer { Store = shared }).

Druckerspeicher: ^DF / ^XF, ^IS / ^IL

var renderer = new ZplRenderer();

// ^DF speichert ein Format im Druckerspeicher, ^XF ruft es auf, ^FN füllt die Felder
renderer.ProcessCommands("^XA^DFR:ADR.ZPL^FS^FO50,50^A0N,40,40^FN1^FS^FO50,110^A0N,30,30^FN2^FS^XZ");
var labels = renderer.ProcessCommands(
    "^XA^XFR:ADR.ZPL^FN1^FDMax Mustermann^FS^FN2^FDGraz^FS^XZ" +
    "^XA^XFR:ADR.ZPL^FN1^FDErika Muster^FS^FN2^FDWien^FS^XZ");

// Speicher bleibt zwischen Aufrufen erhalten – wie bei einem echten Drucker
Console.WriteLine(string.Join(", ", renderer.Store.Formats));
renderer.Store.AddGraphic("R:LOGO.GRF", SKBitmap.Decode("logo.png"), dither: true);
renderer.Reset();                      // Power-on-Reset (~JR)
^XA^FO10,10^GB300,80,3^FS^FO30,30^A0N,40,40^FDFormular^FS
^ISR:FORM.GRF^XZ                 ← Etikett als Bild speichern (und drucken)
^XA^ILR:FORM.GRF                 ← Bild als Hintergrund laden
^FO30,120^A0N,30,30^FDVariable Daten^FS^XZ

Seriennummern, Uhr, ^MC, ^RF

^MC Map Clear

^MCN lässt das Bild für das nächste Etikett stehen (Overlay), ^MCY löscht wieder.

^RF RFID-Wasserzeichen

RfidIndicator = Watermark (Standard)

Etiketten mit RFID-Operation (^RF, ^WT, ^WF, ^RQ) zeigen in der Vorschau ein graues RFID-Symbol: zentriert, ca. 0,86" groß, hinter dem Inhalt, mit ^PO I gedreht; in SVG/Vektor-PDF als Pfad. Nur PNG/JPEG/WebP/BMP/GIF/PDF/SVG/HTML/EPS – nie in GRF, EPL, PCL, Fingerprint, ESC/POS oder 1-Bit-TIFF/PCX. RfidIndicator = ZplRfidIndicator.None schaltet es ab.

^SO / ^ST

Zweit- und Drittuhr mit Versatz (z. B. Mindesthaltbarkeit +2 Monate) bzw. Uhr stellen. ^CD/^CC/^CT ändern die Steuerzeichen, ^TO kopiert gespeicherte Objekte, ^JM halbiert die Punktdichte.

03Unterstützte ZPL-Befehle

Durchsuchbar nach Befehl oder Beschreibung. Nicht aufgeführte Steuer-, Medien- und Statusbefehle werden akzeptiert und über PrinterControl gemeldet.

BefehlBeschreibungKategorie

04Barcode-Galerie

Alle Barcode-Befehle aus ZPL – jedes Bild wurde aus echtem ZPL gerendert. Klick auf „ZPL“ zeigt den Quelltext, Klick auf das Bild vergrößert. ^B7 (PDF417), ^BX (Data Matrix, pixelgleich zu Zebra) und ^BQ (QR, BWIPP-Engine mit Zebras EC-Anhebung) nutzen eigene Encoder; neu sind ^BB Codablock F, ^B4 Code 49, ^BF MicroPDF417 und ^BZ Typ 3 (Intelligent Mail) sowie ^FM für Macro PDF417 (ein Feld, mehrere Symbole an eigenen Positionen).

Label-Modell: Symbologien ohne ZPL-Befehl

Labels.BarcodeItem kennt zusätzlich Micro QR, rMQR, GS1 QR, Swiss QR, EPC QR, GS1 Data Matrix, Aztec Rune, Han Xin, DotCode, Ultracode, Code 16K, Telepen, Postcodes u. v. m. Hat ZPL keinen Befehl dafür, rendert der Barcode-Generator das Symbol und es wird automatisch als ^GFA-Grafik ausgegeben – auch mit UseStoredFormat() korrekt. Codablock F, Code 49, MicroPDF417 und Intelligent Mail bleiben native Befehle.

// Im Label-Modell: Symbologien ohne ZPL-Befehl werden automatisch als ^GFA ausgegeben
var label = new Label(LabelUnit.Mm, 60, 30)
    .Add(new BarcodeItem { X = 3, Y = 3, Code = "DotCode 42", Symbology = ZplCore.Labels.Symbology.DotCode, ModuleWidth = 5 })
    .Add(new BarcodeItem { X = 30, Y = 3, Code = "(01)09521234543213", Symbology = ZplCore.Labels.Symbology.Gs1QrCode, ModuleWidth = 4 })
    .Add(new BarcodeItem { X = 3, Y = 18, Code = "CODABLOCK", Symbology = ZplCore.Labels.Symbology.CodablockF, BarHeight = 2 }); // nativ: ^BB
string zpl = label.ToZpl();   // gilt auch mit UseStoredFormat()
Label mit DotCode, GS1 QR und Codablock F

05EPL2

EplRenderer ist ein virtueller EPL2-Drucker: EPL wird nach ZPL übersetzt und durch denselben ZplRenderer gerendert – gleiche Schriften, Barcodes, Ausgabeformate und Einstellungen. ToZpl() liefert die Übersetzung direkt, also einen EPL→ZPL-Konverter für die Migration alter Formulare.

var epl = new EplRenderer(new ZplRenderSettings { Dpi = 203 }.WithLabelSizeInches(4, 3));
epl.Clock = () => new DateTime(2026, 10, 18, 14, 5, 9);      // für TT/TD-Felder
epl.PrinterControl += (_, e) => Console.WriteLine($"EPL-Steuerbefehl: {e.Command.Name}");

List<byte[]> pngs = epl.ProcessFile("samples/zpl/sample.epl");   // ein PNG pro gedrucktem Etikett
File.WriteAllBytes("sample.png", pngs[0]);

// EPL → ZPL: dieselbe Übersetzung, die der Renderer intern nutzt
string zpl = epl.ToZpl(File.ReadAllText("samples/zpl/form.epl"));
File.WriteAllText("form.zpl", zpl);                              // drei ^XA…^XZ-Blöcke (P3)

epl.AddGraphic("LOGO", File.ReadAllBytes("logo.pcx"));          // für GG x,y,"LOGO"
epl.Settings.OutputFormat = ZplOutputFormat.Pdf;
File.WriteAllBytes("form.pdf", epl.ProcessFile("samples/zpl/form.epl")[0]);
samples/zpl/sample.epl EPL → Bild

samples/zpl/sample.epl, gerendert mit EplRenderer
Übersetztes ZPL anzeigen (ToZpl)

Formulare, Variablen & Zähler

FS"name" … FE zeichnet ein Formular auf, FR"name" ruft es ab. V00–V99 sind Variablen, C0–C9 Zähler (mit Schrittweite und fester Länge); die Daten folgen nach ?, je eine Zeile. P3 druckt drei Etiketten und zählt C0 jeweils weiter. Das Formular bleibt wie am Drucker im Speicher.


Übersetztes ZPL
Formular, Etikett 1 Formular, Etikett 2 Formular, Etikett 3

Unterstützte EPL2-Befehle

BefehlBedeutung
NBildpuffer löschen
q / QEtikettenbreite / -höhe und Lücke
RReferenzpunkt (Ursprung verschieben)
ZT / ZBDruckrichtung oben / unten (ZB = 180°)
AText: residente Schriften 1–5 (203/300 dpi), Rotation, Vergrößerung, N/R (invertiert)
B1D-Barcodes: Code 128 (A/B/C/Auto), Code 39, Code 93, EAN/UPC, I2of5, Codabar, MSI, Plessey, POSTNET …
b2D-Barcodes: QR (Q), Data Matrix (D), PDF417 (P), MaxiCode (M), Aztec (A)
XRechteck (Kasten)
LO / LW / LELinie schwarz / weiß / XOR
LSDiagonale
GWGrafik direkt (binär)
GM / GG / GKPCX-Grafik speichern / drucken / löschen
FS … FEFormular aufzeichnen
FR / FKFormular abrufen / löschen
VFormularvariable (Länge, Ausrichtung, Prompt)
CZähler (Länge, Ausrichtung, Schrittweite ±)
?Variablendaten für V und C
PDrucken (Anzahl Sätze), erhöht Zähler
TS / TT / TDUhr stellen, Zeit- und Datumsfelder
S D I O U JF JB W …Geschwindigkeit, Dichte, Zeichensatz, Optionen: über PrinterControl gemeldet
Steuerbefehle ohne Einfluss auf das Bild (S, D, I, O, U, JF …) löst EplRenderer.PrinterControl aus. TT/TD nutzen Clock, TS setzt einen Versatz. PCX-Grafiken für GG kommen per GM oder AddGraphic(name, pcx).

06Barcode-Generator

ZplCore.Barcodes.Barcode erzeugt Barcodes ganz ohne ZPL: 144 Symbologien (enum Symbology), Ausgabe als Raster, Vektor oder ZPL-Grafik.

var bc = new Barcode
{
    Symbology = Symbology.Ean13,
    Code = "400638133393",                 // Prüfziffer wird ergänzt
    Unit = BarcodeUnit.Millimeter,
    Dpi = 300,
    BarWidth = 0.33,                       // X-Dimension (100 %)
    BarHeight = 22,
};
File.WriteAllBytes("ean13.png", bc.ToPng());
File.WriteAllBytes("ean13.webp", bc.ToWebp(90));
File.WriteAllText("ean13.svg", bc.ToSvg());    // Vektor
File.WriteAllBytes("ean13.pdf", bc.ToPdf());   // Vektor, physische Größe
File.WriteAllText("ean13.eps", bc.ToEps());    // PostScript
string gfa = bc.ToZplGraphic(50, 100);         // ^FO50,100^GFA,…^FS für beliebige ZPL-Etiketten

using var bmp = new SKBitmap(1200, 600);       // oder direkt auf eine eigene Leinwand
using var canvas = new SKCanvas(bmp);
canvas.Clear(SKColors.White);
bc.Draw(canvas, 40, 40);
SKSize size = bc.Size;                         // Pixel bei Dpi, inkl. Ruhezone und Text

Raster

ToBitmap(), ToPng(), ToJpeg(q), ToWebp(q)

Vektor

ToSvg(), ToPdf() (physische Größe), ToEps(), Draw(SKCanvas, x, y)

ZPL

ToZplGraphic(x, y) – ^FO…^GFA…^FS für jedes Zebra-Etikett

Beispieldateien: ean13.svgean13.pdfean13.epsswissqr.svgswissqr.pdfgs1-datamatrix.epsean13-gfa.zpl

Alle Optionen

var bc = new Barcode
{
    Symbology = Symbology.Code39,
    Code = "ZPLCORE",
    Unit = BarcodeUnit.Pixel,                      // Pixel, Millimeter, Inch, Mil
    Dpi = 203,
    BarWidth = 2,                                  // schmaler Strich / Modul
    BarHeight = 70,                                // nur 1D; 2D-Module sind quadratisch
    WideRatio = 2.5,                               // breit:schmal für Code 39, 2 of 5, Codabar …
    QuietZone = 6,                                 // Module (null: 10 bei 1D, 4 bei 2D)
    AddChecksum = true,                            // optionale Prüfziffer
    ForeColor = new SKColor(0x1d, 0x3b, 0x8a),
    BackColor = new SKColor(0xff, 0xf7, 0xd6),
    TextColor = new SKColor(0xc8, 0x10, 0x2e),
    TextPosition = BarcodeTextPosition.Above,      // None, Below, Above
    HumanReadableText = "*ZPLCORE*",            // statt der kodierten Daten
    FontSize = 9,                                  // Punkt; 0 = automatisch
    Rotation = 90,                                 // 0, 90, 180, 270
};
// weitere: Typeface, GuardBars, BearerBars/BearerBarWidth, Supplement,
// QrErrorCorrection, Pdf417Columns, Pdf417SecurityLevel, MaxiCodeMode, DataBarSegmentsPerRow
Code 39 mit Farben, Text oben, 90° gedreht
EigenschaftStandardBedeutung
Symbology, CodeCode128, ""Symbologie und Daten
UnitPixelEinheit für BarWidth/BarHeight/BearerBarWidth: Pixel, Millimeter, Inch, Mil
Dpi300Auflösung für Raster und Einheitenumrechnung
BarWidth2Schmaler Strich bzw. Modulgröße
BarHeight80Höhe linearer Codes (2D-Module sind quadratisch)
WideRatio3.0Breit-zu-schmal (2.0–3.0) für Zwei-Breiten-Codes
QuietZonenullRuhezone in Modulen (null: 10 bei 1D, 4 bei 2D)
ForeColor, BackColor, TextColorSchwarz/Weiß/SchwarzFarben (auch in SVG/PDF/EPS)
TextPositionBelowKlarschrift: None, Below, Above
HumanReadableTextnulleigener Klartext statt der Daten (null = automatisch inkl. Prüfziffer)
FontSize, Typeface0, nullSchriftgröße in Punkt (0 = proportional zum Modul), Schrift
GuardBarstrueEAN/UPC-Randzeichen in die Klarschrift verlängern
BearerBars, BearerBarWidthNone (ITF-14: Frame), 4Schutzbalken: Frame oder HorizontalRules
Supplementnull2- oder 5-stelliges EAN/UPC-Add-on
AddChecksumfalseoptionale Prüfziffer (Code 39, 2 of 5, Code 11 …)
Rotation00, 90, 180, 270 Grad
QrErrorCorrection'M'L, M, Q, H (QR, Micro QR, rMQR, GS1 QR)
Pdf417Columns, Pdf417SecurityLevel0, 2PDF417-Spalten (0 = automatisch), Sicherheitsstufe
MaxiCodeMode4MaxiCode-Modus 2–6
DataBarSegmentsPerRow4Segmente pro Zeile bei DataBar Expanded Stacked
ModuleShape, FinderShapeSquare, SquareModulform 2D (Square, Circle, RoundedSquare, Diamond); QR-Finder (Square, Rounded, Circle)
LogoImage, LogoSizePercent, LogoOpacity, LogoBackgroundnull, 20, 1, trueLogo in der Mitte von 2D-Codes (QR: Fehlerkorrektur H)
LightMarginIndicatorfalseEAN/UPC: </> in den Ruhezonen
TextFormatPattern, HumanReadableAlignmentnull, CenterKlarschrift formatieren (Maske oder {0:…}) und ausrichten (Center, Left, Right, Justify)
CodeEncodingTextDaten als Text, Base64 oder Hex
ProcessTildefalse~~, ~dNNN, ~1 (FNC1), ~2IICC (Structured Append), ~eNNNNNN (ECI)
QrVersion, QrMask0, -1QR-Version 1–40 und Maske 0–7 erzwingen
StructuredAppendInfo / StructuredAppend(i, n)nullDaten auf mehrere Symbole verteilen (QR, Data Matrix, Aztec, MaxiCode, PDF417)

Besondere Symbole

EAN-13 mit 5-stelligem Add-on

var bc = new Barcode
{
    Symbology = Symbology.Ean13,
    Code = "978020137962",
    Supplement = "51995",          // 2- oder 5-stelliges Add-on rechts daneben
    GuardBars = true,              // Randzeichen ragen in die Klarschrift
    Dpi = 203, BarWidth = 2, BarHeight = 70,
};
EAN-13 mit Add-on

ITF-14 mit Schutzrahmen

var bc = new Barcode
{
    Symbology = Symbology.Itf14,
    Code = "0460123456789",        // 13 Stellen → Prüfziffer 3 wird ergänzt
    Dpi = 203, BarWidth = 2, BarHeight = 70,
    BearerBars = BearerBarStyle.Frame,   // Standard bei ITF-14; HorizontalRules = nur oben/unten
    BearerBarWidth = 4,
};
ITF-14 mit Bearer-Rahmen

Swiss QR (QR-Rechnung)

string payload = string.Join("\n",
    "SPC", "0200", "1", "CH4431999123000889012",
    "S", "Muster AG", "Bahnhofstrasse", "1", "8000", "Zürich", "CH",
    "", "", "", "", "", "", "",
    "1949.75", "CHF",
    "S", "Pia Rutschmann", "Marktgasse", "28", "9400", "Rorschach", "CH",
    "QRR", "210000000003139471430009017", "Rechnung 4711", "EPD");
var bc = new Barcode
{
    Symbology = Symbology.SwissQrCode,   // UTF-8, EC-Level M, Schweizerkreuz in der Mitte
    Code = payload,
    Unit = BarcodeUnit.Millimeter, Dpi = 203,
    BarWidth = 0.5,
};
Swiss QR Code mit Schweizerkreuz

EPC-QR (GiroCode)

// EPC069-12 (GiroCode / SEPA-Überweisung): Felder werden geprüft (Länge, BIC, Betrag, IBAN ohne Leerzeichen)
string payload = QrExtras.EpcPayload(
    name: "Max Mustermann",
    iban: "DE89 3704 0044 0532 0130 00",
    amount: 12.50m,
    bic: "COBADEFFXXX",
    remittance: "Rechnung 4711");
var bc = new Barcode { Symbology = Symbology.EpcQrCode, Code = payload, Dpi = 203, BarWidth = 4 };
EPC-QR / GiroCode

QrExtras.EpcPayload baut den Payload (Version 002, UTF-8) und prüft Längen, BIC, Betrag und Referenz/Verwendungszweck.

Ultracode (Farbe) & DotCode (Punkte)

var ultra = new Barcode { Symbology = Symbology.Ultracode, Code = "Ultracode 2026", Dpi = 203, BarWidth = 6 };
var dot = new Barcode { Symbology = Symbology.DotCode, Code = "DotCode 2026", Dpi = 203, BarWidth = 6 };
File.WriteAllBytes("ultracode.png", ultra.ToPng());   // farbige Module (Cyan, Magenta, Gelb, Grün …)
File.WriteAllText("dotcode.svg", dot.ToSvg());        // DotCode als runde Punkte
Ultracode – farbige ModuleDotCode – runde Punkte

Gedreht & eingefärbt

var bc = new Barcode
{
    Symbology = Symbology.Code128,
    Code = "ROT-270",
    Dpi = 203, BarWidth = 2, BarHeight = 60,
    Rotation = 270,
    ForeColor = new SKColor(0x0b, 0x6e, 0x4f),
    BackColor = new SKColor(0xe8, 0xf7, 0xf0),
    TextColor = new SKColor(0x0b, 0x6e, 0x4f),
};
Code 128 um 270° gedreht, grün

QR mit Logo

// logo: z. B. SKBitmap.Decode("logo.png")
var bc = new Barcode
{
    Symbology = Symbology.QrCode,
    Code = "https://example.com/zplcore",
    Dpi = 203, BarWidth = 6,
    LogoImage = logo,                   // QR: Fehlerkorrektur wird auf H angehoben
    LogoSizePercent = 22,               // Breite in % des Symbols
    LogoBackground = true,              // Fläche in BackColor hinter dem Logo
    FinderShape = FinderShape.Rounded,  // Square, Rounded, Circle
    ForeColor = new SKColor(0x1d, 0x3b, 0x8a),
};
QR Code mit Logo und runden Findern

Runde Module, Punkte, Rauten

var round = new Barcode
{
    Symbology = Symbology.QrCode, Code = "Runde Module", Dpi = 203, BarWidth = 6,
    ModuleShape = ModuleShape.RoundedSquare, FinderShape = FinderShape.Circle,
};
var dots = new Barcode
{
    Symbology = Symbology.DataMatrix, Code = "Punkte 2026", Dpi = 203, BarWidth = 8,
    ModuleShape = ModuleShape.Circle,    // Square, Circle, RoundedSquare, Diamond
};
var diamond = new Barcode { Symbology = Symbology.Aztec, Code = "Diamond", Dpi = 203, BarWidth = 7, ModuleShape = ModuleShape.Diamond };
QR mit abgerundeten ModulenData Matrix als PunkteAztec mit Rauten

Klarschrift, Kodierung & Structured Append

Light Margin Indicator, Textmaske, Ausrichtung

var ean = new Barcode
{
    Symbology = Symbology.Ean13, Code = "400638133393", Dpi = 203, BarWidth = 2, BarHeight = 60,
    LightMarginIndicator = true,                   // ‹ › in den Ruhezonen (GS1)
};
var pattern = new Barcode
{
    Symbology = Symbology.Code128, Code = "AB1234567", Dpi = 203, BarWidth = 2, BarHeight = 50,
    TextFormatPattern = "AA-999 99 99",            // Maske: A a 9 # X x * ? übernehmen ein Zeichen
    HumanReadableAlignment = HumanReadableAlignment.Justify,   // Center, Left, Right, Justify
};
// numerisch auch als Composite-Format: TextFormatPattern = "{0:### ### ###}"
EAN-13 mit Light Margin IndicatorCode 128 mit Textmaske

Base64/Hex, Tilde & ECI, QR-Version/-Maske, Structured Append

// Daten als Base64 oder Hex übergeben (Binärdaten)
var b64 = new Barcode { Symbology = Symbology.QrCode, Code = "SGVsbG8gV29ybGQ=", CodeEncoding = CodeEncoding.Base64 };
var hex = new Barcode { Symbology = Symbology.DataMatrix, Code = "48 65 6C 6C 6F", CodeEncoding = CodeEncoding.Hex };

// Tilde-Escapes: ~~ ~dNNN (Byte) ~1 (FNC1) ~2IICC (Structured Append) ~eNNNNNN (ECI)
var eci = new Barcode { Symbology = Symbology.QrCode, Code = "~e000026Gr~d195~d188~d195~d159e", ProcessTilde = true };

// QR fest einstellen: Version 1–40 (0 = automatisch), Maske 0–7 (-1 = automatisch)
var fixedQr = new Barcode { Symbology = Symbology.QrCode, Code = "VERSION 5", QrVersion = 5, QrMask = 3, QrErrorCorrection = 'Q' };

// Structured Append: Daten auf mehrere Symbole verteilen (QR, Data Matrix, Aztec, MaxiCode, PDF417)
var part1 = new Barcode { Symbology = Symbology.QrCode, Code = "Teil eins / ", Dpi = 203, BarWidth = 4 }.StructuredAppend(1, 2);
var part2 = new Barcode { Symbology = Symbology.QrCode, Code = "Teil zwei", Dpi = 203, BarWidth = 4 }.StructuredAppend(2, 2);
QR mit ECI 26QR Version 5 Maske 3Structured Append 1/2Structured Append 2/2

Weitere Ausgabeformate

Neben PNG/JPEG/WebP, SVG, PDF, EPS und ^GFA: TIFF, PCX, PCL, EPS in CMYK oder Graustufen und ein fertiges HTML-<img> mit Data-URI.

File.WriteAllBytes("code.tif", bc.ToTiff());                       // 1-Bit TIFF (PackBits)
File.WriteAllBytes("code.pcx", bc.ToPcx());
File.WriteAllBytes("code.pcl", bc.ToPcl());                        // PCL5-Rasterseite
File.WriteAllText("code-cmyk.eps", bc.ToEps(EpsColorMode.Cmyk));   // Rgb, Cmyk, Gray
string img = bc.ToHtmlImgTag(HtmlImageFormat.Svg, alt: "Barcode"); // <img src="data:image/svg+xml;base64,…">

Größe, Rahmen, Fehlerverhalten & Symbologie-Optionen

Feste Data-Matrix-Größen (inkl. DMRE) und Kodierung, PDF417 Zeilen/Seitenverhältnis/Kompaktierung, Aztec-Format/Lagen/Fehlerkorrektur, QR-Modus (inkl. Kanji), Code-128-Zeichensatz, Han Xin/DotCode/Ultracode-Parameter und die Composite-Wahl CC-A/B/C sind Eigenschaften von Barcode. Dazu Größenmodi (None, Fill, FitProportional, AutoSize), Padding, Rahmen, ErrorBehavior (Ausnahme, leeres Bild oder Bild mit Fehlertext), IsCodeValid und die öffentliche Klasse Checksums.

var bc = new BC.Barcode
{
    Symbology = BC.Symbology.DataMatrix, Code = "ZPLCORE-0001",
    DataMatrixSize = "16x48", DataMatrixDmre = true,
    Sizing = BC.BarcodeSizing.FitProportional, TargetWidth = 300, TargetHeight = 120,
    Padding = new BC.BarcodePadding(8), BorderWidth = 2,
    ErrorBehavior = BC.BarcodeErrorBehavior.ErrorDescriptionImage,
};
if (!bc.IsCodeValid(out string? error)) Console.WriteLine(error);
int check = BC.Checksums.Mod10("400638133393");
return bc;

TLC39, Deutsche Post BZL, GS1-Maße, ECI & weitere Stellschrauben

Symbology.Tlc39 erzeugt den Code 39 mit 6-stelliger ECI und verknüpftem MicroPDF417 (Teil nach dem Komma, Abstand Tlc39Gap). Symbology.DeutschePostBzl ist experimentell ([Experimental("ZPLBZL001")]): Code 128 der S10-Sendungsnummer mit Prüfziffer. Dazu UseQuietZoneForText (Klarschrift in der Ruhezone), SupplementTopMargin (Add-on-Versatz), MagnificationFactor (GS1-Nennmaß 0,80–2,00 bei EAN/UPC), HanXinMode (erzwungener Modus), MaxiCodePixelAligned (ohne Graustufen), ArtBarImagePattern (Bild als Modulfüllung), ByteEncodingName mit automatischer ECI (Eci.FromEncodingName) und Gs1Builder (GTIN, SSCC, Datum, Maße, Prüfziffer).

            // TLC39: Code 39 (6-stellige ECI) + verknüpfter MicroPDF417 nach dem Komma
            var tlc = new BC.Barcode { Symbology = BC.Symbology.Tlc39, Code = "123456,ABCD12345678901234", BarWidth = 3, BarHeight = 90, Tlc39Gap = 6 };

#pragma warning disable ZPLBZL001 // experimentell
            var bzl = new BC.Barcode { Symbology = BC.Symbology.DeutschePostBzl, Code = "RR34567890DE", BarWidth = 3 }; // Prüfziffer ergänzt
#pragma warning restore ZPLBZL001

            var ean = new BC.Barcode
            {
                Symbology = BC.Symbology.Ean13, Code = "400638133393", Dpi = 600,
                MagnificationFactor = 1.0,           // GS1-Nennmaß (0,80–2,00)
                Supplement = "12", SupplementTopMargin = 40,
            };
            var compact = new BC.Barcode { Symbology = BC.Symbology.Code128, Code = "QZ", UseQuietZoneForText = true };
            var hx = new BC.Barcode { Symbology = BC.Symbology.HanXin, Code = "1234567890", HanXinMode = BC.HanXinMode.Numeric };
            var maxi = new BC.Barcode { Symbology = BC.Symbology.MaxiCode, Code = "PIXEL", MaxiCodePixelAligned = true };
            var art = new BC.Barcode { Symbology = BC.Symbology.QrCode, Code = "ART", ArtBarImagePattern = pattern };
            var cyr = new BC.Barcode { Symbology = BC.Symbology.QrCode, Code = "Привет", ByteEncodingName = "ISO-8859-5" }; // ECI 7
            int eci = BC.Eci.FromEncodingName("utf-8");                                                               // 26

            string gs1 = BC.Gs1Builder.Build(("01", BC.Gs1Builder.Gtin("950110153000")[4..]), ("10", "LOT"));
            string date = BC.Gs1Builder.Date("17", new DateOnly(2026, 12, 31));   // (17)261231
            string kg = BC.Gs1Builder.Measure("310", 1.5m, 3);                   // (3103)001500
Symbology.Tlc39 – gerendert mit zplCore.comDeutsche Post BZL

Galerie: alle Symbologien

Alle 144 Werte der Enum ZplCore.Barcodes.Symbology, darunter neu Korea Post, Royal Mail Mailmark (4-State), DPD, UPU S10, Flattermarken, BC412, UPNQR, Grid Matrix und Code One sowie die Anwendungsformate (ISBT 128, HIBC, VICS, USPS IMpb …). Jedes Bild ist mit new Barcode { Symbology = …, Code = …, Dpi = 203, BarWidth = 2 (2D: 3), BarHeight = 50 }.ToPng() erzeugt.

07GS1

Alle GS1-Symbologien laufen durch einen gemeinsamen Parser und Validator (portiert aus BWIPPs gs1process, Tabelle mit 534 Application Identifiern): Länge, Zeichensatz, Prüfziffern, Datumsangaben und Pflicht-/Ausschluss-Kombinationen. Eingabe in Klammerform (01)…(10)… oder roh mit FNC1/GS.

// GS1-128: AIs in Klammern; FNC1 und Trennzeichen setzt der Encoder, die AIs werden geprüft
var gs1 = new Barcode { Symbology = Symbology.Gs1_128, Code = "(01)09521234543213(17)261231(10)LOT42", Dpi = 203, BarWidth = 2, BarHeight = 70 };
var sscc = new Barcode { Symbology = Symbology.Sscc18, Code = "09501234567890123", Dpi = 203, BarWidth = 2, BarHeight = 70 }; // Prüfziffer ergänzt
var ean14 = new Barcode { Symbology = Symbology.Ean14, Code = "(01)04601234567893", Dpi = 203, BarWidth = 2, BarHeight = 70 };
var dm = new Barcode { Symbology = Symbology.Gs1DataMatrix, Code = "(01)09521234543213(21)SN12345", Dpi = 203, BarWidth = 5 };

// Composite: "linearer Teil|(AI)2D-Teil" – CC-A/B/C wird automatisch gewählt
var cc = new Barcode { Symbology = Symbology.Ean13Composite, Code = "2112345678900|(99)1234-abcd", Dpi = 203, BarWidth = 2, BarHeight = 60 };

try
{
    new Barcode { Symbology = Symbology.Gs1_128, Code = "(01)09521234543213(17)261301" }.ToPng();
}
catch (FormatException ex)
{
    Console.WriteLine(ex.Message);   // ungültiges Datum in AI 17 (Monat 13)
}

GS1-128

GS1-128 (01)(17)(10) – gerendert mit zplCore.com

SSCC-18

SSCC-18 (AI 00)

EAN-14

EAN-14 (AI 01)

GS1 Data Matrix

GS1 Data Matrix

EAN-13 Composite (CC-A)

EAN-13 Composite – gerendert mit zplCore.com

DataBar Expanded Composite

DataBar Expanded Composite

GS1-128 Composite (CC-C)

GS1-128 Composite – gerendert mit zplCore.com
Composite-Daten: "linearer Teil|(AI)…". Unterstützt für EAN-13/8, UPC-A/E, GS1-128 und alle DataBar-Varianten; CC-A, CC-B bzw. CC-C (nur GS1-128) wird nach Datenmenge gewählt. Beim Kodieren führen ungültige Daten zu einer FormatException mit AI und Grund.

Parser & Validator direkt nutzen

Gs1.Parse, ParseBracketed, ParseRaw, Validate, ToElementString, ToRaw und NeedsSeparator sind öffentlich; das Ergebnis ist ein Gs1Result mit Elements und Errors. Für QR gibt es außerdem QrExtras.Gs1, SwissQr (Matrix plus Kreuzgeometrie) und Epc.

Gs1Result r = Gs1.Parse("(01)09521234543213(17)261301(10)LOT42");   // Klammerform oder roh mit GS
foreach (Gs1Element e in r.Elements)
    Console.WriteLine($"AI {e.Ai} = {e.Value}");
if (!r.IsValid)
    foreach (Gs1Error err in r.Errors)
        Console.WriteLine(err);              // "AI 17: Invalid month" (err.Ai, err.Code, err.Message)

var ok = Gs1.Parse("(01)09521234543213(10)LOT42(17)261231");
string raw = Gs1.ToRaw(ok.Elements);         // "0109521234543213" + "10LOT42" + GS + "17261231"
string text = Gs1.ToElementString(ok.Elements);
List<Gs1Error> errors = Gs1.Validate(ok.Elements, lintReqs: true);   // inkl. Pflicht-Kombinationen
bool gs = Gs1.NeedsSeparator("10");          // true: variable Länge → FNC1/GS danach

08Text & Formen

^GF versteht ASCII-Hex mit ZPL-Kompression (G–z, ,, !, :), :Z64:, :B64: und Binärdaten; dazu ~DG/^XG, ^IM, ~DY (PNG/GRF), ^ID, ~EG.

09Schriften

Schrift 0 ≈ Swiss 721 Bold Condensed über das eingebettete Roboto Condensed Bold (OFL), kalibriert auf Zebra-Metriken (Versalhöhe 0,735·h, Versaloberkante an ^FO); gemessen innerhalb von 1–3 Punkten der Labelary-Ausgabe. Die Bitmap-Schriften A–H nutzen DejaVu Sans Mono mit Zebra-Vorschub (Zellbreite + Abstand) × Vergrößerung. Eigene TTFs ersetzen sie oder kommen per ^A@ dazu.

var renderer = new ZplRenderer();
FontManager fonts = renderer.Store.Fonts;

fonts.RegisterFont("0", File.ReadAllBytes("MyZebraLike.ttf"));   // Schrift 0 ersetzen
fonts.RegisterFont("E:ARIAL.TTF", File.ReadAllBytes("arial.ttf"));  // für ^A@N,..,E:ARIAL.TTF
fonts.RegisterFolder("fonts/");                                   // 0.ttf, A.ttf, ... PLATE.TTF
fonts.LinkFont("E:ARIAL.TTF", "E:NOTOCJK.TTF");                   // wie ^FL: Fallback-Glyphen

renderer.ProcessCommands("^XA^CI28^FO50,50^A@N,40,40,E:ARIAL.TTF^FDÄrger? Nö!^FS^XZ");

Download per ZPL

~DYE:MONO,A,T,size,,hex oder ~DUE:MONO.TTF,size,hex lädt eine TTF in den Druckerspeicher; ^A@N,40,40,E:MONO.TTF nutzt sie, ^CW vergibt einen Buchstaben-Alias.

Label-Modell

label.AddFont("MONO.TTF", bytes) bettet die Schrift ein: ToZpl() sendet sie per ~DY, JSON speichert sie mit.

Komplexe Schriften, Bidi & Fallback

Text wird mit HarfBuzz geformt: arabische Ligaturen, hebräische und arabische Rechts-nach-links-Darstellung (Bidi), Devanagari- und Thai-Kombinationszeichen. Fehlen Glyphen, wird automatisch eine passende Systemschrift gesucht (CJK, Thai, Hindi …). ^PA schaltet Shaping/Bidi ab, ^FL (bzw. FontManager.LinkFont) verknüpft eine Erweiterungsschrift mit einer Basisschrift.

^XA^FLE:NOTOCJK.TTF,E:ARIAL.TTF,1^XZ     ← Glyphen, die ARIAL fehlen, aus NOTOCJK nehmen
^XA^FLE:NOTOCJK.TTF,E:ARIAL.TTF,0^XZ     ← Verknüpfung lösen

~DB Bitmap-Fonts

Mit ~DB heruntergeladene Bitmap-Schriften werden glyphengenau gezeichnet und ganzzahlig vergrößert; nutzbar über ^A@…,R:NAME.FNT oder einen ^CW-Alias.

PrintAsGraphic

Für Drucker ohne Speicher für TTFs: PrintAsGraphic = true rendert das Element (auch Barcodes) in der Vorschau-Engine und sendet es als ^GFA. Pixelgleich zur direkten TTF-Darstellung – im Test verifiziert.

var label = new Label(LabelUnit.Dots, 600, 200) { DownloadFonts = false }
    .AddFont("MONO.TTF", File.ReadAllBytes("DejaVuSansMono.ttf"))
    .Add(new TextItem
    {
        X = 20, Y = 20, Font = "E:MONO.TTF", Text = "TTF als Grafik", FontSize = 22,
        PrintAsGraphic = true,                // Text wird als ^GFA gesendet
    });

string zpl = label.ToZpl();                   // enthält ^GFA, kein ~DY und kein ^A@

10Label-Modell

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

TextItem

Font, FontSize (pt), Rotation, Width/MaxLines/Alignment (→ ^FB), CounterStep, MaxLength, HideIfEmpty, AutoFit, ValidationRegex, InputMask

BarcodeItem

alle 144 Symbologien (ZPL-Befehl oder ^GFA-Grafik), Generator-Optionen (Farben, Klarschrift, QR-Version/Maske, Modulformen), ModuleWidth, WideRatio, BarHeight, HumanReadable(Above), AddChecksum, QrErrorCorrection

Formen

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

ImageItem

PNG/JPEG → ^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.

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

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(\"40063813339\")" });

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 = "4006381333931" } });

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 142 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);

11Massendaten-Bindung

label.For<T>() liefert eine typisierte, streamende LabelBinding<T>: funktioniert mit Listen, LINQ, EF Core (IQueryable oder AsAsyncEnumerable()), LiteDB, DataRow und Dictionaries – Millionen Datensätze, ohne sie im Speicher zu halten.

var binding = label.For<OrderRec>()
    .Map("Total", o => o.Price * o.Quantity)    // berechnetes Feld (kompilierter Lambda)
    .Copies(o => o.Quantity)                    // ^PQ pro Datensatz
    .UseStoredFormat();                         // ^DF einmal, danach nur ^XF + ^FN-Werte

// DataField = "Customer.Address.City" wird per PropertyPath aufgelöst
string zpl = binding.ToZpl(orders);
foreach (string chunk in binding.Generate(orders)) { /* lazy, Datensatz für Datensatz */ }
foreach (string batch in binding.Batches(orders, labelsPerBatch: 500)) { /* Druckjobs */ }

await using var file = new StreamWriter("job.zpl");
await binding.WriteZplAsync(stream, file);                       // IAsyncEnumerable

int sent = await binding.PrintAsync(new ZplPrinterClient("192.168.1.50"), stream, labelsPerBatch: 500);
IEnumerable<byte[]> pngs = binding.Render(orders.Take(3));
byte[] pdf = binding.RenderPdf(orders.Take(100));
MethodeZweck
Map(field, o => …) / MapItem(name, …)Feld oder benanntes Element an kompilierten Lambda binden
Copies(o => n)Anzahl Kopien pro Datensatz (^PQ)
UseStoredFormat(name)Vorlage einmal per ^DF, danach nur ^XF/^FN-Werte – typisch 80–95 % weniger Daten
Across(columns, gap)mehrere Datensätze nebeneinander (mehrbahnige Rollen)
ToZpl / WriteZpl / WriteZplAsynckomplett als String oder in einen TextWriter
Generate / GenerateAsynclazy, ein ZPL-Format pro Datensatz
Batches(records, n)Druckjobs zu je n Etiketten
PrintAsync(printer, records, n)direkt an ZplPrinterClient senden, liefert Anzahl
Render / RenderPdfVorschau als Bilder bzw. PDF
PropertyPath: DataField = "Customer.Address.City" navigiert durch Properties, Felder, Dictionaries, DataRow, JsonElement und anonyme Typen. Eigene Wrapper-Typen (z. B. BsonValue) entpackt PropertyPath.RegisterUnwrapper<T>().

EF Core + SQLite

Aus dem Test DatabaseBindingTests: 2000 Sendungen in SQLite, Navigation-Properties, asynchrones Streaming mit gespeichertem Format – der letzte Barcode wird gerendert und mit ZXing dekodiert.

var label = new Label(LabelUnit.Mm, 100, 60)
    .Add(new TextItem { X = 3, Y = 3, DataField = "Customer.Name", FontSize = 14 })
    .Add(new TextItem { X = 3, Y = 12, DataField = "Customer.City" })
    .Add(new TextItem { X = 60, Y = 12, DataField = "Weight", DataFieldFormat = "0.0 kg" })
    .Add(new BarcodeItem { X = 3, Y = 22, DataField = "TrackingNo", BarHeight = 15 })
    .Add(new TextItem { X = 3, Y = 50, Expression = "\"Paket \" + LABEL_NUMBER" });

var options = new DbContextOptionsBuilder<ShippingDb>().UseSqlite("Data Source=shipping.db").Options;
await using var db = new ShippingDb(options);

// Streaming über alle offenen Sendungen – nie alles im Speicher
var query = db.Shipments.Include(s => s.Customer)
    .Where(s => !s.Printed && s.Customer.City == "Wien")
    .OrderBy(s => s.TrackingNo)
    .AsNoTracking()
    .AsAsyncEnumerable();

await using var output = new StreamWriter("versand.zpl");
await label.For<Shipment>()
    .Copies(s => s.Parcels)
    .UseStoredFormat()
    .WriteZplAsync(query, output);

LiteDB

using var db = new LiteDatabase("produkte.db");
PropertyPath.RegisterUnwrapper<BsonValue>(v => v.RawValue);   // BsonValue → .NET-Wert

var label = new Label(LabelUnit.Mm, 60, 40)
    .Add(new TextItem { X = 2, Y = 2, DataField = "Name" })
    .Add(new TextItem { X = 2, Y = 9, DataField = "Supplier.Name" })
    .Add(new TextItem { X = 40, Y = 9, DataField = "Price", DataFieldFormat = "0.00" })
    .Add(new BarcodeItem { X = 2, Y = 16, DataField = "Sku", BarHeight = 12 });

// typisiert mit LINQ
var typed = db.GetCollection<LiteProduct>("products").Query()
    .Where(p => p.Price > 400 && p.Supplier.Name == "ACME")
    .OrderBy(p => p.Price)
    .ToEnumerable();
string zpl = label.For<LiteProduct>().ToZpl(typed);

// untypisiert: BsonDocument, verschachtelte Pfade funktionieren genauso
var docs = db.GetCollection("products").Find(Query.EQ("Supplier.Name", "Contoso"));
string zpl2 = label.For<BsonDocument>().ToZpl(docs);

Mehrbahnige Etiketten

var small = new Label(LabelUnit.Mm, 30, 20)
    .Add(new TextItem { X = 2, Y = 2, DataField = "Sku", FontSize = 9 })
    .Add(new BarcodeItem { X = 2, Y = 7, DataField = "Sku", Symbology = Symbology.DataMatrix });

// 3 Etiketten nebeneinander auf einem 96 mm breiten Trägerband, 3 mm Abstand
string zpl = small.For<Line>().Across(3, gap: 3).ToZpl(lines);
Across(3, 3): drei Datensätze nebeneinander

JSON- & dynamische Records, Datensatzanzahl, Stream/PipeWriter, Ausdrücke

Ohne eigene Klasse binden: JsonElement, JsonNode, ExpandoObject, Dictionaries, DataRow und LiteDB-BsonDocument (Pfade wie order.no). Ist die Anzahl bekannt (WithRecordCount, CountQueryableWith oder recordCount:), stehen TOTAL_PAGES und PrintJobInfo.RecordCount schon ab dem ersten Etikett zur Verfügung. WriteZplAsync schreibt UTF-8 direkt in einen Stream oder PipeWriter (ASP.NET Core). Variablen: LABEL_INDEX, LABEL_NUMBER, LABEL_WIDTH, LABEL_HEIGHT, RECORD_INDEX, PAGE_NUMBER, TOTAL_PAGES plus eigene. Funktionen u. a. Upper Lower Left Right Mid Substring Trim PadLeft Replace Contains If Coalesce IsEmpty IsNumeric Format ToNumber ToDate Today Now AddDays Round Min Max Mod10 Mod11 Mod43 Luhn Gs1CheckDigit VoicePickCode. Zahlen und Datumswerte nutzen Expressions.Culture (Standard: invariant); Format/ToNumber nehmen optional einen Kulturnamen.

// Records ohne eigene Klasse: JsonElement, JsonNode, ExpandoObject, Dictionaries, DataRow, BsonDocument
using var doc = JsonDocument.Parse("""[{"name":"Anna","order":{"no":"A-1"}},{"name":"Ben","order":{"no":"A-2"}}]""");
string zpl = label.For<JsonElement>().ToZpl(doc.RootElement.EnumerateArray());

dynamic d = new ExpandoObject(); d.name = "Cleo";
string one = label.For<ExpandoObject>().ToZpl([(ExpandoObject)d]);

// Bekannte Anzahl → TOTAL_PAGES/RecordCount schon beim ersten Etikett; UTF-8 direkt in Stream oder PipeWriter
var binding = label.For<JsonElement>().WithRecordCount(2);
await binding.WriteZplAsync(rows, response, dpi: 203, recordCount: 2);
await binding.WriteZplAsync(rows, pipe);

// Ausdrücke: Funktionen, Variablen und Kultur
label.Expressions.Culture = System.Globalization.CultureInfo.GetCultureInfo("de-DE");
label.Add(new TextItem { X = 5, Y = 50,
    Expression = "\"Seite \" + PAGE_NUMBER + \"/\" + TOTAL_PAGES + \" · \" + Format(Today(), \"d\") + \" · \" + Upper(Left(name, 3))" });

12Druckaufträge & Massendruck

Ein Druckauftrag ist ein ZPL-Strom: optional ein Vorlauf-Etikett, ein Etikett pro Datensatz und optional ein Nachlauf-Etikett. Mit CutAfterLastLabel() läuft der ganze Auftrag im Abreißmodus und nur nach dem letzten Etikett wird geschnitten.

Vorlauf, Nachlauf und Schnitt (LabelBinding)

// Vor- und Nachlauf-Etikett werden an PrintJobInfo gebunden:
// JobName, Created, RecordCount, LabelCount, FirstKey, LastKey
var leading = new Label(LabelUnit.Mm, 100, 60)
    .Add(new TextItem { X = 5, Y = 5, Text = "DRUCKAUFTRAG", FontSize = 22 })
    .Add(new TextItem { X = 5, Y = 20, DataField = "JobName", FontSize = 14 })
    .Add(new TextItem { X = 5, Y = 32, Expression = "Format([Created], \"dd.MM.yyyy HH:mm\")" })
    .Add(new TextItem { X = 5, Y = 42, Expression = "[RecordCount] + \" Sendungen\"" });
var trailing = new Label(LabelUnit.Mm, 100, 60)
    .Add(new TextItem { X = 5, Y = 5, Text = "ENDE DES AUFTRAGS", FontSize = 18 })
    .Add(new TextItem { X = 5, Y = 20, Expression = "[LabelCount] + \" Labels gedruckt\"" })
    .Add(new TextItem { X = 5, Y = 30, Expression = "\"von \" + [FirstKey] + \" bis \" + [LastKey]", FontSize = 9 });
var label = new Label(LabelUnit.Mm, 100, 60)
    .Add(new TextItem { X = 4, Y = 4, DataField = "Name", FontSize = 14 })
    .Add(new TextItem { X = 4, Y = 12, DataField = "City" })
    .Add(new BarcodeItem { X = 4, Y = 22, DataField = "TrackingNo", BarHeight = 18 });

string zpl = label.For<Parcel>()
    .Job("Versand Wien", p => p.TrackingNo)   // Name + Schlüssel für FirstKey/LastKey
    .Copies(p => p.Parcels)                   // LabelCount zählt die Kopien mit
    .WithLeadingLabel(leading)
    .WithTrailingLabel(trailing)
    .CutAfterLastLabel()                      // 1. Format ^MMT (Abreißen), letztes ^MMC (Schneiden)
    .ToZpl(parcels);                          // Liste → RecordCount schon im Vorlauf bekannt
Methode / FeldBedeutung
.Job(name, keySelector)Auftragsname und Schlüssel pro Datensatz (z. B. Sendungsnummer) für FirstKey/LastKey
.WithLeadingLabel(label)Vorlauf-Etikett, gebunden an PrintJobInfo
.WithTrailingLabel(label)Nachlauf-Etikett mit Summen, gebunden an PrintJobInfo
.CutAfterLastLabel()erstes Format bekommt ^MMT (Abreißen, gilt für alle folgenden), nur das letzte Format ^MMC (Schneiden)
PrintJobInfo.JobName, CreatedName und Erstellzeit des Auftrags
PrintJobInfo.RecordCountAnzahl Datensätze – im Vorlauf bekannt, wenn eine Liste/Collection übergeben wird; im Nachlauf immer gesetzt
PrintJobInfo.LabelCountgedruckte Etiketten inkl. Kopien (Copies)
PrintJobInfo.FirstKey, LastKeySchlüssel des ersten und letzten Datensatzes

Alle Felder sind als DataField oder in Ausdrücken ([RecordCount] + " Sendungen") nutzbar. Das Ergebnis für drei Sendungen (eine davon mit zwei Kopien): 5 Formate, 6 Etiketten.

Vorlauf

Vorlauf: JobName, Created, RecordCount

Datensatz 2 (^PQ2)

Datensatz 2, Kopie 1

Nachlauf (^MMC)

Nachlauf: LabelCount, FirstKey, LastKey
Erzeugtes ZPL anzeigen

PrintJobTemplate: ein Auftrag aus JSON-Vorlagen

PrintJobTemplate fasst Vorlauf-, Datensatz- und Nachlauf-Etikett samt Optionen zusammen. Laden aus drei Label-Vorlagen (FromFiles(leading, label, trailing), Vor-/Nachlauf optional) oder aus einer JSON-Datei (ToJson/FromJson); SaveFiles zerlegt wieder in drei Dateien. For<T>() liefert die fertig konfigurierte LabelBinding<T>.

// drei JSON-Vorlagen (z. B. aus einem Designer) → EIN Druckauftrag
var job = PrintJobTemplate.FromFiles("templates/leading.json", "templates/shipping.json", "templates/trailing.json");
job.JobName = "Tagesversand";
job.CutAfterLastLabel = true;          // nur nach dem Nachlauf-Etikett schneiden
job.StoredFormatName = "R:SHIP.ZPL";   // Vorlage einmal per ^DF, danach nur ^XF + ^FN-Werte
job.KeyField = "TrackingNo";           // FirstKey/LastKey im Nachlauf
job.CopiesField = "Parcels";           // ^PQ pro Datensatz

string zpl = job.For<Shipment>().ToZpl(shipments.ToList());

// derselbe Auftrag als EINE JSON-Datei – oder wieder in drei Dateien zerlegt
File.WriteAllText("printjob.json", job.ToJson());
PrintJobTemplate same = PrintJobTemplate.FromJson(File.ReadAllText("printjob.json"));
same.SaveFiles("leading.json", "shipping.json", "trailing.json");
OptionWirkung
JobNameName für PrintJobInfo.JobName (Standard „Druckauftrag“)
CutAfterLastLabel^MMT für alle, ^MMC nur für das letzte Etikett
StoredFormatNameDatensatz-Etikett einmal per ^DF speichern, dann nur ^XF + ^FN
KeyFieldProperty-Pfad für FirstKey/LastKey
CopiesFieldProperty-Pfad für die Kopienzahl pro Datensatz

leading.json (Auszug)

printjob.json – alles in einer Datei (Auszug)

Vorlagen: leading.json · shipping.json · trailing.json · printjob.json

Massendaten-Demo: EF Core + LiteDB → eine .zpl-Datei

samples/MassDataDemo lädt die drei Vorlagen, fragt 50 Sendungen per LINQ aus SQLite (EF Core) bzw. LiteDB ab und schreibt jeweils einen Auftrag: Vorlauf, ^DF-Vorlage, 50 Versandetiketten, Nachlauf, Schnitt.

// samples/MassDataDemo: 50 Versandetiketten + Vorlauf + Nachlauf + Schnitt in EINER .zpl-Datei
var job = PrintJobTemplate.FromFiles("templates/leading.json", "templates/shipping.json", "templates/trailing.json");
job.CutAfterLastLabel = true;        // Abreißen für alle, Schnitt nach dem Nachlauf-Etikett
job.KeyField = "TrackingNo";         // erste/letzte Sendungsnummer im Nachlauf

// ---------- EF Core + SQLite ----------
using var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<ShippingDb>().UseSqlite(connection).Options;
using var db = new ShippingDb(options);
db.Database.EnsureCreated();
db.Shipments.AddRange(seedData);
db.SaveChanges();

var query = db.Shipments.AsNoTracking()          // von SQLite ausgeführt
    .Where(s => !s.Printed && s.Service == "EXPRESS")
    .OrderByDescending(s => s.Id)
    .Take(50);

job.JobName = "EF Core – Express";
job.StoredFormatName = "R:SHIP.ZPL";             // Vorlage einmal, dann nur Feldwerte
string efZpl = job.For<Shipment>()
    .Map("Total", s => s.WeightKg.ToString("0.0") + " kg")
    .ToZpl(query.ToList());
File.WriteAllText("massdata-efcore.zpl", efZpl);

// ---------- LiteDB ----------
using var lite = new LiteDatabase(new MemoryStream());
var col = lite.GetCollection<Shipment>("shipments");
col.InsertBulk(seedData);
col.EnsureIndex(s => s.Country);
var docs = col.Query().Where(s => s.Country == "AT").OrderBy(s => s.TrackingNo).Limit(50).ToEnumerable();

job.JobName = "LiteDB – Österreich";
job.StoredFormatName = null;                     // volles Format pro Etikett
string liteZpl = job.For<Shipment>()
    .Map("Total", s => s.WeightKg.ToString("0.0") + " kg")
    .ToZpl(docs.ToList());
File.WriteAllText("massdata-litedb.zpl", liteZpl);

// Vorschau des ganzen Auftrags als PDF
var renderer = new ZplRenderer(new ZplRenderSettings { OutputFormat = ZplOutputFormat.Pdf }.WithLabelSizeMm(100, 60));
File.WriteAllBytes("massdata-efcore.pdf", renderer.ProcessCommands(efZpl)[0]);
dotnet run --project samples/MassDataDemo -- samples/output samples/templates

Vorlauf (^MMT)

Vorlauf-Etikett der Demo

Versandetikett 1 von 50

Versandetikett der Demo

Nachlauf (^MMC)

Nachlauf-Etikett der Demo
massdata-efcore.zpl (Auszug)
Erzeugte Dateien: massdata-efcore.zpl (9 KB, mit ^DF/^XF) · massdata-efcore.pdf · massdata-litedb.zpl (17 KB, volle Formate) · massdata-litedb.pdf – je 52 Etiketten.

13Drucken

ZplPrinterClient sendet Raw-TCP an Port 9100 und fragt den Status per ~HS ab (ZplPrinterStatus: PaperOut, Paused, HeadOpen, RibbonOut, OverTemperature, UnderTemperature, LabelsRemainingInBatch, FormatsInBuffer).

var printer = new ZplPrinterClient("192.168.1.50")   // Port 9100 (Raw)
{
    Timeout = TimeSpan.FromSeconds(5),
};

ZplPrinterStatus status = await printer.GetStatusAsync();   // ~HS
if (!status.IsReady)
    throw new InvalidOperationException(
        $"Papier leer: {status.PaperOut}, Kopf offen: {status.HeadOpen}, Pause: {status.Paused}");

await printer.SendAsync(zpl);
await printer.SendAsync(label, dpi: 203, copies: 5);

Verbindungen: IPrinterConnection

Alle Transporte implementieren IPrinterConnection (SendAsync(byte[]), QueryAsync); Erweiterungsmethoden senden ZPL-Strings und Labels und fragen per GetStatusAsync() den ~HS-Status ab. PrinterSettings + PrinterConnectionFactory.Create machen den Transport konfigurierbar.

// ein Interface für alle Transportwege
await using IPrinterConnection printer = PrinterConnectionFactory.Create(new PrinterSettings
{
    CommunicationType = CommunicationType.Network,   // Network, Serial, Usb, File, WindowsDriver, Cups, Stream
    Host = "192.168.1.50", Port = 9100,
    Timeout = TimeSpan.FromSeconds(5),
});
ZplPrinterStatus? status = await printer.GetStatusAsync();   // ~HS (null bei reinen Schreib-Transporten)
await printer.SendAsync(zpl);
await printer.SendAsync(label, dpi: 203, copies: 2);

// direkt erzeugt
IPrinterConnection tcp = new TcpPrinterConnection("192.168.1.50");
IPrinterConnection serial = new SerialPrinterConnection("COM3", 9600, Parity.None, 8, StopBits.One, Handshake.XOnXOff);
IPrinterConnection usb = new RawFilePrinterConnection("/dev/usb/lp0");          // oder \\.\USB001, Datei
IPrinterConnection cups = new CupsPrinterConnection("Zebra_ZD421") { JobTitle = "Versand" };
IPrinterConnection mem = new StreamPrinterConnection(new MemoryStream());        // Tests
if (OperatingSystem.IsWindows())
{
    IPrinterConnection spooler = new WindowsSpoolerPrinterConnection("ZDesigner ZD421-203dpi", documentName: "Versand");
}
KlasseCommunicationTypeStatus (~HS)
TcpPrinterConnectionNetwork (Raw, Port 9100)ja
SerialPrinterConnectionSerial (Baud, Parität, Handshake)ja
RawFilePrinterConnectionUsb, File (Gerätepfad oder Datei)nein
WindowsSpoolerPrinterConnectionWindowsDriver (RAW-Druckjob, nur Windows)nein
CupsPrinterConnectionCups (lp -o raw)nein
StreamPrinterConnectionStream (beliebiger Stream, Tests)wenn lesbar

Virtueller Drucker (TCP 9100)

ZplVirtualPrinter lauscht auf dem Raw-Port, rendert eingehende ZPL- und EPL-Jobs (EPL wird erkannt) und speichert jedes Etikett als PNG oder PDF – ideal für Tests von Anwendungen, die sonst an einen Zebra drucken. Statusabfragen werden mit einem bereiten Drucker beantwortet (HostQueryHandler für eigene Antworten). Als Konsolenprogramm: dotnet run --project samples/VirtualPrinter -- --port 9100 --out labels [--pdf] [--any].

await using var printer = new ZplVirtualPrinter(9100, IPAddress.Loopback)
{
    RenderSettings = new ZplRenderSettings { Dpi = 203, OutputFormat = ZplOutputFormat.Png },
    OutputDirectory = "labels",                // jedes Etikett als labels/*.png
};
printer.LabelReceived += (_, e) => Console.WriteLine($"Etikett #{e.Index} → {e.FilePath}");
printer.Start();                               // beantwortet ~HS, ~HI und ~HQ (ES, SN, OD …)

Druckeinstellungen, erweiterter Status, Suche & Trockenlauf

ZplJobSettings erzeugt ^MM (Abreißen, Spenden, Schneiden, RFID …), ^MN (Lücke, Marke, Endlos), ^MT, ^LL/^ML, ^MD, ^PR und die Ausrichtung und fügt sie nach jedem ^XA ein. GetExtendedStatusAsync wertet ~HQES aus (Fehler- und Warnungs-Flags), dazu Seriennummer, Odometer und ~HI. PrinterDiscovery.FindUsbPrintersAsync findet USB-Drucker; mit EnablePrinting = false liefert die Fabrik eine DryRunPrinterConnection, die nur protokolliert. Weitere Transporte: TCP, seriell, USB/Datei, Parallelport, Windows-Spooler, CUPS, Stream.

var settings = new PrinterSettings
{
    Host = "127.0.0.1", Port = 9100,
    EnablePrinting = true,                     // false = Trockenlauf (DryRunPrinterConnection)
    Job = new ZplJobSettings
    {
        PrintMode = PrintMode.Cutter,          // ^MMC
        MediaTracking = MediaTracking.Gap,     // ^MNY
        MediaType = MediaType.DirectThermal,   // ^MTD
        Darkness = 20, PrintSpeed = 4,         // ^MD, ^PR
        Orientation = JobOrientation.Portrait180,
    },
};
await using IPrinterConnection conn = PrinterConnectionFactory.Create(settings);
await conn.SendAsync(settings.Job.Apply(zpl));
ZplExtendedStatus? status = await conn.GetExtendedStatusAsync();      // ~HQES
if (status is { IsReady: false }) Console.WriteLine(status.Errors);

IReadOnlyList<DiscoveredPrinter> usb = await PrinterDiscovery.FindUsbPrintersAsync();

14Export in andere Formate & Sprachen

RasterExport

Gerenderte Bitmaps als PCX, mehrseitiges TIFF (PackBits/CCITT), EPL2-Grafik, Fingerprint, ESC/POS (Rasterbild + Schnitt) und PCL.

LabelLanguageWriter

Das Label-Modell nativ als EPL, Fingerprint, ESC/POS oder PCL – Text und Barcodes bleiben Druckerbefehle.

var renderer = new ZplRenderer();
var results = renderer.Render(File.ReadAllText("labels.zpl"));
SKBitmap bmp = results[0].Bitmap;

File.WriteAllBytes("label.pcx", RasterExport.Pcx(bmp));
File.WriteAllBytes("labels.tif", RasterExport.Tiff(results.Select(r => r.Bitmap), TiffCompression.PackBits));
File.WriteAllBytes("label.epl", RasterExport.Epl(bmp));          // EPL2 GW-Grafik
File.WriteAllBytes("label.fp", RasterExport.Fingerprint(bmp));   // Honeywell/Intermec
File.WriteAllBytes("label.escpos", RasterExport.EscPos(bmp, cut: true));
File.WriteAllBytes("label.pcl", RasterExport.Pcl(bmp, dpi: 300));

// native Befehle aus dem Label-Modell (Text bleibt Text, Barcodes bleiben Barcodes)
string epl = LabelLanguageWriter.ToEpl(label);
string fingerprint = LabelLanguageWriter.ToFingerprint(label);
byte[] escpos = LabelLanguageWriter.ToEscPos(label);
byte[] pcl = LabelLanguageWriter.ToPcl(label);

EPL und Fingerprint mit Datensätzen

Mit Datensätzen erzeugt LabelLanguageWriter ein gespeichertes EPL-Formular (FS/FK) mit Variablen und einen Datenblock pro Datensatz bzw. Fingerprint mit Kopienzahl – Masken-Zähler inklusive.

// Label-Modell + Datensätze nativ als EPL2 bzw. Fingerprint (Text und Barcodes bleiben Befehle)
string epl = LabelLanguageWriter.ToEpl(label, parcels, dpi: 203, copies: p => p.Parcels, formName: "VERSAND");
string fp = LabelLanguageWriter.ToFingerprint(label, parcels, dpi: 203, copies: p => p.Parcels);
File.WriteAllText("versand.epl", epl);   // FS/FK-Formular + Variablen, ein Datenblock pro Datensatz
File.WriteAllText("versand.fp", fp);

TIFF, PCL, ESC/POS NV, GIF, HTML, EPS, Spiegeln & DPI-Umrechnung

TIFF mit CCITT G3/G4, LZW, Deflate oder PackBits und 1-Bit, Gray8, RGB oder RGBA; PCL monochrom/grau/farbig mit Modus 0/2/3; ESC/POS mit NV-Grafiken; GIF, SVG, HTML (eingebettet als PNG, GIF oder SVG) und EPS (optional als Umrisse). Ausgaben lassen sich spiegeln (Flip), quer stellen, skalieren, transparent machen und mit DPI-Metadaten versehen. ZplGraphicEncoder wandelt Bilder mit Dithering in ^GF (Hex, ACS, Z64, B64 oder automatisch das kürzeste). ZplRescaler rechnet rohes ZPL zwischen Auflösungen um: Ursprünge, Etikettgröße, Formen, Schriften, Feldblöcke, ^BY, 2D-Modulgrößen und ^GF-Grafiken.

var s = new ZplRenderSettings
{
    OutputFormat = ZplOutputFormat.Tiff,
    TiffCompression = TiffCompression.CcittGroup4,   // None, CcittGroup3, Lzw, Deflate, PackBits
    TiffPixelFormat = TiffPixelFormat.BlackWhite1,   // Gray8, Rgb24, Rgba32
    Flip = ZplFlip.X, PaperOrientation = ZplPaperOrientation.Landscape,
    WriteDpiMetadata = true,
}.WithLabelSizeMm(100, 60);
byte[] tiff = new ZplRenderer(s).ProcessCommands(zpl)[0];

s.OutputFormat = ZplOutputFormat.Pcl;  s.PclColorMode = PclColorMode.Grayscale; s.PclCompression = PclCompression.DeltaRow;
s.OutputFormat = ZplOutputFormat.Eps;  s.EpsTextAsOutlines = true;
s.OutputFormat = ZplOutputFormat.Html; s.HtmlImageFormat = HtmlLabelImageFormat.Svg;
s.OutputFormat = ZplOutputFormat.Gif;

// ESC/POS: Grafik einmal im NV-Speicher ablegen, danach nur noch aufrufen
byte[] define = EscPosNv.DefineGraphic(photo, "G1"), print = EscPosNv.PrintGraphic("G1");

// Bild → ^GF mit Dithering und kürzester Kodierung
string gf = ZplGraphicEncoder.ToZpl(photo, GraphicCompression.Auto, DitherMode.FloydSteinberg, x: 20, y: 20);

// ZPL von 203 auf 300 dpi umrechnen (Koordinaten, Schriften, ^BY, ^GF …)
string zpl300 = ZplRescaler.Rescale(zpl, 203, 300);

ZPL-Optimierer

ZplOptimizer.Optimize verkleinert ZPL ohne Pixeländerung (geprüft am gesamten Labelary-Korpus): Safe entfernt redundante Zustandsbefehle (^BY, ^CF …), Aggressive zusätzlich Zeilenumbrüche und Standardparameter und komprimiert Grafiken neu (GraphicCompression Hex/ACS/Z64). Felddaten bleiben unangetastet, bei ^CC/^CT bleibt die Eingabe unverändert. Im Label-Modell: Label.Optimization.

string safe = ZplOptimizer.Optimize(zpl, ZplOptimization.Safe);              // doppelte ^BY/^CF/... entfernen
string small = ZplOptimizer.Optimize(zpl, ZplOptimization.Aggressive,        // + Zeilenumbrüche, Standardparameter
    GraphicCompression.Z64);                                                 // + Grafiken neu komprimieren

label.Optimization = ZplOptimization.Aggressive;     // gilt für ToZpl und die Datenbindung
label.GraphicCompression = GraphicCompression.Z64;

Font-Download, Speicher umbenennen & Elemente als JSON

Label.FontDownloadZpl() liefert nur die ~DY/~DU-Befehle der Label-Fonts, FontManager.Convert wandelt jede TTF in einen Download-Befehl, Label.FontAsGraphicThreshold sendet große Fonts stattdessen als Grafik. Der ZplResourceStore kann Grafiken, Formate und Fonts umbenennen und dekodierte Grafiken cachen. ZplRenderResult.RenderedElementsAsJson() gibt die getracten Elemente mit Typ, Daten, Bounds, Rotation und Quellposition aus.

var label = new Label(LabelUnit.Mm, 50, 20).AddFont("MYFONT.TTF", ttf);
label.Add(new TextItem { Text = "Hallo", Font = "E:MYFONT.TTF" });
string fonts = label.FontDownloadZpl();                         // nur ~DY…, einmalig an den Drucker
string du = label.FontDownloadZpl(FontDownloadCommand.DU);
string raw = FontManager.Convert(ttf, "R:OTHER", FontDownloadCommand.DU); // beliebige TTF → ZPL
label.FontAsGraphicThreshold = 512 * 1024;                     // größere Fonts als ^GFA-Grafik
var r = new ZplRenderer();
r.Render("~DGR:OLD.GRF,8,1,FFFFFFFFFFFFFFFF");
r.Store.RenameGraphic("OLD", "E:LOGO.GRF");
r.Store.RenameFormat("A", "C");
r.Store.RenameFont("E:F1.TTF", "F2.TTF");
r.Store.CacheGraphics = true;               // dekodierte ^GF-Grafiken wiederverwenden
int cached = r.Store.CachedGraphicCount;
var renderer = new ZplRenderer(new ZplRenderSettings { TraceElements = true });
foreach (var res in renderer.Render(zpl))
{
    // [{"type":"A","data":"Hi","bounds":{"x":..},"rotation":90,"sourceOffset":..,"sourceLength":..}, ...]
    string json = res.RenderedElementsAsJson(indented: true);
    res.Bitmap.Dispose();
}

15ZPL-Objektmodell

ZplDocument.Parse zerlegt ZPL in ZplFormats mit typisierten Befehlen – je ZPL-Befehl eine Unterklasse von ZplCommandBase (FieldOriginCommand, Code128Command, Pdf417Command …, unbekannte als UnknownCommand). Eigenschaften ändern, ToString() schreibt wieder ZPL. ZplBuilder baut Formate fluent auf.

// typisiertes Objektmodell: parsen, ändern, wieder schreiben
ZplDocument doc = ZplDocument.Parse(zplIn);
foreach (ZplFormat format in doc.LabelFormats)
{
    foreach (FieldOriginCommand fo in format.OfType<FieldOriginCommand>())
        fo.X += 20;                                       // alles 20 Punkte nach rechts
    foreach (Code128Command bc in format.OfType<Code128Command>())
        bc.Height = 60;
}
string moved = doc.ToString();

// oder von Grund auf mit dem ZplBuilder
string zpl = new ZplBuilder()
    .Format(f => f
        .Encoding(28)
        .Text(30, 30, "ZplBuilder", height: 40)
        .FieldOrigin(30, 90).BarcodeDefaults(2, height: 70).Code128().FieldData("BUILD-42")
        .FieldOrigin(330, 30).QrCode(magnification: 4).FieldData("QA,ZplDocument")
        .FieldOrigin(20, 20).GraphicBox(540, 200, 3, rounding: 2).FieldSeparator())
    .ToString();

Ausgabe des ZplBuilder

Mit ZplBuilder erzeugtes Etikett
ZPL

16Farbe & Vorschau

Dasselbe ZPL, drei Einstellungen.

InkColor + BackgroundColor

InkColor #1d3b8a, BackgroundColor #fff7d6

Roter Farbband-Look

InkColor #c8102e – gerendert mit zplCore.com

InvertColors

InvertColors = true
ZPL anzeigen

17Beispiel-Etiketten

Die fünf Etiketten aus samples/zpl/five-labels.zpl – Versand, Produkt, Lagerplatz, Inventar, Paletten. Auch als PDF.

17bKommandozeile, Server-Image & Helm

tools/ZplCli ist als .NET-Tool packbar (Paket ZplCore.Cli, Befehl zplcore).

dotnet pack tools/ZplCli -c Release -o nupkg
dotnet tool install -g ZplCore.Cli --add-source nupkg

zplcore render label.zpl -o label.png --dpi 300 --width 100 --height 150   # mm; ein Bild pro Etikett
zplcore render label.zpl -o labels.pdf                                       # pdf/tiff/html enthalten alle Etiketten
zplcore render label.epl -f tiff -o out.tif                                  # EPL wird erkannt (oder --epl)
zplcore render label.zpl -f svg > label.svg                                  # stdout; "-" liest stdin
zplcore epl2zpl label.epl -o label.zpl
zplcore print label.zpl --host 192.168.1.50 --port 9100
zplcore barcode qrcode "https://example.com" -o qr.svg                       # png/svg/pdf/eps/jpeg/zpl
zplcore validate label.zpl                                                   # Exit-Code 1 bei Warnungen
zplcore formats

Weitere Optionen: --labels 0,2, --flip x|y|xy, --landscape, --no-antialias.

Server-Image (distroless) und Helm-Chart

build/Dockerfile.server veröffentlicht samples/WebApiDemo auf mcr.microsoft.com/dotnet/aspnet:10.0-azurelinux3.0-distroless: keine Shell, Benutzer app (nicht root), Port 8080, /health, SQLite-Demodaten in /tmp – läuft mit --read-only. deploy/helm/zplcore enthält Deployment, Service und HorizontalPodAutoscaler (Standard 2–10 Replikas, 70 % CPU), Probes auf /health, App-Einstellungen über env und ein schreibgeschütztes Root-Dateisystem mit emptyDir auf /tmp.

docker build -f build/Dockerfile.server -t zplcore-server .
docker run --rm --read-only --tmpfs /tmp -p 8080:8080 zplcore-server
curl -X POST 'localhost:8080/api/jobs/preview?format=pdf' -H 'Content-Type: application/json' \
     -d '{"data":[{"trackingNo":"00340000000000000001","name":"Anna Huber","city":"Wien","country":"AT"}]}' -o preview.pdf

helm install labels deploy/helm/zplcore --set image.repository=myregistry/zplcore-server --set image.tag=1.0.0

17cDiagnosen, Quellpositionen, Limits & Threads

Jedes ZplRenderResult hat Diagnostics: ZplDiagnostic mit stabilem Code, Schweregrad, Meldung, Befehl und Position (Offset, Zeile, Spalte). Warnings bleibt erhalten und enthält die Meldungen aller Warnungen und Fehler.

CodeSchwereBedeutung
ZPL1001ErrorBefehl mit fehlerhaften Parametern ignoriert (Rest des Etiketts wird gerendert)
ZPL1002WarningFeld konnte nicht gezeichnet werden (z. B. ungültige Barcode-Daten)
ZPL1003WarningBarcode-Daten länger als jede Symbologie fassen kann
ZPL1101Warning^GF größer als das Etikett oder MaxGraphicBytes
ZPL1102Warning^GF-Daten nicht dekodierbar
ZPL1103Warning^XG/^IM verweist auf nicht gespeicherte Grafik
ZPL1201Warning^A@-Schrift nicht gefunden – Schrift A wird verwendet, wie am Zebra
ZPL2001InfoAkzeptiert, ohne Wirkung aufs Bild (^FE, ^FA, RFID-Operationen außer Schreiben)

Mit TraceElements trägt jedes Element SourceOffset/SourceLength (sein ^FO…^FS-Block); jeder geparste ZplCommand hat Offset/Length, ZplParser.LineColumn rechnet um. Fehlende ^A@-Schriften liefert auf Wunsch der Callback FontProvider.

var renderer = new ZplRenderer(new ZplRenderSettings
{
    TraceElements = true,
    FontProvider = name => name == "E:BRAND.TTF" ? File.ReadAllBytes("fonts/Brand.ttf") : null,
});
foreach (var result in renderer.Render(zpl))
{
    foreach (ZplDiagnostic d in result.Diagnostics)          // z. B. "ZPL1201 warning (3:12): …"
        Console.WriteLine($"{d.Code} {d.Severity} {d.Command} Zeile {d.Line}, Spalte {d.Column}: {d.Message}");
    foreach (var e in result.Elements)                       // ^FO…^FS-Bereich im Quelltext
        Console.WriteLine(zpl.Substring(e.SourceOffset, e.SourceLength));
    result.Bitmap.Dispose();
}

Limits für fremde Eingaben & Thread-Sicherheit

Standardwerte: MaxLabelSide 32000, MaxLabelDots 64 Mio., MaxQuantity 10000, MaxLabels 100000, MaxGraphicBytes 32 MB (auch Z64-Entpackgrenze gegen Zip-Bomben), MaxFieldDataLength 1 Mio., ZplResourceStore.MaxTotalBytes 256 MB. ZplRenderer/EplRenderer halten Druckerzustand und sind nicht thread-sicher – ein Renderer pro Thread; ZplResourceStore, FontManager, ZplCaches, PropertyPath und konfigurierte LabelBindings sind es. ConcurrencyTests vergleicht SHA-256 aller Ausgaben von 16 Threads mit dem Einzel-Thread-Lauf; samples/FuzzRunner füttert den Parser mit Zufalls- und mutierten Eingaben, samples/LeakTest rendert 1 Mio. Etiketten und protokolliert Heap und RSS. Details: THREADING.md, DIAGNOSTICS.md.

var shared = new ZplResourceStore();                          // thread-safe, von vielen Renderern teilbar
var safe = new ZplRenderSettings
{
    MaxLabelSide = 8000, MaxLabelDots = 16_000_000, MaxQuantity = 100,
    MaxLabels = 1000, MaxGraphicBytes = 4 * 1024 * 1024, MaxFieldDataLength = 10_000,
};
Parallel.For(0, 8, _ =>
{
    var r = new ZplRenderer(safe) { Store = shared };         // ein Renderer pro Thread
    r.ProcessCommands(untrusted);
});

17dPlattformen, Schriftsysteme & Datenbanken

Plattformen

Geprüft auf macOS, Windows 11, Debian (mcr.microsoft.com/dotnet/sdk:10.0) und Azure Linux 3.0 (sdk, runtime und runtime-distroless). Keine nativen Pakete nötig: SkiaSharp NativeAssets.Linux.NoDependencies, HarfBuzzSharp bringt seine Bibliothek mit, die Schriften sind eingebettet. Minimal-Images haben keine Systemschriften – Zeichen außerhalb der eingebetteten Schriften (CJK, Emoji) per RegisterFont/~DY/~DU mitliefern (build/LINUX.md, build/Dockerfile.test.azurelinux).

Datenbanken

Bindung und Druckaufträge sind getestet mit SQLite (EF Core) und LiteDB in den Unit-Tests sowie in tests/ZplCore.IntegrationTests per Testcontainers gegen SQL Server 2022 (EF Core), PostgreSQL (Npgsql EF Core) und MongoDB (MongoDB.Driver und MongoDB.EntityFrameworkCore). Ohne Docker werden diese Tests übersprungen.

Schriftsysteme der Welt

WorldScriptTests rendert Arabisch, Chinesisch, Hindi (Devanagari), Koreanisch, Japanisch und Russisch mit heruntergeladenen Noto-TTFs – einmal als TTF im Druckerspeicher (~DU + ^A@), einmal als Grafik im Label-Modell (PrintAsGraphic, der Drucker braucht dann keine Schrift). Auf Linux-Containern ohne Systemschriften belegt das, dass wirklich die geladene Schrift inkl. HarfBuzz-Shaping verwendet wird. Beispiele in samples/fonts:

Arabisch: TTF im Druckerspeicher (~DU + ^A@)
Arabisch
Chinesisch: TTF im Druckerspeicher (~DU + ^A@)
Chinesisch
Hindi: TTF im Druckerspeicher (~DU + ^A@)
Hindi
Koreanisch: TTF im Druckerspeicher (~DU + ^A@)
Koreanisch
Japanisch: TTF im Druckerspeicher (~DU + ^A@)
Japanisch
Russisch: TTF im Druckerspeicher (~DU + ^A@)
Russisch

18Tests & Verifikation

1656 xUnit-Tests

Parser, Rendering, Fonts, Shaping, Binding, Druckaufträge, Export, Objektmodell, Verbindungen, Druckerbefehle, Einstellungen – dotnet test läuft in wenigen Sekunden.

Barcodes werden zurückgelesen

Jeder portierte Encoder (Postcodes, Codablock F, Code 16K/49, PDF417/MicroPDF417, Han Xin, DotCode, Ultracode, rMQR, QR-Varianten, Data Matrix, Aztec, GS1, Composite …) ist bit-genau gegen bwip-js (zint-Portierungen gegen zint) geprüft – darunter alle 7 GS1-DataBar-Varianten mit 80 eigenen Referenzfällen – die Referenzmatrizen stehen in den Tests. Wo ZXing.Net die Symbologie kennt, wird zusätzlich dekodiert.

Datenbanken

EF Core mit SQLite (in-memory, 2000 Zeilen) und LiteDB (typisiert + BsonDocument) in den Unit-Tests, SQL Server 2022, PostgreSQL und MongoDB per Testcontainers in tests/ZplCore.IntegrationTests; samples/MassDataDemo erzeugt die Beispiel-Aufträge.

Diese Doku

Alle Screenshots erzeugt samples/DocsGenerator aus echtem ZPL/EPL bzw. der Barcode-API; alle C#-Beispiele stehen kompilierbar in samples/DocsExamples und werden vom Generator wortgleich in diese Seite kopiert.

dotnet test                                  # 1656 Tests
dotnet run --project samples/LabelaryCompare # Pixelvergleich gegen Labelary-Referenzen
dotnet run --project samples/DocsGenerator   # docs/img, docs/zpl, docs/files, docs/examples.js, C#-Snippets
dotnet build samples/DocsExamples            # Compile-Check der Doku-Snippets

19Genauigkeit

samples/LabelaryCompare rendert den ZPL-Korpus samples/labelary/*.zpl und vergleicht Pixel für Pixel (2 Punkte Toleranz) mit druckergenauen Labelary-Referenzen. LabelaryReferenceTests hält jeden Fall mindestens auf seinem aktuellen Wert.

0,974mittlerer Score
122Testfälle
79Fälle ≥ 0,99
FallScorePräzisionRecallnur Referenznur zplCore.com
ll-clip0,6451,0000,476124830
gs-symbols0,6860,6850,68623722386
rss-stacked0,7670,9260,65599491409
qr-encodings0,8420,7370,9812123836
gd-orient0,8480,8440,852812957
maxicode-modes0,8600,7970,93321439764
gs-sizes0,8680,9030,83541922255
fp-rot0,8800,8940,868407297
aat-cw0,8880,8690,9078471203
ft-barcodes-r0,8980,8850,91242135713
imb-postal0,9011,0000,81921200
code128-invoc0,9010,8400,9738255512
fb-rot-i0,9040,9150,8941067846
fb-bitmap0,9130,9170,909806581
fonts-efgh0,9150,9500,883664218

Die 15 schwächsten von 122 Fällen; 79 Fälle erreichen ≥ 0,99. Vollständige Liste: dotnet run --project samples/LabelaryCompare (offline gegen die gespeicherten Referenzen in tests/ZplCore.Tests/Reference/labelary).

Die schwächsten Fälle betreffen Sonderfälle wie Clipping über die Etikettlänge (ll-clip), ^GS-Symbole, gestapelte DataBar-Varianten, QR-Kodierungsmodi, ^GD-Ausrichtung und MaxiCode-Modi. Alle Symbole bleiben dekodierbar; Labelary rendert ^BB und ^B4 nicht, diese sind ausschließlich gegen bwip-js verifiziert.

19bPerformance

samples/PerfTest erzeugt 100.000 Labels in einem Druckauftrag (Start-Label + 100.000 Sendungs-Labels + End-Label, Schnitt nur nach dem letzten Label) – aus Objekten, gestreamt aus EF Core/SQLite und aus LiteDB. Gemessen im Release-Build auf 14 Kernen (.NET 10).

< 1 s100.000 Labels als ZPL
14,7 MBAuftragsdatei (gespeichertes Format)
24–64 MBHeap (Streaming)
Test (100.000 Labels, ein Job)ZeitLabels/sDateiHeap
Objekte → ZPL, gespeichertes Format (^DF/^XF)0,73 s136.00014,7 MB24 MB
Objekte → ZPL, volles Format je Label0,39 s256.00029,1 MB24 MB
EF Core / SQLite AsAsyncEnumerable() → Datei0,57 s176.00014,7 MB29 MB
LiteDB-Query → Datei0,50 s202.00014,7 MB64 MB
Vorschau PNG sequenziell (799 × 480 px, Graustufen-PNG)≈ 3,4 min49018,2 MB / 2.00084 MB
Vorschau PNG parallel (14 Kerne)≈ 25 s4.03317,1 MB / 2.000106 MB

Die ZPL-Erzeugung für den Drucker ist praktisch kostenlos und streamt mit konstantem Speicher. Das gespeicherte Format halbiert die übertragene Datenmenge.

Render-Optimierung

Schwarz-weiße Labels werden als 8-Bit-Graustufen-PNG (mit Kantenglättung) bzw. echtes 1-Bit-PNG (AntiAlias = false) statt 32-Bit-RGBA geschrieben (ZplRenderSettings.CompactPng, PngCompression Fastest/Optimal/SmallestSize). Einfache ^GB-Rahmen werden direkt gezeichnet, und das Label-Bild wird nur kopiert, wenn ^MC N es braucht.

Pro Label (100 × 60 mm, 203 dpi)vorherjetzt
Rendern zu Bitmap1,28 ms0,39 ms
Rendern + PNG8,19 ms1,86 ms
PNG-Größe18 KB9 KB
100.000 Vorschauen parallel2,2 min25 s

Parallele Skalierung

SkiaSharp serialisiert zwei Dinge global: Skias Glyph-Cache (jedes DrawText) und SkiaSharps Handle-Tabelle (jedes neu erzeugte SKPaint/SKFont/SKBitmap – und DrawBitmap, das intern jedes Mal ein SKImage anlegt). zplCore.com rastert deshalb jede Glyphe einmal pro Schrift/Größe in einen sperrfreien Cache aus SKImage-Masken und zeichnet Text per DrawImage; Schriften werden explizit durchgereicht statt über SKFont.Typeface abgefragt. Ergebnis gemessen mit samples/RenderBench -- 150 scale (Apple M3 Max, 10 Performance- + 4 Effizienzkerne):

ThreadsRendern + PNG vorherRendern + PNG jetztnur Text vorhernur Text jetzt
1499/s (1,0×)549/s (1,0×)5.665/s (1,0×)6.306/s (1,0×)
41.984/s (4,0×)2.225/s (4,1×)3.770/s (0,7×)28.183/s (4,5×)
82.245/s (4,5×)4.090/s (7,5×)3.162/s (0,6×)42.434/s (6,7×)
101.826/s (3,7×)5.022/s (9,2×)3.150/s (0,6×)39.384/s (6,2×)
dotnet run -c Release --project samples/RenderBench -- 150 scale [label|text|barcode|box|empty]
dotnet run -c Release --project samples/RenderBench -- 500   # Mikro-Benchmark pro Element
dotnet run -c Release --project samples/PerfTest -- 100000 perf-out

20Grenzen

Ehrlich aufgelistet – damit es keine Überraschungen am Drucker gibt.

Schriften: Zebras residente Schriften (0, A–H) sind proprietär und werden durch Roboto Condensed Bold bzw. DejaVu Sans Mono ersetzt – auf Zebra-Metriken kalibriert, die Glyphenformen der Bitmap-Schriften A–H weichen aber ab; für exakte Metriken die passende TTF mit Store.Fonts.RegisterFont("0", bytes) registrieren. Die Klarschriftzeile unter Barcodes ist monospaced, aber kein echtes OCR-B.

Nicht implementiert

  • Pharmacode 2D, Response Plus PostMatrix (keine öffentliche Spezifikation)
  • HIBC nach ISO/IEC 15434 (Datenformat-Hülle)
  • Experimentell: Deutsche Post BZL (ZPLBZL001) und JABCode – der JABCode-Encoder ist langsam (ca. 0,5 s für 120 zufällige Unicode-Zeichen)
  • ISBT 128: Hilfsfunktionen nur für die Datenstrukturen DS001–DS011
  • JAB Code ist experimentell (bit-genau gegen BWIPP, aber kein Decoder zur Gegenprobe)

Abweichungen & eingeschränkt verifiziert

  • Glyphenformen der residenten Zebra-Bitmap-Schriften A–H
  • PDF417 (^B7): ohne Spalten/Zeilen wählt Zebra teils eine andere Geometrie
  • QR (^BQ): die Maskenwahl weicht in einzelnen Fällen von Zebra ab (Symbol trotzdem gültig)
  • ^BB (Codablock F) und ^B4 (Code 49) rendert Labelary nicht – nur gegen bwip-js verifiziert
  • Fingerprint-Ausgabe und die echten Drucker-Transporte (seriell, USB, Windows-Spooler, CUPS) sind nicht auf Hardware getestet; TCP 9100 ist gegen den virtuellen Drucker getestet
  • Auf Linux weicht die Glyph-Rasterung (FreeType statt CoreText) leicht ab: bitmap-fonts erreicht dort 0,774 statt ≥ 0,919
  • ZplRescaler skaliert ~DG/~DY-Downloads und binäres ^GFB nicht; 1D-Module werden auf ganze Punkte gerundet
  • Medien-, Sensor- und Konfigurationsbefehle werden akzeptiert, beeinflussen das Bild aber nicht

21Lizenzen & Drittanbieter

KomponenteLizenzVerwendung
BWIPP / bwip-js (© Terry Burton, Mark Warren)MITTeile der Barcode-Encoder (Postcodes, gestapelte und 2D-Symbologien, Composite, GS1-Tabellen) und Referenzmatrizen der Tests
zint (© Robin Stuart u. a.)BSD-3-ClausePortierungen u. a. von Korea Post, Mailmark 4-State, DPD, UPU S10, Flattermarken, UPNQR, Grid Matrix und Code One
ZXing.NetApache-2.0Bausteine für QR, Data Matrix, PDF417, Aztec und EAN/UPC; in den Tests zum Zurücklesen der Barcodes
SkiaSharp, HarfBuzzSharpMITRendering, Text-Shaping
Roboto CondensedSIL OFL 1.1Ersatz für Zebra-Schrift 0
DejaVu Sans / Sans MonoBitstream Vera / DejaVu-Lizenz (frei)Ersatz für Schriften A–H, Klarschrift

Abgeleitete Dateien tragen einen Kopfkommentar. Vollständige Texte: THIRD-PARTY-NOTICES.md