Producing stickers and QR codes on a label printer with Perl
Labels bring order to the mess of wires hiding behind the Perlmeister's home routers and organize the treasures hoarded in a multitude of boxes. With just some tweaking, the Dymo LabelWriter even prints on Linux.
For decades, I have organized my network cables with permanent labels (Figure 1) printed on a portable device by Brother. However, it bugs me that every time I print a label the device wastes raw material (Figure 2), which I have to buy in the form of fairly expensive cartridges.
Brother's engineers deliberately seem to have built the machine to use twice as much label ribbon as I actually need, boosting the ribbon cartridge turnover as a side effect! If there is a hell for committing such wanton waste, I expect that the product managers responsible for this feature will be there some time soon. Apart from this, typing strings like 192.168.0.1 takes ages on the unorthodox keyboard; using a desktop computer would be far quicker.
Faster than Manual
Recently, I found a label printer on eBay that I was able to connect with my Ubuntu desktop via a USB port. The LabelWriter 450 Turbo by Dymo (Figure 3) cost me around $40 secondhand, and I got it working in no time. What you need is the printer's CUPS system driver, which is available as source code .
After installing a couple of additional packages, such as
apt-get, I was able to complete the build using
./configure; make without any trouble. Then,
sudo make install installed the CUPS files. It wasn't until later that I discovered that my Ubuntu distribution has an easy-to-install package and that
sudo apt-get install printer-driver-dymo
would do the job in one fell swoop.
After installing the CUPS drivers, Ubuntu detected the new label printer without much ado. A call to
lpstat -p -d
shows the available printers and their states (e.g., printer LabelWriter-450-Turbo is idle). If you are interested in the label sizes the driver supports, you can type
lpoptions -p LabelWriter-450-Turbo -l
to query them. Below Printers in the system settings, you should see a dialog like that in Figure 4 with a Printer Options submenu, in which you can configure the dimensions of the labels you are using.
I bought a roll of labels numbered "30330" featuring 500 return address labels. I wasn't really worried about what professional shippers use these labels for; it was the handy 19x51mm format that interested me.
The labels are shot vertically out of the thermo-printer, so I needed to adjust the lettering to match; in other words, rotated through 90 degrees in landscape. Now, how does the computer actually send the text to be printed to the device?
The CUPS printing system  uses the
lpr command for this:
lpr -P LabelWriter-450-Turbo -o PageSize=w54h144.1 label.pdf
As you can see, the option
-P LabelWriter-450-Turbo tells CUPS to select the label printer if some other device is configured as the default printer. The label dimensions are set using the
PageSize option, which expects the length and width – not in millimeters or inches, but in points.
Some brief research on Google showed that you need to multiply millimeters by around 1.8 to convert to points (Figure 5). To match the 19x51mm labels, I thus needed rectangles in a format of 54x144.1 points; the parameter I used –
PageSize= w54h144.1 – defined precisely that size.
The final argument the
lpr command expects is the name of the PDF file with the text to be printed.
Formatting as PDF
and dumps it into the middle of a PDF document in landscape format measuring 144.1x54 points. Note that the text needs to be quoted if it contains blanks. Some shell dialects also expect you to quote the exclamation marks, which the shell would otherwise interpret as a call to its history function. Looking at the code, the reasoning behind the expression
20 / length( $string ) * 11;
in line 16 of Listing 1 is that my experiments revealed that a string of eleven 20-point characters would precisely fill out the label lengthwise. Longer strings necessitate a linear font size reduction to fit on the label. This simple formula works amazingly well even though the Helvetica font I chose does not use fixed-width spacing but assigns a proportional amount of space to each letter in the document.
01 #!/usr/local/bin/perl -w 02 use strict; 03 use PDF::Create; 04 use Encode qw( _utf8_on ); 05 06 my( $string ) = @ARGV; 07 _utf8_on( $string ); 08 09 die "usage: $0 string" if !defined $string; 10 11 my $pdf = PDF::Create->new( 12 filename => 'label.pdf' ); 13 14 my $width = 144; 15 my $height = 54; 16 my $font_size = 20 / length( $string ) * 11; 17 my $adjust = $font_size/7; 18 19 my $page = $pdf->new_page( 20 'MediaBox' => [ 0, 0, $width, $height ] ); 21 22 my $f1 = $pdf->font( 23 Subtype => 'Type1', 24 Encoding => 'WinAnsiEncoding', 25 BaseFont => 'Helvetica' 26 ); 27 28 $page->stringc( $f1, 29 $font_size, 30 $width/2 + 10, 31 ($height - $font_size)/2 + $adjust, 32 $string ); 33 34 $pdf->close; 35 36 system( "acroread label.pdf" );
stringc() method called in line 28 prints the string passed in as its fifth argument in the middle of the PDF document. The font size is the second parameter, followed by the x and y coordinates of the center of the label. The x value runs from left to right in the document, and the y value from the bottom to the top edge. For some strange reason, the text string was always too low in the document in my experiments; this prompted me to introduce the
$adjust variable in line 17 to move the string upward by one seventh of the font size.
Thanks to these simple tricks, the label printer positions both short and slightly longer lines of text with up to 25 characters perfectly at the center of the document; the call to
cmd-print fires up the printer, and the label comes flying out.
The process for handling non-standard characters, like umlauts, is something special. Because the string passed in at the command line is already UTF-8 encoded, but Perl interprets it as ASCII by default, you would normally see the printer output cryptic characters rather than what you wanted if left to its own devices. The
_utf8_on function from the Encode module tells the interpreter that the string you passed in is already UTF-8 encoded and that there is thus no need to recode.
The CPAN module for creating PDFs can do more than just position strings on a white background; it can also position images. Of course, a thermal printer that can only print black without grayscale limits your artistic options somewhat, but what about printing sticky labels with barcode?
You can scan QR codes with a smartphone, opening up the option of assigning inventory numbers to devices printed as QR codes on labels that you could then scan and, if needed, store next to an asset number in a database or a filing system like Evernote, say, for a scanned owner's manual.
Listing 2 processes the text passed in at the command line,
dymo-qr "Linux Pro Magazine!"
and uses the CPAN Image::PNG::QRCode module to generate the QR code shown in Figure 6. The free QR Reader iPhone scanner app detected the QR code on the label still in the printer, even without me tearing off the label. Figure 7 shows the plain text version in the app.
Because the CPAN module I used to generate the QR code can only output PNG-formatted images, Listing 2 uses a clone of the CPAN Image::Magick module as an easy option for converting image formats. Installing the module involves some manual attention because it needs a number of developer packages from various libraries. Fortunately, a kind person from Ubuntu went through the trouble of generating a
perlmagick package that provides the feature set with the Graphics::Magick module.
01 #!/usr/local/bin/perl -w 02 use strict; 03 use Image::PNG::QRCode 'qrpng'; 04 use Graphics::Magick; 05 use File::Temp qw( tempfile ); 06 use PDF::Create; 07 08 my( $text ) = @ARGV; 09 die "usage: $0 text" if !defined $text; 10 11 my $width = 144; 12 my $height = 54; 13 my $pdffile = "label.pdf"; 14 15 my( $fh, $pngfile ) = 16 tempfile( UNLINK => 1, 17 SUFFIX => ".png" ); 18 19 ( my $jpgfile = $pngfile ) =~ 20 s/\.png$/.jpg/; 21 22 qrpng ( text => $text, 23 out => $pngfile ); 24 25 my $img = Graphics::Magick->new; 26 $img->Read( $pngfile ); 27 $img->Write( $jpgfile ); 28 29 my $pdf = PDF::Create->new( 30 filename => $pdffile ); 31 32 my $jpg = $pdf->image( $jpgfile ); 33 34 my $page = $pdf->new_page( 35 'MediaBox' => [ 0, 0, $width, $height ] ); 36 37 $page->image( 'image' => $jpg, 38 'xscale' => 0.5, 'yscale' => 0.5, 39 'xpos' => 50, 'ypos' => 0 ); 40 41 $pdf->close;
The module converts the temporary PNG file managed using the File::Temp module into a JPG file, which is then picked up by PDF::Create and dumped into a PDF document. The
label.pdf file that this process creates is then forwarded to the label printer with the previously shown
The only disadvantage with my label printer is that you can't buy rugged plastic labels for it. It only does black thermal printing on white paper or transparent film, both of which are self-adhesive. That said, however, the more expensive printer model called LabelWriter Duo, seems to have more options. I might just go bargain hunting again in the near future.
- Linux SDK for the label printer: http://var.dymo.com/US/resources/sdk/linux/
- Sweet, Michael. CUPS: Common UNIX Printing System. Sams Publishing, 2001.
- Code for this article: ftp://ftp.linux-magazine.com/pub/listings/magazine/183/
Buy this article as PDF
The bug was introduced back in 2009 and has been lurking around all this time.
The new release deprecates the sshd_config UsePrivilegeSeparation option.
Lives on as a community project
Five new systems join Dell XPS 13 Developer Edition that come with Ubuntu pre-installed.
The Skype Linux client now has almost the same capabilities that it enjoys on other platforms.
At CeBIT 2017, OpenStack Day will offer a wide range of lectures and discussions.
A major setback for the Linux desktop.
Improved support for GPU in virtualization.
News site for the openSUSE community falls victim to a Wordpress exploit.
The source code is available online.