Home Wiki > SDB:Using Your Own Filters to Print with CUPS
Sign up | Login

SDB:Using Your Own Filters to Print with CUPS

tagline: From openSUSE

This article addresses experienced Linux users.

Some details here and there could be a bit outdated (depending on the CUPS version) but in general things are still valid.

Situation

You want to use a custom filter to print with CUPS.

Requirements:

You should have a basic knowledge of the printing system and be well acquainted with CUPS (see SDB:CUPS in a Nutshell), bash scripts, and common command-line tools.

CUPS Filter System Background Information

The CUPS default filter system usually produces very good results and provides multiple possibilities for tuning the printing output to meet your requirements. However, custom filters might be needed in some exceptional cases.

Filter Activation Order

CUPS default filtering works in general by way of the entries defined in /usr/share/cups/mime/mime.types or /etc/cups/mime.types and /usr/share/cups/mime/mime.convs or /etc/cups/mime.convs. Where appropriate for each printing queue, the *cupsFilter entries in the PPD file are used.

The actual filtering of a particular print job for a particular printer model on a particular Linux system depends on various conditions, see "What does the filter system do and how does it work" at SDB:CUPS in a Nutshell and "Background Information" at SDB:How to Report a Printing Issue.

To determine the actual filtering for a particular print job for a particular printer model on a particular Linux system inspect the CUPS log messages in /var/log/cups/error_log - for example to get an overview you may run a command like "grep PID /var/log/cups/error_log" - for details see "Usually provide CUPS debug messages" at SDB:How to Report a Printing Issue.

The following is an example how the filtering works for the traditional printing data format PostScript when a PostScript printer is used and when a printer driver is used that is included in Ghostscript.

PostScript printers

Printing documents in PostScript format:

  1. According to the entry in /usr/share/cups/mime/mime.types or /etc/cups/mime.types, data in PostScript format has the MIME type application/postscript.
    According to the entry "application/postscript application/vnd.cups-postscript 66 pstops" in /usr/share/cups/mime/mime.convs or /etc/cups/mime.convs, data with the MIME type application/postscript is converted to data with the MIME type application/vnd.cups-postscript by means of the CUPS filter pstops (i.e., with /usr/lib/cups/filter/pstops).
    The CUPS internal cost for this conversion amounts to 66 units.
  2. Data with the MIME type application/vnd.cups-postscript is sent to the PostScript printer.

Printing plain texts:

  1. According to the entry in /usr/share/cups/mime/mime.types or /etc/cups/mime.types, plain texts have the MIME type text/plain (see also SDB:Plain Text versus Locale).
    According to the entry "text/plain application/postscript 33 texttops" in /usr/share/cups/mime/mime.convs or /etc/cups/mime.convs, data with the mime type text/plain is converted to data with the mime type application/postscript by means of the CUPS filter texttops (i.e., with /usr/lib/cups/filter/texttops).
    The CUPS internal cost for this conversion amounts to 33 units.
  2. As above, data with the MIME type application/postscript is converted to data with the MIME type application/vnd.cups-postscript by way of the CUPS filter /usr/lib/cups/filter/pstops with a cost of 66.
    Along with the conversion in the first step, the whole cost amounts to 99 units.
  3. Data with the MIME type application/vnd.cups-postscript is sent to the PostScript printer.

Non-PostScript printers (e.g., PCL printers)

Printing documents in PostScript format:

  1. As above, data with the MIME type application/postscript is converted to data with the MIME type application/vnd.cups-postscript by way of the CUPS filter /usr/lib/cups/filter/pstops.
  2. According to the *cupsFilter entry in a PPD file, for example the entry in a Foomatic PPD file:
    *cupsFilter: "application/vnd.cups-postscript 0 foomatic-rip"
    data with the MIME type application/vnd.cups-postscript is converted to printer-specific data (e.g., PCL data for a PCL printer) by way of the Foomatic filter /usr/lib/cups/filter/foomatic-rip.
  3. Printer-specific data is then sent to the printer.

Printing plain texts:

  1. As above, data with the MIME type text/plain is converted to data with the MIME type application/postscript by means of the CUPS filter /usr/lib/cups/filter/texttops.
  2. Data with the MIME type application/postscript is converted to data with the MIME type application/vnd.cups-postscript with the CUPS filter /usr/lib/cups/filter/pstops.
  3. Data with the MIME type application/vnd.cups-postscript is converted to printer-specific data with the Foomatic filter /usr/lib/cups/filter/foomatic-rip.
  4. Printer-specific data is then sent to the printer.

Possibilities of Filter Option Settings

See http://localhost:631/help/options.html or http://www.cups.org/documentation.php/doc-1.3/options.html

Application Examples of Custom Filters

Printing Plain Text in Printer-Specific Encoding

You want to use a dot matrix printer to fill in forms with carbon copies on continuous stock. For this purpose, the dot matrix printer queue must forward plain text to the printer, but the character encoding must be customized to that of the printer (see also SDB:Plain Text versus Locale).

Optional PCL Printing on PostScript+PCL Printers

PostScript printers may fail to print extremely complex PostScript documents. Especially if the printer is not equipped with sufficient memory, printing high resolution or color depth raster graphics embedded in the PostScript document may not be possible (see also SDB:Purchasing a Printer and Compatibility).

A 1200x1200 dpi bitmap graphic requires sixteen times as much memory as a 300x300 dpi one. 32-bit color depths need 32 times as much as a 1-bit black and white image. Thus, a 1200x1200 dpi bitmap graphic with a 32-bit color depth requires more than 500 times as much memory as the same graphic in 1-bit black and white with a 300x300 dpi resolution.

The direct solution is to reduce the resolution and color depth values in the application used to create the PostScript file.

The printing system can convert problematic PostScript documents to PCL data (with a smaller resolution or color depth, if necessary) as described above. To be printed, PCL data requires (depending on the resolution and color depth) a printer with substantially less memory. PostScript printers usually can also be addressed via PCL. Most PostScript+PCL printers automatically detect the data type and switch back and forth between the PostScript and PCL mode.

