Address labels
August 18th, 2005
One of the things I really like about Unix is that the “interconnected tools” cliche is a cliche because it’s true. And everyone who comes to Unix learns how great it is to hook programs together to make complex things easy and collapse repetitive sequences into a single task.
One not-so-complex but definitely repetitive thing that I need to do often is make labels: mostly address labels and file folder labels. I use sheet-fed Avery labels or their Maco clones. Since I seldom fill a full sheet at any one time, I need to be able to able to start the printing at any label position. I want a consistent layout for a professional look.
Back in my Macintosh days, I used a ClarisWorks (now AppleWorks) templates in the draw module. I’d fill in the text fields for the label positions I wanted to print and leave the others blank. There was a bit more back-and-forth between clicking and typing than I would have liked, but it worked pretty well.
When I moved to Linux, a similar way of working was out of the question—there were no Linux programs like ClarisWorks in 1997. But there was groff, the GNU version of troff, and there was Perl. I set about writing a program that would take a list of addresses (blank line delimited), format them into a nice set of labels, and shoot them off to the printer.
The result worked well, but I’m too embarrassed to show it. I was new to Perl and the program was unnecessarily complicated in how it kept track of input lines and how it handled command-line switches (it didn’t use either of the Getopt modules!). And the troff-creation code was very poorly formatted and hard to read. I recently rewrote it, putting it into the style I now use and adding a couple of new features. I’m somewhat less embarrassed to show it.
#!/usr/bin/perl
use Getopt::Std;
# Usage/help message.
$usage = <<USAGE;
Usage: palabels [options] [filename]
Print address labels on Avery 5164 sheets
-r m : start at row m (range: 1..3; default: 1)
-c n : start at column n (range 1..2; default: 1)
-h : print this message
If no filename is given, use STDIN. An address entry is a plain text
series of non-blank lines. Blank lines separate entries.
USAGE
# Set up geometry constants for Avery 5164 labels.
$topmargin = 0.75;
$poleft = 0.375;
$poright = 4.55;
$lheight = 3.333;
# Get starting point from command line if present.
getopts('hr:c:', \%opt);
die $usage if ($opt{h});
$row = int($opt{r}) || 1; # chop off any fractional parts and
$col = int($opt{c}) || 1;
# Bail out if position options are out of bounds
die $usage unless (($row >= 1 and $row <= 3) and
($col >= 1 and $col <= 2));
# Set initial horizontal and vertical positions.
if ($col == 1) {
$po = $poleft;
} else {
$po = $poright;
}
$sp = ($topmargin + ($row - 1)*$lheight);
# Pipe output of this program to groff and the printer (manual feed).
open OUT, "| groff -U | lpr -Plpm";
# open OUT, "> labels.rf"; # for debugging
select OUT;
# Set up document.
print <<"SETUP";
.ll 3.5i
.ft H
.ps 14
.vs 16
.nf
SETUP
# The troff code for formatting a single entry, with placeholders for
# positioning on the page. The magic numbers embedded in the formatting
# commands make the layout look nice.
$label = <<ENTRY;
.sp |%.2fi
.po %.2fi
.vs 0
.in 0i
.ti 0.1i
.PSPIC -L "/home/mark/graphics/logoaddr.eps" 2.5i
.sp .125i
.ps 24
\\l'3.5i'
.ps
.vs
.in .375i
.sp .375i
%s
ENTRY
# Slurp all the input into an array of entries.
$/ = "";
@entries = <>;
$bp = 0; # we don't want to start with a page break
foreach (@entries) {
# Break page if we ran off the end.
if ($bp) {
print "\n.bp\n"; # issue the page break command
$bp = 0; # reset flag
}
# Print the label.
s/\s+$//; # eliminate trailing whitespace
printf $label, $sp, $po, $_;
# Now we set up for the next entry.
if ($col == 1){ # last entry was in the left column
$col = 2; # so the next will be in
$po = $poright; # the right column
} else { # last was in the right column
$col = 1; # so the next will be in
$po = $poleft; # the left column
$row++; # of the next row
if ($row > 3) { # we're at the end of the page
$bp = 1; # page break flag
$row = 1; # new page starts at top row
}
$sp = ($topmargin + ($row - 1)*$lheight);
}
}
Avery 5164 labels are the big (3-1/3 by 4 inches) labels used for addressing boxes and 9 by 12 envelopes. There are six labels per page. I think the comments in the program explain it pretty well, but a few things might be worth mentioning:
The
.PSPICcommand in theENTRYhere-document is a groff-only macro for inserting EPS graphics into a page. Thelogoaddr.epsfile is, as you might expect, and EPS file with my company’s logo and return address. It goes at the top of every label.The
\\l'3.5i'command in the same block draws a horizontal line under the logo, separating it and the return address from the recipient’s address. The troff command uses only one backslash; the extra is needed to guard against Perl’s backslash interpretation. The thickness of the line is determined by the.ps 24command on the line above.I don’t like using flag variables, but the
$bpflag seemed like the easiest way to determine whether I needed to issue a page break command. The name of the variable comes from the troff command that creates a page break.The program is written to be able to take its input from a file or standard input. When I have just one label to make, I input the address straight from the command line, ending with a Control-D. If I have a bunch of labels to make, I can use an editor (with all of its correction abilities) to create a file of addresses. Most important, I can write other programs that look up or generate the addresses themselves and pipe the results into
palabels.The program pipes its output to
lpm, which is my normal printer with the manual feed option set. So I don’t have to have the label sheets loaded into the printer before giving apalabelscommand.If the input calls for more labels than can be printed on the sheet, a new page is asked for and the printing continues at the top of the new sheet.



