Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Building A PostScript Toolset

26 views
Skip to first unread message

Clayton Cramer

unread,
Aug 29, 1989, 6:29:13 PM8/29/89
to
There was some discussion a few months back about building libraries
of PostScript functions, but very little seems to have come of it.
I have decided to bring the subject back up again, along with some
examples.

Reasons For A Toolset

1. The obvious one -- to avoid reinventing the wheel.

2. To build on other people's knowledge.

3. To provide some simple examples for those still learning PostScript.
(That includes me -- and I've been writing PostScript, intermittently,
since 1984).

Ways To Build Such A Toolset

1. Write single, simple functions. As an example, the PostScript
function at the end of this posting does pie chart slices -- just
pie chart slices. I originally intended it to compute where to
put legends, and invoke a function passed in to it to display
legends next to the appropriate slices, but both because I don't
have time to debug, and because someone can then build such a
layer around my pie slice function, all it does it produce pie
slices.

2. Make minimal use of global variables. As nice as it is to "setgray"
and have that in effect until changed, it also makes it hard to
distinguish where or how something was set. (All the same arguments
that apply to programming languages apply to page description
languages as well). This means that these functions will be passed
parameters that the same, much of the time. The decision whether
to include all possible variations or not is one of those
unpleasant trade-offs.

3. To prevent creating write-only code, set variable names to stack
items at the start of the function, and reference those variable
names thereafter whenever possible, rather than using lots of dups,
exchs, and rolls. It's less efficient, certainly, but the goal
here is reusable code that can be easily changed to do something
else, if needed.

4. A standard (or at least understandable) convention for showing
the number of parameters and what they do.

5. A %%DEMO section at the end of each tool, so you can see how
it's called, and have one clear example, if the parameter description
isn't. By using %%DEMO before the demonstration invocation, you
make it easier to automate the process of extracting just the
executable code from each tool.

I'm hoping that everyone will contribute general purpose tools
that they need to create, so that over time, we will have most
of the essential components -- triangles, parallelograms, log-log
graphs, bar charts, and so on -- needed to write complex applications.

%!
/PieSlice
{% draw a pie slice
% (pie point x, pie point y, start angle, size, radius, gray, width)
gsave
/Width exch def /Gray exch def /Radius exch def /Size exch def
/Angle exch def /Y exch def /X exch def
newpath X Y moveto X Y Radius Angle Angle Size add arc closepath
Width setlinewidth Gray setgray fill
grestore} bind def
%%DEMO
% Draw a complete pie, with different slices in different gray scales.
300 300 45 75 200 0.00 2 PieSlice
300 300 120 45 200 0.15 2 PieSlice
300 300 165 90 200 0.30 2 PieSlice
300 300 255 105 200 0.45 2 PieSlice
300 300 0 45 200 0.60 2 PieSlice
showpage
--
Clayton E. Cramer {pyramid,pixar,tekbspa}!optilink!cramer
"No man is an island" is the beginning of the end of personal freedom.
----------------------------------------------------------------------------
Disclaimer? You must be kidding! No company would hold opinions like mine!

Roy Smith

unread,
Aug 30, 1989, 1:20:01 PM8/30/89
to
In <22...@optilink.UUCP> cra...@optilink.UUCP (Clayton Cramer) writes:
> 3. To prevent creating write-only code, set variable names to stack
> items at the start of the function [...] rather than using lots of dups,
> exchs, and rolls

Well, here is a counter example. I wrote this a while ago, and
have used it several times. It is a bunch of routines to let you work with
vectors in 2-space (i.e. x,y coordinates). It's all dups, exchs, and
rolls. Given the triviality of these, I think it's pretty easy to follow
the thread of them, and it's all pretty well documented anyway. But, yes,
in general I agree -- code which does lots of fancy stack manipulation is
hard to grok, even for somebody like me who grew up on HP (i.e. stack)
machines. I did a lot of up,up,rolldown,exchange stuff when I was hacking
9810's. Obviously, the following can be extended, dot and cross products,
and dimensions higher than 2, but that's all I needed so that's all I did.

