#!/bin/sh
#******************************************************************************
# File:     @(#)$Id: cups-pcl3,v 2.5 2001/05/18 14:58:24 Martin Rel $
# Contents: CUPS filter converting application/vnd.cups-postscript to PCL-3
#           using ghostscript with the pcl3 driver
# Call:     cups-pcl3 <CUPS filter arguments>
# Author:   Martin Lottermoser, Greifswaldstrasse 28, 38124 Braunschweig,
#           Germany; Martin.Lottermoser@t-online.de
#
#******************************************************************************
#									      *
#	Copyright (C) 2001 by Martin Lottermoser			      *
#	All rights reserved						      *
#									      *
#******************************************************************************
#
# Required entries in PPD files
# -----------------------------
# The PPD files distributed with pcl3 are by default already configured for
# this filter. If you are using one of these files, continue reading with the
# next section.
#
# If you want to use another PPD file or ghostscript driver, add or modify the
# following two statements in the PPD file:
#
#   *cupsFilter: "application/vnd.cups-postscript 0 cups-pcl3"
#   *GhostscriptOptions: "<gs options>"
#
# Replace <gs options> with a sequence of command line options for ghostscript
# which select the device, in the case of pcl3 also the subdevice if the device
# is "pcl3". From the point of view of the PPD specification, the
# *GhostscriptOptions statement may contain only a single-line QuotedValue
# without hexadecimal substrings. From the point of view of this filter, the
# value should not contain any characters with special meaning for the shell,
# except internal field separators.
#
#
# Installation of this filter
# ---------------------------
# Copy this file to the .../cups/filter directory.
#
# If you've not yet installed the necessary PPD files, do it now. After that
# you should be able to create a queue and start to print. The remaining
# comments refer to steps you may perform later if you wish.
#
#   Exception: If the ghostscript binary you wish to use is not the one found
#   by calling "gs" from the filter, you'll have to set "GhostscriptCommand" in
#   a configuration file. Read the instructions in the next section.
#
# Should you encounter problems when trying to print, check CUPS' error log
# file. If necessary, increase the CUPS LogLevel to "debug", restart CUPS and
# try again.
#
#
# Configuration
# -------------
# This filter uses a queue-specific PostScript configuration file if it exists.
# This file must have the name $PRINTER.ps ($PRINTER is the name of the queue)
# and must reside in the "config" subdirectory (create it if it doesn't exist)
# of the $CUPS_SERVERROOT directory, usually /etc/cups. This file will be
# handed to ghostscript before the PostScript file to be printed.
#
# Use this configuration file for correcting misalignments, modifying
# "InputAttributes" or "OutputAttributes", setting default transfer functions
# or any other queue-specific configuration independent of the document to be
# printed. Primarily this file should contain PostScript commands to simulate
# persistent state for the printer (what you would store in a real PostScript
# printer's memory in such a manner that it survives power-off or at least
# end-of-job). Note that because the file to be printed will be processed after
# the configuration file, the former can still override the latter.
#
# In addition, you can insert configuration statements for this filter into the
# file. These statements must be given as PostScript comments starting in
# column 1. The possiblities are:
#
#   % DivertToFile: <path>
#	CUPS' document manager does not always operate correctly. If you
#	suspect that the settings you have demanded do not take effect or
#	ghostscript gives an error when printing through this filter while the
#	original file is valid PostScript, set <path> to the name of a file
#	which is writable for the user running this filter (usually lp).
#	Instead of printing, the filter will then copy its input to that file,
#	prepending a comment containing the command line it would use for
#	calling ghostscript.
#
#   % DoAccounting: <True or False>
#	This parameter determines whether pcl3 will issue CUPS page accounting
#	messages or not. The default is "True". You should set this to "False"
#	only if the page accounting messages are generated by the backend
#	(later in the print pipeline).
#
#   % GhostscriptCommand: <path to gs>
#	Use this to set the command to invoke ghostscript. The default
#	corresponds to "% GhostscriptCommand: gs".
#
#   % UseIntermediateFile: <True or False>
#	This statement determines whether the ghostscript output file is first
#	written to disk or is copied directly to the printer. The former is
#	safer because it is legitimate for a PostScript program to invoke
#	operators like "print" which generate output which ghostscript maps to
#	standard output even for "-sOutputFile=-" (in my opinion this is a bug
#	in ghostscript). If you print such a document, the result is unlikely
#	to be usable. The same applies if you print an illegal PostScript file
#	because ghostscript issues some error messages on standard output.
#	The default is therefore "% UseIntermediateFile: True". This might
#	however require substantial disk space.
#
# Of course the file need not contain any PostScript commands if you just need
# it for filter configuration.
#
#******************************************************************************

#name=`basename "$0"`
name=cups-pcl3	# Invalid CUPS documentation

printf 'DEBUG: %s: called for queue %s\n' "$name" "${PRINTER:-}" >&2
printf 'DEBUG: %s: command line: %s %s\n' "$name" "$0" "$*" >&2

# Arguments
if [ $# -ne 5 -a $# -ne 6 ]; then
  printf 'ERROR: Usage: %s job user title copies options [filename]\n' "$0" >&2
  exit 1
fi
job="$1"
user="$2"
title="$3"
copies="$4"	# can be ignored in this filter (pstops generates "NumCopies")
options="$5"
test $# -lt 6 || exec < "$6" || exit 1

# We ignore all options completely because, as far as they are meaningful and
# as far as I understand the matter, preceding filters (in particular pstops)
# should have taken care of them, i.e., we assume that they have been converted
# into PostScript code as far as possible.
# Besides, I'm not aware of any statement in CUPS which tells a filter's
# author which options can be expected (from the source code it's easiest to
# find out which one needs not to expect, but that is not very interesting).

#******************************************************************************

# Find configuration file for the queue
cfgfile="${CUPS_SERVERROOT:-/etc/cups}/config/${PRINTER:-}.ps"
if [ -f "$cfgfile" ]; then
  printf 'DEBUG: %s: Using configuration file %s.\n' "$name" "$cfgfile" >&2
else
  printf 'DEBUG: %s: No configuration file %s.\n' "$name" "$cfgfile" >&2
  cfgfile=
fi

# Subroutine to extract filter options from the configuration file
extract()
{
  # Argument $1: name of the option
  sed -e "/^% $1:/!d" -e 's/^[^:]*: *//' "$cfgfile" | head -1
}

# Extract filter options
dacc=
divert=
gs_command=
use_intermediate_file=
if [ '' != "$cfgfile" ]; then
  if [ ! -r "$cfgfile" ]; then
    printf 'ERROR: %s: %s is not readable.\n' "$name" "$cfgfile" >&2
    exit 1
  fi
  dacc=`extract DoAccounting`
  divert=`extract DivertToFile`
  gs_command=`extract GhostscriptCommand`
  use_intermediate_file=`extract UseIntermediateFile`
fi
test '' != "$dacc" || dacc=True
test '' != "$gs_command" || gs_command=gs
test '' != "$use_intermediate_file" || use_intermediate_file=True
printf 'DEBUG: %s: GhostscriptCommand: %s, DoAccounting: %s, '\
'UseIntermediateFile: %s.\n' \
  "$name" "$gs_command" "$dacc" "$use_intermediate_file" >&2
accounting=-dCUPSAccounting
test False != "$dacc" || accounting=

# Find the command line options from the PPD file. We need not bother about
# *Include statements because CUPS doesn't support them.
if [ ! -f "${PPD:-}" ]; then
  printf 'ERROR: %s: No PPD file %s.\n' "$name" "${PPD:-}" >&2
  exit 1
fi
printf 'DEBUG: %s: Using PPD file %s.\n' "$name" "$PPD" >&2
gs_options=`grep '^\*GhostscriptOptions:' "$PPD" | \
  sed -e '1!d' -e 's/^[^"]*"//' -e 's/".*$//'`
if [ '' = "$gs_options" ]; then
  printf 'ERROR: %s: No *GhostscriptOptions statement in %s.\n' \
    "$name" "$PPD" >&2
  exit 1
fi
printf 'DEBUG: %s: *GhostscriptOptions: "%s"\n' "$name" "$gs_options" >&2

# Treat RIP_MAX_CACHE
if [ '' != "${RIP_MAX_CACHE:-}" ]; then
  printf 'DEBUG: %s: RIP_MAX_CACHE is %s.\n' "$name" "$RIP_MAX_CACHE" >&2

  # Convert to "MaxBitmap" value (cnf. pstoraster/gdevprn.c in CUPS)
  amount=`printf '%s\n' "$RIP_MAX_CACHE" | sed -e 's/[^0-9].*$//'`
  unit=`printf '%s\n' "$RIP_MAX_CACHE" | sed -e 's/^[0-9]*//'`
  if [ '' = "$amount" ]; then
    amount=33554432	# 32 binary mega bytes
  else
    case "$unit" in
    g*)
      amount=`expr $amount '*' 1073741824`;;	# binary giga bytes
      # Note that this will fail on 32-bit systems if "amount" is larger than
      # one.
    m*)
      amount=`expr $amount '*' 1048576`;;	# binary mega bytes
    k*)
      amount=`expr $amount '*' 1024`;;		# binary kilo bytes
    *)
      # This applies to "t" and to no unit specification.
      amount=`expr $amount '*' 262144`;;	# 256 K
    esac
  fi

  printf 'DEBUG: %s: MaxBitmap will become %s.\n' "$name" "$amount" >&2
  gs_options="$gs_options -dMaxBitmap=$amount"