The easiest approach consists of creating an additional queue for a PostScript+PCL printer. This queue always produces PCL data by using a PPD file for a compatible PCL printer.

The main disadvantage of using several queues for the same printer is that it is necessary to query all queues to display all waiting or active print jobs for that printer. Although not a problem for single printers or stand-alone systems, the use of several queues per printer might prove too complicated in the case of several network printers used by many users. For this reason, there should only be one queue for each PostScript+PCL printer that can alternatively produce PCL data to enable problematic PostScript documents to be printed via PCL (with a smaller resolution and color depth, if necessary).

PostScript Preprocessing with Ghostscript on PostScript Printers

PostScript printers fail to print some PostScript documents if the PostScript level supported by the printer's PostScript interpreter (e.g. level 2) is lower than the level required by the document (level 3).

A direct solution would be setting the PostScript level accordingly in the application used to produce the PostScript document. However, this is not always possible.

A more general, simpler approach consists of creating an additional queue for the printer. This queue uses a generic Foomatic PPD file like /usr/share/cups/model/Postscript-level2.ppd.gz or /usr/share/cups/model/Generic/PostScript_Printer-Postscript.ppd.gz because this way there is an option to produce PostScript level 2 through preprocessing with Ghostscript.

PostScript printers produce faulty printouts (or none at all) if they lack some of the fonts or glyphs required to print PostScript documents containing special characters (e.g., the euro symbol) or specific glyphs.

The best solution is to integrate the necessary fonts in the PostScript document by embedding them in the application used to produce the document.

In some cases it helps to use an additional queue with a generic Foomatic PPD file seen above because this way there is an option to integrate at least the Ghostscript fonts in the PostScript document via preprocessing with Ghostscript.

Assuming that preprocessing with Ghostscript takes place via an additional queue and that yours is a PostScript+PCL printer, three queues for the same printer would be necessary (including the possibility of PCL printing). The disadvantages of having several queues per printer have been mentioned above. Thus, both PostScript preprocessing and PCL printing should be possible via a single queue.

Adding Additional Filter Stages to the Default CUPS Filter System

Some applications frequently produce problematic or faulty PostScript documents, which makes printing impossible or results in faulty printouts. In some cases, the PostScript data can be corrected in order to correct or at least enable the print output. If the correction of the PostScript data can be automated with a script, the correction script can be added to the default CUPS filter system as an additional filter stage. Subsequently, the script will be executed consistently. Therefore, special care and thorough tests are needed in order to prevent the correction script from causing more problems than it solves in the default CUPS filter system.

Procedures for the Examples Above

Printing Plain Text in Printer-Specific Encoding

Here "plain text" means "any sequence of bytes which is meant as text in whatever encoding", see SDB:Plain Text versus Locale.

Create the queue for the dot matrix printer as usual with a Foomatic PPD file suitable for the printer model.

Open the PPD file /etc/cups/ppd/queue.ppd and insert the line

*cupsFilter: "text/plain 0 TextToPrinter"

below the line

*cupsFilter: "application/vnd.cups-postscript 0 foomatic-rip"

The purpose of *cupsFilter entries in PPD files is the conversion to printer-specific data, which is subsequently sent to the printer. This particular entry directly converts all data with the MIME type text/plain to printer-specific data by way of /usr/lib/cups/filter/TextToPrinter, without the intervention of other filters. Therefore it is not possible to customize the printout when printing data with the MIME type text/plain in this queue because the necessary CUPS and Foomatic filters are missing.

/usr/lib/cups/filter/TextToPrinter is the filter script that must be created for the conversion of plain text to printer-specific code. This script must be exactly tuned to the printer model.

Many dot matrix printers support a character encoding compatible with IBM PC.

Assume your plain text is in the ISO-8859-1 encoding.

You can use the command

recode "lat1..ibmpc"

to convert ISO-8859-1 text to IBM PC-compatible character code.

If the printer is attached to the first parallel interface, use the following command to test if recode "lat1..ibmpc" produces the right printer-specific code:

echo -en "\rline 1\numlauts: ÄÖÜäöüß\nline 3\f" | recode "lat1..ibmpc" >/dev/lp0

If this is not the case, recode offers many more recoding possibilities. As a last resort, you can use (additionally) "tr" to convert single characters or "sed" to convert character strings. Refer to the relevant man pages.

This script displays the printable characters along with their octal codes:

 #! /bin/bash
 # carriage return before printing
 echo -en "\r"
 # print printable 8-bit characters (CR, NL, TAB, BS, 32-126, 128-254)
 echo -en "the special characters horizontal tab and backspace:\r\n"
 echo -en "the next line consists of 5 horizontal tabs each followed by I\r\n"
 echo -en "\tI\tI\tI\tI\tI\r\n"
 echo -en "the next line consists of C backspace and =\r\n"
 echo -en "C\b=\r\n"
 echo -en "the printout of C backspace and = may look like an Euro sign\r\n"
 echo -en "\nthe printable 7-bit octal codes (040-176) and characters:\r\n"
 for i in 04 05 06 07 10 11 12 13 14 15 16
 do for j in 0 1 2 3 4 5 6 7
    do echo -en "${i}${j} \\0${i}${j}  "
    done
    echo -en "\r\n"
 done
 for i in 170 171 172 173 174 175 176
 do echo -en "${i} \\0${i}  "
 done
 if test "$1" = "7"
 then
 # form feed after printing
   echo -en "\r\f"
   exit 0
 fi
 echo -en "\r\n"
 if test "$1" = "a"
 then
   echo -en "\nthe 8-bit octal codes (200-237) and characters:\r\n"
   for i in 20 21 22 23
   do for j in 0 1 2 3 4 5 6 7
      do echo -en "${i}${j} \\0${i}${j}  "
      done
      echo -en "\r\n"
   done
 fi
 echo -en "\nthe printable 8-bit octal codes (240-376) and characters:\r\n"
 for i in 24 25 26 27 30 31 32 33 34 35 36
 do for j in 0 1 2 3 4 5 6 7
    do echo -en "${i}${j} \\0${i}${j}  "
    done
    echo -en "\r\n"
 done
 for i in 370 371 372 373 374 375 376
 do echo -en "${i} \\0${i}  "
 done
 # form feed after printing
 echo -en "\r\f"
 

