Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
> ZPL-Renderer-JS is a wrapper of [Zebrash by IngridHQ](https://github.com/ingridhq/zebrash)
# ZPL-Renderer-JS (Fork)

# ZPL-Renderer-JS
Convert Zebra ZPL labels to PNG directly in the browser (or node) without the use of third party services like Labelary or labelzoom!, supports multiple labels with templates!
This is a **fork** of [zpl-renderer-js](https://github.com/Fabrizz/zpl-renderer-js), a wrapper of [Zebrash by IngridHQ](https://github.com/ingridhq/zebrash). Convert Zebra ZPL labels to PNG **in the browser** or in Node.js—without third-party services like Labelary or labelzoom!, with support for multiple labels and templates.

> [!IMPORTANT]
> **Barcode rendering fix** — This fork fixes upstream issues with **Code 128** barcodes (No-mode, ZPL escape `>5` for subset C). The fix is implemented **entirely in the WASM build** by vendoring a patched [Zebrash](https://github.com/ingridhq/zebrash) (`zebrash/zebrash-local/`). Previously, `>5` (switch to Code C) was mishandled: long digit sequences were encoded one character at a time instead of as digit pairs, making barcodes far too wide and causing overflow. With this fork, barcode width matches real Zebra hardware and Labelary. **Usable in the browser** — the WASM is inlined into the JS build; no external service or server is required.

[<img alt="Fabrizz Logo" src="./.github/bar-zpl.png" height="110px"/>](https://www.npmjs.com/package/zpl-renderer-js)
[<img alt="Fabrizz Logo" src="./.github/bar-xaviewer.png" height="110px"/>](https://xaviewer.fabriz.co/)
Expand All @@ -18,7 +20,7 @@ npm i zpl-renderer-js
```

## Usage
The NPM package includes `.umd`, `.esm`, and `.cjs` builds. You can find the raw WASM in the Github Releases.
The NPM package includes `.umd`, `.esm`, and `.cjs` builds. **Runs fully in the browser** — no server or external API needed; the WASM runtime is inlined. You can find the raw WASM in the Github Releases.
> In case of using the raw `WASM` you will need to load `src/wasm_exec.js` and create a wrapper for the function.

> [!WARNING]
Expand Down
10 changes: 7 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions zebrash/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ require (
golang.org/x/image v0.35.0 // indirect
golang.org/x/text v0.33.0 // indirect
)

replace github.com/ingridhq/zebrash v1.35.1 => ./zebrash-local
2 changes: 0 additions & 2 deletions zebrash/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ github.com/ingridhq/gg v1.4.2 h1:ijvQbwJ2pyEso1/ClZCUjFSLGEpA4mO77e0wy7+Rli0=
github.com/ingridhq/gg v1.4.2/go.mod h1:56t1jfA5aMagD1/0QD2VbOBdHRMzEH4uwwqgYGa5gU0=
github.com/ingridhq/maxicode v1.5.1 h1:18md2sXLo3GRda9KYzI5MEApDxTATACbVSrQQw3TqZs=
github.com/ingridhq/maxicode v1.5.1/go.mod h1:Eo4pUt4amoySDj8pPruzgKoNrbZS2bUzSYktceo13Zc=
github.com/ingridhq/zebrash v1.35.1 h1:114pOlJntBM/BrFHm4abxLruv5Kn+TSXxjw8K3aaXQY=
github.com/ingridhq/zebrash v1.35.1/go.mod h1:2lxhyuCmhz7i4TsGYAksWiY3SprXq2OXddqfaNCy8Cs=
golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
Expand Down
117 changes: 117 additions & 0 deletions zebrash/zebrash-local/drawer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package zebrash

import (
"fmt"
"io"
"math"

"github.com/ingridhq/gg"
"github.com/ingridhq/zebrash/drawers"
"github.com/ingridhq/zebrash/elements"
drawers_internal "github.com/ingridhq/zebrash/internal/drawers"
"github.com/ingridhq/zebrash/internal/images"
)

type reversePrintable interface {
IsReversePrint() bool
}

type Drawer struct {
elementDrawers []*drawers_internal.ElementDrawer
}

func NewDrawer() *Drawer {
return &Drawer{
elementDrawers: []*drawers_internal.ElementDrawer{
drawers_internal.NewGraphicBoxDrawer(),
drawers_internal.NewGraphicCircleDrawer(),
drawers_internal.NewGraphicFieldDrawer(),
drawers_internal.NewGraphicDiagonalLineDrawer(),
drawers_internal.NewTextFieldDrawer(),
drawers_internal.NewMaxicodeDrawer(),
drawers_internal.NewBarcode128Drawer(),
drawers_internal.NewBarcodeEan13Drawer(),
drawers_internal.NewBarcode2of5Drawer(),
drawers_internal.NewBarcode39Drawer(),
drawers_internal.NewBarcodePdf417Drawer(),
drawers_internal.NewBarcodeAztecDrawer(),
drawers_internal.NewBarcodeDatamatrixDrawer(),
drawers_internal.NewBarcodeQrDrawer(),
},
}
}

func (d *Drawer) DrawLabelAsPng(label elements.LabelInfo, output io.Writer, options drawers.DrawerOptions) error {
options = options.WithDefaults()
state := &drawers_internal.DrawerState{}

widthMm := options.LabelWidthMm
heightMm := options.LabelHeightMm
dpmm := options.Dpmm

labelWidth := int(math.Ceil(widthMm * float64(dpmm)))
imageWidth := labelWidth
if label.PrintWidth > 0 {
imageWidth = min(labelWidth, label.PrintWidth)
}

imageHeight := int(math.Ceil(heightMm * float64(dpmm)))

gCtx := gg.NewContext(imageWidth, imageHeight)
gCtx.SetColor(images.ColorWhite)
gCtx.Clear()

var gReversePrintBuff *gg.Context

for _, element := range label.Elements {
reversePrint := false

if el, ok := element.(reversePrintable); ok {
reversePrint = el.IsReversePrint()
}

gCtx2 := gCtx
if reversePrint {
if gReversePrintBuff == nil {
gReversePrintBuff = gg.NewContext(imageWidth, imageHeight)
} else if err := images.Zerofill(gReversePrintBuff.Image()); err != nil {
return fmt.Errorf("failed to clear reverse print buffer: %w", err)
}

gCtx2 = gReversePrintBuff
}

for _, drawer := range d.elementDrawers {
err := drawer.Draw(gCtx2, element, options, state)
if err != nil {
return fmt.Errorf("failed to draw zpl element: %w", err)
}
}

if reversePrint {
if err := images.ReversePrint(gCtx2.Image(), gCtx.Image()); err != nil {
return err
}
}
}

// If print width was less than label width
// or label was inverted
// Draw everything onto the new, wider image and center / rotate the content
invertLabel := (options.EnableInvertedLabels && label.Inverted)
if (imageWidth != labelWidth) || invertLabel {
imgCtx := gCtx
gCtx = gg.NewContext(labelWidth, imageHeight)
gCtx.SetColor(images.ColorWhite)
gCtx.Clear()

if invertLabel {
gCtx.Translate(float64(labelWidth), float64(imageHeight))
gCtx.Scale(-1, -1)
}

gCtx.DrawImage(imgCtx.Image(), (labelWidth-imageWidth)/2, 0)
}

return images.EncodeMonochrome(output, gCtx.Image())
}
29 changes: 29 additions & 0 deletions zebrash/zebrash-local/drawers/drawer_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package drawers

type DrawerOptions struct {
LabelWidthMm float64
LabelHeightMm float64
Dpmm int
// Render labels with inverted orientation upside-down
EnableInvertedLabels bool
}

func (d DrawerOptions) WithDefaults() DrawerOptions {
res := d

// by default produce 4x8 inches 203 dpi label

if res.LabelWidthMm == 0 {
res.LabelWidthMm = 101.6
}

if res.LabelHeightMm == 0 {
res.LabelHeightMm = 203.2
}

if res.Dpmm == 0 {
res.Dpmm = 8
}

return res
}
10 changes: 10 additions & 0 deletions zebrash/zebrash-local/elements/label_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package elements

type LabelInfo struct {
// Width of the label
PrintWidth int
// Inverted mode, which mirrors label content across a horizontal axis.
Inverted bool
// Label elements (barcodes, shapes, texts, etc)
Elements []any
}
11 changes: 11 additions & 0 deletions zebrash/zebrash-local/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/ingridhq/zebrash

go 1.25

require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/ingridhq/gg v1.4.2
github.com/ingridhq/maxicode v1.5.1
golang.org/x/image v0.35.0
golang.org/x/text v0.33.0
)
19 changes: 19 additions & 0 deletions zebrash/zebrash-local/internal/assets/assets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package assets

import (
_ "embed"
)

// Slightly modified HelveticaBoldCondensed with some added glyphs for Polish and Turkish
//
//go:embed fonts/HelveticaBoldCondensedCustom.ttf
var FontHelveticaBold []byte

//go:embed fonts/DejaVuSansMono.ttf
var FontDejavuSansMono []byte

//go:embed fonts/DejaVuSansMonoBold.ttf
var FontDejavuSansMonoBold []byte

//go:embed fonts/ZplGSCustom.ttf
var FontZplGS []byte
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
38 changes: 38 additions & 0 deletions zebrash/zebrash-local/internal/barcodes/aztec/azteccode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package aztec

import (
"image"
"image/color"

"github.com/ingridhq/zebrash/internal/barcodes/utils"
"github.com/ingridhq/zebrash/internal/images"
)

type aztecCode struct {
*utils.BitList
size int
content []byte
}

func newAztecCode(size int) *aztecCode {
return &aztecCode{utils.NewBitList(size * size), size, nil}
}

func (c *aztecCode) ColorModel() color.Model {
return color.RGBAModel
}

func (c *aztecCode) Bounds() image.Rectangle {
return image.Rect(0, 0, c.size, c.size)
}

func (c *aztecCode) At(x, y int) color.Color {
if c.GetBit(x*c.size + y) {
return images.ColorBlack
}
return images.ColorTransparent
}

func (c *aztecCode) set(x, y int) {
c.SetBit(x*c.size+y, true)
}
Loading