-------------------
%
% Vectorops.ps -- vector operators for PostScript.
%
% Copyright 1987 Roy Smith
% Permission is hereby granted for any use of this software other
% than selling it or including it in a commercial product.
%
% $Header: vectorops.ps,v 1.1 87/09/16 20:31:59 roy Exp $
%

% vadd -- add two vectors.
% x1 y1 x2 y2 vadd ==> x1+x2 y1+y2
/vadd
{
3 -1 roll add % ==> x1 x2 y2+y1
3 1 roll add % ==> y2+y1 x1+x2
exch % ==> x1+x2 y2+y1
} def

% vsub -- subtract two vectors.
% x1 y1 x2 y2 vsub ==> x1-x2 y1-y2
/vsub
{
3 -1 roll sub neg % ==> x1 x2 -(y2-y1)
3 1 roll sub % ==> -(y2-y1) x1-x2
exch % ==> x1-x2 -(y2-y1)
} def

% vneg -- take negative of vector (scalar multiply by -1).
% x y vneg ==> -x -y
/vneg
{
exch neg % ==> y -x
exch neg % ==> -x -y
} def

% vdiv -- divide vector by scalar.
% x y s vdiv ==> x/s y/s
/vdiv
{
dup % ==> x y s s
3 1 roll div % ==> x s y/s
3 1 roll div % ==> y/s x/s
exch % ==> x/s y/s
} def

% vmul -- multiply vector by scalar.
% x y s vmul ==> x*s y*s
/vmul
{
dup % ==> x y s s
3 1 roll mul % ==> x s y*s
3 1 roll mul % ==> y*s x*s
exch % ==> x*s y*s
} def

% vexch -- exchange two vectors on stack.
% x1 y1 x2 y2 vexch => x2 y2 x1 y1
/vexch
{
4 2 roll % ==> x2 y2 x1 y1
} def

% vpop -- pop a vector off the stack.
% x1 y1 vpop => ---
/vpop
{
pop pop
} def

% vdup -- duplicate a vector on the stack.
% x1 y1 vdup => x1 y1 x1 y1
/vdup
{
dup % ==> x1 y1 y1
3 -1 roll dup % ==> y1 y1 x1 x1
4 1 roll exch % ==> x1 y1 x1 y1
} def
--
Roy Smith, Public Health Research Institute
455 First Avenue, New York, NY 10016
{att,philabs,cmcl2,rutgers,hombre}!phri!roy -or- r...@alanine.phri.nyu.edu
"The connector is the network"

labc...@e260-2d.berkeley.edu

unread,
Aug 30, 1989, 2:46:07 PM8/30/89
to

A couple of points:

In order to minimize potential side effects, when you define names in a
ToolSet procedure, you should put them in their own dictionary.
Start with a "n dict begin" where "n" is the number of locals you'll use, and
finish with an "end".

Example:

/MyProcedure % arg1 arg2 MyProcedure result
{
2 dict begin
/arg2 exch def /arg1 exch def

...

end
} def

Also, "%%DEMO" *might* confuse some spooler software since it looks like
a document structuring comment (obviously, this is how you're using it, but
it isn't part of the standard). How about just "% DEMO"?

Finally, my basic rule about using local variables is to make
a dictionary entry only if I'm going to use a value more than once. A comment
can make up for not having defined a name for an argument. I guess I'd
recommend trying to achieve a balance between naming and efficiency.

Bob Heiney
labc...@web.Berkeley.edu

Laura Lemay

unread,
Aug 30, 1989, 4:45:06 PM8/30/89
to

In <22...@optilink.UUCP> cra...@optilink.UUCP (Clayton Cramer) writes:
> 3. To prevent creating write-only code, set variable names to stack
> items at the start of the function [...] rather than using lots of dups,
> exchs, and rolls

Hm. I was taught to use stack operations rather than lookup operations
whenever possible, since its faster that way.

Evil as hell to trace, tho.


-Laura Lemay lemay%lor...@sun.com
Redhead. Drummer. Geek.

John Sturdy

unread,
Aug 30, 1989, 6:53:08 PM8/30/89
to
Yes, that's a fine idea. But let's be organized about procedure
naming! It would be good to make these toolbits into ProcSets (as in
EPSF %%BeginProcSet etc) and it would be even better to be able to
concatenate them to build prologues, without worrying about name
clashes.

My suggestion here (clumsy but effective) is to give each chunk a
string name, and use that string at the start of each name that the
chunk describes globally. Taking that a little further, they could be
given some EPSF description other than ProcSet, thus telling handlers
that, unlike ProcSets, they can be concatenated safely to make
prologues.

--
__John When asked to attend a court case, Father Moses took with him
a leaking jug of water. Asked about it, he said: "You ask me to judge
the faults of another, while mine run out like water behind me."

jc...@uk.co.harlqn (UK notation) jc...@harlqn.co.uk (most places)
...!mcvax!ukc!harlqn!jcgs (uucp - really has more stages, but ukc knows us)
John Sturdy Telephone +44-223-872522
Harlequin Ltd, Barrington Hall, Barrington, Cambridge, UK

Chung-Pang Lai

unread,
Aug 31, 1989, 3:29:05 AM8/31/89
to
This is for your tool set.

This routine sorts the dictionary keys and leave them on stack for
printing or other purpose. It uses insertion sort. See comment for details.

%!
% Sort Dictionary key
% Stack: Before => After
% dict => (keyn) ... (key3) (key2) (key1) n
% Dictionary is a hash table, this function is useful when the keys
% needed to be printed in increasing order, the results are left on
% stack in string datatype. Necessary conversion is needed if used
% for other purposes.
% Limitation: According to the Red Book, the Operand stack's maximum
% depth is 500, this function will fail if the results
% overflow the stack.
% Written by C.P.Lai Dec 17, 1988
/SortDictKey { % function def
/dictcount 1 def
{ %forall % sort dict keys on operand stack
pop % consume the value part
% convert Key to string since type is unknown, or else ge may choke
80 string cvs % leave each key in a unique string on stack
dictcount -1 2 {% for
dup /dictindex exch def % save index of key being checked
1 index exch % copy latest key for ge
index % use dictindex to get compare key for ge
ge {dictindex 1 roll exit} if % insert into place
} for % check from the already sorted high end
/dictcount dictcount 1 add def
} forall % standard insertion sort algorithm
dictcount 1 sub % leave final count on stack
} def

% DEMO
% yourdictionary SortDictKey {
% show newline
% } repeat
--
.signature under construction ...
{pyramid, osu-cis, uunet, killer}!daisy!cplai C.P. Lai
cplai%daisy...@uunet.UU.NET cplai%da...@killer.DALLAS.TX.USA
Daisy Systems Corp, 700B Middlefield Road, Mtn View CA 94039. (415)960-6961

Scott Hemphill

unread,
Sep 1, 1989, 2:06:22 AM9/1/89
to
A general purpose Quicksort for your toolset.

Scott Hemphill hemp...@csvax.caltech.edu ...!ames!elroy!cit-vax!hemphill
-------------------------------------------------------------------------------
%!
% August 31, 1989 by Scott Hemphill
% I wrote this code, and hereby place it in the public domain, i.e. there are
% no copying restrictions of any kind.
%
% Quicksort an Array


% Stack: Before => After

% array proc =>
% This Quicksort implementation will sort an array of arbitrary PostScript(tm)
% objects, since you provide the procedure that does the object comparisons.
% Your procedure should remove two objects from the stack, pushing true on
% the stack if the first object is strictly less than the second object in
% sort order, otherwise pushing false. (E.g. if you wish to sort an array of
% strings alphabetically, or an array of numbers into increasing order, you
% can simply use the PostScript operator "lt". See below for examples.)
% The array is sorted in place, and is not left on the stack, so you need
% another copy of it somewhere.

/qsortdict 7 dict def
qsortdict begin
/q-compare 0 def % user-supplied comparison procedure
/q-array 1 def % current sub-array being sorted by qsortsub
/q-length 2 def % length of q-array
/q-left 3 def % left scan index
/q-right 4 def % right scan index
/q-last 5 def % partitioning element
/q-temp 6 def % temporary array element
end

/qsort
{
qsortdict begin
/q-compare exch def
qsortsub
end
}
bind def

/qsortsub
{
/q-array exch def
/q-length q-array length def
q-length 1 gt
{
/q-left 0 def
/q-right q-length 1 sub def
/q-last q-array q-right get def
{
{
q-array q-left get q-last q-compare
{/q-left q-left 1 add def}
{exit}
ifelse
}
loop
{
q-left q-right eq {exit} if
q-array q-right get q-last q-compare
{exit}
{/q-right q-right 1 sub def}
ifelse
}
loop
q-left q-right eq {exit} if
/q-temp q-array q-left get def
q-array q-left q-array q-right get put
q-array q-right q-temp put
}
loop
q-array q-length 1 sub q-array q-left get put
q-array q-left q-last put
q-array q-left 1 add q-length q-left sub 1 sub getinterval
q-array 0 q-left getinterval
qsortsub
qsortsub
}
if
}
bind def

% Implementation notes:
% 1. The numbers in the initial qsortdict definitions are just place holders
% for the real definitions which will be made when qsort is invoked.
% 2. qsort is just a shell for the recursive procedure qsortsub, which does
% the real work.
% 3. qsortsub is a straightforward implementation of the Quicksort algorithm.
% It removes the array on the stack, and sorts it in place by calling itself
% recursively on two pieces of the initial array. Instead of passing the
% initial array plus left and right indices, it passes a subarray, taking
% advantage of the fact that the subarray shares the same memory as the
% original array. This involves slightly more overhead, but is cleaner
% to implement.
% 4. The qsortdict dictionary is used at all recursion levels, but there are
% no definition conflicts! This is because all references to the dictionary
% definitions happen before the two recursive qsortsub calls at the end.
% 5. There are quite a number of performance improvements to be made which have
% been deliberately left out, for clarity. One important improvement would
% be to use a different procedure to sort small sub-arrays. There is a lot
% of time wasted sorting two-element arrays, for example. Even as written,
% however, you should see a significant improvement over insertion-sort or
% other n^2 algorithms for moderately sized arrays. Another improvement
% might be to define all the qsortdict variables as one-element executable
% arrays. The variables can be set by storing a value into the array, and
% read by executing the array. The point is that the array itself can be
% put in the qsortsub procedure instead of its name. Each time you do this
% you save one dictionary name lookup.

% DEMO

% This example uses the "lt" operator to do numeric comparisons:
% [3 1 4 1 5 9 2 6 5] dup /lt load qsort ==


% The "lt" operator is used here to do an alphabetical comparisons of strings.
% [(twas) (brillig) (and) (the) (slithy) (toves)] dup /lt load qsort ==

% This final example sorts the keys in a dictionary. It performs the
% comparison with a 6-element procedure which converts the keys to strings.
% Since the strings have been exchanged, it uses a "gt" operator instead of
% "lt". It would be faster to convert all of the keys in the dictionary to
% strings first, but this would waste more memory. Note that particular
% care has been given to the comparison procedure. Execution of the "bind"
% operator after the procedure definition converts the names "cvs", "exch",
% and "gt" to their corresponding operators. The "//" lexical operator
% (in PostScript version 38.0) converts the names "thing1" and "thing2" to
% the actual strings (the locations in memory--not the contents of the
% strings). This means that all 6 names in the procedure have been converted
% to the objects they refer to, and there are no dictionary name lookups
% during the execution of this procedure.
% /arr [/Times-Roman findfont /CharStrings get {pop} forall] def
% /thing1 128 string def
% /thing2 128 string def
% (initial array:) = arr ==
% arr {//thing2 cvs exch //thing1 cvs gt} bind qsort
% (final array:) = arr ==
--
Scott Hemphill hemp...@csvax.caltech.edu ...!ames!elroy!cit-vax!hemphill

Glenn Reid

unread,
Sep 1, 1989, 6:19:13 PM9/1/89
to
In article <1989Aug30.1...@agate.uucp> labc...@web.Berkeley.edu (Bob Heiney) writes:
>
>In order to minimize potential side effects, when you define names in a
>ToolSet procedure, you should put them in their own dictionary.
>Start with a "n dict begin" where "n" is the number of locals you'll use, and
>finish with an "end".
>
>Example:
>
>/MyProcedure % arg1 arg2 MyProcedure result
>{
> 2 dict begin
> /arg2 exch def /arg1 exch def
>
> ...
>
> end
>} def

It is a good idea to keep names local if you can. Unfortunately,
your example isn't good because it creates a new dictionary every time
you call the procedure, which is probably not what you want.

Here are three ways to get the local dictionary without paying for it
each time you call the procedure. The first is nice but won't
work on a version 23.0 LaserWriter (which was discontinued in 1986
or something like that, when the LaserWriter Plus came out). The
second one will work on all implementations, but you have to do
a name lookup on the dictionary every time you call the procedure.
The last one is the least intrusive (no extra names defined) the
most efficient, and the hardest to understand :-)

% version 1:
/localdict 2 dict def


/MyProcedure % arg1 arg2 MyProcedure result
{

//localdict begin
/ arg2 exch def / arg1 exch def
...
end
} def

% version 2:
/localdict 2 dict def


/MyProcedure % arg1 arg2 MyProcedure result
{

localdict begin


/arg2 exch def /arg1 exch def
...
end
} def

/MyProcedure % arg1 arg2 MyProcedure result
{
REPLACEME begin


/arg2 exch def /arg1 exch def
...
end
}

dup % dup the procedure body
0 % the 0'th element is the REPLACEME name
2 dict % you want a dictionary of 2 elements there
put % put 2 dict into slot 0 of procedure body (array)
def % /MyProcedure {...} def


As a final footnote, if you have many procedures that are always
together, there is no real need to have separate dictionaries
for each one. You could define a single name like $mydict that's
big enough for all of them, then start each proc with //$mydict begin.

Glenn Reid
Adobe Systems

Joe Pallas

unread,
Sep 2, 1989, 8:34:04 PM9/2/89
to
Glenn Reid gives three ways to use local dictionaries in PostScript
procedures. One of them uses //, which doesn't work for the
LaserWriter Classic and isn't documented in the Red Book. One of them
is hairy, but clean. One of them is perfectly readable and portable,
but less desirable because it pays for a name lookup.

Can someone tell me why name lookups are so expensive in PostScript
that everybody goes out of their way to avoid them? Instead, we get
gross things like // and bind (the use of which is officially
discouraged in the Red Book, but everybody uses it anyway). And much
of the power of PostScript gets lost (try wrapping code that redefines
showpage around Mac PostScript, for example).

It seems to me that people spend more time cursing and circumventing
PostScript's dynamic name lookup than anything else.

As for making a new dictionary every time a procedure is invoked,
that's the only way to use local variables in a recursive PostScript
procedure.

Oh, for a lexically scoped PostScript.
joe

C.E. Thompson

unread,
Sep 7, 1989, 6:59:20 AM9/7/89
to
In article <6...@windy.dsir.govt.nz> SRW...@windy.dsir.govt.nz (Clive Nicolson) writes:
>Your third version of the "create dictionary once" problem seems to be trying
>to "put" into a readonly array (procedure), you might known how to do this when
>the memory protection is off on one of your PostScript interpreters, but I
>dont know how to do it other than by something like:
>
>/procname [/READONLY .....] .... put cvx def

Procedures created by {...} are readonly only if there has been a
previous "true setpacking". I suppose that there ought to be code like
the following round the example:

systemdict /setpacking known
{/savepacking currentpacking def false setpacking} if
....
systemdict /setpacking known
{savepacking setpacking} if

I think that the version using "cvx" is probably cleaner, though!

Chris Thompson (ce...@uk.ac.cam.phx)

Clive Nicolson

unread,
Sep 7, 1989, 7:50:16 AM9/7/89
to

debr...@prles3.prl.philips.nl

unread,
Sep 7, 1989, 10:03:46 AM9/7/89
to
Here are some PostScript routines that can be added to the toolset:


/SelectFontDict 5 dict def
/SelectFont { % call: /font height width angle SelectFont -
SelectFontDict begin
/Angle exch def /Width exch def /Height exch def /Font exch def
/AngleNum Angle sin Angle cos div 12 mul def
Font findfont [Width 0 AngleNum Height 0 0] makefont setfont
end
} bind def % SelectFont
% DEMO
% /Times-Roman 100 40 40 SelectFont
% 30 500 moveto (XYZZY) show


/ShowStrDict 9 dict def
/ShowStr { % call: x y height width angle gray /font (string) ShowStr -
gsave
ShowStrDict begin
/Str exch def /Font exch def /Gray exch def /Angle exch def
/Width exch def /Height exch def /Y1 exch def /X1 exch def
/AngleNum Angle sin Angle cos div 12 mul def
Font findfont [Width 0 AngleNum Height 0 0] makefont setfont
X1 Y1 moveto Gray setgray Str show
end
grestore
} bind def % ShowStr
% DEMO
% 10 400 20 20 0 0.75 /Times-Roman
% (20hght 20wdth 0ang Times-Roman) ShowStr
% 10 360 20 30 20 0.25 /Times-Roman
% (20hght 30wdth 20ang Times-Roman) ShowStr
% 10 320 20 10 40 0.25 /Times-Roman
% (20hght 10wdth 40ang Times-Roman) ShowStr
% 10 280 30 20 330 0.25 /Times-Roman
% (30hght 20wdth 330ang Times-Roman) ShowStr


