# 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:

• The logic needs to expand to accommodate three horizontal positions instead of two.
• The various margins need to be adjusted to reflect the narrower labels.

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

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:

• Blank lines separate labels.
• A vertical bar acts as a tab to a right-justified tab stop at the right margin.
• All other lines are centered.

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