8. How to set things up

For common configurations, you can probably ignore this section entirely - instead, you should jump straight to Section 9 below, or better yet, your vendor's documentation. Most Linux distributions supply one or more "idiot-proof" tools to do everything described here for common printers.

If your vendor's tool doesn't work out for you, or you'd like the ability to interactively control printing options when you print, then you should use some other system. PDQ is a good choice; it provides very good functionality and is easy to setup. APS Filter is another good system; it configures LPD queues and filters very easily on most any sort of Unix system.

You can also use the printing system interfaces from the Linux Printing Website to connect many free drivers into several spooling systems. Once this project is complete, these interfaces will offer the best functionality: all styles of free software drivers are supported, user-settable options are available, and most common spooling systems are supported.

8.1. Configuring PDQ

PDQ can be configured by either the superuser or by a joeuser. Root's changes are made to /etc/printrc, and affect everyone, while joeuser can only modify his personal .printrc. Everything applies to both types of configuration.

If PDQ is not available prepackaged for your distribution, you should obtain the source distribution from the PDQ web page and compile it yourself. It is an easy compile, but you must first be sure to have installed the various GTK development library packages, the C library development package, the gcc compiler, make, and possibly a few other development things.

8.1.1. Drivers and Interfaces

PDQ lets users select a printer to print to. A printer is defined in PDQ as the combination of a "driver" and an "interface". Both drivers and interfaces are, in fact, merely snippets of text in the PDQ configuration file.

A PDQ interface says everything about how to ship data out to a printer. The most common interfaces, which are predefined in the PDQ distribution's example printrc file, are:

local-port

A local port interface speaks to a parallel or serial port on the machine PDQ is running on. Using this interface, PDQ can print directly to your parallel port. Note that if you have a multiuser system this can cause confusion, and if you have a network the local-port interface will only apply to one system. In those cases, you can define a raw unfiltered lpd queue for the port and print to the system's lpd daemon exactly the same way from all systems and accounts without any troubles. This interface has a device name argument; the typical value would be /dev/lp0.

bsd-lpd

A bsd lpd interface speaks over the network to an LPD daemon or LPD-speaking networked printer. PDQ supports job submission, cancellation, and queries to LPD interfaces. This interface has hostname and queuename arguments.

appletalk

The appletalk interface allows you to print to printers over the Appletalk network; if you have a printer plugged into your Mac this is the way to go. This interface needs to have the Netatalk package installed to work.

A PDQ driver says everything about how to massage print data into a format that a particular printer can handle. For Postscript printers, this will include conversion from ascii into Postscript; for non-Postscript printers this will include conversion from Postscript into the printer's language with Ghostscript.

If one of PDQ's included driver specifications doesn't fit your printer, then read the section below on how to write your own.

8.1.2. Defining Printers

To define a printer in PDQ:

  • First check that you've got suitable driver and interface declarations in the system or your personal printrc.

  • If you want to define the printer in /etc/printrc (for all users), then su to root.

  • Run xpdq, and select Printer->Add printer. This "wizard" will walk you through the selection of a driver and interface.

That's really all there is to it; most of the work lies in finding or creating a suitable driver specification if you can't find one premade.

8.1.3. Creating a PDQ Driver Declaration

Here I'll walk through an example of how to make a PDQ driver declaration. Before you try that, though, there are several places to look for existing driver specs:

  • PDQ itself comes with a collection of prewritten driver files.

  • The Linux Printing Website's database includes a program called "PDQ-O-Matic" which will generate a PDQ specification from the information in the database. Assuming that the database contains the proper information for your printer and driver, this is the best path if you have a non-Postscript printer.

  • I've written a tool called ppdtopdq which takes a Postscript Printer Definition file and converts it into a PDQ driver specification, with about 75% success. This is an option if you have a Postscript printer.

