Ir al contenido

PGD | §3.3 – Procedimientos PostScript

§3.2 – Elementos de PostScript

Es común que al crear un programa para resolver un problema complejo aparezca la necesidad de realizar una misma tarea varias veces. Para evitar duplicar textualmente esa serie de instrucciones, los lenguajes permiten definir lo que se conoce en general como procedimiento, que no es más que la especificación separada de esa tarea con la asignación de un nombre o etiqueta que la identifique. De esa forma, cada vez que sea necesario realizar ese trabajo simplemente se invoca el nombre del procedimiento; el intérprete de ese lenguaje recurre a su definición, dada por nosotros oportunamente, para acceder a las instrucciones necesarias. Naturalmente, en PostScript también es posible definir procedimientos. Veamos un ejemplo.

Definición de procedimientos en PostScript

En el apartado anterior hemos visto que PostScript utiliza como unidad el punto, es decir, una cantidad igual a 1/72 de pulgada. Si hemos de especificar dimensiones, deberíamos en cada caso escribir la secuencia de operadores que conviertan nuestras medidas a puntos. Supongamos que nuestras medidas están en milímetros; convertir 100 mm en puntos exige dividirlos por 25,4 (para pasarlos a pulgadas) y luego multiplicarlos por 72 (para pasarlos a puntos), es decir:

100 25.4 div 72 mul           % 100 mm pasados a puntos
150 25.4 div 72 mul           % 150 mm pasados a puntos
200 25.4 div 72 mul           % 200 mm pasados a puntos, etc.

Evidentemente, la operación “pasar mm a puntos” equivale a la secuencia de operandos y operaciones “25.4 div 72 mul”. Podemos “enseñar” a PostScript esta secuencia y darle un nombre significativo. Para ello utilizaremos la siguiente sintaxis:

/mm {25.4 div 72 mul} def

Explicaremos el significado de esta expresión, pero conociendo el funcionamiento de la pila de PostScript será más claro si lo hacemos de atrás para adelante:

  • El operador def sirve para crear un procedimiento. Requiere dos operandos en la pila: un nombre para el procedimiento y el procedimiento en sí;
  • El primer parámetro de def, /mm, introduce los caracteres mm como un nombre, es decir, una etiqueta que servirá para identificar el procedimiento. La barra diagonal indica a PostScript que el nombre no es (todavía) un operador, sino sólo una etiqueta; de lo contrario PostScript buscaría el símbolo mm en su lista de operadores, y al no encontrarlo daría un error;
  • El segundo parámetro de def, {25.4 div 72 mul}, comienza con la llave { que indica al intérprete el comienzo de instrucciones diferidas, esto es, instrucciones que no debe ejecutar inmediatamente sino que sólo debe acumularlas hasta que encuentre el símbolo }, que indica el fin de las instrucciones diferidas. De no emplear las llaves, PostScript intentaría resolver la división, lo que fracasaría por falta de operandos.

Podemos ahora considerar que mm es un nuevo operador, que espera un operando en la pila (una dimensión en mm) y deja otro en la pila (la misma dimensión en puntos).

Luego de la ejecución de la línea indicada, el símbolo mm se comportará como cualquier otra instrucción de PostScript[1]. Los ejemplos previos quedarían así:

/mm { 25.4 div 72 mul } def   % Procedimiento para pasar de mm a puntos
100 mm                        % Deja en la pila 100 mm pasados a puntos
150 mm                        % Deja en la pila 150 mm pasados a puntos
200 mm                        % Deja en la pila 200 mm pasados a puntos, etc.

Técnicas de diseño PostScript[2]

Como ejemplo de aplicación, diseñaremos un programa PostScript para crear una tira de control en escala de gris, formada por cuadrados llenos con porcentajes de negro desde el 100% hasta el 0% en pasos de 10%.

Podemos considerar que cada parche consiste básicamente de la misma operación, es decir, dibujar cuadrados de iguales dimensiones; sólo cambian en cada caso sus coordenadas y el color de relleno. Supongamos parches de 15 mm de lado, dispuestos horizontalmente a partir de la esquina inferior izquierda de la página. Haremos uso del operador rectfill, cuya definición es:

x y ancho alto rectfill

y cuyo efecto es marcar un rectángulo lleno del ancho y alto especificados, con su vértice inferior izquierdo en las coordenadas x, y, empleando el color actual, definido previamente mediante setrgbcolor, setgray o setcmykcolor.

Una primera estrategia es escribir directamente las instrucciones para cada parche. El resultado podría ser el siguiente:

%!                               % firma PostScript 
/mm {25.4 div 72 mul} def        % definimos conversión de mm a puntos
0 setgray                        % observar que setgray entiende 0 como negro 
                                 % y 1 como blanco
                                 % 0 setgray significa 100% de negro
0 mm 0 mm 15 mm 15 mm rectfill   % parche 100% en (0, 0)
0.1 setgray                      % 0.1 setgray significa 90% negro
15 mm 0 mm 15 mm 15 mm rectfill  % parche 90% en (15 mm, 0)
0.2 setgray                      % etc.
30 mm 0 mm 15 mm 15 mm rectfill 
0.3 setgray 
45 mm 0 mm 15 mm 15 mm rectfill 
0.4 setgray 
60 mm 0 mm 15 mm 15 mm rectfill 
0.5 setgray 
75 mm 0 mm 15 mm 15 mm rectfill 
0.6 setgray 
90 mm 0 mm 15 mm 15 mm rectfill 
0.7 setgray 
105 mm 0 mm 15 mm 15 mm rectfill 
0.8 setgray 
120 mm 0 mm 15 mm 15 mm rectfill 
0.9 setgray 
135 mm 0 mm 15 mm 15 mm rectfill 
1 setgray 
150 mm 0 mm 15 mm 15 mm rectfill 
showpage                          % terminamos: mostrar resultado.