Without entering any additional parameters, the output is the usually easy-to-print 8-bit code.

By introducing "7" as a parameter, only printable 7-bit ASCII code will be produced. The parameter "a" produces all printable 8-bit code, but this may overwrite the terminal settings in the process. Thus, the parameter "a" is not suitable for display on screen.

If the printer is attached to the first parallel interface, you can find out the required recoding by launching this script with a command similar to

Script a >/dev/lp0

The output will be the character set integrated in the printer. This way, the necessary character encoding can be exactly determined.

If the recode command above produces the right printer-specific code, /usr/lib/cups/filter/TextToPrinter may look like this:

 #! /bin/bash
 # see "man 7 filter"
 # debug info in /var/log/cups/error_log
 set -x
 # have the input at fd0 (stdin) in any case
 [ -n "$6" ] && exec <"$6"
 # carriage return before printing
 echo -en "\r"
 # printing
 recode "lat1..ibmpc"
 # form feed after printing
 echo -en "\f"
 

The owner, group, and permissions of the filter script must match the rest of filters in /usr/lib/cups/filter/.

Now reload or restart the cupsd.

Tests

You can print a PostScript test page with the commands

echo -en "line 1\numlauts: ÄÖÜäöüß\nline 3" | a2ps -1 -o - | lp -d queue
lp -d queue /usr/share/ghostscript/*/examples/colorcir.ps

PostScript is processed by the CUPS filter system as described above. As a result, the above-mentioned CUPS filter calls can be found in /var/log/cups/error_log:

I ... Started filter /usr/lib/cups/filter/pstops
I ... Started filter /usr/lib/cups/filter/foomatic-rip

You can print an ISO-8859-1 test page with the commands:

echo -en "line 1\numlauts: ÄÖÜäöüß\nline 3" | lp -d queue
lp -d queue /usr/lib/cups/filter/TextToPrinter

Now /var/log/cups/error_log contains only the filter call:

I ... Started filter /usr/lib/cups/filter/TextToPrinter

If "LogLevel debug" has been activated in /etc/cups/cupsd.conf, the debugging output from /usr/lib/cups/filter/TextToPrinter can be seen in /var/log/cups/error_log:

 D ... + '[' -n /var/spool/cups/... ']'
 D ... + exec
 D ... + echo -en '\r'
 D ... + recode lat1..ibmpc
 D ... + echo -en '\f'
 

Optional

Ignore all data apart from that with the MIME type text/plain:

If you want to use a dot matrix printer to fill in forms with carbon copies on continuous stock by printing plain text in printer-specific code, it usually makes no sense to accept data with a MIME type other than text/plain.

Ignore all data with a MIME type other than text/plain:

Open the PPD file and change the *cupsFilter entry for the MIME type application/vnd.cups-postscript as follows:

 *cupsFilter: "application/vnd.cups-postscript 0 /bin/true"
 

By doing this, /bin/true is activated for all data whose MIME type is not text/plain in the last filtering stage. This logs a successful conclusion without any processing and all temporarily-stored data is subsequently deleted.

Ignore all data with the MIME type text/plain except the relevant form data:

If you want to use a dot matrix printer to fill in forms with carbon copies on continuous stock, it usually makes no sense to accept data with the MIME type text/plain other than the relevant form data.

To accept form data only, this must be distinguishable, for instance, by a particular character string in the first line like "Foo...Bar". In this case /usr/lib/cups/filter/TextToPrinter might be similar to:

 #! /bin/bash
 # see "man 7 filter"
 # debug info in /var/log/cups/error_log
 set -x
 # pattern must match by "egrep -i" in first input line
 pattern="foo.*bar"
 # have the input at fd0 (stdin) in any case
 [ -n "$6" ] && exec <"$6"
 # testing
 read -t 1 -r line
 echo $line | egrep -i -q "$pattern" || exit 0
 # carriage return before printing
 echo -en "\r"
 # printing
 echo $line | recode "lat1..ibmpc"
 recode "lat1..ibmpc"
 # form feed after printing
 echo -en "\f"
 

When testing with the command

echo -en "line 1\numlauts: ÄÖÜäöüß\nline 3" | lp -d queue

nothing is printed and /var/log/cups/error_log contains

...
D ... + egrep -i -q 'foo.*bar'
D ... + exit 0

However, the test command

echo -en "line 1: FooBar\numlauts: ÄÖÜäöüß\nline 3" | lp -d queue

produces the requested output.

If the filter script performs a test whether the input data is of the correct type so only that kind of data is accepted that can be correctly processed, the filter script can be used as "System V style interface script". See the "lpadmin" man page. But since CUPS 2.2.0 "System V style interface scripts" are no longer supported for security reasons.

In the above case (dot matrix printer prints only plain text), it is sufficient to create the queue without a PPD file. Instead, use the filter script above as "System V style interface script":

lpadmin -p queue -i /usr/lib/cups/filter/TextToPrinter ...

The filter script actually executed by CUPS is /etc/cups/interfaces/queue, which is a copy of /usr/lib/cups/filter/TextToPrinter.

Any kind of input data is only processed by the "System V style interface script". Therefore, it is not possible to customize the printout in this queue because the necessary CUPS and Foomatic filters are missing.

Optional PCL Printing on PostScript+PCL Printers

Use the manufacturer PPD file for this particular printer model to create the queue for the PostScript+PCL printer. Alternatively, use the generic CUPS PPD file /usr/share/cups/model/Postscript.ppd.gz or a generic Foomatic PPD file like /usr/share/cups/model/Postscript-level2.ppd.gz or /usr/share/cups/model/Generic/PostScript_Printer-Postscript.ppd.gz to operate the printer as a generic PostScript printer.

If you have used the generic Foomatic PPD file, change /etc/cups/ppd/queue.ppd as follows:

 *cupsFilter: "application/postscript-problematic 0 PsToPCL"
 *cupsFilter: "application/vnd.cups-postscript 0 foomatic-rip"
 

Or insert the following lines in /etc/cups/ppd/queue.ppd as in the generic Foomatic PPD file:

 *cupsFilter: "application/postscript-problematic 0 PsToPCL"
 *cupsFilter: "application/vnd.cups-postscript 0 ToPrinter"
 

The purpose of *cupsFilter entries in PPD files is the conversion to printer-specific data that is subsequently sent to the printer. This particular entry directly converts all data with the MIME type application/postscript-problematic to printer-specific data by way of /usr/lib/cups/filter/PsToPCL without the intervention of other filters. It is not possible to customize the printout when printing data with the MIME type application/postscript-problematic in this queue because the necessary CUPS and Foomatic filters are missing.

Insert the new MIME type application/postscript-problematic as separated additional line in /usr/share/cups/mime/mime.types or /etc/cups/mime.types:

application/postscript-problematic

Otherwise, CUPS will not accept this MIME type.

/usr/lib/cups/filter/PsToPCL is the filter script that must be created for the conversion of PostScript to printer-specific code. This script must be exactly tuned to the printer model.

Most PostScript+PCL printers understand the print language PCL5e, which is produced by the Ghostscript drivers ljet4 and lj4dith for Floyd-Steinberg dithering and ljet4d for duplex printing with resolutions up to 600 dpi.

If the printer is attached to the first parallel interface, use the following Ghostscript command to test whether ljet4 (or other PCL5e drivers) produces the right printer-specific code:

gs -q -dBATCH -dNOPAUSE -sDEVICE=ljet4 -sOutputFile=/dev/lp0 /usr/share/ghostscript/*/examples/colorcir.ps

