[an error occurred while processing this directive] FreeBSD Handbook : Printing : Advanced Printer Setup : Filters
Previous: Advanced Printer Setup
Next: Header Pages

7.6.1. Filters

Although LPD handles network protocols, queuing, access control, and other aspects of printing, most of the real work happens in the filters. Filters are programs that communicate with the printer and handle its device dependencies and special requirements. In the simple printer setup, we installed a plain text filter---an extremely simple one that should work with most printers (section Installing the Text Filter ).

However, in order to take advantage of format conversion, printer accounting, specific printer quirks, and so on, you should understand how filters work. It will ultimately be the filter's responsibility to handle these aspects. And the bad news is that most of the time you have to provide filters yourself. The good news is that many are generally available; when they're not, they're usually easy to write.

Also, FreeBSD comes with one, /usr/libexec/lpr/lpf, that works with many printers that can print plain text. (It handles backspacing and tabs in the file, and does accounting, but that's about all it does.) There are also several filters and filter components in the FreeBSD ports collection.

Here's what you'll find in this section:

7.6.1.1. How Filters Work

As mentioned before, a filter is an executable program started by LPD to handle the device-dependent part of communicating with the printer.

When LPD wants to print a file in a job, it starts a filter program. It sets the filter's standard input to the file to print, its standard output to the printer, and its standard error to the error logging file (specified in the lf capability in /etc/printcap, or /dev/console by default).

Which filter LPD starts and the filter's arguments depend on what's listed in the /etc/printcap file and what arguments the user specified for the job on the lpr command line. For example, if the user typed lpr -t, LPD would start the troff filter, listed in the tf capability for the destination printer. If the user wanted to print plain text, it would start the if filter (this is mostly true: see Output Filters for details).

There are three kinds filters you can specify in /etc/printcap:

Filters should also exit with the following exit status:

exit 0

If the filter printed the file successfully.

exit 1

If the filter failed to print the file but wants LPD to try to print the file again. LPD will restart a filter if it exits with this status.

exit 2

If the filter failed to print the file and doesn't want LPD to try again. LPD will throw out the file.

The text filter that comes with the FreeBSD release, /usr/libexec/lpr/lpf, takes advantage of the page width and length arguments to determine when to send a form feed and how to account for printer usage. It uses the login, host, and accounting file arguments to make the accounting entries.

If you're shopping for filters, see if they're LPD-compatible. If they are, they must support the argument lists described above. If you plan on writing filters for general use, then have them support the same argument lists and exit codes.

7.6.1.2. Accommodating Plain Text Jobs on PostScript Printers

If you're the only user of your computer and PostScript (or other language-based) printer, and you promise to never send plain text to your printer and to never use features of various programs that will want to send plain text to your printer, then you don't need to worry about this section at all.

But, if you would like to send both PostScript and plain text jobs to the printer, then you're urged to augment your printer setup. To do so, we have the text filter detect if the arriving job is plain text or PostScript. All PostScript jobs must start with %! (for other printer languages, see your printer documentation). If those are the first two characters in the job, we have PostScript, and can pass the rest of the job directly. If those aren't the first two characters in the file, then the filter will convert the text into PostScript and print the result.

How do we do this?

If you've got a serial printer, a great way to do it is to install lprps. lprps is a PostScript printer filter which performs two-way communication with the printer. It updates the printer's status file with verbose information from the printer, so users and administrators can see exactly what the state of the printer is (such as ``toner low'' or ``paper jam''). But more importantly, it includes a program called psif which detects whether the incoming job is plain text and calls textps (another program that comes with lprps) to convert it to PostScript. It then uses lprps to send the job to the printer.

lprps should be part of the FreeBSD ports collection (see The Ports Collection ); if not, it should be shortly. You can fetch, build and install it yourself, of course. After installing lprps, just specify the pathname to the psif program that's part of lprps. If you installed lprps from the ports collection, use the following in the serial PostScript printer's entry in /etc/printcap:

	    :if=/usr/local/libexec/psif:
You should also specify the rw capability; that tells LPD to open the printer in read-write mode.

If you have a parralel PostScript printer (and therefore can't use two-way communication with the printer, which lprps needs), you can use the following shell script as the text filter:


#!/bin/sh
#
#  psif - Print PostScript or plain text on a PostScript printer
#  Script version; NOT the version that comes with lprps
#  Installed in /usr/local/libexec/psif
#

read first_line
first_two_chars=`expr "$first_line" : '\(..\)'`

if [ "$first_two_chars" = "%!" ]; then
   #
   #  PostScript job, print it.
   #
   echo $first_line && cat && printf "\004" && exit 0
   exit 2
else
   #
   #  Plain text, convert it, then print it.
   #
   ( echo $first_line; cat ) | /usr/local/bin/textps && printf "\004" && exit 0
   exit 2
fi

In the above script, textps is a program we installed separately to convert plain text to PostScript. You can use any text-to-PostScript program you wish. The FreeBSD ports collection (see The Ports Collection ) includes a full featured text-to-PostScript program called a2ps that you might want to investigate.

7.6.1.3. Simulating PostScript on Non-PostScript Printers

PostScript is the de facto standard for high quality typesetting and printing. PostScript is, however, an expensive standard. Thankfully, Alladin Enterprises has a free PostScript workalike called Ghostscript that runs with FreeBSD. Ghostscript can read most PostScript files and can render their pages onto a variety of devices, including many brands of non-PostScript printers. By installing Ghostscript and using a special text filter for your printer, you can make your non-PostScript printer act like a real PostScript printer.

Ghostscript should be in the FreeBSD ports collection, if you'd like to install it from there. You can fetch, build, and install it quite easily yourself, as well.

To simulate PostScript, we have the text filter detect if it's printing a PostScript file. If it's not, then the filter will pass the file directly to the printer; otherwise, it will use Ghostscript to first convert the file into a format the printer will understand.

Here's an example: the following script is a text filter for Hewlett Packard DeskJet 500 printers. For other printers, substitute the -sDEVICE argument to the gs (Ghostscript) command. (Type gs -h to get a list of devices the current installation of Ghostscript supports.)


#!/bin/sh
#
#  ifhp - Print Ghostscript-simulated PostScript on a DesJet 500
#  Installed in /usr/local/libexec/hpif

#
#  Treat LF as CR+LF:
#
printf "\033&k2G" || exit 2

#
#  Read first two characters of the file
#
read first_line
first_two_chars=`expr "$first_line" : '\(..\)'`

if [ "$first_two_chars" = "%!" ]; then
    #
    #  It's PostScript; use Ghostscript to scan-convert and print it
    #
    /usr/local/bin/gs -dSAFER -dNOPAUSE -q -sDEVICE=djet500 -sOutputFile=- - \
        && exit 0

else
    #
    #  Plain text or HP/PCL, so just print it directly; print a form
    #  at the end to eject the last page.
    #
    echo $first_line && cat && printf "\f" && exit 2
fi

exit 2

Finally, you need to notify LPD of the filter via the if capability:
	:if=/usr/local/libexec/hpif:
That's it. You can type lpr plain.text and lpr whatever.ps and both should print successfully.

7.6.1.4. Conversion Filters

After completing the simple setup described in Simple Printer Setup , the first thing you'll probably want to do is install conversion filters for your favorite file formats (besides plain ASCII text).

Why Install Conversion Filters?

Conversion filters make printing various kinds of files easy. As an example, suppose we do a lot of work with the TeX typesetting system, and we have a PostScript printer. Every time we generate a DVI file from TeX, we can't print it directly until we convert the DVI file into PostScript. The command sequence goes like this:

dvips seaweed-analysis.dvi
lpr seaweed-analysis.ps
By installing a conversion filter for DVI files, we can skip the hand conversion step each time by having LPD do it for us. Now, each time we get a DVI file, we're just one step away from printing it:
lpr -d seaweed-analysis.dvi
We got LPD to do the DVI file conversion for us by specifying the -d option. Section Formatting and Conversion Options lists the conversion options.

For each of the conversion options you want a printer to support, install a conversion filter and specify its pathname in /etc/printcap. A conversion filter is like the text filter for the simple printer setup (see section Installing the Text Filter ) except that instead of printing plain text, the filter converts the file into a format the printer can understand.

Which Conversions Filters Should I Install?

You should install the conversion filters you expect to use. If you print a lot of DVI data, then a DVI conversion filter is in order. If you've got plenty of troff to print out, then you probably want a troff filter.

The following table summarizes the filters that LPD works with, their capability entries for the /etc/printcap file, and how to invoke them with the lpr command:


              /etc/printcap
File type       Capability  lpr option
------------  ------------- ----------
cifplot            cf          -c
DVI                df          -d
plot               gf          -g
ditroff            nf          -n
FORTRAN text       rf          -f
troff              tf          -t
raster             vf          -v
plain text         if     none, -p, or -l

In our example, using lpr -d means the printer needs a df capability in its entry in /etc/printcap.

Despite what others might contend, formats like FORTRAN text and plot are probably obsolete. At your site, you can give new meanings to these or any of the formatting options just by installing custom filters. For example, suppose you'd like to directly print Printerleaf files (files from the Interleaf desktop publishing program), but will never print plot files. You could install a Printerleaf conversion filter under the gf capability and then educate your users that lpr -g mean ``print Printerleaf files.''

Installing Conversion Filters

Since conversion filters are programs you install outside of the base FreeBSD installation, they should probably go under /usr/local. The directory /usr/local/libexec is a popular location, since they they're specialized programs that only LPD will run; regular users shouldn't ever need to run them.

To enable a conversion filter, specify its pathname under the appropriate capability for the destination printer in /etc/printcap.

In our example, we'll add the DVI conversion filter to the entry for the printer named bamboo. Here's the example /etc/printcap file again, with the new df capability for the printer bamboo


#
#  /etc/printcap for host rose - added df filter for bamboo
#
rattan|line|diablo|lp|Diablo 630 Line Printer:\
	:sh:sd=/var/spool/lpd/rattan:\
	:lp=/dev/lpt0:\
	:if=/usr/local/libexec/if-simple:

bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
	:sh:sd=/var/spool/lpd/bamboo:\
	:lp=/dev/ttyd5:fs#0x82000e1:xs#0x820:rw:\
	:if=/usr/local/libexec/psif:\
	:df=/usr/local/libexec/psdf:

The DVI filter is a shell script named /usr/local/libexec/psdf. Here's that script:
#!bin/sh
#
#  DVI to PostScript printer filter
#  Installed in /usr/local/libexec/psdf
#
#  Invoked by lpd when user runs lpr -d
#
exec /usr/local/bin/dvips -f | /usr/local/libexec/lprps "$@"

This script runs dvips in filter mode (the -f argument) on standard input, which is the job to print. It then starts the PostScript printer filter lprps (see section Accomodating Plain Text Jobs on PostScript Printers ) with the arguments LPD passed to this script. lprps will use those arguments to account for the pages printed.

More Conversion Filter Examples

Since there's no fixed set of steps to install conversion filters, let me instead provide more examples. Use these as guidance to making your own filters. Use them directly, if appropriate.

This example script is a raster (well, GIF file, actually) conversion filter for a Hewlett Packard LaserJet III-Si printer:


#!/bin/sh
#
#  hpvf - Convert GIF files into HP/PCL, then print
#  Installed in /usr/local/libexec/hpvf

PATH=/usr/X11R6/bin:$PATH; export PATH

giftopnm | ppmtopgm | pgmtopbm | pbmtolj -resolution 300 \
    && exit 0 \
    || exit 2

It works by converting the GIF file into a portable anymap, converting that into a portable graymap, converting that into a portable bitmap, and converting that into LaserJet/PCL-compatible data.

Here's the /etc/printcap file with an entry for a printer using the above filter:


#
#  /etc/printcap for host orchid
#
teak|hp|laserjet|Hewlett Packard LaserJet 3Si:\
	:lp=/dev/lpt0:sh:sd=/var/spool/lpd/teak:mx#0:\
	:if=/usr/local/libexec/hpif:\
	:vf=/usr/local/libexec/hpvf:

The following script is a conversion filter for troff data from the groff typesetting system for the PostScript printer named bamboo:


#!/bin/sh
#
#  pstf - Convert groff's troff data into PS, then print.
#  Installed in /usr/local/libexec/pstf
#
exec grops | /usr/local/libexec/lprps "$@"

The above script makes use of lprps again to handle the communication with the printer. If the printer were on a parallel port, we'd use this script instead:
#!/bin/sh
#
#  pstf - Convert groff's troff data into PS, then print.
#  Installed in /usr/local/libexec/pstf
#
exec grops

That's it. Here's the entry we need to add to /etc/printcap to enable the filter:
	:tf=/usr/local/libexec/pstf:

Here's an example that might make old hands at FORTRAN blush. It's a FORTRAN-text filter for any printer that can directly print plain text. We'll install it for the printer teak:


#!/bin/sh
#
# hprf - FORTRAN text filter for LaserJet 3si:
# Installed in /usr/local/libexec/hprf
#

printf "\033&k2G" && fpr && printf "\f" && exit 0
exit 2

And we'll add this line to the /etc/printcap for the printer teak to enable this filter:
	:rf=/usr/local/libexec/hprf:

Here's one final, somewhat complex example. We'll add a DVI filter to the LaserJet printer teak introduced earlier. First, the easy part: updating /etc/printcap with the location of the DVI filter:

	:df=/usr/local/libexec/hpdf:

Now, for the hard part: making the filter. For that, we need a DVI-to-LaserJet/PCL conversion program. The FreeBSD ports collection (see The Ports Collection ) has one: dvi2xx is the name of the package. Installing this package gives us the program we need, dvilj2p, which converts DVI into LaserJet IIp, LaserJet III, and LaserJet 2000 compatible codes.

dvilj2p makes the filter hpdf quite complex since dvilj2p can't read from standard input. It wants to work with a filename. What's worse, the filename has to end in .dvi so using /dev/fd/0 for standard input is problematic. We can get around that problem by linking (symbolically) a temporary file name (one that ends in .dvi) to /dev/fd/0, thereby forcing dvilj2p to read from standard input.

The only other fly in the ointment is the fact that we can't use /tmp for the temporary link. Symbolic links are owned by user and group bin. The filter runs as user daemon. And the /tmp directory has the sticky bit set. The filter can create the link, but it won't be able clean up when done and remove it since the link will belong to a different user.

Instead, the filter will make the symbolic link in the current working directory, which is the spooling directory (specified by the sd capability in /etc/printcap). This is a perfect place for filters to do their work, especially since there's (sometimes) more free disk space in the spooling directory than under /tmp.

Here, finally, is the filter:


#!/bin/sh
#
#  hpdf - Print DVI data on HP/PCL printer
#  Installed in /usr/local/libexec/hpdf

PATH=/usr/local/bin:$PATH; export PATH

#
#  Define a function to clean up our temporary files.  These exist
#  in the current directory, which will be the spooling directory
#  for the printer.
#
cleanup() {
   rm -f hpdf$$.dvi
}

#
#  Define a function to handle fatal errors: print the given message
#  and exit 2.  Exiting with 2 tells LPD to don't try to reprint the
#  job.
#
fatal() {
    echo "$@" 1>&2
    cleanup
    exit 2
}

#
#  If user removes the job, LPD will send SIGINT, so trap SIGINT
#  (and a few other signals) to clean up after ourselves.
#
trap cleanup 1 2 15 

#
#  Make sure we're not colliding with any existing files.
#
cleanup

#
#  Link the DVI input file to standard input (the file to print).
#
ln -s /dev/fd/0 hpdf$$.dvi || fatal "Cannot symlink /dev/fd/0"

#
#  Make LF = CR+LF
#
printf "\033&k2G" || fatal "Cannot initialize printer"

# 
#  Convert and print.  Return value from dvilj2p doesn't seem to be
#  reliable, so we ignore it.
#
dvilj2p -M1 -q -e- dfhp$$.dvi

#
#  Clean up and exit
#
cleanup
exit 0

Automated Conversion: An Alternative To Conversion Filters

All these conversion filters accomplish a lot for your printing environment, but at the cost forcing the user to specify (on the lpr command line) which one to use. If your users aren't particularly computer literate, having to specify a filter option will become annoying. What's worse, though, is that an incorrectly specified filter option may run a filter on the wrong type of file and cause your printer to spew out hundreds of sheets of paper.

Rather than install conversion filters at all, you might want to try having the text filter (since it's the default filter) detect the type of file it's asked to print and then automatically run the right conversion filter. Tools such as file can be of help here. Of course, it'll be hard to determine the differences between some file types---and, of course, you can still provide conversion filters just for them.

The FreeBSD ports collection has a text filter that performs automatic conversion called apsfilter. It can detect plain text, PostScript, and DVI files, run the proper conversions, and print.

7.6.1.5. Output Filters

The LPD spooling system supports one other type of filter that we've not yet explored: an output filter. An output filter is intended for printing plain text only, like the text filter, but with many simplifications. If you're using an output filter but no text filter, then

Don't be seduced by an output filter's simplicity. If you'd like each file in a job to start on a different page an output filter won't work. Use a text filter (also known as an input filter); see section Installing the Text Filter . Furthermore, an output filter is actually more complex in that it has to examine the byte stream being sent to it for special flag characters and must send signals to itself on behalf of LPD.

However, an output filter is necessary if you want header pages and need to send escape sequences or other initialization strings to be able to print the header page. (But it's also futile if you want to charge header pages to the requesting user's account, since LPD doesn't give any user or host information to the output filter.)

On a single printer, LPD allows both an output filter and text or other filters. In such cases, LPD will start the output filter to print the header page (see section Header Pages ) only. LPD then expects the output filter to stop itself by sending two bytes to the filter: ASCII 031 followed by ASCII 001. When an output filter sees these two bytes (031, 001), it should stop by sending SIGSTOP to itself. When LPD's done running other filters, it'll restart the output filter by sending SIGCONT to it.

If there's an output filter but no text filter and LPD is working on a plain text job, LPD uses the output filter to do the job. As stated before, the output filter will print each file of the job in sequence with no intervening form feeds or other paper advancement, and this is probably not what you want. In almost all cases, you need a text filter.

The program lpf, whch we introduced earlier as a text filter, can also run as an output filter. If you need a quick-and-dirty output filter but don't want to write the byte detection and signal sending code, try lpf. You can also wrap lpf in a shell script to handle any intialization codes the printer might require.

7.6.1.6. lpf: a Text Filter

The program /usr/libexec/lpr/lpf that comes with FreeBSD binary distribution is a text filter (input filter) that can indent output (job submitted with lpr -i), allow literal characters to pass (job submitted with lpr -l), adjust the printing position for backspaces and tabs in the job, and account for pages printed. It can also act like an output filter.

lpf is suitable for many printing environments. And although it has no capability to send initialization sequences to a printer, it's easy to write a shell script to do the needed initialization and then execute lpf.

In order for lpf to do page accounting correctly, it needs correct values filled in for the pw and pl capabilities in the /etc/printcap file. It uses these values to determine how much text can fit on a page and how many pages were in a user's job. For more information on printer accounting, see Accounting for Printer Usage .


FreeBSD Handbook : Printing : Advanced Printer Setup : Filters
Previous: Advanced Printer Setup
Next: Header Pages [an error occurred while processing this directive]