zplCore.com · High Performance .NET Labelprint Engine
.NET 10 · ZPL II · EPL2 · Barcodes
Render, generate and print labels. With a single library.
zplCore.com is a virtual label printer for .NET: ZPL II and EPL2 become PNG or PDF, labels built from C# objects become ZPL, and 144 barcode symbologies are built in. Runs on Windows, Linux and macOS, with no printer driver.

rendered with zplCore.com · PNG
At a glance
^XA ^CI28 ^PW812 ^LL1218 ^FX Header: filled block + ^FR inverted ^FO0,0^GB812,150,150^FS ^FO32,26^A0N,62,62^FR^FDFLINKFRACHT^FS ^FO640,166^GC130,8,B^FS 24h stamp ^FO32,356^A0N,170,140^FD80-B7^FS Routing, font 0 ^FO32,540^GB748,236,4,B,2^FS rounded ^FO56,598^A0N,44,44^FDJürgen Größmann^FS ^BY3,3,110^FO60,826^BCN,110,Y,N,N^FDZE4711802026DE^FS ^FO40,1000^BXN,7,200^FDZE4711802026DE|80331|B7^FS ^FO220,996^B7N,5,2,4^FDFLINKFRACHT|…^FS ^FO280,1100^BCN,60,Y,N,N^FD>;>800340123450000000018^FS ^XZ // C#: new ZplRenderer(settings).ProcessCommands(zpl) → PNG
Features
Everything that happens between data and label
Render
ZPL II renderer
Output as PNG, JPEG, WebP, BMP, GIF, PDF, GRF, HTML, EPS, TIFF (CCITT G3/G4, LZW, Deflate, PackBits, multi-page), PCL (mono, grayscale, color) and ESC/POS with NV logos. Stored formats with ^DF/^XF, graphics, barcodes, fonts.
Vector output
Entire labels as vector SVG and PDF: text as glyph outlines, barcodes and boxes as paths, graphics as 1-bit images. Every SVG element carries its position in the ZPL source. Rasterized back, the output matches the PNG (mean 1.000 at a 2-dot tolerance, 126 labels).
EPL2 renderer
EPL2 runs through the same engine. ToZpl() converts existing EPL forms to ZPL.
Diagnostics & source mapping
Structured diagnostics with stable codes. Every rendered element knows its position in the ZPL source and can be exported as JSON. Safety limits for untrusted ZPL.
ZPL optimizer
Shrinks ZPL without changing a single pixel: removes redundant commands and default parameters and recompresses graphics (Hex, ACS, Z64). Verified pixel-identical across the entire reference corpus.
DPI conversion
ZplRescaler converts raw ZPL between 203, 300 and 600 dpi. ZplGraphicEncoder encodes images as Hex, ACS, Z64 or B64 with 5 dithering methods.
Verified accuracy
Automated pixel comparison against printer-accurate reference renderings, averaging 0.974 across 122 test cases. Barcode encoders are verified bit-exact against bwip-js.
Generate & bind
Barcode generator
144 symbologies, from Code 128, GS1 DataBar and QR to MaxiCode, DotCode and Han Xin. Output as PNG, SVG, PDF, EPS or ^GFA. GS1 validation included.
Label model
Text, barcode, table, image, RFID in mm or inches. Expressions with element references, validation, AutoFit, duplex with front and back. Multilingual templates with {t:key} and a culture per print job. Save and load as JSON templates.
Data binding
label.For<T>() binds C# objects, EF Core queries and MongoDB or LiteDB documents via LINQ. Plus JSON (JsonElement) and dynamic records. Streaming with constant memory, asynchronously into Stream and PipeWriter.
Print & operate
Print jobs
Leading label, records, trailing label and a cut after the last label, all in one job. Transport via TCP 9100, serial, USB, the Windows spooler or CUPS. ^MM/^MN/^MT, extended ~HQ status, dry run.
Virtual printer
ZplVirtualPrinter listens on TCP 9100 and renders incoming print jobs. Test without hardware.
Command line
The zpl dotnet tool renders, converts EPL to ZPL, prints, generates barcodes and validates.
TrueType fonts
TTF fonts go into printer memory via ~DY or are printed as graphics (PrintAsGraphic).
World scripts
HarfBuzz shaping and bidi for Arabic, Hebrew, Thai, Indic scripts and CJK.
One package instead of four
One NuGet package for four jobs
Many .NET teams buy several SDKs just for labels: a ZPL emulator, an EPL emulator, a label designer and a barcode library. zplCore.com covers ZPL rendering, EPL rendering, label design and barcode generation in a single assembly.
| Use case | Typically | zplCore.com |
|---|---|---|
| ZPL → PNG/PDF preview | ZPL emulator SDK | ZplRenderer |
| EPL2 → image, EPL → ZPL migration | EPL emulator SDK | EplRenderer · ToZpl() |
| Design labels in code, bind data, print | Label designer SDK | Labels.Label · PrintJobTemplate |
| Barcodes as image/vector, GS1 validation | Barcode SDK | Barcodes.Barcode · Gs1 |
Switching from another SDK? zplCore.com is not a 1:1 copy of other vendors' APIs and does not have every feature. Send us your ZPL files and requirements, and we will check coverage before you buy (see also known limitations).
Performance
Fast enough for the 6 p.m. shipping cutoff
Every number is measured, not estimated: release build, .NET 10, Apple M3 Max (10 performance + 4 efficiency cores). The benchmarks ship with the repository.
One print job: leading label + 100,000 shipping labels + trailing label, cut only after the last label. 14 cores, samples/PerfTest.
| Scenario | Time | Labels/s | Size | Memory (heap) |
|---|---|---|---|---|
Objects → ZPL, stored format (^DF/^XF) | 0.73 s | 136,000 | 14.7 MB | 24 MB |
| Objects → ZPL, full format per label | 0.39 s | 256,000 | 29.1 MB | 24 MB |
EF Core / SQLite AsAsyncEnumerable() → file | 0.57 s | 176,000 | 14.7 MB | 29 MB |
| LiteDB query → file | 0.50 s | 202,000 | 14.7 MB | 64 MB |
| PNG preview, sequential (799 × 480 px, grayscale) | 3.4 min | 490 | 18.2 MB / 2,000 | 84 MB |
| PNG preview, parallel (14 cores) | 24.8 s | 4,033 | 17.1 MB / 2,000 | 106 MB |
ZPL generation is practically free and streams with constant memory. The stored format halves the data sent to the printer (14.7 instead of 29.1 MB).
ASP.NET Core minimal API from samples/WebApiDemo: 14 cores, server GC, 14 workers, 100 × 60 mm label at 203 dpi. Client and server in the same process.
| Scenario | Labels/s | MB/s | p50 | p95 | GC heap |
|---|---|---|---|---|---|
| JSON payload → ZPL | |||||
| 1,000 records/request | 62,857 | 20.3 | 16.2 ms | 16.7 ms | 178 MB |
| 10,000 records/request | 112,484 | 36.4 | 76.1 ms | 136.9 ms | 183 MB |
| 100,000 records/request | 186,572 | 60.5 | 527.5 ms | 565.2 ms | 414 MB |
| DB streaming → ZPL | |||||
| 10,000 records | 151,744 | 49.1 | 69.6 ms | 73.1 ms | 361 MB |
| 100,000 records | 199,102 | 64.6 | 504.3 ms | 505.2 ms | 372 MB |
| Concurrent clients | |||||
| JSON 1k, 1 client | 196,112 | 63.4 | 4.8 ms | 8.6 ms | 404 MB |
| JSON 1k, 4 clients | 240,453 | 77.7 | 16.4 ms | 21.7 ms | 404 MB |
| JSON 1k, 16 clients | 155,344 | 50.2 | 100.8 ms | 120.8 ms | 419 MB |
| DB 10k, 4 clients | 215,699 | 69.7 | 186.2 ms | 202.4 ms | 395 MB |
| DB 10k, 16 clients | 179,860 | 58.2 | 878.0 ms | 907.6 ms | 408 MB |
| Preview (50 labels/request) | |||||
| PDF, 1 client | 583 | 5.0 | 87.0 ms | 94.0 ms | 267 MB |
| PNG (zip), 1 client | 484 | 4.6 | 108.3 ms | 108.7 ms | 269 MB |
| PDF, 14 clients | 5,085 | 43.5 | 131.8 ms | 154.4 ms | 472 MB |
| PNG (zip), 14 clients | 3,995 | 38.1 | 162.0 ms | 188.1 ms | 473 MB |
| Job queue (all jobs submitted at once, latency incl. wait time) | |||||
| 256 × JSON 1k → ZPL (153 jobs/s) | 153,438 | 49.6 | 98.8 ms | 365.5 ms | 885 MB |
| 64 × DB 10k → ZPL (18.6 jobs/s) | 186,361 | 60.3 | 2,236.5 ms | 3,411.1 ms | 828 MB |
| 64 × JSON 50 → PDF (104 jobs/s) | 5,396 | 46.2 | 345.2 ms | 445.6 ms | 467 MB |
ZPL generation saturates at 199,000 labels/s (from 4 parallel requests); more clients only add latency. Rendering costs 40× more and scales with the cores.
Render + PNG
labels/s · factor vs. 1 threadBefore (light bar): 499 → 1,826/s, at most 4.5× at 8 threads.
Text only
labels/s · factor vs. 1 threadPreviously, text got slower with more threads (0.6× at 8 threads) because Skia's glyph cache takes a global lock.
Measured with RenderBench -- 150 scale on the M3 Max. Glyphs are stored once per font and size in a lock-free cache, eliminating SkiaSharp's global locks.
Per label, 100 × 60 mm at 203 dpi. Grayscale or 1-bit PNG instead of RGBA, directly drawn ^GB boxes, lock-free glyph cache.
| Measurement | Before | Now | Improvement |
|---|---|---|---|
| Render to bitmap | 1.28 ms | 0.39 ms | −70% |
| Render + PNG | 8.19 ms | 1.86 ms | −77% |
| PNG size | 18 KB | 9 KB | −50% |
| 100,000 previews, sequential | 14.5 min | 3.4 min | −77% |
| 100,000 previews, parallel | 2.2 min | 25 s | −81% |
| Render + PNG, 10 threads | 1,826/s | 5,022/s | 2.8× |
| Text only, 8 threads | 3,162/s | 42,434/s | 13.4× |
| Workload | Labels | Memory |
|---|---|---|
| Soak test, small labels, parallel | 1,000,000 | RSS 128–138 MB · GC heap 2–3 MB |
| Soak test, 4 × 6″ PNGs, 14 threads | 1,000,000 | RSS 438–481 MB |
| ZPL streaming from objects | 100,000 | Heap 24 MB |
| Streaming from EF Core / SQLite into a ZPL file | 100,000 | Heap 29 MB |
| Streaming from LiteDB into a ZPL file | 100,000 | Heap 64 MB |
Methodology
Release build, .NET 10, Apple M3 Max with 10 performance and 4 efficiency cores. Every measurement can be reproduced with a single command.
# 100,000-label print job dotnet run -c Release --project samples/PerfTest -- 100000 # Thread scaling (label|text|barcode|box|empty) dotnet run -c Release --project samples/RenderBench -- 150 scale
Platform & cloud
Runs wherever your backend runs
Windows · Linux · macOS
A single .NET 10 assembly built on SkiaSharp and HarfBuzz. 1,656 automated tests pass on Windows 11, macOS and Linux. No printer driver and no GUI required.
Containers
Tested in the .NET images for Debian and Azure Linux 3.0, including the minimal distroless image. No extra system packages required; the NuGet package ships the native libraries.
Minimal API sample
ASP.NET Core sample with streaming endpoints, a bounded queue (Channel + BackgroundService) and parallel workers.
Data sources
Any data source that delivers C# objects
Data binding works with any IEnumerable<T>, IQueryable<T> or IAsyncEnumerable<T>. Filtering and sorting run in the database, and the labels are streamed. That means every EF Core provider and every database driver with LINQ support works.
Anything C# can read
via EF Core, official .NET drivers or your own objects
- Microsoft SQL Server
- Azure SQL
- PostgreSQL
- MySQL
- MariaDB
- Oracle
- SQLite
- MongoDB
- Azure Cosmos DB
- SAP HANA
- IBM Db2 / Informix
- Couchbase
- Firebird
- Amazon DynamoDB
- RavenDB
- Elasticsearch
- OpenSearch
- LiteDB
- Dapper / ADO.NET
- Entity Framework Core
- C# objects / LINQ
- JSON
Docker & Kubernetes
Dockerfile and Helm chart included
- All tests pass in the official .NET 10 container images for Debian and Azure Linux 3.0.
Dockerfile.serverincluded: distroless image, runs without root privileges, with a/healthendpoint.- Helm chart for Kubernetes included. You build the image yourself; it is not published to any public registry.
- No extra system packages required; the NuGet package ships the native libraries.
- Stateless per renderer: a web service scales horizontally.
- Asynchronous ASP.NET Core minimal API sample with a job queue (199,000 labels/s ZPL generation on a Mac).
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src COPY . . RUN dotnet publish samples/WebApiDemo -c Release -o /app FROM mcr.microsoft.com/dotnet/aspnet:10.0-azurelinux3.0-distroless WORKDIR /app COPY --from=build /app . ENTRYPOINT ["dotnet","WebApiDemo.dll"]
kind: Deployment spec: replicas: 2 template: spec: containers: - name: label-api resources: requests: { cpu: 500m, memory: 512Mi } limits: { cpu: "1", memory: 1Gi } readinessProbe: httpGet: { path: /health, port: 8080 } --- kind: HorizontalPodAutoscaler spec: metrics: - type: Resource resource: name: cpu target: { type: Utilization, averageUtilization: 70 }
World scripts
Arabic, Chinese, Hindi, Korean, Japanese, Russian: shaped correctly
Load a TrueType font into printer memory or print it as graphics. These outputs come straight from the engine.