If this is not the case, Ghostscript offers additional PCL drivers (see the SDB article SDB:Purchasing a Printer and Compatibility)

If the color ellipse has been properly printed with the "gs" command mentioned above, the filter /usr/lib/cups/filter/PsToPCL to convert data with the MIME type application/postscript-problematic with ljet4 to PCL5e with a resolution of 300 dpi might look like this:

 #! /bin/bash
 # see "man 7 filter"
 # debug info in /var/log/cups/error_log
 set -x
 # have the input at fd0 (stdin) in any case
 [ -n "$6" ] && exec <"$6"
 # printing
 gs -q -dBATCH -dPARANOIDSAFER -dNOPAUSE -sDEVICE=ljet4 -r300 -sOutputFile=- -
 

All other data has the MIME type application/vnd.cups-postscript will be either processed with /usr/lib/cups/filter/foomatic-rip or directly sent to the printer by /usr/lib/cups/filter/ToPrinter.

/usr/lib/cups/filter/ToPrinter may be similar to:

 #! /bin/bash
 # see "man 7 filter"
 # debug info in /var/log/cups/error_log
 set -x
 # have the input at fd0 (stdin) in any case
 [ -n "$6" ] && exec <"$6"
 # printing
 cat -
 

The owner, group, and permissions of the filter scripts must match the rest of filters in /usr/lib/cups/filter/.

Now reload or restart the cupsd.

The CUPS option

-o document-format=application/postscript-problematic

enables you to set the MIME type of the PostScript document to print to application/postscript-problematic on the command line. Depending on whether this option is set, data will be processed with the specific filter /usr/lib/cups/filter/PsToPCL or with the common CUPS filters. Because Ghostscript only accepts PostScript data as input from stdin, the MIME type application/postscript-problematic can only be used for PostScript documents.

You can print a PostScript test page with /usr/lib/cups/filter/PsToPCL with the command:

lp -d queue -o document-format=application/postscript-problematic /usr/share/ghostscript/*/examples/colorcir.ps

As a result, /var/log/cups/error_log contains:

I ... Started filter /usr/lib/cups/filter/PsToPCL
...
D ... + '[' -n /var/spool/cups/... ']'
D ... + exec
D ... + gs -q -dBATCH -dPARANOIDSAFER -dNOPAUSE -sDEVICE=ljet4 -r300 -sOutputFile=- -

Optional

You can create an instance of the queue where the option

-o document-format=application/postscript-problematic

is already preset:

lpoptions -o document-format=application/postscript-problematic -p queue/problematic

By doing this, you avoid needing to enter this option on the command line every time by simply entering the queue instance for example like

lp -d queue/problematic /usr/share/ghostscript/*/examples/colorcir.ps

Procedure for optional PCL printing from client hosts in the network:

Unfortunately, the new MIME type application/postscript-problematic is not available on the network, but only on the local host.

To print from a remote client where a local cupsd is running:

Either add the MIME type to /usr/share/cups/mime/mime.types or /etc/cups/mime.types on the client host

or specify the CUPS server in the print command:

lp -d queue -h server -o document-format=application/postscript-problematic

or the client host has a "client-only" configuration (without a local running cupsd but a ServerName entry in /etc/cups/client.conf).

Unfortunately, instances are not available on the network, but only on the local host. Thus, if you want to print from a remote client, the option must be explicitly entered on the command line every time or the instance must be created on the client host.

Printing problematic PDF documents as PCL:

Ghostscript can also handle PDF documents directly, but only if they have been received as ordinary files instead of via stdin. Only the first filter in the chain receives its input from CUPS spool file and /usr/lib/cups/filter/PsToPCL is the only filter. Therefore, this filter can be changed as follows so Ghostscript can receive its input directly from CUPS spool file:

 #! /bin/bash
 # see "man 7 filter"
 # debug info in /var/log/cups/error_log
 set -x
 # set inputfile to where the input comes from
 inputfile="-"
 [ -n "$6" ] && inputfile="$6"
 # printing
 gs -q -dBATCH -dPARANOIDSAFER -dNOPAUSE -sDEVICE=ljet4 -r300 -sOutputFile=- $inputfile
 

Switching from PostScript to PCL with PJL commands:

Many PostScript+PCL printers understand the job control language PJL. If your printer does not switch automatically from PostScript to PCL but supports PJL, the switch can be forced with PJL commands.

/usr/lib/cups/filter/PsToPCL can be expanded to enable the corresponding PJL commands to be sent before and after the PCL data:

 #! /bin/bash
 # see "man 7 filter"
 # debug info in /var/log/cups/error_log
 set -x
 # set inputfile to where the input comes from
 inputfile="-"
 [ -n "$6" ] && inputfile="$6"
 # switch to PCL and do a PCL reset
 echo -en "\033%-12345X@PJL ENTER LANGUAGE = PCL\n\033E"
 # printing
 gs -q -dBATCH -dPARANOIDSAFER -dNOPAUSE -sDEVICE=ljet4 -r300 -sOutputFile=- $inputfile
 # PCL reset and PJL end of job
 echo -en "\033E\033%-12345X\n"
 

In the same way, control commands in job control languages other than PJL can be sent to the printer.

Switching from PCL to PostScript with PJL commands:

After having switched from PostScript to PCL with PJL commands, the final PJL string "\033%-12345X" should switch the printer back to its original PostScript mode. To force the switch from PCL to PostScript with PJL commands, expand /usr/lib/cups/filter/ToPrinter as follows:

 #! /bin/bash
 # see "man 7 filter"
 # debug info in /var/log/cups/error_log
 set -x
 # have the input at fd0 (stdin) in any case
 [ -n "$6" ] && exec <"$6"
 # switch to PostScript
 echo -en "\033%-12345X@PJL ENTER LANGUAGE = POSTSCRIPT\n"
 # printing
 cat -
 # PostScript end of transmission and PJL end of job
 echo -en "\004\033%-12345X\n"
 

If a queue has been created with the printer-specific PPD file of the manufacturer, the corresponding job control commands should be included in the PPD (as *JCL... entries). CUPS automatically sends these commands to the printer and, thus, any additional job control commands will not be required.

Setting particular printing options with PJL commands when printing in PCL mode:

If your printer understands PCL and PJL, you can use PJL commands to activate some printing options. The syntax of a PJL+PCL print job is:

\033%-12345X@PJL SET option-1 = value-1
@PJL SET option-2 = value-2
...
@PJL SET option-n = value-n
@PJL ENTER LANGUAGE = PCL
PCL-commands+data\033%-12345X

Every PJL print job must begin and end with exactly one "Universal Exit Language" command "\033%-12345X". Every PJL line must end with exactly one linefeed character "\n". Possible options and their values depend on the printer model.

If the printer is attached to the first parallel interface, use the following command to test if the PJL commands work as requested. This example activates the toner economy mode and the manual confirmation before printing (e.g., to be able to insert special paper):

echo -en "\033%-12345X@PJL SET ECONOMODE = ON\n@PJL SET MANUALFEED = ON\n@PJL ENTER LANGUAGE = PCL\n" >/dev/lp0
gs -q -dBATCH -dNOPAUSE -sDEVICE=ljet4 -sOutputFile=/dev/lp0 /usr/share/ghostscript/*/examples/colorcir.ps
echo -en "\033%-12345X\n" >/dev/lp0

Expand /usr/lib/cups/filter/PsToPCL as follows to send the corresponding PJL commands:

 #! /bin/bash
 # see "man 7 filter"
 # debug info in /var/log/cups/error_log
 set -x
 # set inputfile to where the input comes from
 inputfile="-"
 [ -n "$6" ] && inputfile="$6"
 # PJL printer setup
 echo -en "\033%-12345X@PJL SET ECONOMODE = ON\n@PJL SET MANUALFEED = ON\n"
 # switch to PCL and do a PCL reset
 echo -en "@PJL ENTER LANGUAGE = PCL\n\033E"
 # printing
 gs -q -dBATCH -dPARANOIDSAFER -dNOPAUSE -sDEVICE=ljet4 -r300 -sOutputFile=- $inputfile
 # PCL reset and PJL end of job
 echo -en "\033E\033%-12345X\n"
 

PostScript Preprocessing with Ghostscript on PostScript Printers

Use the manufacturer PPD file for this particular printer model to create the queue for the PostScript printer or use the generic CUPS or Foomatic PPD files to operate the printer as a generic PostScript printer (see above).

Insert the new MIME type application/postscript-pswrite as an additional line

application/postscript-pswrite

in /usr/share/cups/mime/mime.types or /etc/cups/mime.types.

Now insert the line

application/postscript-pswrite application/postscript 33 PsWrite

in /usr/share/cups/mime/mime.convs or /etc/cups/mime.convs.

By doing this, all data with the MIME type application/postscript-pswrite will be converted to data with the MIME type application/postscript by means of /usr/lib/cups/filter/PsWrite. This filter is available for all queues.

The printout of data with the MIME type application/postscript-pswrite can be customized to a large extent because data with the MIME type application/postscript goes through the common CUPS and Foomatic filters. However, the possibilities of /usr/lib/cups/filter/pstops do not always work in connection with PostScript preprocessing. Nevertheless, at least the printer-specific options according to the PPD file should work.

The Ghostscript driver pswrite is used for PostScript preprocessing with Ghostscript.

The filter /usr/lib/cups/filter/PsWrite may be similar to this:

 #! /bin/bash
 # see "man 7 filter"
 # debug info in /var/log/cups/error_log
 set -x
 # set inputfile to where the input comes from
 inputfile="-"
 [ -n "$6" ] && inputfile="$6"
 # printing
 gs -q -dBATCH -dPARANOIDSAFER -dNOPAUSE -sDEVICE=pswrite -sOutputFile=- $inputfile
 

In recent Ghostscript versions "pswrite" was replaced by "ps2write" that outputs PostScript level 2 so that for recent Ghostscript versions "-sDEVICE=ps2write" must be used.

Instead of calling Ghostscript directly via "gs ..." there are scripts like /usr/bin/ps2ps and /usr/bin/ps2ps2 that provide predefined Ghostscript calls. For older Ghostscript versions those scripts require regular files for input and output (i.e. the scripts must be called like "ps2ps2 input.ps output.ps") while in recent Ghostscript versions it also work with stdin and sdtout (i.e. the scripts can be called like "ps2ps2 - -").

In the latter case the filter /usr/lib/cups/filter/PsWrite could be simplified like this:

 #! /bin/bash
 # see "man 7 filter"
 # debug info in /var/log/cups/error_log
 set -x
 # set inputfile to where the input comes from
 inputfile="-"
 [ -n "$6" ] && inputfile="$6"
 # printing
 ps2ps2 $inputfile -
 

The owner, group, and permissions of the filter script must match the rest of filters in /usr/lib/cups/filter/.

Now reload or restart the cupsd.

The CUPS option

-o document-format=application/postscript-pswrite

enables you to set the MIME type of the PostScript document to print to application/postscript-pswrite on the command line.

Depending on whether this option is set, data will be preprocessed with the specific filter /usr/lib/cups/filter/PsWrite or only the common CUPS filtering is done. Because /usr/lib/cups/filter/PsWrite is the first filter in the chain, it receives its input directly from CUPS spool file. Thus, preprocessing PDF documents is possible, too.

You can print a PostScript test page with /usr/lib/cups/filter/PsWrite with the command:

lp -d queue -o document-format=application/postscript-pswrite /usr/share/ghostscript/*/examples/colorcir.ps

