#!/usr/bin/python # $Id: makelabels,v 1.11 2003/12/12 23:38:38 cjr Exp $ # makelabels - generate mailing labels (see --usage for details) # Copyright (c) 2002 Claude Rubinson # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # $Log: makelabels,v $ # Revision 1.11 2003/12/12 23:38:38 cjr # apparently I can't do basic algebra as the centimeters/points # conversion function was still wrong. Michael Procter provided the # correct equation. should be correct now. # # Revision 1.10 2003/11/11 23:51:27 cjr # updated website and email address # # Revision 1.9 2003/10/02 06:32:52 cjr # fixed bug in centimeters/points conversion as reported by Antti # Kaihola (thanks!) # # Revision 1.8 2002/11/30 04:00:46 claude # spell-checked # # Revision 1.7 2002/11/29 19:05:39 claude # revisions to documentation # # Revision 1.6 2002/11/26 02:41:10 claude # revisions to documentation # # Revision 1.5 2002/11/26 01:58:54 claude # added documentation discussing offset and indent parameters # # Revision 1.4 2002/11/26 01:00:47 claude # added error trapping to procedure for extracting label parameters from # rcfile # # Revision 1.3 2002/11/26 00:17:47 claude # changed param['v_offset'] to param['vert_offset'] and # param['h_offset'] to param['horiz_offset'] for consistency with # command-line parameters. for calculation of x,y coordinates, offsets # are now added (rather than subtracted) for consistency with # documentation. # # Revision 1.2 2002/11/25 20:04:12 claude # version information ('--version') is extracted from RCS revision # number # # Revision 1.1 2002/11/25 19:28:32 claude # Initial revision # import sys, os, string, getopt, re, ConfigParser def toPoint (value, units): # only the first character of unit is tested if units[0] == 'p': return float(value) if units[0] == 'i': return float(value) * 72 # 1 inch is 72 PostScript points if units[0] == 'c': return float(value) * (72/2.54) # 1 inch is 2.54 centimeters else: sys.stderr.write('%s: invalid unit of measurement\n' % progname) sys.exit() def usage (): print 'Usage: %s [OPTIONS]\nPrint mailing labels.' % progname print """ -h, --help\t\tDisplay this help and exit -v, --version\t\tOutput version information and exit Below options all require an argument: -s, --single\t\tInvoke single address mode: generate ARG copies\n\t\t\tof single record -y, --rcfile\t\tpath to configuration file (default is\n\t\t\t$HOME/.makelabelsrc) -a, --label_id\tId of pre-defined label specification -d, --field_delim\tField delimiter (default is newline) -D, --record_delim\tRecord delimiter (default is blank line) -r, --rows\t\tNumber of label rows per sheet -c, --columns\t\tNumber of label columns per sheet -u, --units\t\tUnit of measurement (inches, centimeters, or points) -w, --label_width\tWidth of label -e, --label_height\tHeight of label -l, --left_margin\tDistance from left edge of page to left edge\n\t\t\tof left-most label -b, --bottom_margin\tDistance from bottom of page to bottom edge of\n\t\t\tbottom-most label -t, --top_indent\tAmount to indent text from top edge of label -i, --left_indent\tAmount to indent text from left edge of label -j, --horiz_sep\tDistance between adjacent label columns -v, --vert_sep\tDistance between adjacent label rows -V, --vert_offset\tAmount of vertical adjustment to apply; positive\n\t\t\toffset shifts printing up; negative offset\n\t\t\tshifts printing down -H, --horiz_offset\tAmount of horizontal adjustment to apply; positive\n\t\t\toffset shifts printing right; negative offset\n\t\t\tshifts printing left -f, --font\t\tFont face (default is Courier) -z, --font_size\tFont size in points (default is 10) -p, --leading\t\tLine height (default is to calculate automatically) """ print """makelabels is a program for creating PostScript files for printing onto peel-off mailing label sheets. Similar programs already exist (see the PostScript::MailLabels module and LabelNation); however, I found that these programs were not quite flexible enough to meet my needs. In particular, this program supports printing text of an arbitrary length and includes facilities for specifying the positioning of the text to be printed (in order to accommodate labels with pre-printed company logos, for example). Parameters describing a sheet of mailing labels may be specified (a) on the command-line, (b) as a label specification within an external configuration file, or (c) both on the command-line and within an external configuration file. Content to be printed on the mailing labels is read from standard input. PostScript is output to standard output. Command-line parameters override label specification parameters. This is useful if you wish to, for example, use a different font face, apply an indent, or adjust the printing location by applying a horizontal or vertical offset. Labels parameters are described above. You should have received a default configuration file along with this program (if you didn't receive this file, you can download it from http://www.u.arizona.edu/~rubinson/hacks/). To preserve a label specification, add it to this file. Label specification format: \t[First_Label_ID] \tParameter_name: Value \tParameter_name: Value \tParameter_name: Value \t[Second_Label_ID] \tParameter_name: Value \tParameter_name: Value \tParameter_name: Value A label specification may include any number of parameters. Valid parameters (described above under 'Options') include: bottom_margin, columns, label_height, font, horiz_offset, left_indent, horiz_sep, left_margin, leading, rows, top_indent, units, vert_offset, vert_sep, label_width, and font_size. Content to be printed on the mailing labels (e.g., an address list) is read from standard input. By default, newlines separate fields and a blank line separates records. On output, each field is printed on a separate line and each record constitutes a separate label. Normally, the program will process each record read from standard input, generating a single label for each record. If you wish to generate multiple copies of a record (e.g., for return address labels), specify the '--single=NUM' option when invoking the program (where NUM is the number of labels that you wish to generate). The program will then only process the first record read from standard input, generating NUM labels. On offsets and indents: The '--horiz_offset' and '--vert_offset' options permit you to account for printer idiosyncrasies. For example, if you find that your text is printing too far to the left, applying a positive horizontal offset will correct this. The '--left_indent' and '--top_indent' have a slightly different purpose. These options allow you indent your text so as to better center the label text or account for any logos or text pre-printed on the labels. Note that this program is fairly dumb. For example, there's no validation of the label text to ensure that it will fit within the specified label parameters. If a line of text is too long, it will happily print across one label and onto the next one. Also, the program is incapable of manipulating the text in any manner (e.g., rotating, resizing, or centering). """ print """Bugs: If command-line parameters are used along with a label specification, both must use same unit of measurement. If the units of measurement differ (e.g., the label spec is in inches but the command-line parameters are in centimeters), you'll experience something similar to the crash of the Mars Orbiter (although probably not quite as expensive). Please report any additional bugs to . """ print """Examples: prompt$ makelabels -a avery5577 < address_list | lpr Generate labels for Avery mailing labels type 5577. Read addresses from address_list and pipe output directly to printer. prompt$ makelabels -a custom1 -V 1 < address_list > labels.ps Generate labels for custom label specification. Adjust vertical offset to accommodate printer quirk. Redirect output to file named 'labels.ps.' (Note that you'll probably always have to supply vertical and horizontal offsets--either on the command-line or within a label specification--in order to achieve proper placement of the labels.) prompt$ makelabels -a custom2 -s12 < myaddress Generate 12 return address labels of type custom2. Dump to standard output.""" # # INITIALIZATIONS # progname = sys.argv[0] # ...version number (extracted from RCS revision number) rcs = '$Id: makelabels,v 1.11 2003/12/12 23:38:38 cjr Exp $' regex = '.*,v\ ([0-9]+\.[0-9]+)\ .*' version = re.match(regex,rcs).group(1) # ...variable defaults record_delim = '\n\n' field_delim = '\n' rcfile = os.environ["HOME"] + '/.makelabelsrc' duplicates = None label_id = None # ...label parameter defaults param = { 'units': 'inches', 'bottom_margin': 0, 'left_margin': 0, 'label_vsep': 0, 'label_hsep': 0, 'left_indent': 0, 'top_indent': 0, 'vert_offset': 0, 'horiz_offset': 0, 'font': 'Courier', 'font_size': 10, } # # PROCESS COMMAND-LINE ARGUMENTS AND OPTIONS # # presence of 'help' or 'version' flag overrides any other options for opt in sys.argv[1:]: if opt in ('-h', '--help'): usage() sys.exit() elif opt in ('-v', '--version'): print 'makelabels', version print 'Copyright (C) 2002 Claude Rubinson' sys.exit() # command-line options override label parameter defaults short_opts = 'hva:b:c:d:D:e:f:H:i:j:l:p:r:s:t:u:V:v:w:y:z:' long_opts = ['help', 'version', 'label_id=', 'bottom_margin=', 'columns=', 'field_delim=', 'record_delim=', 'label_height=', 'font=', 'horiz_offset=', 'left_indent=', 'horiz_sep=', 'left_margin=', 'leading=', 'rows=', 'single=', 'top_indent=', 'units=', 'vert_offset=', 'vert_sep=', 'label_width=', 'rcfile=', 'font_size='] try: opts, args = getopt.getopt(sys.argv[1:], short_opts, long_opts) except getopt.GetoptError, value: # print usage information and exit sys.stderr.write('%s: %s\n' % (progname, value)) sys.exit() # if predefined label from rcfile is specified, get label_id and # rcfile before doing anything else for opt, arg in opts: if opt in ('-a', '--label_id'): label_id = arg if opt in ('-y', '--rcfile'): rcfile = arg # ...and extract label parameters from rcfile if label_id: try: cp = ConfigParser.ConfigParser() cp.read(rcfile) for key in cp.options(label_id): param[key]=cp.get(label_id,key) except ConfigParser.NoSectionError: sys.stderr.write('%s: No label_id: %s. \n' % (progname, label_id)) sys.exit() # process remaining command-line options for opt, arg in opts: if opt in ('-d', '--field_delim'): field_delim = arg if opt in ('-D', '--record_delim'): record_delim = arg if opt in ('-s', '--single'): duplicates = arg # options below override label_id parameters if opt in ('-u', '--units'): param['units'] = arg if opt in ('-c', '--columns'): param['columns'] = arg if opt in ('-r', '--rows'): param['rows'] = arg if opt in ('-e', '--label_height'): param['label_height'] = arg if opt in ('-w', '--label_width'): param['label_width'] = arg if opt in ('-b', '--bottom_margin'): param['bottom_margin'] = arg if opt in ('-l', '--left_margin'): param['left_margin'] = arg if opt in ('-v', '--vert_sep'): param['label_vsep'] = arg if opt in ('-j', '--horiz_sep'): param['label_hsep'] = arg if opt in ('-i', '--left_indent'): param['left_indent'] = arg if opt in ('-t', '--top_indent'): param['top_indent'] = arg if opt in ('-V', '--vert_offset'): param['vert_offset'] = arg if opt in ('-H', '--horiz_offset'): param['horiz_offset'] = arg if opt in ('-f', '--font'): param['font'] = arg if opt in ('-z', '--font_size'): param['font_size'] = arg if opt in ('-p', '--leading'): param['leading'] = arg # convert all measurement parameters to points try: param['label_height'] = toPoint(param['label_height'], param['units']) param['label_width'] = toPoint(param['label_width'], param['units']) param['bottom_margin'] = toPoint(param['bottom_margin'], param['units']) param['left_margin'] = toPoint(param['left_margin'], param['units']) param['label_vsep'] = toPoint(param['label_vsep'], param['units']) param['label_hsep'] = toPoint(param['label_hsep'], param['units']) param['left_indent'] = toPoint(param['left_indent'], param['units']) param['top_indent'] = toPoint(param['top_indent'], param['units']) param['vert_offset'] = toPoint(param['vert_offset'], param['units']) param['horiz_offset'] = toPoint(param['horiz_offset'], param['units']) except KeyError, value: sys.stderr.write('%s: Missing required parameter: %s\n' % (progname,value)) sys.exit() # if leading not explicitly set, calculate (according to # http://www.dtp-aus.com/typo/typset.htm, rule of thumb is leading # equals font_size + 20 percent) if not param.has_key('leading'): param['leading'] = float(param['font_size']) \ + (float(param['font_size']) * 0.2) # read in content of labels from standard input, escaping backslashes records = sys.stdin.read().replace('\\', '\\\\').split(record_delim) # if -s flag, just print first record number of specified times if duplicates: records = records[0:1] * int(duplicates) # # GENERATE MAILING LABELS # # header print '%!PS-Adobe-1.0' # font settings print '/%s findfont' % param['font'] print '%s scalefont' % param['font_size'] print 'setfont' # generate labels left-to-right, down-to-up. we don't know how many # labels there are, so just keep looping until we run out (see the # inner try-except block). try: label = 0 while 1: row = 0 while row <= int(param['rows']) - 1: column = 0 while column <= int(param['columns']) - 1: try: # get next record record = records[label] except IndexError: # no more records -- all done! if label % (int(param['rows']) * int(param['columns'])): # only if page was not ejected below print 'showpage' sys.exit() else: # generate label print '\n%% LABEL %s,%s' % (row, column), print '%' * 25 # ...calculate bottom indent param['bottom_indent'] = param['label_height'] \ - (param['top_indent'] \ + (param['leading'] \ * (string.count(record, field_delim)-1) ) ) # ...calculate x and y coordinates of line y = param['bottom_margin'] \ + (param['label_height'] * row) \ + param['bottom_indent'] \ + param['vert_offset'] x = param['left_margin'] \ + (param['label_width'] * column) \ + param['label_hsep'] \ + param['left_indent'] \ + param['horiz_offset'] # ...print out fields (need to reverse order of fields # or they'll come out upside down) fields = string.split(record, field_delim) fields.reverse() for field in fields: print '% begin field', '-' * 10 print 'newpath' print x, y, 'moveto' print '('+field+') show' y = y + float(param['leading']) label = label + 1 column = column + 1 row = row + 1 #if we get down here, we've filled up an entire page print 'showpage' except KeyError, value: sys.stderr.write('%s: Missing required parameter: %s\n' % (progname,value)) sys.exit()