Render ZPL II (Zebra Programming Language) label code to an Android Bitmap completely offline — no Labelary, no network calls, no internet permission. EasyZPLViewer is a pure-Kotlin ZPL viewer, renderer, parser, and preview library for Android that turns raw ZPL/ZPL2 shipping-label code into a crisp on-screen image and sends it to any Android print service — including networked Zebra thermal label printers. This repository is both a reusable library (easyzpl) and an open-source example app showing how to use it.
Keywords: ZPL, ZPL II, ZPL2, Zebra Programming Language, ZPL viewer, ZPL renderer, ZPL parser, ZPL to image, ZPL to PNG, ZPL to bitmap, ZPL preview, Android label printing, Zebra printer, thermal printer, shipping label, barcode generator, Code 128, Code 39, Data Matrix, Kotlin, Jetpack Compose, offline label rendering, Labelary alternative.
| Module | What it is |
|---|---|
easyzpl |
The Android library: ZplRenderer, a ZPL-to-Canvas renderer, and a Kotlin DSL (k2zpl) for building ZPL. Published via JitPack. |
app |
Example app: loads every ZPL file from assets/ (plain text, Base64, or ZIP), renders each label to a bitmap, and shows them in a swipeable pager — each page with a Print button. |
- 🖨️ Offline ZPL II rendering — convert Zebra ZPL/ZPL2 label code to an Android
Bitmapwith no network calls and no Labelary dependency. - 🔤 Text & fonts — scalable font
0and bitmap fontsA–H, withN/R/I/Brotation and^FWlandscape orientation. - 🧱 Graphics — graphic boxes (
^GB), ASCII-hex graphic fields (^GF), field reverse (^FR) for logo cut-outs, and field-block word wrap (^FB). - 📦 Barcodes — Code 128, Code 39, and a Data Matrix placeholder, plus human-readable interpretation lines.
- 🧩 Kotlin DSL — build ZPL programmatically with the type-safe
k2zpl { }builder instead of hand-writing command strings. - 🪶 Tiny & dependency-light — pure Kotlin,
minSdk 23, works great with Jetpack Compose or classic Views. - 🖼️ Print-ready — hand the rendered bitmap to Android's print framework to reach any Zebra or thermal label printer.
Real ZPL labels rendered on-device, fully offline by the example app's swipeable preview pager:
| Text & box label | Logo, Code 128 & address | Code 39 from a ZIP asset |
|---|---|---|
![]() |
![]() |
![]() |
sample_1 — text, fonts & rules |
sample_4 — ^FR logo cut-out + barcode |
sample_7.zip — Code 39 + ZIP loading |
- Features
- Screenshots
- Installation
- Usage
- Supported ZPL commands
- Running the example app
- Publishing on JitPack
- Contributing
- Keywords & related searches
- Credits & acknowledgements
- License
Step 1. Add JitPack to your repositories:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven("https://jitpack.io")
}
}Step 2. Add the dependency:
// build.gradle.kts (module)
dependencies {
implementation("com.github.umair13adil:EasyZPLViewer:1.2")
}Replace
v1.0with the latest release tag shown on the JitPack badge above.
ZplRenderer.renderZplToBitmap is a suspend function — rendering runs on Dispatchers.Default, so it never blocks the main thread:
import com.github.umair13adil.easyzpl.zpl.ZplRenderer
val renderer = ZplRenderer()
lifecycleScope.launch {
val bitmap: Bitmap? = renderer.renderZplToBitmap(
zplCode = """
^XA
^FO50,50^A0N,40,40^FDHello ZPL^FS
^FO50,120^GB400,3,3^FS
^BY3,3,90
^FO50,160^BCN,90,Y,N,N^FD123456789^FS
^XZ
""".trimIndent(),
dpmm = 8, // print density: 8 dpmm = 203 dpi
widthMm = 102, // 4 inches
heightMm = 152 // 6 inches
)
imageView.setImageBitmap(bitmap)
}| Parameter | Default | Meaning |
|---|---|---|
zplCode |
— | The ZPL II source (^XA … ^XZ) |
dpmm |
8 |
Dots per millimeter (6 = 152 dpi, 8 = 203 dpi, 12 = 300 dpi, 24 = 600 dpi) |
widthMm |
102 |
Label width in millimeters |
heightMm |
51 |
Label height in millimeters |
Returns null if the dimensions are invalid. The output is supersampled up to 4096 px so it stays crisp on high-density screens while remaining inside common GPU texture limits.
Tip: if your ZPL declares
^PW(print width, dots) and^LL(label length, dots), you can derive the size instead of hard-coding it — seeinferLabelSizeMmin the example app.
@Composable
fun ZplLabelImage(zpl: String) {
val renderer = remember { ZplRenderer() }
var bitmap by remember(zpl) { mutableStateOf<Bitmap?>(null) }
LaunchedEffect(zpl) {
bitmap = renderer.renderZplToBitmap(zpl, dpmm = 8, widthMm = 102, heightMm = 152)
}
bitmap?.let {
Image(
bitmap = it.asImageBitmap(),
contentDescription = null,
modifier = Modifier.fillMaxWidth().background(Color.White),
contentScale = ContentScale.FillWidth
)
}
}The example app's MainActivity extends this with a HorizontalPager, a loading state, and a print button per page.
You don't have to hand-write ZPL — the k2zpl { } builder generates it:
import com.github.umair13adil.easyzpl.zpl.k2zpl
val zpl: String = k2zpl {
startFormat() // ^XA
printWidth(812) // ^PW812
labelLength(1218) // ^LL1218
setDefaultFont(fontHeight = 40, fontWidth = 40)
field(x = 50, y = 50, data = "Intershipping, Inc.")
field(x = 50, y = 110, data = "1000 Shipping Lane")
line(x = 50, y = 170, width = 700, thickness = 3) // ^GB horizontal rule
fieldOrigin(x = 550, y = 220)
graphicBox(width = 200, height = 200, thickness = 4) // ^GB box at current origin
fieldSeparator()
barcode128(
data = "PKG-778899",
x = 50, y = 220,
height = 120,
interpretationLine = true // ^BC … with human-readable line
)
barcode39(
data = "ZIP001",
x = 50, y = 420,
height = 80,
interpretationLine = true // ^B3
)
endFormat() // ^XZ
}
// Render the built ZPL like any other ZPL string:
val bitmap = ZplRenderer().renderZplToBitmap(zpl)Available builder functions include startFormat() / endFormat(), field(...), font(...), fieldOrigin(...), fieldData(...), fieldBlock(...), fieldSeparator(), graphicBox(...), graphicField(...), line(...), barcode128(...), barcode39(...), labelHome(...), labelLength(...), printWidth(...), mediaMode(...), mediaType(...), and command("...") for any raw ZPL. Unit helpers Int.mm, Int.cm, Int.inches, Int.dots convert measurements once dpiSetting is set.
The example app ships ZplAssetLoader, which you can copy into your project. It scans assets/ and detects each file's format by content, not extension:
- Plain ZPL text — used as-is (
assets/sample_1…sample_5) - Base64-encoded ZPL — decoded transparently (
assets/sample_6_base64.txt) - ZIP archives — every entry is extracted; entries may themselves be plain or Base64 ZPL (
assets/sample_7.zip) - Files containing multiple
^XA … ^XZblocks produce one label per block
val labels: List<ZplLabel> = withContext(Dispatchers.IO) {
ZplAssetLoader.loadAll(context) // List<ZplLabel(name, zpl)>
}Drop your own ZPL files into app/src/main/assets/ and they appear in the pager automatically — no code changes needed.
Each pager page in the example app has a Print button backed by the Android print framework:
import androidx.print.PrintHelper
fun printLabel(context: Context, jobName: String, bitmap: Bitmap) {
PrintHelper(context).apply {
scaleMode = PrintHelper.SCALE_MODE_FIT
colorMode = PrintHelper.COLOR_MODE_MONOCHROME
}.printBitmap(jobName, bitmap)
}This opens the system print dialog, so the label can go to any print service the device knows — including networked Zebra printers via their print service plugin. (Requires androidx.print:print in your dependencies.)
| Command | Description |
|---|---|
^XA / ^XZ |
Label start / end |
^FO / ^FT |
Field origin (top-left) / field typeset (baseline) |
^A* |
Font selection with height, width, and orientation (N/R/I/B) |
^CF |
Change default font |
^FW |
Field orientation default — rotate every following field (landscape labels) |
^FD / ^FS |
Field data / field separator |
^FB |
Field block — word wrap, max lines, line spacing, L/C/R justification |
^FR |
Field reverse — invert the pixels under a field (logo cut-outs) |
^FH |
Field hexadecimal — decode _XX hex escapes in field data |
^GB |
Graphic box (outline or filled, black or white) |
^GF |
Graphic field — ASCII-hex bitmaps with run-length compression (G–Y, g–z, , ! :) |
^LH |
Label home offset |
^BY |
Barcode defaults (module width, ratio, height) |
^BC |
Code 128 (Subset B, with interpretation line) |
^B3 |
Code 39 (with interpretation line) |
^BD |
Data Matrix (rendered as a placeholder symbol) |
^FX |
Comment (ignored) |
^CI |
International character set (parsed, ignored) |
Unknown commands are skipped gracefully, so real-world labels render without erroring out.
git clone https://github.com/umair13adil/EasyZPLViewer.git
cd EasyZPLViewer
./gradlew :app:installDebugOn launch, the app renders every ZPL label found in app/src/main/assets/ into a swipeable preview pager. Other useful tasks:
./gradlew :app:assembleDebug # example app APK
./gradlew :easyzpl:assembleRelease # library AAR
./gradlew :easyzpl:publishToMavenLocalThe library is ready to publish from any fork:
- Push the repository to GitHub.
- Create a release tag, e.g.
1.0.0. - Open
https://jitpack.io/#<your-user>/<your-repo>and build the tag.
JitPack uses the included jitpack.yml (JDK 17) and the maven-publish setup in easyzpl/build.gradle.kts, which publishes the release AAR together with a sources jar and a full POM.
Contributions are welcome — bug reports, new ZPL command support, rendering fidelity fixes, docs, and sample labels all help. 🎉
- 🐛 Report a bug — open an issue and include the ZPL source, what you expected, what rendered, and ideally a screenshot.
- ✨ Add ZPL command support — implement an unsupported command in the renderer and add it to the Supported ZPL commands table.
- 🎯 Improve rendering accuracy — fonts, spacing, barcodes, and rotation are the usual suspects; compare against Labelary as a reference.
- 📖 Improve docs — fix typos, clarify usage, or add examples.
| Path | What lives here |
|---|---|
easyzpl/.../zpl/builder/ZplCanvasRenderer.kt |
The core ZPL-to-Canvas renderer (tokenizer + command dispatch + drawing). Most rendering changes go here. |
easyzpl/.../zpl/builder/ZplBuilder.kt |
The k2zpl { } Kotlin DSL for building ZPL. |
easyzpl/.../zpl/command/ |
One file per ZPL command used by the DSL. |
app/src/main/assets/ |
Sample ZPL labels shown in the example app's pager. |
- Fork the repository and create a branch:
git checkout -b feature/my-change. - Make your change. For renderer work, drop a sample label into
app/src/main/assets/so the behaviour is visible in the example app's pager. - Build and test:
./gradlew :easyzpl:assembleDebug # compile the library ./gradlew test # run unit tests ./gradlew :app:installDebug # run the example app to eyeball the result
- Match the existing style — Kotlin, descriptive names, and a short comment citing the ZPL command/spec behaviour for any new command.
- Commit with a clear message and open a pull request describing the change. Include before/after screenshots for any rendering change.
- Add a tokenizer/dispatch case for the command in
ZplCanvasRenderer.kt. - Track any new state on the renderer's
Statedata class, and reset it on^XAwhere appropriate. - Implement the drawing in a focused helper function.
- Document it in the Supported ZPL commands table.
- (Optional) Expose it in the
k2zplDSL by adding a builder function underzpl/command/.
By contributing, you agree that your contributions are licensed under the project's MIT License.
EasyZPLViewer helps you render, view, preview, parse, and print ZPL II labels on Android without an internet connection. If you searched for any of the following, this library is for you:
ZPL viewer Android · ZPL renderer Kotlin · ZPL to bitmap · ZPL to PNG/image · ZPL preview · ZPL parser · Zebra label printing Android · Zebra printer SDK alternative · print ZPL from Android · offline Labelary alternative · ZPL barcode rendering · Code 128 / Code 39 / Data Matrix in ZPL · thermal label printer · shipping label generator ·
^XA^XZ^FO^FD^GB^BCparser · Jetpack Compose label preview · ZPL II Kotlin DSL (k2zpl).
Tip for maintainers: add these as GitHub repository topics (Settings → Topics) for extra discoverability, e.g.
zpl,zpl2,zebra,zebra-printer,label-printing,barcode,thermal-printer,android,kotlin,jetpack-compose,labelary.
The k2zpl Kotlin DSL in this project was inspired by the excellent sainsburys-tech/k2zpl — a Kotlin DSL for generating ZPL. Huge thanks to the original authors and contributors for their work, which shaped the builder API here.
EasyZPLViewer extends that idea with an offline ZPL-to-Bitmap renderer and an Android example app. If you only need to generate ZPL on the JVM, do check out the original project.
MIT © Umair Adil