As a result, /var/log/cups/error_log contains:

I ... Started filter /usr/lib/cups/filter/PsWrite
I ... Started filter /usr/lib/cups/filter/pstops
...
D ... + gs -q -dBATCH -dPARANOIDSAFER -dNOPAUSE -sDEVICE=pswrite -dLanguageLevel=1 -sOutputFile=- /var/spool/cups/...

Optional

Proceed as described in the "Optional PCL Printing on PostScript+PCL Printers" section above to create a queue instance where the option

-o document-format=application/postscript-pswrite

is preset.

Proceed as described in the "Optional PCL Printing on PostScript+PCL Printers" section above for PostScript preprocessing from client hosts in the network.

Adding Additional Filter Stages to the Default CUPS Filter System

Add a new mime type application/postscript-prefiltered to /usr/share/cups/mime/mime.types or /etc/cups/mime.types by appending the following line:

application/postscript-prefiltered

Insert an additional line in /usr/share/cups/mime/mime.convs or /etc/cups/mime.convs and modify the "pstops" line as follows:

application/postscript application/postscript-prefiltered 10 PsPrefilter
application/postscript-prefiltered application/vnd.cups-postscript 66 pstops

In this way, all data of the mime type application/postscript will be prefiltered with /usr/lib/cups/filter/PsPrefilter and converted to data of the mime type application/postscript-prefiltered.

Subsequently, these data will be processed by the default CUPS filter CUPS-Filter /usr/lib/cups/filter/pstops.

All customization options for the print output are retained, as the data traverse the usual CUPS and Foomatic filters.

This requires that /usr/lib/cups/filter/PsPrefilter works properly for all data of the mime type application/postscript.

/usr/lib/cups/filter/PsPrefilter is the filter script you need to create for the prefiltering of PostScript data.

As an example assume you use an old CUPS 1.1 version and want to solve the problem described in the old Support Database article "No Landscape Format with CUPS" (for SuSE Linux 8.1) where the DSC comment "%%Orientation: Landscape" must be replaced by "%%Orientation: Portrait" in the PostScript output of particular application programs to work around the issue.

If you like to change the PostScript output of application programs by means of a PostScript prefilter script, /usr/lib/cups/filter/PsPrefilter could look as follows:

 #! /bin/bash
 # see "man 7 filter"
 # debug info in /var/log/cups/error_log
 set -x
 # have the input at fd0 (stdin) in any case
 [ -n "$6" ] && exec <"$6"
 # prefiltering
 sed -e 's/Orientation: Landscape/Orientation: Portrait/'
 

Based on this example, the following steps demonstrate how important thorough tests are in order to prevent the correction script from causing more problems than it solves in the default CUPS filter system.

The filter script must have the same owner, group, and access permissions as the other filters in /usr/lib/cups/filter/.

The cupsd has to be reloaded or restarted.

Tests

Test print for PostScript, e.g. with the following commands:

