Table of Contents

1. Painting

Painting is the process of rendering paths and other graphics to the output device. Understanding painting operators is crucial for making your PostScript paths visible.

1.1. Overview

PostScript provides three main painting operations:

  • Stroking - Drawing the outline of a path

  • Filling - Filling the interior of a closed path

  • Clipping - Restricting where subsequent graphics can appear

Each operation consumes the current path, so you must reconstruct the path if you need to paint it multiple ways.

1.2. Stroke Operations

1.2.1. Basic Stroking: stroke

Draws the outline of the current path using current graphics state:

newpath
100 100 moveto
200 100 lineto
200 200 lineto
100 200 lineto
closepath

2 setlinewidth
stroke  % Path is consumed (cleared)

The appearance is controlled by:

  • Line width (setlinewidth)

  • Line cap (setlinecap)

  • Line join (setlinejoin)

  • Dash pattern (setdash)

  • Current color

1.2.2. Preserving Path After Stroke

The path is consumed by stroke, so use gsave/grestore:

newpath
100 100 moveto
200 200 lineto

% Stroke without losing path
gsave
  stroke
grestore

% Path still exists
2 0 0 setrgbcolor  % Change to red
stroke             % Stroke again

Or rebuild the path:

% Better: define path as procedure
/myPath {
  newpath
  100 100 moveto
  200 200 lineto
} def

myPath stroke
myPath 1 0 0 setrgbcolor stroke

1.2.3. Stroking Rectangles: rectstroke

Optimized operator for rectangular strokes:

% rectstroke: x y width height rectstroke
100 100 200 150 rectstroke

% Equivalent to:
% newpath
% 100 100 moveto
% 200 0 rlineto
% 0 150 rlineto
% -200 0 rlineto
% closepath
% stroke

1.2.4. User Space Stroking: ustroke

Strokes in user space (Level 2+), ignoring current transformation:

gsave
  % Even with transformations
  200 200 translate
  2 2 scale

  newpath
  0 0 50 0 360 arc

  % Stroke at original line width
  ustroke
grestore

1.2.5. Stroke Path Outline: strokepath

Converts stroke to fillable path:

newpath
100 100 moveto
200 200 lineto

10 setlinewidth
strokepath  % Path is now the outline

0.8 0.8 0.8 setrgbcolor
fill        % Fill the stroke outline

1.3. Fill Operations

1.3.1. Basic Filling: fill

Fills the interior of the current path:

newpath
100 100 moveto
200 100 lineto
200 200 lineto
100 200 lineto
closepath

0.8 0.2 0.2 setrgbcolor
fill  % Path is consumed

1.3.2. Fill Rules

PostScript uses the non-zero winding number rule by default:

% Non-zero winding number (default)
newpath
% Outer rectangle (counterclockwise)
100 100 moveto
300 100 lineto
300 300 lineto
100 300 lineto
closepath

% Inner rectangle (same direction)
150 150 moveto
250 150 lineto
250 250 lineto
150 250 lineto
closepath

fill  % Both filled (same winding direction)

1.3.3. Even-Odd Fill: eofill

Uses the even-odd rule for filling:

% Even-odd rule
newpath
% Outer rectangle
100 100 moveto
300 100 lineto
300 300 lineto
100 300 lineto
closepath

% Inner rectangle (creates hole)
150 150 moveto
250 150 lineto
250 250 lineto
150 250 lineto
closepath

eofill  % Inner is a hole (odd/even crossings)

Visual comparison:

% Create star with crossing lines
/star {
  newpath
  200 300 moveto
  250 150 lineto
  100 220 lineto
  300 220 lineto
  150 150 lineto
  closepath
} def

% Non-zero winding
gsave
  star fill
grestore

% Even-odd (creates holes at intersections)
gsave
  100 0 translate
  star eofill
grestore

1.3.4. Filling Rectangles: rectfill

Optimized operator for rectangular fills:

% rectfill: x y width height rectfill
100 100 200 150 rectfill

% Can fill multiple rectangles at once (Level 2+)
% [x1 y1 w1 h1 x2 y2 w2 h2 ...] rectfill

1.3.5. User Space Filling: ufill and ueofill

Fills in user space (Level 2+):

gsave
  2 2 scale

  newpath
  50 50 moveto
  100 50 lineto
  100 100 lineto
  closepath

  % Fills at user space coordinates
  ufill
grestore

1.4. Combined Fill and Stroke

1.4.1. Fill Then Stroke

Common pattern for outlined shapes:

% Define path once
/myShape {
  newpath
  200 200 100 0 360 arc
} def

% Fill
myShape
0.8 0.8 1 setrgbcolor
fill

% Stroke
myShape
0 0 0.5 setrgbcolor
2 setlinewidth
stroke

Using gsave/grestore:

newpath
200 200 100 0 360 arc

% Fill preserving path
gsave
  0.8 0.8 1 setrgbcolor
  fill
grestore

% Stroke
0 0 0.5 setrgbcolor
2 setlinewidth
stroke

1.4.2. Inline Fill and Stroke

% Single path, multiple renders
newpath
100 100 moveto
300 100 lineto
200 250 lineto
closepath

% Fill
gsave
  1 0.8 0.6 setrgbcolor
  fill
grestore

% Stroke
0 0 0 setrgbcolor
3 setlinewidth
stroke

1.5. Clipping Operations

1.5.1. Basic Clipping: clip

Intersects current clipping path with current path:

gsave
  % Set clipping path
  newpath
  200 200 100 0 360 arc
  clip
  newpath  % Clear path after clip

  % Only visible inside circle
  100 100 200 200 rectfill
grestore
% Clipping restored

Important: Always use newpath after clip to avoid clipping to an empty path.

1.5.2. Even-Odd Clipping: eoclip

Clips using even-odd rule:

gsave
  % Create clipping path with hole
  newpath
  100 100 moveto
  300 100 lineto
  300 300 lineto
  100 300 lineto
  closepath

  150 150 moveto
  250 150 lineto
  250 250 lineto
  150 250 lineto
  closepath

  eoclip
  newpath

  % Visible in outer, not inner
  50 50 250 250 rectfill
grestore

1.5.3. Rectangular Clipping: rectclip

Optimized for rectangular clipping regions:

gsave
  % rectclip: x y width height rectclip
  100 100 200 150 rectclip

  % Draw - only visible in rectangle
  0 0 400 400 rectfill
grestore

1.5.4. Getting Clipping Path: clippath

Makes clipping path the current path:

% Get current clipping boundary
newpath
clippath

% Stroke it to visualize
0.5 setgray
0.5 setlinewidth
stroke

1.5.5. Nested Clipping

Clipping regions accumulate (intersect):

gsave
  % First clip
  100 100 200 200 rectclip

  gsave
    % Second clip (intersection)
    150 150 100 100 rectclip

    % Only visible in intersection
    0 0 400 400 rectfill
  grestore
grestore

1.6. Painting Strategies

1.6.1. Strategy 1: Path Reuse with Procedures

/triangle {
  newpath
  100 100 moveto
  200 100 lineto
  150 200 lineto
  closepath
} def

% Use multiple times
triangle 1 0.8 0.6 setrgbcolor fill
triangle 0 0 0 setrgbcolor 2 setlinewidth stroke

1.6.2. Strategy 2: Layered Drawing

Draw from back to front:

% Background
0.9 0.9 0.9 setrgbcolor
0 0 612 792 rectfill

% Middle layer
0.8 0.8 1 setrgbcolor
100 100 200 200 rectfill

% Foreground
1 1 0.8 setrgbcolor
150 150 100 100 rectfill

% Outline on top
0 0 0 setrgbcolor
1 setlinewidth
100 100 200 200 rectstroke

1.6.3. Strategy 3: Masking with Clipping

gsave
  % Create mask
  newpath
  /Helvetica-Bold findfont 72 scalefont setfont
  100 200 moveto
  (MASK) true charpath
  clip
  newpath

  % Draw background visible through text
  0 10 792 {
    /y exch def
    y 100 mod 100 div dup 0 setrgbcolor
    0 y 612 1 rectfill
  } for
grestore

1.6.4. Strategy 4: Compound Shapes

% Create complex shape with multiple subpaths
newpath
% Outer circle
200 200 100 0 360 arc

% Inner circle (hole) - reverse direction
200 200 50 0 360 arcn

% Fill creates donut
eofill

1.7. Advanced Painting Techniques

1.7.1. Gradient Fills (Simulated)

% Simple horizontal gradient
/hgradient {  % x y width height -> -
  4 dict begin
    /h exch def
    /w exch def
    /y exch def
    /x exch def

    0 1 w {
      /i exch def
      i w div setgray
      x i add y 1 h rectfill
    } for
  end
} def

100 100 200 100 hgradient

Radial gradient (simplified):

/radialgradient {  % x y maxRadius -> -
  3 dict begin
    /mr exch def
    /cy exch def
    /cx exch def

    mr -1 0 {
      /r exch def
      r mr div setgray
      newpath
      cx cy r 0 360 arc
      fill
    } for
  end
} def

200 200 100 radialgradient

1.7.2. Pattern Fills

Using clipping for pattern fills:

/dotPattern {
  gsave
    % Set up pattern
    10 10 scale
    0 1 10 {
      /y exch def
      0 1 10 {
        /x exch def
        x y 0.3 0 360 arc
        fill
      } for
    } for
  grestore
} def

% Apply pattern to shape
gsave
  newpath
  200 200 100 0 360 arc
  clip
  newpath
  dotPattern
grestore

1.7.3. Multi-Pass Rendering

Render same path with different effects:

/myPath {
  newpath
  200 200 100 0 360 arc
} def

% Pass 1: Shadow
gsave
  205 195 translate
  myPath
  0.7 setgray
  fill
grestore

% Pass 2: Fill
myPath
1 0.8 0.6 setrgbcolor
fill

% Pass 3: Highlight
gsave
  myPath
  clip
  newpath
  190 220 30 0 360 arc
  1 1 1 setrgbcolor
  fill
grestore

% Pass 4: Outline
myPath
0 0 0 setrgbcolor
2 setlinewidth
stroke

1.7.4. Transparency Simulation

Use patterns or gray levels to simulate transparency:

% Crosshatch pattern for transparency
/transparent {  % density (0-1)
  1 dict begin
    /d exch def

    gsave
      % Create fine crosshatch
      0.5 setlinewidth
      0 2 100 {
        dup 0 moveto 100 lineto
      } for
      0 2 100 {
        dup 0 exch moveto 100 exch lineto
      } for
      stroke
    grestore
  end
} def

1.8. Practical Painting Examples

1.8.1. Example 1: Button with Border

/button {  % x y width height label
  5 dict begin
    /label exch def
    /h exch def
    /w exch def
    /y exch def
    /x exch def

    % Background
    0.9 0.9 0.9 setrgbcolor
    x y w h rectfill

    % Border
    0 0 0 setrgbcolor
    1 setlinewidth
    x y w h rectstroke

    % Text
    /Helvetica findfont 12 scalefont setfont
    x w 2 div add y h 2 div add moveto
    label dup stringwidth pop 2 div neg 0 rmoveto
    show
  end
} def

100 100 100 40 (Click Me) button

1.8.2. Example 2: Pie Chart

/pieSlice {  % cx cy r startAngle endAngle color
  6 dict begin
    /color exch def
    /ea exch def
    /sa exch def
    /r exch def
    /cy exch def
    /cx exch def

    newpath
    cx cy moveto
    cx cy r sa ea arc
    closepath

    % Fill
    color aload pop setrgbcolor
    gsave fill grestore

    % Outline
    0 0 0 setrgbcolor
    1 setlinewidth
    stroke
  end
} def

% Draw pie chart
200 200 100 0 90 [1 0.8 0.8] pieSlice
200 200 100 90 180 [0.8 1 0.8] pieSlice
200 200 100 180 270 [0.8 0.8 1] pieSlice
200 200 100 270 360 [1 1 0.8] pieSlice

1.8.3. Example 3: Progress Bar

/progressBar {  % x y width height percent
  5 dict begin
    /pct exch def
    /h exch def
    /w exch def
    /y exch def
    /x exch def

    % Background
    0.9 0.9 0.9 setrgbcolor
    x y w h rectfill

    % Progress
    0.2 0.6 1 setrgbcolor
    x y w pct mul h rectfill

    % Border
    0 0 0 setrgbcolor
    1 setlinewidth
    x y w h rectstroke
  end
} def

