Setting up a Printer Interface File
Larry Reznick
The printer interface file is a filter that receives
all the data
from the spooler and prepares it for the printer. The
interface is
conceptually simple. It sees all its input from a set
of files named
on its command line, and writes all its output to stdout,
expecting
that whatever goes to stdout will go to the printer.
The interface
file can be divided into three parts: preprocessing,
the data file
output, and postprocessing. The preprocessing stage
sets up any special
printer modes and may output a banner. The primary data
processing
not only handles the data output, but may also deal
with special embedded
codes in the data. The postprocessing handles cleanup,
including a
formfeed for the print job.
The interface file is a shell script. It does not have
to be, but
with the power of shell script programming, why not
take advantage
of something as easily maintained as shell script to
get the job done?
On SVR4 all of the interface files are located in the
/usr/spool/lp/admins/lp/model
directory. Your files may be in a slightly different
path depending
on which vendor's UNIX you use. For instance, /usr/spool
may
be /var/spool on SVR4, but /usr/spool is symbolically
linked to /var/spool for compatibility reasons. On SCO,
the
path is /usr/spool/lp/model. If you are not sure where
yours
is, try to find the "standard" interface file
by using:
find /usr/spool -name standard -print
This command should return the path where all the other
interface files are located.
Specifying the Interface
If you use lpadmin(1M) to set up a new printer, you
can specify
one of three interface options. The -m option is used
to specify
the model interface file; the -i option is used to install
a new interface file for a printer already in service;
and the -e
option copies the interface file from an existing printer
already
installed. These options are mutually exclusive. The
-m option
takes as an argument the interface file's name without
a path --
the file is retrieved from the model directory. The
-i option
wants the full path, while the -e option wants the name
of
the existing printer.
The simplest approach to setting up a new printer is
to find an existing
model that comes close to the definitions of your printer,
copy it,
and modify it to suit your needs. Why reinvent the wheel?
I have never
needed to create a file completely from scratch, since
there has always
been one to modify. If you cannot find a suitable model,
start with
the file named standard -- that is, the one with the
-o
options mentioned in the lp(1) man page: length, width,
lpi, cpi, and stty. The standard file always
provides these options, but you should be aware that
some of the other
interface files do not.
The standard file is a large example of a Bourne shell
script. The
file is a comprehensive example of what can be accomplished
with shell
script programming because it contains interrupt handling,
functions,
error handling, terminfo handling, and command-line
argument parsing.
After you've studied this file, you should be able to
do almost anything
with shell script.
Defining Options
In most cases, all you'll need to do is insert some
relevant options
specific to the printer being installed. The argument
handling mechanism
-- a function named parse and a comment showing the
command-line
arguments -- can be found about one-third of the way
through the
standard interface file. The arguments are generally
assigned to some
variables: the first three (request ID, user name, and
title) are
commonly used for the banner page; the fourth is the
number of copies
requested of the print job coming in; and the fifth
is the option
list combined into a single argument with spaces separating
each one.
After that come the names of the files to be printed,
each in a separate
argument.
Once the command-line arguments have been absorbed into
appropriately
named variables, the options variables in the fifth
argument are initialized.
Typically, the modifications made to the standard interface
file consist
of added options. There are three types of options:
simple options,
value-setting options, and list options. The simple
options, such
as
-o nobanner
use a single word to make something happen. The value
settings, such as
-o cpi=17
name a variable to be assigned a value. The list options,
such as
-o stty='a b c d e'
give multiple settings to a single command. If you are
adding your own and you want to be more specific, name
the variables
after the facility they represent -- e.g., "spacing,"
"quality,"
"font," or "nlq," "compressed,"
"italic."
The simple options usually come first, and are just
initialized to
"no" or "yes" as appropriate. Later
on, they are tested
by
if [ "no" = varname ]
syntax to determine whether other special operations
must be performed later to change the output format
on the printer.
The value-setting options come next, and they should
be initialized
to whatever default setting seems best. Value-setting
options usually
take one of two forms: either a specific name is given
or a number
is given. The name is used as an alias for a value,
such as "pica"
for 10 cpi and "elite" for 12. For options
of this kind, the
shell script case option simply checks to see if the
word has been
used and sets the corresponding value. However, if a
specific value,
such as "cpi=17", is given, the parse function
is used.
This function is a marvelous little regular expression
using the expr(1)
program and the colon (:) operator.
parse () {
echo "`expr"$1\" : \"^[^=]*=\(.*\)\"`"
}
The expr colon operator says that whatever is
on the left side of the colon (the value passed to parse)
will
be matched against the argument on the right side of
the colon. expr
returns the number of characters matched, but when the
\( and
\) operators are used, it returns the characters from
first
argument referred to by that part of the regular expression.
Before
looking at the regular expression, notice that echo's
argument
is quoted, so embedded quotation marks must be escaped
with backslashes.
Thus,"$1\" puts quotation marks around expr's first
argument, and there are similar quotation marks around
the regular
expression. If you ignore the quotation marks around
the regular expression,
what's left is:
^[^=]*=\(.*\)
which means, "find a string starting at the beginning
of the argument composed of zero or more of any characters
except
the = symbol, followed by an = symbol, followed by zero
or more of
any character." The value returned is the "zero
or more of
any character." So, if the argument in "$1"
is "cpi=17",
the result would be "17" since only the part
on the right
side of the = symbol would be returned by expr.
What you usually get in the value-setting part of the
option list
case is something like:
cpi=* )
cpi=`parse ${i}`
;;
where ${i} is the current option setting (the
entire "cpi=17" value) being given to the
parse function,
which will evaluate as the "$1" inside that
function. Thus,
a variable named cpi will be set to the value 17, which
the
parse function has extracted from the "cpi=17"
option string
of the command line. You could set up your own variables
in this case
operation, too. There are places later in the interface
file, where
actual initialization occurs, where these settings would
fit in.
Finally, there are the list options settings, such as
stty.
If you use these options, you must add them in two places.
You first
add them to the main case list, which uses the vertical
bar, '|',
to OR them all together. This qualifies them for further
parsing,
which occurs within a secondary case. This operation
seems a bit complicated,
but essentially, it separates the command name value
(such as "stty")
from the item list being assigned to it. The tests that
separate the
item list look for items of the following forms:
anything preceded by an = sign surrounded by apostrophes
anything preceded by an = sign and an apostrophe but
no closing apostrophe
anything preceded by an = sign whether or not there
are apostrophes
anything followed by an apostrophe
anything else.
After the command is separated from the item list, the
command name
is handled with another case and the item list is combined
with the
appropriate real command. Your own local option commands
would be
added to both of these cases.
How the Filter Works
These options are usually used to initialize the printer
before any
of the data actually goes to the printer. Generally,
the banner is
kept separate from any initialization performed on the
main print
job, so that all banners always look the same. The initialization
options that you insert are usually applied only to
the actual print
job data, and they apply to the entire print job. In
the standard
file, the best place to put these changes is right after
the second
appearance of the internal_lpset function call, which
comes
after the banner, but just before the print job files
are output.
If you use echo to output the codes, they will get to
the printer,
just as the banner output does. If more detail is needed,
another
filter program should be used.
The actual printing attaches each file by redirection
to the stdin
of a filter program specified for the printer. If you
have specified
a filter using the lpfilter(1M) command, its name will
be set
in a FILTER variable and the interface file will use
it. If there
is no filter, the interface file will use either a special
file named
/usr/spool/lp/bin/lp.cat or, if lp.cat does not exist,
the simple cat(1) program. Whichever of these is available
becomes the value of the FILTER variable. To do the
printing,
the data is passed through the filter to the printer.
If additional
processing on a file-by-file basis is required, the
filter program
must do it.
Unless -o nofilebreak was specified, a formfeed will
be output
when each print job is finished. The last print job
gets a formfeed
even if nofilebreak was specified. (Strictly speaking,
the
formfeed goes to the printer only if the printer's terminfo
definition
has a formfeed (FF) variable set.) The interface file
then waits for
the printer to finish receiving all the data and quits.
Conclusion
Modifying a copy of the printer interface file is the
simplest way
to add special features to the printer's handling of
entire print
jobs, such as printing an entire document in near-letter-quality
vs
draft mode or producing program listings in compressed
type to get
132-character lines on 80-column paper. Since these
options apply
to entire print jobs, the next step is to create a filter
program
that can process the special codes within a single document
that affect only portions of that document.
About the Author
Larry Reznick has been programming professionally since
1978. He is currently
working on systems programming in UNIX and DOS. He teaches
C language courses
at American River College in Sacramento. He can be reached
via email at:
rezbook!reznick@csusac.ecs.csus.ed.
|