/ShowStrGrayDict 8 dict def
/ShowStrGray { % call: x y mingray maxgray string ShowStrGray -
% show a string with increasing (decreasing) gray-levels
gsave
ShowStrGrayDict begin
/Str exch def /MaxG exch def /MinG exch def /Y exch def /X exch def
/Step MaxG MinG sub Str length 1 sub div def
/TmpStr 2 string def
X Y moveto
0 1 Str length 1 sub {
/LoopVar exch def
MinG setgray
TmpStr 0 Str LoopVar get put TmpStr show
/MinG MinG Step add def
} for
end
grestore
} bind def % ShowStrGray
% DEMO
% /Times-Roman findfont [30 0 0 40 0 0] makefont setfont
% 30 400 0.0 0.9 (Abcde Fghij Klmno) ShowStrGray
% 30 300 0.9 0.0 (Abcde Fghij Klmno) ShowStrGray


/SquareEmptyDict 6 dict def
/SquareEmpty { % call: xbl ybl xtr ytr gray width SquareEmpty -
% bl = bottom-left, tr = top-right
gsave
SquareEmptyDict begin


/Width exch def /Gray exch def

/Ytr exch def /Xtr exch def /Ybl exch def /Xbl exch def
newpath Xbl Ybl moveto Xtr Ybl lineto Xtr Ytr lineto
Xbl Ytr lineto Xbl Ybl lineto closepath
Width setlinewidth Gray setgray stroke
end
grestore
} bind def % SquareEmpty
% DEMO
% 100 100 300 500 0.75 5 SquareEmpty


/SquareSolidDict 5 dict def
/SquareSolid { % call: xbl ybl xtr ytr gray SquareEmpty -
% bl = bottom-left, tr = top-right
gsave
SquareSolidDict begin
/Gray exch def /Ytr exch def /Xtr exch def /Ybl exch def /Xbl exch def
newpath Xbl Ybl moveto Xtr Ybl lineto Xtr Ytr lineto
Xbl Ytr lineto Xbl Ybl lineto closepath
Gray setgray fill
end
grestore
} bind def % SquareSolid
% DEMO
% 50 400 400 600 0.25 SquareSolid


Please report any remarks and/or bugs to:
Jos de Bruijne
Philips Research Labs Eindhoven,
Holland (The country in Europe, NOT the city in the USA!)
Email: debr...@prles3.prl.philips.nl

debr...@prles3.prl.philips.nl

unread,
Sep 8, 1989, 4:15:19 AM9/8/89
to
Oops, I found one bug myself in the toolset routines that I posted
yesterday:
In the procedure ShowStrGray a string is defined as "/TmpStr 2 string def".
The "2" however should be "1" !

So, here is what should be changed:
8c8
< /TmpStr 2 string def
---
> /TmpStr 1 string def


Jos de Bruijne
Philips Research Labs
Eindhoven, Holland

Email: debr...@prles3.prl.philips.nl

0 new messages