The need for shadowop
This page applies to Harlequin v13.1r0 and later; both Harlequin Core and Harlequin MultiRIP.
Simple redefinition is the basis of separator programs such as Adobe Separator and Aldus PrePrint. Consider trying to produce a cyan separation. If the setcmykcolor
operator is redefined to set the gray value to the desired cyan component intensity (by subtracting the intensity from one) and discard the other operands, only the cyan part of the color is produced:
/setcmykcolor {
pop pop pop 1 exch sub setgray
} bind def
If we treat all the relevant operators in analogous ways, we have produced a separation.
Where one system has control over the PostScript it is generating, this is a satisfactory way of working; however, where a fragment of PostScript redefines an operator outside a job to change its behavior inside, there are a number of pitfalls (in principle, stand-alone separator programs could cause jobs to fail because of this):
- An operator cannot be redefined more than once to do something and then call the original, otherwise an infinite recursive loop will result. For example if we have an original redefinition:
/setgray { (executing setgray) == setgray } bind def
the bind
operator ensures that the setgray
within the procedure is the operator, not a name to look up. However, if we then add the following, perhaps unaware of the first redefinition:
/setgray { (operand:) = dup == setgray } bind def
then the setgray
here is not bound by the bind
operator, because when the name is looked up by the bind
operator it finds the earlier redefinition, which is a procedure, not an operator. Then when setgray
is run it prints the messages and then finds the name setgray
, which it looks up in the dictionary stack and resolves as the second redefinition, repeats the messages and looks up setgray
again, and so on. This results in an infinitely repeated series of messages.
- An operator is not the same as a procedure. For most purposes this does not matter. If the interpreter simply encounters the name
setgray
, the procedure or operator to which it refers will be executed, and providing the number of operands consumed is correct, the program will behave as expected. A contrived and rare case where it would not behave as expected is in testing the type, like this, where theif
procedure is not executed ifsetgray
resolves to a procedure (an executable array):
/setgray load type /operatortype eq {...} if
A more realistic example which goes wrong because of the same distinction, is in using the //
construct, or loading the operator:
/proc [ ... //setgray ... /setgray load ... ] cvx def
If setgray
resolves to an operator, executing proc
will cause setgray
to be executed twice; but if it resolves to a procedure, the procedure will simply be pushed on the stack and left there each time an extra exec
is required to execute it.
- Sometimes an application will explicitly look up the operator in
systemdict
, bypassing any redefinition:
/setgray { (operand) = dup == //systemdict /setgray get exec } bind def