echo -en "Line 1\nUmlauts: ÄÖÜäöüß\nLine 3" | a2ps -1 -o - | lp -d queue
lp -d queue /usr/share/ghostscript/*/examples/colorcir.ps

Now, /var/log/cups/error_log should contain the following filter calls:

I ... Started filter /usr/lib/cups/filter/PsPrefilter
I ... Started filter /usr/lib/cups/filter/pstops

Test print for ISO-8859-1 text, e.g. with the following commands:

echo -en "Line 1\nUmlauts: ÄÖÜäöüß\nLine 3" | lp -d queue
lp -d queue /usr/lib/cups/filter/PsPrefilter

Now, /var/log/cups/error_log should contain the following filter calls:

I ... Started filter /usr/lib/cups/filter/texttops
I ... Started filter /usr/lib/cups/filter/PsPrefilter
I ... Started filter /usr/lib/cups/filter/pstops

The last test should deliver a faulty printout wrongly containing the following line:

sed -e 's/Orientation: Portrait/Orientation: Portrait/'

Explanation:

The PostScript output of /usr/lib/cups/filter/texttops contains the character string "Orientation: Landscape" from /usr/lib/cups/filter/PsPrefilter. However, in the following filter stage it is converted to the character string "Orientation: Portrait" by /usr/lib/cups/filter/PsPrefilter and printed in this way.

/usr/lib/cups/filter/PsPrefilter can be improved by specifying the character string to be replaced as precisely as possible:

 #! /bin/bash
 # see "man 7 filter"
 # debug info in /var/log/cups/error_log
 set -x
 # have the input at fd0 (stdin) in any case
 [ -n "$6" ] && exec <"$6"
 # prefiltering
 sed -e 's/^%%Orientation: Landscape$/%%Orientation: Portrait/'
 

Repetition of the tests:

Now both the failed test as well as

echo '%%Orientation: Landscape' | a2ps -1 -r -o - | lp -d queue

should be printed correctly.

However, in old CUPS 1.1 versions,

lp -d queue -o landscape /usr/lib/cups/filter/PsPrefilter

is not printed in landscape mode, but in portrait mode, although the text is already positioned correctly for landscape mode. As of CUPS version 1.1.18, this works correctly.

The reason for the faulty printout in CUPS before version 1.1.18 is that the option "-o landscape" adds the line

%%Orientation: Landscape

to the PostScript output of /usr/lib/cups/filter/texttops, which causes the following filter /usr/lib/cups/filter/pstops to rotate the representation by 90 degrees in order to print it in landscape mode. However, this is prevented by /usr/lib/cups/filter/PsPrefilter.

Since CUPS version 1.1.18, the CUPS-specific line

%cupsRotation: 90

is used instead of the "%%Orientation" entry in order to prompt the CUPS filter system to rotate the representation by 90 degrees.

Further improvement of the filter for CUPS before version 1.1.18:

In CUPS versions before 1.1.18, the prevention of the 90-degree rotation must depend on the program that generated the PostScript data. Normally, the generating program is specified in the "%%Creator" line, such as in /usr/lib/cups/filter/texttops, a2ps, NetScape, and Mozilla:

 %%Creator: texttops/CUPS ...
 %%Creator: a2ps version ...
 %%Creator: Mozilla (NetScape) ...
 %%Creator: Mozilla PostScript module ...
 

Accordingly, /usr/lib/cups/filter/PsPrefilter can be improved by limiting the prevention of the 90-degree rotation to known problematic cases and leaving the PostScript data unmodified in all other cases:

 #! /bin/bash
 # see "man 7 filter"
 # debug info in /var/log/cups/error_log
 set -x
 # have the input at fd0 (stdin) in any case
 [ -n "$6" ] && exec <"$6"
 # for simple testing with "egrep" have the input as regular file
 cleanup() { EXIT_CODE=$? ; rm -f $INPUT &>/dev/null ; exit $EXIT_CODE ; }
 trap 'cleanup' 0 1 2 3 15
 MY_NAME=${0##*/}
 INPUT=$( mktemp /var/spool/cups/tmp/$MY_NAME.XXXXXX ) || exit 1
 cat - >$INPUT
 # prefiltering only for particular PostScript "Creator" patterns
 if egrep -q '^%%Creator: a2ps|^%%Creator: Mozilla' $INPUT
 then sed -e 's/^%%Orientation: Landscape$/%%Orientation: Portrait/' $INPUT
      exit $?
 fi
 # otherwise no prefiltering
 cat $INPUT
 

Final tests:

As above, test the line "%%Creator: a2ps" in order to make sure that the "sed" command is not wrongly executed when the PostScript data is generated by /usr/lib/cups/filter/texttops:

 echo -e '%%Orientation: Landscape\n%%Creator: a2ps' | lp -d queue
 echo -e '%%Orientation: Landscape\n%%Creator: a2ps' | lp -d queue -o landscape
 echo -e '%%Orientation: Landscape\n%%Creator: a2ps' | a2ps -1 -o - | lp -d queue
 echo -e '%%Orientation: Landscape\n%%Creator: a2ps' | a2ps -1 -r -o - | lp -d queue
 

In gereral regarding printing in the so called "landscape mode" see SDB:Landscape Printing.

Print Queue Specific Custom Filters

The above procedures in general describe how to implement own filters that affect all print jobs on all print queues because changes are applied to general config files (like mime.types and mime.convs).

The basic idea to implement something only for a particular print queue is to not define it via general config files that affect all print jobs or all print queues but instead to define it via print queue specific config files.

For example the above described "System V style interface script" is specific for a single print queue.

For usual print queues the PPD file /etc/cups/<queue_name>.ppd is the only print queue specific config file that is appropriate to implement own filters specifically for a single print queue.

Up to CUPS < 1.3 "*cupsFilter:" entries in the PPD file are the only way how to specify print queue specific filtering. This way it is only possible to provide filters that must output the final printer-specific format. It is not possible to insert a filter elsewhere in between the various steps of the conversion chain. In particular only preprocessing the input is not possible via "*cupsFilter:" entries in the PPD file.

Since CUPS 1.3 preprocessing the input is possible by adding "*cupsPreFilter:" entries to the PPD file, see the "CUPS PPD Extensions" documentation e.g for CUPS 1.3 at http://cups.org/documentation.php/doc-1.3/spec-ppd.html but note that preprocessing with "*cupsPreFilter:" must not change the data type (i.e. same MIME type for input and output). For example PostScript preprocessing via Ghostscript's pswrite or ps2write is possible but in contrast PDF preprocessing does not work this way because pswrite and ps2write output PostScript. For PDF preprocessing Ghostscript's pdfwrite can be used because that outputs PDF, for example via the various /usr/bin/ps2pdf* scripts. Regardless that the script names indicate only PostScript input, it also works for PDF input because Ghostscript accepts both PostScript and PDF as input.