Esto hará correctamente la tarea, pero hemos repetido 11 veces parámetros que no cambian, como el ancho y el alto de cada parche. Sería mejor enseñar a PostScript cómo dibujar un parche cualquiera, para simplificar el código. Para ello, observemos por ejemplo las instrucciones que generan el parche del 40%:

0.6 setgray
75 mm 0 mm 15 mm 15 mm rectfill

El mismo resultado podría obtenerse así, recordando el funcionamiento de la pila:

75 mm 0 mm 0.6 setgray 15 mm 15 mm rectfill

La ventaja de este reordenamiento es que nos deja una instrucción que comienza con tres parámetros (x, y, nivel de gris), que son los valores que cambian en cada parche, seguido de operadores y operandos que no cambian. Definiendo un procedimiento “parche” como sigue:

/parche {setgray 15 mm 15 mm rectfill} def

el parche anterior se escribe

75 mm 0 mm 0.5 parche

A partir de ahora, podemos contar con un operador PostScript parche que espera tres parámetros en la pila (x, y, nivel de gris) y dibuja el parche correspondiente. Así llegamos a una segunda versión, más compacta:

%!                                           % firma PostScript
/mm {25.4 div 72 mul} def                    % definimos conversión de mm a puntos
/parche {setgray 15 mm 15 mm rectfill} def   % definimos parche
0 mm 0 mm 0 parche                           % parche 100% en (0, 0)
15 mm 0 mm 0.1 parche                        % parche 90% en (15 mm, 0)
30 mm 0 mm 0.2 parche                        % etc
45 mm 0 mm 0.3 parche
60 mm 0 mm 0.4 parche
75 mm 0 mm 0.5 parche
90 mm 0 mm 0.6 parche
105 mm 0 mm 0.7 parche
120 mm 0 mm 0.8 parche
135 mm 0 mm 0.9 parche
150 mm 0 mm 1 parche
showpage                                     % terminamos: mostrar resultado.

Este segundo programa es suficiente en este caso porque se trata de pocos parches, pero si hubiera que crear cien sería engorroso; crear mil sería ya prohibitivo.

Existe la posibilidad de condensar aún más este código, e incluso adaptarlo para un número arbitrario de parches. En efecto, todavía repetimos instrucciones para crear cada parche; sin embargo todos ellos se pueden producir según una misma regla: el parche número n (con n entre 0 y 10) requiere una instrucción PostScript que adopte esta “forma”:

(n x 15 mm) 0 mm (n/10) parche

Es decir, el valor de x es n veces 15 mm, mientras que el nivel de gris es n/10. Un operador especial, for, nos permite repetir un mismo procedimiento con un valor distinto cada vez. La sintaxis de for es:

inicio incremento límite procedimiento for

Su significado es el siguiente: colocar en la pila un número al principio igual a inicio, ejecutar procedimiento, agregar incremento al número, ejecutar otra vez procedimiento, y así sucesivamente hasta que se supere límite.

Imaginemos ahora que escribimos un nuevo procedimiento, parchenumero, que haga lo mismo que parche pero que requiera en la pila sólo el número de parche y no las coordenadas y el nivel de gris: por ejemplo, para crear el parche de 50%, en lugar de escribir

75 mm 0 mm 0.5 parche

escribiríamos

5 parchenumero

Si suponemos este procedimiento ya escrito, podríamos crear los 11 parches con esta sencilla instrucción:

0 1 10 {parchenumero} for

Resta escribir el procedimiento parchenumero para completar el programa. Dejamos al lector la tarea de comprobar que el siguiente procedimiento realiza esa función:

/parchenumero {dup 15 mm mul 0 mm 3 2 roll 10 div parche} def

Hemos hecho uso de dos nuevos operadores PostScript. Uno de ellos, dup, tiene por objeto duplicar el último elemento de la pila, mientras que el otro, roll, tiene como función rotar elementos de la pila. Más precisamente, la instrucción

n i roll

desplaza cíclicamente los últimos n elementos de la pila i lugares hacia adelante. El programa PostScript para nuestra tira de control queda así finalmente:

%!                                             % firma PostScript
/mm {25.4 div 72 mul} def                      % definimos conversión de mm a puntos
/parche {setgray 15 mm 15 mm rectfill} def     % definimos parche
/parchenumero {                                % definimos parchenumero
   dup 15 mm mul 0 mm 3 2 roll 10 div parche
} def
0 1 10 {parchenumero} for                      % ejecutar parchenumero 
                                               % desde 0 hasta 10
showpage                                       % terminamos: mostrar resultado.

Comparar esta última versión con la primera; la extensión del código es mucho menor, a la vez que se ha ganado en generalidad.

ps-strip
Resultado de la ejecución del código mostrado sobre una página en formato A4.
§3.4 – Gráficos >

1 La pila queda vacía luego de la ejecución de def; técnicamente, el resultado de esta ejecución es agregar al diccionario de operadores un nuevo operador, cuyo nombre es mm en este caso.
2 Debe quedar claro que, en la vida real, son los drivers los que crearán código PostScript de manera automática. La idea de escribir manualmente código PostScript es, básicamente, conocer la filosofía detrás del lenguaje, sus alcances y limitaciones. Esta sección está dirigida, en esencia, a mis alumnos de Procesos Gráficos Digitales de Fundación Gutenberg, aunque también podrían interesar a cualquier lector con curiosidad suficiente.