Skip to content
GOODBOY008Public

About

Render ZPL, EPL, TSPL/TSC, CPCL and DPL labels to PNG/PDF, or encode images as ZPL/TSPL. Rust engine with CLI, HTTP, WebAssembly and Android support.

Topics

Resources

Stars

81 stars

Watchers

1 watching

Forks

Latest commit

ย 

History

253 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Labelize logo

Labelize

Parse and render ZPL / EPL / TSPL / CPCL / DPL labels to PNG & PDF โ€” fast, offline, open source.

CI Crates.io npm Docker License

๐ŸŒ Playground ยท ๐Ÿ“š Documentation ยท API console ยท ๐Ÿ“œ Changelog ยท ๐Ÿš€ Releases

The Labelize web playground rendering a shipping label

Labelize is a Rust engine that parses ZPL (Zebra Programming Language), EPL (Eltron Programming Language), TSPL/TSC, and CPCL (Zebra mobile printers), and DPL (Datamax-O'Neil) label data and renders it to PNG or PDF โ€” no printer hardware required. The same engine ships as a CLI tool, an HTTP microservice, a WebAssembly package, an Android library, and a plain Rust crate, sharing the same rendering engine across these surfaces. Match input bytes, dimensions and output options when comparing results.

A free public playground is hosted on Cloudflare Workers at https://labelize.764629910.workers.dev โ€” paste a label, see it render, download PNG or PDF. No install, no signup, no data leaving the browser tab beyond the render call.

The API tab lets you test requests, inspect responses and copy cURL, JavaScript or Python examples. See the public API guide for OpenAPI and scripts that convert a directory of labels through the API.

Labelize Studio

Labelize Studio (formerly Labelize Printer) is the standalone Rust + GPUI desktop app for Windows, macOS, and Linux. Design labels, open ZPL/EPL/TSPL/CPCL/DPL files in a multi-tab workspace, preview pages, and export PDF or PNG. It uses Labelize for printer-language parsing and rendering, and also provides the Labelize Printer virtual print queue; macOS/Linux system raster pages go directly to file output. Studio project ยท Installers and validation ยท Upgrade from Printer

โœจ Why Labelize?

Previewing thermal labels today usually means one of three compromises: send your label data to the Labelary web API (third-party service, data-privacy concerns, ~400 ms per render), pay for a commercial SDK (often hundreds of dollars), or keep a physical printer around just to see what would print. Labelize is the self-hosted alternative:

Labelize Labelary (web) Zebra Printer
Offline / self-hosted โœ… โŒ โœ…
No hardware needed โœ… โœ… โŒ
Open source โœ… โŒ โŒ
EPL + TSPL + CPCL + DPL support โœ… โŒ โœ… (EPL)
PDF output โœ… โŒ โŒ
Embeddable library โœ… โŒ โŒ
REST API โœ… โœ… โŒ
Cost Free Free / paid tiers Hardware cost

Features at a glance

  • ๐Ÿ–จ๏ธ 38+ ZPL commands โ€” text & fonts, 12 barcode symbologies (Code 128, EAN-13/8, UPC-A/E, Code 39, Interleaved 2-of-5, PDF417, Aztec, DataMatrix, QR, MaxiCode), boxes/circles/diagonals/ellipses, graphic fields, stored formats (^DF/^XF), label rotation & inversion
  • ๐Ÿท๏ธ EPL2 support โ€” text, the full 1D/2D barcode command set (B/b), lines, diagonals, boxes, binary graphics (GW)
  • ๐Ÿ“ฑ CPCL support โ€” Zebra mobile-printer sessions (! 0 203 203 โ€ฆ): resident fonts 0-7, SETMAG, justification, Code 128/39/EAN/UPC/I2of5, QR/PDF-417/MaxiCode/Aztec, BOX/LINE/INVERSE-LINE, counted EG/CG graphics
  • ๐Ÿญ DPL support โ€” Datamax-O'Neil formats (<STX>L โ€ฆ E): bitmap fonts 0-8 + smooth font 9, bottom-left home coordinates, Code 128/39/UCC/EAN/UPC/I2of5, QR/PDF-417/DataMatrix/MaxiCode/Aztec, X line/box records
  • ๐Ÿ–ผ๏ธ PNG & PDF output โ€” black/white by default, optional grayscale. Opt-in vector PDF and scalable PNG share one layout and preserve searchable text
  • ๐Ÿ”„ Reverse rendering (image โ†’ code) โ€” turn any raster (PNG/JPEG/GIF/BMP) into ZPL ^GFA or TSPL BITMAP printer code; pixel-faithful, round-trip tested
  • ๐Ÿ”ค Embedded fonts โ€” zero runtime font dependencies (Roboto Condensed, DejaVu Sans Mono, ZPL GS โ€” all permissively licensed)
  • โšก ~5 ms per render โ€” no network, no interpreter, no printer
  • ๐Ÿงช Golden-file E2E tests against reference renders on every push, including eight image-to-code cases

๐Ÿš€ Quick Start

Pick the surface that fits your stack โ€” every path below produces the same pixels.

Command line (macOS ยท Linux ยท Windows)

# Homebrew (macOS / Linux)
brew tap GOODBOY008/homebrew-labelize && brew install labelize

# Or from source / crates.io (requires a Rust toolchain)
cargo install labelize --features cli

# Windows: download labelize-x86_64-pc-windows-msvc.zip from Releases
labelize convert label.zpl                    # โ†’ label.png (format auto-detected)
labelize convert label.epl -t pdf             # EPL in, PDF out
labelize convert label.cpcl                    # CPCL in (Zebra mobile)
labelize convert label.dpl                     # DPL in (Datamax)
labelize convert label.zpl --width 100 --height 62 --dpmm 12

# Reverse rendering: image โ†’ printer code (one pixel = one dot)
labelize encode logo.png                       # โ†’ logo.zpl (ZPL ^GFA graphic field)
labelize encode logo.png -l tspl               # TSPL SIZE + BITMAP output
labelize encode logo.png --dpmm 12 --threshold 160

Docker

Pre-built multi-arch images (linux/amd64 + linux/arm64) on every release:

docker run -p 8080:8080 goodboy008/labelize:latest   # or ghcr.io/goodboy008/labelize

curl -X POST http://localhost:8080/convert \
  -H "Content-Type: application/zpl" \
  -d '^XA^FO50,50^A0N,40,40^FDHello Docker^FS^XZ' \
  -o label.png

# Reverse rendering: image in, ZPL out (add ?language=tspl for binary TSPL)
curl -X POST "http://localhost:8080/encode?dpmm=8" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @logo.png -o logo.zpl

Open http://localhost:8080/ for the interactive playground. Tags: latest, 1.6.0 / 1.6 / 1, edge (current main).

Environment variables

serve mode reads two optional flags; both default to true:

Variable Effect when false
LABELIZE_PLAYGROUND_ENABLED API-only container โ€” / returns an endpoint listing instead of the web UI
LABELIZE_PLAYGROUND_LABELARY_COMPARE Hides the playground's "Compare with Labelary" button, which POSTs the current ZPL to the external api.labelary.com service
# Privacy-sensitive deployment: keep label data in-house
docker run -p 8080:8080 \
  -e LABELIZE_PLAYGROUND_LABELARY_COMPARE=false \
  goodboy008/labelize:latest

Rendering itself (/convert, the CLI, the library) never makes outbound requests.

JavaScript / TypeScript

The engine compiled to WebAssembly โ€” renders in browsers, Node.js, and bundlers with no server:

npm install @goodboy008/labelize-wasm
import { lz_render } from "@goodboy008/labelize-wasm/init";   // Node โ‰ฅ 20

const zpl = Buffer.from("^XA^FO50,50^A0N,40,40^FDHello World^FS^XZ", "ascii");
const png = lz_render(zpl, 102.0, 152.0, 8, false, false, false); // Uint8Array

Bundlers (webpack 5, Vite + vite-plugin-wasm) import from the package root instead. For TSPL/TSC input, use the language-explicit variant:

import { lz_render_lang } from "@goodboy008/labelize-wasm/init";

const tspl = Buffer.from('SIZE 101.6 mm,152.4 mm\nCLS\nTEXT 40,40,"4",0,1,1,"Hello World"\nPRINT 1', "ascii");
const png = lz_render_lang(tspl, 101.6, 152.4, 8, false, false, "tspl");

Full API in the JavaScript tutorial.

Android

Self-contained AAR (com.goodboy008.labelize) with native libraries for arm64-v8a, armeabi-v7a, x86_64, x86 (minSdk 24) โ€” bit-identical output to the desktop builds. Download labelize-android-aar.zip from Releases:

// app/build.gradle.kts
repositories {
    flatDir { dirs("libs") }
}
dependencies {
    implementation(name = "labelize-android-release", ext = "aar")
}

// then, from Kotlin or Java:
val png = Labelize.renderZplToPng(zpl.toByteArray(), widthMm = 102.0, heightMm = 152.0)

More in the Android tutorial and android/README.md.

Rust

cargo add labelize
use std::io::Cursor;
use labelize::{ZplParser, Renderer, DrawerOptions};

let mut parser = ZplParser::new();
let labels = parser.parse(b"^XA^FO50,50^A0N,40,40^FDHello^FS^XZ")?;

let mut buf = Cursor::new(Vec::new());
Renderer::new().draw_label_as_png(&labels[0], &mut buf, DrawerOptions::default())?;
std::fs::write("label.png", buf.into_inner())?;

Reverse rendering runs the other way โ€” raster in, printer code out:

use labelize::{zpl_from_image_bytes, ReverseOptions};

let options = ReverseOptions::default(); // dpmm: 8, threshold: 128
let zpl = zpl_from_image_bytes(&png_bytes, &options)?; // or tspl_from_image_bytes
std::fs::write("label.zpl", zpl)?;

Image encoding applies EXIF orientation and accepts up to 16 Mi pixels, 32000 dots per dimension and 128 MiB of decoded data. Large ZPL images are split into graphic fields within Zebraโ€™s 99999-byte limit. Generated ZPL leaves the canvas size to the printer or render options; use image dimensions divided by dpmm in millimetres for an exact preview. TSPL declares its own size.

The reverse golden fixtures include committed ZPL/TSPL snapshots, independently checked Labelary PNGs, and visual comparisons. Both languages must reproduce the expected raster with zero pixel differences.

A complete runnable example lives at examples/render_label.rs (cargo run --example render_label -- label.zpl), with a walkthrough in the Rust tutorial.

๐ŸŽจ Web Playground

Every Labelize server serves an interactive UI at GET / โ€” the same page running at the public instance:

  • Paste ZPL/EPL/TSPL/CPCL/DPL or open a .zpl / .epl / .tspl / .cpcl / .dpl file, pick a label size (4ร—6, 4ร—4, โ€ฆ)
  • Image โ†’ Code โ€” turn a PNG/JPEG into ZPL printer code (loaded into the editor with a matching canvas) or download it as TSPL
  • Live preview with debounced auto-render, zoom fit / percent, dark & light themes, English / ็ฎ€ไฝ“ไธญๆ–‡
  • One-click PNG / PDF download, copy-PNG to clipboard, Ctrl+S
  • Compare with Labelary (ZPL) โ€” fetches the reference render and scores the diff on the same scale as CI
  • Shareable permalinks that encode the label + settings in the URL hash
labelize serve --port 8080     # or: docker run -p 8080:8080 goodboy008/labelize

๐Ÿ–ผ๏ธ Render Quality

Labelize is calibrated against reference renderers with per-label pixel-diff tolerance thresholds โ€” 151 real-world carrier and synthetic labels are compared on every push. ZPL/EPL labels are calibrated against Labelary (left = reference, right = Labelize); TSPL labels against the labelixa renderer with real-world captures (see docs/TSPL_BASELINES.md); CPCL labels against labelixa's CPCL renderer where it covers the commands, renderer baseline otherwise.

Label Diff Preview
amazon 1.08%
dhlpaket 1.48%
ups 2.98%
fedex 4.85%

151 labels tested โ€” 18 perfect ยท 63 good (<1%) ยท 62 minor (<5%) ยท 8 moderate (<15%) ยท 0 high

All side-by-side images: testdata/diffs/ ยท full reports: labels ยท unit ยท thresholds: docs/DIFF_THRESHOLDS.md

๐Ÿ“š Documentation

The documentation index organizes platform tutorials, output options, architecture, testing and renderer references. Start with a tutorial:

Guide For
Command Line Converting files from a shell
Docker Running the service in a container
HTTP Service REST API & playground integration
JavaScript / WebAssembly Client-side rendering
Android Rendering inside an app
Rust Library Embedding the engine in Rust

References: Usage ยท Vector output ยท ZPL commands ยท Testing ยท Architecture

๐Ÿ“– Reference

CLI reference
Usage: labelize <COMMAND>

Commands:
  convert  Convert a ZPL/EPL/TSPL/CPCL/DPL file to PNG or PDF
  serve    Start HTTP server for label conversion

Convert Options:
  <INPUT>               Input file path (.zpl, .epl, .tspl, .cpcl, or .dpl)
  -o, --output <PATH>   Output file path (default: input stem + .png/.pdf)
  -f, --format <FMT>    Input format override: zpl | epl | tspl | cpcl | dpl
  -t, --type <TYPE>     Output type: png | pdf [default: png]
  --width <MM>          Label width in mm [default: 102; TSPL: from SIZE, CPCL: from PAGE-WIDTH]
  --height <MM>         Label height in mm [default: 152; TSPL: from SIZE, CPCL: from header]
  --dpmm <N>            Dots per mm: 6, 8, 12, or 24 [default: 8]
  --antialias           8-bit grayscale output (default: 1-bit)

Serve Options:
  --host <HOST>         Bind address [default: 0.0.0.0]
  -p, --port <PORT>     Listen port [default: 8080]
HTTP API
Endpoint Method Description
/ GET Interactive web playground
/health GET Health check โ†’ {"status":"ok"}
/convert POST Convert label data โ†’ PNG or PDF

POST /convert โ€” the parser is selected by Content-Type (application/zpl / application/epl / application/tspl / application/cpcl / application/dpl); without one, the body is sniffed:

Parameter Default Description
width 102 Label width in mm
height 152 Label height in mm
dpmm 8 Dots per mm
output png Output format: png/pdf
antialias false Preserve antialiased greys instead of 1-bit

Status codes: 200 success (body is PNG/PDF bytes) ยท 400 parse error (bad label data) ยท 500 render error. The first label is rendered by default; mode=vector&output=pdf&pages=all combines all labels in one PDF.

Supported commands

ZPL

DataMatrix ECC 200 field-data escapes and firmware defaults are described in DataMatrix field data.

Category Commands
Text & Font ^FO ^FT ^FD ^FS ^A ^A@ (named font) ^CF ^CW (font identifier) ^FB ^FR ^FH ^FN ^FW ^FV
Barcodes ^BC (Code 128) ^BE (EAN-13) ^B8 (EAN-8) ^B9 (UPC-E) ^BU (UPC-A) ^B2 (Interleaved 2-of-5) ^B3 (Code 39) ^B7 (PDF417) ^BO (Aztec) ^BX (DataMatrix) ^BQ (QR Code) ^BD (MaxiCode) ^BY (defaults)
Graphics ^GB (box) ^GC (circle) ^GD (diagonal) ^GE (ellipse) ^GF (graphic field) ^GS (symbol) ~DG (download graphic) ^IL ^XG ^ID ^IM ^IS ~EG
Label Control ^XA ^XZ ^PW ^PO ^PM (persistent mirror image) ^LH ^LR ^LT (label top) ^LS (label shift) ^LL (label length) ^CI ^MU (units) ^PQ (print quantity) ^FX (comment) ^SN/^SF (serial state)
Stored Formats ^DF ^XF

DataMatrix rendering supports ECC 000, 050, 080, 100, 140 and 200. Omitted or empty ZPL ^BX quality defaults to ECC 000, as specified by Zebra; use ^BXN,4,200 for modern ECC 200. The Legacy path supports six encodation formats, CRC, convolutional protection, randomization and square symbols up to 49 modules. Legacy ZPL preserves raw field bytes and ^FH bytes, including stored-format recalls. Backslashes and pipes remain literal, matching the recorded CI13/CI27 printer probes; control bytes can be supplied through ^FH. Numeric records above 511 remain explicitly unsupported. They never silently become ECC 200. The raw Legacy encoder API accepts bytes. EPL DataMatrix continues to use ECC 200. See Legacy scope and evidence for the norm-based implementation, printer observations, limitations and source attribution. No independent overall validation is claimed.

EPL

N (new label) ยท A (text) ยท B (1D barcodes โ€” Code 128, Code 39, EAN-13/8, UPC-A/E, 2-of-5, Codabar, โ€ฆ) ยท b (2D: QR, DataMatrix, Aztec, PDF417, MaxiCode) ยท LO/LW (black/white line) ยท LS (diagonal) ยท X (box) ยท GW (graphic write) ยท R (reference point) ยท P (print)

TSPL / TSC

Category Commands
Setup SIZE (mm/dot/inch โ€” sets the label canvas) ยท DIRECTION ยท REFERENCE ยท SHIFT ยท CLS ยท GAP/DENSITY/SPEED/OFFSET/CODEPAGE (accepted, no render effect)
Text TEXT (resident fonts 1โ€“5, xmul/ymul magnification)
Barcodes BARCODE (Code 128/128M/EAN128/EAN128M, Code 39/39C, EAN-13, ITF14) ยท QRCODE (ECC defaults to M) ยท PDF417 (raw L<len>, incl. compound option tokens) ยท DMATRIX
Graphics BAR ยท BOX ยท CIRCLE ยท ELLIPSE ยท ERASE ยท REVERSE (print-time region inversion) ยท BITMAP (binary cursor; modes 0/1/2 = overwrite/OR/XOR)
Output PRINT

Unsupported TSPL commands (MPDF417, PUTPCX, PUTBMP, โ€ฆ) fail loudly with an explicit error rather than rendering wrong output. TSPL calibration baselines and open questions: docs/TSPL_BASELINES.md.

CPCL (Zebra mobile)

! {offset} {hdpi} {vdpi} {height} {qty} sessions with PRINT/END/ABORT terminators. Resident fonts 0-7 (manual Appendix-D cell metrics) with SETMAG magnification, SCALE-TEXT point sizing with ROTATE, ML multiline blocks, LEFT/CENTER/RIGHT justification, Code 128 / UCCEAN128 / 39 (+C, full ASCII) / UPC-A / UPC-E / EAN-13 / EAN-8 / I2of5 (+C) with BARCODE-TEXT HRI, QR / PDF-417 / MaxiCode (mode 2 tags) / Aztec, BOX, LINE, INVERSE-LINE, and counted EG/CG graphics. Canvas comes from the header height and PAGE-WIDTH. Real-provenance corpus and reference-renderer details: CPCL/DPL corpus.

DPL (Datamax-O'Neil)

<STX>L โ€ฆ E label formats with 15-byte record headers ({rotation}{type} {c}{d}{eee}{row}{col}โ€ฆ). Bitmap fonts 0-8 (Table C-2 cells) with base-25 multipliers, the smooth font 9 point table, scalable S/U IDs, Code 128 / 39 (+HIBC) / UCC-EAN 128 / UPC-A/E / EAN-13/8 / I2of5 (+mod-10) with the uppercase-HRI convention, QR (auto + manual payload forms), PDF-417, DataMatrix, MaxiCode (fixed-field form), Aztec, and X line/box records. Positions use the bottom-left home in hundredths of an inch (m = metric); DPL declares no in-band label size, so the canvas comes from the caller. Real-provenance corpus (manual Appendix B samples) and reference-renderer details: CPCL/DPL corpus.

Architecture
  ZPL / EPL / TSPL / CPCL / DPL input
       โ”‚
       โ–ผ
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚  Parser  โ”‚ โ”€โ”€โ–ถ โ”‚ Renderer โ”‚ โ”€โ”€โ–ถ โ”‚ Encoder โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚                โ”‚                โ”‚
   LabelInfo        RgbaImage       PNG / PDF
  • Parser (src/parsers/) โ€” tokenizes input, maintains VirtualPrinter state, produces typed LabelElements
  • Renderer (src/drawers/) โ€” rasterizes elements onto an RgbaImage canvas (text, graphics, barcodes, reverse print, label inversion)
  • Encoder (src/images/) โ€” emits eight-bit grayscale PNG (black/white by default) or a raster PDF; the optional scene backend emits searchable vector/hybrid PDF and scaled PNG

๐Ÿ› ๏ธ Development

cargo build --release                 # binary: target/release/labelize
cargo test                            # all tests (124 golden E2E + unit)
cargo test --test 'e2e_*'             # golden-file tests vs Labelary references
cargo test --test 'unit_*'            # unit tests
PATH="$PATH:target/debug" bash e2e/http/test_http.sh   # HTTP integration
PATH="$PATH:target/debug" bash e2e/cli/test_cli.sh     # CLI integration

After any rendering change, regenerate and commit the diff artifacts (cargo test --test e2e_diff_report -- --nocapture) โ€” CI enforces this via the golden-staleness check. See AGENTS.md for the full workflow.

Building a Windows binary without a Windows toolchain:

tools/build/build-windows.sh   # โ†’ target/windows-release/labelize.exe (via Docker + mingw-w64)

๐Ÿ’ก Use Cases

  • Shipping label preview โ€” see exactly what prints before it hits the printer
  • Warehouse management โ€” batch-convert label templates to PDF for archival
  • E-commerce integrations โ€” generate label PNGs on the fly from a microservice
  • Automated QA โ€” validate label content in CI/CD with golden-file tests
  • Label design tools โ€” add real-time ZPL/TSPL/CPCL/DPL preview with the library or wasm API

๐Ÿค Contributing

Contributions are welcome! Bug reports, new ZPL/EPL/TSPL/CPCL/DPL commands, rendering improvements, and docs fixes all count โ€” see AGENTS.md for the development workflow, then open an issue or submit a pull request.

๐Ÿ“„ License

MIT AND BSD-3-Clause โ€” see THIRD_PARTY_LICENSES.md for embedded assets.


Looking for a ZPL renderer, ZPL to PNG converter, ZPL to PDF, EPL parser, TSPL renderer, TSC label preview, CPCL converter, DPL preview, Zebra label preview, thermal label rendering, or a Labelary alternative? Labelize covers all of these.

About

Render ZPL, EPL, TSPL/TSC, CPCL and DPL labels to PNG/PDF, or encode images as ZPL/TSPL. Rust engine with CLI, HTTP, WebAssembly and Android support.

Topics

Resources

Stars

81 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages