Dieses Buch wird vielleicht nur der verstehen, der die Gedanken, die darin
ausgedrückt sind oder doch ähnliche Gedanken schon selbst einmal gedacht hat.
Es ist also kein Lehrbuch. Sein Zweck wäre erreicht, wenn es einem, der es mit
Verständnis liest, Vergnügen bereitete.
PostScript is one of the most
common (and in my opinion, successful) page description languages, used to produce
and print documents and graphics.
Since the '90s, when I got a paper copy of the second edition of the Red Book (i.e. the
PostScript® Language Reference, latest issue available
online), I found
it a great way of generating graphics, both programmatically as well as by
hand editing, both for real work and for fun.
Although I've never done such an ambitious thing as writing a word processor in PostScript (like Graham's Freeman Quikscript, which I - and knowingly or unknowingly many people in my institute - use to print ASCII files, e-mail, publication lists, etc.), I played around with PostScript a bit, and I would like to share my fun so that ... its purpose will be reached, if it will bring pleasure to an understanding reader (L. Wittgenstein, Tractatus Logico-philosophicus, Foreword)
%!PS-Adobe-2.0 % /Helvetica findfont 40 scalefont setfont 0 0 0 setrgbcolor 100 400 moveto (Hello World !) show showpage
%!PS-Adobe-2.0 % Prologue /BD {bind def} bind def /SRGB {setrgbcolor} BD /M {moveto} BD /ED {exch def} BD /H {/fs ED /Helvetica findfont fs scalefont setfont} BD % Body 40 H 0 0 0 SRGB 100 400 M (Hello World !) show 10 H 0 0 1 SRGB 100 390 M (a little blue world) show showpage
/BD {bind def} bind def /cm {2.54 div 72 mul} BD
Personally I find easier working in cm than in points (with the possible exception of defining scalable symbols, which can be defined with an "ideal size" of 1 point and then rescaled arbitrarily.
/ED {exch def} BD /H {/fs ED /Helvetica findfont fs scalefont setfont} BD /HB {/fs ED /Helvetica-Bold findfont fs scalefont setfont} BD /F {/fs ED /Times-Roman findfont fs scalefont setfont} BD /FB {/fs ED /Times-Bold findfont fs scalefont setfont} BD /FI {/fs ED /Times-Italic findfont fs scalefont setfont} BD /SB {/fs ED /Symbol findfont fs scalefont setfont} BD /HF {/fs ED /HumboldtFraktur findfont fs scalefont setfont} BD /Tib {/fs ED /Tibetan-ModernA findfont fs scalefont setfont} BD % % or also like this % /E {exch} BD /FFSFSF {E findfont E scalefont setfont} BD /FTB {/Times-Bold 80 FFSFSF} BD
/SRGB {setrgbcolor} BD /SL {setlinewidth} BD /M {moveto} BD /L {lineto} BD /RL {rlineto} BD /RM {rmoveto} BD /GS {gsave} BD /GR {grestore} BD /CPT {currentpoint translate} BD /CPTZ {currentpoint translate 0 0 M} BD /Dot {GS currentpoint 0.2 cm 0 360 arc 0 0 0 SRGB fill GR} BD
CPT is useful to reset the origin of all subsequent graphics to the last
point where one has moved (directly or implicitly). CPTZ will also
explicitly move to the new origin.
Read the Red Book about the graphic context (and saving and restoring it).
Dot is what I use for debugging. It will draw a little black filled circle at the current position, and can be used instead of any other more complex macro to be sure one is located where one should.
These for instance are some useful string macros to use instead of the plain (string) show which prints the string at the right of the current position.
/Rshow {GS 90 rotate show GR} BD /CS {dup stringwidth pop 2 div neg 0 RM} BD /RS {dup stringwidth pop neg 0 RM} BD /wosh {/c2 exch def /c1 exch def /stx exch def GS c1 SRGB stx true charpath fill GR GS 0 SL c2 SRGB stx true charpath stroke GR} BD /Wosh { {currentrgbcolor} {0 0 0} wosh} BD
The next is instead an example of a colour lookup table
The next example defines macros for paper orientation
/lut2 { [1 0 1 1 0 0 1 0.5 0 1 1 0 0 1 0 0 1 1 0 0 1] } def
/Pen2 { 3 mul lut2 exch 3 getinterval aload pop SRGB } BD
%--- standard portrait vs landscape orientation
/PORTRAIT { /PH 29.7 cm def /PL 21.0 cm def } BD
/LANDSCAPE { PORTRAIT PaperRotate } BD
/PaperRotate { PL 0 translate 90 rotate PL PH /PL exch def /PH exch def } BD
Let us assume you want to draw a birthday cake for somebody's birthday, and make it using his or her name. This is an idea of what I mean although it was not drawn for a real person but for ... a data format !
First of all some additional basic macros (self-explanatory), which will be used along with those defined above.
/TC {true charpath} BD /F {fill} BD /S {stroke} BD /D {def} BD /T {translate} BD /Sk {scale} BD /CP {closepath} BD /Ori {0 0 M} BD /OrR {Ori rotate} BD
Of course any drawiing should be enclosed in a rectangular background of a predefined
colour.
A decent way of doing it is to translate the origin at a given point of the
page, define the lower left and upper right corners of the interesting
area (and save them so that one can re-use them e.g. to locate the centre or draw a
grid), give a rectangular path and fill it.
3.5 cm 5 cm T 0.3 0.0 0.8 SRGB /llx 0 D /lly 0 D /urx 16.5 cm D /ury 16.5 cm D newpath llx lly M llx ury L urx ury L urx lly L CP F
In the prologue
define two macros with the name, filled or stroked in different colours, so one
can draw the name in one colour bordered with another colour.
Note that the code in the body above is wrapped in a pair of gsave and
grestore which relocates temporarily the origin at the centre, moves
there, and defines the font (macro FTB). Also colours fc and
pc are defined.
The next step to "project it in perspective" is simply to apply a distortion,
i.e. a simple 1 0.5 scale.
The layer is defined as a macro in view of future re-use.
One can then decide to overlap various layers simply translating them in the
vertical direction.
But since one wants the layers to alternate between two different colour patterns
we define an if-then-else operator which toggles trivially between two
families of colours. The flag set to true or false establishes the
colour of the first layer (try to change it !).
The candle is not drawn in an optimized way (ideally it should be defined in a
1-point size and made scalable and relocatable arbitrarily).
While you can see the effect in the complete file
I report its code here
Also define a macro M1 which puts on the stack the coordinates of
the centre of the rectangular area. The "logo" will be placed here.
One can then produce a decorative circle simply repeating the name in a loop at
e.g. 15 degrees from each other.
In this particular case the last "word" is repainted once in red colour.
% place this in the prologue
%
/FITS1 {GS OrR pc SRGB (FITS) TC S GR} BD
/FITS2 {GS OrR fc SRGB (FITS) TC F GR} BD
/FITS3 {-15 15 360 {dup FITS2 FITS1 } for 1 0 0 SRGB Ori (FITS) TC F 0 0 0 SRGB 0 FITS1} BD
/M1 {urx 2 div ury 2 div} BD
%
% place this in the body after the rectangle fill
%
/fc {1 0.5 0} D /pc {0 0 0} D
GS M1 T Ori FTB FITS3 GR
Since this would be a layer of the cake, we enclose the logo inside a (distorted).
circle of colour cc with border colour sc.
% append this in the prologue
%
/layer {GS M1 1 0.5 Sk % go to origin and distort x=1 y=0.5
0 0 6.4 cm 0 360 arc cc SRGB CP % define a circle
GS F GR % fill it with colour cc (and restore graphics context for re-use)
6 SL sc SRGB S % stroke its border with thickness 6
1 SL FTB FITS3 % draw the name logo (with thickness 1)
GR} BD
%
% replace this in the body after the rectangle fill
%
/fc {1 0.5 0} D /pc {0 0 0} D /cc {1 1 0} D /sc {0 1 1} D
GS M1 T layer GR
To do this we apply the modification in red, i.e.
we execute layer in a for-loop starting 100 points below and going up
by 20 points (for a total of 6 layers 100 80 60 40 20 0). We read the index
of the for loop from the stack using the roll operator.
% replace this in the prologue
%
/layer {GS M1 3 -1 roll sub T % shift down the origin by current for loop index
1 0.5 Sk % go to new origin and distort x=1 y=0.5
0 0 6.4 cm 0 360 arc cc SRGB CP % define a circle
GS F GR % fill it with colour cc (and restore graphics context for re-use)
6 SL sc SRGB S % stroke its border with thickness 6
1 SL FTB FITS3 % draw the name logo (with thickness 1)
GR} BD
%
% replace this in the body after the rectangle fill
%
/fc {1 0.5 0} D /pc {0 0 0} D /cc {1 1 0} D /sc {0 1 1} D
100 -20 0 {layer} for
% append this in the prologue
%
/toggle {flag {/fc {0.85 0.65 0.55} D /pc {0.3 1 0.3} D /cc {1 1 1} D /sc {1 0 0} D}
{/fc {1 0.5 0} D /pc {0 0 0} D /cc {1 1 0} D /sc {0 1 1} D} ifelse
flag not /flag E D } BD
%
% replace this in the body after the rectangle fill
%
/fc {1 0.5 0} D /pc {0 0 0} D /cc {1 1 0} D /sc {0 1 1} D
/flag true D
100 -20 0 {layer toggle} for
% append this in the prologue
%
/disc {GS 1 0.5 Sk 20 0 360 arc F GR} BD
/candle {GS M1 T Ori -20 0 L -20 150 L 20 150 L 20 0 L CP 0.95 0.95 0.8 SRGB F 0 0 disc 0.9 0.9 0.75 SRGB 0 300 disc GR } BD
/flame {GS M2 bc SRGB f f Sk 0 50 M 50 -10 -50 -10 0 50 curveto F GR} BD
%
% append this in the body after the cake stuff
%
candle
/bc {1 0.1 0} D /f 1.5 D flame
/bc {1 0.5 0} D /f 1.25 D flame
/bc {1 1 0} D /f 0.9 D flame
/bc {1 1 1} D /f 0.8 D flame
/bc {0.9 0.9 1} D /f 0.5 D flame
GS M2 0 10 5 15 4 arct 5 SL 0 0 0 SRGB S GR
The flame uses the curveto Bezier operator, and is repeated with different
colours and scaling factor to give the
Faradayan
effect of a candle flame with
temperature getting higher to the inside.
The last statement uses the arct operator to draw the wick.
It is left as an exercise to the reader to transform the candle in one of arbitrary
size and length and position, so you can make cakes for any birthday !
% place this in the prologue % /nome {(Anyname)} BD /FITS1 {GS OrR pc SRGB nome TC S GR} BD /FITS2 {GS OrR nome TC F GR} BD /FITS3 {15 15 345 { dup ini 1 add dup /ini E D Pen2 FITS2 FITS1 } for 0 0 0 SRGB Ori nome TC F /pc {1 0 0} def 0.5 SL 1 0 0 SRGB 0 FITS1} BD % % place this in the body % 3.5 cm 5 cm T /pc {0 0.5 0.5} D 0.1 SL FTB GS 7 cm 12 cm T 0.5 0.5 Sk /ini -1 def FITS3 GR
/FITS3 {30 30 330 { dup ini 1 add dup /ini E D Pen2 FITS2 FITS1 } for 1 0 0 SRGB Ori nome TC F 1 0 0 SRGB 0 FITS1} BD
We then have a loop of 90 steps which rotate the (elliptically distorted) inscription by 20 degrees at each step, shrinking the length of the inscription and moving it up in such a way to emulate a spiral tree. The colour shade is changed at each step. In the full code the last word is redrawn in an altogether different colour (bright green) but this is not shown in the excerpt code.
% % place in the prologue ----------------------------------------------- /nome {(Aname)} BD /FITS1 {GS pc SRGB nome TC S GR} BD /FITS2 {GS fc SRGB nome TC F GR} BD /FITS3 {FITS2 FITS1} BD /le {5.7 1 div} D % initialize default scale length for CHARACTER 5.7 cm /item {GS CPT Ori 1 cm 0 M le 6. div 1 Sk FITS3 GR} BD % this gives a "conic" spiral % horizontal shrinking law (on loop index a) ................................................. /Sh {2.71828 1. a 1800 div sub exp 0.35 mul 6 mul /le E D} BD % shrinking law exponential % vertical raising law (on loop index a) .................................................... /step {60 sin 0.53 mul} D /zero {80 step mul} D /Up {0 a step mul zero sub} BD % raising law /US {Up RM Sh} BD % raise and shrink % colour modification (pseudo-LUT) .......................................................... /Fc {/k 1 a 1800 div sub D /fc {1 k 0} D} BD % yellow 1 1 0 to red 1 0 0 % place in the body ------------------------------------------------------------------------ GS 1 0.5 Sk 20 20 1800 {/a E D GS US a neg rotate Fc item GR} for % draw the spiral GR
Macro Fc redefines the filling colour fc as a function of the loop index a. The example shown moves from full yellow to red in various shades of orange. The full code contains also (commented) other examples of colour variations.
/vertex {/ang E D radius 90 ang sub cos mul radius 90 ang sub sin mul} BD /pentagon {GS CPT 0 vertex M 72 72 360 {vertex L} for S GR} BD
/pentagon2 {side 2 div neg side 2 div 54 cos div 54 sin mul neg RM 0 1 4 {side 0 RL 72 rotate} for } BD
1 0 0 SRGB /side 4 cm D 2 SL 7 cm 10 cm M pentagon2 S 0 0 0 SRGB /radius 4 cm D 1 SL 7 cm 10 cm M pentagon
/side 3 cm D /radius side 2 div 54 cos div D 1 1 0 SRGB 3 SL 7 cm 10 cm M pentagon2 F 0 1 0 SRGB 3 SL 7 cm 10 cm M pentagon
/vertex {/ang E D radius 90 ang sub cos mul radius 90 ang sub sin mul} BD /polygon {/n E D /cang 360 n div D GS CPT 0 vertex M cang cang 360 {vertex L} for S GR} BD /polygon2 {/n E D /cang 360 n div D /bang 180 cang sub 2 div D side 2 div neg side 2 div bang cos div bang sin mul neg RM 0 1 n 1 sub {side 0 RL cang rotate} for
% macro %SRGB x y size star % where size is implicitly in cm % while the units of xy must be explicitated /star { cm /size E D /dbx size 2 div neg D /dby dbx 18 sin 18 cos div mul neg D M GS CPT ang rotate dbx dby M size 0 RL 0 1 3 {pop 144 neg rotate size 0 RL} for CP F GR } BD %- place in body ------------------------------------------------------------- /ang 0 def 1 0 0 SRGB 7 cm 10 cm 4 star
% macro %SRGB x y size nsides star /star {/nsides E D nsides 2 sub nsides div 180 mul /alpha E D 180 alpha sub 2 div /beta E D beta 2 div /halfbeta E D 180 beta sub /compbeta E D cm /size E D /dbx size 2 div neg D /dby dbx halfbeta sin halfbeta cos div mul neg D M GS CPT ang rotate dbx dby M size 0 RL 0 1 nsides 2 sub {pop compbeta neg rotate size 0 RL } for CP F GR } BD %- place in body ------------------------------------------------------------- /ang 0 def 1 0 0 SRGB 7 cm 10 cm 4 5 star
One other reason is that connecting every other vertex generates a closed-loop
star for polygons with odd number of sides, but for large number of sides one can also
consider stars connecting every third vertex or fourth, fifth etc.
So one can replace the rotation angle with p*360/n where the multiplier p is like
saying "connect every other p points". The default case is p=2.
With a large number of sides n, p=2 or close to it gives a less pointed star, close
to a circle or polygon, while p close to n/2 gives a spiky star.
% macro %SRGB x y size sides multip star2 % wants an extra arg. The simple formula is correct by chance for sides=5 % but otherwise it is better to rotate by p*360/n % where the multiplier has the effect of deepening the points of the star % however varying the multip p one has to shift the centre % /star2 {/multip E D /nsides E D nsides 2 sub nsides div 180 mul /alpha E D 180 alpha sub 2 div /beta E D beta 2 div /halfbeta E D % rotation angle is p*360/n sides multip 360 mul nsides div /compbeta E D cm /size E D /dbx size 2 div neg D /dby dbx halfbeta sin halfbeta cos div mul neg D M GS CPT ang rotate dbx dby M size 0 RL 0 1 nsides 2 sub {pop compbeta neg rotate size 0 RL } for CP F GR } BD %- place in body ------------------------------------------------------------- /ang 0 def 1 0 0 SRGB 7 cm 10 cm 4 5 2 star2
So one has to generate a full, more complex macro.
For historical reasons the two arguments n and p are reversed, and the positioning and
eventual orientation is done outside of the macro. So a
2.5 2 5 multistar
is a 2.5 cm 5-pointed star (the 3,5 and 2,5 cases differ only in orientation), while a
3.5 21 47 multistar
is a larger, 47-point spiky star.
Note that using a p=1 will simply as in
3.5 1 12 multistar
will just produce a polygon, not a star.
% macro % radius Delta nside multistar % nside = number of sides of polygon % Delta = 1 ... n/2 number of jumps (1 polygon, 2 not so pointed star, n/2 spiky) % radius in cm /multistar { /nside E D /Delta E D cm /rad E D % reserve space for arrays /theta nside array def /X nside array def /Y nside array def % arrays indexed 0..n-1 ... fill them 0 1 nside 1 sub {/i E D theta i 360 nside div i mul put } for 0 1 nside 1 sub {/i E D X i theta i get cos put } for 0 1 nside 1 sub {/i E D Y i theta i get sin put } for % XY indexed 1..n /XY {1 sub /i E D X i get rad mul Y i get rad mul} BD 1 /j E D -1 /jold E D 0 /K E D 1 /TEST E D newpath 1 1 nside 1 add { /i E D i 1 sub Delta mul nside mod 1 add K add /j E D i 1 eq {j XY M} {j XY L} ifelse jold j gt j TEST eq and i nside 1 add ne and {j 1 add /TEST E D K 1 add /K E D TEST XY M } {} ifelse j /jold E D } for fill } BD %- place in body ------------------------------------------------------------- 1 0 0 SRGB 7 cm 10 cm M 2.0 2 5 GS CPT multistar GR
The code shown above may not be immediately obvious, but what it does is to compute
preliminarily all the vertex coordinates in arrays X and Y, while
macro XY returns both coordinates, scaled for the star radius, onto the
stack.
Then it loops among the n vertices skipping p modulo n, which can generate a single
closed sequence like 1, 4, 7, 10, 13, 16, 3, 6, 9, 12, 15, 2, 5, 6, 11, 14, 1
for the case 16,3, or a combination of more independent closed sequences, like
(1, 5, 9, 13, 1), (2, 6, 10, 14, 2), (3, 7... 3) and (4, 8... 4) for 16.4.
It moves to the first vertex of each closed sequence, and then joins it to
the next ones.
Incidentally this means the path is suitable for filling but not for stroking
(or at least, stroking it will not draw the outline but connect the vertices in the
given order).
The code above is a nice example of usage of the conditional ifelse operator.
0 1 23 { /inx E D inx P2 7 cm 10 cm 46 inx 2 mul sub 10 div star} for
/mulfac 10 D 0 1 23 { /inx E D inx P2 7 cm 10 cm M GS CPT 0 0 46 inx 2 mul sub 10 div inx mulfac mul rotate star GR} for
% macro % mulfac Radius delta mside multirota % as multistar but rotated by mulfac at each step in a loop changing the LUT /multirota { /mside E D /delta E D /Rad E D /mulfac E D 0 1 23 { /inx E D inx P2 0 0 M GS CPT inx mulfac mul rotate 23 inx sub 900 div Rad mul delta mside multistar GR } for } BD %- place in body ------------------------------------------------------------- 7 cm 10 cm M 10 3.2 cm 2 5 GS CPT multirota GR
0 1 23 { /inx E D inx P2 3 cm 19 cm 46 inx 2 mul sub 10 div star} for % coefficients of the parabola /para -0.0014 D /parb 0.5 D % expansion factor for sinusoid amplitude /tfac 100 D 22 -1 0 { /inx E D 0.1 cm inx 0.05 mul cm add 3 div /wid E D % width of strip depends on loop index newpath 3 cm 19 cm M % move to origin GS CPT 0 0 M 0 1 4500 { dup sin 0.1 cm mul E 180 div 0.5 cm mul E /y E D /x E D % strip upper border, compute sinusoidal modulation x y x x mul para mul add x parb mul add wid x tfac div mul add L } for % and apply it to parabola 4500 -1 0 { dup sin 0.1 cm mul E 180 div 0.5 cm mul E /y E D /x E D % return on strip lower border x y x x mul para mul add x parb mul add wid x tfac div mul sub L } for % and apply it to parabola CP inx P2 F GR } for
It is left as an exercise to the reader to transfor this in a general purpose macro.
0 1 23 { /inx E D inx P2 3 cm 19 cm 46 inx 2 mul sub 10 div star} for % coefficients of the log spiral /para 0.65 cm D /parb 1.42 D /nstep 450 D /dtor 0.0174533 D % expansion factor for sinusoid amplitude /tfac 100 D 22 -1 0 { /inx E D 0.1 cm inx 0.02 mul cm add 3 div D % width of strip depends on loop index newpath 3 cm 19 cm M % move to origin GS CPT 0 0 M 0 1 nstep { % strip upper border /theta E D para parb theta dtor mul exp mul /erre E D erre theta cos mul para sub wid theta tfac div mul theta cos mul add erre theta sin mul neg wid theta tfac div mul theta sin mul sub L } for nstep -1 0 { % return on strip lower border /theta E D para parb theta dtor mul exp mul /erre E D erre theta cos mul para sub wid theta tfac div mul theta cos mul sub erre theta sin mul neg wid theta tfac div mul theta sin mul add L } for inx P2 F GR } for
Once one has defined in Ref the coordinates of the vertex of the
spiral, one in principle invokes several turns of the spiral giving the
start and end angles like
θ1 θ2 Spir
This is shown for θ1=0 θ2=990 in the topmost case in the
example. This is however not nice looking because
the most recent part of the ribbon is drawn over the least recent, losing
the 3-d effect.
To achieve a3-d effect one should split the ribbon in several invocation
of Spir, first all the parts on the back, and then all the parts
on the front. This is shown at the end of the code here below, or as the
bottom-most case in the example.
As for the "star tails" shown above, the code works in three loops, an outer loop on the colour in the LUT, with the strip width function of the loop index, and two parallel inner loops going forward from θ1 to θ2 along the upper border, and backward from θ2 to θ1 along the lower border. A service macro LorM connects a line with pen down or moves with pen up depending whether one is at the end or in the middle of the loop (i.e. conditionally invokes macros L or M).
Some quantities are hardcoded as variable values or constants:
rad is the radius of the spiral at the top
dy controls (indirectly) the height of the ribbon (the actual height
of course varies with the index of the colour LUT loop) via an initial scaling
factor 0.01
a pass scaling factor 0.007 cm controls the
pass of the spiral, and hence its total height
an horizontal scaling factor 2.5 controls
the width of the spiral
% macros % service macro Line or Move /LorM {sta inx ne {L} {M} ifelse } BD % spiral ribbon % theta1 theta2 Spir % part of spiral ribbon % dy is the height of the ribbon % rad is the radius % 0.007 and 2.5 control the total height /Spir {/fin E D /sta E D newpath Ref M 1 1 1 SRGB /rad 0.5 cm def 22 -1 0 { /jnx E D jnx P2 /dy 0.01 jnx mul cm D % width of strip depends on loop index GS CPT 2.5 1 scale 0 0 M 0 rad RM sta 1 fin {/inx E D /rrad rad 1 inx 540 div add mul D % strip upper border rrad inx sin mul rrad inx cos mul 0.007 cm inx mul sub dy inx 1440 div 1 add mul sub LorM } for fin -1 sta {/inx E D /rrad rad 1 inx 540 div add mul D % return on strip lower border rrad inx sin mul rrad inx cos mul 0.007 cm inx mul sub dy inx 1440 div 1 add mul add LorM } for fill GR } for } BD %- place in body --------------------------------------------------------------------------- /Ref {7 cm 22 cm} BD 0 990 Spir % all turns of the ribbon, not nice looking /Ref {7 cm 12 cm} BD 0 90 Spir 270 450 Spir 630 810 Spir 990 1170 Spir % draw first the parts on the back 90 270 Spir 450 630 Spir 810 990 Spir 1170 1340 Spir % and then those on the front
The realization occurs using a service macro
θ lemnipt, which computes
the lemniscate as parametric equation in θ.
For a full lemniscate θ ranges a full turn from 0 to 360 degrees.
To make a lemniscate multicolour ribbon one may wrap the invocation of the
entire curve inside a loop in a colour LUT (similar to the ribbons and tails
shown above, or see full code of the exanple.
% macros % service macro: single lemniscate point /lemnipt { 90 add /Lt E D Lt sin Lt sin mul 1 add /Lden E D Lt cos La mul 2 sqrt mul Lden div Lt sin Lt cos mul La mul 2 sqrt mul Lden div } BD % fromangle toangle lemnisc /lemnisc { /LTo E D /LFrom E D GS CPT LFrom lemnipt M LFrom 1 LTo { lemnipt L } for stroke GR } BD %- place in body --------------------------------------------------------------------------- 3.0 cm /La E D 9 cm 20 cm M 1 0 0 SRGB 0 360 lemnisc 9 cm 5 cm M 0 0 1 SRGB 10 SL -175 175 lemnisc
% macros % constants /focus { 6 cm 12.0 cm} BD %focus 4.5 cm /maja E D 3.0 cm /minb E D %major minor axes 1 minb minb mul maja maja mul div sub sqrt /ecc E D %eccentricity maja minb div minb div /coeff E D %C coefficient 1 1 ecc add coeff mul div /r1 E D %r1 of periastron 1 1 ecc sub coeff mul div /r2 E D %r2 of apoastron % macro "point of ellipse at theta" /ellipsept {/theta E D theta cos ecc mul 1 add coeff mul 1 exch div /r E D r theta cos mul r theta sin mul} BD %- place in body --------------------------------------------------------------------------- focus M GS CPT Ori 0 ellipsept M 0 0.5 360 {ellipsept L} for stroke GR
For instance in the example
4.0 cm 1.5 cm M 3.0 cm 3.5 cm 9.0 cm 10.0 cm 8.0 cm 13.0 cm curveto stroke
corresponds to the white curve inside the thick maroon "branch" (which is
obtained by two slightly offset Bezier curves). The gray lines connect the four
control points.
So far so good, drawing Bezier curves seems just to require a bit of black magic
and proceed by trial and error until one obtains the wished shape.
But now let us assume we have drawn the wished curve, and want to place something
at specific positions along it.
We'd want to invert the two third-degree polynomials x(t) and y(t) which
define the curve (see description of curveto in the
Red Book) and
write a t Bezier macro which
returns the x,y position at parameter t=0.0-1.0.
For instance in the example shown we place one "long needle" at t=0.0 on the right of
the "branch", two needles on both sides at t=1.0, and shorter needles at t=0.1...0.4
(on the right) and 0.5...0.9 (on the left).
(The needles themselves are obtained filling a couple of other Bezier curves with
tunable parameters, but this is outside the scope of this demo (but the entire code is
accessible, just look inside the source file).
% macros % branch (prepare) % 4.0 cm 1.5 cm M 3.0 cm 3.5 cm 9.0 cm 10.0 cm 8.0 cm 13.0 cm curveto stroke % x0 y0 x1 y1 x2 y2 x3 y3 % hardcode Bezier coeff /Bcx 3.0 4.0 sub cm 3 mul def /Bcy 3.5 1.5 sub cm 3 mul def /Bbx 9.0 3 mul 3.0 3 mul sub cm Bcx sub def /Bby 10. 3 mul 3.5 3 mul sub cm Bcy sub def /Bax 8.0 4.0 sub cm Bcx sub Bbx sub def /Bay 13. 1.5 sub cm Bcy sub Bby sub def % t Bezier with t 0.0-1.0 /Bezier {/t E D Bax t mul t mul t mul Bbx t mul t mul add Bcx t mul add 4.0 cm add Bay t mul t mul t mul Bby t mul t mul add Bcy t mul add 1.5 cm add } BD %- place in body --------------------------------------------------------------------------- colour SRGB 4.0 cm 1.5 cm M 3.0 cm 3.5 cm 9.0 cm 10.0 cm 8.0 cm 13.0 cm curveto S 0.6 Bezier M GS CPT draw a needle GR
If one wants to write in an arbitrary alphabet, one can look for True Type fonts on the network. One will typically obtain a file with extension .ttf.
Next one should procure an utility called ttf2pt1 (e.g. look on sourceforge). Compile it and use it like
ttf2pt1 -e file.ttf fileThis will generate two files file.afm and file.pfa
The .pfa files (quite bulky) can be incorporated into your PostScript file
(even at the very beginning, appending your code at the end).
You should take note of the value on the resource line named
/FontName:
the string between /FontName and def is the name of interest.
Then in your file you should define a macro e.g.
/myfont {/fs exch def name findfont fs scalefont setfont} BD
But what if the font is in a foreign alphabet ? You can use the octal representation for the characters you need, e.g. (\060\061\364) show.
But how can you know the octal representation for your alphabet ? You can write a test program like this, after the font pfa file and the standard macros define
/T {name findfont 30 scalefont setfont} BD /F {/Times-Roman findfont 30 scalefont setfont} BD /C {{/myh exch def /mys exch def gsave currentpoint translate T myh true charpath stroke 30 0 moveto F mys show grestore 0 30 neg rmoveto} BD
In practice you invoke the macro in a sequence, e.g. this (when completed !!) will put the characters on 5 columns on two pages
20 800 moveto 0 setgray (000) (\000) C (001) (\001) C (002) (\002) C (003) (\003) C (004) (\004) C (005) (\005) C (006) (\006) C (007) (\007) C (010) (\010) C (011) (\011) C ... (031) (\031) C 100 800 moveto (032) (\032) C ... (063) (\063) C 180 800 moveto (064) (\064) C ... (115) (\115) C 260 800 moveto (116) (\116) C ... (147) (\147) C 340 800 moveto (150) (\150) C (177) (\177) C showpage 20 800 moveto (200) (\200) C ... (377) (\377) C showpage