There are several places to look for the information needed to write your own PDQ driver:

  • The PDQ driver specification syntax is quite rich, and is fully documented in the printrc(5) man page.

  • The PDQ distribution includes a few example files. Look in particular at the Epson Stylus file, which demonstrates the structure of the definition for a Ghostscript-driven printer.

  • The Printing HOWTO Database includes raw Linux driver information for over 600 printers. This will tell you what options to give Ghostscript, or what extra program to run on the Ghostscript output.

If you have to create your own driver specification, or if you enhance one from the PDQ distribution or one of the PDQ driver generator programs mentioned above, please share your creation with the world! Send it to me (gtaylor+pht@picante.com), and I'll make sure that it gets found by future PDQ users with your type of printer.

Now, let's walk through the writing of a driver specification for a printer listed in the Printing HOWTO's database as working, but for which you can't find a PDQ driver spec. I'll use the Canon BJC-210 as the example printer.

First, we look at the database entry for this printer. Note that it is supported "perfectly", so we can expect to get comparable results (or better) to Windows users. The important information is in two places in the entry:

Notes

The human-readable notes will often contain useful information. For some printers, there is a More Info link, which usually refers to a web page run by a user with this printer, or to the driver's home page.

Driver List

Most printers have a list of drivers that are known to work. This is the most important part. You can follow the driver links to a driver-specific page, which will often have more information about how to execute the driver, as well as a link to the driver's web page, if it has one.

A PDQ driver spec has two logical functions: user interaction, and print job processing. These are represented in the file in three places:

Option Declarations

These define what options the user can set, and declare PDQ variables for later parts of the driver to use.

Language Filters

These process the print job from whatever format it arrived in (typically Postscript or ASCII) into a language the printer can understand (for example, PCL). Option values are available here, as well as in the output filter.

Output Filter

This final filter bundles up the printer data regardless of input type; often printer options are set here.

Let's work on each of these for a Canon BJC-210:

8.1.3.1. Options

The driver list for this printer includes the bj200 and bjc600 drivers, both of which are Ghostscript style drivers. The notes suggest that we use the bj200 for black-and-white printing.

So, as far as the user is concerned, the BJC-210 supports one useful option: the user should pick color or black-and-white. Let's declare that as choice option called "MODE":
option {
  var = "MODE"
  desc = "Print Mode"
  # default_choice "Color"    # uncomment to default to color
  choice "BW" {
    # The value part assigns to the variable MODE whatever you 
    # want. Here we'll assign the text that varies between the 
    # two Ghostscript option sets for the two modes.
    value = "bj200"
    help = "Fast black printing with the black cartridge."
    desc = "Black-only"
  }
  choice "Color" {
    value = "bjc600"
    help = "Full-color printing."
    desc = "Color"
  }
}
With the above choice declarations, the user will see a Color or BW choice in the driver options dialog when he prints from xpdq. In the command-line pdq tool, he may specify -oBW or -oColor. The default can be set from xpdq, or declared above with the default_choice keyword.

8.1.3.2. Language Filtering

PDQ normally identifies its input with the file(1) command. For each type returned by file that you want to handle, you provide a language_driver clause. The clause consists mostly of a script to process the printjob language, in any (!) scripting language you wish (the default is the usual Bourne shell).

In our case, we want to print Postscript and ASCII on our BJC-210. This needs two language drivers: one to run Ghostscript for Postscript jobs, and one to add carriage returns to ASCII jobs:
# The first language_driver in the file that matches what file(1) 
# says is what gets used.
language_driver ps {
  # file(1) returns "PostScript document text conforming at..."
  filetype_regx = "postscript"
  convert_exec = { 
    gs -sDEVICE=$MODE -r360x360 \     # gs options from the database
       -q -dNOPAUSE -dBATCH -dSAFER \ # the "usual" Ghostscript options
       -sOutputFile=$OUTPUT $INPUT    # process INPUT into file OUTPUT

    # Those last two lines will often be the same for gs-supported
    # printers.  The gs... line, however, will be different for each
    # printer.      
  }
}