100 100 200 30 0.75 progressBar  % 75% complete

1.8.4. Example 4: Drop Shadow

/shapePath {
  newpath
  200 200 moveto
  300 200 lineto
  250 300 lineto
  closepath
} def

% Shadow
gsave
  5 -5 translate
  shapePath
  0.5 setgray
  fill
grestore

% Shape
shapePath
1 0.8 0.6 setrgbcolor
fill

% Outline
shapePath
0 0 0 setrgbcolor
2 setlinewidth
stroke

1.9. Best Practices

1.9.1. Separate Path and Paint

% Good: reusable path
/myShape {
  newpath
  100 100 moveto
  200 200 lineto
} def

myShape stroke
myShape fill

% Bad: path and paint mixed
/myShape {
  newpath
  100 100 moveto
  200 200 lineto
  stroke  % Can't reuse for fill
} def

1.9.2. Use Graphics State for Complex Painting

% Good: isolated state changes
gsave
  1 0 0 setrgbcolor
  3 setlinewidth
  newpath
  100 100 200 200 rlineto
  stroke
grestore
% State restored

% Bad: permanent changes
1 0 0 setrgbcolor
3 setlinewidth
% ... affects everything after

1.9.3. Order Operations Correctly

% Correct order:
% 1. Build path
newpath
200 200 100 0 360 arc

% 2. Set graphics state
1 0.8 0.6 setrgbcolor
2 setlinewidth

% 3. Paint
stroke

% Wrong: setting state after painting
newpath
200 200 100 0 360 arc
stroke
1 0.8 0.6 setrgbcolor  % Too late!

1.9.4. Always Clear Path After Clip

% Correct
newpath
200 200 100 0 360 arc
clip
newpath  % Essential!

% Wrong
newpath
200 200 100 0 360 arc
clip
% Current path is now empty AND clip path

1.10. Common Pitfalls

1.10.1. Path Consumed by Paint

% Wrong: path lost after stroke
newpath
100 100 moveto
200 200 lineto
stroke
fill  % ERROR: nothing to fill

% Correct: save path or rebuild
/myPath {
  newpath
  100 100 moveto
  200 200 lineto
} def

myPath stroke
myPath fill

1.10.2. Clipping Without Restore

% Wrong: clip affects all subsequent drawing
newpath
200 200 100 0 360 arc
clip
newpath
% ... everything clipped forever

% Correct: use gsave/grestore
gsave
  newpath
  200 200 100 0 360 arc
  clip
  newpath
  % ... clipped region
grestore
% Clipping restored

1.10.3. Fill vs. EOfill Confusion

% Shape with hole
newpath
% Outer
100 100 300 300 rectstroke
% Inner (same direction)
150 150 200 200 rectstroke

fill    % Both filled (non-zero rule)
eofill  % Inner is hole (even-odd rule)

1.11. Performance Considerations

1.11.1. Use Optimized Operators

% Faster: optimized operators
100 100 200 150 rectfill
100 100 200 150 rectstroke

% Slower: manual path construction
newpath
100 100 moveto
200 0 rlineto
0 150 rlineto
-200 0 rlineto
closepath
fill

newpath
100 100 moveto
200 0 rlineto
0 150 rlineto
-200 0 rlineto
closepath
stroke

1.11.2. Minimize State Changes

% Good: batch similar operations
1 0 0 setrgbcolor
shape1 fill
shape2 fill
shape3 fill

% Less efficient: frequent state changes
1 0 0 setrgbcolor shape1 fill
0 1 0 setrgbcolor shape2 fill
0 0 1 setrgbcolor shape3 fill

1.11.3. Cache Complex Paths

% Good: define once, use many times
/complexShape {
  newpath
  % ... many path operations
} def

complexShape fill
complexShape stroke

% Bad: rebuild every time
newpath
% ... many path operations
fill
newpath
% ... rebuild same path
stroke

1.12. See Also


Back to top

Copyright © 2025 Ribose. PostScript is a trademark of Adobe. Distributed under the MIT License.