A quick script for Avery 5160 labels

An occasional theme of these blog posts is the value of scripting as a way of showing your computer who’s boss. If you can’t write scripts—and here I’m adopting a broad definition of scripts to include macros and other customizations—you become a slave to your computer, forced to do things the way it wants you to instead of the way that’s most natural and efficient for you.

Tonight I helped my wife and a friend who needed to print up a bunch of labels for the neighborhood swim team. The computer program the swim team uses to run the meets and keep track of times can print labels, but not the type of label they needed. What they needed was a label for each swimmer who swam faster than the “city time”

1 in one or more events this year. The labels were to be stuck to the backs of ribbons given out at tomorrow morning’s awards ceremony. (Oh yes, I forgot to mention: there was a looming deadline for these labels.)

What we had was a printed list of all the swimmers who were to get ribbons and the events in which they’d made city times. What we needed was to get that information printed on a set of Avery 5160 labels. Some division of labor was in order.

The list went to our daughter, the fastest non-professional typist I know. I asked her to retype the list of names and events into a text file with a simple format and email it to me. Yes, we could have transferred the file over the network here in the house, but it’s usually fastest to use the tools you’re most familiar with.

While she was typing, I pulled up my ancient Perl code for printing file folder labels and started modifying it. That script was written for Avery 5161 label sheets, which have two columns of ten labels each. The 5160 sheets have three columns of ten labels each, so the necessary changes were obvious:

These changes went smoothly, and after a few syntax errors—programming in Python has gotten me out of the habit of ending statements with a semicolon—the script was up and running. It takes a text file that looks like this:

2

#Irene Hartnett|2010
50 Free, 50 Back, 50 Breast
50 Fly, 100 IM

#Rosalina Reial|2010
50 Fly

#Darrin Schrick|2010
50 Back, 50 Fly

#Douglas Dunnam|2010
50 Free, 50 Back, 50 Breast
50 Fly, 100 IM

#Carmina Jaworsky|2010
50 Fly

#Elliott Oland|2010
50 Free, 50 Back, 50 Breast
50 Fly, 100 IM

#Salena Angel|2010
25 Back

#Elroy Tigert|2010
50 Free

#Robin Turiano|2010
50 Back

#Jolyn Mcclerkin|2010
50 Free, 50 Breast
50 Fly, 100 IM

#James Wolf|2010
50 Free, 50 Back, 50 Breast
50 Fly, 100 IM

#Renea Addison|2010
50 Free, 50 Back, 50 Breast
50 Fly, 100 IM

#Jesse Bautista|2010
25 Free, 50 Free, 25 Back
25 Breast, 25 Fly

#Gail Tanous|2010
50 Free, 50 Breast, 50 Fly

#Miguel Loarca|2010
25 Free, 50 Free, 25 Back
25 Breast, 25 Fly

and generates a PDF that looks like this:

I didn’t ask my daughter to type in the hash marks, vertical bars, or the year; I added those to the file with a couple of search-and-replace commands. I used the same formatting rules as my file folder label program:

Here’s the script that does it:

  1  #!/usr/bin/perl
  2  
  3  use Getopt::Std;
  4  
  5  # Usage/help message.
  6  $usage = <<USAGE;
  7  Usage: prlabels [options] [filename]
  8  Print file folder labels on Avery 5160 sheets
  9  
 10    -r m : start at row m (range: 1..10; default: 1)
 11    -c n : start at column n (range 1..3; 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 5160.
 26  $topmargin = 0.60;
 27  $poleft = 0.4;
 28  $pomiddle = 3.20;
 29  $poright = 5.95;
 30  $lheight = 1;
 31  
 32  # get starting point from command line if present
 33  getopts('hr:c:', \%opt);
 34  die $usage if ($opt{h});
 35  
 36  $row = int($opt{r}) || 1;    # chop off any fractional parts and
 37  $col = int($opt{c}) || 1;
 38  
 39  # Bail out if position options are out of bounds
 40  die $usage unless (($row >= 1 and $row <= 10) and 
 41                     ($col >= 1 and $col <= 3));
 42  
 43  # Set initial horizontal and vertical positions.
 44  if ($col == 1) {
 45    $po = $poleft;
 46  } elsif ($col == 2) {
 47    $po = $pomiddle;
 48  } else {
 49    $po = $poright;
 50  }
 51  $sp = ($topmargin + ($row - 1)*$lheight);
 52  
 53  # Pipe output through groff and ps2pdf.
 54  open OUT, "| groff | ps2pdf -";
 55  # open OUT, "> labels.rf";    # for debugging
 56  select OUT;
 57  
 58  # Set up document.
 59  print <<SETUP;
 60  .ps 11
 61  .vs 15
 62  .ll 2.20i
 63  .ta 2.20iR
 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  .ft HB
 74  %s
 75  .ft H
 76  .ce 3
 77  %s
 78  .ce 0
 79  ENTRY
 80  
 81  # Slurp all the input into an array of entries.
 82  $/ = "";
 83  @entries = <>;
 84  
 85  $bp = 0;                  # we don't want to start with a page break
 86  
 87  foreach $body (@entries) {
 88    # Parse and transform the header and body.
 89    if ($body =~ /^#/) {    # it's a header line
 90      ($header, $body) = split(/\n/, $body, 2);
 91      $header = substr($header, 1);
 92      $header =~ s/\|/\t/;
 93    }  
 94    $body =~ s/\s+$//;
 95  
 96    # Break page if we ran off the end.
 97    if ($bp) {
 98      print "\n.bp\n";      # issue the page break command
 99      $bp = 0;              # reset flag
100    }
101    
102    # Print the label.
103    printf $label, $sp, $po, $header, $body;
104    
105    # Now we set up for the next entry.
106    if ($col == 1){       # last entry was in the left column
107      $col = 2;             # so the next will be in
108      $po = $pomiddle;      # the middle column
109    } elsif ($col == 2) { # last entry was in the middle column
110      $col = 3;             # so the next will be in
111      $po = $poright;       # the right column
112    } else {              # last was in the right column
113      $col = 1;             # so the next will be in
114      $po = $poleft;        # the left column
115      $row++;               # of the next row
116      if ($row > 10) {      # we're at the end of the page
117        $bp = 1;            # page break flag
118        $row = 1;           # new page starts at top row
119      }
120      $sp = ($topmargin + ($row - 1)*$lheight);
121    }
122  }

I fed the input file into this script and sent the resulting PDF to the printer. Lots of labels in very little time.

More important, we now have a tool for doing this again and again. Maybe next year we’ll be able to save a step by getting the swim team’s program to spit out a text file instead of a printed list that has to be retyped.


  1. This is a somewhat arbitrary “wheat from chaff” separator. It’s called a city time because those who beat it get to compete in a city-wide meet at the end of the season. 

  2. By the way, if you ever need to create some fake names, I suggest The Name Generator