# We declare text after postscript, because the command "file" will
# often describe a postscript file as text (which it is).
language_driver text {
  # No filetype_regx; we match the driver's name: "text"
  convert_exec = {#!/usr/bin/perl
     # a Perl program, just because we can!
     my ($in, $out) = ($ENV{'INPUT'}, $ENV{'OUTPUT'});
     open INPUT, "$in";
     open OUTPUT, ">$out";
     while(<INPUT>) {
        chomp;
        print OUTPUT, "$_\r\n";
     }
  }
}

That's it! While other printers may need output filtering (as described in the next section), the above clauses are it for the BJC-210. We just wrap them all up in a named driver clause:
driver canon-bjc210-0.1 {
  option {
    var = "MODE"
    desc = "Print Mode"
    # default_choice "Color"    # uncomment to default to color
    choice "BW" {
      # The value part assigns to the variable MODE whatever you 
      # want. Here we'll assign the text that varies between the 
      # two Ghostscript option sets for the two modes.
      value = "bj200"
      help = "Fast black printing with the black cartridge."
      desc = "Black-only"
    }
    choice "Color" {
      value = "bjc600"
      help = "Full-color printing."
      desc = "Color"
    }
  }

  # The first language_driver in the file that matches what file(1) 
  # says is what gets used.
  language_driver ps {
    # file(1) returns "PostScript document text conforming at..."
    filetype_regx = "postscript"
    convert_exec = { 
      gs -sDEVICE=$MODE -r360x360 \     # gs options from the database
         -q -dNOPAUSE -dBATCH -dSAFER \ # the "usual" Ghostscript options
         -sOutputFile=$OUTPUT $INPUT    # process INPUT into file OUTPUT

      # Those last two lines will often be the same for gs-supported
      # printers.  The gs... line, however, will be different for each
      # printer.      
    }
  }

  # We declare text after postscript, because the command "file" will
  # often describe a postscript file as text (which it is).
  language_driver text {
    # No filetype_regx; we match the driver's name: "text"
    convert_exec = {#!/usr/bin/perl
       # a Perl program, just because we can!
       my ($in, $out) = ($ENV{'INPUT'}, $ENV{'OUTPUT'});
       open INPUT, "$in";
       open OUTPUT, ">$out";
       while(<INPUT>) {
          chomp;
          print OUTPUT, "$_\r\n";
       }
    }
  }
}

8.1.3.3. Output Filtering

If you want to prepend or append something to all printjobs, or do some sort of transformation on all the data of all types, then it belongs in the filter_exec clause. Our little Canon doesn't require such a clause, but just to have an example, here's a simple illustration showing how to support duplexing and resolution choice on a Laserjet or clone that speaks PJL:
driver generic-ljet4-with-duplex-0.1 {
  # First, two option clauses for the user-selectable things:
  option {
    var = "DUPLEX_MODE"
    desc = "Duplex Mode"
    default_choice = "SIMPLEX"
    choice "SIMPLEX" {
      value = "OFF"
      desc = "One-sided prints"
    }
    choice "DUPLEX" {
      value = "ON"
      desc = "Two-sided prints"
    }
  }

  option { 
    var = "GS_RES"
    desc = "Resolution"
    default_choice = "DPI600"
    choice "DPI300" {
      value = "-r300x300"
      desc = "300 dpi" 
    }
    choice "DPI600" {
      value = "-r600x600"
      desc = "600 dpi" 
    }
  }

  # Now, we handle Postscript input with Ghostscript's ljet4 driver:
  language_driver ps {
    filetype_regx = "postscript"
    convert_exec = { 
       gs -sDEVICE=ljet4 $GS_RES \
          -q -dNOPAUSE -dBATCH -dSAFER \
          -sOutputFile=$OUTPUT $INPUT
    }
  }

  # Finally, we wrap the job in PJL commands:
  filter_exec {
    # requires echo with escape code ability...
    echo -ne '\33%-12345X' > $OUTPUT

    echo "@PJL SET DUPLEX=$DUPLEX_MODE"    >> $OUTPUT
    # You can add additional @PJL commands like the above line here.
    # Be sure to always append (>>) to the output file!

    cat $INPUT >> $OUTPUT
    echo -ne '\33%-12345X' >> $OUTPUT
  }
}

