Cover V02, I01
Article
Listing 1
Sidebar 1

jan93.tar


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.


     



  •