On this page:
parse-dasharray
parse-linecap
parse-linejoin
parse-clip-rule
dc-path->polylines
dash-split-polyline

7 Stroke Geometry🔗ℹ

racket/draw’s pen% has no concept of a dash pattern at all, so drawing a dashed stroke means computing the dashed sub-segments directly: flatten a dc-path%’s curves into a polyline, then split that polyline into the "on" runs a dash pattern would draw. dc-path->polylines and dash-split-polyline are the two pieces of that pipeline, exported separately in case you want polyline flattening or dash-splitting without the other.

parse-linecap, parse-linejoin, and parse-clip-rule are small, self-contained parsers converting SVG’s own attribute-value keywords into the symbols racket/draw expects.

procedure

(parse-dasharray s)

  (or/c (listof (and/c real? (not/c negative?))) #f)
  s : (or/c string? #f)
Parses a stroke-dasharray value — a comma-or-whitespace-separated list of lengths — into a list of non-negative numbers, always of even length (an odd-length list is doubled, per spec, so "5 10 15" becomes '(5 10 15 5 10 15)). Returns #f for "none", empty input, a list containing a negative number, or a list that sums to zero — all of which mean "no dashing, draw a normal solid stroke" per spec, which is exactly the signal #f is meant to carry to a caller deciding whether to dash a stroke at all.

Examples:
> (parse-dasharray "5,10")

'(5 10)

> (parse-dasharray "5 10 15")

'(5 10 15 5 10 15)

> (parse-dasharray "none")

#f

> (parse-dasharray "-1,2")

#f

A solid line, and the same line dashed via (parse-dasharray "18,10") fed to dash-split-polyline below:

a solid horizontal line above a dashed horizontal line

procedure

(parse-linecap s)  (or/c 'round 'projecting 'butt)

  s : string?
Parses a stroke-linecap value ("round", "square", or "butt") into the symbol pen% expects — note that SVG’s "square" corresponds to racket/draw’s 'projecting, not a symbol named 'square. Anything other than "round" or "square" (including an absent/unrecognized value) gives 'butt, SVG’s default.

Butt, round, and square caps on three identical, otherwise-unstyled segments (the thin line below each shows where the segment’s true endpoints are):

three thick horizontal segments with butt, round, and square end caps respectively

procedure

(parse-linejoin s)  (or/c 'round 'bevel 'miter)

  s : string?
Parses a stroke-linejoin value ("round", "bevel", or "miter") into the symbol pen% expects. Anything other than "round" or "bevel" gives 'miter, SVG’s default. (There is no corresponding parse-stroke-miterlimit support: racket/draw’s pen% exposes no miter-limit control at all, so stroke-miterlimit has no effect anywhere in this library regardless of value.)

Miter, round, and bevel joins on the same corner:

three identical sharp corners with miter (pointed), round, and bevel (flat-cut) joins respectively

procedure

(parse-clip-rule s)  (or/c 'odd-even 'winding)

  s : (or/c string? #f)
Parses a fill-rule/clip-rule value into the symbol dc-path%’s own fill-rule argument expects: "evenodd" gives 'odd-even; anything else (including "nonzero" and an absent value) gives 'winding, the SVG default.

Two overlapping circles, filled as one path, under 'winding (left: both circles fully filled, since the overlap is still covered at least once in the same winding direction) versus 'odd-even (right: the overlap is left unfilled, since it’s covered an even number of times):

two overlapping circles, fully filled as one solid blob the same two overlapping circles, with the overlapping region unfilled, showing as a hole

procedure

(dc-path->polylines p [curve-samples])

  (listof (listof (cons/c real? real?)))
  p : (is-a?/c dc-path%)
  curve-samples : exact-positive-integer? = 16
Flattens p however it was built (line-to, curve-to, the native .arc, or .ellipse/.rounded-rectangle) — into one polyline per subpath, each a list of (x . y) points. Works uniformly across every shape because dc-path%’s own get-datum turns out to represent all of them, once built, using only two segment shapes — a bare waypoint or a 6-element cubic Bézier control-point vector — so curved segments are sampled at curve-samples evenly-spaced points along the Bézier (higher values give a smoother approximation at the cost of more points).

A closed subpath’s polyline always ends by repeating its own first point (closing the loop explicitly), even if the underlying path segments didn’t already return to their exact starting coordinates.

Example:
> (dc-path->polylines (car (path-data->dc-paths "M0,0 L50,0 L25,50 Z")))

'(((0.0 . 0.0) (50.0 . 0.0) (25.0 . 50.0) (0.0 . 0.0)))

A curve (light gray) with its flattened sample points overlaid as dots:

a smooth curve with a dozen evenly-spaced red dots marking its polyline approximation

procedure

(dash-split-polyline pts pattern offset)

  (listof (listof (cons/c real? real?)))
  pts : (listof (cons/c real? real?))
  pattern : (listof (and/c real? (not/c negative?)))
  offset : real?
Splits polyline pts into the "on" sub-polylines a dash pattern (an even-length list of alternating on/off lengths, as parse-dasharray produces) would draw, starting offset units into the pattern — the stroke-dashoffset value, which may be negative or larger than the pattern’s total length (both wrap correctly). Returns '() if pts has fewer than two points (nothing to dash).

Example:
> (dash-split-polyline '((0 . 0) (100 . 0)) '(10 5) 0)

'(((0 . 0) (10.0 . 0))

  ((15.0 . 0) (25.0 . 0))

  ((30.0 . 0) (40.0 . 0))

  ((45.0 . 0) (55.00000000000001 . 0))

  ((60.0 . 0) (70.0 . 0))

  ((75.0 . 0) (85.0 . 0))

  ((90.0 . 0) (100.0 . 0)))

See parse-dasharray above for this function’s own visual example — the two are almost always used together.