Since CUPS 1.5 "*cupsFilter2:" entries are possible instead of "*cupsFilter:", see the "CUPS PPD Extensions" documentation e.g for CUPS 1.5 at http://cups.org/documentation.php/doc-1.5/spec-ppd.html

Print Job Data Specific Custom Filters

The above procedures in general describe how to implement own filters that affect all print job data of a particular kind (e.g. all PostScript data) because changes are applied using general MIME types (like "application/postscript").

The basic idea to implement custom filters only for specific print job data is to define a new special MIME type that matches only the specific print job data and then make a custom filter only for that special MIME type.

See "man mime.types" how to define MIME types.

Up to CUPS 1.3 when several MIME types match print job data, the type names are sorted alphanumerically in ascending order and the first type is chosen. For example if you defined a new special MIME type "application/postscript-special" that only matches special kind of PostScript, it would not work because the generic MIME type "application/postscript" would always be chosen because it also matches the special kind of PostScript (because it matches any PostScript) and "application/postscript" is sorted before "application/postscript-special".

Since CUPS 1.4 there is support for explicit priority settings for MIME types (check "man mime.types" for "TYPE MATCHING AND PRIORITY" and see http://cups.org/documentation.php/doc-1.4/man-mime.types.html).

Example:

Assume the application "ACME CrazyPS" produces somewhat broken/bad/weird PostScript that does not print as you need it and you found a way how to fix that PostScript and its PostScript header looks like:

%!PS-Adobe-3.0
...
%%Creator: ACME CrazyPS

To set up a special filter to fix only that PostScript define a special MIME type (in a ".types" mime type description file for CUPS) that matches PostScript files (i.e. files that start with "%!") that also must contain "%%Creator: ACME CrazyPS" (within the first 1024 bytes) like:

application/acme-crazy-postscript string(0,%!) + contains(0,1024,"%%Creator: ACME CrazyPS")

Then define a conversion rule (in a ".convs" mime type conversion file for CUPS) for "application/acme-crazy-postscript" to convert that special kind of PostScript into normal PostScript that can be further processed as usual by subsequent filters:

application/acme-crazy-postscript application/postscript 0 Fix-ACME-CrazyPS

Create the new filter /usr/lib/cups/filter/Fix-ACME-CrazyPS that implements what is needed to convert that special kind of PostScript into normal PostScript.

Finally reload or restart the cupsd so that it recognizes the new MIME type, conversion rule, and filter.

Since the move away from PostScript to PDF as the standard print job format (see Concepts printing) it might be even better when that special kind of PostScript is directy fixed and converted into PDF via a conversion rule like

application/acme-crazy-postscript application/pdf 0 ACME-CrazyPS-to-PDF

where /usr/lib/cups/filter/ACME-CrazyPS-to-PDF implements both, fixing that special kind of PostScript plus converting it into normal PDF that can be further processed as usual by subsequent filters.

Limitations and Restrictions

Neither optional PCL printing for PostScript+PCL printers nor PostScript or PDF preprocessing for PostScript printers should be considered as a panacea for printing any PostScript or PDF document.

Strictly speaking using Ghostscript for PostScript or PDF preprocessing to make some weird PostScript or PDF print is a hack and the right solution is to fix those applications that produce weird PostScript or PDF.

PostScript or PDF preprocessing with Ghostscript is a hack because it can lead to arbitrary drawbacks that are unexpected by unexperienced users. The root cause is that after a conversion from PostScript/PDF into PostScript/PDF by Ghostscript one gets a completely new from scratch crated PostScript or PDF that has nothing in common with the original PostScript or PDF except its visual appearance, cf. http://bugs.ghostscript.com/show_bug.cgi?id=694853#c5 and http://bugs.ghostscript.com/show_bug.cgi?id=695016#c1 which read (excerpts):

Ghostscript does not 'fix' errors in PDF files. When you create a PDF file as output it is a completely new PDF file, it does not inherit any structure from the original file.

The original goal of pdfwrite is the same as Adobe Acrobat Distiller, to produce a PDF file from PostScript input, the PDF file should have the same visual appearance as the input. We do now also produce PDF files from any input source, including PDF, but the mechanism is exactly the same; the input is interpreted to produce a series of marking operations, which are then processed to create the output, in this case a PDF file. Again the goal is the same visual appearance.

The ps2write device is not simply 'resizing' your EPS file. The EPS is completely interpreted, converted to graphics primitives, and passed to the ps2write device which creates a brand new file by converting those graphics primitives back to PostScript.

Its important to understand this because although Ghostscript is a level 3 interpreter the ps2write device only outputs level 2 (hence ps*2*write). Any level 3 constructs in the input will not be preserved. In this case your input file uses shading patterns which are a level 3 feature, after passing through ps2write these will have been rendered to images. This increases the file size and reduces the ability to scale the objects, they are no longer vector primitives.

But for printing same visual appearance means same looking printout so that for printing PostScript or PDF preprocessing with Ghostscript should not cause real bad drawbacks (perhaps except somewhat lower quality, cf. "rendered to images" above).

Therefore it is a reasonable workaround when a particular user with a particular kind of weird PostScript or PDF implements a custom filter for PostScript or PDF preprocessing with Ghostscript that makes his particular kind of weird PostScript or PDF print o.k. for his particular needs.

Because PostScript or PDF preprocessing with Ghostscript is only a workaround but no solution, the user with a particular kind of weird PostScript or PDF should report the issue to the author or vendor of the application that produced that weird PostScript or PDF.

In any case, Ghostscript must be able to process the PostScript or PDF from the application. If Ghostscript fails to process it, it is probably so faulty that it is impossible to print.

For information about how to use Ghostscript to test if a PostScript or PDF file seems to be correct see "Test if what the application had submitted to CUPS seems to be correct" at SDB:How to Report a Printing Issue.

Further Information

Portal:Printing