cramer@optilink.UUCP (Clayton Cramer) (08/30/89)
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@phri.UUCP (Roy Smith) (08/31/89)
In <2251@optilink.UUCP> cramer@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- roy@alanine.phri.nyu.edu "The connector is the network"
labc-1aa@e260-2d.berkeley.edu (08/31/89)
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-1aa@web.Berkeley.edu
lemay@lorelei.Sun.COM (Laura Lemay) (08/31/89)
In <2251@optilink.UUCP> cramer@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%lorelei@sun.com Redhead. Drummer. Geek.
jcgs@wundt.harlqn.uucp (John Sturdy) (08/31/89)
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." jcgs@uk.co.harlqn (UK notation) jcgs@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
cplai@daisy.UUCP (Chung-Pang Lai) (08/31/89)
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.UUCP@uunet.UU.NET cplai%daisy@killer.DALLAS.TX.USA Daisy Systems Corp, 700B Middlefield Road, Mtn View CA 94039. (415)960-6961
hemphill@cit-vax.Caltech.Edu (Scott Hemphill) (09/01/89)
A general purpose Quicksort for your toolset. Scott Hemphill hemphill@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 hemphill@csvax.caltech.edu ...!ames!elroy!cit-vax!hemphill
greid@adobe.com (Glenn Reid) (09/02/89)
In article <1989Aug30.184607.21647@agate.uucp> labc-1aa@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
cet1@cl.cam.ac.uk (C.E. Thompson) (09/07/89)
In article <653@windy.dsir.govt.nz> SRWMCLN@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 (cet1@uk.ac.cam.phx)
SRWMCLN@windy.dsir.govt.nz (Clive Nicolson) (09/07/89)
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
debruyne@prles3.prl.philips.nl (09/07/89)
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: debruyne@prles3.prl.philips.nl
debruyne@prles3.prl.philips.nl (09/08/89)
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: debruyne@prles3.prl.philips.nl