fi

#******************************************************************************

if [ '' != "$divert" ]; then
  cat << --- > "$divert" || { \
    printf 'ERROR: %s: Failure to write to %s.\n' "$name" "$divert" >&2; \
    exit 1; }
%!
% $name input for job $job
% Call to ghostscript: $gs_command $gs_options -dCUPSMessages $accounting $cfgfile <this file>
---
  cat >> "$divert" || exit 1
  printf 'WARNING: %s: No printing, output for job %s diverted to %s.\n' \
    "$name" "$job" "$divert" >&2
  exit 0
fi

#******************************************************************************

if [ False = "$use_intermediate_file" ]; then
  # Directly to stdout, hoping that other data will not appear. However,
  # in order not to loose an error message text, the following command is
  # used to ensure that this output will be printed properly and in
  # particular without a "staircase effect" provided the error occurs on the
  # first page.
  printf '\033E\033&k2G\033&s0C'
  # PCL: Printer Reset, Line Termination (LF -> CR+LF), End-of-Line-Wrap (ON).

  # Now call ghostscript ("-dNOPAUSE -dBATCH" are currently superfluous, but
  # ghostscript's documentation is not sufficiently clear on this point to make
  # it appear safe to drop them).
  $gs_command $gs_options -dCUPSMessages $accounting -q -dNOPAUSE -dBATCH \
    -dSAFER -sOutputFile=- $cfgfile -
  # Newer gs versions (>= 5.65) accept also "-_" which might be faster than "-".
  rc=$?
else
  # Call ghostscript with an intermediate file. See also comments above.
  tmp="${TMPDIR:-/tmp}/$$.tmp"
  rm -f "$tmp"
  $gs_command $gs_options -dCUPSMessages $accounting -q -dNOPAUSE -dBATCH \
    -dSAFER -sOutputFile="$tmp" $cfgfile - >&2
  rc=$?

  # Copy the file to stdout on success
  test 0 -ne $rc -o ! -f "$tmp" || cat "$tmp" || {
    rm -f "$tmp"
    printf 'ERROR: %s: cat to stdout failed for %s.\n' "$name" "$tmp" >&2
    exit 1
  }
  rm -f "$tmp"
fi

if [ 0 -ne $rc ]; then
  printf 'ERROR: %s: ghostscript returned %s for job %s (invalid PostScript input?).\n' \
    "$name" "$rc" "$job" >&2

  # Send a mail message if we have mailx and "$user" is something sensible
  # (CUPS doesn't seem to guarantee that at present for remote users)
  if type mailx > /dev/null 2>&1 && \
	{ expr x"$user" : '.*@' > /dev/null || id "$user" > /dev/null 2>&1; }
      then
    mailx -s "$name"": Error printing job $job" "$user" << ---
Ghostscript returned an error code ($rc) for your print job $job.

The most likely cause is an invalid input file. If you submitted a
PostScript file, run ghostscript or ghostview on it to check its
correctness. If it is valid or you submitted a non-PostScript file,
inspect CUPS' error log file for further information on the error.

If that does not expose the cause, read the initial comments in
$0, especially on DivertToFile.
---
  fi
  # No exit with non-zero return code because that would disable the printer.
  # As the error is most likely due to an illegal input file, such a drastic
  # step is not justified (it would even prevent other users from printing).
fi

exit 0
