# More Avery labels

This week I had to create lots of small labels to attach to laboratory samples. To make this easier, I modified my file folder label script to handle the smaller Avery 5167 labels, the kind usually thought of as return address labels.

The new program, called ptlabels (“print tiny labels”), follows the same logic as the old one and uses the same command-line options. You can tell it which row and column to start on through the -r and -c options. The input format is also the same:

• Individual labels are separated by a blank line.
• Header lines, which get printed in bold, are designated by a leading hash mark.
• Header lines can have a left-justified and right-justified part; they’re separated by a vertical bar.
• Headers apply to all subsequent labels until a new header line is encountered.

As an example, this input

#Lorem project|1234
Sample 1

Sample 2

Sample 3

Sample 4

Sample 5

Sample 6

Sample 7

Sample 8

#Dolor project|9876
Sample 1

Sample 2

Sample 3

Sample 4


passed to

ptlist -r 3 -c 2


generates this output

As with the file folder label script, I find it easiest to run the script in TextMate via Filter Through Command… (⌥⌘R).

The script is in Perl, because that was my main language back when I wrote the original version. Rewriting in from scratch in Python would have been a waste of time.

  1:  #!/usr/bin/perl
2:
3:  use Getopt::Std;
4:
5:  # Usage/help message.
6:  $usage = <<USAGE; 7: Usage: ptlabels [options] [filename] 8: Print tiny labels on Avery 5167 sheets 9: 10: -r m : start at row m (range: 1..20; default: 1) 11: -c n : start at column n (range 1..4; default: 1) 12: -h : print this message 13: 14: If no filename is given, use STDIN. A label entry is a plain text 15: series of non-blank lines. Blank lines separate entries. 16: 17: The first line of an entry is special. If it starts with a #, then it's 18: considered a header line. Everything in the header line up to the | is 19: printed flush left in bold and everything after the | is printed flush 20: right in bold. Subsequent lines are printed centered in normal weight. 21: If the first line of an entry doesn't start with #, it uses the header 22: of the previous entry. 23: USAGE 24: 25: # Set up geometry constants for Avery 5167. 26:$topmargin = 0.55;
27:  $pocol[1] = 0.45; 28:$pocol[2]  = 2.50;
29:  $pocol[3] = 4.55; 30:$pocol[4]  = 6.60;
31:  $lheight = 0.50; 32: 33: # get starting point from command line if present 34: getopts('hr:c:', \%opt); 35: die$usage if ($opt{h}); 36: 37:$row = int($opt{r}) || 1; # chop off any fractional parts and 38:$col = int($opt{c}) || 1; 39: 40: # Bail out if position options are out of bounds 41: die$usage unless (($row >= 1 and$row <= 20) and
42:                     ($col >= 1 and$col <= 4));
43:
44:  # Set initial horizontal and vertical positions.
45:  $po =$pocol[$col]; 46:$sp = ($topmargin + ($row - 1)*$lheight); 47: 48: # Pipe output through groff to printer (manual feed). 49: open OUT, "| groff | lpr -o ManualFeed=True"; 50: # Change to PDF before sending to printer. 51: # open OUT, "| groff | ps2pdf - - | lpr -o ManualFeed=True"; 52: # Preview output instead of printing directly. 53: # open OUT, "| groff | ps2pdf - - | open -a /Applications/Preview.app"; 54: # Print raw troff code for debugging. 55: # open OUT, "> labels.rf"; 56: select OUT; 57: 58: # Set up document. 59: print <<SETUP; 60: .vs 12 61: .nf 62: .ll 1.50i 63: .ta 1.50iR 64: 65: SETUP 66: 67: # The troff code for formatting a single entry, with placeholders for 68: # positioning on the page. The magic numbers embedded in the formatting 69: # commands make the layout look nice. 70:$label = <<ENTRY;
71:  .sp |%.2fi
72:  .po %.2fi
73:  .ps 10
74:  .ft HB
75:  %s
76:  .ps 10
77:  .ft H
78:  .ce 2
79:  %s
80:  .ce 0
81:  ENTRY
82:
83:  # Slurp all the input into an array of entries.
84:  $/ = ""; 85: @entries = <>; 86: 87:$bp = 0;                  # we don't want to start with a page break
88:
89:  foreach $body (@entries) { 90: # Parse and transform the header and body. 91: if ($body =~ /^#/) {    # it's a header line
92:      ($header,$body) = split(/\n/, $body, 2); 93:$header = substr($header, 1); 94:$header =~ s/\|/\t/;
95:    }
96:    $body =~ s/\s+$//;
97:
98:    # Break page if we ran off the end.
99:    if ($bp) { 100: print "\n.bp\n"; # issue the page break command 101:$bp = 0;              # reset flag
102:    }
103:
104:    # Print the label.
105:    printf $label,$sp, $po,$header, $body; 106: 107: # Now we set up for the next entry. 108:$col = ($col % 4) + 1; # step to next column 109:$po = $pocol[$col];
110:    if ($col == 1) { # we just went down a row 111:$row++;
112:      if ($row > 20) { # we just went off the bottom 113:$bp = 1;                  # start a new page
114:        $row = 1; # at the top 115: } 116:$sp = ($topmargin + ($row - 1)*\$lheight);
117:    }
118:  }


The geometry constants in Lines 26-31 were initially set by making measurements of the labels and then adjusted through trial and error until the printing was nicely aligned with the die cuts on the label sheets. The final values are based not only on the sheet geometry, but also on how the labels pass through my printer via the manual feed slot. For good alignment on another printer, the values might need adjusting by a few hundredths.

Line 49 was also written with my default printer in mind. Because it’s a PostScript printer, I can take the PostScript output directly from groff and pipe it to lpr—no need to convert it first to PDF and no need to use lpr’s -P option to tell it which printer to use. (The -o option should be self-explanatory.)

Line 51 (commented out) is an example of what you may need to do if you don’t have a PostScript printer.

51:  # open OUT, "| groff | ps2pdf - - | lpr -o ManualFeed=True";


Ps2pdf is part of Ghostscript, an open source suite of PostScript utilities that doesn’t come with OS X, but which I find invaluable. The two hyphens after ps2pdf tell it to use standard input and output instead of files on disk.

A more Mac-like possibility is shown in Line 53:

53:  # open OUT, "| groff | ps2pdf - - | open -a /Applications/Preview.app";


This generates the PDF and opens it in Preview so you can see it before printing. I’m a wild and impetuous sort of guy, so I just send it off the printer and let the ink fall where it may.