8.2. Configuring LPD

Most Linux systems ship with LPD. This section describes a very basic setup for LPD; further sections detail the creation of complex filters and network configuration.

8.2.1. Basic LPD configuration

The minimal setup for lpd results in a system that can queue files and print them. It will not pay any attention to wether or not your printer will understand them, and will probably not let you produce attractive output. But we have to start somewhere.

To add a print queue to lpd, you must add an entry in /etc/printcap, and make the new spool directory under /var/spool/lpd.

An entry in /etc/printcap looks like:
# LOCAL djet500
lp|dj|deskjet:\
        :sd=/var/spool/lpd/dj:\
        :mx#0:\
        :lp=/dev/lp0:\
        :sh:
This defines a spool called lp, dj, or deskjet, spooled in the directory /var/spool/lpd/dj, with no per-job maximum size limit, which prints to the device /dev/lp0, and which does not have a banner page (with the name of the person who printed, etc) added to the front of the print job.

Go now and read the man page for printcap.

The above looks very simple, but there a catch - unless I send in files a DeskJet 500 can understand, this DeskJet will print strange things. For example, sending an ordinary Unix text file to a deskjet results in literally interpreted newlines, and gets me:
This is line one.
                 This is line two.
                                  This is line three.
ad nauseam. Printing a PostScript file to this spool would get a beautiful listing of the PostScript commands, printed out with this "staircase effect", but no useful output.

Clearly more is needed, and this is the purpose of filtering. The more observant of you who read the printcap man page might have noticed the spool attributes if and of. Well, if, or the input filter, is just what we need here.

If we write a small shell script called filter that adds carriage returns before newlines, the staircasing can be eliminated. So we have to add in an if line to our printcap entry above:
lp|dj|deskjet:\
        :sd=/var/spool/lpd/dj:\
        :mx#0:\
        :lp=/dev/lp0:\
        :if=/var/spool/lpd/dj/filter:\
        :sh:
A simple filter script might be:
#!perl
# The above line should really have the whole path to perl
# This script must be executable: chmod 755 filter
while(<STDIN>){chomp $_; print "$_\r\n";};
# You might also want to end with a form feed: print "\f";
If we were to do the above, we'd have a spool to which we could print regular Unix text files and get meaningful results. (Yes, there are four million better ways to write this filter, but few so illustrative. You are encouraged to do this more efficiently.)

The only remaining problem is that printing plain text is really not too hot - surely it would be better to be able to print PostScript and other formatted or graphic types of output. Well, yes, it would, and it's easy to do. The method is simply an extention of the above linefeed-fixing filter.

Such a filter is called a magic filter. It plays the same role as the language filters of PDQ. Don't bother writing one yourself unless you print strange things - there are a good many written for you already, and most have easy-to-use interactive configuration tools. You should simply select a suitable pre-written filter:

LPD-O-Matic

Lpdomatic is a filter designed to use data from the Linux Printing printer database. It will soon support essentially all free software printer drivers, including regular Ghostscript drivers, Uniprint drivers, and the assorted filter programs floating around out there. It works with various strains of LPD, including stock BSD, LPRng, and the new VA Linux LPD, to allow option selection.

APS Filter

apsfilter is a filter designed for use on a wide variety of Unices. It supports essentially all Ghostscript drivers. It, too, works with various strains of LPD, including stock BSD and LPRng. At the moment, this is probably the best third-party system around for non-PostScript printers.

RHS-Printfilters