Code
Three lines to a preview
var renderer = new ZplRenderer(new ZplRenderSettings { Dpi = 203 }.WithLabelSizeMm(100, 150)); List<byte[]> pngs = renderer.ProcessCommands( "^XA^FO50,50^A0N,40,40^FDHello World^FS^BY2^FO50,120^BCN,80^FD12345^FS^XZ"); renderer.Settings.OutputFormat = ZplOutputFormat.Pdf; File.WriteAllBytes("preview.pdf", renderer.ProcessFile("labels.zpl")[0]);
using ZplCore.Labels; var job = PrintJobTemplate.FromFiles("templates/leading.json", "templates/shipping.json", "templates/trailing.json"); job.JobName = "Tour 7"; job.KeyField = "TrackingNo"; // first/last key on the trailing label job.CutAfterLastLabel = true; // cut only after the last label job.StoredFormatName = "R:SHIP.ZPL"; // template once, then field values only string zpl = job.For<Shipment>().ToZpl(db.Shipments.Where(s => !s.Printed).Take(50).ToList()); File.WriteAllText("job.zpl", zpl); // 52 labels in ONE file
# samples/WebApiDemo – ASP.NET Core Minimal API dotnet run -c Release --project samples/WebApiDemo # Stream 50,000 shipments from EF Core/SQLite as ZPL curl 'localhost:5080/api/jobs/db/zpl?country=AT&take=50000' -o at.zpl # PDF preview from JSON records curl -X POST 'localhost:5080/api/jobs/preview?format=pdf' -H 'Content-Type: application/json' -d @records.json -o preview.pdf # Asynchronous via the queue: 202 Accepted, 429 when full curl -X POST localhost:5080/api/queue -H 'Content-Type: application/json' -d '{"db":{"country":"AT","take":20000},"output":"zpl"}'
var epl = new EplRenderer(new ZplRenderSettings { Dpi = 203 }); List<byte[]> labels = epl.ProcessFile("label.epl"); string zpl = epl.ToZpl(File.ReadAllText("label.epl")); // migrate EPL forms to ZPL var bc = new ZplCore.Barcodes.Barcode { Symbology = ZplCore.Barcodes.Symbology.Gs1_128, Code = "(01)09521234543213(10)LOT42", Unit = ZplCore.Barcodes.BarcodeUnit.Millimeter, BarWidth = 0.33, BarHeight = 20, }; File.WriteAllText("gs1.svg", bc.ToSvg());
Pricing
Perpetual license, no runtime fees
Developer
per developer, one-time
- internal use
- unlimited own servers & containers
- 12 months of updates
- email support (2 business days)
Team
up to 5 developers, one-time
- everything in Developer
- support within 1 business day
- 12 months of updates
OEM / SaaS
up to 5 developers, one-time
- royalty-free redistribution in your products
- operation as a SaaS / server service
- 12 months of updates + support
Enterprise
on request
- unlimited developers
- OEM included
- custom EULA, priority support
Prices are net, plus VAT. Renewal of updates & support: 40% of the list price per year. Without renewal, you may keep using the last version you received indefinitely. An annual subscription is available on request.
FAQ
Frequently asked questions
How is it licensed?
Per developer who writes code with zplCore.com. Production, test and build servers do not count as developers. The license is perpetual and includes 12 months of updates.
Can I redistribute zplCore.com royalty-free?
Yes, from the OEM / SaaS license upward, within your own applications and services. Reselling it as a standalone developer SDK is excluded.
What does support look like?
Email support directly from the development team. Response time depends on the license tier. Bug fixes ship with the regular updates.
Does zplCore.com contain code from other vendors?
zplCore.com is an independent implementation based on the public ZPL II specification and the symbology standards. It contains no code from other commercial vendors. Open-source components it uses (e.g. from BWIPP/bwip-js and zint, under MIT and BSD licenses respectively) are documented in THIRD-PARTY-NOTICES.md.
What can't zplCore.com do (yet)?
Pharmacode 2D, Response Plus PostMatrix and HIBC per ISO/IEC 15434 are not implemented, as no public specification exists for them. Deutsche Post BZL and JABCode are experimental. The proprietary resident Zebra fonts are replaced by DejaVu. If you need identical metrics, register the original TTF. QR mask selection differs from Zebra's output, but the codes remain readable. Label templates are available as JSON only.
Can I try it before buying?
Yes. Write to us and we will send you a trial version. We are also happy to check your own ZPL/EPL files.
Let's talk about your labels
Trial version, quote or migration check: just send us a short message.