< §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.