RHS-Printfilters is a filter system constructed by Red Hat. It shipped beginning, I think, in version 4 of Red Hat Linux, as the backend to the easy-to-use printtool GUI printer configuration tool. Other distributions, including Debian, now ship the rhs-printfilters/printool combo as a printing option. Thus this filter system is arguably the most widely deployed one.

The rhs filter system is built on an ascii database listing distributed with it. This listing supports many Ghostscript and Uniprint drivers, but not filter-style drivers. The filters constructed also do not support much in the way of user-controllable options at print time.

The printtool places a configuration file named postscript.cfg in the spool directory. Inside this Bourne shell-style file, each setting is a variable. In unusual cases, you can make useful changes directly to the config file which the printtool won't allow; typically this would be the specification of an unusual Ghostscript driver, or a PPD filename for the VA rhs-printfilters version.

VA Linux has made some enhancements to the rhs-printfilters system under contract from HP. With the proper versions, it is now possible to select options for Postscript printers under control of Adobe PPD files. I cover this system in Section 8.2.2.

There's one catch to such filters: older version of lpd don't run the if filter for remote printers, while most newer ones do (although often with no arguments). The version of LPD shipped with modern Linux and FreeBSD distributions does; most commercial unices that still ship LPD have a version that does not. See the section on network printing later in this document for more information on this. If you only have locally-connected printers, then this won't affect you.

8.2.2. LPD for PostScript Printers

While most versions of LPD don't gracefully handle PostScript (nevermind user options), VA Linux recently modified LPD and Red Hat's filtering software to support PostScript printers fairly well. For the moment, this system works only with Red Hat 6.2, although the packages could be easily adapted for other distributions.

8.2.2.1. How it works

VA's new system uses Postscript Printer Definition, or PPD, files. PPD files are provided by printer manufacturers and declare the available options on a printer, along with the Postscript code needed to activate them. With the VA system, the normal LPD scheme works a little differently:

  1. The user can specify options with the -o flag. For example, you might specify -o MediaType:Transparency if you were about to print on overhead film. Alternatively, the front-end GPR can be used to specify options in a dialog box; you can see screenshots of GPR in Section 3.3.1.

  2. LPR passes the options to LPD as an extended attribute in the LPD control file.

  3. A modified version of the rhs-printfilters package is given the extended options data in an environment variable, and uses ppdfilt to add these options to the print data.

8.2.2.2. Obtaining and Installing

You can obtain RPM packages, or source tarballs, from the project's website on SourceForge. For installation details, consult the project's installation micro-HOWTO. In essence, you need to uninstall the Red Hat version of printtool, lpd, and rhs-printfilters entirely, and then install the VA versions, plus ppdfilt, gpr, and a few other utilities.

You will also need PPD files for your Postscript printers. PPD files are usually fairly easy to find. VA Linux and HP distribute PPD files for many Laserjet models. Other vendors provide PPDs for their own printers, and Adobe distributes PPD files for many printers.

At the moment, much of this is a bit difficult to install. But future installation tools will build upon the printer configuration library libprinterconf, which enables both the autodetection and rhs-printfilter configuration of both networked and local printers.

Note: It is possible to use GPR alone, without the modified LPD or even rhs-printfilters. GPR can be compiled with all the logic needed to massage Postscript jobs directly. This may be an easier-to-install option suitable for people who never really need to print using lpr directly.

8.2.2.3. Controlling Postscript Options

Once you've setup VA's Postscript-capable LPD system, you can control your printer's options in two ways:

With the GUI

To use GPR, you first make sure that you've specified the proper PPD file. Then the printer's options will be available on the `Advanced' panel. Basic ppdfilt options will be available on the `Common' panel.

With the command line

