Tools to work with ZPL GRF images and CUPS

pip install zplgrf==1.6.0


https://travis-ci.org/kylemacfarlane/zplgrf.svg?branch=master https://coveralls.io/repos/github/kylemacfarlane/zplgrf/badge.svg?branch=master

UPDATE: If you're only interested in the quality of barcodes when printing then first try using the new center of pixel options in cups-filters 1.11.4.


This project contains the utils I made to convert to and from GRF images from ZPL while trying to diagnose why barcodes printed from CUPS on Linux and OSX were so blurry/fuzzy/etc. It also contains my attempt at sharpening the barcodes and a CUPS filter you can setup to try for yourself.

Barcode Quality

The way CUPS (and at least my Windows driver too) prints PDFs to label printers is by converting the PDF to an image and then embedding that image in ZPL. The problem with this is that all PDF converters seem to aim to maintain font quality and don't care much about the quality of vectors. Often this create unscanable barcodes especially at the low 203 dpi of most label printers.

There are a couple of ways around this when generating your own PDFs (a barcode font, snap the barcode to round units, etc.) but they all have their downsides and don't help you if you have to print a label generated by someone else.

Originally I tried getting a greyscale image from Ghostscript and converting to mono while maintaining barcode quality but I always ended up destroying the font quality. I noticed that GRFs generated by Windows showed similar artifacting on text and a retail OSX driver says in their guide that their driver may also affect text quality so I think this kind of post processing is possibly what others are doing.

In the end I opted for getting a mono image from Ghostscript and then searching the image data to find any suspected barcodes and simply widening the white areas. It's very dumb and simple but works for me. You may need to tweak it for your own labels and there's a good chance it could actually make your barcodes worse.

UPDATE: I also added support for the center of pixel rule in Ghostscript when converting from PDFs. This improves barcode quality but also decreases the quality of some text.


Run pip install zplgrf.


Normal installation should handle regular Python dependencies but this project also requires Ghostscript (gs) to be installed.

I would recommend installing the newest version of Ghostscript. The tests fail due to slight rendering differences on old versions and the current center of pixel implementation isn't compatible with 9.22-9.26.

Using the Python API

Some quick demos.

Open a PDF, optimise the barcodes, and show the ZPL:

from zplgrf import GRF
with open('source.pdf', 'rb') as pdf:
    pages = GRF.from_pdf(pdf.read(), 'DEMO')
for grf in pages:

When converting from PDFs you will get better performance and barcodes by using Ghostscript's center of pixel rule instead of my optimise_barcodes() method:

from zplgrf import GRF
with open('source.pdf', 'rb') as pdf:
    pages = GRF.from_pdf(pdf.read(), 'DEMO', center_of_pixel=True)
for grf in pages:

To convert an image instead:

from zplgrf import GRF
with open('source.png', 'rb') as image:
    grf = GRF.from_image(image.read(), 'DEMO')
print(grf.to_zpl(compression=3, quantity=1)) # Some random options

If the ZPL won't print it's possible that your printer doesn't support ZB64 compressed images so try compression=2 instead.

Extract all GRFs from ZPL and save them as PNGs:

from zplgrf import GRF
with open('source.zpl', 'r') as zpl:
    grfs = GRF.from_zpl(zpl.read())
for i, grf in enumerate(grfs):
    grf.to_image().save('output-%s.png' % i, 'PNG')

Optimise all barcodes in a ZPL file:

from zplgrf import GRF
with open('source.zpl', 'r') as zpl:

Arguments for the various methods are documented in the source. Some such as to_zpl() and optimise_barcodes() have quite a few arguments that may need tweaking for your purposes.

Using the CUPS Filter

Install the package normally and then copy pdftozpl to your CUPS filter directory which is usually /usr/lib/cups/filter. Make sure that the copied file has the same permissions as the other filters in the folder.

Now edit the PPD file for your printer which is usually in /etc/cups/ppd. Find the lines containing *cupsFilter and add the following below them:

*cupsFilter2: "application/pdf application/octet-stream 50 pdftozpl"

Now restart CUPS and this new filter will take affect. Note that *cupsFilter2 filters require CUPS 1.5+ and they disable all regular *cupsFilter filters so you may need to setup more filters for other mimetypes.

application/octet-stream is the mimetype CUPS uses for raw printing which is what we want to send raw ZPL to the printer.


Performance of the CUPS filter is pretty bad in comparison to the native filters written in C. On a Raspberry Pi 3 it takes about 2.5s to run but is low 100s of ms on a decent computer.