This lpr supports the -o option. You may specify any option/value pair from your printer's PPD file with -o. For example, consider this PPD file option clause:
*OpenUI *PrintQuality/Print Quality: PickOne
*DefaultPrintQuality: None
*OrderDependency: 150 AnySetup *PrintQuality
*PrintQuality None/Printer Setting: ""
*PrintQuality Quick/QuickPrint:  "<< /DeviceRenderingInfo ...
*PrintQuality Normal/Normal: "<< /DeviceRenderingInfo << /...
*PrintQuality Pres/Presentation: "<< /DeviceRenderingInfo ...
*PrintQuality Image/1200 Image Quality: "<< /DeviceRenderi...
*CloseUI: *PrintQuality
For the option PrintQuality, the possible values are Quick, Normal, Pres, or Image. You might give a command like:
% lpr -o PrintQuality:Image file.ps

There are a number of options common to all printers which will work in addition to the ones from your PPD. These include:

page-ranges

You can specify a range of pages to print. For example, page-ranges:2-3.

page-set

You can print only odd or even pages. For example, page-set:odd.

number-up

You can print multiple pages on each piece of paper. For example, number-up:2.

Other options are detailed in the ppdfilt man page.

8.2.3. File Permissions

By popular demand, I include below a listing of the permissions on interesting files on my system. There are a number of better ways to do this, ideally using only SGID binaries and not making everything SUID root, but this is how my system came out of the box, and it works for me. (Quite frankly, if your vendor can't even ship a working lpd you're in for a rough ride).
-r-sr-sr-x   1 root     lp    /usr/bin/lpr*
-r-sr-sr-x   1 root     lp    /usr/bin/lprm*
-rwxr--r--   1 root     root  /usr/sbin/lpd*
-r-xr-sr-x   1 root     lp    /usr/sbin/lpc*
drwxrwxr-x   4 root     lp    /var/spool/lpd/
drwxr-xr-x   2 root     lp    /var/spool/lpd/lp/

Lpd must currently be run as root so that it can bind to the low-numbered lp service port. It should probably become UID lp.lp or something after binding, but I don't think it does. This is simply one more reason to avoid the stock BSD LPD.

PDQ uses a different, non-daemon-centric scheme, so it has different programs. The only SUID root programs are the lpd interface programs lpd_cancel, lpd_print, and lpd_status; these are SUID because actual Unix print servers require print requests to originate from a priviledged port. If the only printers for which you use PDQ's bsd-lpd interface are networked print servers (like the HP JetDirect or Lexmark's MarkNet adapters) then you do not need the SUID bit on these programs.

8.3. Large Installations

Large installations, by which I mean networks including more than two printers or hosts, have special needs. Below are some tips. For really large environments, merely distributing printcap/filter information becomes a difficult problem; the Cisco Enterprise Print System addresses this and is probably either a good starting point or a nearly complete solution, depending on your needs. Medium to large environments can be well supported by native LPRng features.

8.4. Accounting

Regular LPD provides very little to help you with accounting. You can specify the name of an accounting file in the af printcap attribute, but this is merely passed as an argument to your if filter. It's up to you to make your if filter write entries to the accounting file, and up to you to process the accounting file later (the traditional format is mainly useful for line printers, and is nontrivial to parse in Perl, so there's no reason to preserve it). Also, if you're using my lpdomatic program as your filter, you'll need to make changes, since it depends on being given a configuration file as the ``accounting'' file name.

Ghostscript provides a PageCount operator that you can use to count the number of pages in each job; basically you just tack a few lines of postscript onto the end of the job to write an accounting file entry; for the best example of this see the file unix-lpr.sh in the Ghostscript source distribution.

Note that the unix-lpr implementation of accounting writes to a file from the Ghostscript interpreter, and is thus incompatible with the recommended -dSAFER option. A better solution might be to query the printer with a PJL command after each job, or to write a postscript snippet that prints the pagecount on stdout, where it can be captured without having to write to a file.

The LPRng print spooler includes an HP-specific sample implementation of accounting; I assume that it queries the printer with PJL. This technique should work for most PJL, Postscript, or SNMP printers with which you have two-way communications.

If you have a networked printer that supports SNMP, you can use the npadmin program to query a pagecount after each job. This should work properly for all print jobs. See Section 11.10.1 for more information on npadmin.